Merge pull request #3592 from pchote/world-projectiles

Convert projectiles to world coordinates.
This commit is contained in:
Chris Forbes
2013-08-01 01:36:44 -07:00
28 changed files with 520 additions and 961 deletions

View File

@@ -67,6 +67,42 @@ namespace OpenRA
return new WAngle(Angle - 512).Tan();
}
public static WAngle ArcTan(int y, int x) { return ArcTan(y, x, 1); }
public static WAngle ArcTan(int y, int x, int stride)
{
if (y == 0)
return new WAngle(x >= 0 ? 0 : 512);
if (x == 0)
return new WAngle(Math.Sign(y)*256);
var ay = Math.Abs(y);
var ax = Math.Abs(x);
// Find the closest angle that satisfies y = x*tan(theta)
var bestVal = int.MaxValue;
var bestAngle = 0;
for (var i = 0; i < 256; i+= stride)
{
var val = Math.Abs(1024*ay - ax*TanTable[i]);
if (val < bestVal)
{
bestVal = val;
bestAngle = i;
}
}
// Calculate quadrant
if (x < 0 && y > 0)
bestAngle = 512 - bestAngle;
else if (x < 0 && y < 0)
bestAngle = 512 + bestAngle;
else if (x > 0 && y < 0)
bestAngle = 1024 - bestAngle;
return new WAngle(bestAngle);
}
// Must not be used outside rendering code
public float RendererRadians() { return (float)(Angle * Math.PI / 512f); }
public float RendererDegrees() { return Angle * 0.3515625f; }

View File

@@ -10,6 +10,7 @@
using System;
using System.Drawing;
using System.Linq;
namespace OpenRA
{
@@ -34,6 +35,17 @@ namespace OpenRA
public static bool operator ==(WRange me, WRange other) { return (me.Range == other.Range); }
public static bool operator !=(WRange me, WRange other) { return !(me == other); }
// Sampled a N-sample probability density function in the range [-1024..1024]
// 1 sample produces a rectangular probability
// 2 samples produces a triangular probability
// ...
// N samples approximates a true gaussian
public static WRange FromPDF(Thirdparty.Random r, int samples)
{
return new WRange(Exts.MakeArray(samples, _ => r.Next(-1024, 1024))
.Sum() / samples);
}
public static bool TryParse(string s, out WRange result)
{
s = s.ToLowerInvariant();

View File

@@ -69,6 +69,16 @@ namespace OpenRA
return new WVec(ret.X, ret.Y, ret.Z + offset);
}
// Sampled a N-sample probability density function in the range [-1024..1024, -1024..1024]
// 1 sample produces a rectangular probability
// 2 samples produces a triangular probability
// ...
// N samples approximates a true gaussian
public static WVec FromPDF(Thirdparty.Random r, int samples)
{
return new WVec(WRange.FromPDF(r, samples), WRange.FromPDF(r, samples), WRange.Zero);
}
public override int GetHashCode() { return X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode(); }
public override bool Equals(object obj)

View File

@@ -72,8 +72,9 @@ namespace OpenRA
public static CPos BottomRightAsCPos(this Rectangle r) { return new CPos(r.Right, r.Bottom); }
}
public static class WPosExtensions
public static class WorldCoordinateExtensions
{
public static CPos ToCPos(this WPos a) { return new CPos(a.X / 1024, a.Y / 1024); }
public static CVec ToCVec(this WVec a) { return new CVec(a.X / 1024, a.Y / 1024); }
}
}

View File

