From 0573f52a376612d3e2eca82077ce07dc7bd66d41 Mon Sep 17 00:00:00 2001 From: Forcecore Date: Mon, 5 Jun 2017 20:42:22 -0500 Subject: [PATCH] Add Railgun projectile --- .../Graphics/RailgunRenderable.cs | 87 +++++++ OpenRA.Mods.Common/OpenRA.Mods.Common.csproj | 2 + OpenRA.Mods.Common/Projectiles/Railgun.cs | 239 ++++++++++++++++++ mods/ts/weapons/energyweapons.yaml | 13 +- 4 files changed, 334 insertions(+), 7 deletions(-) create mode 100644 OpenRA.Mods.Common/Graphics/RailgunRenderable.cs create mode 100644 OpenRA.Mods.Common/Projectiles/Railgun.cs diff --git a/OpenRA.Mods.Common/Graphics/RailgunRenderable.cs b/OpenRA.Mods.Common/Graphics/RailgunRenderable.cs new file mode 100644 index 0000000000..4bb1856fc3 --- /dev/null +++ b/OpenRA.Mods.Common/Graphics/RailgunRenderable.cs @@ -0,0 +1,87 @@ +#region Copyright & License Information +/* + * Copyright 2007-2017 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, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System.Collections.Generic; +using System.Drawing; +using OpenRA.Graphics; +using OpenRA.Mods.Common.Projectiles; + +namespace OpenRA.Mods.Common.Graphics +{ + public struct RailgunHelixRenderable : IRenderable, IFinalizedRenderable + { + readonly WPos pos; + readonly int zOffset; + readonly Railgun railgun; + readonly RailgunInfo info; + readonly WDist helixRadius; + readonly int alpha; + readonly int ticks; + + WAngle angle; + + public RailgunHelixRenderable(WPos pos, int zOffset, Railgun railgun, RailgunInfo railgunInfo, int ticks) + { + this.pos = pos; + this.zOffset = zOffset; + this.railgun = railgun; + this.info = railgunInfo; + this.ticks = ticks; + + helixRadius = info.HelixRadius + new WDist(ticks * info.HelixRadiusDeltaPerTick); + alpha = (railgun.HelixColor.A + ticks * info.HelixAlphaDeltaPerTick).Clamp(0, 255); + angle = new WAngle(ticks * info.HelixAngleDeltaPerTick.Angle); + } + + public WPos Pos { get { return pos; } } + public PaletteReference Palette { get { return null; } } + public int ZOffset { get { return zOffset; } } + public bool IsDecoration { get { return true; } } + + public IRenderable WithPalette(PaletteReference newPalette) { return new RailgunHelixRenderable(pos, zOffset, railgun, info, ticks); } + public IRenderable WithZOffset(int newOffset) { return new RailgunHelixRenderable(pos, newOffset, railgun, info, ticks); } + public IRenderable OffsetBy(WVec vec) { return new RailgunHelixRenderable(pos + vec, zOffset, railgun, info, ticks); } + public IRenderable AsDecoration() { return this; } + + public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; } + public void Render(WorldRenderer wr) + { + if (railgun.ForwardStep == WVec.Zero) + return; + + var screenWidth = wr.ScreenVector(new WVec(info.HelixThickness.Length, 0, 0))[0]; + + // Move forward from self to target to draw helix + var centerPos = this.pos; + var points = new float3[railgun.CycleCount * info.QuantizationCount]; + for (var i = points.Length - 1; i >= 0; i--) + { + // Make it narrower near the end. + var rad = i < info.QuantizationCount ? helixRadius / 4 : + i < 2 * info.QuantizationCount ? helixRadius / 2 : + helixRadius; + + // Note: WAngle.Sin(x) = 1024 * Math.Sin(2pi/1024 * x) + var u = rad.Length * angle.Cos() * railgun.LeftVector / (1024 * 1024) + + rad.Length * angle.Sin() * railgun.UpVector / (1024 * 1024); + points[i] = wr.Screen3DPosition(centerPos + u); + + centerPos += railgun.ForwardStep; + angle += railgun.AngleStep; + } + + Game.Renderer.WorldRgbaColorRenderer.DrawLine(points, screenWidth, Color.FromArgb(alpha, railgun.HelixColor)); + } + + public void RenderDebugGeometry(WorldRenderer wr) { } + public Rectangle ScreenBounds(WorldRenderer wr) { return Rectangle.Empty; } + } +} diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index f251250015..0123851f41 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -145,6 +145,7 @@ + @@ -187,6 +188,7 @@ + diff --git a/OpenRA.Mods.Common/Projectiles/Railgun.cs b/OpenRA.Mods.Common/Projectiles/Railgun.cs new file mode 100644 index 0000000000..4fbbb30c8c --- /dev/null +++ b/OpenRA.Mods.Common/Projectiles/Railgun.cs @@ -0,0 +1,239 @@ +#region Copyright & License Information +/* + * Copyright 2007-2017 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, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System.Collections.Generic; +using System.Drawing; +using OpenRA.GameRules; +using OpenRA.Graphics; +using OpenRA.Mods.Common.Graphics; +using OpenRA.Mods.Common.Traits; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Projectiles +{ + [Desc("Laser effect with helix coiling around.")] + public class RailgunInfo : IProjectileInfo + { + [Desc("Damage all units hit by the beam instead of just the target?")] + public readonly bool DamageActorsInLine = false; + + [Desc("Maximum offset at the maximum range.")] + public readonly WDist Inaccuracy = WDist.Zero; + + [Desc("Can this projectile be blocked when hitting actors with an IBlocksProjectiles trait.")] + public readonly bool Blockable = false; + + [Desc("Duration of the beam and helix")] + public readonly int Duration = 15; + + [Desc("Equivalent to sequence ZOffset. Controls Z sorting.")] + public readonly int ZOffset = 0; + + [Desc("The width of the main trajectory. (\"beam\").")] + public readonly WDist BeamWidth = new WDist(86); + + [Desc("The shape of the beam. Accepts values Cylindrical or Flat.")] + public readonly BeamRenderableShape BeamShape = BeamRenderableShape.Cylindrical; + + [Desc("Beam color in (A),R,G,B.")] + public readonly Color BeamColor = Color.FromArgb(128, 255, 255, 255); + + [Desc("When true, this will override BeamColor parameter and draw the laser with player color." + + " (Still uses BeamColor's alpha information)")] + public readonly bool BeamPlayerColor = false; + + [Desc("Beam alpha gets + this value per tick during drawing; hence negative value makes it fade over time.")] + public readonly int BeamAlphaDeltaPerTick = -8; + + [Desc("Thickness of the helix")] + public readonly WDist HelixThickness = new WDist(32); + + [Desc("The radius of the spiral effect. (WDist)")] + public readonly WDist HelixRadius = new WDist(64); + + [Desc("Height of one complete helix turn, measured parallel to the axis of the helix (WDist)")] + public readonly WDist HelixPitch = new WDist(512); + + [Desc("Helix radius gets + this value per tick during drawing")] + public readonly int HelixRadiusDeltaPerTick = 8; + + [Desc("Helix alpha gets + this value per tick during drawing; hence negative value makes it fade over time.")] + public readonly int HelixAlphaDeltaPerTick = -8; + + [Desc("Helix spins by this much over time each tick.")] + public readonly WAngle HelixAngleDeltaPerTick = new WAngle(16); + + [Desc("Draw each cycle of helix with this many quantization steps")] + public readonly int QuantizationCount = 16; + + [Desc("Helix color in (A),R,G,B.")] + public readonly Color HelixColor = Color.FromArgb(128, 255, 255, 255); + + [Desc("Draw helix in PlayerColor? Overrides RGB part of the HelixColor. (Still uses HelixColor's alpha information)")] + public readonly bool HelixPlayerColor = false; + + [Desc("Impact animation.")] + public readonly string HitAnim = null; + + [Desc("Sequence of impact animation to use.")] + [SequenceReference("HitAnim")] + public readonly string HitAnimSequence = "idle"; + + [PaletteReference] + public readonly string HitAnimPalette = "effect"; + + [Desc("Scan radius for actors damaged by beam. If set to zero (default), it will automatically scale to the largest health shape.", + "Only set custom values if you know what you're doing.")] + public WDist AreaVictimScanRadius = WDist.Zero; + + [Desc("Scan radius for actors with projectile-blocking trait. If set to zero (default), it will automatically scale", + "to the blocker with the largest health shape. Only set custom values if you know what you're doing.")] + public WDist BlockerScanRadius = WDist.Zero; + + public IProjectile Create(ProjectileArgs args) + { + var bc = BeamPlayerColor ? Color.FromArgb(BeamColor.A, args.SourceActor.Owner.Color.RGB) : BeamColor; + var hc = HelixPlayerColor ? Color.FromArgb(HelixColor.A, args.SourceActor.Owner.Color.RGB) : HelixColor; + return new Railgun(args, this, bc, hc); + } + + public void RulesetLoaded(Ruleset rules, WeaponInfo wi) + { + if (BlockerScanRadius == WDist.Zero) + BlockerScanRadius = Util.MinimumRequiredBlockerScanRadius(rules); + + if (AreaVictimScanRadius == WDist.Zero) + AreaVictimScanRadius = Util.MinimumRequiredVictimScanRadius(rules); + } + } + + public class Railgun : IProjectile + { + readonly ProjectileArgs args; + readonly RailgunInfo info; + readonly Animation hitanim; + public readonly Color BeamColor; + public readonly Color HelixColor; + + int ticks = 0; + bool animationComplete; + WPos target; + + // Computing these in Railgun instead of RailgunRenderable saves Info.Duration ticks of computation. + // Fortunately, railguns don't track the target. + public int CycleCount { get; private set; } + public WVec SourceToTarget { get; private set; } + public WVec ForwardStep { get; private set; } + public WVec LeftVector { get; private set; } + public WVec UpVector { get; private set; } + public WAngle AngleStep { get; private set; } + + public Railgun(ProjectileArgs args, RailgunInfo info, Color beamColor, Color helixColor) + { + this.args = args; + this.info = info; + target = args.PassiveTarget; + + this.BeamColor = beamColor; + this.HelixColor = helixColor; + + if (!string.IsNullOrEmpty(info.HitAnim)) + hitanim = new Animation(args.SourceActor.World, info.HitAnim); + + CalculateVectors(); + } + + void CalculateVectors() + { + // Check for blocking actors + WPos blockedPos; + if (info.Blockable && BlocksProjectiles.AnyBlockingActorsBetween(args.SourceActor.World, target, args.Source, + info.BeamWidth, info.BlockerScanRadius, out blockedPos)) + target = blockedPos; + + // Note: WAngle.Sin(x) = 1024 * Math.Sin(2pi/1024 * x) + AngleStep = new WAngle(1024 / info.QuantizationCount); + + SourceToTarget = target - args.Source; + + // Forward step, pointing from src to target. + // QuantizationCont * forwardStep == One cycle of beam in src2target direction. + ForwardStep = (info.HelixPitch.Length * SourceToTarget) / (info.QuantizationCount * SourceToTarget.Length); + + // An easy vector to find which is perpendicular vector to forwardStep, with 0 Z component + LeftVector = new WVec(ForwardStep.Y, -ForwardStep.X, 0); + LeftVector = 1024 * LeftVector / LeftVector.Length; + + // Vector that is pointing upwards from the ground + UpVector = new WVec( + -ForwardStep.X * ForwardStep.Z, + -ForwardStep.Z * ForwardStep.Y, + ForwardStep.X * ForwardStep.X + ForwardStep.Y * ForwardStep.Y); + UpVector = 1024 * UpVector / UpVector.Length; + + //// LeftVector and UpVector are unit vectors of size 1024. + + CycleCount = SourceToTarget.Length / info.HelixPitch.Length; + if (SourceToTarget.Length % info.HelixPitch.Length != 0) + CycleCount += 1; // math.ceil, int version. + + // Using ForwardStep * CycleCount, the helix and the main beam gets "out of sync" + // if drawn from source to target. Instead, the main beam is drawn from source to end point of helix. + // Trade-off between computation vs Railgun weapon range. + // Modders must not have too large range for railgun weapons. + SourceToTarget = info.QuantizationCount * CycleCount * ForwardStep; + } + + public void Tick(World world) + { + if (ticks == 0) + { + if (hitanim != null) + hitanim.PlayThen(info.HitAnimSequence, () => animationComplete = true); + else + animationComplete = true; + + if (!info.DamageActorsInLine) + args.Weapon.Impact(Target.FromPos(target), args.SourceActor, args.DamageModifiers); + else + { + var actors = world.FindActorsOnLine(args.Source, target, info.BeamWidth, info.AreaVictimScanRadius); + foreach (var a in actors) + args.Weapon.Impact(Target.FromActor(a), args.SourceActor, args.DamageModifiers); + } + } + + if (hitanim != null) + hitanim.Tick(); + + if (ticks++ > info.Duration && animationComplete) + world.AddFrameEndTask(w => w.Remove(this)); + } + + public IEnumerable Render(WorldRenderer wr) + { + if (wr.World.FogObscures(target) && + wr.World.FogObscures(args.Source)) + yield break; + + if (ticks < info.Duration) + { + yield return new RailgunHelixRenderable(args.Source, info.ZOffset, this, info, ticks); + yield return new BeamRenderable(args.Source, info.ZOffset, SourceToTarget, info.BeamShape, info.BeamWidth, + Color.FromArgb(BeamColor.A + info.BeamAlphaDeltaPerTick * ticks, BeamColor)); + } + + if (hitanim != null) + foreach (var r in hitanim.Render(target, wr.Palette(info.HitAnimPalette))) + yield return r; + } + } +} diff --git a/mods/ts/weapons/energyweapons.yaml b/mods/ts/weapons/energyweapons.yaml index cc97b2aa21..394eaa23ba 100644 --- a/mods/ts/weapons/energyweapons.yaml +++ b/mods/ts/weapons/energyweapons.yaml @@ -2,14 +2,13 @@ ReloadDelay: 60 Range: 6c0 Report: bigggun1.aud - Projectile: AreaBeam + Projectile: Railgun Speed: 20c0 - Duration: 3 - DamageInterval: 2 + Duration: 15 Width: 80 - BeyondTargetRange: 0c64 Blockable: true - Color: 0080FFC8 + DamageActorsInLine: true + BeamColor: 0080FFC8 Warhead@1Dam: SpreadDamage Range: 0, 32 Falloff: 100, 100 @@ -48,8 +47,8 @@ MechRailgun: Burst: 2 # for alternating muzzle offsets, dmg/s identical to original BurstDelay: 60 Report: railuse5.aud - Projectile: AreaBeam - Color: 00FFFFC8 + Projectile: Railgun + BeamColor: 00FFFFC8 Warhead@1Dam: SpreadDamage Damage: 200 Versus: