Merge pull request #3531 from pchote/world-targets

Convert more things to world coordinates
This commit is contained in:
Matthias Mailänder
2013-07-08 10:33:25 -07:00
77 changed files with 486 additions and 341 deletions

View File

@@ -34,6 +34,22 @@ namespace OpenRA
public static bool operator ==(WPos me, WPos other) { return (me.X == other.X && me.Y == other.Y && me.Z == other.Z); }
public static bool operator !=(WPos me, WPos other) { return !(me == other); }
public static WPos Lerp(WPos a, WPos b, int mul, int div) { return a + (b - a) * mul / div; }
public static WPos LerpQuadratic(WPos a, WPos b, WAngle pitch, int mul, int div)
{
// Start with a linear lerp between the points
var ret = Lerp(a, b, mul, div);
if (pitch.Angle == 0)
return ret;
// Add an additional quadratic variation to height
// Uses fp to avoid integer overflow
var offset = (int)((float)((float)(b - a).Length*pitch.Tan()*mul*(div - mul)) / (float)(1024*div*div));
return new WPos(ret.X, ret.Y, ret.Z + offset);
}
public static WPos Average(params WPos[] list)
{
if (list == null || list.Length == 0)

View File

@@ -22,6 +22,7 @@ namespace OpenRA
public WRange(int r) { Range = r; }
public static readonly WRange Zero = new WRange(0);
public static WRange FromCells(int cells) { return new WRange(1024*cells); }
public static WRange operator +(WRange a, WRange b) { return new WRange(a.Range + b.Range); }
public static WRange operator -(WRange a, WRange b) { return new WRange(a.Range - b.Range); }

View File

@@ -53,6 +53,20 @@ namespace OpenRA
public static WVec Lerp(WVec a, WVec b, int mul, int div) { return a + (b - a) * mul / div; }
public static WVec LerpQuadratic(WVec a, WVec b, WAngle pitch, int mul, int div)
{
// Start with a linear lerp between the points
var ret = Lerp(a, b, mul, div);
if (pitch.Angle == 0)
return ret;
// Add an additional quadratic variation to height
// Uses fp to avoid integer overflow
var offset = (int)((float)((float)(b - a).Length*pitch.Tan()*mul*(div - mul)) / (float)(1024*div*div));
return new WVec(ret.X, ret.Y, ret.Z + offset);
}
public override int GetHashCode() { return X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode(); }
public override bool Equals(object obj)

View File

@@ -21,8 +21,6 @@ namespace OpenRA
public readonly int X, Y;
public CPos(int x, int y) { X = x; Y = y; }
public CPos(WPos a) { X = a.X / 1024; Y = a.Y / 1024; }
public static readonly CPos Zero = new CPos(0, 0);
public static explicit operator CPos(int2 a) { return new CPos(a.X, a.Y); }
@@ -71,4 +69,9 @@ namespace OpenRA
public static CPos TopLeftAsCPos(this Rectangle r) { return new CPos(r.Left, r.Top); }
public static CPos BottomRightAsCPos(this Rectangle r) { return new CPos(r.Right, r.Bottom); }
}
}
public static class WPosExtensions
{
public static CPos ToCPos(this WPos a) { return new CPos(a.X / 1024, a.Y / 1024); }
}
}

View File

@@ -137,5 +137,37 @@ namespace OpenRA.GameRules
return ret;
}
public bool IsValidAgainst(Actor a)
{
var targetable = a.TraitOrDefault<ITargetable>();
if (targetable == null || !ValidTargets.Intersect(targetable.TargetTypes).Any())
return false;
if (Warheads.All(w => w.EffectivenessAgainst(a) <= 0))
return false;
return true;
}
public bool IsValidAgainst(Target target, World world)
{
if (!target.IsValid)
return false;
if (target.IsActor)
return IsValidAgainst(target.Actor);
else
{
var cell = target.CenterPosition.ToCPos();
if (ValidTargets.Contains("Ground") && world.GetTerrainType(cell) != "Water")
return true;
if (ValidTargets.Contains("Water") && world.GetTerrainType(cell) == "Water")
return true;
return false;
}
}
}
}

View File

@@ -60,13 +60,13 @@ namespace OpenRA.Graphics
// Start of the first line segment is the tail of the list - don't smooth it.
var curPos = trail[idx(next - skip - 1)];
var curCell = new CPos(curPos);
var curCell = curPos.ToCPos();
var curColor = color;
for (var i = 0; i < length - skip - 4; i++)
{
var j = next - skip - i - 2;
var nextPos = WPos.Average(trail[idx(j)], trail[idx(j-1)], trail[idx(j-2)], trail[idx(j-3)]);
var nextCell = new CPos(nextPos);
var nextCell = nextPos.ToCPos();
var nextColor = Exts.ColorLerp(i * 1f / (length - 4), color, Color.Transparent);
if (!world.FogObscures(curCell) && !world.FogObscures(nextCell))

View File

@@ -80,6 +80,31 @@ namespace OpenRA
il.EmitCall(OpCodes.Call, ((Func<PSubVec, int>)hash_PSubVec).Method, null);
il.Emit(OpCodes.Xor);
}
else if (type == typeof(WRange))
{
il.EmitCall(OpCodes.Call, ((Func<WRange, int>)(a => a.GetHashCode())).Method, null);
il.Emit(OpCodes.Xor);
}
else if (type == typeof(WPos))
{
il.EmitCall(OpCodes.Call, ((Func<WPos, int>)(a => a.GetHashCode())).Method, null);
il.Emit(OpCodes.Xor);
}
else if (type == typeof(WVec))
{
il.EmitCall(OpCodes.Call, ((Func<WVec, int>)(a => a.GetHashCode())).Method, null);
il.Emit(OpCodes.Xor);
}
else if (type == typeof(WAngle))
{
il.EmitCall(OpCodes.Call, ((Func<WAngle, int>)(a => a.GetHashCode())).Method, null);
il.Emit(OpCodes.Xor);
}
else if (type == typeof(WRot))
{
il.EmitCall(OpCodes.Call, ((Func<WRot, int>)(a => a.GetHashCode())).Method, null);
il.Emit(OpCodes.Xor);
}
else if (type == typeof(TypeDictionary))
{
il.EmitCall(OpCodes.Call, ((Func<TypeDictionary, int>)hash_tdict).Method, null);

View File

@@ -52,8 +52,6 @@ namespace OpenRA.Traits
public void RenderAfterWorld(WorldRenderer wr)
{
//if (self.IsIdle) return;
var force = Game.GetModifierKeys().HasModifier(Modifiers.Alt);
if ((lifetime <= 0 || --lifetime <= 0) && !force)
return;
@@ -61,9 +59,7 @@ namespace OpenRA.Traits
if (targets == null || targets.Count == 0)
return;
var move = self.TraitOrDefault<IMove>();
var origin = (move != null ? self.CenterLocation - new PVecInt(0, move.Altitude) : self.CenterLocation).ToFloat2();
var from = wr.ScreenPxPosition(self.CenterPosition);
var wlr = Game.Renderer.WorldLineRenderer;
foreach (var target in targets)
@@ -71,9 +67,10 @@ namespace OpenRA.Traits
if (!target.IsValid)
continue;
wlr.DrawLine(origin, target.CenterLocation.ToFloat2(), c, c);
DrawTargetMarker(wlr, target.CenterLocation.ToFloat2());
DrawTargetMarker(wlr, origin);
var to = wr.ScreenPxPosition(target.CenterPosition);
wlr.DrawLine(from, to, c, c);
DrawTargetMarker(wlr, from);
DrawTargetMarker(wlr, to);
}
}

View File