@@ -74,8 +74,6 @@ namespace OpenRA.GameRules
}
}
public enum DamageModel
{
Normal, // classic RA damage model: point actors, distance-based falloff
@@ -85,14 +83,12 @@ namespace OpenRA.GameRules
public class ProjectileArgs
{
public WeaponInfo weapon;
public Actor firedBy;
public PPos src;
public int srcAltitude;
public int facing;
public Target target;
public PPos dest;
public int destAltitude;
public float firepowerModifier = 1.0f;
public int facing;
public WPos source;
public Actor sourceActor;
public WPos passiveTarget;
public Target guidedTarget;
}
public interface IProjectileInfo { IEffect Create(ProjectileArgs args); }

View File

@@ -86,8 +86,6 @@
<Compile Include="ActorMap.cs" />
<Compile Include="ActorReference.cs" />
<Compile Include="Graphics\QuadRenderer.cs" />
<Compile Include="PSubVec.cs" />
<Compile Include="PSubPos.cs" />
<Compile Include="PVecInt.cs" />
<Compile Include="PPos.cs" />
<Compile Include="Download.cs" />

View File

@@ -77,7 +77,6 @@ namespace OpenRA
public float2 ToFloat2() { return new float2(X, Y); }
public int2 ToInt2() { return new int2(X, Y); }
public CPos ToCPos() { return new CPos((int)(1f / Game.CellSize * X), (int)(1f / Game.CellSize * Y)); }
public PSubPos ToPSubPos() { return new PSubPos(X * PSubPos.PerPx, Y * PSubPos.PerPx); }
public PPos Clamp(Rectangle r)
{

View File

@@ -1,72 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2011 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.Drawing;
namespace OpenRA
{
/// <summary>
/// Sub-pixel coordinate position in the world (very fine).
/// </summary>
public struct PSubPos
{
public readonly int X, Y;
public PSubPos(int x, int y) { X = x; Y = y; }
public const int PerPx = 1024;
public static readonly PSubPos Zero = new PSubPos(0, 0);
public static explicit operator PSubPos(int2 a) { return new PSubPos(a.X, a.Y); }
public static explicit operator PSubVec(PSubPos a) { return new PSubVec(a.X, a.Y); }
public static PSubPos operator +(PSubPos a, PSubVec b) { return new PSubPos(a.X + b.X, a.Y + b.Y); }
public static PSubVec operator -(PSubPos a, PSubPos b) { return new PSubVec(a.X - b.X, a.Y - b.Y); }
public static PSubPos operator -(PSubPos a, PSubVec b) { return new PSubPos(a.X - b.X, a.Y - b.Y); }
public static bool operator ==(PSubPos me, PSubPos other) { return (me.X == other.X && me.Y == other.Y); }
public static bool operator !=(PSubPos me, PSubPos other) { return !(me == other); }
public static PSubPos Max(PSubPos a, PSubPos b) { return new PSubPos(Math.Max(a.X, b.X), Math.Max(a.Y, b.Y)); }
public static PSubPos Min(PSubPos a, PSubPos b) { return new PSubPos(Math.Min(a.X, b.X), Math.Min(a.Y, b.Y)); }
public static PSubPos Lerp(PSubPos a, PSubPos b, int mul, int div)
{
return a + ((PSubVec)(b - a) * mul / div);
}
public float2 ToFloat2() { return new float2(X, Y); }
public int2 ToInt2() { return new int2(X, Y); }
public PPos ToPPos() { return new PPos(X / PerPx, Y / PerPx); }
public CPos ToCPos() { return ToPPos().ToCPos(); }
public PSubPos Clamp(Rectangle r)
{
return new PSubPos(Math.Min(r.Right, Math.Max(X, r.Left)),
Math.Min(r.Bottom, Math.Max(Y, r.Top)));
}
public override int GetHashCode() { return X.GetHashCode() ^ Y.GetHashCode(); }
public override bool Equals(object obj)
{
if (obj == null)
return false;
PSubPos o = (PSubPos)obj;
return o == this;
}
public override string ToString() { return "{0},{1}".F(X, Y); }
}
}

View File

@@ -1,98 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2011 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.Drawing;
namespace OpenRA
{
/// <summary>
/// Sub-pixel coordinate vector (very fine)
/// </summary>
public struct PSubVec
{
public readonly int X, Y;
public PSubVec(int x, int y) { X = x; Y = y; }
public PSubVec(Size p) { X = p.Width; Y = p.Height; }
public static readonly PSubVec Zero = new PSubVec(0, 0);
public static PSubVec OneCell { get { return new PSubVec(Game.CellSize, Game.CellSize); } }
public static explicit operator PSubVec(int2 a) { return new PSubVec(a.X, a.Y); }
public static explicit operator PSubVec(float2 a) { return new PSubVec((int)a.X, (int)a.Y); }
public static PSubVec FromRadius(int r) { return new PSubVec(r, r); }
public static PSubVec operator +(PSubVec a, PSubVec b) { return new PSubVec(a.X + b.X, a.Y + b.Y); }
public static PSubVec operator -(PSubVec a, PSubVec b) { return new PSubVec(a.X - b.X, a.Y - b.Y); }
public static PSubVec operator *(int a, PSubVec b) { return new PSubVec(a * b.X, a * b.Y); }
public static PSubVec operator *(PSubVec b, int a) { return new PSubVec(a * b.X, a * b.Y); }
public static PSubVec operator /(PSubVec a, int b) { return new PSubVec(a.X / b, a.Y / b); }
public static PSubVec operator -(PSubVec a) { return new PSubVec(-a.X, -a.Y); }
public static bool operator ==(PSubVec me, PSubVec other) { return (me.X == other.X && me.Y == other.Y); }
public static bool operator !=(PSubVec me, PSubVec other) { return !(me == other); }
public static PSubVec Max(PSubVec a, PSubVec b) { return new PSubVec(Math.Max(a.X, b.X), Math.Max(a.Y, b.Y)); }
public static PSubVec Min(PSubVec a, PSubVec b) { return new PSubVec(Math.Min(a.X, b.X), Math.Min(a.Y, b.Y)); }
public static int Dot(PSubVec a, PSubVec b) { return a.X * b.X + a.Y * b.Y; }
public PSubVec Sign() { return new PSubVec(Math.Sign(X), Math.Sign(Y)); }
public PSubVec Abs() { return new PSubVec(Math.Abs(X), Math.Abs(Y)); }
public int LengthSquared { get { return X * X + Y * Y; } }
public int Length { get { return (int)Math.Sqrt(LengthSquared); } }
public float2 ToFloat2() { return new float2(X, Y); }
public int2 ToInt2() { return new int2(X, Y); }
public PVecInt ToPVecInt() { return new PVecInt(X / PSubPos.PerPx, Y / PSubPos.PerPx); }
public PSubVec Clamp(Rectangle r)
{
return new PSubVec(
Math.Min(r.Right, Math.Max(X, r.Left)),
Math.Min(r.Bottom, Math.Max(Y, r.Top))
);
}
public override int GetHashCode() { return X.GetHashCode() ^ Y.GetHashCode(); }
public override bool Equals(object obj)
{
if (obj == null)
return false;
PSubVec o = (PSubVec)obj;
return o == this;
}
public override string ToString() { return "{0},{1}".F(X, Y); }
}
public static class PSubVecExtensions
{
/// <summary>
/// Scales the float2 vector up to a subpixel vector.
/// </summary>
/// <param name="vec"></param>
/// <returns></returns>
public static PSubVec ToPSubVec(this float2 vec)
{
return new PSubVec((int)(vec.X * PSubPos.PerPx), (int)(vec.Y * PSubPos.PerPx));
}
public static PSubVec ToPSubVec(this PVecInt vec)
{
return new PSubVec((vec.X * PSubPos.PerPx), (vec.Y * PSubPos.PerPx));
}
}
}

View File

@@ -37,8 +37,6 @@ namespace OpenRA
{typeof(CVec), ((Func<CVec, int>)hash_CVec).Method},
{typeof(PPos), ((Func<PPos, int>)hash_PPos).Method},
{typeof(PVecInt), ((Func<PVecInt, int>)hash_PVecInt).Method},
{typeof(PSubPos), ((Func<PSubPos, int>)hash_PSubPos).Method},
{typeof(PSubVec), ((Func<PSubVec, int>)hash_PSubVec).Method},
{typeof(WRange), ((Func<WRange, int>)hash<WRange>).Method},
{typeof(WPos), ((Func<WPos, int>)hash<WPos>).Method},
{typeof(WVec), ((Func<WVec, int>)hash<WVec>).Method},
@@ -126,16 +124,6 @@ namespace OpenRA
return ((i2.X * 5) ^ (i2.Y * 3)) / 4;
}
public static int hash_PSubPos(PSubPos i2)
{
return ((i2.X * 5) ^ (i2.Y * 3)) / 4;
}
public static int hash_PSubVec(PSubVec i2)
{
return ((i2.X * 5) ^ (i2.Y * 3)) / 4;
}
public static int hash_tdict(TypeDictionary d)
{
int ret = 0;

View File

@@ -25,7 +25,6 @@ namespace OpenRA.Traits
int generation;
public static Target FromPos(WPos p) { return new Target { pos = p, valid = true }; }
public static Target FromPos(PPos p) { return new Target { pos = p.ToWPos(0), valid = true }; }
public static Target FromCell(CPos c) { return new Target { pos = c.CenterPosition, valid = true }; }
public static Target FromOrder(Order o)
{
@@ -45,7 +44,6 @@ namespace OpenRA.Traits
}
public bool IsValid { get { return valid && (actor == null || (actor.IsInWorld && !actor.IsDead() && actor.Generation == generation)); } }
public PPos CenterLocation { get { return IsActor ? actor.CenterLocation : PPos.FromWPos(pos); } }
public Actor Actor { get { return IsActor ? actor : null; } }
// TODO: This should return true even if the actor is destroyed

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2011 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2013 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,
@@ -18,60 +18,41 @@ namespace OpenRA.Traits
{
public static class Util
{
public static int TickFacing( int facing, int desiredFacing, int rot )
public static int TickFacing(int facing, int desiredFacing, int rot)
{
var leftTurn = ( facing - desiredFacing ) & 0xFF;
var rightTurn = ( desiredFacing - facing ) & 0xFF;
if( Math.Min( leftTurn, rightTurn ) < rot )
var leftTurn = (facing - desiredFacing) & 0xFF;
var rightTurn = (desiredFacing - facing) & 0xFF;
if (Math.Min(leftTurn, rightTurn) < rot)
return desiredFacing & 0xFF;
else if( rightTurn < leftTurn )
return ( facing + rot ) & 0xFF;
else if (rightTurn < leftTurn)
return (facing + rot) & 0xFF;
else
return ( facing - rot ) & 0xFF;
return (facing - rot) & 0xFF;
}
public static int GetFacing(WVec d, int currentFacing)
{
return GetFacing(new int2(d.X, d.Y), currentFacing);
}
if (d.LengthSquared == 0)
return currentFacing;
public static int GetFacing(PVecInt d, int currentFacing)
{
return GetFacing(d.ToInt2(), currentFacing);
// OpenRA defines north as -y, so invert
var angle = WAngle.ArcTan(-d.Y, d.X, 4).Angle;
// Convert back to a facing
return (angle / 4 - 0x40) & 0xFF;
}
public static int GetFacing(CVec d, int currentFacing)
{
return GetFacing(d.ToInt2(), currentFacing);
return GetFacing(d.ToWVec(), currentFacing);
}
public static int GetFacing(int2 d, int currentFacing)
{
if (d == int2.Zero)
return currentFacing;
int highest = -1;
int highestDot = -1;
for( int i = 0 ; i < fvecs.Length ; i++ )
{
int dot = int2.Dot( fvecs[ i ], d );
if( dot > highestDot )
{
highestDot = dot;
highest = i;
}
}
return highest * 8;
}
public static int GetNearestFacing( int facing, int desiredFacing )
public static int GetNearestFacing(int facing, int desiredFacing)
{
var turn = desiredFacing - facing;
if( turn > 128 )
if (turn > 128)
turn -= 256;
if( turn < -128 )
if (turn < -128)
turn += 256;
return facing + turn;
@@ -84,35 +65,15 @@ namespace OpenRA.Traits
return a / step;
}
public static float2 RotateVectorByFacing(float2 v, int facing, float ecc)
{
var angle = (facing / 256f) * (2 * (float)Math.PI);
var sinAngle = (float)Math.Sin(angle);
var cosAngle = (float)Math.Cos(angle);
return new float2(
(cosAngle * v.X + sinAngle * v.Y),
ecc * (cosAngle * v.Y - sinAngle * v.X));
}
public static PPos CenterOfCell(CPos loc)
{
return loc.ToPPos() + new PVecInt(Game.CellSize / 2, Game.CellSize / 2);
}
public static WPos BetweenCells(CPos from, CPos to)
{
return WPos.Lerp(from.CenterPosition, to.CenterPosition, 1, 2);
}
public static int2 AsInt2(this int[] xs) { return new int2(xs[0], xs[1]); }
public static float2 RelOffset(this int[] offset) { return new float2(offset[0], offset[1]); }
public static float2 AbsOffset(this int[] offset) { return new float2(offset.ElementAtOrDefault(2), offset.ElementAtOrDefault(3)); }
public static Activity SequenceActivities(params Activity[] acts)
{
return acts.Reverse().Aggregate(
(next, a) => { a.Queue( next ); return a; });
(next, a) => { a.Queue(next); return a; });
}
public static Activity RunActivity(Actor self, Activity act)
@@ -130,14 +91,10 @@ namespace OpenRA.Traits
if (prev == act)
break;
}
return act;
}
public static Color ArrayToColor(int[] x) { return Color.FromArgb(x[0], x[1], x[2]); }
[Obsolete("Use ToCPos() method", true)]
public static int2 CellContaining(float2 pos) { return (1f / Game.CellSize * pos).ToInt2(); }
/* pretty crap */
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> ts, Thirdparty.Random random)
{
@@ -179,309 +136,12 @@ namespace OpenRA.Traits
{
var cells = target.IsActor
? target.Actor.OccupiesSpace.OccupiedCells().Select(c => c.First).ToArray()
: new CPos[] {};
: new CPos[] { };
if (cells.Length == 0)
cells = new CPos[] { target.CenterPosition.ToCPos() };
return Util.ExpandFootprint(cells, true);
}
static int2[] fvecs =
{
new int2( 0, -1331 ),
new int2( -199, -1305 ),
new int2( -391, -1229 ),
new int2( -568, -1106 ),
new int2( -724, -941 ),
new int2( -851, -739 ),
new int2( -946, -509 ),
new int2( -1004, -259 ),
new int2( -1024, 0 ),
new int2( -1004, 259 ),
new int2( -946, 509 ),
new int2( -851, 739 ),
new int2( -724, 941 ),
new int2( -568, 1106 ),
new int2( -391, 1229 ),
new int2( -199, 1305 ),
new int2( 0, 1331 ),
new int2( 199, 1305 ),
new int2( 391, 1229 ),
new int2( 568, 1106 ),
new int2( 724, 941 ),
new int2( 851, 739 ),
new int2( 946, 509 ),
new int2( 1004, 259 ),
new int2( 1024, 0 ),
new int2( 1004, -259 ),
new int2( 946, -509 ),
new int2( 851, -739 ),
new int2( 724, -941 ),
new int2( 568, -1106 ),
new int2( 391, -1229 ),
new int2( 199, -1305 )
};
public static readonly PSubVec[] SubPxVector =
{
new PSubVec( 0, 1024 ),
new PSubVec( 25, 1023 ),
new PSubVec( 50, 1022 ),
new PSubVec( 75, 1021 ),
new PSubVec( 100, 1019 ),
new PSubVec( 125, 1016 ),
new PSubVec( 150, 1012 ),
new PSubVec( 175, 1008 ),
new PSubVec( 199, 1004 ),
new PSubVec( 224, 999 ),
new PSubVec( 248, 993 ),
new PSubVec( 273, 986 ),
new PSubVec( 297, 979 ),
new PSubVec( 321, 972 ),
new PSubVec( 344, 964 ),
new PSubVec( 368, 955 ),
new PSubVec( 391, 946 ),
new PSubVec( 414, 936 ),
new PSubVec( 437, 925 ),
new PSubVec( 460, 914 ),
new PSubVec( 482, 903 ),
new PSubVec( 504, 890 ),
new PSubVec( 526, 878 ),
new PSubVec( 547, 865 ),
new PSubVec( 568, 851 ),
new PSubVec( 589, 837 ),
new PSubVec( 609, 822 ),
new PSubVec( 629, 807 ),
new PSubVec( 649, 791 ),
new PSubVec( 668, 775 ),
new PSubVec( 687, 758 ),
new PSubVec( 706, 741 ),
new PSubVec( 724, 724 ),
new PSubVec( 741, 706 ),
new PSubVec( 758, 687 ),
new PSubVec( 775, 668 ),
new PSubVec( 791, 649 ),
new PSubVec( 807, 629 ),
new PSubVec( 822, 609 ),
new PSubVec( 837, 589 ),
new PSubVec( 851, 568 ),
new PSubVec( 865, 547 ),
new PSubVec( 878, 526 ),
new PSubVec( 890, 504 ),
new PSubVec( 903, 482 ),
new PSubVec( 914, 460 ),
new PSubVec( 925, 437 ),
new PSubVec( 936, 414 ),
new PSubVec( 946, 391 ),
new PSubVec( 955, 368 ),
new PSubVec( 964, 344 ),
new PSubVec( 972, 321 ),
new PSubVec( 979, 297 ),
new PSubVec( 986, 273 ),
new PSubVec( 993, 248 ),
new PSubVec( 999, 224 ),
new PSubVec( 1004, 199 ),
new PSubVec( 1008, 175 ),
new PSubVec( 1012, 150 ),
new PSubVec( 1016, 125 ),
new PSubVec( 1019, 100 ),
new PSubVec( 1021, 75 ),
new PSubVec( 1022, 50 ),
new PSubVec( 1023, 25 ),
new PSubVec( 1024, 0 ),
new PSubVec( 1023, -25 ),
new PSubVec( 1022, -50 ),
new PSubVec( 1021, -75 ),
new PSubVec( 1019, -100 ),
new PSubVec( 1016, -125 ),
new PSubVec( 1012, -150 ),
new PSubVec( 1008, -175 ),
new PSubVec( 1004, -199 ),
new PSubVec( 999, -224 ),
new PSubVec( 993, -248 ),
new PSubVec( 986, -273 ),
new PSubVec( 979, -297 ),
new PSubVec( 972, -321 ),
new PSubVec( 964, -344 ),
new PSubVec( 955, -368 ),
new PSubVec( 946, -391 ),
new PSubVec( 936, -414 ),
new PSubVec( 925, -437 ),
new PSubVec( 914, -460 ),
new PSubVec( 903, -482 ),
new PSubVec( 890, -504 ),
new PSubVec( 878, -526 ),
new PSubVec( 865, -547 ),
new PSubVec( 851, -568 ),
new PSubVec( 837, -589 ),
new PSubVec( 822, -609 ),
new PSubVec( 807, -629 ),
new PSubVec( 791, -649 ),
new PSubVec( 775, -668 ),
new PSubVec( 758, -687 ),
new PSubVec( 741, -706 ),
new PSubVec( 724, -724 ),
new PSubVec( 706, -741 ),
new PSubVec( 687, -758 ),
new PSubVec( 668, -775 ),
new PSubVec( 649, -791 ),
new PSubVec( 629, -807 ),
new PSubVec( 609, -822 ),
new PSubVec( 589, -837 ),
new PSubVec( 568, -851 ),
new PSubVec( 547, -865 ),
new PSubVec( 526, -878 ),
new PSubVec( 504, -890 ),
new PSubVec( 482, -903 ),
new PSubVec( 460, -914 ),
new PSubVec( 437, -925 ),
new PSubVec( 414, -936 ),
new PSubVec( 391, -946 ),
new PSubVec( 368, -955 ),
new PSubVec( 344, -964 ),
new PSubVec( 321, -972 ),
new PSubVec( 297, -979 ),
new PSubVec( 273, -986 ),
new PSubVec( 248, -993 ),
new PSubVec( 224, -999 ),
new PSubVec( 199, -1004 ),
new PSubVec( 175, -1008 ),
new PSubVec( 150, -1012 ),
new PSubVec( 125, -1016 ),
new PSubVec( 100, -1019 ),
new PSubVec( 75, -1021 ),
new PSubVec( 50, -1022 ),
new PSubVec( 25, -1023 ),
new PSubVec( 0, -1024 ),
new PSubVec( -25, -1023 ),
new PSubVec( -50, -1022 ),
new PSubVec( -75, -1021 ),
new PSubVec( -100, -1019 ),
new PSubVec( -125, -1016 ),
new PSubVec( -150, -1012 ),
new PSubVec( -175, -1008 ),
new PSubVec( -199, -1004 ),
new PSubVec( -224, -999 ),
new PSubVec( -248, -993 ),
new PSubVec( -273, -986 ),
new PSubVec( -297, -979 ),
new PSubVec( -321, -972 ),
new PSubVec( -344, -964 ),
new PSubVec( -368, -955 ),
new PSubVec( -391, -946 ),
new PSubVec( -414, -936 ),
new PSubVec( -437, -925 ),
new PSubVec( -460, -914 ),
new PSubVec( -482, -903 ),
new PSubVec( -504, -890 ),
new PSubVec( -526, -878 ),
new PSubVec( -547, -865 ),
new PSubVec( -568, -851 ),
new PSubVec( -589, -837 ),
new PSubVec( -609, -822 ),
new PSubVec( -629, -807 ),
new PSubVec( -649, -791 ),
new PSubVec( -668, -775 ),
new PSubVec( -687, -758 ),
new PSubVec( -706, -741 ),
new PSubVec( -724, -724 ),
new PSubVec( -741, -706 ),
new PSubVec( -758, -687 ),
new PSubVec( -775, -668 ),
new PSubVec( -791, -649 ),
new PSubVec( -807, -629 ),
new PSubVec( -822, -609 ),
new PSubVec( -837, -589 ),
new PSubVec( -851, -568 ),
new PSubVec( -865, -547 ),
new PSubVec( -878, -526 ),
new PSubVec( -890, -504 ),
new PSubVec( -903, -482 ),
new PSubVec( -914, -460 ),
new PSubVec( -925, -437 ),
new PSubVec( -936, -414 ),
new PSubVec( -946, -391 ),
new PSubVec( -955, -368 ),
new PSubVec( -964, -344 ),
new PSubVec( -972, -321 ),
new PSubVec( -979, -297 ),
new PSubVec( -986, -273 ),
new PSubVec( -993, -248 ),
new PSubVec( -999, -224 ),
new PSubVec( -1004, -199 ),
new PSubVec( -1008, -175 ),
new PSubVec( -1012, -150 ),
new PSubVec( -1016, -125 ),
new PSubVec( -1019, -100 ),
new PSubVec( -1021, -75 ),
new PSubVec( -1022, -50 ),
new PSubVec( -1023, -25 ),
new PSubVec( -1024, 0 ),
new PSubVec( -1023, 25 ),
new PSubVec( -1022, 50 ),
new PSubVec( -1021, 75 ),
new PSubVec( -1019, 100 ),
new PSubVec( -1016, 125 ),
new PSubVec( -1012, 150 ),
new PSubVec( -1008, 175 ),
new PSubVec( -1004, 199 ),
new PSubVec( -999, 224 ),
new PSubVec( -993, 248 ),
new PSubVec( -986, 273 ),
new PSubVec( -979, 297 ),
new PSubVec( -972, 321 ),
new PSubVec( -964, 344 ),
new PSubVec( -955, 368 ),
new PSubVec( -946, 391 ),
new PSubVec( -936, 414 ),
new PSubVec( -925, 437 ),
new PSubVec( -914, 460 ),
new PSubVec( -903, 482 ),
new PSubVec( -890, 504 ),
new PSubVec( -878, 526 ),
new PSubVec( -865, 547 ),
new PSubVec( -851, 568 ),
new PSubVec( -837, 589 ),
new PSubVec( -822, 609 ),
new PSubVec( -807, 629 ),
new PSubVec( -791, 649 ),
new PSubVec( -775, 668 ),
new PSubVec( -758, 687 ),
new PSubVec( -741, 706 ),
new PSubVec( -724, 724 ),
new PSubVec( -706, 741 ),
new PSubVec( -687, 758 ),
new PSubVec( -668, 775 ),
new PSubVec( -649, 791 ),
new PSubVec( -629, 807 ),
new PSubVec( -609, 822 ),
new PSubVec( -589, 837 ),
new PSubVec( -568, 851 ),
new PSubVec( -547, 865 ),
new PSubVec( -526, 878 ),
new PSubVec( -504, 890 ),
new PSubVec( -482, 903 ),
new PSubVec( -460, 914 ),
new PSubVec( -437, 925 ),
new PSubVec( -414, 936 ),
new PSubVec( -391, 946 ),
new PSubVec( -368, 955 ),
new PSubVec( -344, 964 ),
new PSubVec( -321, 972 ),
new PSubVec( -297, 979 ),
new PSubVec( -273, 986 ),
new PSubVec( -248, 993 ),
new PSubVec( -224, 999 ),
new PSubVec( -199, 1004 ),
new PSubVec( -175, 1008 ),
new PSubVec( -150, 1012 ),
new PSubVec( -125, 1016 ),
new PSubVec( -100, 1019 ),
new PSubVec( -75, 1021 ),
new PSubVec( -50, 1022 ),
new PSubVec( -25, 1023 )
};
}
}

