Rewrite bullets using world coords and improved trails.
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -28,171 +29,145 @@ namespace OpenRA.Mods.RA.Effects
|
|||||||
public readonly string Image = null;
|
public readonly string Image = null;
|
||||||
[Desc("Check for whether an actor with Wall: trait blocks fire")]
|
[Desc("Check for whether an actor with Wall: trait blocks fire")]
|
||||||
public readonly bool High = false;
|
public readonly bool High = false;
|
||||||
public readonly int RangeLimit = 0;
|
|
||||||
public readonly int Arm = 0;
|
|
||||||
public readonly bool Shadow = false;
|
public readonly bool Shadow = false;
|
||||||
public readonly bool Proximity = false;
|
|
||||||
public readonly float Angle = 0;
|
public readonly float Angle = 0;
|
||||||
public readonly int TrailInterval = 2;
|
public readonly int TrailInterval = 2;
|
||||||
|
public readonly int TrailDelay = 1;
|
||||||
public readonly int ContrailLength = 0;
|
public readonly int ContrailLength = 0;
|
||||||
public readonly Color ContrailColor = Color.White;
|
public readonly Color ContrailColor = Color.White;
|
||||||
public readonly bool ContrailUsePlayerColor = false;
|
public readonly bool ContrailUsePlayerColor = false;
|
||||||
public readonly int ContrailDelay = 1;
|
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
|
public class Bullet : IEffect
|
||||||
{
|
{
|
||||||
readonly BulletInfo Info;
|
readonly BulletInfo info;
|
||||||
readonly ProjectileArgs Args;
|
readonly ProjectileArgs args;
|
||||||
PPos src, dest;
|
|
||||||
int srcAltitude, destAltitude;
|
|
||||||
|
|
||||||
int t = 0;
|
ContrailRenderable trail;
|
||||||
Animation anim;
|
Animation anim;
|
||||||
|
WAngle angle;
|
||||||
const int BaseBulletSpeed = 100; /* pixels / 40ms frame */
|
WPos pos, target;
|
||||||
ContrailRenderable Trail;
|
int length;
|
||||||
|
int facing;
|
||||||
|
int ticks, smokeTicks;
|
||||||
|
|
||||||
public Bullet(BulletInfo info, ProjectileArgs args)
|
public Bullet(BulletInfo info, ProjectileArgs args)
|
||||||
{
|
{
|
||||||
Info = info;
|
this.info = info;
|
||||||
Args = args;
|
this.args = args;
|
||||||
|
this.pos = args.source;
|
||||||
|
|
||||||
src = PPos.FromWPos(args.source);
|
// Convert ProjectileArg definitions to world coordinates
|
||||||
srcAltitude = args.source.Z * Game.CellSize / 1024;
|
// TODO: Change the yaml definitions so we don't need this
|
||||||
|
var range = new WRange((int)(1024 * args.weapon.Range)); // Range in world units
|
||||||
dest = PPos.FromWPos(args.passiveTarget);
|
var inaccuracy = new WRange((int)(info.Inaccuracy * 1024 / Game.CellSize)); // Offset in world units at max range
|
||||||
destAltitude = args.passiveTarget.Z * Game.CellSize / 1024;
|
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)
|
if (info.Inaccuracy > 0)
|
||||||
{
|
{
|
||||||
var factor = ((dest - src).ToCVec().Length) / args.weapon.Range;
|
var maxOffset = inaccuracy.Range * (target - args.source).Length / range.Range;
|
||||||
dest += (PVecInt) (info.Inaccuracy * factor * args.sourceActor.World.SharedRandom.Gauss2D(2)).ToInt2();
|
target += WVec.FromPDF(args.sourceActor.World.SharedRandom, 2) * maxOffset / 1024;
|
||||||
Log.Write("debug", "Bullet with Inaccuracy; factor: #{0}; Projectile dest: {1}", factor, dest);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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");
|
anim.PlayRepeating("idle");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Info.ContrailLength > 0)
|
if (info.ContrailLength > 0)
|
||||||
{
|
{
|
||||||
var color = Info.ContrailUsePlayerColor ? ContrailRenderable.ChooseColor(args.sourceActor) : Info.ContrailColor;
|
var color = info.ContrailUsePlayerColor ? ContrailRenderable.ChooseColor(args.sourceActor) : info.ContrailColor;
|
||||||
Trail = new ContrailRenderable(args.sourceActor.World, color, Info.ContrailLength, Info.ContrailDelay, 0);
|
trail = new ContrailRenderable(args.sourceActor.World, color, info.ContrailLength, info.ContrailDelay, 0);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
int TotalTime() { return (dest - src).Length * BaseBulletSpeed / Info.Speed; }
|
smokeTicks = info.TrailDelay;
|
||||||
|
|
||||||
float GetAltitude()
|
|
||||||
{
|
|
||||||
var at = (float)t / TotalTime();
|
|
||||||
return (dest - src).Length * Info.Angle * 4 * at * (1 - at);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int GetEffectiveFacing()
|
int GetEffectiveFacing()
|
||||||
{
|
{
|
||||||
var at = (float)t / TotalTime();
|
var at = (float)ticks / (length - 1);
|
||||||
var attitude = Info.Angle * (1 - 2 * at);
|
var attitude = angle.Tan() * (1 - 2 * at) / (4 * 1024);
|
||||||
|
|
||||||
var rawFacing = Traits.Util.GetFacing(dest - src, 0);
|
var u = (facing % 128) / 128f;
|
||||||
var u = (rawFacing % 128) / 128f;
|
|
||||||
var scale = 512 * u * (1 - u);
|
var scale = 512 * u * (1 - u);
|
||||||
|
|
||||||
return (int)(rawFacing < 128
|
return (int)(facing < 128
|
||||||
? rawFacing - scale * attitude
|
? facing - scale * attitude
|
||||||
: rawFacing + scale * attitude);
|
: facing + scale * attitude);
|
||||||
}
|
}
|
||||||
|
|
||||||
int ticksToNextSmoke;
|
public void Tick(World world)
|
||||||
|
|
||||||
public void Tick( World world )
|
|
||||||
{
|
{
|
||||||
t += 40;
|
// Fade the trail out gradually
|
||||||
|
if (ticks > length && info.ContrailLength > 0)
|
||||||
if (anim != null) anim.Tick();
|
|
||||||
|
|
||||||
if (t > TotalTime()) Explode( world );
|
|
||||||
|
|
||||||
{
|
{
|
||||||
var at = (float)t / TotalTime();
|
trail.Update(pos);
|
||||||
var altitude = float2.Lerp(srcAltitude, destAltitude, at);
|
return;
|
||||||
var pos = float2.Lerp(src.ToFloat2(), 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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Info.High) // check for hitting a wall
|
if (anim != null)
|
||||||
{
|
anim.Tick();
|
||||||
var at = (float)t / TotalTime();
|
|
||||||
var pos = float2.Lerp(src.ToFloat2(), dest.ToFloat2(), at);
|
|
||||||
var cell = ((PPos) pos.ToInt2()).ToCPos();
|
|
||||||
|
|
||||||
if (world.ActorMap.GetUnitsAt(cell).Any(
|
pos = WPos.LerpQuadratic(args.source, target, angle, ticks, length);
|
||||||
a => a.HasTrait<IBlocksBullets>()))
|
|
||||||
{
|
if (info.Trail != null && --smokeTicks < 0)
|
||||||
dest = (PPos) pos.ToInt2();
|
{
|
||||||
Explode(world);
|
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)
|
public IEnumerable<IRenderable> Render(WorldRenderer wr)
|
||||||
{
|
{
|
||||||
if (Info.ContrailLength > 0)
|
if (info.ContrailLength > 0)
|
||||||
yield return Trail;
|
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();
|
if (info.Shadow)
|
||||||
|
|
||||||
var altitude = float2.Lerp(srcAltitude, destAltitude, at);
|
|
||||||
var pos = float2.Lerp(src.ToFloat2(), dest.ToFloat2(), at) - new float2(0, altitude);
|
|
||||||
|
|
||||||
var cell = ((PPos)pos.ToInt2()).ToCPos();
|
|
||||||
if (!Args.sourceActor.World.FogObscures(cell))
|
|
||||||
{
|
{
|
||||||
if (Info.High || Info.Angle > 0)
|
var shadowPos = pos - new WVec(0, 0, pos.Z);
|
||||||
{
|
foreach (var r in anim.Render(shadowPos, wr.Palette("shadow")))
|
||||||
if (Info.Shadow)
|
yield return r;
|
||||||
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 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));
|
if (info.ContrailLength > 0)
|
||||||
Combat.DoImpacts(dest.ToWPos(destAltitude), Args.sourceActor, Args.weapon, Args.firepowerModifier);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -563,8 +563,6 @@ Napalm:
|
|||||||
Image: BOMBLET
|
Image: BOMBLET
|
||||||
Speed: 5
|
Speed: 5
|
||||||
High: yes
|
High: yes
|
||||||
RangeLimit: 24
|
|
||||||
Arm: 24
|
|
||||||
Warhead:
|
Warhead:
|
||||||
Spread: 4
|
Spread: 4
|
||||||
Versus:
|
Versus:
|
||||||
@@ -952,11 +950,10 @@ SCUD:
|
|||||||
Report: MISSILE1.AUD
|
Report: MISSILE1.AUD
|
||||||
Projectile: Bullet
|
Projectile: Bullet
|
||||||
Speed: 10
|
Speed: 10
|
||||||
Arm: 10
|
|
||||||
High: true
|
High: true
|
||||||
Shadow: false
|
Shadow: false
|
||||||
Proximity: true
|
|
||||||
Trail: smokey
|
Trail: smokey
|
||||||
|
TrailDelay: 5
|
||||||
Inaccuracy: 5
|
Inaccuracy: 5
|
||||||
Image: V2
|
Image: V2
|
||||||
Angle: .1
|
Angle: .1
|
||||||
|
|||||||
Reference in New Issue
Block a user