@@ -46,9 +46,9 @@ namespace OpenRA.Traits
var Xy = new float2(bounds.Right, bounds.Top);
wr.DrawSelectionBox(self, Color.White);
DrawHealthBar(self, xy, Xy);
DrawExtraBars(self, xy, Xy);
DrawUnitPath(self);
DrawHealthBar(wr, self, xy, Xy);
DrawExtraBars(wr, self, xy, Xy);
DrawUnitPath(wr, self);
}
public void DrawRollover(WorldRenderer wr, Actor self)
@@ -61,11 +61,11 @@ namespace OpenRA.Traits
var xy = new float2(bounds.Left, bounds.Top);
var Xy = new float2(bounds.Right, bounds.Top);
DrawHealthBar(self, xy, Xy);
DrawExtraBars(self, xy, Xy);
DrawHealthBar(wr, self, xy, Xy);
DrawExtraBars(wr, self, xy, Xy);
}
void DrawExtraBars(Actor self, float2 xy, float2 Xy)
void DrawExtraBars(WorldRenderer wr, Actor self, float2 xy, float2 Xy)
{
foreach (var extraBar in self.TraitsImplementing<ISelectionBar>())
{
@@ -74,12 +74,12 @@ namespace OpenRA.Traits
{
xy.Y += 4;
Xy.Y += 4;
DrawSelectionBar(self, xy, Xy, extraBar.GetValue(), extraBar.GetColor());
DrawSelectionBar(wr, self, xy, Xy, extraBar.GetValue(), extraBar.GetColor());
}
}
}
void DrawSelectionBar(Actor self, float2 xy, float2 Xy, float value, Color barColor)
void DrawSelectionBar(WorldRenderer wr, Actor self, float2 xy, float2 Xy, float value, Color barColor)
{
if (!self.IsInWorld) return;
@@ -102,7 +102,7 @@ namespace OpenRA.Traits
wlr.DrawLine(xy + new float2(0, -4), z + new float2(0, -4), barColor2, barColor2);
}
void DrawHealthBar(Actor self, float2 xy, float2 Xy)
void DrawHealthBar(WorldRenderer wr, Actor self, float2 xy, float2 Xy)
{
if (!self.IsInWorld) return;
@@ -148,24 +148,21 @@ namespace OpenRA.Traits
}
}
void DrawUnitPath(Actor self)
void DrawUnitPath(WorldRenderer wr, Actor self)
{
if (self.World.LocalPlayer == null ||!self.World.LocalPlayer.PlayerActor.Trait<DeveloperMode>().PathDebug) return;
var activity = self.GetCurrentActivity();
var mobile = self.TraitOrDefault<IMove>();
if (activity != null && mobile != null)
if (activity != null)
{
var alt = new float2(0, -mobile.Altitude);
var targets = activity.GetTargets(self);
var start = self.CenterLocation.ToFloat2() + alt;
var start = wr.ScreenPxPosition(self.CenterPosition);
var c = Color.Green;
var wlr = Game.Renderer.WorldLineRenderer;
foreach (var step in targets.Select(p => p.CenterLocation.ToFloat2()))
foreach (var stp in targets.Where(t => t.IsValid).Select(p => wr.ScreenPxPosition(p.CenterPosition)))
{
var stp = step + alt;
wlr.DrawLine(stp + new float2(-1, -1), stp + new float2(-1, 1), c, c);
wlr.DrawLine(stp + new float2(-1, 1), stp + new float2(1, 1), c, c);
wlr.DrawLine(stp + new float2(1, 1), stp + new float2(1, -1), c, c);

View File

@@ -8,17 +8,32 @@
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
namespace OpenRA.Traits
{
public struct Target // a target: either an actor, or a fixed location.
public struct Target
{
public static Target[] NoTargets = {};
public static readonly Target[] NoTargets = {};
public static readonly Target None = new Target();
Actor actor;
PPos pos;
WPos pos;
bool valid;
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)
{
return o.TargetActor != null
? Target.FromActor(o.TargetActor)
: Target.FromCell(o.TargetLocation);
}
public static Target FromActor(Actor a)
{
return new Target
@@ -28,22 +43,56 @@ namespace OpenRA.Traits
generation = a.Generation,
};
}
public static Target FromPos(PPos p) { return new Target { pos = p, valid = true }; }
public static Target FromCell(CPos c) { return new Target { pos = Util.CenterOfCell(c), valid = true }; }
public static Target FromOrder(Order o)
{
return o.TargetActor != null
? Target.FromActor(o.TargetActor)
: Target.FromCell(o.TargetLocation);
}
public static readonly Target None = new Target();
public bool IsValid { get { return valid && (actor == null || (actor.IsInWorld && !actor.IsDead() && actor.Generation == generation)); } }
public PPos PxPosition { get { return IsActor ? actor.Trait<IHasLocation>().PxPosition : pos; } }
public PPos PxPosition { get { return IsActor ? actor.Trait<IHasLocation>().PxPosition : PPos.FromWPos(pos); } }
public PPos CenterLocation { get { return PxPosition; } }
public Actor Actor { get { return IsActor ? actor : null; } }
// TODO: This should return true even if the actor is destroyed
public bool IsActor { get { return actor != null && !actor.Destroyed; } }
// Representative position - see Positions for the full set of targetable positions.
public WPos CenterPosition
{
get
{
if (!IsValid)
throw new InvalidOperationException("Attempting to query the position of an invalid Target");
return actor != null ? actor.CenterPosition : pos;
}
}
// Positions available to target for range checks
static readonly WPos[] NoPositions = {};
public IEnumerable<WPos> Positions
{
get
{
if (!IsValid)
return NoPositions;
if (actor == null)
return new []{pos};
var targetable = actor.TraitOrDefault<ITargetable>();
if (targetable == null)
return new []{actor.CenterPosition};
return targetable.TargetableCells(actor).Select(c => c.CenterPosition);
}
}
public bool IsInRange(WPos origin, WRange range)
{
if (!IsValid)
return false;
// Target ranges are calculated in 2D, so ignore height differences
return Positions.Any(t => (t.X - origin.X)*(t.X - origin.X) +
(t.Y - origin.Y)*(t.Y - origin.Y) <= range.Range*range.Range);
}
}
}

View File

@@ -30,6 +30,11 @@ namespace OpenRA.Traits
return ( facing - rot ) & 0xFF;
}
public static int GetFacing(WVec d, int currentFacing)
{
return GetFacing(new int2(d.X, d.Y), currentFacing);
}
public static int GetFacing(PVecInt d, int currentFacing)
{
return GetFacing(d.ToInt2(), currentFacing);
@@ -40,7 +45,7 @@ namespace OpenRA.Traits
return GetFacing(d.ToInt2(), currentFacing);
}
public static int GetFacing( int2 d, int currentFacing )
public static int GetFacing(int2 d, int currentFacing)
{
if (d == int2.Zero)
return currentFacing;
@@ -177,7 +182,7 @@ namespace OpenRA.Traits
: new CPos[] {};
if (cells.Length == 0)
cells = new CPos[] { target.CenterLocation.ToCPos() };
cells = new CPos[] { target.CenterPosition.ToCPos() };
return Util.ExpandFootprint(cells, true);
}

View File

@@ -202,7 +202,7 @@ namespace OpenRA.Traits
return cells.Select(c => c.First);
}
return new[] { a.CenterLocation.ToCPos() };
return new[] { a.CenterPosition.ToCPos() };
}
public void Explore(World world, CPos center, int range)

View File

@@ -39,6 +39,11 @@ namespace OpenRA
return actors.OrderBy( a => (a.CenterLocation - px).LengthSquared ).FirstOrDefault();
}
public static IEnumerable<Actor> FindUnitsInCircle(this World world, WPos a, WRange r)
{
return world.FindUnitsInCircle(PPos.FromWPos(a), r.Range * Game.CellSize / 1024);
}
public static IEnumerable<Actor> FindUnitsInCircle(this World world, PPos a, int r)
{
using (new PerfSample("FindUnitsInCircle"))

View File

@@ -27,16 +27,15 @@ namespace OpenRA.Mods.Cnc
readonly RenderUnit ru;
State state;
PPos startDock;
PPos endDock;
WPos startDock, endDock;
public HarvesterDockSequence(Actor self, Actor proc)
{
this.proc = proc;
state = State.Turn;
harv = self.Trait<Harvester>();
ru = self.Trait<RenderUnit>();
startDock = self.Trait<IHasLocation>().PxPosition;
endDock = proc.Trait<IHasLocation>().PxPosition + new PVecInt(-15,8);
startDock = self.Trait<IHasLocation>().PxPosition.ToWPos(0);
endDock = (proc.Trait<IHasLocation>().PxPosition + new PVecInt(-15,8)).ToWPos(0);
}
public override Activity Tick(Actor self)

View File

@@ -34,15 +34,13 @@ namespace OpenRA.Mods.Cnc.Effects
public IEnumerable<IRenderable> Render(WorldRenderer wr)
{
yield return new SpriteRenderable(anim.Image,
target.CenterLocation.ToFloat2() - new float2(0, 0.5f*anim.Image.size.Y - Game.CellSize),
wr.Palette("effect"), (int)target.CenterLocation.Y);
yield return new SpriteRenderable(anim.Image, target.CenterPosition, 1, wr.Palette("effect"), 1.0f);
}
void Finish( World world )
void Finish(World world)
{
world.AddFrameEndTask(w => w.Remove(this));
Combat.DoExplosion(firedBy, "IonCannon", target.CenterLocation, 0);
Combat.DoExplosion(firedBy, "IonCannon", target.CenterPosition);
}
}
}

View File

