427 lines
10 KiB
C#
Raw Normal View History

2025-09-24 11:24:38 +05:00
using UnityEngine;
using Fusion;
namespace TPSBR
{
public sealed class ShrinkingArea : ContextBehaviour
{
// PUBLIC MEMBERS
public bool IsActive { get { return _flags.IsBitSet(0); } private set { _flags = _flags.SetBitNoRef(0, value); } }
public bool IsShrinking { get { return _flags.IsBitSet(1); } private set { _flags = _flags.SetBitNoRef(1, value); } }
public bool IsAnnounced { get { return _flags.IsBitSet(2); } private set { _flags = _flags.SetBitNoRef(2, value); } }
public bool IsPaused { get { return _flags.IsBitSet(3); } private set { _flags = _flags.SetBitNoRef(3, value); } }
[Networked, HideInInspector]
public float ShrinkDelay { get; private set; }
public TickTimer NextShrinking => _nextShrinking;
public bool IsFinished => Radius <= _endRadius;
[Networked, HideInInspector]
public Vector3 Center { get; set; }
[Networked, HideInInspector]
public float Radius { get; set; }
[Networked, HideInInspector]
public Vector3 ShrinkCenter { get; set; }
[Networked, HideInInspector]
public float ShrinkRadius { get; set; }
public System.Action<Vector3, float> ShrinkingAnnounced;
// PRIVATE MEMBERS
[Header("Size")]
[SerializeField]
private float _mapRadius = 200f;
[SerializeField]
private float _startRadius = 100f;
[SerializeField]
private float _endRadius = 40f;
[SerializeField]
private Transform _shrinkArea;
[SerializeField]
private Transform _shrinkAreaTarget;
[Header("Timing")]
[SerializeField]
private float _shrinkStartDelay = 30f;
[SerializeField]
private float _minShrinkDelay = 35f;
[SerializeField]
private float _maxShrinkDelay = 90f;
[SerializeField]
private int _minShrinkDelayPlayers = 2;
[SerializeField]
private int _maxShrinkDelayPlayers = 60;
[SerializeField]
private float _shrinkDuration = 20f;
[SerializeField]
private float _shrinkAnnounceDuration = 30f;
[SerializeField]
private int _shrinkSteps = 5;
[Header("Damage")]
[SerializeField]
private float _damagePerTick = 5f;
[SerializeField]
private float _damageTickTime = 1f;
[Networked]
private byte _flags { get; set; }
[Networked]
private TickTimer _nextShrinking { get; set; }
private int _currentStage;
private AreaStage[] _calculatedStages;
private int _shrinkStartTick;
private int _shrinkEndTick;
private Material _shrinkAreaMaterial;
private Material _shrinkAreaTargetMaterial;
private int _materialRadiusID;
private TickTimer _damageTimer;
// PUBLIC METHODS
public void Activate()
{
if (IsActive == true)
return;
if (HasStateAuthority == false)
return;
IsActive = true;
if (_calculatedStages == null)
{
var endCenter = new Vector2(transform.position.x, transform.position.z) + Random.insideUnitCircle * (_mapRadius - _endRadius);
CalculateStages(endCenter);
}
var startStage = _calculatedStages[0];
Center = new Vector3(startStage.Center.x, transform.position.y, startStage.Center.y);
Radius = startStage.Radius;
_damageTimer = TickTimer.CreateFromSeconds(Runner, _damageTickTime);
SetNextShrinkingTimer(_shrinkStartDelay);
}
public void Deactivate()
{
if (IsActive == false)
return;
if (HasStateAuthority == false)
return;
IsActive = false;
}
public void Pause(bool pause)
{
if (IsPaused == pause)
return;
IsPaused = pause;
if (pause == false)
{
_damageTimer = TickTimer.CreateFromSeconds(Runner, _damageTickTime);
SetNextShrinkingTimer();
}
}
public void OverrideParameters(Vector3 center, float mapRadius, float startRadius, float endRadius)
{
transform.position = center;
_mapRadius = mapRadius;
_startRadius = startRadius;
_endRadius = endRadius;
}
public bool SetEndCenter(Vector2 endCenter, bool force = false)
{
if (HasStateAuthority == false)
return false;
var position = new Vector2(transform.position.x, transform.position.z);
if (Vector2.Distance(endCenter, position) > _mapRadius - _endRadius)
{
if (force == false)
return false;
var direction = endCenter - position;
endCenter = position + direction.normalized * Random.value * (_mapRadius - _endRadius);
}
CalculateStages(endCenter);
return true;
}
// NetworkBehaviour INTERFACE
public override void FixedUpdateNetwork()
{
if (IsActive == false || IsPaused == true)
return;
if (HasStateAuthority == false)
return;
if (IsPaused == false)
{
if (_nextShrinking.IsRunning == true)
{
var remainingTime = _nextShrinking.RemainingTime(Runner);
if (remainingTime <= 0f)
{
Shrink();
}
else if (remainingTime <= _shrinkAnnounceDuration)
{
Announce();
}
}
if (IsShrinking == true)
{
UpdateShrinking();
}
}
if (_damageTimer.IsRunning == true && _damageTimer.Expired(Runner) == true)
{
UpdateDamage();
}
}
public override void Render()
{
if (IsActive == true)
{
_shrinkArea.SetActive(true);
_shrinkArea.position = Center;
_shrinkArea.localScale = new Vector3(Radius * 2f, 1f, Radius * 2f);
_shrinkAreaMaterial.SetFloat(_materialRadiusID, Radius);
if (IsAnnounced == true)
{
_shrinkAreaTarget.position = ShrinkCenter;
_shrinkAreaTarget.localScale = new Vector3(ShrinkRadius * 2f, 1f, ShrinkRadius * 2f);
_shrinkAreaTargetMaterial.SetFloat(_materialRadiusID, ShrinkRadius);
_shrinkAreaTarget.SetActive(true);
}
else
{
_shrinkAreaTarget.SetActive(false);
}
}
else
{
_shrinkAreaTarget.SetActive(false);
_shrinkArea.SetActive(false);
}
}
// MONOBEHAVIOUR
private void Start()
{
_shrinkAreaMaterial = _shrinkArea.GetComponentInChildren<MeshRenderer>(true).material;
_shrinkAreaTargetMaterial = _shrinkAreaTarget.GetComponentInChildren<MeshRenderer>(true).material;
_materialRadiusID = Shader.PropertyToID("Radius");
}
private void OnDrawGizmosSelected()
{
var tmpColor = Gizmos.color;
Gizmos.color = Color.green;
Gizmos.DrawWireSphere(transform.position, _startRadius);
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, _endRadius);
Gizmos.color = tmpColor;
}
// PRIVATE METHODS
private void Announce()
{
if (IsActive == false || IsAnnounced == true)
return;
if (HasStateAuthority == false)
return;
if (IsFinished == true)
return;
IsAnnounced = true;
_currentStage += 1;
var stage = _calculatedStages[_currentStage];
ShrinkRadius = stage.Radius;
ShrinkCenter = new Vector3(stage.Center.x, transform.position.y, stage.Center.y);
ShrinkingAnnounced?.Invoke(ShrinkCenter, ShrinkRadius);
}
private void Shrink()
{
if (IsActive == false || IsShrinking == true)
return;
if (HasStateAuthority == false)
return;
if (IsFinished == true)
return;
_shrinkStartTick = Runner.Tick;
_shrinkEndTick = _shrinkStartTick + Mathf.CeilToInt(_shrinkDuration / Runner.DeltaTime);
IsShrinking = true;
_nextShrinking = default;
}
private void UpdateShrinking()
{
var currentTick = Runner.Tick;
if (currentTick >= _shrinkEndTick)
{
IsAnnounced = false;
IsShrinking = false;
Radius = ShrinkRadius;
Center = ShrinkCenter;
if (_currentStage < _shrinkSteps)
{
SetNextShrinkingTimer();
}
return;
}
float progress = (currentTick - _shrinkStartTick) / (float)(_shrinkEndTick - _shrinkStartTick);
var previousStage = _calculatedStages[_currentStage - 1];
Radius = Mathf.Lerp(previousStage.Radius, ShrinkRadius, progress);
Center = Vector3.Lerp(new Vector3(previousStage.Center.x, transform.position.y, previousStage.Center.y), ShrinkCenter, progress);
}
private void UpdateDamage()
{
var radiusSqr = Radius * Radius;
foreach (var player in Context.NetworkGame.ActivePlayers)
{
if (player == null)
continue;
var agent = player.ActiveAgent;
if (agent == null)
continue;
var agentPosition = agent.transform.position;
var direction = agentPosition - Center;
direction.y = 0f;
if (radiusSqr > direction.sqrMagnitude)
continue;
var hitData = new HitData()
{
Action = EHitAction.Damage,
HitType = EHitType.ShrinkingArea,
Amount = _damagePerTick,
Position = agentPosition,
Target = agent.Health,
Normal = Vector3.up,
};
HitUtility.ProcessHit(ref hitData);
}
_damageTimer = TickTimer.CreateFromSeconds(Runner, _damageTickTime);
}
private void SetNextShrinkingTimer(float additionalTime = 0f)
{
int playerCount = Context.NetworkGame.ActivePlayerCount;
ShrinkDelay = MathUtility.Map(_minShrinkDelayPlayers, _maxShrinkDelayPlayers, _minShrinkDelay, _maxShrinkDelay, playerCount);
ShrinkDelay += additionalTime;
_nextShrinking = TickTimer.CreateFromSeconds(Runner, ShrinkDelay);
}
private void CalculateStages(Vector2 endCenter)
{
_calculatedStages = new AreaStage[_shrinkSteps + 1];
Vector2 startCenter = new Vector2(transform.position.x, transform.position.z);
Vector2 center = endCenter;
float radius = _endRadius;
// Last stage is fixed
_calculatedStages[_shrinkSteps] = new AreaStage(center, radius);
for (int i = _shrinkSteps - 1; i >= 0; i--)
{
float nextRadius = Mathf.Lerp(_startRadius, _endRadius, i / (float)_shrinkSteps);
Vector2 nextCenter = Vector2.zero;
bool success = false;
float maxSqrDistanceFromCenter = _mapRadius - nextRadius;
maxSqrDistanceFromCenter *= maxSqrDistanceFromCenter;
// Just try it few times instead of complex calculation, lol
for (int j = 0; j < 50; j++)
{
nextCenter = center + Random.insideUnitCircle * (nextRadius - radius);
if (Vector2.SqrMagnitude(nextCenter - startCenter) < maxSqrDistanceFromCenter)
{
success = true;
break;
}
}
if (success == false)
{
var direction = nextCenter - startCenter;
nextCenter = startCenter + direction.normalized * (_mapRadius - nextRadius);
}
center = nextCenter;
radius = nextRadius;
_calculatedStages[i] = new AreaStage(center, radius);
}
}
// HELPERS
private struct AreaStage
{
public Vector2 Center;
public float Radius;
public AreaStage(Vector2 center, float radius)
{
Center = center;
Radius = radius;
}
}
}
}