View File

@@ -138,19 +138,6 @@ namespace OpenRA
r.Next(w.Map.Bounds.Top, w.Map.Bounds.Bottom));
}
public static float Gauss1D(this Thirdparty.Random r, int samples)
{
return Exts.MakeArray(samples, _ => r.NextFloat() * 2 - 1f)
.Sum() / samples;
}
// Returns a random offset in the range [-1..1,-1..1] with a separable
// Gauss distribution with 'samples' values taken for each axis
public static float2 Gauss2D(this Thirdparty.Random r, int samples)
{
return new float2(Gauss1D(r, samples), Gauss1D(r, samples));
}
public static bool HasVoices(this Actor a)
{
var selectable = a.Info.Traits.GetOrDefault<SelectableInfo>();

View File

@@ -1,4 +1,4 @@
#region Copyright & License Information
#region Copyright & License Information
/*
* Copyright 2007-2011 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
@@ -121,26 +121,20 @@ namespace OpenRA.Mods.RA
var barrel = Barrels[Burst % Barrels.Length];
var muzzlePosition = self.CenterPosition + MuzzleOffset(self, barrel);
var legacyMuzzlePosition = PPos.FromWPos(muzzlePosition);
var legacyMuzzleAltitude = Game.CellSize*muzzlePosition.Z/1024;
var legacyFacing = MuzzleOrientation(self, barrel).Yaw.Angle / 4;
var args = new ProjectileArgs
{
weapon = Weapon,
firedBy = self,
target = target,
src = legacyMuzzlePosition,
srcAltitude = legacyMuzzleAltitude,
dest = PPos.FromWPos(target.CenterPosition),
destAltitude = target.CenterPosition.Z * Game.CellSize / 1024,
facing = legacyFacing,
firepowerModifier = self.TraitsImplementing<IFirepowerModifier>()
.Select(a => a.GetFirepowerModifier())
.Product()
.Product(),
source = muzzlePosition,
sourceActor = self,
passiveTarget = target.CenterPosition,
guidedTarget = target
};
attack.ScheduleDelayedAction(Info.FireDelay, () =>

View File

@@ -31,8 +31,8 @@ namespace OpenRA.Mods.RA
public void TickIdle(Actor self)
{
var target = (Util.SubPxVector[self.World.SharedRandom.Next(255)] * Info.MoveRadius).ToPVecInt().ToCVec() + self.Location;
self.Trait<AttackMove>().ResolveOrder(self, new Order("AttackMove", self, false) { TargetLocation = target });
var offset = new WVec(0, -1024*Info.MoveRadius, 0).Rotate(WRot.FromFacing(self.World.SharedRandom.Next(255))).ToCVec();
self.Trait<AttackMove>().ResolveOrder(self, new Order("AttackMove", self, false) { TargetLocation = self.Location + offset });
}
}
}

View File

@@ -56,17 +56,15 @@ namespace OpenRA.Mods.RA
var weapon = Rules.Weapons[info.Weapon.ToLowerInvariant()];
dropDelay = weapon.ROF;
var centerLocation = PPos.FromWPos(self.CenterPosition);
var altitude = self.CenterPosition.Z * Game.CellSize / 1024;
var pos = self.CenterPosition;
var args = new ProjectileArgs
{
srcAltitude = altitude,
destAltitude = 0,
src = centerLocation,
dest = centerLocation,
weapon = weapon,
facing = self.Trait<IFacing>().Facing,
firedBy = self,
weapon = weapon
source = pos,
sourceActor = self,
passiveTarget = pos - new WVec(0, 0, pos.Z)
};
self.World.Add(args.weapon.Projectile.Create(args));

View File

@@ -32,23 +32,21 @@ namespace OpenRA.Mods.RA
return null;
}
public static void DoImpact(WarheadInfo warhead, ProjectileArgs args)
public static void DoImpact(WPos pos, WarheadInfo warhead, WeaponInfo weapon, Actor firedBy, float firepowerModifier)
{
var world = args.firedBy.World;
var targetTile = args.dest.ToCPos();
var world = firedBy.World;
var targetTile = pos.ToCPos();
if (!world.Map.IsInMap(targetTile))
return;
var isWater = args.destAltitude == 0 && world.GetTerrainInfo(targetTile).IsWater;
var isWater = pos.Z == 0 && world.GetTerrainInfo(targetTile).IsWater;
var explosionType = isWater ? warhead.WaterExplosion : warhead.Explosion;
var dest = args.dest.ToWPos(args.destAltitude);
if (explosionType != null)
world.AddFrameEndTask(
w => w.Add(new Explosion(w, dest, explosionType)));
world.AddFrameEndTask(w => w.Add(new Explosion(w, pos, explosionType)));
Sound.Play(GetImpactSound(warhead, isWater), dest);
Sound.Play(GetImpactSound(warhead, isWater), pos);
var smudgeLayers = world.WorldActor.TraitsImplementing<SmudgeLayer>().ToDictionary(x => x.Info.Type);
@@ -106,12 +104,12 @@ namespace OpenRA.Mods.RA
{
var maxSpread = warhead.Spread * (float)Math.Log(Math.Abs(warhead.Damage), 2);
var range = new WRange((int)maxSpread * 1024 / Game.CellSize);
var hitActors = world.FindActorsInCircle(args.dest.ToWPos(0), range);
var hitActors = world.FindActorsInCircle(pos, range);
foreach (var victim in hitActors)
{
var damage = (int)GetDamageToInflict(victim, args, warhead, args.firepowerModifier);
victim.InflictDamage(args.firedBy, damage, warhead);
var damage = (int)GetDamageToInflict(pos, victim, warhead, weapon, firepowerModifier);
victim.InflictDamage(firedBy, damage, warhead);
}
} break;
@@ -119,23 +117,22 @@ namespace OpenRA.Mods.RA
{
foreach (var t in world.FindTilesInCircle(targetTile, warhead.Size[0]))
foreach (var unit in world.FindActorsInBox(t, t))
unit.InflictDamage(args.firedBy,
unit.InflictDamage(firedBy,
(int)(warhead.Damage * warhead.EffectivenessAgainst(unit)), warhead);
} break;
}
}
public static void DoImpacts(ProjectileArgs args)
public static void DoImpacts(WPos pos, Actor firedBy, WeaponInfo weapon, float damageModifier)
{
foreach (var warhead in args.weapon.Warheads)
foreach (var wh in weapon.Warheads)
{
// NOTE(jsd): Fixed access to modified closure bug!
var warheadClosed = warhead;
var warhead = wh;
Action a = () => DoImpact(pos, warhead, weapon, firedBy, damageModifier);
Action a = () => DoImpact(warheadClosed, args);
if (warhead.Delay > 0)
args.firedBy.World.AddFrameEndTask(
w => w.Add(new DelayedAction(warheadClosed.Delay, a)));
firedBy.World.AddFrameEndTask(
w => w.Add(new DelayedAction(warhead.Delay, a)));
else
a();
}
@@ -143,24 +140,11 @@ namespace OpenRA.Mods.RA
public static void DoExplosion(Actor attacker, string weapontype, WPos pos)
{
var pxPos = PPos.FromWPos(pos);
var altitude = pos.Z * Game.CellSize / 1024;
var args = new ProjectileArgs
{
src = pxPos,
dest = pxPos,
srcAltitude = altitude,
destAltitude = altitude,
firedBy = attacker,
target = Target.FromPos(pos),
weapon = Rules.Weapons[weapontype.ToLowerInvariant()],
facing = 0
};
var weapon = Rules.Weapons[weapontype.ToLowerInvariant()];
if (weapon.Report != null && weapon.Report.Any())
Sound.Play(weapon.Report.Random(attacker.World.SharedRandom), pos);
if (args.weapon.Report != null && args.weapon.Report.Any())
Sound.Play(args.weapon.Report.Random(attacker.World.SharedRandom), pos);
DoImpacts(args);
DoImpacts(pos, attacker, weapon, 1f);
}
static readonly float[] falloff =
@@ -177,16 +161,16 @@ namespace OpenRA.Mods.RA
return (falloff[u] * (1 - t)) + (falloff[u + 1] * t);
}
static float GetDamageToInflict(Actor target, ProjectileArgs args, WarheadInfo warhead, float modifier)
static float GetDamageToInflict(WPos pos, Actor target, WarheadInfo warhead, WeaponInfo weapon, float modifier)
{
// don't hit air units with splash from ground explosions, etc
if (!args.weapon.IsValidAgainst(target))
if (!weapon.IsValidAgainst(target))
return 0f;
var health = target.Info.Traits.GetOrDefault<HealthInfo>();
if( health == null ) return 0f;
var distance = (int)Math.Max(0, (target.CenterLocation - args.dest).Length - health.Radius);
var distance = (int)Math.Max(0, (target.CenterPosition - pos).Length * Game.CellSize / 1024 - health.Radius);
var falloff = (float)GetDamageFalloff(distance / warhead.Spread);
var rawDamage = (float)(warhead.Damage * modifier * falloff);
var multiplier = (float)warhead.EffectivenessAgainst(target);

View File

@@ -1,4 +1,4 @@
#region Copyright & License Information
#region Copyright & License Information
/*
* Copyright 2007-2011 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
@@ -8,6 +8,7 @@
*/
#endregion
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
@@ -28,163 +29,145 @@ namespace OpenRA.Mods.RA.Effects
public readonly string Image = null;
[Desc("Check for whether an actor with Wall: trait blocks fire")]
public readonly bool High = false;
public readonly int RangeLimit = 0;
public readonly int Arm = 0;
public readonly bool Shadow = false;
public readonly bool Proximity = false;
public readonly float Angle = 0;
public readonly int TrailInterval = 2;
public readonly int TrailDelay = 1;
public readonly int ContrailLength = 0;
public readonly Color ContrailColor = Color.White;
public readonly bool ContrailUsePlayerColor = false;
public readonly int ContrailDelay = 1;
public IEffect Create(ProjectileArgs args) { return new Bullet( this, args ); }
public IEffect Create(ProjectileArgs args) { return new Bullet(this, args); }
}
public class Bullet : IEffect
{
readonly BulletInfo Info;
readonly ProjectileArgs Args;
readonly BulletInfo info;
readonly ProjectileArgs args;
int t = 0;
ContrailRenderable trail;
Animation anim;
const int BaseBulletSpeed = 100; /* pixels / 40ms frame */
ContrailRenderable Trail;
WAngle angle;
WPos pos, target;
int length;
int facing;
int ticks, smokeTicks;
public Bullet(BulletInfo info, ProjectileArgs args)
{
Info = info;
Args = args;
this.info = info;
this.args = args;
this.pos = args.source;
// Convert ProjectileArg definitions to world coordinates
// TODO: Change the yaml definitions so we don't need this
var range = new WRange((int)(1024 * args.weapon.Range)); // Range in world units
var inaccuracy = new WRange((int)(info.Inaccuracy * 1024 / Game.CellSize)); // Offset in world units at max range
var speed = (int)(info.Speed * 4 * 1024 / (10 * Game.CellSize)); // Speed in world units per tick
angle = WAngle.ArcTan((int)(info.Angle * 4 * 1024), 1024); // Angle in world angle
target = args.passiveTarget;
if (info.Inaccuracy > 0)
{
var factor = ((Args.dest - Args.src).ToCVec().Length) / args.weapon.Range;
Args.dest += (PVecInt) (info.Inaccuracy * factor * args.firedBy.World.SharedRandom.Gauss2D(2)).ToInt2();
Log.Write("debug", "Bullet with Inaccuracy; factor: #{0}; Projectile dest: {1}", factor, Args.dest);
var maxOffset = inaccuracy.Range * (target - args.source).Length / range.Range;
target += WVec.FromPDF(args.sourceActor.World.SharedRandom, 2) * maxOffset / 1024;
}
if (Info.Image != null)
facing = Traits.Util.GetFacing(target - args.source, 0);
length = Math.Max((target - args.source).Length / speed, 1);
if (info.Image != null)
{
anim = new Animation(Info.Image, GetEffectiveFacing);
anim = new Animation(info.Image, GetEffectiveFacing);
anim.PlayRepeating("idle");
}
if (Info.ContrailLength > 0)
if (info.ContrailLength > 0)
{
var color = Info.ContrailUsePlayerColor ? ContrailRenderable.ChooseColor(args.firedBy) : Info.ContrailColor;
Trail = new ContrailRenderable(args.firedBy.World, color, Info.ContrailLength, Info.ContrailDelay, 0);
var color = info.ContrailUsePlayerColor ? ContrailRenderable.ChooseColor(args.sourceActor) : info.ContrailColor;
trail = new ContrailRenderable(args.sourceActor.World, color, info.ContrailLength, info.ContrailDelay, 0);
}
}
int TotalTime() { return (Args.dest - Args.src).Length * BaseBulletSpeed / Info.Speed; }
float GetAltitude()
{
var at = (float)t / TotalTime();
return (Args.dest - Args.src).Length * Info.Angle * 4 * at * (1 - at);
smokeTicks = info.TrailDelay;
}
int GetEffectiveFacing()
{
var at = (float)t / TotalTime();
var attitude = Info.Angle * (1 - 2 * at);
var at = (float)ticks / (length - 1);
var attitude = angle.Tan() * (1 - 2 * at) / (4 * 1024);
var rawFacing = Traits.Util.GetFacing(Args.dest - Args.src, 0);
var u = (rawFacing % 128) / 128f;
var u = (facing % 128) / 128f;
var scale = 512 * u * (1 - u);
return (int)(rawFacing < 128
? rawFacing - scale * attitude
: rawFacing + scale * attitude);
return (int)(facing < 128
? facing - scale * attitude
: facing + scale * attitude);
}
int ticksToNextSmoke;
public void Tick( World world )
public void Tick(World world)
{
t += 40;
if (anim != null) anim.Tick();
if (t > TotalTime()) Explode( world );
// Fade the trail out gradually
if (ticks > length && info.ContrailLength > 0)
{
var at = (float)t / TotalTime();
var altitude = float2.Lerp(Args.srcAltitude, Args.destAltitude, at);
var pos = float2.Lerp(Args.src.ToFloat2(), Args.dest.ToFloat2(), at) - new float2(0, altitude);
var highPos = (Info.High || Info.Angle > 0)
? (pos - new float2(0, GetAltitude()))
: pos;
if (Info.Trail != null && --ticksToNextSmoke < 0)
{
world.AddFrameEndTask(w => w.Add(
new Smoke(w, ((PPos)highPos.ToInt2()).ToWPos(0), Info.Trail)));
ticksToNextSmoke = Info.TrailInterval;
}
if (Info.ContrailLength > 0)
{
var alt = (Info.High || Info.Angle > 0) ? GetAltitude() : 0;
Trail.Update(new PPos((int)pos.X, (int)pos.Y).ToWPos((int)alt));
}
trail.Update(pos);
return;
}
if (!Info.High) // check for hitting a wall
{
var at = (float)t / TotalTime();
var pos = float2.Lerp(Args.src.ToFloat2(), Args.dest.ToFloat2(), at);
var cell = ((PPos) pos.ToInt2()).ToCPos();
if (anim != null)
anim.Tick();
if (world.ActorMap.GetUnitsAt(cell).Any(
a => a.HasTrait<IBlocksBullets>()))
{
Args.dest = (PPos) pos.ToInt2();
Explode(world);
}
pos = WPos.LerpQuadratic(args.source, target, angle, ticks, length);
if (info.Trail != null && --smokeTicks < 0)
{
var delayedPos = WPos.LerpQuadratic(args.source, target, angle, ticks - info.TrailDelay, length);
world.AddFrameEndTask(w => w.Add(new Smoke(w, delayedPos, info.Trail)));
smokeTicks = info.TrailInterval;
}
if (info.ContrailLength > 0)
trail.Update(pos);
if (ticks++ >= length || (!info.High && world.ActorMap
.GetUnitsAt(pos.ToCPos()).Any(a => a.HasTrait<IBlocksBullets>())))
{
Explode(world);
}
}
const float height = .1f;
public IEnumerable<IRenderable> Render(WorldRenderer wr)
{
if (Info.ContrailLength > 0)
yield return Trail;
if (info.ContrailLength > 0)
yield return trail;
if (anim != null)
if (anim == null || ticks >= length)
yield break;
var cell = pos.ToCPos();
if (!args.sourceActor.World.FogObscures(cell))
{
var at = (float)t / TotalTime();
var altitude = float2.Lerp(Args.srcAltitude, Args.destAltitude, at);
var pos = float2.Lerp(Args.src.ToFloat2(), Args.dest.ToFloat2(), at) - new float2(0, altitude);
var cell = ((PPos)pos.ToInt2()).ToCPos();
if (!Args.firedBy.World.FogObscures(cell))
if (info.Shadow)
{
if (Info.High || Info.Angle > 0)
{
if (Info.Shadow)
yield return new SpriteRenderable(anim.Image, pos, wr.Palette("shadow"), (int)pos.Y);
var highPos = pos - new float2(0, GetAltitude());
yield return new SpriteRenderable(anim.Image, highPos, wr.Palette("effect"), (int)pos.Y);
}
else
yield return new SpriteRenderable(anim.Image, pos,
wr.Palette(Args.weapon.Underwater ? "shadow" : "effect"), (int)pos.Y);
var shadowPos = pos - new WVec(0, 0, pos.Z);
foreach (var r in anim.Render(shadowPos, wr.Palette("shadow")))
yield return r;
}
var palette = wr.Palette(args.weapon.Underwater ? "shadow" : "effect");
foreach (var r in anim.Render(pos, palette))
yield return r;
}
}
void Explode( World world )
void Explode(World world)
{
world.AddFrameEndTask(w => w.Remove(this));
Combat.DoImpacts(Args);
if (info.ContrailLength > 0)
world.AddFrameEndTask(w => w.Add(new DelayedAction(info.ContrailLength, () => w.Remove(this))));
else
world.AddFrameEndTask(w => w.Remove(this));
Combat.DoImpacts(target, args.sourceActor, args.weapon, args.firepowerModifier);
}
}
}

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
#region Copyright & License Information
/*
* Copyright 2007-2011 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2013 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,
@@ -19,19 +19,26 @@ namespace OpenRA.Mods.RA.Effects
public class GravityBombInfo : IProjectileInfo
{
public readonly string Image = null;
public readonly WRange Velocity = WRange.Zero;
public readonly WRange Acceleration = new WRange(15);
public IEffect Create(ProjectileArgs args) { return new GravityBomb(this, args); }
}
public class GravityBomb : IEffect
{
GravityBombInfo info;
Animation anim;
int altitude;
ProjectileArgs Args;
ProjectileArgs args;
WVec velocity;
WPos pos;
public GravityBomb(GravityBombInfo info, ProjectileArgs args)
{
Args = args;
altitude = args.srcAltitude;
this.info = info;
this.args = args;
pos = args.source;
velocity = new WVec(WRange.Zero, WRange.Zero, -info.Velocity);
anim = new Animation(info.Image);
if (anim.HasSequence("open"))
@@ -42,10 +49,13 @@ namespace OpenRA.Mods.RA.Effects
public void Tick(World world)
{
if (--altitude <= Args.destAltitude)
velocity -= new WVec(WRange.Zero, WRange.Zero, info.Acceleration);
pos += velocity;
if (pos.Z <= args.passiveTarget.Z)
{
world.AddFrameEndTask(w => w.Remove(this));
Combat.DoImpacts(Args);
Combat.DoImpacts(args.passiveTarget, args.sourceActor, args.weapon, args.firepowerModifier);
}
anim.Tick();
@@ -53,8 +63,7 @@ namespace OpenRA.Mods.RA.Effects
public IEnumerable<IRenderable> Render(WorldRenderer wr)
{
var pos = Args.dest.ToInt2() - new int2(0, altitude) - .5f * anim.Image.size;
yield return new SpriteRenderable(anim.Image, pos, wr.Palette("effect"), Args.dest.Y);
return anim.Render(pos, wr.Palette("effect"));
}
}
}

View File

@@ -11,9 +11,9 @@
using System.Collections.Generic;
using System.Drawing;
using OpenRA.Effects;
using OpenRA.FileFormats;
using OpenRA.GameRules;
using OpenRA.Graphics;
using OpenRA.FileFormats;
using OpenRA.Traits;
namespace OpenRA.Mods.RA.Effects
@@ -29,7 +29,7 @@ namespace OpenRA.Mods.RA.Effects
public IEffect Create(ProjectileArgs args)
{
var c = UsePlayerColor ? args.firedBy.Owner.Color.RGB : Color;
var c = UsePlayerColor ? args.sourceActor.Owner.Color.RGB : Color;
return new LaserZap(args, this, c);
}
}
@@ -41,13 +41,16 @@ namespace OpenRA.Mods.RA.Effects
int ticks = 0;
Color color;
bool doneDamage;
bool animationComplete;
Animation hitanim;
WPos target;
public LaserZap(ProjectileArgs args, LaserZapInfo info, Color color)
{
this.args = args;
this.info = info;
this.color = color;
this.target = args.passiveTarget;
if (info.HitAnim != null)
this.hitanim = new Animation(info.HitAnim);
@@ -56,43 +59,36 @@ namespace OpenRA.Mods.RA.Effects
public void Tick(World world)
{
// Beam tracks target
if (args.target.IsValid)
args.dest = args.target.CenterLocation;
if (args.guidedTarget.IsValid)
target = args.guidedTarget.CenterPosition;
if (!doneDamage)
{
if (hitanim != null)
hitanim.PlayThen("idle",
() => world.AddFrameEndTask(w => w.Remove(this)));
Combat.DoImpacts(args);
hitanim.PlayThen("idle", () => animationComplete = true);
Combat.DoImpacts(target, args.sourceActor, args.weapon, args.firepowerModifier);
doneDamage = true;
}
++ticks;
if (hitanim != null)
hitanim.Tick();
else
if (ticks >= info.BeamDuration)
world.AddFrameEndTask(w => w.Remove(this));
if (++ticks >= info.BeamDuration && animationComplete)
world.AddFrameEndTask(w => w.Remove(this));
}
public IEnumerable<IRenderable> Render(WorldRenderer wr)
{
if (ticks < info.BeamDuration)
{
var src = new PPos(args.src.X, args.src.Y).ToWPos(args.srcAltitude);
var dest = new PPos(args.dest.X, args.dest.Y).ToWPos(args.destAltitude);
var rc = Color.FromArgb((info.BeamDuration - ticks)*255/info.BeamDuration, color);
yield return new BeamRenderable(src, 0, dest - src, info.BeamWidth, rc);
var rc = Color.FromArgb((info.BeamDuration - ticks) * 255 / info.BeamDuration, color);
yield return new BeamRenderable(args.source, 0, target - args.source, info.BeamWidth, rc);
}
if (hitanim != null)
yield return new SpriteRenderable(hitanim.Image, args.dest.ToFloat2(),
wr.Palette("effect"), (int)args.dest.Y);
if (ticks >= info.BeamDuration)
yield break;
foreach (var r in hitanim.Render(target, wr.Palette("effect")))
yield return r;
}
}
}

View File

@@ -1,4 +1,4 @@
#region Copyright & License Information
#region Copyright & License Information
/*
* Copyright 2007-2011 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
@@ -23,11 +23,10 @@ namespace OpenRA.Mods.RA.Effects
class MissileInfo : IProjectileInfo
{
public readonly int Speed = 1;
public readonly WAngle MaximumPitch = WAngle.FromDegrees(30);
public readonly int Arm = 0;
[Desc("Check for whether an actor with Wall: trait blocks fire")]
public readonly bool High = false;
public readonly bool Shadow = true;
public readonly bool Proximity = false;
public readonly string Trail = null;
public readonly float Inaccuracy = 0;
public readonly string Image = null;
@@ -48,139 +47,155 @@ namespace OpenRA.Mods.RA.Effects
class Missile : IEffect
{
readonly MissileInfo Info;
readonly ProjectileArgs Args;
PVecInt offset;
public PSubPos SubPxPosition;
public PPos PxPosition { get { return SubPxPosition.ToPPos(); } }
readonly MissileInfo info;
readonly ProjectileArgs args;
readonly Animation anim;
int Facing;
int t;
int Altitude;
ContrailRenderable Trail;
ContrailRenderable trail;
WPos pos;
int facing;
WPos target;
WVec offset;
int ticks;
bool exploded;
readonly int speed;
public Missile(MissileInfo info, ProjectileArgs args)
{
Info = info;
Args = args;
this.info = info;
this.args = args;
SubPxPosition = Args.src.ToPSubPos();
Altitude = Args.srcAltitude;
Facing = Args.facing;
pos = args.source;
facing = args.facing;
target = args.passiveTarget;
// Convert ProjectileArg definitions to world coordinates
// TODO: Change the yaml definitions so we don't need this
var inaccuracy = (int)(info.Inaccuracy * 1024 / Game.CellSize);
speed = info.Speed * 1024 / (5 * Game.CellSize);
if (info.Inaccuracy > 0)
offset = (PVecInt)(info.Inaccuracy * args.firedBy.World.SharedRandom.Gauss2D(2)).ToInt2();
offset = WVec.FromPDF(args.sourceActor.World.SharedRandom, 2) * inaccuracy / 1024;
if (Info.Image != null)
if (info.Image != null)
{
anim = new Animation(Info.Image, () => Facing);
anim = new Animation(info.Image, () => facing);
anim.PlayRepeating("idle");
}
if (Info.ContrailLength > 0)
if (info.ContrailLength > 0)
{
var color = Info.ContrailUsePlayerColor ? ContrailRenderable.ChooseColor(args.firedBy) : Info.ContrailColor;
Trail = new ContrailRenderable(args.firedBy.World, color, Info.ContrailLength, Info.ContrailDelay, 0);
var color = info.ContrailUsePlayerColor ? ContrailRenderable.ChooseColor(args.sourceActor) : info.ContrailColor;
trail = new ContrailRenderable(args.sourceActor.World, color, info.ContrailLength, info.ContrailDelay, 0);
}
}
// In pixels
const int MissileCloseEnough = 7;
static readonly WRange MissileCloseEnough = new WRange(7 * 1024 / Game.CellSize);
int ticksToNextSmoke;
bool JammedBy(TraitPair<JamsMissiles> tp)
{
if ((tp.Actor.CenterPosition - pos).HorizontalLengthSquared > tp.Trait.Range * tp.Trait.Range)
return false;
if (tp.Actor.Owner.Stances[args.sourceActor.Owner] == Stance.Ally && !tp.Trait.AlliedMissiles)
return false;
return tp.Actor.World.SharedRandom.Next(100 / tp.Trait.Chance) == 0;
}
public void Tick(World world)
{
t += 40;
// In pixels
var dist = Args.target.CenterLocation + offset - PxPosition;
var targetAltitude = 0;
if (Args.target.IsValid)
targetAltitude = Args.target.CenterPosition.Z * Game.CellSize / 1024;
var jammed = Info.Jammable && world.ActorsWithTrait<JamsMissiles>().Any(tp =>
(tp.Actor.CenterLocation - PxPosition).ToCVec().Length <= tp.Trait.Range
&& (tp.Actor.Owner.Stances[Args.firedBy.Owner] != Stance.Ally
|| (tp.Actor.Owner.Stances[Args.firedBy.Owner] == Stance.Ally && tp.Trait.AlliedMissiles))
&& world.SharedRandom.Next(100 / tp.Trait.Chance) == 0);
if (!jammed)
// Fade the trail out gradually
if (exploded && info.ContrailLength > 0)
{
Altitude += Math.Sign(targetAltitude - Altitude);
if (Args.target.IsValid)
Facing = Traits.Util.TickFacing(Facing,
Traits.Util.GetFacing(dist, Facing),
Info.ROT);
}
else
{
Altitude += world.SharedRandom.Next(-1, 2);
Facing = Traits.Util.TickFacing(Facing,
Facing + world.SharedRandom.Next(-20, 21),
Info.ROT);
trail.Update(pos);
return;
}
ticks++;
anim.Tick();
if (dist.LengthSquared < MissileCloseEnough * MissileCloseEnough && Args.target.IsValid)
Explode(world);
// Missile tracks target
if (args.guidedTarget.IsValid)
target = args.guidedTarget.CenterPosition;
// TODO: Replace this with a lookup table
var dir = (-float2.FromAngle((float)(Facing / 128f * Math.PI))).ToPSubVec();
var dist = target + offset - pos;
var desiredFacing = Traits.Util.GetFacing(dist, facing);
var desiredAltitude = target.Z;
var jammed = info.Jammable && world.ActorsWithTrait<JamsMissiles>().Any(j => JammedBy(j));
var move = Info.Speed * dir;
if (targetAltitude > 0 && Info.TurboBoost)
if (jammed)
{
desiredFacing = facing + world.SharedRandom.Next(-20, 21);
desiredAltitude = world.SharedRandom.Next(-43, 86);
}
else if (!args.guidedTarget.IsValid)
desiredFacing = facing;
facing = Traits.Util.TickFacing(facing, desiredFacing, info.ROT);
var move = new WVec(0, -1024, 0).Rotate(WRot.FromFacing(facing)) * speed / 1024;
if (target.Z > 0 && info.TurboBoost)
move = (move * 3) / 2;
move = move / 5;
SubPxPosition += move;
if (Info.Trail != null)
if (pos.Z != desiredAltitude)
{
var sp = ((SubPxPosition - (move * 3) / 2)).ToPPos() - new PVecInt(0, Altitude);
if (--ticksToNextSmoke < 0)
{
world.AddFrameEndTask(w => w.Add(new Smoke(w, sp.ToWPos(0), Info.Trail)));
ticksToNextSmoke = Info.TrailInterval;
}
var delta = move.HorizontalLength * info.MaximumPitch.Tan() / 1024;
var dz = (target.Z - pos.Z).Clamp(-delta, delta);
move += new WVec(0, 0, dz);
}
if (Info.RangeLimit != 0 && t > Info.RangeLimit * 40)
pos += move;
if (info.Trail != null && --ticksToNextSmoke < 0)
{
world.AddFrameEndTask(w => w.Add(new Smoke(w, pos - 3 * move / 2, info.Trail)));
ticksToNextSmoke = info.TrailInterval;
}
if (info.ContrailLength > 0)
trail.Update(pos);
var shouldExplode = pos.Z < 0 // Hit the ground
|| dist.LengthSquared < MissileCloseEnough.Range * MissileCloseEnough.Range // Within range
|| info.RangeLimit != 0 && ticks > info.RangeLimit // Ran out of fuel
|| (!info.High && world.ActorMap.GetUnitsAt(pos.ToCPos())
.Any(a => a.HasTrait<IBlocksBullets>())); // Hit a wall
if (shouldExplode)
Explode(world);
if (!Info.High) // check for hitting a wall
{
var cell = PxPosition.ToCPos();
if (world.ActorMap.GetUnitsAt(cell).Any(a => a.HasTrait<IBlocksBullets>()))
Explode(world);
}
if (Info.ContrailLength > 0)
Trail.Update(PxPosition.ToWPos(Altitude));
}
void Explode(World world)
{
world.AddFrameEndTask(w => w.Remove(this));
Args.dest = PxPosition;
if (t > Info.Arm * 40) /* don't blow up in our launcher's face! */
Combat.DoImpacts(Args);
exploded = true;
if (info.ContrailLength > 0)
world.AddFrameEndTask(w => w.Add(new DelayedAction(info.ContrailLength, () => w.Remove(this))));
else
world.AddFrameEndTask(w => w.Remove(this));
// Don't blow up in our launcher's face!
if (ticks <= info.Arm)
return;
Combat.DoImpacts(pos, args.sourceActor, args.weapon, args.firepowerModifier);
}
public IEnumerable<IRenderable> Render(WorldRenderer wr)
{
if (Info.ContrailLength > 0)
yield return Trail;
if (info.ContrailLength > 0)
yield return trail;
if (!Args.firedBy.World.FogObscures(PxPosition.ToCPos()))
yield return new SpriteRenderable(anim.Image, PxPosition.ToFloat2() - new float2(0, Altitude),
wr.Palette(Args.weapon.Underwater ? "shadow" : "effect"), PxPosition.Y);
if (!args.sourceActor.World.FogObscures(pos.ToCPos()))
{
var palette = wr.Palette(args.weapon.Underwater ? "shadow" : "effect");
foreach (var r in anim.Render(pos, palette))
yield return r;
}
}
}
}