@@ -518,7 +518,7 @@ namespace OpenRA.Mods.RA.AI
if (owner.attackOrFleeFuzzy.CanAttack)
{
foreach(var u in owner.units)
owner.world.IssueOrder(new Order("AttackMove", u, false) { TargetLocation = owner.Target.CenterLocation.ToCPos() });
owner.world.IssueOrder(new Order("AttackMove", u, false) { TargetLocation = owner.Target.CenterPosition.ToCPos() });
// We have gathered sufficient units. Attack the nearest enemy unit.
owner.fsm.ChangeState(new GroundUnitsAttackMoveState(), true);
return;
@@ -560,7 +560,7 @@ namespace OpenRA.Mods.RA.AI
{
owner.world.IssueOrder(new Order("Stop", leader, false));
foreach (var unit in owner.units.Where(a => !ownUnits.Contains(a)))
owner.world.IssueOrder(new Order("AttackMove", unit, false) { TargetLocation = leader.CenterLocation.ToCPos() });
owner.world.IssueOrder(new Order("AttackMove", unit, false) { TargetLocation = leader.CenterPosition.ToCPos() });
}
else
{

View File

@@ -19,7 +19,7 @@ namespace OpenRA.Mods.RA.Activities
{
protected Target Target;
ITargetable targetable;
int Range;
WRange Range;
bool AllowMovement;
int nextPathTime;
@@ -27,7 +27,10 @@ namespace OpenRA.Mods.RA.Activities
const int delayBetweenPathingAttempts = 20;
const int delaySpread = 5;
public Attack(Target target, int range, bool allowMovement)
public Attack(Target target, WRange range)
: this(target, range, true) {}
public Attack(Target target, WRange range, bool allowMovement)
{
Target = target;
if (target.IsActor)
@@ -37,20 +40,18 @@ namespace OpenRA.Mods.RA.Activities
AllowMovement = allowMovement;
}
public Attack(Target target, int range) : this(target, range, true) {}
public override Activity Tick( Actor self )
public override Activity Tick(Actor self)
{
var attack = self.Trait<AttackBase>();
var ret = InnerTick( self, attack );
attack.IsAttacking = ( ret == this );
var ret = InnerTick(self, attack);
attack.IsAttacking = (ret == this);
return ret;
}
protected virtual Activity InnerTick( Actor self, AttackBase attack )
protected virtual Activity InnerTick(Actor self, AttackBase attack)
{
if (IsCanceled) return NextActivity;
if (IsCanceled)
return NextActivity;
if (!Target.IsValid)
return NextActivity;
@@ -61,7 +62,7 @@ namespace OpenRA.Mods.RA.Activities
if (targetable != null && !targetable.TargetableBy(Target.Actor, self))
return NextActivity;
if (!Combat.IsInRange(self.CenterLocation, Range, Target))
if (!Target.IsInRange(self.CenterPosition, Range))
{
if (--nextPathTime > 0)
return this;
@@ -69,13 +70,13 @@ namespace OpenRA.Mods.RA.Activities
nextPathTime = self.World.SharedRandom.Next(delayBetweenPathingAttempts - delaySpread,
delayBetweenPathingAttempts + delaySpread);
return (AllowMovement) ? Util.SequenceActivities(self.Trait<Mobile>().MoveWithinRange(Target, Range), this) : NextActivity;
return (AllowMovement) ? Util.SequenceActivities(self.Trait<Mobile>().MoveWithinRange(Target, Range.Range / 1024), this) : NextActivity;
}
var desiredFacing = Util.GetFacing(Target.CenterLocation - self.CenterLocation, 0);
var desiredFacing = Util.GetFacing(Target.CenterPosition - self.CenterPosition, 0);
var facing = self.Trait<IFacing>();
if (facing.Facing != desiredFacing)
return Util.SequenceActivities( new Turn( desiredFacing ), this );
return Util.SequenceActivities(new Turn(desiredFacing), this);
attack.DoAttack(self, Target);
return this;

View File

@@ -35,9 +35,9 @@ namespace OpenRA.Mods.RA.Activities
// Move to the middle of the target, ignoring impassable tiles
var mobile = self.Trait<Mobile>();
var to = target.CenterLocation;
var from = self.CenterLocation;
var speed = mobile.MovementSpeedForCell(self, self.Location);
var to = target.CenterPosition;
var from = self.CenterPosition;
var speed = mobile.WorldMovementSpeedForCell(self, self.Location);
var length = speed > 0 ? (int)((to - from).Length * 3 / speed) : 0;
return Util.SequenceActivities(

View File

@@ -33,7 +33,7 @@ namespace OpenRA.Mods.RA.Activities
if (IsCanceled) return NextActivity;
if (!Target.IsValid) return NextActivity;
var inRange = ( Target.CenterLocation.ToCPos() - self.Location ).LengthSquared < Range * Range;
var inRange = ( Target.CenterPosition.ToCPos() - self.Location ).LengthSquared < Range * Range;
if( inRange ) return this;
if (--nextPathTime > 0) return this;

View File

@@ -17,10 +17,10 @@ namespace OpenRA.Mods.RA.Activities
/* non-turreted attack */
public class Heal : Attack
{
public Heal(Target target, int range, bool allowMovement)
public Heal(Target target, WRange range, bool allowMovement)
: base(target, range, allowMovement) {}
protected override Activity InnerTick( Actor self, AttackBase attack )
protected override Activity InnerTick(Actor self, AttackBase attack)
{
if (Target.IsActor && Target.Actor.GetDamageState() == DamageState.Undamaged)
return NextActivity;

View File

@@ -38,7 +38,7 @@ namespace OpenRA.Mods.RA.Activities
return Util.SequenceActivities(
new MoveAdjacentTo(Target.FromActor(rearmTarget)),
mobile.MoveTo(rearmTarget.CenterLocation.ToCPos(), rearmTarget),
mobile.MoveTo(rearmTarget.CenterPosition.ToCPos(), rearmTarget),
new Rearm(self),
new Repair(rearmTarget),
this );

View File

@@ -8,7 +8,9 @@
*/
#endregion
using System;
using System.Linq;
using OpenRA.GameRules;
using OpenRA.Mods.RA.Move;
using OpenRA.Mods.RA.Render;
using OpenRA.Traits;
@@ -17,40 +19,54 @@ namespace OpenRA.Mods.RA.Activities
{
class Leap : Activity
{
Target target;
PPos initialLocation;
Mobile mobile;
WeaponInfo weapon;
int moveFraction;
const int delay = 6;
WPos from;
WPos to;
int ticks;
int length;
WAngle angle;
public Leap(Actor self, Target target)
public Leap(Actor self, Actor target, WeaponInfo weapon, WRange speed, WAngle angle)
{
this.target = target;
initialLocation = (PPos) self.Trait<Mobile>().PxPosition;
var targetMobile = target.TraitOrDefault<Mobile>();
if (targetMobile == null)
throw new InvalidOperationException("Leap requires a target actor with the Mobile trait");
self.Trait<RenderInfantry>().Attacking(self, target);
Sound.Play("dogg5p.aud", self.CenterLocation);
this.weapon = weapon;
this.angle = angle;
mobile = self.Trait<Mobile>();
mobile.SetLocation(mobile.fromCell, mobile.fromSubCell, targetMobile.fromCell, targetMobile.fromSubCell);
mobile.IsMoving = true;
from = self.CenterPosition;
var offset = MobileInfo.SubCellOffsets[targetMobile.fromSubCell];
to = targetMobile.fromCell.CenterPosition + new WVec(offset.X * 1024 / Game.CellSize, offset.Y * 1024 / Game.CellSize, 0);
length = Math.Max((to - from).Length / speed.Range, 1);
self.Trait<RenderInfantry>().Attacking(self, Target.FromActor(target));
if (weapon.Report != null && weapon.Report.Any())
Sound.Play(weapon.Report.Random(self.World.SharedRandom), self.CenterLocation);
}
public override Activity Tick(Actor self)
{
if( moveFraction == 0 && IsCanceled ) return NextActivity;
if (!target.IsValid) return NextActivity;
if (ticks == 0 && IsCanceled)
return NextActivity;
self.Trait<AttackLeap>().IsLeaping = true;
var mobile = self.Trait<Mobile>();
++moveFraction;
mobile.PxPosition = PPos.Lerp(initialLocation, target.PxPosition, moveFraction, delay);
if (moveFraction >= delay)
mobile.AdjustPxPosition(self, PPos.FromWPosHackZ(WPos.LerpQuadratic(from, to, angle, ++ticks, length)));
if (ticks >= length)
{
self.TraitsImplementing<IMove>().FirstOrDefault()
.SetPosition(self, target.CenterLocation.ToCPos());
mobile.SetLocation(mobile.toCell, mobile.toSubCell, mobile.toCell, mobile.toSubCell);
mobile.FinishedMoving(self);
mobile.IsMoving = false;
self.World.ActorMap.GetUnitsAt(mobile.toCell, mobile.toSubCell)
.Except(new []{self}).Where(t => weapon.IsValidAgainst(t))
.Do(t => t.Kill(self));
if (target.IsActor)
target.Actor.Kill(self);
self.Trait<AttackLeap>().IsLeaping = false;
return NextActivity;
}

View File

@@ -41,7 +41,7 @@ namespace OpenRA.Mods.RA.Activities
}
ps1.heuristic = PathSearch.DefaultEstimator(mobile.toCell);
var ps2 = PathSearch.FromPoint(self.World, mobile.Info, self, mobile.toCell, target.CenterLocation.ToCPos(), true);
var ps2 = PathSearch.FromPoint(self.World, mobile.Info, self, mobile.toCell, target.CenterPosition.ToCPos(), true);
var ret = self.World.WorldActor.Trait<PathFinder>().FindBidiPath(ps1, ps2);
return Util.SequenceActivities(mobile.MoveTo(() => ret), this);

View File

@@ -79,23 +79,24 @@ namespace OpenRA.Mods.RA.Activities
return this;
var actor = cargo.Unload(self);
var exitPx = Util.CenterOfCell(exitTile.Value);
var currentPx = Util.CenterOfCell(self.Location);
var exit = exitTile.Value.CenterPosition;
var current = self.Location.CenterPosition;
self.World.AddFrameEndTask(w =>
{
if (actor.Destroyed) return;
if (actor.Destroyed)
return;
var mobile = actor.Trait<Mobile>();
mobile.Facing = Util.GetFacing( (exitPx - currentPx).ToInt2(), mobile.Facing );
mobile.Facing = Util.GetFacing(exit - current, mobile.Facing );
mobile.SetPosition(actor, exitTile.Value);
mobile.AdjustPxPosition(actor, currentPx);
var speed = mobile.MovementSpeedForCell(actor, exitTile.Value);
var length = speed > 0 ? ((int)(exitPx - currentPx).Length * 3 / speed) : 0;
mobile.AdjustPxPosition(actor, PPos.FromWPos(current));
var speed = mobile.WorldMovementSpeedForCell(actor, exitTile.Value);
var length = speed > 0 ? ((int)(exit - current).Length * 3 / speed) : 0;
w.Add(actor);
actor.CancelActivity();
actor.QueueActivity(new Drag(currentPx, exitPx, length));
actor.QueueActivity(new Drag(current, exit, length));
actor.QueueActivity(mobile.MoveTo(exitTile.Value, 0));
var rallyPoint = ChooseRallyPoint(actor).Value;

View File

@@ -177,7 +177,7 @@ namespace OpenRA.Mods.RA.Air
return new Order(order.OrderID, self, queued) { TargetActor = target.Actor };
if (order.OrderID == "Move")
return new Order(order.OrderID, self, queued) { TargetLocation = target.CenterLocation.ToCPos() };
return new Order(order.OrderID, self, queued) { TargetLocation = target.CenterPosition.ToCPos() };
return null;
}

View File

@@ -50,7 +50,7 @@ namespace OpenRA.Mods.RA.Air
if (aircraft.Altitude <= 0)
{
if (info.Explosion != null)
Combat.DoExplosion(self, info.Explosion, self.CenterLocation, 0);
Combat.DoExplosion(self, info.Explosion, self.CenterPosition);
self.Destroy();
return null;

View File

@@ -35,7 +35,7 @@ namespace OpenRA.Mods.RA.Air
var aircraft = self.Trait<Aircraft>();
var desiredFacing = Util.GetFacing(d.ToInt2(), aircraft.Facing);
var desiredFacing = Util.GetFacing(d, aircraft.Facing);
if (aircraft.Altitude == cruiseAltitude)
aircraft.Facing = Util.TickFacing(aircraft.Facing, desiredFacing, aircraft.ROT);

View File

@@ -38,12 +38,12 @@ namespace OpenRA.Mods.RA.Air
}
var attack = self.Trait<AttackHeli>();
var dist = target.CenterLocation - self.CenterLocation;
var dist = target.CenterPosition - self.CenterPosition;
var desiredFacing = Util.GetFacing(dist, aircraft.Facing);
aircraft.Facing = Util.TickFacing(aircraft.Facing, desiredFacing, aircraft.ROT);
if (!Combat.IsInRange(self.CenterLocation, attack.GetMaximumRange(), target))
if (!target.IsInRange(self.CenterPosition, attack.GetMaximumRange()))
aircraft.TickMove(PSubPos.PerPx * aircraft.MovementSpeed, desiredFacing);
attack.DoAttack( self, target );

View File

@@ -27,8 +27,8 @@ namespace OpenRA.Mods.RA.Air
if (IsCanceled) return NextActivity;
var d = Target.CenterLocation - self.CenterLocation;
if (d.LengthSquared < 50) /* close enough */
var d = Target.CenterPosition - self.CenterPosition;
if (d.LengthSquared < 256*256) // close enough (1/4 cell)
return NextActivity;
var aircraft = self.Trait<Aircraft>();

View File

@@ -107,9 +107,17 @@ namespace OpenRA.Mods.RA
if (limitedAmmo != null && !limitedAmmo.HasAmmo())
return;
if (!Combat.IsInRange(self.CenterLocation, Weapon.Range, target)) return;
if (Combat.IsInRange(self.CenterLocation, Weapon.MinRange, target)) return;
if (!IsValidAgainst(self.World, target)) return;
// TODO: Define weapon ranges as WRange
var range = new WRange((int)(1024*Weapon.Range));
var minRange = new WRange((int)(1024*Weapon.MinRange));
if (!target.IsInRange(self.CenterPosition, range))
return;
if (target.IsInRange(self.CenterPosition, minRange))
return;
if (!Weapon.IsValidAgainst(target, self.World))
return;
var barrel = Barrels[Burst % Barrels.Length];
var destMove = target.IsActor ? target.Actor.TraitOrDefault<IMove>() : null;
@@ -164,14 +172,6 @@ namespace OpenRA.Mods.RA
}
}
public bool IsValidAgainst(World world, Target target)
{
if (target.IsActor)
return Combat.WeaponValidForTarget(Weapon, target.Actor);
else
return Combat.WeaponValidForTarget(Weapon, world, target.CenterLocation.ToCPos());
}
public bool IsReloading { get { return FireDelay > 0; } }
public WVec MuzzleOffset(Actor self, Barrel b)

View File

@@ -117,7 +117,7 @@ namespace OpenRA.Mods.RA
if (target.IsActor)
return new Order("Attack", self, queued) { TargetActor = target.Actor };
else
return new Order("Attack", self, queued) { TargetLocation = target.CenterLocation.ToCPos() };
return new Order("Attack", self, queued) { TargetLocation = target.CenterPosition.ToCPos() };
}
return null;
}
@@ -139,10 +139,10 @@ namespace OpenRA.Mods.RA
public abstract Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove);
public bool HasAnyValidWeapons(Target t) { return Armaments.Any(a => a.IsValidAgainst(self.World, t)); }
public float GetMaximumRange() { return Armaments.Select(a => a.Weapon.Range).Aggregate(0f, Math.Max); }
public bool HasAnyValidWeapons(Target t) { return Armaments.Any(a => a.Weapon.IsValidAgainst(t, self.World)); }
public WRange GetMaximumRange() { return new WRange((int)(1024*Armaments.Max(a => a.Weapon.Range))); }
public Armament ChooseArmamentForTarget(Target t) { return Armaments.FirstOrDefault(a => a.IsValidAgainst(self.World, t)); }
public Armament ChooseArmamentForTarget(Target t) { return Armaments.FirstOrDefault(a => a.Weapon.IsValidAgainst(t, self.World)); }
public void AttackTarget(Target target, bool queued, bool allowMove)
{

View File

@@ -28,15 +28,15 @@ namespace OpenRA.Mods.RA
public AttackFrontal(Actor self, AttackFrontalInfo info)
: base( self ) { this.info = info; }
protected override bool CanAttack( Actor self, Target target )
protected override bool CanAttack(Actor self, Target target)
{
if( !base.CanAttack( self, target ) )
if (!base.CanAttack(self, target))
return false;
var facing = self.Trait<IFacing>().Facing;
var facingToTarget = Util.GetFacing(target.CenterLocation - self.CenterLocation, facing);
var facingToTarget = Util.GetFacing(target.CenterPosition - self.CenterPosition, facing);
if( Math.Abs( facingToTarget - facing ) % 256 > info.FacingTolerance )
if (Math.Abs(facingToTarget - facing) % 256 > info.FacingTolerance)
return false;
return true;
@@ -44,10 +44,13 @@ namespace OpenRA.Mods.RA
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove)
{
var weapon = ChooseArmamentForTarget(newTarget);
if (weapon == null)
var a = ChooseArmamentForTarget(newTarget);
if (a == null)
return null;
return new Activities.Attack(newTarget, Math.Max(0, (int)weapon.Weapon.Range), allowMove);
// TODO: Define weapon ranges as WRange
var range = new WRange(Math.Max(0,(int)(1024*a.Weapon.Range)));
return new Activities.Attack(newTarget, range, allowMove);
}
}
}

View File

@@ -10,6 +10,7 @@
using System;
using System.Linq;
using OpenRA.FileFormats;
using OpenRA.Mods.RA.Activities;
using OpenRA.Traits;
@@ -17,38 +18,39 @@ namespace OpenRA.Mods.RA
{
class AttackLeapInfo : AttackFrontalInfo
{
[Desc("Leap speed (in units/tick).")]
public readonly WRange Speed = new WRange(426);
public readonly WAngle Angle = WAngle.FromDegrees(20);
public override object Create(ActorInitializer init) { return new AttackLeap(init.self, this); }
}
class AttackLeap : AttackFrontal, ISync
{
[Sync] internal bool IsLeaping;
AttackLeapInfo info;
public AttackLeap(Actor self, AttackLeapInfo info)
: base(self, info) {}
: base(self, info)
{
this.info = info;
}
public override void DoAttack(Actor self, Target target)
{
if (!CanAttack(self, target))
if (!CanAttack(self, target) || !target.IsActor)
return;
var a = ChooseArmamentForTarget(target);
if (a == null)
return;
if (!Combat.IsInRange(self.CenterLocation, a.Weapon.Range, target))
// TODO: Define weapon ranges as WRange
var range = new WRange((int)(1024*a.Weapon.Range));
if (!target.IsInRange(self.CenterPosition, range))
return;
self.CancelActivity();
self.QueueActivity(new Leap(self, target));
}
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove)
{
var a = ChooseArmamentForTarget(newTarget);
if (a == null)
return null;
return new Activities.Attack(newTarget, Math.Max(0, (int)a.Weapon.Range), allowMove);
self.QueueActivity(new Leap(self, target.Actor, a.Weapon, info.Speed, info.Angle));
}
}
}

View File

@@ -33,7 +33,9 @@ namespace OpenRA.Mods.RA
if (arm == null)
return;
if (!Combat.IsInRange(self.CenterLocation, arm.Weapon.Range, target))
// TODO: Define weapon ranges as WRange
var range = new WRange((int)(1024*arm.Weapon.Range));
if (!target.IsInRange(self.CenterPosition, range))
return;
var move = self.TraitOrDefault<IMove>();

View File

@@ -29,10 +29,13 @@ namespace OpenRA.Mods.RA
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove)
{
var weapon = ChooseArmamentForTarget(newTarget);
if (weapon == null)
var a = ChooseArmamentForTarget(newTarget);
if (a == null)
return null;
return new Activities.Heal(newTarget, Math.Max(0, (int)weapon.Weapon.Range), allowMove);
// TODO: Define weapon ranges as WRange
var range = new WRange(Math.Max(0,(int)(1024*a.Weapon.Range)));
return new Activities.Heal(newTarget, range, allowMove);
}
}
}

