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