using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using FinTOKMAK.TimelineSystem.Runtime; using UnityEngine; namespace FinTOKMAK.WeaponSystem.Runtime { [RequireComponent(typeof(TimelineSystem.Runtime.TimelineSystem))] public class WeaponManager : MonoBehaviour, IWeaponManager> where ConfigType : WeaponConfigData where RuntimeType : WeaponRuntimeData { #region Private Field /// /// The timeline system to play all the timeline. /// private TimelineSystem.Runtime.TimelineSystem _timelineSystem; /// /// The TimelineEventManager used by the TimelineSystem. /// private TimelineEventManager _timelineEventManager; /// /// The action event called when the WeaponManager starts initialization. /// private Action _onInitialize; /// /// The action event called when the WeaponManager finish initialization. /// private Action _onFinishInitialize; /// /// All the weapons current WeaponManager is carrying. /// private List> _carryWeapons = new List>(); /// /// The weapon player is currently using. /// private Weapon _currWeapon; /// /// The index of _currWeapon; /// private int _currIndex = -1; #region Weapon Status /// /// The current WeaponManager's state. /// private WeaponManagerState _state; private bool _triggerDown = false; private bool _reloadDown = false; private bool _aimDown = false; #endregion #region Operation Enable private bool _able2Shoot = true; private bool _able2Aim = true; private bool _able2Reload = true; private bool _able2PutInOut = true; #endregion #region Event Hook private Action _putOutWeaponEvent; private Action _asyncPutOutWepaonEvent; private Action _putInWeaponEvent; private Action _asyncPutInWeaponEvent; #endregion #endregion #region Serialized Private Field [SerializeField] private MonoBehaviour _remoteAgent; /// /// If the current WeaponManager has local authority. /// [SerializeField] private bool _isLocal = true; /// /// All the weapons that should be load in awake. /// [SerializeField] private List> _preLoadWeapons; /// /// The dictionary that stores all the mount points that can mount weapon instance. /// [SerializeField] private WeaponMountPointDict _weaponMountPoint; #endregion #region Hide Public Field public IRemoteWeaponAgent> weaponAgent { get { var _remoteAgent1 = _remoteAgent as IRemoteWeaponAgent>; if (_remoteAgent1 == null) throw new InvalidCastException("Remote Agent not implement IRemoteWeaponAgent interface."); return _remoteAgent1; } } public bool isLocal { get => _isLocal; set => _isLocal = value; } public Action onInitialize => _onInitialize; public Action onFinishInitialize => _onFinishInitialize; public WeaponMountPointDict weaponMountPoint => _weaponMountPoint; public List> carryWeapons => _carryWeapons; public Weapon currWeapon => _currWeapon; public int currIndex => _currIndex; public WeaponManagerState state => _state; public bool able2Shoot { get => _able2Shoot; set { // Call the shoot enable callback function. if (_currWeapon != null) { _currWeapon.OnShootEnableChanged(value); } _able2Shoot = value; } } public bool able2Aim { get => _able2Aim; set { // Call the aim enable callback function. if (_currWeapon != null) { _currWeapon.OnAimEnableChanged(value); } _able2Aim = value; } } public bool able2Reload { get => _able2Reload; set { // Call the reload enable callback function. if (_currWeapon != null) { _currWeapon.OnReloadEnableChanged(value); } _able2Reload = value; } } public bool able2PutInOut { get => _able2PutInOut; set => _able2PutInOut = value; } public Action putOutWeaponEvent { get => _putOutWeaponEvent; set => _putOutWeaponEvent = value; } public Action asyncPutOutWeaponEvent { get => _asyncPutOutWepaonEvent; set => _asyncPutOutWepaonEvent = value; } public Action putInWeaponEvent { get => _putInWeaponEvent; set => _putInWeaponEvent = value; } public Action asyncPutInWeaponEvent { get => _asyncPutInWeaponEvent; set => _asyncPutInWeaponEvent = value; } #endregion private void Awake() { // Initialize all the private variables. _timelineSystem = gameObject.GetComponent(); _timelineEventManager = gameObject.GetComponent(); // Initialize WeaponManager weaponAgent.manager = this; // Start initialization. onInitialize?.Invoke(); // Load all the preLoadWeapons. foreach (Weapon preLoadWeapon in _preLoadWeapons) { AddWeapon(preLoadWeapon); } // Finish initialization. onFinishInitialize?.Invoke(); } private void Update() { // Call the update callback function of the current weapon. if (_currWeapon != null) { _currWeapon.OnUpdate(); } } #region IWeaponManager Interface #region Weapon Add & Remove public void AddWeapon(Weapon weapon) { int index = _carryWeapons.Count; AddWeapon(weapon, index); } public void AddWeapon(Weapon weapon, int index) { if (!(weapon is Weapon weapon1)) { throw new InvalidCastException($"{weapon} is not a Weapon instance."); } Weapon weaponInstance = Instantiate(weapon1); _carryWeapons.Insert(index, weaponInstance); // Set the WeaponManager and TimelineSystem to the Weapon. weaponInstance.weaponManager = this; weaponInstance.timelineSystem = _timelineSystem; weaponInstance.timelineEventManager = _timelineEventManager; UpdateWeaponIndex(); // Initialize the weapon. weaponInstance.OnInitialize(); } public Weapon RemoveWeapon(int index) { // Check the index range. if (index < 0 || index >= carryWeapons.Count) { throw new IndexOutOfRangeException("The index of weapon is out of the range of carry weapons."); } Weapon removedWeapon = _carryWeapons[index]; _carryWeapons.RemoveAt(index); UpdateWeaponIndex(); return removedWeapon; } public Weapon RemoveWeapon(string id) { for (int i = 0; i < _carryWeapons.Count; i++) { if (_carryWeapons[i].id == id) { return RemoveWeapon(i); } } throw new InvalidOperationException($"No weapon named {id} in the carry weapons."); } #endregion #region Put Out & Put In public void PutOut(int index) { if (_state == WeaponManagerState.PuttingIn || _state == WeaponManagerState.PuttingOut) return; if (!_able2PutInOut) return; // Handle the index out of range exception. if (index < 0 || index >= _carryWeapons.Count) { throw new IndexOutOfRangeException("The index of weapon is out of the range of carry weapons."); } // When the weapon is already in use, refuse to put the weapon out. if (_currWeapon != null && _currIndex == index) { throw new InvalidOperationException("Weapon already in use."); } // If there's still using weapon. if (_currWeapon != null) { // Put in _currWeapon first PutIn(); } // Put out the weapon directly _carryWeapons[index].OnPutOut(); _currWeapon = _carryWeapons[index]; _currIndex = index; // Invoke the event _putOutWeaponEvent?.Invoke(index); _state = WeaponManagerState.Ready; } public void PutOut(string id) { // Find the index of weapon with same id for (int i = 0; i < carryWeapons.Count; i++) { if (carryWeapons[i].id == id) { PutOut(i); return; } } // No weapon found. throw new InvalidOperationException($"No weapon with name {id}"); } public void PutOut(Weapon weapon) { if (!_carryWeapons.Contains(weapon)) throw new InvalidOperationException($"No weapon {weapon}"); // Try to cast IWeapon to Weapon. if (!(weapon is Weapon weapon1)) throw new InvalidCastException($"{weapon} is not a Weapon instance."); PutOut(_carryWeapons.IndexOf(weapon1)); } public async Task PutOutAsync(int index) { // Handle the index out of range exception. if (index < 0 || index >= _carryWeapons.Count) { throw new IndexOutOfRangeException("The index of weapon is out of the range of carry weapons."); } if (_state == WeaponManagerState.PuttingIn || _state == WeaponManagerState.PuttingOut) return; if (!_able2PutInOut) return; // When the weapon is already in use, refuse to put the weapon out. if (_currWeapon != null && _currIndex == index) { Debug.LogWarning("Weapon already in use."); return; } // If there's still using weapon. if (_currWeapon != null) { // Put in _currWeapon first await PutInAsync(); } _state = WeaponManagerState.PuttingOut; // Put out the weapon directly await _carryWeapons[index].OnPutOutAsync(); _currWeapon = _carryWeapons[index]; _currIndex = index; // Invoke the event. _asyncPutOutWepaonEvent?.Invoke(index); _state = WeaponManagerState.Ready; } public async Task PutOutAsync(string id) { // Find the index of weapon with same id for (int i = 0; i < carryWeapons.Count; i++) { if (carryWeapons[i].id == id) { await PutOutAsync(i); return; } } // No weapon found. throw new InvalidOperationException($"No weapon with name {id}"); } public async Task PutOutAsync(Weapon weapon) { if (!_carryWeapons.Contains(weapon)) throw new InvalidOperationException($"No weapon {weapon}"); // Try to cast IWeapon to Weapon. if (!(weapon is Weapon weapon1)) throw new InvalidCastException($"{weapon} is not a Weapon instance."); await PutOutAsync(_carryWeapons.IndexOf(weapon1)); } public void PutIn() { if (_currWeapon == null) { Debug.LogWarning("No weapon being used currently."); return; } if (_state == WeaponManagerState.PuttingIn || _state == WeaponManagerState.PuttingOut) return; if (!_able2PutInOut) return; // Put in current weapon. _currWeapon.OnPutIn(); // There's no currently using weapon. _currWeapon = null; _currIndex = -1; // Invoke the event. _putInWeaponEvent?.Invoke(); _state = WeaponManagerState.Empty; } public async Task PutInAsync() { if (_currWeapon == null) { Debug.LogWarning("No weapon being used currently."); return; } if (_state == WeaponManagerState.PuttingIn || _state == WeaponManagerState.PuttingOut) return; if (!_able2PutInOut) return; _state = WeaponManagerState.PuttingIn; // Async put in. await _currWeapon.OnPutInAsync(); // No weapon being used currently. _currWeapon = null; _currIndex = -1; // Invoke the event _asyncPutInWeaponEvent?.Invoke(); _state = WeaponManagerState.Empty; } #endregion #region Weapon Operation public void TriggerDown() { if (!_able2Shoot) { Debug.LogWarning("Not able to shoot."); return; } if (_currWeapon == null) { Debug.LogWarning("No weapon being used currently."); return; } _currWeapon.OnTriggerDown(); _triggerDown = true; } public void TriggerUp() { if (!_triggerDown) return; if (!_able2Shoot) { _triggerDown = false; return; } if (_currWeapon == null) { Debug.LogWarning("No weapon being used currently."); _triggerDown = false; return; } _currWeapon.OnTriggerUp(); _triggerDown = false; } public void ReloadDown() { if (!_able2Reload) { Debug.LogWarning("Not able to reload."); return; } if (_currWeapon == null) { Debug.LogWarning("No weapon being used currently."); return; } _currWeapon.OnReloadDown(); _reloadDown = true; } public void ReloadUp() { if (!_reloadDown) return; if (!_able2Reload) { _reloadDown = false; return; } if (_currWeapon == null) { Debug.LogWarning("No weapon being used currently."); _reloadDown = false; return; } _currWeapon.OnReloadUp(); _reloadDown = false; } public void AimDown() { if (!_able2Aim) { Debug.LogWarning("Not able to aim."); return; } if (_currWeapon == null) { Debug.LogWarning("No weapon being used currently."); return; } _currWeapon.OnAimDown(); _aimDown = true; } public void AimUp() { if (!_aimDown) return; if (!_able2Aim) { _aimDown = false; return; } if (_currWeapon == null) { Debug.LogWarning("No weapon being used currently."); _aimDown = false; return; } _currWeapon.OnAimUp(); _aimDown = false; } #endregion #endregion #region Private Methods /// /// Update the index of all the carry Weapons. /// private void UpdateWeaponIndex() { for (int i = 0; i < _carryWeapons.Count; i++) { _carryWeapons[i].index = i; } } #endregion } }