View File

@@ -19,10 +19,10 @@ namespace OpenRA.Mods.RA
class AutoHeal : INotifyIdle
{
public void TickIdle( Actor self )
public void TickIdle(Actor self)
{
var attack = self.Trait<AttackBase>();
var inRange = self.World.FindUnitsInCircle(self.CenterLocation, (int)(Game.CellSize * attack.GetMaximumRange()));
var inRange = self.World.FindUnitsInCircle(self.CenterPosition, attack.GetMaximumRange());
var target = inRange
.Where(a => a != self && a.AppearsFriendlyTo(self))
@@ -31,8 +31,8 @@ namespace OpenRA.Mods.RA
.Where(a => attack.HasAnyValidWeapons(Target.FromActor(a)))
.ClosestTo( self.CenterLocation );
if( target != null )
self.QueueActivity(attack.GetAttackActivity(self, Target.FromActor( target ), false ));
if (target != null)
self.QueueActivity(attack.GetAttackActivity(self, Target.FromActor(target), false));
}
}
}

View File

@@ -87,8 +87,9 @@ namespace OpenRA.Mods.RA
var target = ScanForTarget(self, null);
if (target != null)
{
self.SetTargetLine(Target.FromActor(target), Color.Red, false);
attack.AttackTarget(Target.FromActor(target), false, Info.AllowMovement && stance != UnitStance.Defend);
var t = Target.FromActor(target);
self.SetTargetLine(t, Color.Red, false);
attack.AttackTarget(t, false, Info.AllowMovement && stance != UnitStance.Defend);
}
}
@@ -100,10 +101,9 @@ namespace OpenRA.Mods.RA
public Actor ScanForTarget(Actor self, Actor currentTarget)
{
var range = Info.ScanRadius > 0 ? Info.ScanRadius : attack.GetMaximumRange();
if (self.IsIdle || currentTarget == null || !Combat.IsInRange(self.CenterLocation, range, currentTarget))
if(nextScanTime <= 0)
var range = Info.ScanRadius > 0 ? WRange.FromCells(Info.ScanRadius) : attack.GetMaximumRange();
if (self.IsIdle || currentTarget == null || !Target.FromActor(currentTarget).IsInRange(self.CenterPosition, range))
if (nextScanTime <= 0)
return ChooseTarget(self, range);
return currentTarget;
@@ -116,11 +116,10 @@ namespace OpenRA.Mods.RA
attack.AttackTarget(Target.FromActor(targetActor), false, Info.AllowMovement && stance != UnitStance.Defend);
}
Actor ChooseTarget(Actor self, float range)
Actor ChooseTarget(Actor self, WRange range)
{
nextScanTime = self.World.SharedRandom.Next(Info.MinimumScanTimeInterval, Info.MaximumScanTimeInterval);
var inRange = self.World.FindUnitsInCircle(self.CenterLocation, (int)(Game.CellSize * range));
var inRange = self.World.FindUnitsInCircle(self.CenterPosition, range);
if (self.Owner.HasFogVisibility())
{

View File

@@ -29,7 +29,7 @@ namespace OpenRA.Mods.RA
{
// Offset effective position to the top of the northernmost occupied cell
var bi = self.Info.Traits.GetOrDefault<BuildingInfo>();
offset = (bi != null) ? -FootprintUtils.CenterOffset(bi).Y : -512;
offset = ((bi != null) ? -FootprintUtils.CenterOffset(bi).Y : 0) - 512;
}
public IEnumerable<IRenderable> ModifyRender(Actor self, WorldRenderer wr, IEnumerable<IRenderable> r)

View File

@@ -17,7 +17,7 @@ namespace OpenRA.Mods.RA.Buildings
{
public class BaseProviderInfo : ITraitInfo
{
public readonly float Range = 10;
public readonly int Range = 10;
public readonly int Cooldown = 0;
public readonly int InitialDelay = 0;

View File

@@ -38,23 +38,20 @@ namespace OpenRA.Mods.RA.Buildings
public object Create(ActorInitializer init) { return new Building(init, this); }
public PPos CenterLocation(CPos topLeft)
public Actor FindBaseProvider(World world, Player p, CPos topLeft)
{
return (PPos)((2 * topLeft.ToInt2() + Dimensions) * Game.CellSize / 2);
}
bool HasBaseProvider(World world, Player p, CPos topLeft)
{
var center = CenterLocation(topLeft);
var center = topLeft.CenterPosition + FootprintUtils.CenterOffset(this);
foreach (var bp in world.ActorsWithTrait<BaseProvider>())
{
if (bp.Actor.Owner.Stances[p] != Stance.Ally || !bp.Trait.Ready())
continue;
if (Combat.IsInRange(center, bp.Trait.Info.Range, bp.Actor.CenterLocation))
return true;
// Range is counted from the center of the actor, not from each cell.
var target = Target.FromPos(bp.Actor.CenterPosition);
if (target.IsInRange(center, WRange.FromCells(bp.Trait.Info.Range)))
return bp.Actor;
}
return false;
return null;
}
public bool IsCloseEnoughToBase(World world, Player p, string buildingName, CPos topLeft)
@@ -62,7 +59,7 @@ namespace OpenRA.Mods.RA.Buildings
if (p.PlayerActor.Trait<DeveloperMode>().BuildAnywhere)
return true;
if (RequiresBaseProvider && !HasBaseProvider(world, p, topLeft))
if (RequiresBaseProvider && FindBaseProvider(world, p, topLeft) == null)
return false;
var buildingMaxBounds = (CVec)Dimensions;
@@ -126,7 +123,9 @@ namespace OpenRA.Mods.RA.Buildings
occupiedCells = FootprintUtils.UnpathableTiles( self.Info.Name, Info, TopLeft )
.Select(c => Pair.New(c, SubCell.FullCell)).ToArray();
pxPosition = Info.CenterLocation(topLeft);
var position = topLeft.CenterPosition + FootprintUtils.CenterOffset(Info);
pxPosition = PPos.FromWPosHackZ(position);
}
public int GetPowerUsage()

View File

@@ -65,7 +65,8 @@ namespace OpenRA.Mods.RA.Buildings
public static WVec CenterOffset(BuildingInfo buildingInfo)
{
var dim = buildingInfo.Dimensions;
return new CVec(dim.X, dim.Y).ToWVec() / 2;
// Offset is measured relative to the center of the cell, so need to subtract an additional half cell.
return new CVec(dim.X, dim.Y).ToWVec() / 2 - new WVec(512, 512, 0);
}
}
}

View File

@@ -14,25 +14,37 @@ using OpenRA.Traits;
namespace OpenRA.Mods.RA
{
class CarpetBombInfo : TraitInfo<CarpetBomb>
class CarpetBombInfo : ITraitInfo
{
[WeaponReference]
public readonly string Weapon = null;
public readonly int Range = 3;
public object Create(ActorInitializer init) { return new CarpetBomb(this); }
}
class CarpetBomb : ITick, ISync // TODO: maybe integrate this better with the normal weapons system?
// TODO: maybe integrate this better with the normal weapons system?
class CarpetBomb : ITick, ISync
{
[Sync] CPos Target;
[Sync] int dropDelay;
CarpetBombInfo info;
Target target;
public void SetTarget(CPos targetCell) { Target = targetCell; }
[Sync] int dropDelay;
[Sync] WRange range;
public CarpetBomb(CarpetBombInfo info)
{
this.info = info;
// TODO: Push this conversion into the yaml
range = WRange.FromCells(info.Range);
}
public void SetTarget(CPos targetCell) { target = Target.FromCell(targetCell); }
public void Tick(Actor self)
{
var info = self.Info.Traits.Get<CarpetBombInfo>();
if( !Combat.IsInRange( self.CenterLocation, info.Range, Target.ToPPos() ) )
if (!target.IsInRange(self.CenterPosition, range))
return;
var limitedAmmo = self.TraitOrDefault<LimitedAmmo>();

View File

@@ -45,7 +45,7 @@ namespace OpenRA.Mods.RA
if (explosionType != null)
world.AddFrameEndTask(
w => w.Add(new Explosion(w, args.dest, explosionType, isWater, args.destAltitude)));
w => w.Add(new Explosion(w, args.dest.ToWPos(args.destAltitude), explosionType)));
Sound.Play(GetImpactSound(warhead, isWater), args.dest);
@@ -139,12 +139,14 @@ namespace OpenRA.Mods.RA
}
}
public static void DoExplosion(Actor attacker, string weapontype, PPos pos, int altitude)
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 = pos,
dest = pos,
src = pxPos,
dest = pxPos,
srcAltitude = altitude,
destAltitude = altitude,
firedBy = attacker,
@@ -154,7 +156,7 @@ namespace OpenRA.Mods.RA
};
if (args.weapon.Report != null && args.weapon.Report.Any())
Sound.Play(args.weapon.Report.Random(attacker.World.SharedRandom), pos);
Sound.Play(args.weapon.Report.Random(attacker.World.SharedRandom), pxPos);
DoImpacts(args);
}
@@ -176,7 +178,8 @@ namespace OpenRA.Mods.RA
static float GetDamageToInflict(Actor target, ProjectileArgs args, WarheadInfo warhead, float modifier)
{
// don't hit air units with splash from ground explosions, etc
if (!WeaponValidForTarget(args.weapon, target)) return 0f;
if (!args.weapon.IsValidAgainst(target))
return 0f;
var health = target.Info.Traits.GetOrDefault<HealthInfo>();
if( health == null ) return 0f;
@@ -188,48 +191,5 @@ namespace OpenRA.Mods.RA
return (float)(rawDamage * multiplier);
}
public static bool WeaponValidForTarget(WeaponInfo weapon, Actor target)
{
var targetable = target.TraitOrDefault<ITargetable>();
if (targetable == null || !weapon.ValidTargets.Intersect(targetable.TargetTypes).Any())
return false;
if (weapon.Warheads.All( w => w.EffectivenessAgainst(target) <= 0))
return false;
return true;
}
public static bool WeaponValidForTarget(WeaponInfo weapon, World world, CPos location)
{
if (weapon.ValidTargets.Contains("Ground") && world.GetTerrainType(location) != "Water") return true;
if (weapon.ValidTargets.Contains("Water") && world.GetTerrainType(location) == "Water") return true;
return false;
}
public static bool IsInRange(PPos attackOrigin, float range, Actor target)
{
var rsq = range * range * Game.CellSize * Game.CellSize;
foreach (var cell in target.Trait<ITargetable>().TargetableCells(target))
if ((attackOrigin - Util.CenterOfCell(cell)).LengthSquared <= rsq)
return true;
return false;
}
public static bool IsInRange(PPos attackOrigin, float range, PPos targetLocation)
{
var rsq = range * range * Game.CellSize * Game.CellSize;
return (attackOrigin - targetLocation).LengthSquared <= rsq;
}
public static bool IsInRange(PPos attackOrigin, float range, Target target)
{
if (!target.IsValid) return false;
if (target.IsActor)
return IsInRange(attackOrigin, range, target.Actor);
else
return IsInRange(attackOrigin, range, target.CenterLocation);
}
}
}

