OrbiterBlade is a game entity that orbits a unit and deals melee-style damage to enemies on contact. It manages rotation, visibility, hit timing, damage scaling with perks, and can spawn bullets on hit.
using System;
using Sandbox;
public class OrbiterBlade : Orbiter
{
[Sync] public float DamagePercent { get; set; }
private TimeSince _timeSinceDamage;
private float _lastAlpha;
public int BladeNum { get; set; }
private Dictionary<Thing, float> _damageTimes;
private Enemy _lastEnemyHit;
private int _consecutiveHitsOnSameEnemy;
public float RotationSpeed { get; set; }
public float OriginalOrbitSpeed { get; set; }
public float Damage { get; set; }
public Player Player { get; set; }
protected override void OnStart()
{
base.OnStart();
if ( IsProxy )
return;
CollideWithTags.Add( "enemy" );
OrbitDistance = 40f;
_lastAlpha = -1f;
_damageTimes = new();
}
protected override void OnUpdate()
{
base.OnUpdate();
float alpha = OrbitedUnit.IsValid() && !OrbitedUnit.IsDying
? 1f
: 0f;
if( alpha != _lastAlpha)
{
Model.Tint = Model.Tint.WithAlpha( alpha );
_lastAlpha = alpha;
}
if ( IsProxy )
return;
if ( !OrbitedUnit.IsValid() || OrbitedUnit.IsDying )
return;
//WorldRotation = Rotation.LookAt( Position2D - OrbitedUnit.Position2D );
// todo: speed up rotation speed with PerkOrbiterBladeHitInterval
WorldRotation = WorldRotation.RotateAroundAxis( Vector3.Up, RotationSpeed * Time.Delta );
var hitStopTime = Player.IsValid() && !Player.IsProxy ? Player.Stats[PlayerStat.OrbiterBladeStopTime] : 0f;
var orbitSpeedMult = Player.IsValid() && !Player.IsProxy ? Player.Stats[PlayerStat.OrbiterBladeSpeedMultiplier] : 1f;
var targetOrbitSpeed = OriginalOrbitSpeed * orbitSpeedMult;
OrbitSpeed = _timeSinceDamage < hitStopTime ? 0f : targetOrbitSpeed;
// todo: orbit faster while standing still ?
OrbitDistance = OrbitedUnit.Radius + 55f + BladeNum * 22f;
if ( _timeSinceDamage < 0.25f )
{
WorldScale = new Vector3( Utils.Map( _timeSinceDamage, 0f, 0.25f, 0.5f, 1f, EasingType.SineOut ) );
}
var hitInterval = Player.IsValid() && !Player.IsProxy ? Player.Stats[PlayerStat.OrbiterBladeHitInterval] : 1f;
// todo: change rotation speed based on hit interval
for ( int i = _damageTimes.Count - 1; i >= 0; i-- )
{
var pair = _damageTimes.ElementAt( i );
if ( Time.Now > pair.Value + hitInterval )
_damageTimes.Remove( pair.Key );
}
}
protected override float GetZPos()
{
return OrbitedUnit.WorldPosition.z + 35f;
}
public override void Colliding( Thing other, float percent, float dt )
{
base.Colliding( other, percent, dt );
if ( !OrbitedUnit.IsValid() || OrbitedUnit.IsDying || _damageTimes.ContainsKey( other ) || Manager.Instance.IsGameOver )
return;
if ( other is Enemy enemy && !enemy.IsDying )
{
if ( !_lastEnemyHit.IsValid() )
{
_lastEnemyHit = null;
_consecutiveHitsOnSameEnemy = 0;
}
if ( _lastEnemyHit == enemy )
_consecutiveHitsOnSameEnemy++;
else
{
_lastEnemyHit = enemy;
_consecutiveHitsOnSameEnemy = 1;
}
WorldScale = new Vector3( 0.5f );
_timeSinceDamage = 0f;
var dir = Utils.GetPerpendicularVector( (Position2D - OrbitedUnit.Position2D).Normal ) * (OrbitSpeed < 0f ? 1f : -1f);
var player = OrbitedUnit as Player;
if ( player.IsValid() )
{
var finalDamage = Damage;
var sameEnemyExtraHits = Math.Max( 0, _consecutiveHitsOnSameEnemy - 1 );
var perkLevel = 0;
var perkType = TypeLibrary.GetType( typeof( PerkOrbiterBladeConsecutiveDmg ) );
if ( perkType != null )
perkLevel = player.GetPerkLevel( perkType );
if ( perkLevel > 0 && sameEnemyExtraHits > 0 )
{
var flatBonusPerHit = PerkOrbiterBladeConsecutiveDmg.GetFlatBonusPerConsecutiveHit( perkLevel );
var maxFlatBonus = PerkOrbiterBladeConsecutiveDmg.GetMaxFlatBonus( perkLevel );
var flatBonus = Math.Min( maxFlatBonus, flatBonusPerHit * sameEnemyExtraHits );
finalDamage += flatBonus;
}
enemy.DamageRpc( finalDamage, player, DamageType.OrbitingBlade, WorldPosition, dir * finalDamage * 20f, isCrit: false, shouldFlinch: true );
var bulletChance = player.Stats[PlayerStat.OrbiterBladeBulletChance];
if( Game.Random.Float(0f, 1f) < bulletChance )
{
var bulletDir = (Position2D - enemy.Position2D).Normal;
var dmg = player.GetBulletDamage( isFromClip: false, isLastAmmo: false );
var bulletPos = Position2D + bulletDir * enemy.Radius * 1.5f;
var b = Player.SpawnBullet( bulletPos, Utils.RotateVector( bulletDir, Game.Random.Float( -20f, 20f ) ), dmg, isFromClip: false, bulletType: BulletType.Normal );
// todo: bone model for bullet
// todo: Highlight perk
if ( other.IsValid() )
b.HitThings.Add( other );
// todo: sfx
//Manager.Instance.PlaySfxNearbyRpc( "player.shoot", bulletPos, pitch: Game.Random.Float( 1.2f, 1.3f ), volume: 1f, maxDist: 350f );
}
}
_damageTimes.Add( other, Time.Now );
}
}
}