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.LocalizedName; rounds.text = pilot.Rounds.ToString(); rounds.EnableInClassList("ammo-low", pilot.Rounds <= pilot.Primary.magazineSize / 4); reload.text = 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"); secondary.text = game.Part(1, game.Run.equipment[1]).LocalizedName + " · " + state; cargo.text = GameLocalization.Text("ui.combat.cargo", game.Run.cargo.Count); 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(); } } }