View File

@@ -27,7 +27,7 @@ namespace OpenRA.Mods.RA
public override void Activate(Actor collector)
{
Combat.DoExplosion(self, (info as ExplodeCrateActionInfo).Weapon, collector.CenterLocation, 0);
Combat.DoExplosion(self, (info as ExplodeCrateActionInfo).Weapon, collector.CenterPosition);
base.Activate(collector);
}
}

View File

@@ -18,26 +18,20 @@ namespace OpenRA.Mods.RA.Effects
public class Explosion : IEffect
{
Animation anim;
PPos pos;
int altitude;
WPos pos;
public Explosion(World world, PPos pixelPos, string style, bool isWater, int altitude)
public Explosion(World world, WPos pos, string style)
{
this.pos = pixelPos;
this.altitude = altitude;
this.pos = pos;
anim = new Animation("explosion");
anim.PlayThen(style,
() => world.AddFrameEndTask(w => w.Remove(this)));
anim.PlayThen(style, () => world.AddFrameEndTask(w => w.Remove(this)));
}
public void Tick( World world ) { anim.Tick(); }
public IEnumerable<IRenderable> Render(WorldRenderer wr)
{
var p = pos.ToInt2() - new int2(0, altitude);
yield return new SpriteRenderable(anim.Image, p, wr.Palette("effect"), p.Y);
yield return new SpriteRenderable(anim.Image, pos, 0, wr.Palette("effect"), 1f);
}
public Player Owner { get { return null; } }
}
}

