/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; using System.Collections.Generic; using System.Linq; using Cinemachine; using GameCult.Aetheria.State.Verse; using UniRx; using UnityEngine; public sealed class AetheriaUnityCurrentEntityPresentation { private readonly List _shipSubscriptions = new List(); private readonly List _targetSubscriptions = new List(); private float _hitMarkerTime; public ZoneRenderer ZoneRenderer { get; set; } public CinemachineVirtualCamera DockCamera { get; set; } public CinemachineVirtualCamera FollowCamera { get; set; } public MenuPanel Menu { get; set; } public TradeMenu TradeMenu { get; set; } public CanvasGroup GameplayUI { get; set; } public InventoryPanel ShipPanel { get; set; } public InventoryPanel TargetShipPanel { get; set; } public SchematicDisplay SchematicDisplay { get; set; } public SchematicDisplay TargetSchematicDisplay { get; set; } public ImageTargetPresentation TargetImages { get; set; } public AetheriaRuntimeCatalogSnapshot RuntimeCatalog { get; set; } public PlaceUIElementWorldspace[] Crosshairs { get; set; } = Array.Empty(); public Prototype LockIndicator { get; set; } public GameObject HitMarker { get; set; } public float HitMarkerDuration { get; set; } public (HardpointData[] hardpoints, Transform[] barrels, PlaceUIElementWorldspace crosshair)[] ArticulationGroups { get; private set; } = Array.Empty<(HardpointData[] hardpoints, Transform[] barrels, PlaceUIElementWorldspace crosshair)>(); public (LockWeapon targetLock, PlaceUIElementWorldspace indicator, Rotate spin)[] LockingIndicators { get; private set; } = Array.Empty<(LockWeapon targetLock, PlaceUIElementWorldspace indicator, Rotate spin)>(); public void BindUndocked( Entity entity, EntityInstance entityInstance, IReadOnlyList actionBarBindings, Action enablePlayerInput, Action playMusic, Action updateCurrentEntity, Action updateTargetPanel, Action reconcileVisibleTargetIndicators, Action> applyActionBarBindings) { if (entity == null || entityInstance == null) return; updateCurrentEntity?.Invoke(entity); ZoneRenderer.PerspectiveEntity = entity; Menu.gameObject.SetActive(false); DockCamera.enabled = false; FollowCamera.enabled = true; Cursor.lockState = CursorLockMode.Locked; enablePlayerInput?.Invoke(); GameplayUI.gameObject.SetActive(true); ShipPanel.Display(entity, true); SchematicDisplay.ShowShip(entity); FollowCamera.LookAt = entityInstance.LookAtPoint; FollowCamera.Follow = entityInstance.transform; ArticulationGroups = entity.Equipment .Where(HasArticulatedWeaponBehavior) .GroupBy(item => entityInstance .GetBarrel(entity.Hardpoints[item.Position.x, item.Position.y]) .GetComponentInParent()?.Group ?? -1) .Select((group, index) => { return ( group.Select(item => entity.Hardpoints[item.Position.x, item.Position.y]).ToArray(), group.Select(item => entityInstance.GetBarrel(entity.Hardpoints[item.Position.x, item.Position.y])).ToArray(), Crosshairs[index] ); }).ToArray(); foreach (var crosshair in Crosshairs) crosshair.gameObject.SetActive(false); foreach (var group in ArticulationGroups) group.crosshair.gameObject.SetActive(true); _shipSubscriptions.Add(entity.TargetedByCount.Subscribe(count => { playMusic?.Invoke(count > 0 ? MusicType.Combat : MusicType.Overworld); })); _shipSubscriptions.Add(entity.Target.Subscribe(target => { ClearTargetSubscriptions(); updateTargetPanel?.Invoke(target); ConfigureTargetShieldPresentation(target); if (target != null) { _targetSubscriptions.Add(target.IncomingHit.Where(e => e == entity).Subscribe(_ => { HitMarker.SetActive(true); _hitMarkerTime = HitMarkerDuration; })); } })); reconcileVisibleTargetIndicators?.Invoke(); LockingIndicators = entity.GetBehaviors() .Select(x => { var indicator = LockIndicator.Instantiate(); return (x, indicator, indicator.GetComponent()); }).ToArray(); applyActionBarBindings?.Invoke(actionBarBindings ?? Array.Empty()); } public bool HasArticulatedWeaponBehavior(EquippedItem item) { return AetheriaUnityWeaponPresentationPolicy.HasArticulatedWeaponBehavior(RuntimeCatalog, item); } public void BindDocked( Entity entity, AetheriaRuntimeCurrentDockingDocument docking, AetheriaRuntimeZoneRenderDocument zoneRender) { if (entity == null) return; TradeMenu.Inventory = entity.CargoBays.First(); ZoneRenderer.PerspectiveEntity = entity; DockCamera.enabled = true; FollowCamera.enabled = false; var orbital = entity as OrbitalEntity; if (orbital == null || !ZoneRenderer.TryGetEntityInstance(orbital, out var orbitalInstance)) { Debug.LogError($"Attempted to dock at entity {entity.Name}, but ZoneRenderer has no daemon-indexed instance."); return; } DockCamera.Follow = orbitalInstance.transform; var parentOrbitPlanetBodyKey = ResolveDockParentBodyKey(orbital, docking, zoneRender); if (ZoneRenderer.TryGetBodyView(parentOrbitPlanetBodyKey, out var parentBodyView)) DockCamera.LookAt = parentBodyView.Body.transform; else DockCamera.LookAt = ZoneRenderer.ZoneRoot; Menu.ShowTab(MenuTab.Inventory); } private static string ResolveDockParentBodyKey( OrbitalEntity orbital, AetheriaRuntimeCurrentDockingDocument docking, AetheriaRuntimeZoneRenderDocument zoneRender) { if (orbital == null) return ""; var bodyPoses = zoneRender?.BodyPoses ?? Array.Empty(); var parentOrbitKey = bodyPoses .FirstOrDefault(body => body != null && string.Equals(body.OrbitKey ?? "", orbital.OrbitKey, StringComparison.Ordinal)) ?.ParentOrbitKey ?? docking?.DockParentParentOrbitKey ?? ""; return bodyPoses .FirstOrDefault(body => body != null && string.Equals(body.OrbitKey ?? "", parentOrbitKey, StringComparison.Ordinal)) ?.BodyKey ?? docking?.DockParentParentBodyKey ?? ""; } public void ClearBinding() { foreach (var (_, indicator, _) in LockingIndicators) indicator.GetComponent().ReturnToPool(); LockingIndicators = Array.Empty<(LockWeapon targetLock, PlaceUIElementWorldspace indicator, Rotate spin)>(); ArticulationGroups = Array.Empty<(HardpointData[] hardpoints, Transform[] barrels, PlaceUIElementWorldspace crosshair)>(); foreach (var subscription in _shipSubscriptions) subscription.Dispose(); _shipSubscriptions.Clear(); ClearTargetSubscriptions(); } public void Tick(float deltaTime) { _hitMarkerTime -= deltaTime; if (HitMarker.activeSelf && _hitMarkerTime < 0) HitMarker.SetActive(false); } private void ClearTargetSubscriptions() { foreach (var subscription in _targetSubscriptions) subscription.Dispose(); _targetSubscriptions.Clear(); } private void ConfigureTargetShieldPresentation(Entity target) { if (target == null || TargetImages == null) return; if (target.Shield != null) { TargetImages.TargetShieldsBackground.color = new Color(TargetImages.ShieldColor.r, TargetImages.ShieldColor.g, TargetImages.ShieldColor.b, .4f); TargetImages.TargetShieldsIcon.color = TargetImages.ShieldColor; TargetImages.TargetShieldsIcon.sprite = TargetImages.ShieldIcon; } else { TargetImages.TargetShieldsBackground.color = new Color(TargetImages.NoShieldColor.r, TargetImages.NoShieldColor.g, TargetImages.NoShieldColor.b, .4f); TargetImages.TargetShieldsIcon.color = TargetImages.NoShieldColor; TargetImages.TargetShieldsIcon.sprite = TargetImages.NoShieldIcon; } } } public sealed class ImageTargetPresentation { public UnityEngine.UI.Image TargetShieldsBackground { get; set; } public UnityEngine.UI.Image TargetShieldsIcon { get; set; } public Color ShieldColor { get; set; } public Color NoShieldColor { get; set; } public Sprite ShieldIcon { get; set; } public Sprite NoShieldIcon { get; set; } }