2025-09-24 11:24:38 +05:00

300 lines
7.8 KiB
C#

using UnityEngine;
using Fusion;
namespace TPSBR
{
[DefaultExecutionOrder(6000)]
public sealed class Jetpack : ContextBehaviour
{
// CONSTANTS
private const int BIT_IS_ACTIVE = 0;
private const int BIT_FULL_THRUST = 1;
private const int BIT_HAS_STARTED = 2;
// PUBLIC MEMBERS
public bool IsActive { get { return _state.IsBitSet(BIT_IS_ACTIVE); } set { byte state = _state; state.SetBit(BIT_IS_ACTIVE, value); _state = state; } }
public bool FullThrust { get { return _state.IsBitSet(BIT_FULL_THRUST); } set { byte state = _state; state.SetBit(BIT_FULL_THRUST, value); _state = state; } }
public bool HasStarted { get { return _state.IsBitSet(BIT_HAS_STARTED); } set { byte state = _state; state.SetBit(BIT_HAS_STARTED, value); _state = state; } }
public bool IsRunning => IsActive == true && _fuel > 0f;
public float Fuel => _fuel;
public float MaxFuel => _maxFuel;
// PRIVATE MEMBERS
[SerializeField]
private float _initialFuel = 100f;
[SerializeField]
private float _maxFuel = 100f;
[SerializeField]
private float _idleThrustConsumption = 7f;
[SerializeField]
private float _fullThrustConsumption = 15f;
[SerializeField]
private GameObject _jetpackObject;
[SerializeField]
private Animation _jetpackAnimation;
[SerializeField]
private Transform _visualsRoot;
[SerializeField]
private Vector3 _centerOfGravityOffset = new Vector3(0f, 1.5f, 0f);
[SerializeField]
private ShakeSetup _cameraPositionShake;
[SerializeField]
private ShakeSetup _cameraRotationShake;
[SerializeField]
private float _fullThrustShakeMultiplier = 2f;
[Header("Propellers")]
[SerializeField]
private Transform _rightMount;
[SerializeField]
private Transform _leftMount;
[SerializeField]
private Transform _rightPropeller;
[SerializeField]
private Transform _leftPropeller;
[SerializeField]
private float _mountForwardAngle = 30f;
[SerializeField]
private float _mountBackwardAngle = -15;
[SerializeField]
private float _mountChangeSpeed = 8f;
[SerializeField]
private float _idlePropellerSpeed = 720f;
[SerializeField]
private float _fullPropellerSpeed = 3500f;
[SerializeField]
private float _propellerSpeedChange = 8f;
[Header("Audio")]
[SerializeField]
private AudioSource _engineSound;
[SerializeField]
private float _engineSoundChangeSpeed = 8f;
[SerializeField]
private float _fullThrustPitch = 1.5f;
[SerializeField]
private float _idleThrustVolume = 0.6f;
[SerializeField]
private float _fullThrustVolume = 1f;
[Networked, HideInInspector]
private byte _state { get; set; }
[Networked]
private float _fuel { get; set; }
private Agent _agent;
private byte _localState;
private float _mountAngle;
private float _propellerSpeed;
private float _defaultPitch;
private float _positionShakeMagnitude;
private float _rotationShakeMagnitude;
private Vector3 _tiltAngles;
// PUBLIC METHODS
public void OnSpawned(Agent agent)
{
_agent = agent;
_localState = default;
_jetpackObject.SetActive(false);
AddFuel(_initialFuel);
UpdateLocalState();
}
public void OnFixedUpdate()
{
if (IsActive == false)
return;
if (_agent.Health.IsAlive == false)
{
Deactivate();
return;
}
bool isGrounded = _agent.Character.CharacterController.Data.IsGrounded;
if (HasStarted == false)
{
HasStarted = isGrounded == false;
}
else if (isGrounded == true)
{
Deactivate();
return; // Deactivate after touch down
}
HasStarted |= _agent.Character.CharacterController.Data.IsGrounded == false;
UpdateLocalState();
// Ensure no weapon can be held
_agent.Weapons.DisarmCurrentWeapon();
float consumption = FullThrust == true ? _fullThrustConsumption : _idleThrustConsumption;
_fuel -= consumption * Runner.DeltaTime;
if (_fuel <= 0f)
{
_fuel = 0f;
//Deactivate();
}
}
public void OnDespawned()
{
Deactivate();
}
public bool AddFuel(float fuel)
{
if (_fuel >= _maxFuel)
return false;
_fuel = Mathf.Min(_fuel + fuel, _maxFuel);
return true;
}
public bool Activate()
{
if (IsActive == true)
return false;
if (_fuel <= 0)
return false;
IsActive = true;
UpdateLocalState();
return true;
}
public void Deactivate()
{
if (IsActive == false)
return;
IsActive = false;
FullThrust = false;
HasStarted = false;
UpdateLocalState();
}
public override void Render()
{
UpdateLocalState();
float moveDirectionY = _agent.HasInputAuthority == true ? _agent.AgentInput.AccumulatedInput.MoveDirection.y : default;
float targetMountAngle = MathUtility.Map(-1, 1, _mountBackwardAngle, _mountForwardAngle, moveDirectionY);
_mountAngle = Mathf.Lerp(_mountAngle, targetMountAngle, Time.deltaTime * _mountChangeSpeed);
_rightMount.localRotation = Quaternion.Euler(new Vector3(_mountAngle, 0f, 0f));
_leftMount.localRotation = _rightMount.localRotation;
float targetPropellerSpeed = _fuel > 0f ? (FullThrust == true ? _fullPropellerSpeed : _idlePropellerSpeed) : 0f;
_propellerSpeed = Mathf.Lerp(_propellerSpeed, targetPropellerSpeed, Time.deltaTime * _propellerSpeedChange);
_rightPropeller.Rotate(Vector3.up, _propellerSpeed * Time.deltaTime, Space.Self);
_leftPropeller.Rotate(Vector3.up, _propellerSpeed * Time.deltaTime, Space.Self);
float targetVolume = IsRunning == true ? (FullThrust == true ? _fullThrustVolume : _idleThrustVolume) : 0f;
float targetPitch = IsRunning == true && FullThrust == true ? _fullThrustPitch : _defaultPitch;
_engineSound.volume = Mathf.Lerp(_engineSound.volume, targetVolume, Time.deltaTime * _engineSoundChangeSpeed);
_engineSound.pitch = Mathf.Lerp(_engineSound.pitch, targetPitch, Time.deltaTime * _engineSoundChangeSpeed);
_visualsRoot.localPosition = Vector3.zero;
_visualsRoot.localRotation = Quaternion.identity;
var localVelocity = IsRunning == true ? Quaternion.Inverse(transform.rotation) * _agent.Character.CharacterController.Data.RealVelocity : Vector3.zero;
_tiltAngles.x = Mathf.Lerp(_tiltAngles.x, -localVelocity.x, Time.deltaTime * 8f);
_tiltAngles.z = Mathf.Lerp(_tiltAngles.z, localVelocity.z * 2f, Time.deltaTime * 8f);
_visualsRoot.RotateAround(transform.position + _centerOfGravityOffset, transform.forward, _tiltAngles.x);
_visualsRoot.RotateAround(transform.position + _centerOfGravityOffset, transform.right, _tiltAngles.z);
_cameraPositionShake.Magnitude = FullThrust == true ? _positionShakeMagnitude * _fullThrustShakeMultiplier : _positionShakeMagnitude;
_cameraRotationShake.Magnitude = FullThrust == true ? _rotationShakeMagnitude * _fullThrustShakeMultiplier : _rotationShakeMagnitude;
}
// MONOBEHAVIOUR
private void Awake()
{
_positionShakeMagnitude = _cameraPositionShake.Magnitude;
_rotationShakeMagnitude = _cameraRotationShake.Magnitude;
_defaultPitch = _engineSound.pitch;
}
// PRIVATE METHODS
private void Activate_Internal()
{
_jetpackAnimation.PlayForward();
_engineSound.Play();
if (HasInputAuthority == true)
{
var shake = Context.Camera.ShakeEffect;
shake.Play(_cameraPositionShake);
shake.Play(_cameraRotationShake);
}
}
private void Deactivate_Internal()
{
_jetpackAnimation.Play(_jetpackAnimation.clip.name, -3f);
_engineSound.Stop();
if (HasInputAuthority == true)
{
var shake = Context.Camera.ShakeEffect;
shake.Stop(_cameraPositionShake);
shake.Stop(_cameraRotationShake);
}
}
// NETWORK CALLBACKS
private void UpdateLocalState()
{
bool stateIsActive = _state.IsBitSet(BIT_IS_ACTIVE);
bool localStateIsActive = _localState.IsBitSet(BIT_IS_ACTIVE);
_localState = _state;
if (stateIsActive != localStateIsActive)
{
if (stateIsActive == true)
{
Activate_Internal();
}
else
{
Deactivate_Internal();
}
}
}
}
}