View File

@@ -66,7 +66,7 @@ namespace OpenRA.Mods.RA.Effects
void Explode(World world)
{
world.AddFrameEndTask(w => w.Remove(this));
Combat.DoExplosion(firedBy.PlayerActor, weapon, PPos.FromWPos(pos), pos.Z * Game.CellSize / 1024);
Combat.DoExplosion(firedBy.PlayerActor, weapon, pos);
world.WorldActor.Trait<ScreenShaker>().AddEffect(20, PPos.FromWPos(pos).ToFloat2(), 5);
foreach (var a in world.ActorsWithTrait<NukePaletteEffect>())

View File

@@ -36,7 +36,7 @@ namespace OpenRA.Mods.RA.Effects
parachuteOffset = pai.Offset;
// Adjust x,y to match the target subcell
cargo.Trait<ITeleportable>().SetPosition(cargo, new CPos(dropPosition));
cargo.Trait<ITeleportable>().SetPosition(cargo, dropPosition.ToCPos());
var cp = cargo.CenterPosition;
pos = new WPos(cp.X, cp.Y, dropPosition.Z);
}

View File

@@ -42,11 +42,7 @@ namespace OpenRA.Mods.RA
var weapon = ChooseWeaponForExplosion(self);
if (weapon != null)
{
var move = self.TraitOrDefault<IMove>();
var altitude = move != null ? move.Altitude : 0;
Combat.DoExplosion(e.Attacker, weapon, self.CenterLocation, altitude);
}
Combat.DoExplosion(e.Attacker, weapon, self.CenterPosition);
}
string ChooseWeaponForExplosion(Actor self)

View File

@@ -250,7 +250,7 @@ namespace OpenRA.Mods.RA
return new Order(order.OrderID, self, queued) { TargetActor = target.Actor };
if (order.OrderID == "Harvest")
return new Order(order.OrderID, self, queued) { TargetLocation = target.CenterLocation.ToCPos() };
return new Order(order.OrderID, self, queued) { TargetLocation = target.CenterPosition.ToCPos() };
return null;
}

View File

@@ -46,7 +46,7 @@ namespace OpenRA.Mods.RA
{
if( order is BeginMinefieldOrderTargeter )
{
var start = target.CenterLocation.ToCPos();
var start = target.CenterPosition.ToCPos();
self.World.OrderGenerator = new MinefieldOrderGenerator( self, start );
return new Order("BeginMinefield", self, false) { TargetLocation = start };
}

View File

@@ -297,7 +297,7 @@ namespace OpenRA.Mods.RA.Missions
var enemy = FirstUnshroudedOrDefault(enemies.OrderBy(u => (self.CenterLocation - u.CenterLocation).LengthSquared), world, 10);
if (enemy != null)
self.QueueActivity(new AttackMove.AttackMoveActivity(self, new Attack(Target.FromActor(enemy), 3)));
self.QueueActivity(new AttackMove.AttackMoveActivity(self, new Attack(Target.FromActor(enemy), WRange.FromCells(3))));
}
void ManageSovietUnits()

View File

@@ -125,7 +125,7 @@ namespace OpenRA.Mods.RA.Missions
if (world.FrameNumber == frameInfiltrated + 1500 * 12 && !bridgeTank.IsDead() && bridgeTank.IsInWorld && !bridge.IsDead())
{
bridgeTank.QueueActivity(new Attack(Target.FromPos(bridge.CenterLocation), 4));
bridgeTank.QueueActivity(new Attack(Target.FromPos(bridge.CenterPosition), WRange.FromCells(4)));
attackingBridge = true;
}
if (attackingBridge && bridge.IsDead())
@@ -152,7 +152,7 @@ namespace OpenRA.Mods.RA.Missions
var enemy = enemies.OrderBy(u => (attacker.CenterLocation - u.CenterLocation).LengthSquared).FirstOrDefault();
if (enemy != null)
attacker.QueueActivity(new AttackMove.AttackMoveActivity(attacker, new Attack(Target.FromActor(enemy), 3)));
attacker.QueueActivity(new AttackMove.AttackMoveActivity(attacker, new Attack(Target.FromActor(enemy), WRange.FromCells(3))));
else
{
attackingTown = false;

View File

@@ -82,7 +82,7 @@ namespace OpenRA.Mods.RA.Missions
var targetEnemy = enemies.OrderBy(u => (self.CenterLocation - u.CenterLocation).LengthSquared).FirstOrDefault();
if (targetEnemy != null)
{
self.QueueActivity(new AttackMove.AttackMoveActivity(self, new Attack(Target.FromActor(targetEnemy), 6)));
self.QueueActivity(new AttackMove.AttackMoveActivity(self, new Attack(Target.FromActor(targetEnemy), WRange.FromCells(6))));
}
}

View File

@@ -213,7 +213,7 @@ namespace OpenRA.Mods.RA.Missions
var enemy = enemies.OrderBy(u => (self.CenterLocation - u.CenterLocation).LengthSquared).FirstOrDefault();
if (enemy != null)
self.QueueActivity(queued, new AttackMove.AttackMoveActivity(self, new Attack(Target.FromActor(enemy), 3)));
self.QueueActivity(queued, new AttackMove.AttackMoveActivity(self, new Attack(Target.FromActor(enemy), WRange.FromCells(3))));
}
}

View File

@@ -258,7 +258,7 @@ namespace OpenRA.Mods.RA.Missions
{
Sound.Play("reinfor1.aud");
foreach (var unit in units)
world.CreateActor(unit, greece, startEntryPoint.Location, Util.GetFacing(startBridgeEndPoint.CenterLocation - startEntryPoint.CenterLocation, 0))
world.CreateActor(unit, greece, startEntryPoint.Location, Util.GetFacing(startBridgeEndPoint.CenterPosition - startEntryPoint.CenterPosition, 0))
.QueueActivity(new Move.Move(startMovePoint.Location, 0));
}
@@ -266,7 +266,7 @@ namespace OpenRA.Mods.RA.Missions
{
Sound.Play("reinfor1.aud");
foreach (var unit in units)
world.CreateActor(unit, greece, alliedBaseEntryPoint.Location, Util.GetFacing(alliedBaseMovePoint.CenterLocation - alliedBaseEntryPoint.CenterLocation, 0))
world.CreateActor(unit, greece, alliedBaseEntryPoint.Location, Util.GetFacing(alliedBaseMovePoint.CenterPosition - alliedBaseEntryPoint.CenterPosition, 0))
.QueueActivity(new Move.Move(alliedBaseMovePoint.Location, 0));
}

View File

@@ -128,7 +128,7 @@ namespace OpenRA.Mods.RA.Missions
.Where(a => a.HasTrait<Bridge>() && !a.IsDead())
.OrderBy(a => (startJeep.CenterLocation - a.CenterLocation).LengthSquared)
.First();
Combat.DoExplosion(bridge, "Demolish", bridge.CenterLocation, 0);
Combat.DoExplosion(bridge, "Demolish", bridge.CenterPosition);
world.WorldActor.Trait<ScreenShaker>().AddEffect(15, bridge.CenterLocation.ToFloat2(), 6);
bridge.Kill(bridge);
}));

View File

