#region Copyright & License Information /* * Copyright 2007-2018 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; using System.Collections.Generic; using System.Drawing; using System.Linq; using OpenRA.Graphics; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits.Render { [Desc("Renders the MuzzleSequence from the Armament trait.")] class WithMuzzleOverlayInfo : ConditionalTraitInfo, Requires, Requires, Requires { [Desc("Ignore the weapon position, and always draw relative to the center of the actor")] public readonly bool IgnoreOffset = false; public override object Create(ActorInitializer init) { return new WithMuzzleOverlay(init.Self, this); } } class WithMuzzleOverlay : ConditionalTrait, INotifyAttack, IRender, ITick { readonly Dictionary visible = new Dictionary(); readonly Dictionary anims = new Dictionary(); readonly Func getFacing; readonly Armament[] armaments; public WithMuzzleOverlay(Actor self, WithMuzzleOverlayInfo info) : base(info) { var render = self.Trait(); var facing = self.TraitOrDefault(); armaments = self.TraitsImplementing() .Where(arm => arm.Info.MuzzleSequence != null) .ToArray(); foreach (var arm in armaments) { foreach (var b in arm.Barrels) { var barrel = b; var turreted = self.TraitsImplementing() .FirstOrDefault(t => t.Name == arm.Info.Turret); // Workaround for broken ternary operators in certain versions of mono (3.10 and // certain versions of the 3.8 series): https://bugzilla.xamarin.com/show_bug.cgi?id=23319 if (turreted != null) getFacing = () => turreted.TurretFacing; else if (facing != null) getFacing = () => facing.Facing; else getFacing = () => 0; var muzzleFlash = new Animation(self.World, render.GetImage(self), getFacing); visible.Add(barrel, false); anims.Add(barrel, new AnimationWithOffset(muzzleFlash, () => info.IgnoreOffset ? WVec.Zero : arm.MuzzleOffset(self, barrel), () => IsTraitDisabled || !visible[barrel], p => RenderUtils.ZOffsetFromCenter(self, p, 2))); } } } void INotifyAttack.Attacking(Actor self, Target target, Armament a, Barrel barrel) { if (a == null || barrel == null || !armaments.Contains(a)) return; var sequence = a.Info.MuzzleSequence; if (a.Info.MuzzleSplitFacings > 0) sequence += Util.QuantizeFacing(getFacing(), a.Info.MuzzleSplitFacings).ToString(); visible[barrel] = true; anims[barrel].Animation.PlayThen(sequence, () => visible[barrel] = false); } void INotifyAttack.PreparingAttack(Actor self, Target target, Armament a, Barrel barrel) { } IEnumerable IRender.Render(Actor self, WorldRenderer wr) { foreach (var arm in armaments) { var palette = wr.Palette(arm.Info.MuzzlePalette); foreach (var b in arm.Barrels) { var anim = anims[b]; if (anim.DisableFunc != null && anim.DisableFunc()) continue; foreach (var r in anim.Render(self, wr, palette, 1f)) yield return r; } } } IEnumerable IRender.ScreenBounds(Actor self, WorldRenderer wr) { // Muzzle flashes don't contribute to actor bounds yield break; } void ITick.Tick(Actor self) { foreach (var a in anims.Values) a.Animation.Tick(); } } }