Player/Car/Car.cs

Component representing a player car. Stores synced session/state (slot, bot, autopilot), references car resource and derived stats, wires required subcomponents (movement, drift, boost, input, visuals, items, respawn), sets up model/ stats from the CarResource and notifies ICarChangeListener on change.

NetworkingFile Access
using Machines.Components;
using Machines.Events;
using Machines.Resources;
using Machines.UI;

namespace Machines.Player;

/// <summary>
/// The central component representing a player's car in the scene.
/// </summary>
public sealed partial class Car : Component
{
	/// <summary>
	/// Forces AI control regardless of owner; host sets this when a player finishes
	/// </summary>
	[Sync( SyncFlags.FromHost )]
	public bool Autopilot { get; set; }

	/// <summary>
	/// Defines the vehicle's identity and baseline stats; setting fires ICarChanged
	/// </summary>
	[Property, Sync]
	public CarResource Resource
	{
		get;
		set
		{
			if ( field == value )
				return;

			field = value;
			SetupFromResource();
		}
	}

	/// <summary>
	/// Session slot index assigned by host at spawn; -1 = unassigned; keys spawn points and standings
	/// </summary>
	[Sync]
	public int Slot { get; set; } = -1;

	/// <summary>
	/// Selected car resource path; UserInfo so the host can read it at spawn via GetUserData
	/// </summary>
	[ConVar( "preferred_car", Flags = ConVarFlags.Saved | ConVarFlags.UserInfo )]
	public static string PreferredCar { get; set; }

	/// <summary>
	/// Whether this car is driven by an AI bot (host-owned, no real connection).
	/// </summary>
	[Sync]
	public bool IsBot { get; set; }

	/// <summary>
	/// A pool of bot names
	/// </summary>
	private static readonly string[] BotNames = { "Bolt", "Sparky", "Turbo", "Blaze", "Nitro", "Dash", "Rumble", "Flare" };

	/// <summary>
	/// Clientside display name: connection nickname for players, slot-derived name for bots
	/// </summary>
	public string DisplayName => IsBot
		? BotNames[Math.Max( Slot, 0 ) % BotNames.Length]
		: Network.Owner?.DisplayName ?? "Player";

	/// <summary>
	/// The player's assigned colour, derived from the slot index.
	/// </summary>
	public Color PlayerColor => Slot >= 0 ? PlayerColors.GetColor( Slot ) : Color.White;

	/// <summary>
	/// True when this car belongs to the player on this machine.
	/// </summary>
	public bool IsLocalPlayer => !IsBot && Network.IsOwner;

	/// <summary>
	/// True on the simulating machine (local player or host-run bot); gate single-run gameplay on this
	/// </summary>
	public bool IsAuthority => !IsProxy;

	/// <summary>
	/// The local player's car in the scene, or null.
	/// </summary>
	public static Car Local => Game.ActiveScene?.GetAllComponents<Car>().FirstOrDefault( c => c.IsValid() && c.IsLocalPlayer );

	/// <summary>
	/// True when enabled and not mid-respawn; false cars are ignored by sweeps and spot checks
	/// </summary>
	public bool IsPhysicallyPresent => GameObject.Enabled && (!Respawn.IsValid() || !Respawn.IsRespawning);

	/// <summary>
	/// The immutable baseline stats copied from the CarResource on initialization.
	/// </summary>
	public CarStatValues BaseStats { get; private set; }

	/// <summary>
	/// Live stats used by physics; reset to BaseStats to remove runtime modifications
	/// </summary>
	public CarStatValues ActiveStats { get; set; }

	/// <summary>
	/// The movement physics controller.
	/// </summary>
	[RequireComponent]
	public CarMovement Movement { get; private set; }

	/// <summary>
	/// The drift mechanic component.
	/// </summary>
	[RequireComponent]
	public CarDrift Drift { get; private set; }

	/// <summary>
	/// The boost mechanic component.
	/// </summary>
	[RequireComponent]
	public CarBoost Boost { get; private set; }

	/// <summary>
	/// The input reader component.
	/// </summary>
	[RequireComponent]
	public CarInput Input { get; private set; }

	/// <summary>
	/// The body tilt visual component.
	/// </summary>
	[RequireComponent]
	public CarBodyTilt BodyTilt { get; private set; }

	/// <summary>
	/// Wall-scrape and car collision response
	/// </summary>
	[RequireComponent]
	public CarCollision Collision { get; private set; }

	/// <summary>
	/// Non-destructive respawn: disable/re-enable and reposition on track
	/// </summary>
	[RequireComponent]
	public CarRespawn Respawn { get; private set; }

	/// <summary>
	/// Held pickup item and its activation.
	/// </summary>
	[RequireComponent]
	public Items.CarInventory Inventory { get; private set; }

	/// <summary>
	/// Spin-out and hit reaction from weapons/mines
	/// </summary>
	[RequireComponent]
	public Items.CarSpinout Spinout { get; private set; }

	/// <summary>
	/// Per-race points/XP for the local player (HUD display).
	/// </summary>
	[RequireComponent]
	public CarScore Score { get; private set; }

	/// <summary>
	/// Model renderer; used to apply the mesh from CarResource
	/// </summary>
	[Property]
	public SkinnedModelRenderer Renderer { get; set; }

	protected override void OnStart()
	{
		if ( !Renderer.IsValid() )
		{
			Renderer = Components.GetInDescendantsOrSelf<SkinnedModelRenderer>();
		}

		SetupFromResource();
	}

	private void SetupFromResource()
	{
		if ( !Resource.IsValid() )
			BaseStats = CarStatValues.Default;
		else
			BaseStats = Resource.Stats;

		ActiveStats = BaseStats;

		// Apply model from resource
		if ( Renderer.IsValid() && Resource.Model.IsValid() )
		{
			Renderer.Model = Resource.Model;
		}

		// Notify siblings
		GameObject.RunEvent<ICarChangeListener>( x => x.OnCarChanged( this ) );
	}
}