#region Copyright & License Information /* * Copyright 2007-2020 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.Linq; using OpenRA.Graphics; using OpenRA.Mods.Common.Effects; using OpenRA.Mods.Common.Graphics; using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [Desc("Displays fireports, muzzle offsets, and hit areas in developer mode.")] public class CombatDebugOverlayInfo : TraitInfo { public override object Create(ActorInitializer init) { return new CombatDebugOverlay(init.Self); } } public class CombatDebugOverlay : IRenderAnnotations, INotifyDamage, INotifyCreated { static readonly WVec TargetPosHLine = new WVec(0, 128, 0); static readonly WVec TargetPosVLine = new WVec(128, 0, 0); readonly DebugVisualizations debugVis; readonly IHealthInfo healthInfo; readonly Lazy coords; HitShape[] shapes; IBlocksProjectiles[] allBlockers; public CombatDebugOverlay(Actor self) { healthInfo = self.Info.TraitInfoOrDefault(); coords = Exts.Lazy(self.Trait); debugVis = self.World.WorldActor.TraitOrDefault(); } void INotifyCreated.Created(Actor self) { shapes = self.TraitsImplementing().ToArray(); allBlockers = self.TraitsImplementing().ToArray(); } IEnumerable IRenderAnnotations.RenderAnnotations(Actor self, WorldRenderer wr) { if (debugVis == null || !debugVis.CombatGeometry || self.World.FogObscures(self)) return Enumerable.Empty(); return RenderAnnotations(self, wr); } IEnumerable RenderAnnotations(Actor self, WorldRenderer wr) { var blockers = allBlockers.Where(Exts.IsTraitEnabled).ToList(); if (blockers.Count > 0) { var height = new WVec(0, 0, blockers.Max(b => b.BlockingHeight.Length)); yield return new LineAnnotationRenderable(self.CenterPosition, self.CenterPosition + height, 1, Color.Orange); } var activeShapes = shapes.Where(Exts.IsTraitEnabled); foreach (var s in activeShapes) foreach (var r in s.RenderDebugOverlay(self, wr)) yield return r; var positions = Target.FromActor(self).Positions; foreach (var p in positions) { yield return new LineAnnotationRenderable(p - TargetPosHLine, p + TargetPosHLine, 1, Color.Lime); yield return new LineAnnotationRenderable(p - TargetPosVLine, p + TargetPosVLine, 1, Color.Lime); } foreach (var attack in self.TraitsImplementing().Where(x => !x.IsTraitDisabled)) foreach (var r in RenderArmaments(self, attack)) yield return r; } bool IRenderAnnotations.SpatiallyPartitionable { get { return true; } } IEnumerable RenderArmaments(Actor self, AttackBase attack) { // Fire ports on garrisonable structures var garrison = attack as AttackGarrisoned; if (garrison != null) { var bodyOrientation = coords.Value.QuantizeOrientation(self, self.Orientation); foreach (var p in garrison.Info.Ports) { var pos = self.CenterPosition + coords.Value.LocalToWorld(p.Offset.Rotate(bodyOrientation)); var da = coords.Value.LocalToWorld(new WVec(224, 0, 0).Rotate(WRot.FromYaw(p.Yaw + p.Cone)).Rotate(bodyOrientation)); var db = coords.Value.LocalToWorld(new WVec(224, 0, 0).Rotate(WRot.FromYaw(p.Yaw - p.Cone)).Rotate(bodyOrientation)); yield return new LineAnnotationRenderable(pos, pos + da * 224 / da.Length, 1, Color.White); yield return new LineAnnotationRenderable(pos, pos + db * 224 / da.Length, 1, Color.White); } yield break; } foreach (var a in attack.Armaments) { if (a.IsTraitDisabled) continue; foreach (var b in a.Barrels) { var barrelEnd = new Barrel { Offset = b.Offset + new WVec(224, 0, 0), Yaw = b.Yaw }; var muzzle = self.CenterPosition + a.MuzzleOffset(self, b); var endMuzzle = self.CenterPosition + a.MuzzleOffset(self, barrelEnd); yield return new LineAnnotationRenderable(muzzle, endMuzzle, 1, Color.White); } } } void INotifyDamage.Damaged(Actor self, AttackInfo e) { if (debugVis == null || !debugVis.CombatGeometry || e.Damage.Value == 0) return; if (healthInfo == null) return; var maxHP = healthInfo.MaxHP > 0 ? healthInfo.MaxHP : 1; var damageText = "{0} ({1}%)".F(-e.Damage.Value, e.Damage.Value * 100 / maxHP); self.World.AddFrameEndTask(w => w.Add(new FloatingText(self.CenterPosition, e.Attacker.Owner.Color, damageText, 30))); } } }