Weapons/ToolGun/Modes/Motor/MotorEntity.cs
/// <summary>
/// Drives a <see cref="HingeJoint"/> at a continuous angular velocity — a spinning axle,
/// turntable, or wheel that isn't a ground-contact wheel.
/// </summary>
public sealed class MotorEntity : Component, IPlayerControllable
{
[Property, Range( 0, 1 ), ClientEditable]
public float Speed { get; set; } = 0.5f;
[Property, Range( 0, 1 ), ClientEditable]
public float Torque { get; set; } = 0.5f;
[Property, ClientEditable]
public bool Reversed { get; set; }
/// <summary>
/// Spins on its own as soon as it's created, without needing a driver or a wire signal.
/// </summary>
[Property, ClientEditable]
public bool StartActive { get; set; } = true;
[Property, ClientEditable, ToggleGroup( "Limited" )]
public bool Limited { get; set; }
[Property, ClientEditable, ToggleGroup( "Limited" ), Range( -180, 180 )]
public float MinAngle { get; set; } = -90f;
[Property, ClientEditable, ToggleGroup( "Limited" ), Range( -180, 180 )]
public float MaxAngle { get; set; } = 90f;
/// <summary>
/// While held, spins forward regardless of <see cref="Reversed"/> or <see cref="StartActive"/>.
/// </summary>
[Property, ClientEditable]
public ClientInput Forward { get; set; }
/// <summary>
/// While held, spins backward regardless of <see cref="Reversed"/> or <see cref="StartActive"/>.
/// </summary>
[Property, ClientEditable]
public ClientInput Reverse { get; set; }
[Property, ClientEditable]
public ClientInput Toggle { get; set; }
[Property]
public HingeJoint Joint { get; set; }
/// <summary>
/// Emits the joint's current angle, normalized so a full turn is 1.0 — wire it into a
/// gauge, or into another motor's Forward/Reverse to sync two axles.
/// </summary>
[SignalOutput]
public SignalOutput AngleOutput { get; set; } = new();
const float MaxDegreesPerSecond = 720f;
const float MaxTorqueScale = 200000f;
bool _active;
float _lastEmitted = float.NaN;
protected override void OnEnabled()
{
base.OnEnabled();
_active = StartActive;
}
[SignalInput( Id = nameof( Forward ), Default = true )]
public void ForwardSignal( float amount ) => Drive( amount, 0f );
[SignalInput( Id = nameof( Reverse ) )]
public void ReverseSignal( float amount ) => Drive( 0f, amount );
[SignalInput( Id = nameof( Toggle ) )]
public void ToggleSignal() => _active = !_active;
public void OnControl()
{
if ( Toggle.Pressed() ) _active = !_active;
Drive( Forward.GetAnalog(), Reverse.GetAnalog() );
}
void Drive( float forward, float reverse )
{
if ( !Networking.IsHost ) return;
if ( !Joint.IsValid() ) return;
Joint.MinAngle = Limited ? MinAngle : -100000f;
Joint.MaxAngle = Limited ? MaxAngle : 100000f;
var held = forward > 0.5f || reverse > 0.5f;
if ( !held && !_active )
{
Joint.Motor = HingeJoint.MotorMode.Disabled;
return;
}
var direction = held ? (forward > reverse ? 1f : -1f) : (Reversed ? -1f : 1f);
Joint.Motor = HingeJoint.MotorMode.TargetVelocity;
Joint.TargetVelocity = direction * Speed * MaxDegreesPerSecond;
Joint.MaxTorque = Torque * MaxTorqueScale;
}
protected override void OnFixedUpdate()
{
if ( !Networking.IsHost ) return;
if ( !Joint.IsValid() ) return;
var turns = Joint.Angle / 360f;
if ( float.IsNaN( _lastEmitted ) || MathF.Abs( turns - _lastEmitted ) > 0.001f )
{
_lastEmitted = turns;
AngleOutput.Emit( this, turns );
}
}
}