View File

@@ -1,4 +1,4 @@
#region Copyright & License Information
#region Copyright & License Information
/*
* Copyright 2007-2011 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
@@ -30,7 +30,7 @@ namespace OpenRA.Mods.RA.Effects
{
readonly ProjectileArgs Args;
readonly TeslaZapInfo Info;
IEnumerable<IRenderable> renderables;
TeslaZapRenderable zap;
int timeUntilRemove = 2; // # of frames
bool doneDamage = false;
bool initialized = false;
@@ -41,33 +41,15 @@ namespace OpenRA.Mods.RA.Effects
Info = info;
}
public IEnumerable<IRenderable> GenerateRenderables(WorldRenderer wr)
{
var bright = SequenceProvider.GetSequence(Info.Image, "bright");
var dim = SequenceProvider.GetSequence(Info.Image, "dim");
var src = new PPos(Args.src.X, Args.src.Y - Args.srcAltitude);
var dest = new PPos(Args.dest.X, Args.dest.Y - Args.destAltitude);
for (var n = 0; n < Info.DimZaps; n++)
foreach (var z in DrawZapWandering(wr, src, dest, dim))
yield return z;
for (var n = 0; n < Info.BrightZaps; n++)
foreach (var z in DrawZapWandering(wr, src, dest, bright))
yield return z;
}
public void Tick(World world)
{
if (timeUntilRemove <= 0)
if (timeUntilRemove-- <= 0)
world.AddFrameEndTask(w => w.Remove(this));
--timeUntilRemove;
if (!doneDamage)
{
if (Args.target.IsValid)
Args.dest = Args.target.CenterLocation;
Combat.DoImpacts(Args);
var pos = Args.guidedTarget.IsValid ? Args.guidedTarget.CenterPosition : Args.passiveTarget;
Combat.DoImpacts(pos, Args.sourceActor, Args.weapon, Args.firepowerModifier);
doneDamage = true;
}
}
@@ -76,75 +58,10 @@ namespace OpenRA.Mods.RA.Effects
{
if (!initialized)
{
renderables = GenerateRenderables(wr);
initialized = true;
var pos = Args.guidedTarget.IsValid ? Args.guidedTarget.CenterPosition : Args.passiveTarget;
zap = new TeslaZapRenderable(Args.source, 0, pos - Args.source, Info.Image, Info.BrightZaps, Info.DimZaps);
}
return renderables;
yield return zap;
}
static IEnumerable<IRenderable> DrawZapWandering(WorldRenderer wr, PPos from, PPos to, Sequence s)
{
var z = float2.Zero; /* hack */
var dist = to - from;
var norm = (1f / dist.Length) * new float2(-dist.Y, dist.X);
var renderables = new List<IRenderable>();
if (Game.CosmeticRandom.Next(2) != 0)
{
var p1 = from.ToFloat2() + (1 / 3f) * dist.ToFloat2() + Game.CosmeticRandom.Gauss1D(1) * .2f * dist.Length * norm;
var p2 = from.ToFloat2() + (2 / 3f) * dist.ToFloat2() + Game.CosmeticRandom.Gauss1D(1) * .2f * dist.Length * norm;
renderables.AddRange(DrawZap(wr, from.ToFloat2(), p1, s, out p1));
renderables.AddRange(DrawZap(wr, p1, p2, s, out p2));
renderables.AddRange(DrawZap(wr, p2, to.ToFloat2(), s, out z));
}
else
{
var p1 = from.ToFloat2() + (1 / 2f) * dist.ToFloat2() + Game.CosmeticRandom.Gauss1D(1) * .2f * dist.Length * norm;
renderables.AddRange(DrawZap(wr, from.ToFloat2(), p1, s, out p1));
renderables.AddRange(DrawZap(wr, p1, to.ToFloat2(), s, out z));
}
return renderables;
}
static IEnumerable<IRenderable> DrawZap(WorldRenderer wr, float2 from, float2 to, Sequence s, out float2 p)
{
var dist = to - from;
var q = new float2(-dist.Y, dist.X);
var c = -float2.Dot(from, q);
var rs = new List<IRenderable>();
var z = from;
while ((to - z).X > 5 || (to - z).X < -5 || (to - z).Y > 5 || (to - z).Y < -5)
{
var step = steps.Where(t => (to - (z + new float2(t[0],t[1]))).LengthSquared < (to - z).LengthSquared )
.OrderBy(t => Math.Abs(float2.Dot(z + new float2(t[0], t[1]), q) + c)).First();
rs.Add(new SpriteRenderable(s.GetSprite(step[4]), z + new float2(step[2], step[3]),
wr.Palette("effect"), (int)from.Y));
z += new float2(step[0], step[1]);
if( rs.Count >= 1000 )
break;
}
p = z;
return rs;
}
static int[][] steps = new []
{
new int[] { 8, 8, 4, 4, 0 },
new int[] { -8, -8, -4, -4, 0 },
new int[] { 8, 0, 4, 4, 1 },
new int[] { -8, 0, -4, 4, 1 },
new int[] { 0, 8, 4, 4, 2 },
new int[] { 0, -8, 4, -4, 2 },
new int[] { -8, 8, -4, 4, 3 },
new int[] { 8, -8, 4, -4, 3 }
};
}
}

