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;
13 using System.Collections.Generic;
14 using System.Linq;
15 using OpenRA.Graphics;
16 using OpenRA.Primitives;
17 using OpenRA.Traits;
18 
19 namespace OpenRA.Mods.Common.Traits.Render
20 {
21 	[Desc("Renders the MuzzleSequence from the Armament trait.")]
22 	class WithMuzzleOverlayInfo : ConditionalTraitInfo, Requires<RenderSpritesInfo>, Requires<AttackBaseInfo>, Requires<ArmamentInfo>
23 	{
24 		[Desc("Ignore the weapon position, and always draw relative to the center of the actor")]
25 		public readonly bool IgnoreOffset = false;
26 
Create(ActorInitializer init)27 		public override object Create(ActorInitializer init) { return new WithMuzzleOverlay(init.Self, this); }
28 	}
29 
30 	class WithMuzzleOverlay : ConditionalTrait<WithMuzzleOverlayInfo>, INotifyAttack, IRender, ITick
31 	{
32 		readonly Dictionary<Barrel, bool> visible = new Dictionary<Barrel, bool>();
33 		readonly Dictionary<Barrel, AnimationWithOffset> anims = new Dictionary<Barrel, AnimationWithOffset>();
34 		readonly Func<int> getFacing;
35 		readonly Armament[] armaments;
36 
WithMuzzleOverlay(Actor self, WithMuzzleOverlayInfo info)37 		public WithMuzzleOverlay(Actor self, WithMuzzleOverlayInfo info)
38 			: base(info)
39 		{
40 			var render = self.Trait<RenderSprites>();
41 			var facing = self.TraitOrDefault<IFacing>();
42 
43 			armaments = self.TraitsImplementing<Armament>()
44 				.Where(arm => arm.Info.MuzzleSequence != null)
45 				.ToArray();
46 
47 			foreach (var arm in armaments)
48 			{
49 				foreach (var b in arm.Barrels)
50 				{
51 					var barrel = b;
52 					var turreted = self.TraitsImplementing<Turreted>()
53 						.FirstOrDefault(t => t.Name == arm.Info.Turret);
54 
55 					// Workaround for broken ternary operators in certain versions of mono (3.10 and
56 					// certain versions of the 3.8 series): https://bugzilla.xamarin.com/show_bug.cgi?id=23319
57 					if (turreted != null)
58 						getFacing = () => turreted.TurretFacing;
59 					else if (facing != null)
60 						getFacing = () => facing.Facing;
61 					else
62 						getFacing = () => 0;
63 
64 					var muzzleFlash = new Animation(self.World, render.GetImage(self), getFacing);
65 					visible.Add(barrel, false);
66 					anims.Add(barrel,
67 						new AnimationWithOffset(muzzleFlash,
68 							() => info.IgnoreOffset ? WVec.Zero : arm.MuzzleOffset(self, barrel),
69 							() => IsTraitDisabled || !visible[barrel],
70 							p => RenderUtils.ZOffsetFromCenter(self, p, 2)));
71 				}
72 			}
73 		}
74 
INotifyAttack.Attacking(Actor self, Target target, Armament a, Barrel barrel)75 		void INotifyAttack.Attacking(Actor self, Target target, Armament a, Barrel barrel)
76 		{
77 			if (a == null || barrel == null || !armaments.Contains(a))
78 				return;
79 
80 			var sequence = a.Info.MuzzleSequence;
81 			if (a.Info.MuzzleSplitFacings > 0)
82 				sequence += Util.QuantizeFacing(getFacing(), a.Info.MuzzleSplitFacings).ToString();
83 
84 			visible[barrel] = true;
85 			anims[barrel].Animation.PlayThen(sequence, () => visible[barrel] = false);
86 		}
87 
INotifyAttack.PreparingAttack(Actor self, Target target, Armament a, Barrel barrel)88 		void INotifyAttack.PreparingAttack(Actor self, Target target, Armament a, Barrel barrel) { }
89 
IRender.Render(Actor self, WorldRenderer wr)90 		IEnumerable<IRenderable> IRender.Render(Actor self, WorldRenderer wr)
91 		{
92 			foreach (var arm in armaments)
93 			{
94 				var palette = wr.Palette(arm.Info.MuzzlePalette);
95 				foreach (var b in arm.Barrels)
96 				{
97 					var anim = anims[b];
98 					if (anim.DisableFunc != null && anim.DisableFunc())
99 						continue;
100 
101 					foreach (var r in anim.Render(self, wr, palette, 1f))
102 						yield return r;
103 				}
104 			}
105 		}
106 
IRender.ScreenBounds(Actor self, WorldRenderer wr)107 		IEnumerable<Rectangle> IRender.ScreenBounds(Actor self, WorldRenderer wr)
108 		{
109 			// Muzzle flashes don't contribute to actor bounds
110 			yield break;
111 		}
112 
ITick.Tick(Actor self)113 		void ITick.Tick(Actor self)
114 		{
115 			foreach (var a in anims.Values)
116 				a.Animation.Tick();
117 		}
118 	}
119 }
120