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.
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 ) );
}
}