View File

@@ -42,8 +42,7 @@ namespace OpenRA.Mods.RA
this.self = init.self;
TopLeft = init.Get<LocationInit, CPos>();
var ppos = init.Contains<CenterLocationInit>() ? init.Get<CenterLocationInit, PPos>() : Util.CenterOfCell(TopLeft);
CenterPosition = ppos.ToWPos(0);
CenterPosition = init.Contains<CenterLocationInit>() ? init.Get<CenterLocationInit, PPos>().ToWPos(0) : TopLeft.CenterPosition;
Facing = init.Contains<FacingInit>() ? init.Get<FacingInit, int>() : 128;
var speed = init.Contains<HuskSpeedInit>() ? init.Get<HuskSpeedInit, int>() : 0;

View File

@@ -26,7 +26,8 @@ namespace OpenRA.Mods.RA
{
readonly JamsMissilesInfo info;
public int Range { get { return info.Range; } }
// Convert cells to world units
public int Range { get { return 1024 * info.Range; } }
public bool AlliedMissiles { get { return info.AlliedMissiles; } }
public int Chance { get { return info.Chance; } }

View File

@@ -462,6 +462,7 @@
<Compile Include="Orders\SetChronoTankDestination.cs" />
<Compile Include="Effects\FrozenActorProxy.cs" />
<Compile Include="Widgets\Logic\WorldTooltipLogic.cs" />
<Compile Include="TeslaZapRenderable.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\OpenRA.FileFormats\OpenRA.FileFormats.csproj">

View File

@@ -0,0 +1,148 @@
#region Copyright & License Information
/*
* Copyright 2007-2013 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.GameRules;
using OpenRA.Graphics;
using OpenRA.Traits;
namespace OpenRA.Mods.RA
{
struct TeslaZapRenderable : IRenderable
{
static int[][] steps = new[]
{
new int[] { 8, 8, 4, 4, 0 },
new int[] { -8, -8, -4, -4, 0 },
new int[] { 8, 0, 4, 4, 1 },
new int[] { -8, 0, -4, 4, 1 },
new int[] { 0, 8, 4, 4, 2 },
new int[] { 0, -8, 4, -4, 2 },
new int[] { -8, 8, -4, 4, 3 },
new int[] { 8, -8, 4, -4, 3 }
};
readonly WPos pos;
readonly int zOffset;
readonly WVec length;
readonly string image;
readonly int brightZaps, dimZaps;
WPos cachedPos;
WVec cachedLength;
IEnumerable<IRenderable> cache;
public TeslaZapRenderable(WPos pos, int zOffset, WVec length, string image, int brightZaps, int dimZaps)
{
this.pos = pos;
this.zOffset = zOffset;
this.length = length;
this.image = image;
this.brightZaps = brightZaps;
this.dimZaps = dimZaps;
cachedPos = WPos.Zero;
cachedLength = WVec.Zero;
cache = new IRenderable[] { };
}
public WPos Pos { get { return pos; } }
public float Scale { get { return 1f; } }
public PaletteReference Palette { get { return null; } }
public int ZOffset { get { return zOffset; } }
public IRenderable WithScale(float newScale) { return new TeslaZapRenderable(pos, zOffset, length, image, brightZaps, dimZaps); }
public IRenderable WithPalette(PaletteReference newPalette) { return new TeslaZapRenderable(pos, zOffset, length, image, brightZaps, dimZaps); }
public IRenderable WithZOffset(int newOffset) { return new TeslaZapRenderable(pos, zOffset, length, image, brightZaps, dimZaps); }
public IRenderable WithPos(WPos pos) { return new TeslaZapRenderable(pos, zOffset, length, image, brightZaps, dimZaps); }
public void BeforeRender(WorldRenderer wr) { }
public void RenderDebugGeometry(WorldRenderer wr) { }
public void Render(WorldRenderer wr)
{
if (!cache.Any() || length != cachedLength || pos != cachedPos)
cache = GenerateRenderables(wr);
cache.Do(c => c.Render(wr));
}
public IEnumerable<IRenderable> GenerateRenderables(WorldRenderer wr)
{
var bright = SequenceProvider.GetSequence(image, "bright");
var dim = SequenceProvider.GetSequence(image, "dim");
var source = wr.ScreenPosition(pos);
var target = wr.ScreenPosition(pos + length);
for (var n = 0; n < dimZaps; n++)
foreach (var z in DrawZapWandering(wr, source, target, dim))
yield return z;
for (var n = 0; n < brightZaps; n++)
foreach (var z in DrawZapWandering(wr, source, target, bright))
yield return z;
}
static IEnumerable<IRenderable> DrawZapWandering(WorldRenderer wr, float2 from, float2 to, Sequence s)
{
var z = float2.Zero; /* hack */
var dist = to - from;
var norm = (1f / dist.Length) * new float2(-dist.Y, dist.X);
var renderables = new List<IRenderable>();
if (Game.CosmeticRandom.Next(2) != 0)
{
var p1 = from + (1 / 3f) * dist + WRange.FromPDF(Game.CosmeticRandom, 2).Range * dist.Length / 4096 * norm;
var p2 = from + (2 / 3f) * dist + WRange.FromPDF(Game.CosmeticRandom, 2).Range * dist.Length / 4096 * norm;
renderables.AddRange(DrawZap(wr, from, p1, s, out p1));
renderables.AddRange(DrawZap(wr, p1, p2, s, out p2));
renderables.AddRange(DrawZap(wr, p2, to, s, out z));
}
else
{
var p1 = from + (1 / 2f) * dist + WRange.FromPDF(Game.CosmeticRandom, 2).Range * dist.Length / 4096 * norm;
renderables.AddRange(DrawZap(wr, from, p1, s, out p1));
renderables.AddRange(DrawZap(wr, p1, to, s, out z));
}
return renderables;
}
static IEnumerable<IRenderable> DrawZap(WorldRenderer wr, float2 from, float2 to, Sequence s, out float2 p)
{
var dist = to - from;
var q = new float2(-dist.Y, dist.X);
var c = -float2.Dot(from, q);
var rs = new List<IRenderable>();
var z = from;
while ((to - z).X > 5 || (to - z).X < -5 || (to - z).Y > 5 || (to - z).Y < -5)
{
var step = steps.Where(t => (to - (z + new float2(t[0], t[1]))).LengthSquared < (to - z).LengthSquared)
.OrderBy(t => Math.Abs(float2.Dot(z + new float2(t[0], t[1]), q) + c)).First();
rs.Add(new SpriteRenderable(s.GetSprite(step[4]), z + new float2(step[2], step[3]),
wr.Palette("effect"), (int)from.Y));
z += new float2(step[0], step[1]);
if (rs.Count >= 1000)
break;
}
p = z;
return rs;
}
}
}

