#region Copyright & License Information /* * Copyright 2007-2022 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 OpenRA.Graphics; using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Common.Graphics { public class IsometricSelectionBarsAnnotationRenderable : IRenderable, IFinalizedRenderable { const int BarWidth = 3; const int BarHeight = 4; const int BarStride = 5; static readonly Color EmptyColor = Color.FromArgb(160, 30, 30, 30); static readonly Color DarkEmptyColor = Color.FromArgb(160, 15, 15, 15); static readonly Color DarkenColor = Color.FromArgb(24, 0, 0, 0); static readonly Color LightenColor = Color.FromArgb(24, 255, 255, 255); readonly WPos pos; readonly Actor actor; readonly bool displayHealth; readonly bool displayExtra; readonly Polygon bounds; public IsometricSelectionBarsAnnotationRenderable(Actor actor, Polygon bounds, bool displayHealth, bool displayExtra) : this(actor.CenterPosition, actor, bounds) { this.displayHealth = displayHealth; this.displayExtra = displayExtra; } public IsometricSelectionBarsAnnotationRenderable(WPos pos, Actor actor, Polygon bounds) { this.pos = pos; this.actor = actor; this.bounds = bounds; } public WPos Pos => pos; public bool DisplayHealth => displayHealth; public bool DisplayExtra => displayExtra; public int ZOffset => 0; public bool IsDecoration => true; public IRenderable WithZOffset(int newOffset) { return this; } public IRenderable OffsetBy(in WVec vec) { return new IsometricSelectionBarsAnnotationRenderable(pos + vec, actor, bounds); } public IRenderable AsDecoration() { return this; } void DrawExtraBars(WorldRenderer wr) { var i = 1; foreach (var extraBar in actor.TraitsImplementing()) { var value = extraBar.GetValue(); if (value != 0 || extraBar.DisplayWhenEmpty) DrawBar(wr, extraBar.GetValue(), extraBar.GetColor(), i++); } } void DrawBar(WorldRenderer wr, float value, Color barColor, int barNum, float? secondValue = null, Color? secondColor = null) { var darkColor = Color.FromArgb(barColor.A, barColor.R / 2, barColor.G / 2, barColor.B / 2); var barAspect = new float2(1f, 0.5f); var stepAspect = new float2(1f, -0.5f); var offset = barNum * BarStride * barAspect - new float2(0, BarHeight + 1); var start = wr.Viewport.WorldToViewPx(bounds.Vertices[1]).ToFloat2() + offset; var end = wr.Viewport.WorldToViewPx(bounds.Vertices[0]).ToFloat2() + offset; // HACK: Work around rounding errors that may cause a few-px offset in the end relative to the start // Force the bar to take a 45 degree angle end = new float2(end.X, start.Y - (end.X - start.X) / 2); // Round the cut point to the nearest pixel to avoid potential off-by-one pixel offsets distorting the bar var cutX = (int)(float2.Lerp(start.X, end.X, value) + 0.5f); var cut = new float2(cutX, start.Y - (cutX - start.X) / 2); var cr = Game.Renderer.RgbaColorRenderer; var da = BarWidth * barAspect; var db = new int2(0, BarHeight); var dc = da + db; // Filled bar cr.FillRect(start + da, start + dc, cut + dc, cut + da, darkColor); cr.FillRect(start, start + da, start + dc, start + db, darkColor); cr.FillRect(start, start + da, cut + da, cut, barColor); // Faint marks to break the monotony of the solid bar var dx = BarWidth; while (dx < cut.X - start.X) { var step = start + dx * stepAspect; cr.DrawLine(step, step + da, 1, DarkenColor); cr.DrawLine(step + da, step + dc, 1, LightenColor); dx += BarWidth; } // Second bar (e.g. applied damage) if (secondValue.HasValue && secondColor.HasValue) { var secondCutX = (int)(float2.Lerp(start.X, end.X, secondValue.Value) + 0.5f); var secondCut = new float2(secondCutX, start.Y - (secondCutX - start.X) / 2); var darkSecond = Color.FromArgb(secondColor.Value.A, secondColor.Value.R / 2, secondColor.Value.G / 2, secondColor.Value.B / 2); cr.FillRect(cut + da, cut + dc, secondCut + dc, secondCut + da, darkSecond); cr.FillRect(cut, cut + da, secondCut + da, secondCut, secondColor.Value); value = secondValue.Value; cut = secondCut; } // Empty bar if (value < 1) { cr.FillRect(cut + da, cut + dc, end + dc, end + da, DarkEmptyColor); cr.FillRect(cut, cut + da, end + da, end, EmptyColor); } } Color GetHealthColor(IHealth health) { if (Game.Settings.Game.UsePlayerStanceColors) return actor.Owner.PlayerRelationshipColor(actor); return health.DamageState == DamageState.Critical ? Color.Red : health.DamageState == DamageState.Heavy ? Color.Yellow : Color.LimeGreen; } public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; } public void Render(WorldRenderer wr) { if (!actor.IsInWorld || actor.IsDead) return; var health = actor.TraitOrDefault(); if (DisplayHealth) { if (health == null || health.IsDead) return; var displayValue = health.DisplayHP != health.HP ? (float?)health.DisplayHP / health.MaxHP : null; DrawBar(wr, (float)health.HP / health.MaxHP, GetHealthColor(health), 0, displayValue, Color.OrangeRed); } if (DisplayExtra) DrawExtraBars(wr); } public void RenderDebugGeometry(WorldRenderer wr) { } public Rectangle ScreenBounds(WorldRenderer wr) { return Rectangle.Empty; } } }