PaintballCombatant.Overdrive.cs
using Sandbox;
namespace PaintballBaddies;
public sealed partial class PaintballCombatant
{
[Sync(SyncFlags.FromHost)] public int OverdriveHealth {get;private set;}=100;
[Sync(SyncFlags.FromHost)] public int OverdriveDowns {get;private set;}
[Sync(SyncFlags.FromHost)] public int SausageKills {get;private set;}
[Sync(SyncFlags.FromHost)] public int EquippedWeapon {get;private set;}
public bool HasCrossbow=>EquippedWeapon==1;
internal void EquipWeapon(int weapon)
{
if(!MultiplayerSession.Authority || weapon<0 || weapon>1 || MultiplayerSession.Find(Scene) is not {IsOverdrive:true,InRound:true}
|| !AcceptHits || Components.Get<CloseCombat>()?.Incapacitated==true)return;
EquippedWeapon=weapon;
}
[Sync(SyncFlags.FromHost)] public int SausageHeadshots {get;private set;}
[Sync(SyncFlags.FromHost)] public int GrenadeCharges {get;private set;}
[Sync(SyncFlags.FromHost)] public string SpecialQueue {get;private set;}="";
public bool FreezeGrenadeReady=>SpecialQueue.StartsWith("F");
public string SpecialName=>FreezeGrenadeReady ? "FLASH FREEZE" : "EXPLOSIVE GRENADE";
public string NextSpecialName=>(SausageHeadshots/5)%2==0 ? "EXPLOSIVE GRENADE" : "FLASH FREEZE";
[Sync(SyncFlags.FromHost)] public float HotShotSeconds {get;private set;}
[Sync(SyncFlags.FromHost)] public int PowerupSerial {get;private set;}
[Sync(SyncFlags.FromHost)] public string LastPowerup {get;private set;}="";
internal void RegisterSausageKill(bool headshot=false)
{if(MultiplayerSession.Authority){SausageKills++;if(headshot)RegisterHeadshot();}}
internal void RegisterHeadshot()
{
if(!MultiplayerSession.Authority || MultiplayerSession.Find(Scene) is not {IsOverdrive:true,InRound:true})return;
SausageHeadshots++;
if(SausageHeadshots%5!=0)return;
bool freeze=(SausageHeadshots/5)%2==0;
QueueSpecial(freeze);
}
private void QueueSpecial(bool freeze)
{
SpecialQueue+=freeze ? "F" : "E";GrenadeCharges++;
LastPowerup=freeze ? "FLASH FREEZE READY" : "GRENADE READY";
PowerupSerial++;
}
// Helpers have one wave allowance, so body-aiming bots can demonstrate the
// specials without taking human headshot rewards or accumulating free ammo.
internal void PrepareAISpecial(int wave,int character)
{
if(!MultiplayerSession.Authority || !Components.Get<NetworkBot>().IsValid() || GrenadeCharges>0
|| MultiplayerSession.Find(Scene) is not {IsOverdrive:true,InRound:true})return;
QueueSpecial((wave+character)%2==0);
}
internal bool ConsumeGrenade()
{if(!MultiplayerSession.Authority || GrenadeCharges<=0)return false;GrenadeCharges--;if(SpecialQueue.Length>0)SpecialQueue=SpecialQueue.Substring(1);return true;}
private float grenadeCooldown;
public bool TryThrowGrenade(Vector3 aim)
{
if(!MultiplayerSession.Authority || grenadeCooldown>0 || GrenadeCharges<=0 || !AcceptHits
|| MultiplayerSession.Find(Scene) is not {IsOverdrive:true,InRound:true}
|| Components.Get<CloseCombat>() is {Incapacitated:true} or {Attacking:true}
|| Components.Get<VaultController>()?.IsVaulting==true || Components.Get<CoverController>()?.Sliding==true
|| !float.IsFinite(aim.x) || !float.IsFinite(aim.y) || !float.IsFinite(aim.z))return false;
var origin=Components.Get<PlayerController>()?.EyePosition ?? WorldPosition+Vector3.Up*52;
if((aim-origin).Length<1)return false;
// Start beside the thrower's eye, never on the other side of cover.
// The projectile's swept trace handles any immediate wall impact.
bool freeze=FreezeGrenadeReady;
if(!ConsumeGrenade())return false;
grenadeCooldown=.65f;SausageGrenade.Fire(this,origin,aim,freeze);return true;
}
private float overdriveImmunity;
private bool overdriveWasDown;
internal void ResetOverdrive(){OverdriveHealth=100;OverdriveDowns=0;SausageKills=SausageHeadshots=GrenadeCharges=PowerupSerial=0;HotShotSeconds=0;LastPowerup="";SpecialQueue="";EquippedWeapon=0;overdriveImmunity=grenadeCooldown=0;overdriveWasDown=false;}
internal void HealOverdrive(){if(MultiplayerSession.Authority)OverdriveHealth=100;}
private void TickOverdriveHealth()
{
if(!MultiplayerSession.Authority)return;
grenadeCooldown=System.MathF.Max(0,grenadeCooldown-Time.Delta);
overdriveImmunity=System.MathF.Max(0,overdriveImmunity-Time.Delta);
if(MultiplayerSession.Find(Scene)?.IsOverdrive!=true)return;
HotShotSeconds=System.MathF.Max(0,HotShotSeconds-Time.Delta);
bool down=Components.Get<CloseCombat>()?.Incapacitated==true;
if(overdriveWasDown && !down){OverdriveHealth=50;overdriveImmunity=2;}
overdriveWasDown=down;
}
internal bool ReceiveOverdriveDamage(int amount,Vector3 origin)
{
var session=MultiplayerSession.Find(Scene);
if(!MultiplayerSession.Authority || session?.IsOverdrive!=true || !session.InRound || !AcceptHits
|| overdriveImmunity>0 || Components.Get<CloseCombat>()?.Incapacitated==true)return false;
OverdriveHealth=System.Math.Max(0,OverdriveHealth-amount);overdriveImmunity=.6f;
LastHitSerial=unchecked(LastHitSerial+1);LastHitDirection=(origin-WorldPosition).WithZ(0).Normal;
HitFeedbackRemaining=.45f;DirectionIndicatorRemaining=DirectionIndicatorDuration;
if(OverdriveHealth==0){OverdriveDowns++;Components.Get<CloseCombat>()?.ReceiveContact(origin,true);overdriveWasDown=true;}
else {BeginHitSlowdown();Components.Get<CloseCombat>()?.ReceiveContact(origin,false);}
return true;
}
}