You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
359 lines
8.0 KiB
C#
359 lines
8.0 KiB
C#
using System.Collections.Generic;
|
|
using Fusion;
|
|
using UnityEngine;
|
|
|
|
namespace Projectiles
|
|
{
|
|
public class WeaponContext
|
|
{
|
|
// We do not want to link AgentInput directly in case the weapon is used
|
|
// by other entities (e.g. turret, NPC)
|
|
public NetworkButtons Buttons;
|
|
public NetworkButtons PressedButtons;
|
|
public Vector3 MoveVelocity;
|
|
|
|
public Transform FireTransform;
|
|
public HitscanProjectileBuffer HitscanProjectiles;
|
|
public KinematicProjectileBuffer KinematicProjectiles;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles player weapons.
|
|
/// </summary>
|
|
[DefaultExecutionOrder(5)]
|
|
public class Weapons : ContextBehaviour
|
|
{
|
|
// PUBLIC MEMBERS
|
|
|
|
public bool IsSwitchingWeapon => _switchCooldown.ExpiredOrNotRunning(Runner) == false;
|
|
public float ElapsedSwitchTime => _weaponSwitchDuration - _switchCooldown.RemainingTime(Runner).GetValueOrDefault();
|
|
|
|
public Weapon CurrentWeapon => _weapons[CurrentWeaponSlot];
|
|
public Weapon PendingWeapon => _weapons[PendingWeaponSlot];
|
|
|
|
[Networked, HideInInspector]
|
|
public int CurrentWeaponSlot { get; private set; }
|
|
[Networked,HideInInspector]
|
|
public int PendingWeaponSlot { get; private set; }
|
|
|
|
public int Version => _version;
|
|
|
|
// PRIVATE MEMBERS
|
|
|
|
[Networked, Capacity(12)]
|
|
private NetworkArray<Weapon> _weapons { get; }
|
|
[Networked]
|
|
private TickTimer _switchCooldown { get; set; }
|
|
|
|
[SerializeField]
|
|
private Weapon[] _initialWeapons;
|
|
[SerializeField]
|
|
private Transform _weaponsRoot;
|
|
[SerializeField]
|
|
private Transform _fireTransform;
|
|
[SerializeField]
|
|
private Vector3 _firstPersonWeaponOffset = new(-0.15f, 0f, 0f);
|
|
|
|
[Header("Weapon Switch")]
|
|
[SerializeField]
|
|
private float _weaponSwitchDuration = 1f;
|
|
[SerializeField, Tooltip("When the actual weapon swap happens during weapon switch")]
|
|
private float _weaponSwapTime = 0.5f;
|
|
|
|
private int _version;
|
|
|
|
private PlayerAgent _agent;
|
|
private WeaponContext _weaponContext = new();
|
|
|
|
// PUBLIC METHODS
|
|
|
|
public void SwitchWeapon(int weaponSlot, bool immediate)
|
|
{
|
|
if (weaponSlot < 0 || weaponSlot >= _weapons.Length)
|
|
return;
|
|
|
|
var weapon = _weapons[weaponSlot];
|
|
if (weapon == null)
|
|
return;
|
|
|
|
if (immediate == true || _weaponSwitchDuration <= 0f)
|
|
{
|
|
PendingWeaponSlot = weaponSlot;
|
|
CurrentWeaponSlot = weaponSlot;
|
|
_switchCooldown = default;
|
|
}
|
|
else
|
|
{
|
|
StartWeaponSwitch(weaponSlot);
|
|
}
|
|
}
|
|
|
|
public int GetNextWeaponSlot(int fromSlot, bool ignoreZeroWeapon = false)
|
|
{
|
|
int weaponsLength = _weapons.Length;
|
|
|
|
for (int i = 0; i < weaponsLength; i++)
|
|
{
|
|
int slot = (fromSlot + i + 1) % weaponsLength;
|
|
|
|
if (slot == 0 && ignoreZeroWeapon == true)
|
|
continue;
|
|
|
|
if (_weapons[slot] != null)
|
|
return slot;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
public int GetPreviousWeaponSlot(int fromSlot, bool ignoreZeroWeapon = false)
|
|
{
|
|
int weaponsLength = _weapons.Length;
|
|
|
|
for (int i = 0; i < weaponsLength; i++)
|
|
{
|
|
int slot = (weaponsLength + fromSlot - i - 1) % weaponsLength;
|
|
|
|
if (slot == 0 && ignoreZeroWeapon == true)
|
|
continue;
|
|
|
|
if (_weapons[slot] != null)
|
|
return slot;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
public void GetAllWeapons(List<Weapon> weapons)
|
|
{
|
|
for (int i = 0; i < _weapons.Length; i++)
|
|
{
|
|
if (_weapons[i] != null)
|
|
{
|
|
weapons.Add(_weapons[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// NetworkBehaviour INTERFACE
|
|
|
|
public override void Spawned()
|
|
{
|
|
if (HasStateAuthority == false)
|
|
{
|
|
RefreshWeapons();
|
|
return;
|
|
}
|
|
|
|
int minWeaponSlot = int.MaxValue;
|
|
|
|
// Spawn initial weapons
|
|
for (int i = 0; i < _initialWeapons.Length; i++)
|
|
{
|
|
var weaponPrefab = _initialWeapons[i];
|
|
if (weaponPrefab == null)
|
|
continue;
|
|
|
|
var weapon = Runner.Spawn(weaponPrefab, inputAuthority: Object.InputAuthority);
|
|
AddWeapon(weapon);
|
|
|
|
if (weapon.WeaponSlot < minWeaponSlot)
|
|
{
|
|
minWeaponSlot = weapon.WeaponSlot;
|
|
}
|
|
}
|
|
|
|
// Equip first weapon
|
|
SwitchWeapon(minWeaponSlot, true);
|
|
RefreshWeapons();
|
|
}
|
|
|
|
public override void Despawned(NetworkRunner runner, bool hasState)
|
|
{
|
|
// Cleanup weapons
|
|
for (int i = 0; i < _weapons.Length; i++)
|
|
{
|
|
var weapon = _weapons[i];
|
|
if (weapon != null)
|
|
{
|
|
RemoveWeapon(weapon.WeaponSlot, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void FixedUpdateNetwork()
|
|
{
|
|
ProcessInput();
|
|
UpdateWeaponSwitch();
|
|
}
|
|
|
|
public override void Render()
|
|
{
|
|
if (CurrentWeapon == null)
|
|
return;
|
|
|
|
int layer = HasInputAuthority ? ObjectLayer.FirstPerson : ObjectLayer.ThirdPerson;
|
|
var offset = HasInputAuthority ? _firstPersonWeaponOffset : Vector3.zero;
|
|
|
|
RefreshWeapons();
|
|
SetWeaponView(layer, offset);
|
|
}
|
|
|
|
// MONOBEHAVIOUR
|
|
|
|
protected void Awake()
|
|
{
|
|
_agent = GetComponent<PlayerAgent>();
|
|
|
|
_weaponContext.KinematicProjectiles = GetComponent<KinematicProjectileBuffer>();
|
|
_weaponContext.HitscanProjectiles = GetComponent<HitscanProjectileBuffer>();
|
|
_weaponContext.FireTransform = _fireTransform;
|
|
}
|
|
|
|
// PRIVATE METHODS
|
|
|
|
private void ProcessInput()
|
|
{
|
|
if (IsProxy == true)
|
|
return;
|
|
|
|
if (CurrentWeapon == null)
|
|
return;
|
|
|
|
if (GetInput(out GameplayInput input) == false)
|
|
return;
|
|
|
|
SwitchWeapon(input.WeaponSlot, false);
|
|
|
|
if (IsSwitchingWeapon == true)
|
|
return;
|
|
|
|
_weaponContext.Buttons = input.Buttons;
|
|
_weaponContext.PressedButtons = input.Buttons.GetPressed(_agent.Input.PreviousButtons);
|
|
_weaponContext.MoveVelocity = _agent.KCC.RealVelocity;
|
|
|
|
if (CurrentWeapon.ProcessFireInput() == true)
|
|
{
|
|
_agent.Health.StopImmortality();
|
|
}
|
|
}
|
|
|
|
private void StartWeaponSwitch(int weaponSlot)
|
|
{
|
|
if (weaponSlot == PendingWeaponSlot)
|
|
return;
|
|
|
|
PendingWeaponSlot = weaponSlot;
|
|
|
|
if (ElapsedSwitchTime < _weaponSwapTime)
|
|
return; // We haven't swap weapon yet, just continue with new pending weapon
|
|
|
|
_switchCooldown = TickTimer.CreateFromSeconds(Runner, _weaponSwitchDuration);
|
|
}
|
|
|
|
private void UpdateWeaponSwitch()
|
|
{
|
|
if (IsProxy == true)
|
|
return;
|
|
|
|
if (CurrentWeaponSlot == PendingWeaponSlot)
|
|
return;
|
|
|
|
if (ElapsedSwitchTime < _weaponSwapTime)
|
|
return;
|
|
|
|
CurrentWeaponSlot = PendingWeaponSlot;
|
|
}
|
|
|
|
private void RefreshWeapons()
|
|
{
|
|
var currentWeapon = CurrentWeapon;
|
|
if (currentWeapon == null)
|
|
return;
|
|
|
|
if (currentWeapon.IsArmed == true)
|
|
return; // Proper weapon is ready
|
|
|
|
for (int i = 0; i < _weapons.Length; i++)
|
|
{
|
|
var weapon = _weapons[i];
|
|
|
|
if (weapon == null)
|
|
continue;
|
|
|
|
weapon.SetWeaponContext(_weaponContext);
|
|
|
|
if (weapon != currentWeapon)
|
|
{
|
|
weapon.DisarmWeapon();
|
|
weapon.SetActive(false);
|
|
}
|
|
}
|
|
|
|
currentWeapon.transform.SetParent(_weaponsRoot, false);
|
|
currentWeapon.SetActive(true);
|
|
|
|
currentWeapon.ArmWeapon();
|
|
|
|
_version++;
|
|
|
|
if (_weaponContext.HitscanProjectiles != null)
|
|
{
|
|
_weaponContext.HitscanProjectiles.UpdateBarrelTransforms(currentWeapon.BarrelTransforms);
|
|
}
|
|
|
|
if (_weaponContext.KinematicProjectiles != null)
|
|
{
|
|
_weaponContext.KinematicProjectiles.UpdateBarrelTransforms(currentWeapon.BarrelTransforms);
|
|
}
|
|
}
|
|
|
|
private void SetWeaponView(int layer, Vector3 offset)
|
|
{
|
|
var currentWeapon = CurrentWeapon;
|
|
|
|
if (currentWeapon == null)
|
|
return;
|
|
|
|
if (currentWeapon.gameObject.layer != layer)
|
|
{
|
|
// First person weapon is rendered differently (see ForwardRenderer asset)
|
|
currentWeapon.gameObject.SetLayer(layer, true);
|
|
}
|
|
|
|
// Weapon is in different position for first person vs third person to align nicely in camera view
|
|
currentWeapon.transform.localPosition = offset;
|
|
}
|
|
|
|
private void AddWeapon(Weapon weapon)
|
|
{
|
|
if (weapon == null)
|
|
return;
|
|
|
|
RemoveWeapon(weapon.WeaponSlot);
|
|
|
|
weapon.Object.AssignInputAuthority(Object.InputAuthority);
|
|
|
|
_weapons.Set(weapon.WeaponSlot, weapon);
|
|
}
|
|
|
|
private void RemoveWeapon(int slot, bool despawn = true)
|
|
{
|
|
var weapon = _weapons[slot];
|
|
if (weapon == null)
|
|
return;
|
|
|
|
if (despawn == true)
|
|
{
|
|
Runner.Despawn(weapon.Object);
|
|
}
|
|
else
|
|
{
|
|
weapon.Object.RemoveInputAuthority();
|
|
}
|
|
|
|
_weapons.Set(slot, null);
|
|
}
|
|
}
|
|
}
|