136 lines
6.7 KiB
C#
136 lines
6.7 KiB
C#
using System.Linq;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
|
|
namespace Ironspore
|
|
{
|
|
// Field-only instruments. Weapon state and navigation targets belong to the simulation.
|
|
public sealed class CombatInstruments
|
|
{
|
|
readonly VisualElement field, map;
|
|
readonly GameSession game;
|
|
readonly Label primary, rounds, reload, secondary, cargo;
|
|
readonly string reloadHint;
|
|
float nextMapUpdate;
|
|
int displayedRounds = -1;
|
|
|
|
public CombatInstruments(VisualElement field, VisualElement vitals, GameSession game)
|
|
{
|
|
this.field = field;
|
|
this.game = game;
|
|
var nav = Panel("combat-nav");
|
|
Caption(nav, GameLocalization.Text("ui.combat.nav") + " N ↑", "combat-kicker");
|
|
map = new VisualElement();
|
|
map.AddToClassList("combat-map");
|
|
nav.Add(map);
|
|
map.generateVisualContent += DrawMap;
|
|
Caption(nav, GameLocalization.Text("ui.combat.legend"), "combat-small");
|
|
|
|
var weapon = Panel("combat-weapon");
|
|
primary = Caption(weapon, "", "combat-primary");
|
|
Caption(weapon, GameLocalization.Text("ui.combat.ammo"), "combat-kicker");
|
|
var row = new VisualElement();
|
|
row.AddToClassList("combat-rounds-row");
|
|
weapon.Add(row);
|
|
rounds = Caption(row, "", "combat-rounds");
|
|
reload = Caption(row, "", "combat-small");
|
|
secondary = Caption(weapon, "", "combat-secondary");
|
|
cargo = Caption(vitals, "", "combat-cargo");
|
|
reloadHint = GameLocalization.Text("ui.combat.reload", game.Settings.Bindings.Display("Reload"));
|
|
}
|
|
|
|
VisualElement Panel(string style)
|
|
{
|
|
var panel = new VisualElement();
|
|
panel.AddToClassList("combat-panel");
|
|
panel.AddToClassList(style);
|
|
field.Add(panel);
|
|
return panel;
|
|
}
|
|
|
|
static Label Caption(VisualElement parent, string text, string style)
|
|
{
|
|
var label = new Label(text);
|
|
label.AddToClassList(style);
|
|
parent.Add(label);
|
|
return label;
|
|
}
|
|
|
|
public void Refresh()
|
|
{
|
|
var pilot = game.Pilot;
|
|
field.EnableInClassList("cockpit-hud", pilot.FirstPerson);
|
|
bool redraw = Time.unscaledTime >= nextMapUpdate;
|
|
if (!redraw && pilot.Rounds == displayedRounds) return;
|
|
displayedRounds = pilot.Rounds;
|
|
primary.text = pilot.Primary ? pilot.Primary.LocalizedName : GameLocalization.Text("ui.combat.empty");
|
|
rounds.text = pilot.Primary ? pilot.Rounds.ToString() : "—";
|
|
rounds.EnableInClassList("ammo-low", pilot.Primary && pilot.Rounds <= pilot.Primary.magazineSize / 4);
|
|
reload.text = !pilot.Primary ? "" : pilot.ReloadRemaining > 0 ? GameLocalization.Text("ui.combat.loading", pilot.ReloadRemaining) : reloadHint;
|
|
var state = pilot.SecondaryCooldown > 0 ? GameLocalization.Text("ui.combat.charging", pilot.SecondaryCooldown) :
|
|
GameLocalization.Text(pilot.Energy < 15 ? "ui.combat.lowEnergy" : "ui.combat.ready");
|
|
var secondaryPart = game.Part(1, game.Run.equipment[1]);
|
|
secondary.text = secondaryPart ? secondaryPart.LocalizedName + " · " + state : GameLocalization.Text("ui.combat.empty");
|
|
cargo.text = GameLocalization.Text("ui.combat.cargo", game.Run.cargo.Sum(InventoryGrid.Area), game.Run.safeCase.Sum(InventoryGrid.Area));
|
|
if (redraw)
|
|
{
|
|
map.MarkDirtyRepaint();
|
|
nextMapUpdate = Time.unscaledTime + .075f;
|
|
}
|
|
}
|
|
|
|
void DrawMap(MeshGenerationContext context)
|
|
{
|
|
var pilot = game.Pilot;
|
|
if (!pilot || !game.InField) return;
|
|
var painter = context.painter2D;
|
|
var rect = map.contentRect;
|
|
// Keep north up and use one scale on both axes. Include the pilot past the yard edges.
|
|
var position = pilot.transform.position;
|
|
var scale = Mathf.Min(rect.width / Mathf.Max(90, Mathf.Abs(position.x) * 2 + 8),
|
|
rect.height / Mathf.Max(100, Mathf.Abs(position.z - 8) * 2 + 8));
|
|
Vector2 Project(float x, float z) => rect.center + new Vector2(x, 8 - z) * scale;
|
|
void Line(Vector2 a, Vector2 b)
|
|
{
|
|
painter.BeginPath(); painter.MoveTo(a); painter.LineTo(b); painter.Stroke();
|
|
}
|
|
painter.lineWidth = 1;
|
|
painter.strokeColor = new Color(.15f, .28f, .30f);
|
|
for (int x = -30; x <= 30; x += 10) Line(Project(x, -40), Project(x, 56));
|
|
for (int z = -40; z <= 50; z += 10) Line(Project(-35, z), Project(35, z));
|
|
painter.lineWidth = 2;
|
|
painter.strokeColor = new Color(.33f, .49f, .5f);
|
|
Line(Project(-35, -38), Project(-35, 58));
|
|
Line(Project(35, -38), Project(35, 58));
|
|
painter.fillColor = painter.strokeColor;
|
|
for (int i = 0; i < game.World.Cover.Count; i++)
|
|
{
|
|
var box = game.World.Cover[i];
|
|
painter.BeginPath(); painter.MoveTo(Project(box.xMin, box.yMin));
|
|
painter.LineTo(Project(box.xMax, box.yMin)); painter.LineTo(Project(box.xMax, box.yMax));
|
|
painter.LineTo(Project(box.xMin, box.yMax)); painter.ClosePath(); painter.Fill();
|
|
}
|
|
var target = game.World.NavigationTarget;
|
|
if (target)
|
|
{
|
|
var pos = Project(target.position.x, target.position.z);
|
|
painter.strokeColor = new Color(.45f, .84f, 1);
|
|
painter.BeginPath(); painter.MoveTo(pos + Vector2.up * 6);
|
|
painter.LineTo(pos + Vector2.right * 6); painter.LineTo(pos + Vector2.down * 6);
|
|
painter.LineTo(pos + Vector2.left * 6); painter.ClosePath(); painter.Stroke();
|
|
}
|
|
var pad = game.World.ExtractionPosition;
|
|
painter.strokeColor = game.World.TransportReady ? Color.green : new Color(1, .72f, .36f);
|
|
painter.BeginPath(); painter.Arc(Project(pad.x, pad.z), 6, 0, 360); painter.Stroke();
|
|
var origin = Project(position.x, position.z);
|
|
var radians = pilot.Heading * Mathf.Deg2Rad;
|
|
var forward = new Vector2(Mathf.Sin(radians), -Mathf.Cos(radians));
|
|
var right = new Vector2(-forward.y, forward.x);
|
|
painter.fillColor = new Color(.43f, 1, .80f);
|
|
painter.BeginPath(); painter.MoveTo(origin + forward * 9);
|
|
painter.LineTo(origin - forward * 6 + right * 5); painter.LineTo(origin - forward * 6 - right * 5);
|
|
painter.ClosePath(); painter.Fill();
|
|
}
|
|
}
|
|
}
|