/* 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; using System.Collections.Generic; using System.IO; using System.Linq; using Cinemachine; using Ink; using Ink.Runtime; using MessagePack; using TMPro; using UniRx; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.Rendering.PostProcessing; using UnityEngine.Serialization; using UnityEngine.UI; using Unity.Mathematics; using UnityEngine.EventSystems; using static Unity.Mathematics.math; using float2 = Unity.Mathematics.float2; using float3 = Unity.Mathematics.float3; using Path = System.IO.Path; using quaternion = Unity.Mathematics.quaternion; using Random = UnityEngine.Random; public class ActionGameManager : MonoBehaviour { public static ActionGameManager Instance { get; private set; } private static DirectoryInfo _gameDataDirectory; public static DirectoryInfo GameDataDirectory { get => _gameDataDirectory ??= new DirectoryInfo(Application.dataPath).Parent.CreateSubdirectory("GameData"); } private static CultCache _cultCache; public static CultCache CultCache { get { if (_cultCache != null) return _cultCache; _cultCache = new CultCache(Path.Combine(GameDataDirectory.FullName, "AetherDB.msgpack")); _cultCache.Load(); return _cultCache; } } private static PlayerSettings _playerSettings; public static PlayerSettings PlayerSettings { get => _playerSettings ??= File.Exists(_playerSettingsFilePath) ? MessagePackSerializer.Deserialize(File.ReadAllBytes(_playerSettingsFilePath)) : new PlayerSettings {Name = Environment.UserName}; } private static string _playerSettingsFilePath => Path.Combine(GameDataDirectory.FullName, "PlayerSettings.msgpack"); public static void SavePlayerSettings() { File.WriteAllBytes(_playerSettingsFilePath, MessagePackSerializer.Serialize(_playerSettings)); } public static Sector CurrentSector; public static bool IsTutorial; public GameSettings Settings; //public string StarterShipTemplate = "Longinus"; public float2 Sensitivity; public int Credits = 15000000; public float TargetSpottedBlinkFrequency = 20; public float TargetSpottedBlinkOffset = -.25f; [Header("Postprocessing")] public float DeathPPTransitionTime; public PostProcessVolume DeathPP; public PostProcessVolume HeatstrokePP; public PostProcessVolume HypothermiaPP; public PostProcessVolume SevereHeatstrokePP; public PostProcessVolume SevereHypothermiaPP; [Header("Input Icons")] public Sprite ShiftIcon; public Sprite MouseLeftIcon; public Sprite MouseRightIcon; public Sprite MouseMiddleIcon; [Header("Scene Links")] public Transform ActionBar; public ActionBarSlot ActionBarSlot; public Transform EffectManagerParent; public TradeMenu TradeMenu; public Prototype HostileTargetIndicator; public PlaceUIElementWorldspace ViewDot; public PlaceUIElementWorldspace TargetIndicator; public Image TargetHitpointsFill; public Image TargetVisibilityFill; public Image VisibilityToTargetFill; public Image TargetShieldsFill; public Prototype LockIndicator; public PlaceUIElementWorldspace[] Crosshairs; public GameObject HitMarker; public float HitMarkerDuration; public EventLog EventLog; public ZoneRenderer ZoneRenderer; public CinemachineVirtualCamera DockCamera; public CinemachineVirtualCamera FollowCamera; public CinemachineVirtualCamera WormholeCamera; public CanvasGroup GameplayUI; public MainMenu MainMenu; public MenuPanel Menu; public MapRenderer MenuMap; //public SectorRenderer SectorRenderer; public SectorMap SectorMap; public VolumeSampling VolumeRenderer; public SchematicDisplay SchematicDisplay; public SchematicDisplay TargetSchematicDisplay; public InventoryMenu Inventory; public InventoryPanel ShipPanel; public InventoryPanel TargetShipPanel; public ConfirmationDialog Dialog; public ContextMenu Context; public DropdownMenu Dropdown; public float IntroDuration; //public PlayerInput Input; // private CinemachineFramingTransposer _transposer; // private CinemachineComposer _composer; private DirectoryInfo _loadoutPath; private bool _paused; private float _time; private int _zoomLevelIndex; private Entity _currentEntity; // private ShipInput _shipInput; private float2 _entityYawPitch; private float3 _viewDirection; private (HardpointData[] hardpoints, Transform[] barrels, PlaceUIElementWorldspace crosshair)[] _articulationGroups; private (LockWeapon targetLock, PlaceUIElementWorldspace indicator, Rotate spin)[] _lockingIndicators; private Dictionary _visibleHostileIndicators = new Dictionary(); private List _shipSubscriptions = new List(); private List _targetSubscriptions = new List(); private float _severeHeatstrokePhase; private bool _uiHidden; private bool _menuShown; private List _actionBarSlots = new List(); private List _actionBarActions = new List(); private float _hitMarkerTime; public AetheriaInput Input { get; private set; } public EquippedDockingBay DockingBay { get; private set; } public Entity DockedEntity { get; private set; } public Entity CurrentEntity { get => _currentEntity; set => _currentEntity = value; } public ItemManager ItemManager { get; private set; } public Zone Zone { get; private set; } public List Loadouts { get; } = new List(); public IEnumerable GetStories => _stories; private readonly (float2 direction, string name)[] _directions = { (float2(0, 1), "Front"), (float2(1, 0), "Right"), (float2(-1, 0), "Left"), (float2(0, -1), "Rear") }; public DragObject DragObject { get; private set; } private Func _endDragCallback; private List _stories = new List(); public EntitySettings NewEntitySettings { get => MessagePackSerializer.Deserialize(MessagePackSerializer.Serialize(Settings.GameplaySettings.DefaultEntitySettings)); } public void SaveLoadout(EntityPack pack) { File.WriteAllBytes(Path.Combine(_loadoutPath.FullName, $"{pack.Name}.loadout"), MessagePackSerializer.Serialize(pack)); } private void OnApplicationQuit() => SaveState(); public void SaveState() { PlayerSettings.SavedRun = CurrentSector == null ? null : new SavedGame(CurrentSector, Zone, DockedEntity ?? CurrentEntity); if(PlayerSettings.SavedRun != null) { PlayerSettings.SavedRun.IsTutorial = IsTutorial; } SavePlayerSettings(); } private void OnDisable() { Input.Dispose(); ConsoleController.ClearCommands(); EntityInstance.ClearWeaponManagers(); } void Start() { Instance = this; EntityInstance.EffectManagerParent = EffectManagerParent; AkSoundEngine.RegisterGameObj(gameObject); ConsoleController.MessageReceiver = this; ItemManager = new ItemManager(CultCache, Settings.GameplaySettings, Debug.Log); ZoneRenderer.ItemManager = ItemManager; var narrativePath = GameDataDirectory.CreateSubdirectory("Narrative"); // TODO: Process Stories // _loadoutPath = GameDataDirectory.CreateSubdirectory("Loadouts"); // Loadouts.AddRange(_loadoutPath.EnumerateFiles("*.loadout") // .Select(fi => MessagePackSerializer.Deserialize(File.ReadAllBytes(fi.FullName)))); #region Input Handling Input = new AetheriaInput(); Input.Global.Enable(); _zoomLevelIndex = Settings.DefaultMinimapZoom; Input.Player.MinimapZoom.performed += context => { _zoomLevelIndex = (_zoomLevelIndex + 1) % Settings.MinimapZoomLevels.Length; ZoneRenderer.MinimapDistance = Settings.MinimapZoomLevels[_zoomLevelIndex]; }; Input.Global.ZoneMap.performed += context => { ToggleMenuTab(MenuTab.Map); MenuMap.Position = CurrentEntity.Position.xz; }; Input.Global.Inventory.performed += context => ToggleMenuTab(MenuTab.Inventory); Input.Global.GalaxyMap.performed += context => ToggleMenuTab(MenuTab.Galaxy); Input.Global.Dock.performed += context => { if (EventSystem.current.currentSelectedGameObject != null && EventSystem.current.currentSelectedGameObject.GetComponent() != null) return; if (MainMenu.gameObject.activeSelf) return; if (CurrentEntity == null) { AkSoundEngine.PostEvent("UI_Fail", gameObject); Dialog.Clear(); Dialog.Title.text = "Can't undock. You dont have a ship!"; Dialog.Show(); Dialog.MoveToCursor(); } else if (CurrentEntity.Parent == null) Dock(); else Undock(); }; Input.Global.MainMenu.performed += context => ToggleMainMenu(); Input.Player.EnterWormhole.performed += context => { foreach (var wormhole in ZoneRenderer.WormholeInstances.Keys) { if (!(length(wormhole.Position - CurrentEntity.Position.xz) < Settings.GameplaySettings.WormholeExitRadius)) continue; EnterWormhole(wormhole); } }; Input.Player.HideUI.performed += context => { _uiHidden = !_uiHidden; GameplayUI.alpha = _uiHidden ? 0 : 1; }; Input.Player.OverrideShutdown.performed += context => { CurrentEntity.OverrideShutdown = !CurrentEntity.OverrideShutdown; }; Input.Player.Ping.performed += context => { CurrentEntity.Sensor?.Ping(); }; Input.Player.ToggleHeatsinks.performed += context => { CurrentEntity.HeatsinksEnabled = !CurrentEntity.HeatsinksEnabled; AkSoundEngine.PostEvent(CurrentEntity.HeatsinksEnabled ? "UI_Success" : "UI_Fail", gameObject); }; Input.Player.ToggleShield.performed += context => { if (CurrentEntity.Shield != null) { CurrentEntity.Shield.Item.Enabled.Value = !CurrentEntity.Shield.Item.Enabled.Value; AkSoundEngine.PostEvent(CurrentEntity.Shield.Item.Enabled.Value ? "UI_Success" : "UI_Fail", gameObject); } }; #region Targeting Input.Player.TargetReticle.performed += context => { if (!CurrentEntity.VisibleHostiles.Any()) return; var underReticle = CurrentEntity.VisibleHostiles.Where(x => x != CurrentEntity) .MaxBy(x => dot(normalize(x.Position - CurrentEntity.Position), CurrentEntity.LookDirection)); CurrentEntity.Target.Value = CurrentEntity.Target.Value == underReticle ? null : underReticle; }; Input.Player.TargetNearest.performed += context => { if(CurrentEntity.VisibleHostiles.Any()) { CurrentEntity.Target.Value = CurrentEntity.VisibleHostiles.Where(x => x != CurrentEntity) .MaxBy(x => length(x.Position - CurrentEntity.Position)); } }; Input.Player.TargetNext.performed += context => { if (!CurrentEntity.VisibleHostiles.Any()) return; var targets = CurrentEntity.VisibleHostiles.Where(x => x != CurrentEntity).OrderBy(x => length(x.Position - CurrentEntity.Position)).ToArray(); var currentTargetIndex = Array.IndexOf(targets, CurrentEntity.Target.Value); CurrentEntity.Target.Value = targets[(currentTargetIndex + 1) % targets.Length]; }; Input.Player.TargetPrevious.performed += context => { if (!CurrentEntity.VisibleHostiles.Any()) return; var targets = CurrentEntity.VisibleHostiles.Where(x => x != CurrentEntity).OrderBy(x => length(x.Position - CurrentEntity.Position)).ToArray(); var currentTargetIndex = Array.IndexOf(targets, CurrentEntity.Target.Value); CurrentEntity.Target.Value = targets[(currentTargetIndex + targets.Length - 1) % targets.Length]; }; #endregion #region Action Bar ActionBarSlot createBinding(string controlPath) { var action = new InputAction(binding: controlPath); _actionBarActions.Add(action); var slot = Instantiate(ActionBarSlot, ActionBar); slot.Binding = null; _actionBarSlots.Add(slot); action.started += context => slot.Binding?.Activate(); action.canceled += context => slot.Binding?.Deactivate(); slot.PointerEnterTrigger.OnPointerEnterAsObservable().Subscribe(_ => { //Debug.Log($"Pointer entered action bar slot {controlPath}"); RegisterDragTarget(dragAction => { //Debug.Log("Registering binding!"); switch (dragAction) { case EquippedItemDragObject equippedItemDragAction: var trigger = equippedItemDragAction.EquippedItem.GetBehavior(); if (trigger == null) return false; slot.Binding = new ActionBarGearBinding(CurrentEntity, slot, equippedItemDragAction.EquippedItem, trigger); return true; case ItemInstanceDragObject itemInstanceDragAction: if (!(itemInstanceDragAction.Item.Data.Value is ConsumableItemData consumable)) return false; slot.Binding = new ActionBarConsumableBinding(CurrentEntity, slot, consumable); return true; case WeaponGroupDragObject weaponGroupDragAction: slot.Binding = new ActionBarWeaponGroupBinding(CurrentEntity, slot, weaponGroupDragAction.Group); return true; default: throw new ArgumentOutOfRangeException(nameof(dragAction)); } }); }); slot.PointerExitTrigger.OnPointerExitAsObservable().Subscribe(_ => { //Debug.Log($"Pointer exited action bar slot {controlPath}"); UnregisterDragTarget(); }); return slot; } var shift = createBinding("/leftShift"); shift.InputIcon.sprite = ShiftIcon; shift.InputLabel.gameObject.SetActive(false); var left = createBinding("/leftButton"); left.InputIcon.sprite = MouseLeftIcon; left.InputLabel.gameObject.SetActive(false); var right = createBinding("/rightButton"); right.InputIcon.sprite = MouseRightIcon; right.InputLabel.gameObject.SetActive(false); var middle = createBinding("/middleButton"); middle.InputIcon.sprite = MouseMiddleIcon; middle.InputLabel.gameObject.SetActive(false); for (int i = 1; i < 6; i++) { var num = createBinding($"/{i}"); num.InputIcon.gameObject.SetActive(false); num.InputLabel.text = i.ToString(); } #endregion #endregion StartGame(); ConsoleController.AddCommand("revealzones", _ => { foreach (var zones in CurrentSector.Zones .Where(z=>!CurrentSector.DiscoveredZones.Contains(z)) .GroupBy(z=>z.Distance[CurrentSector.Entrance]) .OrderBy(g=>g.Key)) { SectorMap.QueueZoneReveal(zones); } }); ConsoleController.AddCommand("give", args => { var itemName = string.Join(" ", args); var item = ItemManager.ItemData.GetAll() .FirstOrDefault(itemData => string.Equals(itemData.Name, itemName, StringComparison.InvariantCultureIgnoreCase)); if (item != null) { _currentEntity.CargoBays.First().TryStore(ItemManager.CreateInstance(item, .95f)); } }); ConsoleController.AddCommand("trackmissile", _ => { foreach (var missileManager in FindObjectsOfType()) { missileManager.OnFireGuided.Where(x => x.source == _currentEntity).Take(1).Subscribe(x => { FollowCamera.Follow = x.missile.transform; FollowCamera.LookAt = x.target; x.missile.OnKill += () => { FollowCamera.LookAt = ZoneRenderer.EntityInstances[CurrentEntity].LookAtPoint; FollowCamera.Follow = ZoneRenderer.EntityInstances[CurrentEntity].transform; }; }); } }); ConsoleController.AddCommand("spawnturret", _ => { var nearestFaction = CurrentSector.Factions.MinBy(f => CurrentSector.HomeZones[f].Distance[Zone.SectorZone]); var loadoutGenerator = IsTutorial ? new LoadoutGenerator( ref ItemManager.Random, ItemManager, CurrentSector, Zone.SectorZone, nearestFaction, .5f) : new LoadoutGenerator( ref ItemManager.Random, ItemManager, nearestFaction, .5f); var turret = EntitySerializer.Unpack(ItemManager, Zone, loadoutGenerator.GenerateTurretLoadout(), true); turret.Position.xz = _currentEntity.Position.xz + ItemManager.Random.NextFloat2Direction() * ItemManager.Random.NextFloat(50, 500); turret.Zone = Zone; Zone.Entities.Add(turret); turret.Activate(); }); // ConsoleController.AddCommand("pingscene", // _ => // { // var startTime = Time.time; // Observable.EveryUpdate().TakeWhile(_ => Time.time - startTime < 5).Subscribe( // _ => Debug.Log($"{(int) (Time.time - startTime)}"), // () => // { // var nearestFaction = CurrentSector.Factions.MinBy(f => CurrentSector.HomeZones[f].Distance[Zone.SectorZone]); // var nearestFactionHomeZone = CurrentSector.HomeZones[nearestFaction]; // var factionPresence = nearestFaction.InfluenceDistance - nearestFactionHomeZone.Distance[Zone.SectorZone] + 1; // // var loadoutGenerator = new LoadoutGenerator( // ref ItemManager.Random, // ItemManager, // CurrentSector, // Zone.SectorZone, // nearestFaction, // .5f); // // for (int i = 0; i < 8; i++) // { // var ship = EntitySerializer.Unpack(ItemManager, Zone, loadoutGenerator.GenerateShipLoadout(), true); // ship.Position.xz = _currentEntity.Position.xz + // ItemManager.Random.NextFloat2Direction() * ItemManager.Random.NextFloat(50, 500); // ship.Zone = Zone; // Zone.Entities.Add(ship); // ship.Activate(); // } // // for (int i = 0; i < 8; i++) // { // var turret = EntitySerializer.Unpack(ItemManager, Zone, loadoutGenerator.GenerateTurretLoadout(), true); // turret.Position.xz = _currentEntity.Position.xz + // ItemManager.Random.NextFloat2Direction() * ItemManager.Random.NextFloat(50, 500); // turret.Zone = Zone; // Zone.Entities.Add(turret); // turret.Activate(); // } // }); // }); } public void BeginDrag(DragObject dragObject) { this.DragObject = dragObject; } public void RegisterDragTarget(Func onEndDrag) { _endDragCallback = onEndDrag; } public void UnregisterDragTarget() { _endDragCallback = null; } public bool EndDrag() { var success = _endDragCallback?.Invoke(DragObject); DragObject = null; return success ?? false; } public void EnablePlayerInput() { Input.Player.Enable(); foreach (var a in _actionBarActions) a.Enable(); } public void DisablePlayerInput() { Input.Player.Disable(); foreach (var a in _actionBarActions) a.Disable(); } private void EnterWormhole(Wormhole wormhole) { if (!(CurrentEntity is Ship ship)) return; // var wormholeCameraFollow = new GameObject("Wormhole Camera Follow").transform; // wormholeCameraFollow.position = new Vector3(wormhole.Position.x, -50, wormhole.Position.y); // wormholeCameraFollow.rotation = Quaternion.LookRotation(Vector3.down, ship.LookDirection); // WormholeCamera.enabled = true; // WormholeCamera.Follow = wormholeCameraFollow; // FollowCamera.enabled = false; ship.EnterWormhole(wormhole.Position); ship.OnEnteredWormhole += () => { var oldZone = Zone; PopulateLevel(wormhole.Target); foreach (var zone in wormhole.Target.AdjacentZones) CurrentSector.DiscoveredZones.Add(zone); SectorMap.QueueZoneReveal(wormhole.Target.AdjacentZones); ship.ExitWormhole(ZoneRenderer.WormholeInstances.Keys.First(w => w.Target == oldZone.SectorZone).Position, Settings.GameplaySettings.WormholeExitVelocity * ItemManager.Random.NextFloat2Direction()); CurrentEntity.Zone = Zone; SaveState(); }; } public void PopulateLevel(SectorZone sectorZone) { if (sectorZone == null) throw new ArgumentNullException(nameof(sectorZone)); if (sectorZone.Contents == null) { sectorZone.PackedContents ??= ZoneGenerator.GenerateZone( ItemManager, Settings.ZoneSettings, CurrentSector, sectorZone, IsTutorial ); sectorZone.Contents = new Zone(ItemManager, Settings.PlanetSettings, sectorZone.PackedContents, sectorZone); } Zone = sectorZone.Contents; Zone.Log = s => Debug.Log($"Zone: {s}"); if (CurrentEntity != null) { CurrentEntity.Deactivate(); CurrentEntity.Zone.Entities.Remove(CurrentEntity); CurrentEntity.Zone = Zone; Zone.Entities.Add(CurrentEntity); CurrentEntity.Activate(); } ZoneRenderer.LoadZone(Zone); if (CurrentEntity != null) { UnbindEntity(); BindToEntity(CurrentEntity); } } private void ToggleMainMenu() { if (EventSystem.current.currentSelectedGameObject != null && EventSystem.current.currentSelectedGameObject.GetComponent() != null) return; if (CurrentEntity == null) return; if (MainMenu.gameObject.activeSelf) { _paused = false; VolumeRenderer.EnableDepth = true; MainMenu.gameObject.SetActive(false); if (_menuShown) { Menu.gameObject.SetActive(true); } else { GameplayUI.gameObject.SetActive(true); EnablePlayerInput(); Cursor.lockState = CursorLockMode.Locked; } } else { _paused = true; VolumeRenderer.EnableDepth = false; MainMenu.gameObject.SetActive(true); _menuShown = Menu.gameObject.activeSelf; if (_menuShown) { Menu.gameObject.SetActive(false); } else { GameplayUI.gameObject.SetActive(false); Cursor.lockState = CursorLockMode.None; DisablePlayerInput(); } } } private void ToggleMenuTab(MenuTab tab) { if (EventSystem.current.currentSelectedGameObject != null && EventSystem.current.currentSelectedGameObject.GetComponent() != null) return; if (MainMenu.gameObject.activeSelf) return; if (Menu.gameObject.activeSelf && Menu.CurrentTab == tab) { Cursor.lockState = CursorLockMode.Locked; Menu.gameObject.SetActive(false); if (CurrentEntity != null && CurrentEntity.Parent == null) { EnablePlayerInput(); GameplayUI.gameObject.SetActive(true); SchematicDisplay.ShowShip(CurrentEntity); ShipPanel.Display(CurrentEntity, true); } return; } Cursor.lockState = CursorLockMode.None; DisablePlayerInput(); Menu.ShowTab(tab); GameplayUI.gameObject.SetActive(false); } private void StartGame() { if (CurrentSector != null) { if (PlayerSettings.SavedRun == null) { SectorMap.QueueZoneReveal(CurrentSector.Entrance.AdjacentZones.Prepend(CurrentSector.Entrance)); PopulateLevel(CurrentSector.Entrance); var loadoutGenerator = IsTutorial ? new LoadoutGenerator(ref ItemManager.Random, ItemManager, null, 2) : new LoadoutGenerator(ref ItemManager.Random, ItemManager, CurrentSector, Zone.SectorZone, null, 2); var ship = EntitySerializer.Unpack( ItemManager, Zone, loadoutGenerator.GenerateShipLoadout(data => string.IsNullOrEmpty(Settings.StartingHullName) || data.Name==Settings.StartingHullName ), true); // EntitySerializer.Unpack(ItemManager, Zone, Loadouts.First(x => x.Name == StarterShipTemplate), true); ((Ship) ship).IsPlayerShip = true; ship.Position = float3.zero; ship.Zone = Zone; Zone.Entities.Add(ship); ship.Activate(); BindToEntity(ship); } else { foreach(var group in CurrentSector.DiscoveredZones .GroupBy(dz=>dz.Distance[CurrentSector.Entrance])) SectorMap.QueueZoneReveal(group); PopulateLevel(CurrentSector.Zones[PlayerSettings.SavedRun.CurrentZone]); var targetEntity = Zone.Entities[PlayerSettings.SavedRun.CurrentZoneEntity]; if (targetEntity is OrbitalEntity orbitalEntity) { CurrentEntity = targetEntity.Children.First(c => c is Ship {IsPlayerShip: true}); DoDock(orbitalEntity, orbitalEntity.DockingBays.First()); } else { //StartCoroutine(IntroCutscene(targetEntity as Ship)); BindToEntity(targetEntity); } } } } private IEnumerator IntroCutscene(Ship ship) { ZoneRenderer.PerspectiveEntity = ship; var entityPosition = ship.Position.xz; var followOrbit = Zone.Orbits.Keys.MinBy(o => lengthsq(Zone.GetOrbitPosition(o) - entityPosition)); var followPlanet = ZoneRenderer.Planets[Zone.Planets.FirstOrDefault(p => p.Value.Orbit == followOrbit).Key]; DockCamera.Follow = followPlanet.Body.transform; var rootOrbit = followOrbit; while (Zone.Orbits[rootOrbit].Data.Parent != Guid.Empty) rootOrbit = Zone.Orbits[rootOrbit].Data.Parent; var rootPlanet = ZoneRenderer.Planets[Zone.Planets.FirstOrDefault(p => p.Value.Orbit == rootOrbit).Key]; DockCamera.LookAt = rootPlanet.Body.transform; var shipVelocity = ship.GetBehavior().Limit; var followOrbitPosition = Zone.GetOrbitPosition(followOrbit); var shipDirection = normalize(Zone.GetOrbitPosition(rootOrbit) - followOrbitPosition); ship.Position.xz = followOrbitPosition - shipDirection * shipVelocity * IntroDuration; var startTime = Time.time; while (Time.time - startTime < IntroDuration) { ship.Direction = shipDirection; ship.Velocity = shipDirection * shipVelocity; yield return null; } BindToEntity(ship); } public void Dock() { if (CurrentEntity.Parent != null) return; if (CurrentEntity is Ship ship) { foreach (var entity in Zone.Entities.ToArray()) { if (lengthsq(entity.Position.xz - CurrentEntity.Position.xz) < Settings.GameplaySettings.DockingDistance * Settings.GameplaySettings.DockingDistance) { var bay = entity.TryDock(ship); if (bay != null) { UnbindEntity(); DoDock(entity, bay); AkSoundEngine.PostEvent("Dock", gameObject); return; } } } } AkSoundEngine.PostEvent("Dock_Fail", gameObject); } private void DoDock(Entity entity, EquippedDockingBay dockingBay) { TradeMenu.Inventory = entity.CargoBays.First(); DockedEntity = entity; ZoneRenderer.PerspectiveEntity = DockedEntity; DockingBay = dockingBay; DockCamera.enabled = true; FollowCamera.enabled = false; var orbital = (OrbitalEntity) entity; DockCamera.Follow = ZoneRenderer.EntityInstances[orbital].transform; var parentOrbit = Zone.Orbits[orbital.OrbitData].Data.Parent; var parentOrbitPlanet = Zone.Planets.FirstOrDefault(p => p.Value.Orbit == parentOrbit).Key; if (ZoneRenderer.Planets.ContainsKey(parentOrbitPlanet)) DockCamera.LookAt = ZoneRenderer.Planets[parentOrbitPlanet].Body.transform; else DockCamera.LookAt = ZoneRenderer.ZoneRoot; Menu.ShowTab(MenuTab.Inventory); } public void Undock() { if (CurrentEntity.Parent == null) return; if (CurrentEntity is Ship ship) { if (CurrentEntity.GetBehavior() == null) { Dialog.Clear(); Dialog.Title.text = "Can't undock. Missing cockpit component!"; Dialog.Show(); Dialog.MoveToCursor(); AkSoundEngine.PostEvent("UI_Fail", gameObject); } else if (CurrentEntity.GetBehavior() == null && CurrentEntity.GetBehavior() == null) { Dialog.Clear(); Dialog.Title.text = "Can't undock. Missing thruster component!"; Dialog.Show(); Dialog.MoveToCursor(); AkSoundEngine.PostEvent("UI_Fail", gameObject); } else if (CurrentEntity.GetBehavior() == null) { Dialog.Clear(); Dialog.Title.text = "Can't undock. Missing reactor component!"; Dialog.Show(); Dialog.MoveToCursor(); AkSoundEngine.PostEvent("UI_Fail", gameObject); } else if (CurrentEntity.Parent.TryUndock(ship)) { BindToEntity(ship); AkSoundEngine.PostEvent("Undock", gameObject); } else { Dialog.Title.text = "Can't undock. Must empty docking bay!"; Dialog.Show(); Dialog.MoveToCursor(); AkSoundEngine.PostEvent("UI_Fail", gameObject); } } } private void UnbindEntity() { foreach (var indicator in _visibleHostileIndicators) { Destroy(indicator.Value.gameObject); } _visibleHostileIndicators.Clear(); if(_lockingIndicators!=null) foreach(var (_, indicator, _) in _lockingIndicators) indicator.GetComponent().ReturnToPool(); DisablePlayerInput(); Cursor.lockState = CursorLockMode.None; GameplayUI.gameObject.SetActive(false); foreach(var subscription in _shipSubscriptions) subscription.Dispose(); _shipSubscriptions.Clear(); } private void BindToEntity(Entity entity) { if (!ZoneRenderer.EntityInstances.ContainsKey(entity)) { Debug.LogError($"Attempted to bind to entity {entity.Name}, but SectorRenderer has no such instance!"); return; } CurrentEntity = entity; DeathPP.weight = 0; ZoneRenderer.PerspectiveEntity = CurrentEntity; Menu.gameObject.SetActive(false); DockedEntity = null; DockingBay = null; DockCamera.enabled = false; FollowCamera.enabled = true; if (length(CurrentEntity.Direction) > .1f) _viewDirection = float3(CurrentEntity.Direction.x,0,CurrentEntity.Direction.y); Cursor.lockState = CursorLockMode.Locked; EnablePlayerInput(); GameplayUI.gameObject.SetActive(true); ShipPanel.Display(CurrentEntity, true); SchematicDisplay.ShowShip(CurrentEntity); FollowCamera.LookAt = ZoneRenderer.EntityInstances[CurrentEntity].LookAtPoint; FollowCamera.Follow = ZoneRenderer.EntityInstances[CurrentEntity].transform; _articulationGroups = CurrentEntity.Equipment .Where(item => item.Behaviors.Any(x => x.Data is WeaponData && !(x.Data is LauncherData))) .GroupBy(item => ZoneRenderer.EntityInstances[CurrentEntity] .GetBarrel(CurrentEntity.Hardpoints[item.Position.x, item.Position.y]) .GetComponentInParent()?.Group ?? -1) .Select((group, index) => { return ( group.Select(item => CurrentEntity.Hardpoints[item.Position.x, item.Position.y]).ToArray(), group.Select(item => ZoneRenderer.EntityInstances[CurrentEntity].GetBarrel(CurrentEntity.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(CurrentEntity.Target.Subscribe(target => { // Clear previous subscriptions related to currently targeted enemy foreach(var subscription in _targetSubscriptions) subscription.Dispose(); _targetSubscriptions.Clear(); TargetIndicator.gameObject.SetActive(CurrentEntity.Target.Value != null); TargetShipPanel.gameObject.SetActive(target != null); if (target != null) { TargetShipPanel.Display(target, true); TargetSchematicDisplay.ShowShip(target, CurrentEntity); // Subscribe to incoming hits from the player ship to display the hit marker _targetSubscriptions.Add(target.IncomingHit.Where(e => e == CurrentEntity).Subscribe(_ => { HitMarker.SetActive(true); _hitMarkerTime = HitMarkerDuration; })); } })); foreach (var hostile in CurrentEntity.VisibleHostiles) { var indicator = HostileTargetIndicator.Instantiate(); _visibleHostileIndicators.Add(hostile, indicator); } _shipSubscriptions.Add(CurrentEntity.VisibleHostiles.ObserveAdd().Subscribe(addEvent => { var indicator = HostileTargetIndicator.Instantiate(); _visibleHostileIndicators.Add(addEvent.Value, indicator); })); _shipSubscriptions.Add(CurrentEntity.VisibleHostiles.ObserveRemove().Subscribe(removeEvent => { _visibleHostileIndicators[removeEvent.Value].GetComponent().ReturnToPool(); _visibleHostileIndicators.Remove(removeEvent.Value); })); _shipSubscriptions.Add(CurrentEntity.Death.Subscribe(Die)); _lockingIndicators = CurrentEntity.GetBehaviors() .Select(x => { var i = LockIndicator.Instantiate(); return (x, i, i.GetComponent()); }).ToArray(); } private void Die(CauseOfDeath cause) { var deathTime = Time.time; UnbindEntity(); CurrentEntity = null; VolumeRenderer.EnableDepth = false; MainMenu.gameObject.SetActive(true); Menu.gameObject.SetActive(false); CurrentSector = null; SavePlayerSettings(); Observable.EveryUpdate() .Where(_ => Time.time - deathTime < DeathPPTransitionTime) .Subscribe(_ => { var t = (Time.time - deathTime) / DeathPPTransitionTime; if(cause==CauseOfDeath.Heatstroke) { HeatstrokePP.weight = 1 - t; SevereHeatstrokePP.weight = 1 - t; } else if (cause == CauseOfDeath.Hypothermia) { HypothermiaPP.weight = 1 - t; SevereHypothermiaPP.weight = 1 - t; } DeathPP.weight = t; }, () => { HeatstrokePP.weight = 0; SevereHeatstrokePP.weight = 0; HypothermiaPP.weight = 0; SevereHypothermiaPP.weight = 0; DeathPP.weight = 1; }); } public void SaveZone(string name) => File.WriteAllBytes( Path.Combine(_gameDataDirectory.FullName, $"{name}.zone"), MessagePackSerializer.Serialize(Zone.PackZone())); // public void ToggleEditMode() // { // _editMode = !_editMode; // FollowCamera.gameObject.SetActive(!_editMode); // TopDownCamera.gameObject.SetActive(_editMode); // } public IEnumerable AvailableCargoBays() { if (CurrentEntity.Parent != null) { foreach (var bay in CurrentEntity.Parent.DockingBays) { if (bay.DockedShip.IsPlayerShip) yield return bay; } } } public IEnumerable AvailableEntities() { if(DockedEntity != null) foreach (var entity in DockedEntity.Children) { if (entity is Ship { IsPlayerShip: true }) yield return entity; } else if (CurrentEntity != null) yield return CurrentEntity; } void Update() { if(!_paused) { _time += Time.deltaTime; _hitMarkerTime -= Time.deltaTime; if(HitMarker.activeSelf && _hitMarkerTime < 0) HitMarker.SetActive(false); // ItemManager.Time = _time; if(CurrentEntity !=null && CurrentEntity.Parent==null) { foreach (var indicator in _visibleHostileIndicators) { indicator.Value.gameObject.SetActive(indicator.Key!=CurrentEntity.Target.Value); indicator.Value.Place.Target = indicator.Key.Position; if (!indicator.Key.Active) indicator.Value.Fill.enabled = false; else { indicator.Value.Fill.fillAmount = saturate(indicator.Key.EntityInfoGathered[CurrentEntity] / Settings.GameplaySettings.TargetDetectionInfoThreshold); indicator.Value.Fill.enabled = !(indicator.Key.EntityInfoGathered[CurrentEntity] > Settings.GameplaySettings.TargetDetectionInfoThreshold) || sin(TargetSpottedBlinkFrequency * Time.time) + TargetSpottedBlinkOffset > 0; } } var look = Input.Player.Look.ReadValue(); _entityYawPitch = float2(_entityYawPitch.x + look.x * Sensitivity.x, clamp(_entityYawPitch.y + look.y * Sensitivity.y, -.45f * PI, .45f * PI)); _viewDirection = mul(float3(0, 0, 1), Unity.Mathematics.float3x3.Euler(float3(_entityYawPitch.yx, 0), RotationOrder.YXZ)); CurrentEntity.LookDirection = _viewDirection; HeatstrokePP.weight = saturate(unlerp(0, Settings.GameplaySettings.SevereHeatstrokeRiskThreshold, CurrentEntity.Heatstroke)); var severeHeatstrokeLerp = saturate(unlerp(Settings.GameplaySettings.SevereHeatstrokeRiskThreshold, 1, CurrentEntity.Heatstroke)); SevereHeatstrokePP.weight = severeHeatstrokeLerp + severeHeatstrokeLerp * (1 - severeHeatstrokeLerp) * max(Settings.HeatstrokePhasingFloor, sin(Time.time * Settings.HeatstrokePhasingFrequency)); if(CurrentEntity is Ship ship) { ship.MovementDirection = Input.Player.Move.ReadValue(); } var target = CurrentEntity.Target.Value; if (target != null) { var threshold = Settings.GameplaySettings.TargetDetectionInfoThreshold; TargetVisibilityFill.fillAmount = lerp(.25f, .75f, (CurrentEntity.EntityInfoGathered[target] - threshold)/(1-threshold)); VisibilityToTargetFill.fillAmount = lerp(.25f, .75f, target.EntityInfoGathered[CurrentEntity] / threshold); TargetHitpointsFill.fillAmount = lerp(.25f, .75f, target.Hull.Durability / target.EquippedHull.Data.Durability); } var tractorPower = Input.Player.TractorBeam.ReadValue(); CurrentEntity.TractorPower = saturate(CurrentEntity.TractorPower + sign(tractorPower - CurrentEntity.TractorPower) * Time.deltaTime * 2); } Zone.Update(Time.deltaTime); } } private void LateUpdate() { UpdateTargetIndicators(); } private void UpdateTargetIndicators() { if (CurrentEntity == null || CurrentEntity.Parent != null) return; ViewDot.Target = ZoneRenderer.EntityInstances[CurrentEntity].LookAtPoint.position; if (CurrentEntity.Target.Value != null) TargetIndicator.Target = CurrentEntity.Target.Value.Position; var distance = length((float3)ViewDot.Target - CurrentEntity.Position); foreach (var (_, barrels, crosshair) in _articulationGroups) { var averagePosition = Vector3.zero; foreach (var barrel in barrels) averagePosition += barrel.position + barrel.forward * distance; averagePosition /= barrels.Length; crosshair.Target = averagePosition; } foreach (var (targetLock, indicator, spin) in _lockingIndicators) { var showLockingIndicator = targetLock.Lock > .01f && CurrentEntity.Target.Value != null && CurrentEntity.Target.Value.IsHostileTo(CurrentEntity); indicator.gameObject.SetActive(showLockingIndicator); if(showLockingIndicator) { indicator.Target = CurrentEntity.Target.Value.Position; indicator.NoiseAmplitude = Settings.GameplaySettings.LockIndicatorNoiseAmplitude * (1 - targetLock.Lock); indicator.NoiseFrequency = Settings.GameplaySettings.LockIndicatorFrequency.Evaluate(targetLock.Lock); spin.Speed = Settings.GameplaySettings.LockSpinSpeed.Evaluate(targetLock.Lock); } } } } public abstract class DragObject{} public class WeaponGroupDragObject : DragObject { public WeaponGroupDragObject(int group) { Group = group; } public int Group { get; } } public abstract class ItemDragObject : DragObject { protected ItemDragObject(int2 originCellOffset, ItemInstance item) { OriginCellOffset = originCellOffset; Item = item; } public ItemInstance Item { get; } public int2 OriginCellOffset { get; } } public class ItemInstanceDragObject : ItemDragObject { public ItemInstanceDragObject(ItemInstance item, EquippedCargoBay originInventory, int2 originCellOffset) : base(originCellOffset, item) { OriginInventory = originInventory; } public EquippedCargoBay OriginInventory { get; } } public class EquippedItemDragObject : ItemDragObject { public EquippedItemDragObject(EquippedItem item, Entity originEntity, int2 originCellOffset) : base(originCellOffset, item.EquippableItem) { EquippedItem = item; OriginEntity = originEntity; } public EquippedItem EquippedItem { get; } public Entity OriginEntity { get; } }