View File

@@ -76,7 +76,7 @@ namespace OpenRA.Mods.RA
length = (finalPos - initialPos).Length / info.Velocity;
// Facing rotation
rotation = Game.CosmeticRandom.Gauss1D(2) * info.ROT;
rotation = WRange.FromPDF(Game.CosmeticRandom, 2).Range * info.ROT / 1024;
var anim = new Animation(rs.GetImage(self), () => (int)facing);
anim.PlayRepeating(info.Anim);

View File

@@ -563,8 +563,6 @@ Napalm:
Image: BOMBLET
Speed: 5
High: yes
RangeLimit: 24
Arm: 24
Warhead:
Spread: 4
Versus:
@@ -865,6 +863,8 @@ ParaBomb:
Report: CHUTE1.AUD
Projectile: GravityBomb
Image: PARABOMB
Velocity: 43
Acceleration: 0
Warhead:
Spread: 6
Versus:
@@ -950,11 +950,10 @@ SCUD:
Report: MISSILE1.AUD
Projectile: Bullet
Speed: 10
Arm: 10
High: true
Shadow: false
Proximity: true
Trail: smokey
TrailDelay: 5
Inaccuracy: 5
Image: V2
Angle: .1
@@ -1284,4 +1283,4 @@ Mandible:
Heavy: 10%
Concrete: 10%
InfDeath: 1
Damage: 60
Damage: 60