Added LineImpactProjectile.

This commit is contained in:
UberWaffe
2014-08-13 21:30:43 +02:00
parent a34b0be873
commit f7f0dde4bb
4 changed files with 332 additions and 0 deletions

View File

@@ -40,6 +40,20 @@ namespace OpenRA
/// </summary>
public static WPos Lerp(WPos a, WPos b, int mul, int div) { return a + (b - a) * mul / div; }
/// <summary>
/// Returns the linear interpolation between points 'a' and 'b'
/// </summary>
public static WPos Lerp(WPos a, WPos b, long mul, long div)
{
// The intermediate variables may need more precision than
// an int can provide, so we can't use WPos.
var x = (int)(a.X + (b.X - a.X) * mul / div);
var y = (int)(a.Y + (b.Y - a.Y) * mul / div);
var z = (int)(a.Z + (b.Z - a.Z) * mul / div);
return new WPos(x, y, z);
}
public static WPos LerpQuadratic(WPos a, WPos b, WAngle pitch, int mul, int div)
{
// Start with a linear lerp between the points

View File

@@ -0,0 +1,217 @@
#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.Drawing;
using System.Linq;
using OpenRA.Effects;
using OpenRA.GameRules;
using OpenRA.Graphics;
using OpenRA.Mods.Common.Graphics;
using OpenRA.Mods.Common.Traits;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Effects
{
public class AreaBeamInfo : IProjectileInfo
{
[Desc("Projectile speed in WDist / tick, two values indicate a randomly picked velocity per beam.")]
public readonly WDist[] Speed = { new WDist(128) };
[Desc("The maximum duration (in ticks) of each beam burst.")]
public readonly int Duration = 10;
[Desc("The number of ticks between the beam causing warhead impacts in its area of effect.")]
public readonly int DamageInterval = 3;
[Desc("The width of the beam.")]
public readonly WDist Width = new WDist(512);
[Desc("How far beyond the target the projectile keeps on travelling.")]
public readonly WDist BeyondTargetRange = new WDist(0);
[Desc("Damage modifier applied at each range step.")]
public readonly int[] Falloff = { 100, 100 };
[Desc("Ranges at which each Falloff step is defined.")]
public readonly WDist[] Range = { WDist.Zero, new WDist(int.MaxValue) };
[Desc("Maximum offset at the maximum range.")]
public readonly WDist Inaccuracy = WDist.Zero;
[Desc("Can this projectile be blocked when hitting actors with an IBlocksProjectiles trait.")]
public readonly bool Blockable = false;
[Desc("Extra search radius beyond beam width. Required to ensure affecting actors with large health radius.")]
public readonly WDist TargetExtraSearchRadius = new WDist(2048);
[Desc("Should the beam be visuall rendered? False = Beam is invisible.")]
public readonly bool RenderBeam = true;
[Desc("Color of the beam.")]
public readonly Color Color = Color.Red;
[Desc("Beam color is the player's color.")]
public readonly bool UsePlayerColor = false;
public IEffect Create(ProjectileArgs args)
{
var c = UsePlayerColor ? args.SourceActor.Owner.Color.RGB : Color;
return new AreaBeam(this, args, c);
}
}
public class AreaBeam : IEffect, ISync
{
readonly AreaBeamInfo info;
readonly ProjectileArgs args;
readonly AttackBase actorAttackBase;
readonly Color color;
readonly WDist speed;
[Sync] WPos headPos;
[Sync] WPos tailPos;
[Sync] WPos target;
int length;
int towardsTargetFacing;
int headTicks;
int tailTicks;
bool isHeadTravelling = true;
bool isTailTravelling;
bool IsBeamComplete { get { return !isHeadTravelling && headTicks >= length &&
!isTailTravelling && tailTicks >= length; } }
public AreaBeam(AreaBeamInfo info, ProjectileArgs args, Color color)
{
this.info = info;
this.args = args;
this.color = color;
actorAttackBase = args.SourceActor.Trait<AttackBase>();
var world = args.SourceActor.World;
if (info.Speed.Length > 1)
speed = new WDist(world.SharedRandom.Next(info.Speed[0].Length, info.Speed[1].Length));
else
speed = info.Speed[0];
// Both the head and tail start at the source actor, but initially only the head is travelling.
headPos = args.Source;
tailPos = headPos;
target = args.PassiveTarget;
if (info.Inaccuracy.Length > 0)
{
var inaccuracy = OpenRA.Traits.Util.ApplyPercentageModifiers(info.Inaccuracy.Length, args.InaccuracyModifiers);
var maxOffset = inaccuracy * (target - headPos).Length / args.Weapon.Range.Length;
target += WVec.FromPDF(world.SharedRandom, 2) * maxOffset / 1024;
}
towardsTargetFacing = OpenRA.Traits.Util.GetFacing(target - headPos, 0);
// Update the target position with the range we shoot beyond the target by
// I.e. we can deliberately overshoot, so aim for that position
var dir = new WVec(0, -1024, 0).Rotate(WRot.FromFacing(towardsTargetFacing));
target += dir * info.BeyondTargetRange.Length / 1024;
length = Math.Max((target - headPos).Length / speed.Length, 1);
}
public void Tick(World world)
{
if (++headTicks >= length)
{
headPos = target;
isHeadTravelling = false;
}
else if (isHeadTravelling)
headPos = WPos.LerpQuadratic(args.Source, target, WAngle.Zero, headTicks, length);
if (tailTicks <= 0 && args.SourceActor.IsInWorld && !args.SourceActor.IsDead)
{
args.Source = args.CurrentSource();
tailPos = args.Source;
}
// Allow for 1 cell (1024) leniency to avoid edge case stuttering (start firing and immediately stop again).
var outOfWeaponRange = args.Weapon.Range.Length + 1024 < (args.PassiveTarget - args.Source).Length;
// While the head is travelling, the tail must start to follow Duration ticks later.
// Alternatively, also stop emitting the beam if source actor dies or is ordered to stop.
if ((headTicks >= info.Duration && !isTailTravelling) || args.SourceActor.IsDead ||
!actorAttackBase.IsAttacking || outOfWeaponRange)
isTailTravelling = true;
if (isTailTravelling)
{
if (++tailTicks >= length)
{
tailPos = target;
isTailTravelling = false;
}
else
tailPos = WPos.LerpQuadratic(args.Source, target, WAngle.Zero, tailTicks, length);
}
// Check for blocking actors
WPos blockedPos;
if (info.Blockable && BlocksProjectiles.AnyBlockingActorsBetween(world, tailPos, headPos,
info.Width, info.TargetExtraSearchRadius, out blockedPos))
{
headPos = blockedPos;
target = headPos;
length = Math.Min(headTicks, length);
}
// Damage is applied to intersected actors every DamageInterval ticks
if (headTicks % info.DamageInterval == 0)
{
var actors = world.FindActorsOnLine(tailPos, headPos, info.Width, info.TargetExtraSearchRadius);
foreach (var a in actors)
{
var adjustedModifiers = args.DamageModifiers.Append(GetFalloff((args.Source - a.CenterPosition).Length));
args.Weapon.Impact(Target.FromActor(a), args.SourceActor, adjustedModifiers);
}
}
if (IsBeamComplete)
world.AddFrameEndTask(w => w.Remove(this));
}
public IEnumerable<IRenderable> Render(WorldRenderer wr)
{
if (!IsBeamComplete && info.RenderBeam && !(wr.World.FogObscures(tailPos) && wr.World.FogObscures(headPos)))
{
float width, y, z;
wr.ScreenVectorComponents(new WVec(info.Width, WDist.Zero, WDist.Zero), out width, out y, out z);
var beamRender = new BeamRenderable(headPos, 0, tailPos - headPos, width, color);
return new[] { (IRenderable)beamRender };
}
return SpriteRenderable.None;
}
int GetFalloff(int distance)
{
var inner = info.Range[0].Length;
for (var i = 1; i < info.Range.Length; i++)
{
var outer = info.Range[i].Length;
if (outer > distance)
return int2.Lerp(info.Falloff[i - 1], info.Falloff[i], distance - inner, outer - inner);
inner = outer;
}
return 0;
}
}
}

View File

@@ -157,6 +157,7 @@
<Compile Include="Effects\FloatingText.cs" />
<Compile Include="Effects\GravityBomb.cs" />
<Compile Include="Effects\LaserZap.cs" />
<Compile Include="Effects\AreaBeam.cs" />
<Compile Include="Effects\Missile.cs" />
<Compile Include="Effects\NukeLaunch.cs" />
<Compile Include="Effects\PowerdownIndicator.cs" />
@@ -533,6 +534,7 @@
<Compile Include="UtilityCommands\ExtractLanguageStringsCommand.cs" />
<Compile Include="UtilityCommands\ExtractLuaDocsCommand.cs" />
<Compile Include="UtilityCommands\ExtractTraitDocsCommand.cs" />
<Compile Include="WorldExtensions.cs" />
<Compile Include="UtilityCommands\GenerateMinimapCommand.cs" />
<Compile Include="UtilityCommands\GetMapHashCommand.cs" />
<Compile Include="UtilityCommands\Glob.cs" />

View File

@@ -0,0 +1,99 @@
#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 OpenRA.Mods.Common.Traits;
namespace OpenRA.Mods.Common
{
public static class WorldExtensions
{
/// <summary>
/// Finds all the actors of which their health radius is intersected by a line (with a definable width) between two points.
/// </summary>
/// <param name="world">The engine world the line intersection is to be done in.</param>
/// <param name="lineStart">The position the line should start at</param>
/// <param name="lineEnd">The position the line should end at</param>
/// <param name="lineWidth">How close an actor's health radius needs to be to the line to be considered 'intersected' by the line</param>
/// <returns>A list of all the actors intersected by the line</returns>
public static IEnumerable<Actor> FindActorsOnLine(this World world, WPos lineStart, WPos lineEnd, WDist lineWidth, WDist targetExtraSearchRadius)
{
// This line intersection check is done by first just finding all actors within a square that starts at the source, and ends at the target.
// Then we iterate over this list, and find all actors for which their health radius is at least within lineWidth of the line.
// For actors without a health radius, we simply check their center point.
// The square in which we select all actors must be large enough to encompass the entire line's width.
var xDir = Math.Sign(lineEnd.X - lineStart.X);
var yDir = Math.Sign(lineEnd.Y - lineStart.Y);
var dir = new WVec(xDir, yDir, 0);
var overselect = dir * (1024 + lineWidth.Length + targetExtraSearchRadius.Length);
var finalTarget = lineEnd + overselect;
var finalSource = lineStart - overselect;
var actorsInSquare = world.ActorMap.ActorsInBox(finalTarget, finalSource);
var intersectedActors = new List<Actor>();
foreach (var currActor in actorsInSquare)
{
var actorWidth = 0;
var healthInfo = currActor.Info.TraitInfoOrDefault<HealthInfo>();
if (healthInfo != null)
actorWidth = healthInfo.Radius.Length;
var projection = MinimumPointLineProjection(lineStart, lineEnd, currActor.CenterPosition);
var distance = (currActor.CenterPosition - projection).HorizontalLength;
var maxReach = actorWidth + lineWidth.Length;
if (distance <= maxReach)
intersectedActors.Add(currActor);
}
return intersectedActors;
}
/// <summary>
/// Find the point (D) on a line (A-B) that is closest to the target point (C).
/// </summary>
/// <param name="lineStart">The source point (tail) of the line</param>
/// <param name="lineEnd">The target point (head) of the line</param>
/// <param name="point">The target point that the minimum distance should be found to</param>
/// <returns>The WPos that is the point on the line that is closest to the target point</returns>
public static WPos MinimumPointLineProjection(WPos lineStart, WPos lineEnd, WPos point)
{
var squaredLength = (lineEnd - lineStart).HorizontalLengthSquared;
// Line has zero length, so just use the lineEnd position as the closest position.
if (squaredLength == 0)
return lineEnd;
// Consider the line extending the segment, parameterized as target + t (source - target).
// We find projection of point onto the line.
// It falls where t = [(point - target) . (source - target)] / |source - target|^2
// The normal DotProduct math would be (xDiff + yDiff) / dist, where dist = (target - source).LengthSquared;
// But in order to avoid floating points, we do not divide here, but rather work with the large numbers as far as possible.
// We then later divide by dist, only AFTER we have multiplied by the dotproduct.
var xDiff = ((long)point.X - lineEnd.X) * (lineStart.X - lineEnd.X);
var yDiff = ((long)point.Y - lineEnd.Y) * (lineStart.Y - lineEnd.Y);
var t = xDiff + yDiff;
// Beyond the 'target' end of the segment
if (t < 0)
return lineEnd;
// Beyond the 'source' end of the segment
if (t > squaredLength)
return lineStart;
// Projection falls on the segment
return WPos.Lerp(lineEnd, lineStart, t, squaredLength);
}
}
}