Initial commit: OpenRA game engine
Fork from OpenRA/OpenRA with one-click launch script (start-ra.cmd) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
57
OpenRA.Mods.Common/Warheads/ChangeOwnerWarhead.cs
Normal file
57
OpenRA.Mods.Common/Warheads/ChangeOwnerWarhead.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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 OpenRA.GameRules;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Warheads
|
||||
{
|
||||
[Desc("Interacts with the `" + nameof(TemporaryOwnerManager) + "` trait.")]
|
||||
public class ChangeOwnerWarhead : Warhead
|
||||
{
|
||||
[Desc("Duration of the owner change (in ticks). Set to 0 to make it permanent.")]
|
||||
public readonly int Duration = 0;
|
||||
|
||||
public readonly WDist Range = WDist.FromCells(1);
|
||||
|
||||
public override void DoImpact(in Target target, WarheadArgs args)
|
||||
{
|
||||
var firedBy = args.SourceActor;
|
||||
var actors = target.Type == TargetType.Actor ? [target.Actor] :
|
||||
firedBy.World.FindActorsInCircle(target.CenterPosition, Range);
|
||||
|
||||
foreach (var a in actors)
|
||||
{
|
||||
if (!IsValidAgainst(a, firedBy))
|
||||
continue;
|
||||
|
||||
// Don't do anything on friendly fire
|
||||
if (a.Owner == firedBy.Owner)
|
||||
continue;
|
||||
|
||||
if (Duration == 0)
|
||||
a.ChangeOwner(firedBy.Owner); // Permanent
|
||||
else
|
||||
{
|
||||
var tempOwnerManager = a.TraitOrDefault<TemporaryOwnerManager>();
|
||||
if (tempOwnerManager == null)
|
||||
continue;
|
||||
|
||||
tempOwnerManager.ChangeOwner(a, firedBy.Owner, Duration);
|
||||
}
|
||||
|
||||
// Stop shooting, you have new enemies
|
||||
a.CancelActivity();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
149
OpenRA.Mods.Common/Warheads/CreateEffectWarhead.cs
Normal file
149
OpenRA.Mods.Common/Warheads/CreateEffectWarhead.cs
Normal file
@@ -0,0 +1,149 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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.Immutable;
|
||||
using System.Linq;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Mods.Common.Effects;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Warheads
|
||||
{
|
||||
[Desc("Spawn a sprite with sound.")]
|
||||
public class CreateEffectWarhead : Warhead
|
||||
{
|
||||
[SequenceReference(nameof(Image), allowNullImage: true)]
|
||||
[Desc("List of explosion sequences that can be used.")]
|
||||
public readonly ImmutableArray<string> Explosions = [];
|
||||
|
||||
[Desc("Image containing explosion effect sequence.")]
|
||||
public readonly string Image = "explosion";
|
||||
|
||||
[PaletteReference(nameof(UsePlayerPalette))]
|
||||
[Desc("Palette to use for explosion effect.")]
|
||||
public readonly string ExplosionPalette = "effect";
|
||||
|
||||
[Desc("Remap explosion effect to player color, if art supports it.")]
|
||||
public readonly bool UsePlayerPalette = false;
|
||||
|
||||
[Desc("Display explosion effect at ground level, regardless of explosion altitude.")]
|
||||
public readonly bool ForceDisplayAtGroundLevel = false;
|
||||
|
||||
[Desc("List of sounds that can be played on impact.")]
|
||||
public readonly ImmutableArray<string> ImpactSounds = [];
|
||||
|
||||
[Desc("Chance of impact sound to play.")]
|
||||
public readonly int ImpactSoundChance = 100;
|
||||
|
||||
[Desc("Whether to consider actors in determining whether the explosion should happen. If false, only terrain will be considered.")]
|
||||
public readonly bool ImpactActors = true;
|
||||
|
||||
[Desc("The maximum inaccuracy of the effect spawn position relative to actual impact position.")]
|
||||
public readonly WDist Inaccuracy = WDist.Zero;
|
||||
|
||||
static readonly BitSet<TargetableType> TargetTypeAir = new("Air");
|
||||
|
||||
/// <summary>Checks if there are any actors at impact position and if the warhead is valid against any of them.</summary>
|
||||
ImpactActorType ActorTypeAtImpact(World world, WPos pos, Actor firedBy)
|
||||
{
|
||||
var anyInvalidActor = false;
|
||||
|
||||
// Check whether the impact position overlaps with an actor's hitshape
|
||||
foreach (var victim in world.FindActorsOnCircle(pos, WDist.Zero))
|
||||
{
|
||||
if (!AffectsParent && victim == firedBy)
|
||||
continue;
|
||||
|
||||
var activeShapes = victim.TraitsImplementing<HitShape>().Where(t => !t.IsTraitDisabled);
|
||||
if (!activeShapes.Any(s => s.DistanceFromEdge(victim, pos).Length <= 0))
|
||||
continue;
|
||||
|
||||
if (IsValidAgainst(victim, firedBy))
|
||||
return ImpactActorType.Valid;
|
||||
|
||||
anyInvalidActor = true;
|
||||
}
|
||||
|
||||
return anyInvalidActor ? ImpactActorType.Invalid : ImpactActorType.None;
|
||||
}
|
||||
|
||||
// ActorTypeAtImpact already checks AffectsParent beforehand, to avoid parent HitShape look-ups
|
||||
// (and to prevent returning ImpactActorType.Invalid on AffectsParent=false)
|
||||
public override bool IsValidAgainst(Actor victim, Actor firedBy)
|
||||
{
|
||||
var relationship = firedBy.Owner.RelationshipWith(victim.Owner);
|
||||
if (!ValidRelationships.HasRelationship(relationship))
|
||||
return false;
|
||||
|
||||
// A target type is valid if it is in the valid targets list, and not in the invalid targets list.
|
||||
if (!IsValidTarget(victim.GetEnabledTargetTypes()))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void DoImpact(in Target target, WarheadArgs args)
|
||||
{
|
||||
if (target.Type == TargetType.Invalid)
|
||||
return;
|
||||
|
||||
var firedBy = args.SourceActor;
|
||||
var pos = target.CenterPosition;
|
||||
var world = firedBy.World;
|
||||
var actorAtImpact = ImpactActors ? ActorTypeAtImpact(world, pos, firedBy) : ImpactActorType.None;
|
||||
|
||||
// Ignore the impact if there are only invalid actors within range
|
||||
if (actorAtImpact == ImpactActorType.Invalid)
|
||||
return;
|
||||
|
||||
// Ignore the impact if there are no valid actors and no valid terrain
|
||||
// (impacts are allowed on valid actors sitting on invalid terrain!)
|
||||
if (actorAtImpact == ImpactActorType.None && !IsValidAgainstTerrain(world, pos))
|
||||
return;
|
||||
|
||||
var explosion = Explosions.RandomOrDefault(world.LocalRandom);
|
||||
if (Image != null && explosion != null)
|
||||
{
|
||||
if (Inaccuracy.Length > 0)
|
||||
pos += WVec.FromPDF(world.SharedRandom, 2) * Inaccuracy.Length / 1024;
|
||||
|
||||
if (ForceDisplayAtGroundLevel)
|
||||
{
|
||||
var dat = world.Map.DistanceAboveTerrain(pos);
|
||||
pos -= new WVec(0, 0, dat.Length);
|
||||
}
|
||||
|
||||
var palette = ExplosionPalette;
|
||||
if (UsePlayerPalette)
|
||||
palette += firedBy.Owner.InternalName;
|
||||
|
||||
world.AddFrameEndTask(w => w.Add(new SpriteEffect(pos, w, Image, explosion, palette)));
|
||||
}
|
||||
|
||||
var impactSound = ImpactSounds.RandomOrDefault(world.LocalRandom);
|
||||
if (impactSound != null && world.LocalRandom.Next(0, 100) < ImpactSoundChance)
|
||||
Game.Sound.Play(SoundType.World, impactSound, pos);
|
||||
}
|
||||
|
||||
/// <summary>Checks if the warhead is valid against the terrain at impact position.</summary>
|
||||
bool IsValidAgainstTerrain(World world, WPos pos)
|
||||
{
|
||||
var cell = world.Map.CellContaining(pos);
|
||||
if (!world.Map.Contains(cell))
|
||||
return false;
|
||||
|
||||
var dat = world.Map.DistanceAboveTerrain(pos);
|
||||
return IsValidTarget(dat > AirThreshold ? TargetTypeAir : world.Map.GetTerrainInfo(cell).TargetTypes);
|
||||
}
|
||||
}
|
||||
}
|
||||
59
OpenRA.Mods.Common/Warheads/CreateResourceWarhead.cs
Normal file
59
OpenRA.Mods.Common/Warheads/CreateResourceWarhead.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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.Immutable;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Warheads
|
||||
{
|
||||
[Desc("Creates resources in a circle.")]
|
||||
public class CreateResourceWarhead : Warhead
|
||||
{
|
||||
[Desc("Size of the area. The resources are seeded within this area.", "Provide 2 values for a ring effect (outer/inner).")]
|
||||
public readonly ImmutableArray<int> Size = [0, 0];
|
||||
|
||||
[Desc("Will this splatter resources and which?")]
|
||||
[FieldLoader.Require]
|
||||
public readonly string AddsResourceType = null;
|
||||
|
||||
// TODO: Allow maximum resource splatter to be defined. (Per tile, and in total).
|
||||
public override void DoImpact(in Target target, WarheadArgs args)
|
||||
{
|
||||
if (target.Type == TargetType.Invalid)
|
||||
return;
|
||||
|
||||
var firedBy = args.SourceActor;
|
||||
var pos = target.CenterPosition;
|
||||
var world = firedBy.World;
|
||||
var dat = world.Map.DistanceAboveTerrain(pos);
|
||||
if (dat > AirThreshold)
|
||||
return;
|
||||
|
||||
var targetTile = world.Map.CellContaining(pos);
|
||||
|
||||
var minRange = (Size.Length > 1 && Size[1] > 0) ? Size[1] : 0;
|
||||
var allCells = world.Map.FindTilesInAnnulus(targetTile, minRange, Size[0]);
|
||||
|
||||
var resourceLayer = world.WorldActor.Trait<IResourceLayer>();
|
||||
var maxDensity = resourceLayer.GetMaxDensity(AddsResourceType);
|
||||
foreach (var cell in allCells)
|
||||
{
|
||||
if (!resourceLayer.CanAddResource(AddsResourceType, cell))
|
||||
continue;
|
||||
|
||||
var splash = (byte)world.SharedRandom.Next(1, maxDensity - resourceLayer.GetResource(cell).Density);
|
||||
resourceLayer.AddResource(AddsResourceType, cell, splash);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
94
OpenRA.Mods.Common/Warheads/DamageWarhead.cs
Normal file
94
OpenRA.Mods.Common/Warheads/DamageWarhead.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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.Frozen;
|
||||
using System.Linq;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Warheads
|
||||
{
|
||||
public abstract class DamageWarhead : Warhead
|
||||
{
|
||||
[Desc("How much (raw) damage to deal.")]
|
||||
public readonly int Damage = 0;
|
||||
|
||||
[Desc("Types of damage that this warhead causes. Leave empty for no damage types.")]
|
||||
public readonly BitSet<DamageType> DamageTypes = default;
|
||||
|
||||
[Desc("Damage percentage versus each armor type.")]
|
||||
public readonly FrozenDictionary<string, int> Versus = FrozenDictionary<string, int>.Empty;
|
||||
|
||||
public override bool IsValidAgainst(Actor victim, Actor firedBy)
|
||||
{
|
||||
// Cannot be damaged without a Health trait
|
||||
if (!victim.Info.HasTraitInfo<IHealthInfo>())
|
||||
return false;
|
||||
|
||||
return base.IsValidAgainst(victim, firedBy);
|
||||
}
|
||||
|
||||
public override void DoImpact(in Target target, WarheadArgs args)
|
||||
{
|
||||
var firedBy = args.SourceActor;
|
||||
|
||||
// Used by traits or warheads that damage a single actor, rather than a position
|
||||
if (target.Type == TargetType.Actor)
|
||||
{
|
||||
var victim = target.Actor;
|
||||
|
||||
if (!IsValidAgainst(victim, firedBy))
|
||||
return;
|
||||
|
||||
// PERF: Avoid using TraitsImplementing<HitShape> that needs to find the actor in the trait dictionary.
|
||||
var closestActiveShape = (HitShape)victim.EnabledTargetablePositions.MinByOrDefault(t =>
|
||||
{
|
||||
if (t is HitShape h)
|
||||
return h.DistanceFromEdge(victim, victim.CenterPosition);
|
||||
else
|
||||
return WDist.MaxValue;
|
||||
});
|
||||
|
||||
// Cannot be damaged without an active HitShape
|
||||
if (closestActiveShape == null)
|
||||
return;
|
||||
|
||||
InflictDamage(victim, firedBy, closestActiveShape, args);
|
||||
}
|
||||
else if (target.Type != TargetType.Invalid)
|
||||
DoImpact(target.CenterPosition, firedBy, args);
|
||||
}
|
||||
|
||||
protected virtual int DamageVersus(Actor victim, HitShape shape, WarheadArgs args)
|
||||
{
|
||||
// If no Versus values are defined, DamageVersus would return 100 anyway, so we might as well do that early.
|
||||
if (Versus.Count == 0)
|
||||
return 100;
|
||||
|
||||
var armor = victim.TraitsImplementing<Armor>()
|
||||
.Where(a => !a.IsTraitDisabled && a.Info.Type != null && Versus.ContainsKey(a.Info.Type) &&
|
||||
(shape.Info.ArmorTypes.IsEmpty || shape.Info.ArmorTypes.Contains(a.Info.Type)))
|
||||
.Select(a => Versus[a.Info.Type]);
|
||||
|
||||
return Util.ApplyPercentageModifiers(100, armor);
|
||||
}
|
||||
|
||||
protected virtual void InflictDamage(Actor victim, Actor firedBy, HitShape shape, WarheadArgs args)
|
||||
{
|
||||
var damage = Util.ApplyPercentageModifiers(Damage, args.DamageModifiers.Append(DamageVersus(victim, shape, args)));
|
||||
victim.InflictDamage(firedBy, new Damage(damage, DamageTypes));
|
||||
}
|
||||
|
||||
protected abstract void DoImpact(WPos pos, Actor firedBy, WarheadArgs args);
|
||||
}
|
||||
}
|
||||
66
OpenRA.Mods.Common/Warheads/DestroyResourceWarhead.cs
Normal file
66
OpenRA.Mods.Common/Warheads/DestroyResourceWarhead.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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.Frozen;
|
||||
using System.Collections.Immutable;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Warheads
|
||||
{
|
||||
[Desc("Destroys resources in a circle.")]
|
||||
public class DestroyResourceWarhead : Warhead
|
||||
{
|
||||
[Desc("Size of the area. The resources are removed within this area.", "Provide 2 values for a ring effect (outer/inner).")]
|
||||
public readonly ImmutableArray<int> Size = [0, 0];
|
||||
|
||||
[Desc("Amount of resources to be removed. If zero, all resources within the area will be removed.")]
|
||||
public readonly byte ResourceAmount = 0;
|
||||
|
||||
[Desc("Resource types to remove with this warhead.", "If empty, all resource types will be removed.")]
|
||||
public readonly FrozenSet<string> ResourceTypes = FrozenSet<string>.Empty;
|
||||
|
||||
public override void DoImpact(in Target target, WarheadArgs args)
|
||||
{
|
||||
if (target.Type == TargetType.Invalid)
|
||||
return;
|
||||
|
||||
var firedBy = args.SourceActor;
|
||||
var pos = target.CenterPosition;
|
||||
var world = firedBy.World;
|
||||
var dat = world.Map.DistanceAboveTerrain(pos);
|
||||
if (dat > AirThreshold)
|
||||
return;
|
||||
|
||||
var targetTile = world.Map.CellContaining(pos);
|
||||
var resourceLayer = world.WorldActor.Trait<IResourceLayer>();
|
||||
|
||||
var minRange = (Size.Length > 1 && Size[1] > 0) ? Size[1] : 0;
|
||||
var allCells = world.Map.FindTilesInAnnulus(targetTile, minRange, Size[0]);
|
||||
|
||||
var removeAllTypes = ResourceTypes.Count == 0;
|
||||
|
||||
foreach (var cell in allCells)
|
||||
{
|
||||
var cellContents = resourceLayer.GetResource(cell);
|
||||
|
||||
if (removeAllTypes || ResourceTypes.Contains(cellContents.Type))
|
||||
{
|
||||
if (ResourceAmount <= 0)
|
||||
resourceLayer.ClearResources(cell);
|
||||
else
|
||||
resourceLayer.RemoveResource(cellContents.Type, cell, ResourceAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
118
OpenRA.Mods.Common/Warheads/FireClusterWarhead.cs
Normal file
118
OpenRA.Mods.Common/Warheads/FireClusterWarhead.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Warheads
|
||||
{
|
||||
[Desc("Fires weapons from the point of impact.")]
|
||||
public class FireClusterWarhead : Warhead, IRulesetLoaded<WeaponInfo>
|
||||
{
|
||||
[WeaponReference]
|
||||
[FieldLoader.Require]
|
||||
[Desc("Has to be defined in weapons.yaml as well.")]
|
||||
public readonly string Weapon = null;
|
||||
|
||||
[Desc("Number of weapons fired at random 'x' cells. Negative values will result in a number equal to 'x' footprint cells fired.")]
|
||||
public readonly int RandomClusterCount = -1;
|
||||
|
||||
[FieldLoader.Require]
|
||||
[Desc("Size of the cluster footprint")]
|
||||
public readonly CVec Dimensions = CVec.Zero;
|
||||
|
||||
[FieldLoader.Require]
|
||||
[Desc("Cluster footprint. Cells marked as X will be attacked.",
|
||||
"Cells marked as x will be attacked randomly until RandomClusterCount is reached.")]
|
||||
public readonly string Footprint = string.Empty;
|
||||
|
||||
WeaponInfo weapon;
|
||||
|
||||
public void RulesetLoaded(Ruleset rules, WeaponInfo info)
|
||||
{
|
||||
if (!rules.Weapons.TryGetValue(Weapon.ToLowerInvariant(), out weapon))
|
||||
throw new YamlException($"Weapons Ruleset does not contain an entry '{Weapon.ToLowerInvariant()}'");
|
||||
}
|
||||
|
||||
public override void DoImpact(in Target target, WarheadArgs args)
|
||||
{
|
||||
if (target.Type == TargetType.Invalid)
|
||||
return;
|
||||
|
||||
var firedBy = args.SourceActor;
|
||||
var map = firedBy.World.Map;
|
||||
var targetCell = map.CellContaining(target.CenterPosition);
|
||||
|
||||
var targetCells = CellsMatching(targetCell, false);
|
||||
foreach (var c in targetCells)
|
||||
FireProjectileAtCell(map, firedBy, target, c, args);
|
||||
|
||||
if (RandomClusterCount != 0)
|
||||
{
|
||||
var randomTargetCells = CellsMatching(targetCell, true).ToList();
|
||||
var clusterCount = RandomClusterCount < 0 ? randomTargetCells.Count : RandomClusterCount;
|
||||
if (randomTargetCells.Count != 0)
|
||||
for (var i = 0; i < clusterCount; i++)
|
||||
FireProjectileAtCell(map, firedBy, target, randomTargetCells.Random(firedBy.World.SharedRandom), args);
|
||||
}
|
||||
}
|
||||
|
||||
void FireProjectileAtCell(Map map, Actor firedBy, Target target, CPos targetCell, WarheadArgs args)
|
||||
{
|
||||
var tc = Target.FromCell(firedBy.World, targetCell);
|
||||
|
||||
if (!weapon.IsValidAgainst(tc, firedBy.World, firedBy))
|
||||
return;
|
||||
|
||||
var projectileArgs = new ProjectileArgs
|
||||
{
|
||||
Weapon = weapon,
|
||||
Facing = (map.CenterOfCell(targetCell) - target.CenterPosition).Yaw,
|
||||
CurrentMuzzleFacing = () => (map.CenterOfCell(targetCell) - target.CenterPosition).Yaw,
|
||||
|
||||
DamageModifiers = args.DamageModifiers,
|
||||
InaccuracyModifiers = [],
|
||||
RangeModifiers = [],
|
||||
|
||||
Source = target.CenterPosition,
|
||||
CurrentSource = () => target.CenterPosition,
|
||||
SourceActor = firedBy,
|
||||
PassiveTarget = map.CenterOfCell(targetCell),
|
||||
GuidedTarget = tc
|
||||
};
|
||||
|
||||
if (projectileArgs.Weapon.Projectile != null)
|
||||
{
|
||||
var projectile = projectileArgs.Weapon.Projectile.Create(projectileArgs);
|
||||
if (projectile != null)
|
||||
firedBy.World.AddFrameEndTask(w => w.Add(projectile));
|
||||
|
||||
if (projectileArgs.Weapon.Report != null && projectileArgs.Weapon.Report.Length > 0)
|
||||
Game.Sound.Play(SoundType.World, projectileArgs.Weapon.Report, firedBy.World, target.CenterPosition);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<CPos> CellsMatching(CPos location, bool random)
|
||||
{
|
||||
var cellType = !random ? 'X' : 'x';
|
||||
var index = 0;
|
||||
var footprint = Footprint.Where(c => !char.IsWhiteSpace(c)).ToArray();
|
||||
var x = location.X - (Dimensions.X - 1) / 2;
|
||||
var y = location.Y - (Dimensions.Y - 1) / 2;
|
||||
for (var j = 0; j < Dimensions.Y; j++)
|
||||
for (var i = 0; i < Dimensions.X; i++)
|
||||
if (footprint[index++] == cellType)
|
||||
yield return new CPos(x + i, y + j);
|
||||
}
|
||||
}
|
||||
}
|
||||
35
OpenRA.Mods.Common/Warheads/FlashEffectWarhead.cs
Normal file
35
OpenRA.Mods.Common/Warheads/FlashEffectWarhead.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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 OpenRA.GameRules;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Warheads
|
||||
{
|
||||
[Desc("Used to trigger a FlashPostProcessEffect trait on the world actor.")]
|
||||
public class FlashEffectWarhead : Warhead
|
||||
{
|
||||
[Desc("Corresponds to `Type` from `FlashPostProcessEffect` on the world actor.")]
|
||||
public readonly string FlashType = null;
|
||||
|
||||
[FieldLoader.Require]
|
||||
[Desc("Duration of the flashing, measured in ticks. Set to -1 to default to the `Length` of the `FlashPostProcessEffect`.")]
|
||||
public readonly int Duration = 0;
|
||||
|
||||
public override void DoImpact(in Target target, WarheadArgs args)
|
||||
{
|
||||
foreach (var flash in args.SourceActor.World.WorldActor.TraitsImplementing<FlashPostProcessEffect>())
|
||||
if (flash.Info.Type == FlashType)
|
||||
flash.Enable(Duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
OpenRA.Mods.Common/Warheads/FlashTargetsInRadiusWarhead.cs
Normal file
60
OpenRA.Mods.Common/Warheads/FlashTargetsInRadiusWarhead.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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 OpenRA.GameRules;
|
||||
using OpenRA.Mods.Common.Effects;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Warheads
|
||||
{
|
||||
[Desc("Trigger a flash effect on the targeted actor, or actors within a circle.")]
|
||||
public class FlashTargetsInRadiusWarhead : Warhead
|
||||
{
|
||||
[Desc("The overlay color to display when ActorFlashType is Overlay.")]
|
||||
public readonly Color ActorFlashOverlayColor = Color.White;
|
||||
|
||||
[Desc("The overlay transparency to display when ActorFlashType is Overlay.")]
|
||||
public readonly float ActorFlashOverlayAlpha = 0.5f;
|
||||
|
||||
[Desc("The tint to apply when ActorFlashType is Tint.")]
|
||||
public readonly float3 ActorFlashTint = new(1.4f, 1.4f, 1.4f);
|
||||
|
||||
[Desc("Number of times to flash actors.")]
|
||||
public readonly int ActorFlashCount = 2;
|
||||
|
||||
[Desc("Number of ticks between actor flashes.")]
|
||||
public readonly int ActorFlashInterval = 2;
|
||||
|
||||
[Desc("Radius of an area at which effect will be applied. If left default effect applies only to target actor.")]
|
||||
public readonly WDist Radius = new(0);
|
||||
|
||||
[Desc("Controls the way damage is calculated. Possible values are 'HitShape', 'ClosestTargetablePosition' and 'CenterPosition'.")]
|
||||
public readonly DamageCalculationType DamageCalculationType = DamageCalculationType.HitShape;
|
||||
|
||||
public override void DoImpact(in Target target, WarheadArgs args)
|
||||
{
|
||||
var targetActor = target.Actor;
|
||||
var firedBy = args.SourceActor;
|
||||
var victims = Radius == WDist.Zero && targetActor != null ? [targetActor] : firedBy.World.FindActorsInCircle(target.CenterPosition, Radius);
|
||||
|
||||
foreach (var victim in victims)
|
||||
{
|
||||
if (!IsValidAgainst(victim, firedBy))
|
||||
continue;
|
||||
|
||||
victim.World.AddFrameEndTask(w => w.Add(new FlashTarget(
|
||||
victim, ActorFlashOverlayColor, ActorFlashOverlayAlpha,
|
||||
ActorFlashCount, ActorFlashInterval)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
52
OpenRA.Mods.Common/Warheads/GrantExternalConditionWarhead.cs
Normal file
52
OpenRA.Mods.Common/Warheads/GrantExternalConditionWarhead.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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.Linq;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Warheads
|
||||
{
|
||||
[Desc("Grant an external condition to hit actors.")]
|
||||
public class GrantExternalConditionWarhead : Warhead
|
||||
{
|
||||
[FieldLoader.Require]
|
||||
[Desc("The condition to apply. Must be included in the target actor's ExternalConditions list.")]
|
||||
public readonly string Condition = null;
|
||||
|
||||
[Desc("Duration of the condition (in ticks). Set to 0 for a permanent condition.")]
|
||||
public readonly int Duration = 0;
|
||||
|
||||
public readonly WDist Range = WDist.FromCells(1);
|
||||
|
||||
public override void DoImpact(in Target target, WarheadArgs args)
|
||||
{
|
||||
var firedBy = args.SourceActor;
|
||||
|
||||
if (target.Type == TargetType.Invalid)
|
||||
return;
|
||||
|
||||
var actors = target.Type == TargetType.Actor ? [target.Actor] :
|
||||
firedBy.World.FindActorsInCircle(target.CenterPosition, Range);
|
||||
|
||||
foreach (var a in actors)
|
||||
{
|
||||
if (!IsValidAgainst(a, firedBy))
|
||||
continue;
|
||||
|
||||
a.TraitsImplementing<ExternalCondition>()
|
||||
.FirstOrDefault(t => t.Info.Condition == Condition && t.CanGrantCondition(firedBy))
|
||||
?.GrantCondition(a, firedBy, Duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
28
OpenRA.Mods.Common/Warheads/HealthPercentageDamageWarhead.cs
Normal file
28
OpenRA.Mods.Common/Warheads/HealthPercentageDamageWarhead.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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 OpenRA.GameRules;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Warheads
|
||||
{
|
||||
[Desc("Apply damage based on the target's health.")]
|
||||
public class HealthPercentageDamageWarhead : TargetDamageWarhead
|
||||
{
|
||||
protected override void InflictDamage(Actor victim, Actor firedBy, HitShape shape, WarheadArgs args)
|
||||
{
|
||||
var healthInfo = victim.Info.TraitInfo<HealthInfo>();
|
||||
var damage = Util.ApplyPercentageModifiers(healthInfo.HP, args.DamageModifiers.Append(Damage, DamageVersus(victim, shape, args)));
|
||||
victim.InflictDamage(firedBy, new Damage(damage, DamageTypes));
|
||||
}
|
||||
}
|
||||
}
|
||||
75
OpenRA.Mods.Common/Warheads/LeaveSmudgeWarhead.cs
Normal file
75
OpenRA.Mods.Common/Warheads/LeaveSmudgeWarhead.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Warheads
|
||||
{
|
||||
[Desc("Creates a smudge in `SmudgeLayer`.")]
|
||||
public class LeaveSmudgeWarhead : Warhead
|
||||
{
|
||||
[Desc("Size of the area. A smudge will be created in each tile.", "Provide 2 values for a ring effect (outer/inner).")]
|
||||
public readonly ImmutableArray<int> Size = [0, 0];
|
||||
|
||||
[Desc("Type of smudge to apply to terrain.")]
|
||||
public readonly FrozenSet<string> SmudgeType = FrozenSet<string>.Empty;
|
||||
|
||||
[Desc("Percentage chance the smudge is created.")]
|
||||
public readonly int Chance = 100;
|
||||
|
||||
public override void DoImpact(in Target target, WarheadArgs args)
|
||||
{
|
||||
if (target.Type == TargetType.Invalid)
|
||||
return;
|
||||
|
||||
var firedBy = args.SourceActor;
|
||||
var world = firedBy.World;
|
||||
|
||||
if (Chance < world.LocalRandom.Next(100))
|
||||
return;
|
||||
|
||||
var pos = target.CenterPosition;
|
||||
var dat = world.Map.DistanceAboveTerrain(pos);
|
||||
|
||||
if (dat > AirThreshold)
|
||||
return;
|
||||
|
||||
var targetTile = world.Map.CellContaining(pos);
|
||||
var smudgeLayers = world.WorldActor.TraitsImplementing<SmudgeLayer>().ToDictionary(x => x.Info.Type);
|
||||
|
||||
var minRange = (Size.Length > 1 && Size[1] > 0) ? Size[1] : 0;
|
||||
var allCells = world.Map.FindTilesInAnnulus(targetTile, minRange, Size[0]);
|
||||
|
||||
// Draw the smudges:
|
||||
foreach (var sc in allCells)
|
||||
{
|
||||
var smudgeType = world.Map.GetTerrainInfo(sc).AcceptsSmudgeType.FirstOrDefault(SmudgeType.Contains);
|
||||
if (smudgeType == null)
|
||||
continue;
|
||||
|
||||
var cellActors = world.ActorMap.GetActorsAt(sc);
|
||||
if (cellActors.Any(a => !IsValidAgainst(a, firedBy)))
|
||||
continue;
|
||||
|
||||
if (!smudgeLayers.TryGetValue(smudgeType, out var smudgeLayer))
|
||||
throw new NotImplementedException($"Unknown smudge type `{smudgeType}`");
|
||||
|
||||
smudgeLayer.AddSmudge(sc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
34
OpenRA.Mods.Common/Warheads/ShakeScreenWarhead.cs
Normal file
34
OpenRA.Mods.Common/Warheads/ShakeScreenWarhead.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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 OpenRA.GameRules;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Warheads
|
||||
{
|
||||
[Desc("Makes the screen shake.")]
|
||||
public class ShakeScreenWarhead : Warhead
|
||||
{
|
||||
[Desc("Duration of the shaking.")]
|
||||
public readonly int Duration = 0;
|
||||
|
||||
[Desc("Shake intensity.")]
|
||||
public readonly int Intensity = 0;
|
||||
|
||||
[Desc("Shake multipliers by the X and Y axis, comma-separated.")]
|
||||
public readonly float2 Multiplier = new(0, 0);
|
||||
|
||||
public override void DoImpact(in Target target, WarheadArgs args)
|
||||
{
|
||||
args.SourceActor.World.WorldActor.Trait<ScreenShaker>().AddEffect(Duration, target.CenterPosition, Intensity, Multiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
143
OpenRA.Mods.Common/Warheads/SpreadDamageWarhead.cs
Normal file
143
OpenRA.Mods.Common/Warheads/SpreadDamageWarhead.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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.Immutable;
|
||||
using System.Linq;
|
||||
using OpenRA.GameRules;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Warheads
|
||||
{
|
||||
public enum DamageCalculationType { HitShape, ClosestTargetablePosition, CenterPosition }
|
||||
|
||||
[Desc("Apply damage in a specified range.")]
|
||||
public class SpreadDamageWarhead : DamageWarhead, IRulesetLoaded<WeaponInfo>
|
||||
{
|
||||
[Desc("Range between falloff steps.")]
|
||||
public readonly WDist Spread = new(43);
|
||||
|
||||
[Desc("Damage percentage at each range step")]
|
||||
public readonly ImmutableArray<int> Falloff = [100, 37, 14, 5, 0];
|
||||
|
||||
[Desc("Ranges at which each Falloff step is defined. Overrides Spread.")]
|
||||
public readonly ImmutableArray<WDist> Range = default;
|
||||
|
||||
[Desc("Controls the way damage is calculated. Possible values are 'HitShape', 'ClosestTargetablePosition' and 'CenterPosition'.")]
|
||||
public readonly DamageCalculationType DamageCalculationType = DamageCalculationType.HitShape;
|
||||
|
||||
ImmutableArray<WDist> effectiveRange;
|
||||
|
||||
void IRulesetLoaded<WeaponInfo>.RulesetLoaded(Ruleset rules, WeaponInfo info)
|
||||
{
|
||||
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.");
|
||||
|
||||
effectiveRange = Range;
|
||||
}
|
||||
else
|
||||
effectiveRange = Exts.MakeArray(Falloff.Length, i => i * Spread).ToImmutableArray();
|
||||
}
|
||||
|
||||
protected override void DoImpact(WPos pos, Actor firedBy, WarheadArgs args)
|
||||
{
|
||||
var debugVis = firedBy.World.WorldActor.TraitOrDefault<DebugVisualizations>();
|
||||
if (debugVis != null && debugVis.CombatGeometry)
|
||||
firedBy.World.WorldActor.Trait<WarheadDebugOverlay>().AddImpact(pos, effectiveRange, DebugOverlayColor);
|
||||
|
||||
foreach (var victim in firedBy.World.FindActorsOnCircle(pos, effectiveRange[^1]))
|
||||
{
|
||||
if (!IsValidAgainst(victim, firedBy))
|
||||
continue;
|
||||
|
||||
HitShape closestActiveShape = null;
|
||||
var closestDistance = int.MaxValue;
|
||||
|
||||
// PERF: Avoid using TraitsImplementing<HitShape> that needs to find the actor in the trait dictionary.
|
||||
foreach (var targetPos in victim.EnabledTargetablePositions)
|
||||
{
|
||||
if (targetPos is HitShape h)
|
||||
{
|
||||
var distance = h.DistanceFromEdge(victim, pos).Length;
|
||||
if (distance < closestDistance)
|
||||
{
|
||||
closestDistance = distance;
|
||||
closestActiveShape = h;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cannot be damaged without an active HitShape.
|
||||
if (closestActiveShape == null)
|
||||
continue;
|
||||
|
||||
var falloffDistance = 0;
|
||||
switch (DamageCalculationType)
|
||||
{
|
||||
case DamageCalculationType.HitShape:
|
||||
falloffDistance = closestDistance;
|
||||
break;
|
||||
case DamageCalculationType.ClosestTargetablePosition:
|
||||
falloffDistance = victim.GetTargetablePositions().Min(x => (x - pos).Length);
|
||||
break;
|
||||
case DamageCalculationType.CenterPosition:
|
||||
falloffDistance = (victim.CenterPosition - pos).Length;
|
||||
break;
|
||||
}
|
||||
|
||||
// The range to target is more than the range the warhead covers, so GetDamageFalloff() is going to give us 0 and we're going to do 0 damage anyway, so bail early.
|
||||
if (falloffDistance > effectiveRange[^1].Length)
|
||||
continue;
|
||||
|
||||
var localModifiers = args.DamageModifiers.Append(GetDamageFalloff(falloffDistance));
|
||||
var impactOrientation = args.ImpactOrientation;
|
||||
|
||||
// If a warhead lands outside the victim's HitShape, we need to calculate the vertical and horizontal impact angles
|
||||
// from impact position, rather than last projectile facing/angle.
|
||||
if (falloffDistance > 0)
|
||||
{
|
||||
var towardsTargetYaw = (victim.CenterPosition - args.ImpactPosition).Yaw;
|
||||
var impactAngle = Util.GetVerticalAngle(args.ImpactPosition, victim.CenterPosition);
|
||||
impactOrientation = new WRot(WAngle.Zero, impactAngle, towardsTargetYaw);
|
||||
}
|
||||
|
||||
var updatedWarheadArgs = new WarheadArgs(args)
|
||||
{
|
||||
DamageModifiers = localModifiers.ToArray(),
|
||||
ImpactOrientation = impactOrientation,
|
||||
};
|
||||
|
||||
InflictDamage(victim, firedBy, closestActiveShape, updatedWarheadArgs);
|
||||
}
|
||||
}
|
||||
|
||||
int GetDamageFalloff(int distance)
|
||||
{
|
||||
var inner = effectiveRange[0].Length;
|
||||
for (var i = 1; i < effectiveRange.Length; i++)
|
||||
{
|
||||
var outer = effectiveRange[i].Length;
|
||||
if (outer > distance)
|
||||
return int2.Lerp(Falloff[i - 1], Falloff[i], distance - inner, outer - inner);
|
||||
|
||||
inner = outer;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
67
OpenRA.Mods.Common/Warheads/TargetDamageWarhead.cs
Normal file
67
OpenRA.Mods.Common/Warheads/TargetDamageWarhead.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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 OpenRA.GameRules;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Warheads
|
||||
{
|
||||
[Desc("Apply damage to the targeted actor.")]
|
||||
public class TargetDamageWarhead : DamageWarhead
|
||||
{
|
||||
[Desc("Damage will be applied to actors in this area. A value of zero means only targeted actor will be damaged.")]
|
||||
public readonly WDist Spread = WDist.Zero;
|
||||
|
||||
protected override void DoImpact(WPos pos, Actor firedBy, WarheadArgs args)
|
||||
{
|
||||
if (Spread == WDist.Zero)
|
||||
return;
|
||||
|
||||
var debugVis = firedBy.World.WorldActor.TraitOrDefault<DebugVisualizations>();
|
||||
if (debugVis != null && debugVis.CombatGeometry)
|
||||
firedBy.World.WorldActor.Trait<WarheadDebugOverlay>().AddImpact(pos, [WDist.Zero, Spread], DebugOverlayColor);
|
||||
|
||||
foreach (var victim in firedBy.World.FindActorsOnCircle(pos, Spread))
|
||||
{
|
||||
if (!IsValidAgainst(victim, firedBy))
|
||||
continue;
|
||||
|
||||
HitShape closestActiveShape = null;
|
||||
var closestDistance = int.MaxValue;
|
||||
|
||||
// PERF: Avoid using TraitsImplementing<HitShape> that needs to find the actor in the trait dictionary.
|
||||
foreach (var targetPos in victim.EnabledTargetablePositions)
|
||||
{
|
||||
if (targetPos is HitShape hitshape)
|
||||
{
|
||||
var distance = hitshape.DistanceFromEdge(victim, pos).Length;
|
||||
if (distance < closestDistance)
|
||||
{
|
||||
closestDistance = distance;
|
||||
closestActiveShape = hitshape;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cannot be damaged without an active HitShape.
|
||||
if (closestActiveShape == null)
|
||||
continue;
|
||||
|
||||
// Cannot be damaged if HitShape is outside Spread.
|
||||
if (closestDistance > Spread.Length)
|
||||
continue;
|
||||
|
||||
InflictDamage(victim, firedBy, closestActiveShape, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
98
OpenRA.Mods.Common/Warheads/Warhead.cs
Normal file
98
OpenRA.Mods.Common/Warheads/Warhead.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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 OpenRA.GameRules;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Warheads
|
||||
{
|
||||
public enum ImpactActorType
|
||||
{
|
||||
None,
|
||||
Invalid,
|
||||
Valid,
|
||||
}
|
||||
|
||||
[Desc("Base warhead class. This can be used to derive other warheads from.")]
|
||||
public abstract class Warhead : IWarhead
|
||||
{
|
||||
[Desc("What types of targets are affected.")]
|
||||
public readonly BitSet<TargetableType> ValidTargets = new("Ground", "Water");
|
||||
|
||||
[Desc("What types of targets are unaffected.", "Overrules ValidTargets.")]
|
||||
public readonly BitSet<TargetableType> InvalidTargets;
|
||||
|
||||
[Desc("What player relationships are affected.")]
|
||||
public readonly PlayerRelationship ValidRelationships = PlayerRelationship.Ally | PlayerRelationship.Neutral | PlayerRelationship.Enemy;
|
||||
|
||||
[Desc("Can this warhead affect the actor that fired it.")]
|
||||
public readonly bool AffectsParent = false;
|
||||
|
||||
[Desc(
|
||||
"If impact is above this altitude, " +
|
||||
"warheads that would affect terrain ignore terrain target types " +
|
||||
"(and either do nothing or perform their own checks).")]
|
||||
public readonly WDist AirThreshold = new(128);
|
||||
|
||||
[Desc("Delay in ticks before applying the warhead effect.", "0 = instant (old model).")]
|
||||
public readonly int Delay = 0;
|
||||
|
||||
int IWarhead.Delay => Delay;
|
||||
|
||||
[Desc("The color used for this warhead's visualization in the world's `" + nameof(WarheadDebugOverlay) + "` trait.")]
|
||||
public readonly Color DebugOverlayColor = Color.Red;
|
||||
|
||||
protected bool IsValidTarget(BitSet<TargetableType> targetTypes)
|
||||
{
|
||||
return ValidTargets.Overlaps(targetTypes) && !InvalidTargets.Overlaps(targetTypes);
|
||||
}
|
||||
|
||||
/// <summary>Applies the warhead's effect against the target.</summary>
|
||||
public abstract void DoImpact(in Target target, WarheadArgs args);
|
||||
|
||||
/// <summary>Checks if the warhead is valid against (can do something to) the actor.</summary>
|
||||
public virtual bool IsValidAgainst(Actor victim, Actor firedBy)
|
||||
{
|
||||
if (!AffectsParent && victim == firedBy)
|
||||
return false;
|
||||
|
||||
var relationship = firedBy.Owner.RelationshipWith(victim.Owner);
|
||||
if (!ValidRelationships.HasRelationship(relationship))
|
||||
return false;
|
||||
|
||||
// A target type is valid if it is in the valid targets list, and not in the invalid targets list.
|
||||
if (!IsValidTarget(victim.GetEnabledTargetTypes()))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Checks if the warhead is valid against (can do something to) the frozen actor.</summary>
|
||||
public bool IsValidAgainst(FrozenActor victim, Actor firedBy)
|
||||
{
|
||||
if (!victim.IsValid)
|
||||
return false;
|
||||
|
||||
// AffectsParent checks do not make sense for FrozenActors, so skip to relationship checks
|
||||
var relationship = firedBy.Owner.RelationshipWith(victim.Owner);
|
||||
if (!ValidRelationships.HasRelationship(relationship))
|
||||
return false;
|
||||
|
||||
// A target type is valid if it is in the valid targets list, and not in the invalid targets list.
|
||||
if (!IsValidTarget(victim.TargetTypes))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user