1 #region Copyright & License Information 2 /* 3 * Copyright 2007-2020 The OpenRA Developers (see AUTHORS) 4 * This file is part of OpenRA, which is free software. It is made 5 * available to you under the terms of the GNU General Public License 6 * as published by the Free Software Foundation, either version 3 of 7 * the License, or (at your option) any later version. For more 8 * information, see COPYING. 9 */ 10 #endregion 11 12 using System.Collections.Generic; 13 using OpenRA.GameRules; 14 using OpenRA.Graphics; 15 using OpenRA.Mods.Common.Effects; 16 using OpenRA.Mods.Common.Graphics; 17 using OpenRA.Mods.Common.Traits; 18 using OpenRA.Primitives; 19 using OpenRA.Traits; 20 21 namespace OpenRA.Mods.Common.Projectiles 22 { 23 [Desc("Not a sprite, but an engine effect.")] 24 public class LaserZapInfo : IProjectileInfo 25 { 26 [Desc("The width of the zap.")] 27 public readonly WDist Width = new WDist(86); 28 29 [Desc("The shape of the beam. Accepts values Cylindrical or Flat.")] 30 public readonly BeamRenderableShape Shape = BeamRenderableShape.Cylindrical; 31 32 [Desc("Equivalent to sequence ZOffset. Controls Z sorting.")] 33 public readonly int ZOffset = 0; 34 35 [Desc("The maximum duration (in ticks) of the beam's existence.")] 36 public readonly int Duration = 10; 37 38 [Desc("Total time-frame in ticks that the beam deals damage every DamageInterval.")] 39 public readonly int DamageDuration = 1; 40 41 [Desc("The number of ticks between the beam causing warhead impacts in its area of effect.")] 42 public readonly int DamageInterval = 1; 43 44 public readonly bool UsePlayerColor = false; 45 46 [Desc("Color of the beam.")] 47 public readonly Color Color = Color.Red; 48 49 [Desc("Beam follows the target.")] 50 public readonly bool TrackTarget = true; 51 52 [Desc("Maximum offset at the maximum range.")] 53 public readonly WDist Inaccuracy = WDist.Zero; 54 55 [Desc("Beam can be blocked.")] 56 public readonly bool Blockable = false; 57 58 [Desc("Draw a second beam (for 'glow' effect).")] 59 public readonly bool SecondaryBeam = false; 60 61 [Desc("The width of the zap.")] 62 public readonly WDist SecondaryBeamWidth = new WDist(86); 63 64 [Desc("The shape of the beam. Accepts values Cylindrical or Flat.")] 65 public readonly BeamRenderableShape SecondaryBeamShape = BeamRenderableShape.Cylindrical; 66 67 [Desc("Equivalent to sequence ZOffset. Controls Z sorting.")] 68 public readonly int SecondaryBeamZOffset = 0; 69 70 public readonly bool SecondaryBeamUsePlayerColor = false; 71 72 [Desc("Color of the secondary beam.")] 73 public readonly Color SecondaryBeamColor = Color.Red; 74 75 [Desc("Impact animation.")] 76 public readonly string HitAnim = null; 77 78 [SequenceReference("HitAnim")] 79 [Desc("Sequence of impact animation to use.")] 80 public readonly string HitAnimSequence = "idle"; 81 82 [PaletteReference] 83 public readonly string HitAnimPalette = "effect"; 84 85 [Desc("Image containing launch effect sequence.")] 86 public readonly string LaunchEffectImage = null; 87 88 [SequenceReference("LaunchEffectImage")] 89 [Desc("Launch effect sequence to play.")] 90 public readonly string LaunchEffectSequence = null; 91 92 [PaletteReference] 93 [Desc("Palette to use for launch effect.")] 94 public readonly string LaunchEffectPalette = "effect"; 95 Create(ProjectileArgs args)96 public IProjectile Create(ProjectileArgs args) 97 { 98 var c = UsePlayerColor ? args.SourceActor.Owner.Color : Color; 99 return new LaserZap(this, args, c); 100 } 101 } 102 103 public class LaserZap : IProjectile, ISync 104 { 105 readonly ProjectileArgs args; 106 readonly LaserZapInfo info; 107 readonly Animation hitanim; 108 readonly Color color; 109 readonly Color secondaryColor; 110 readonly bool hasLaunchEffect; 111 int ticks; 112 int interval; 113 bool showHitAnim; 114 115 [Sync] 116 WPos target; 117 118 [Sync] 119 WPos source; 120 LaserZap(LaserZapInfo info, ProjectileArgs args, Color color)121 public LaserZap(LaserZapInfo info, ProjectileArgs args, Color color) 122 { 123 this.args = args; 124 this.info = info; 125 this.color = color; 126 secondaryColor = info.SecondaryBeamUsePlayerColor ? args.SourceActor.Owner.Color : info.SecondaryBeamColor; 127 target = args.PassiveTarget; 128 source = args.Source; 129 130 if (info.Inaccuracy.Length > 0) 131 { 132 var inaccuracy = OpenRA.Mods.Common.Util.ApplyPercentageModifiers(info.Inaccuracy.Length, args.InaccuracyModifiers); 133 var maxOffset = inaccuracy * (target - source).Length / args.Weapon.Range.Length; 134 target += WVec.FromPDF(args.SourceActor.World.SharedRandom, 2) * maxOffset / 1024; 135 } 136 137 if (!string.IsNullOrEmpty(info.HitAnim)) 138 { 139 hitanim = new Animation(args.SourceActor.World, info.HitAnim); 140 showHitAnim = true; 141 } 142 143 hasLaunchEffect = !string.IsNullOrEmpty(info.LaunchEffectImage) && !string.IsNullOrEmpty(info.LaunchEffectSequence); 144 } 145 Tick(World world)146 public void Tick(World world) 147 { 148 source = args.CurrentSource(); 149 150 if (hasLaunchEffect && ticks == 0) 151 world.AddFrameEndTask(w => w.Add(new SpriteEffect(args.CurrentSource, args.CurrentMuzzleFacing, world, 152 info.LaunchEffectImage, info.LaunchEffectSequence, info.LaunchEffectPalette))); 153 154 // Beam tracks target 155 if (info.TrackTarget && args.GuidedTarget.IsValidFor(args.SourceActor)) 156 target = args.Weapon.TargetActorCenter ? args.GuidedTarget.CenterPosition : args.GuidedTarget.Positions.PositionClosestTo(source); 157 158 // Check for blocking actors 159 WPos blockedPos; 160 if (info.Blockable && BlocksProjectiles.AnyBlockingActorsBetween(world, source, target, 161 info.Width, out blockedPos)) 162 { 163 target = blockedPos; 164 } 165 166 if (ticks < info.DamageDuration && --interval <= 0) 167 { 168 args.Weapon.Impact(Target.FromPos(target), new WarheadArgs(args)); 169 interval = info.DamageInterval; 170 } 171 172 if (showHitAnim) 173 { 174 if (ticks == 0) 175 hitanim.PlayThen(info.HitAnimSequence, () => showHitAnim = false); 176 177 hitanim.Tick(); 178 } 179 180 if (++ticks >= info.Duration && !showHitAnim) 181 world.AddFrameEndTask(w => w.Remove(this)); 182 } 183 Render(WorldRenderer wr)184 public IEnumerable<IRenderable> Render(WorldRenderer wr) 185 { 186 if (wr.World.FogObscures(target) && 187 wr.World.FogObscures(source)) 188 yield break; 189 190 if (ticks < info.Duration) 191 { 192 var rc = Color.FromArgb((info.Duration - ticks) * color.A / info.Duration, color); 193 yield return new BeamRenderable(source, info.ZOffset, target - source, info.Shape, info.Width, rc); 194 195 if (info.SecondaryBeam) 196 { 197 var src = Color.FromArgb((info.Duration - ticks) * secondaryColor.A / info.Duration, secondaryColor); 198 yield return new BeamRenderable(source, info.SecondaryBeamZOffset, target - source, 199 info.SecondaryBeamShape, info.SecondaryBeamWidth, src); 200 } 201 } 202 203 if (showHitAnim) 204 foreach (var r in hitanim.Render(target, wr.Palette(info.HitAnimPalette))) 205 yield return r; 206 } 207 } 208 } 209