@@ -214,7 +214,7 @@ namespace OpenRA.Mods.RA.Missions
var targetEnemy = enemies.OrderBy(u => (self.CenterLocation - u.CenterLocation).LengthSquared).FirstOrDefault();
if (targetEnemy != null)
{
self.QueueActivity(new AttackMove.AttackMoveActivity(self, new Attack(Target.FromActor(targetEnemy), 3)));
self.QueueActivity(new AttackMove.AttackMoveActivity(self, new Attack(Target.FromActor(targetEnemy), WRange.FromCells(3))));
}
}

View File

@@ -139,7 +139,7 @@ namespace OpenRA.Mods.RA.Missions
var enemy = FirstUnshroudedOrDefault(enemies.OrderBy(u => (self.CenterLocation - u.CenterLocation).LengthSquared), world, 20);
if (enemy != null)
self.QueueActivity(new AttackMove.AttackMoveActivity(self, new Attack(Target.FromActor(enemy), 3)));
self.QueueActivity(new AttackMove.AttackMoveActivity(self, new Attack(Target.FromActor(enemy), WRange.FromCells(3))));
}
void SpawnAndAttack(string[] squad, Player owner, CPos location)

View File

@@ -15,25 +15,25 @@ namespace OpenRA.Mods.RA.Move
{
public class Drag : Activity
{
PPos endLocation;
PPos startLocation;
WPos start, end;
int length;
int ticks = 0;
public Drag(PPos start, PPos end, int length)
public Drag(WPos start, WPos end, int length)
{
startLocation = start;
endLocation = end;
this.start = start;
this.end = end;
this.length = length;
}
public override Activity Tick( Actor self )
{
var mobile = self.Trait<Mobile>();
mobile.PxPosition = length > 1
? PPos.Lerp(startLocation, endLocation, ticks, length - 1)
: endLocation;
var pos = length > 1
? WPos.Lerp(start, end, ticks, length - 1)
: end;
mobile.PxPosition = PPos.FromWPos(pos);
if (++ticks >= length)
{
mobile.IsMoving = false;
@@ -46,7 +46,7 @@ namespace OpenRA.Mods.RA.Move
public override IEnumerable<Target> GetTargets( Actor self )
{
yield return Target.FromPos(endLocation);
yield return Target.FromPos(end);
}
// Cannot be cancelled

View File

@@ -81,7 +81,7 @@ namespace OpenRA.Mods.RA.Move
return passability.ToBits();
}
public readonly Dictionary<SubCell, PVecInt> SubCellOffsets = new Dictionary<SubCell, PVecInt>()
public static readonly Dictionary<SubCell, PVecInt> SubCellOffsets = new Dictionary<SubCell, PVecInt>()
{
{SubCell.TopLeft, new PVecInt(-7,-6)},
{SubCell.TopRight, new PVecInt(6,-6)},
@@ -170,7 +170,9 @@ namespace OpenRA.Mods.RA.Move
public void SetLocation(CPos from, SubCell fromSub, CPos to, SubCell toSub)
{
if (fromCell == from && toCell == to) return;
if (fromCell == from && toCell == to && fromSubCell == fromSub && toSubCell == toSub)
return;
RemoveInfluence();
__fromCell = from;
__toCell = to;
@@ -197,7 +199,7 @@ namespace OpenRA.Mods.RA.Move
if (init.Contains<LocationInit>())
{
this.__fromCell = this.__toCell = init.Get<LocationInit, CPos>();
this.PxPosition = Util.CenterOfCell(fromCell) + info.SubCellOffsets[fromSubCell];
this.PxPosition = Util.CenterOfCell(fromCell) + MobileInfo.SubCellOffsets[fromSubCell];
}
this.Facing = init.Contains<FacingInit>() ? init.Get<FacingInit, int>() : info.InitialFacing;
@@ -207,7 +209,7 @@ namespace OpenRA.Mods.RA.Move
public void SetPosition(Actor self, CPos cell)
{
SetLocation(cell,fromSubCell, cell,fromSubCell);
PxPosition = Util.CenterOfCell(fromCell) + Info.SubCellOffsets[fromSubCell];
PxPosition = Util.CenterOfCell(fromCell) + MobileInfo.SubCellOffsets[fromSubCell];
FinishedMoving(self);
}
@@ -232,7 +234,7 @@ namespace OpenRA.Mods.RA.Move
if (order is MoveOrderTargeter)
{
if (Info.OnRails) return null;
return new Order("Move", self, queued) { TargetLocation = target.CenterLocation.ToCPos() };
return new Order("Move", self, queued) { TargetLocation = target.CenterPosition.ToCPos() };
}
return null;
}
@@ -418,6 +420,11 @@ namespace OpenRA.Mods.RA.Move
return (int)(speed / 100);
}
public int WorldMovementSpeedForCell(Actor self, CPos cell)
{
return MovementSpeedForCell(self, cell) * 1024 / Game.CellSize;
}
public void AddInfluence()
{
if (self.IsInWorld)

View File

@@ -58,11 +58,18 @@ namespace OpenRA.Mods.RA.Move
this.ignoreBuilding = ignoreBuilding;
}
static readonly List<CPos> NoPath = new List<CPos>();
public Move(Target target, int range)
{
this.getPath = (self,mobile) => self.World.WorldActor.Trait<PathFinder>().FindUnitPathToRange(
mobile.toCell, target.CenterLocation.ToCPos(),
range, self);
this.getPath = (self, mobile) =>
{
if (!target.IsValid)
return NoPath;
return self.World.WorldActor.Trait<PathFinder>().FindUnitPathToRange(
mobile.toCell, target.CenterPosition.ToCPos(), range, self);
};
this.destination = null;
this.nearEnough = range;
}
@@ -144,8 +151,8 @@ namespace OpenRA.Mods.RA.Move
mobile.SetLocation( mobile.fromCell, mobile.fromSubCell, nextCell.Value.First, nextCell.Value.Second );
var move = new MoveFirstHalf(
this,
Util.CenterOfCell( mobile.fromCell ) + mobile.Info.SubCellOffsets[mobile.fromSubCell],
Util.BetweenCells( mobile.fromCell, mobile.toCell ) + (mobile.Info.SubCellOffsets[mobile.fromSubCell] + mobile.Info.SubCellOffsets[mobile.toSubCell] ) / 2,
Util.CenterOfCell( mobile.fromCell ) + MobileInfo.SubCellOffsets[mobile.fromSubCell],
Util.BetweenCells( mobile.fromCell, mobile.toCell ) + (MobileInfo.SubCellOffsets[mobile.fromSubCell] + MobileInfo.SubCellOffsets[mobile.toSubCell] ) / 2,
mobile.Facing,
mobile.Facing,
0 );
@@ -335,15 +342,15 @@ namespace OpenRA.Mods.RA.Move
protected override MovePart OnComplete( Actor self, Mobile mobile, Move parent )
{
var fromSubcellOffset = mobile.Info.SubCellOffsets[mobile.fromSubCell];
var toSubcellOffset = mobile.Info.SubCellOffsets[mobile.toSubCell];
var fromSubcellOffset = MobileInfo.SubCellOffsets[mobile.fromSubCell];
var toSubcellOffset = MobileInfo.SubCellOffsets[mobile.toSubCell];
var nextCell = parent.PopPath( self, mobile );
if( nextCell != null )
{
if(IsTurn(mobile, nextCell.Value.First))
{
var nextSubcellOffset = mobile.Info.SubCellOffsets[nextCell.Value.Second];
var nextSubcellOffset = MobileInfo.SubCellOffsets[nextCell.Value.Second];
var ret = new MoveFirstHalf(
move,
Util.BetweenCells( mobile.fromCell, mobile.toCell ) + (fromSubcellOffset + toSubcellOffset) / 2,

View File

@@ -98,7 +98,7 @@ namespace OpenRA.Mods.RA.Orders
initialized = true;
}
var offset = (topLeft - CPos.Zero).ToWVec() + FootprintUtils.CenterOffset(BuildingInfo);
var offset = topLeft.CenterPosition + FootprintUtils.CenterOffset(BuildingInfo) - WPos.Zero;
foreach (var r in preview)
r.WithPos(r.Pos + offset).Render(wr);

View File

@@ -44,7 +44,7 @@ namespace OpenRA.Mods.RA
if (self.World.FrameNumber - lastAttackTime > info.NotifyInterval * 25)
Sound.PlayNotification(self.Owner, "Speech", "BaseAttack", self.Owner.Country.Race);
lastAttackLocation = self.CenterLocation.ToCPos();
lastAttackLocation = self.CenterPosition.ToCPos();
lastAttackTime = self.World.FrameNumber;
}
}

View File

@@ -44,7 +44,7 @@ namespace OpenRA.Mods.RA
if (self.World.FrameNumber - lastAttackTime > info.NotifyInterval * 25)
Sound.PlayNotification(self.Owner, "Speech", "HarvesterAttack", self.Owner.Country.Race);
lastAttackLocation = self.CenterLocation.ToCPos();
lastAttackLocation = self.CenterPosition.ToCPos();
lastAttackTime = self.World.FrameNumber;
}
}

View File

@@ -83,18 +83,11 @@ namespace OpenRA.Mods.RA
if (buildingInfo.RequiresBaseProvider)
{
var center = buildingInfo.CenterLocation(order.TargetLocation);
foreach (var bp in w.ActorsWithTrait<BaseProvider>())
{
if (bp.Actor.Owner.Stances[self.Owner] != Stance.Ally || !bp.Trait.Ready())
continue;
if (Combat.IsInRange(center, bp.Trait.Info.Range, bp.Actor.CenterLocation))
{
bp.Trait.BeginCooldown();
break;
}
}
// May be null if the build anywhere cheat is active
// BuildingInfo.IsCloseEnoughToBase has already verified that this is a valid build location
var producer = buildingInfo.FindBaseProvider(w, self.Owner, order.TargetLocation);
if (producer != null)
producer.Trait<BaseProvider>().BeginCooldown();
}
if (GetNumBuildables(self.Owner) > prevItems)

View File

@@ -50,8 +50,8 @@ namespace OpenRA.Mods.RA
public void DoProduction(Actor self, ActorInfo producee, ExitInfo exitinfo)
{
var exit = self.Location + exitinfo.ExitCellVector;
var spawn = self.Trait<IHasLocation>().PxPosition + exitinfo.SpawnOffsetVector;
var to = Util.CenterOfCell(exit);
var spawn = (self.Trait<IHasLocation>().PxPosition + exitinfo.SpawnOffsetVector).ToWPos(0);
var to = exit.CenterPosition;
var fi = producee.Traits.Get<IFacingInfo>();
var initialFacing = exitinfo.Facing < 0 ? Util.GetFacing(to - spawn, fi.GetInitialFacing()) : exitinfo.Facing;
@@ -66,7 +66,7 @@ namespace OpenRA.Mods.RA
// TODO: Move this into an *Init
// TODO: We should be adjusting the actual position for aircraft, not just visuals.
var teleportable = newUnit.Trait<ITeleportable>();
teleportable.AdjustPxPosition(newUnit, spawn);
teleportable.AdjustPxPosition(newUnit, PPos.FromWPos(spawn));
// TODO: Generalize this for non-mobile (e.g. aircraft) too
// Remember to update the Enter activity too
@@ -74,7 +74,7 @@ namespace OpenRA.Mods.RA
if (mobile != null)
{
// Animate the spawn -> exit transition
var speed = mobile.MovementSpeedForCell(newUnit, exit);
var speed = mobile.WorldMovementSpeedForCell(newUnit, exit);
var length = speed > 0 ? (int)((to - spawn).Length * 3 / speed) : 0;
newUnit.QueueActivity(new Drag(spawn, to, length));
}

View File

@@ -42,7 +42,7 @@ namespace OpenRA.Mods.RA
public Order IssueOrder( Actor self, IOrderTargeter order, Target target, bool queued )
{
if( order.OrderID == "SetRallyPoint" )
return new Order(order.OrderID, self, false) { TargetLocation = target.CenterLocation.ToCPos() };
return new Order(order.OrderID, self, false) { TargetLocation = target.CenterPosition.ToCPos() };
return null;
}

View File

@@ -49,10 +49,12 @@ namespace OpenRA.Mods.RA.Render
: base(init, info)
{
roof = new Animation(GetImage(init.self));
var bi = init.self.Info.Traits.Get<BuildingInfo>();
// Additional 512 units move from center -> top of cell
var offset = FootprintUtils.CenterOffset(bi).Y + 512;
anims.Add("roof", new AnimationWithOffset(roof, null,
() => !buildComplete, FootprintUtils.CenterOffset(bi).Y));
() => !buildComplete, offset));
}
public void BuildingComplete( Actor self )

View File

@@ -22,7 +22,7 @@ namespace OpenRA.Mods.RA.Render
var bi = self.Info.Traits.Get<BuildingInfo>();
FootprintUtils.UnpathableTiles(self.Info.Name, bi, self.Location).Do(
t => self.World.AddFrameEndTask(
w => w.Add(new Explosion(w, Traits.Util.CenterOfCell(t), "building", false, 0))));
w => w.Add(new Explosion(w, t.CenterPosition, "building"))));
}
}
}

View File

@@ -47,9 +47,11 @@ namespace OpenRA.Mods.RA
if (self.Owner != self.World.LocalPlayer)
return;
// Hack: Convert world coords to cells
var pxRange = self.Trait<AttackBase>().GetMaximumRange().Range / 1024f;
wr.DrawRangeCircleWithContrast(
Color.FromArgb(128, Color.Yellow),
self.CenterLocation.ToFloat2(), self.Trait<AttackBase>().GetMaximumRange(),
self.CenterLocation.ToFloat2(), pxRange,
Color.FromArgb(96, Color.Black),
1);
}

View File

@@ -78,7 +78,7 @@ namespace OpenRA.Mods.RA
self.CancelActivity();
self.QueueActivity(new MoveAdjacentTo(target));
self.QueueActivity(mobile.MoveTo(order.TargetActor.CenterLocation.ToCPos(), order.TargetActor));
self.QueueActivity(mobile.MoveTo(order.TargetActor.CenterPosition.ToCPos(), order.TargetActor));
self.QueueActivity(new Rearm(self));
self.QueueActivity(new Repair(order.TargetActor));

View File

@@ -43,7 +43,7 @@ namespace OpenRA.Mods.RA
{
var position = self.CenterPosition;
if (position.Z > 0 && self.GetDamageState() >= info.MinDamage &&
!self.World.FogObscures(new CPos(position)))
!self.World.FogObscures(position.ToCPos()))
{
var offset = info.Offset.Rotate(body.QuantizeOrientation(self, self.Orientation));
var pos = position + body.LocalToWorld(offset);

View File

@@ -33,7 +33,7 @@ namespace OpenRA.Mods.RA
{
new LocationInit( enterCell ),
new OwnerInit( self.Owner ),
new FacingInit( Util.GetFacing((order.TargetLocation - enterCell).ToInt2(), 0) ),
new FacingInit( Util.GetFacing(order.TargetLocation - enterCell, 0) ),
new AltitudeInit( Rules.Info["u2"].Traits.Get<PlaneInfo>().CruiseAltitude ),
});

View File

@@ -54,7 +54,7 @@ namespace OpenRA.Mods.RA
public virtual IEnumerable<CPos> TargetableCells( Actor self )
{
yield return self.CenterLocation.ToCPos();
yield return self.CenterPosition.ToCPos();
}
}
}

View File

@@ -22,14 +22,20 @@ namespace OpenRA.Mods.RA
[Desc("Initial position relative to body")]
public readonly WVec Offset = WVec.Zero;
[Desc("Minimum distance to throw the particle")]
public readonly WRange MinThrowRange = new WRange(256);
[Desc("Maximum distance to throw the particle")]
public readonly WRange ThrowRange = new WRange(768);
public readonly WRange MaxThrowRange = new WRange(768);
[Desc("Maximum height to throw the particle")]
public readonly WRange ThrowHeight = new WRange(256);
[Desc("Minimum angle to throw the particle")]
public readonly WAngle MinThrowAngle = WAngle.FromDegrees(30);
[Desc("Number of ticks to animate")]
public readonly int Length = 15;
[Desc("Maximum angle to throw the particle")]
public readonly WAngle MaxThrowAngle = WAngle.FromDegrees(60);
[Desc("Speed to throw the particle (horizontal WPos/tick)")]
public readonly int Velocity = 75;
[Desc("Maximum rotation rate")]
public readonly float ROT = 15;
@@ -39,19 +45,19 @@ namespace OpenRA.Mods.RA
class ThrowsParticle : ITick
{
ThrowsParticleInfo info;
WVec pos;
WVec initialPos;
WVec finalPos;
WAngle angle;
int tick = 0;
int length;
float facing;
float rotation;
public ThrowsParticle(ActorInitializer init, ThrowsParticleInfo info)
{
this.info = info;
var self = init.self;
var rs = self.Trait<RenderSimple>();
var body = self.Trait<IBodyOrientation>();
@@ -62,10 +68,12 @@ namespace OpenRA.Mods.RA
// Calculate final position
var throwRotation = WRot.FromFacing(Game.CosmeticRandom.Next(1024));
var throwOffset = new WVec((int)(Game.CosmeticRandom.Gauss1D(1)*info.ThrowRange.Range), 0, 0).Rotate(throwRotation);
var throwDistance = Game.CosmeticRandom.Next(info.MinThrowRange.Range, info.MaxThrowRange.Range);
initialPos = pos = info.Offset.Rotate(body.QuantizeOrientation(self, WRot.FromFacing(bodyFacing)));
finalPos = initialPos + throwOffset;
finalPos = initialPos + new WVec(throwDistance, 0, 0).Rotate(throwRotation);
angle = new WAngle(Game.CosmeticRandom.Next(info.MinThrowAngle.Angle, info.MaxThrowAngle.Angle));
length = (finalPos - initialPos).Length / info.Velocity;
// Facing rotation
rotation = Game.CosmeticRandom.Gauss1D(2) * info.ROT;
@@ -77,15 +85,10 @@ namespace OpenRA.Mods.RA
public void Tick(Actor self)
{
if (tick == info.Length)
if (tick == length)
return;
tick++;
// Lerp position horizontally and height along a sinusoid using a cubic ease
var t = (tick*tick*tick / (info.Length*info.Length) - 3*tick*tick / info.Length + 3*tick);
var tp = WVec.Lerp(initialPos, finalPos, t, info.Length);
var th = new WAngle(512*(info.Length - t) / info.Length).Sin()*info.ThrowHeight.Range / 1024;
pos = new WVec(tp.X, tp.Y, th);
pos = WVec.LerpQuadratic(initialPos, finalPos, angle, tick++, length);
// Spin the particle
facing += rotation;

View File

@@ -71,7 +71,7 @@ namespace OpenRA.Mods.RA
public bool FaceTarget(Actor self, Target target)
{
desiredFacing = Util.GetFacing(target.CenterLocation - self.CenterLocation, turretFacing);
desiredFacing = Util.GetFacing(target.CenterPosition - self.CenterPosition, turretFacing);
return turretFacing == desiredFacing;
}

View File

@@ -223,6 +223,7 @@ ionsfx:
idle:
Start: 0
Length: *
Offset: 0, -78
bomblet:
idle: