Scripting API

Everything your game code needs from the Tuner goes through one class: RCCPT_TunerController. This chapter is the reference for it, the events you can hook, the wallet, resets, localization, and the one namespace line you need.

All Tuner types live in:

using BoneCrackerGames.RCCP.Tuner;

The Controller at a Glance

var tuner = RCCPT_TunerController.Instance;   // soft scene singleton (null if none in the scene)

// Open / close
tuner.OpenGarage();                    // resolved vehicle: explicit -> active player -> scene scan
tuner.OpenGarage(myVehicle);           // a specific RCCP_CarController
tuner.CloseGarage();                   // discards any staged cart (the UI confirms first when needed)
bool open = RCCPT_TunerController.IsActive;   // allocation-free static state mirror

// Economy hooks
tuner.AddCurrency(2500);               // race-reward hook (persists immediately)
tuner.SetEconomyEnabled(false);        // runtime master toggle (sandbox mode)

// Resets
tuner.ResetVehicleToStock();           // bound vehicle back to factory; ownership KEPT
tuner.ResetProgress();                 // wipes balance/ownership/unlocks + re-seeds starting balance

// Events
tuner.OnGarageOpened  += () => { };
tuner.OnGarageClosed  += () => { };
tuner.OnVehicleBound  += vehicle => { };                    // RCCP_CarController
tuner.Wallet.OnBalanceChanged += () => { };
tuner.Session.OnCheckoutCompleted += result => { };         // RCCPT_PurchaseResult

Methods

OpenGarage() / OpenGarage(RCCP_CarController vehicle)

Opens the garage. The parameterless overload resolves the target vehicle in order: the controller's explicitVehicle field, else RCCP Scene Manager's active player vehicle, else the first RCCP_CarController found in the scene. Does nothing if the garage is already open, and logs a warning and aborts when no vehicle can be found or the vehicle has no RCCP_Customizer. A missing catalog set does not abort - the garage opens empty with a console warning telling you where to assign one.

CloseGarage()

Closes the garage, discarding any staged cart and reverting the vehicle to its owned baseline. The scene state (inputs, engine, damage, HUD, cameras) is restored exactly as it was before opening - plus any post-exit handoff overrides you enabled on the controller (Scene Setup).

AddCurrency(int amount)

Adds funds to the global wallet and writes the save immediately. This is the hook for race rewards, mission payouts, daily bonuses. Amounts of zero or less leave the balance alone - but the save is still written, so tuner.AddCurrency(0) is a deliberate "flush now" call after direct wallet or ownership writes (see The Wallet).

SetEconomyEnabled(bool state)

Runtime master toggle for the economy. false makes everything owned/free/unlocked and hides all wallet UI. Note it combines with the Economy Config's own Economy Enabled - both must be on for the economy to be active.

ResetVehicleToStock()

Factory-resets the bound vehicle's applied RCCP configuration (paint, wheels, levels, tuning) while the garage is open. Ownership and money are untouched - the player still owns everything and can re-equip for free.

ResetProgress()

Wipes the entire Tuner save - balance, all ownership, all unlocks - then re-seeds the starting balance. Vehicles' applied RCCP configurations are untouched. This is your "new game" call.

You do not need code for any of these while testing. The controller's inspector carries the same four actions as buttons in Play Mode - Open Garage / Close Garage, Add (the economy config's debug reward), Reset To Stock and Reset Progress - next to a live readout of the session state, bound vehicle, category, cart and balance. See Editor Tools.

Properties

Property Type What it is
RCCPT_TunerController.Instance RCCPT_TunerController Soft scene singleton - finds the scene's controller; null when none exists (never auto-creates)
RCCPT_TunerController.IsActive bool (static) True while the garage is open. Allocation-free - safe to poll every frame
tuner.Session RCCPT_TunerSession The live cart/session state machine (events below)
tuner.Wallet RCCPT_Wallet The wallet (below)
tuner.Ownership RCCPT_OwnershipStore Who owns what - queries and grants, see Reading and Granting Ownership
tuner.Checkout RCCPT_CheckoutService The purchase pipeline the cart runs through. Its IsEffectivelyOwnedGlobal / GetEffectiveOwnedLevel are the economy-aware ownership queries the UI itself uses
tuner.Adapter RCCPT_CustomizerAdapter The one class that talks to RCCP's customization API, see A Note on the RCCP Seam
tuner.Director RCCPT_CameraDirector The garage camera, see Camera & Shots
tuner.Catalogs / tuner.Economy / tuner.Theme / tuner.Shots assets The resolved configuration (controller override, else RCCPT_Settings default)

Events

Controller events

Event Signature Fires
OnGarageOpened Action After the garage opened and the camera took over
OnGarageClosed Action After the garage closed and the scene was restored
OnVehicleBound Action<RCCP_CarController> When a vehicle is bound on open (before OnGarageOpened)

Session events (tuner.Session)

Three terms appear in this table. A cart entry (RCCPT_CartEntry) is one staged, not-yet-paid-for item: what it is, which exclusive slot it occupies, its base price, and the payload needed to apply it. A hover try-on is the temporary preview the car wears while the pointer rests on a card. The projection is what the car is currently showing - always "everything you own, plus everything in the cart, plus any pending tuning edits" - which is why a cart change repaints the car. All three are explained in Using the Garage.

Event Signature Fires
OnCheckoutCompleted Action<RCCPT_PurchaseResult> After every checkout attempt, with its result
OnEquippedOwned Action When an owned/free item equips instantly
OnItemStaged Action<RCCPT_CartEntry> When a click stages an item into the cart (nothing is paid yet)
OnCategoryChanged Action<RCCPT_Category> When the player switches category
OnProjectionChanged Action Whenever what the car is showing changed (cart edits, reverts...)
OnHoverPreview Action<RCCPT_CartEntry> When a hover try-on lands on the car
OnHoverCleared Action When a hover try-on ends

RCCPT_PurchaseResult values: Success, Free, AlreadyOwned, InsufficientFunds, EconomyDisabled, Invalid - meanings in Prices & Economy.

Prefer OnItemStaged over Cart.OnChanged when you want to react to “the player just added something”. Replacing the occupant of a slot - swapping one wheel set for another - leaves the cart count unchanged, so OnChanged fires but tells you nothing new, while OnItemStaged hands you the entry that was staged. The built-in toast and the cart badge punch both ride on it.

A typical game integration needs only this much:

var tuner = RCCPT_TunerController.Instance;

tuner.OnGarageOpened += PauseMyGameSystems;
tuner.OnGarageClosed += ResumeMyGameSystems;
tuner.Session.OnCheckoutCompleted += result => {
    if (result == RCCPT_PurchaseResult.Success)
        Analytics.Log("garage_purchase");
};

The Wallet

var wallet = RCCPT_TunerController.Instance.Wallet;

int balance = wallet.Balance;          // current funds
bool ok     = wallet.CanAfford(5000);
wallet.Earn(1000);                     // add (ignores amounts <= 0)
bool spent  = wallet.TrySpend(1000);   // atomic spend - false if unaffordable
wallet.SetBalance(99999);              // direct set, clamped >= 0 (debug / cheats)
wallet.OnBalanceChanged += () => { };

Which of these survive a quit

Every call above changes the save in memory. Only some of them write it to disk, and that difference is the one thing to get right before you ship:

Call Changes the balance Writes the save
tuner.AddCurrency(amount) Yes (amounts <= 0 are ignored) Yes, immediately
wallet.Earn(amount) Yes No
wallet.TrySpend(amount) Yes, if affordable No
wallet.SetBalance(amount) Yes (clamped >= 0) No
A successful checkout in the garage Yes Yes - the checkout service writes once, after the whole cart is paid for
tuner.ResetProgress() Yes (wipe + re-seed) Yes

So wallet.SetBalance(99999) on its own is lost when the player quits. If you want it kept, follow it with tuner.AddCurrency(0) - that changes nothing but flushes the whole save blob, ownership included. For anything the player earns in normal play, just use AddCurrency and stop thinking about it.

Reading and Granting Ownership

tuner.Ownership (an RCCPT_OwnershipStore) is the "who owns what" record. It has two keyspaces, exactly as described in Prices & Economy: global cosmetics keyed by a string, per-vehicle performance levels keyed by that vehicle's GUID.

var own = RCCPT_TunerController.Instance.Ownership;

// Global cosmetics. The key is "<category prefix>:<entry id>".
bool hasIt = own.IsOwnedGlobal("paint:paint_521A80");   // Midnight Purple, in the demo catalog
own.MarkOwnedGlobal("paint:paint_521A80");              // idempotent grant - a pre-order bonus, say

// Per-vehicle performance, levels 0-5, keyed by the vehicle's Vehicle Id GUID.
string guid = myVehicle.GetComponentInChildren<RCCPT_VehicleId>(true).Guid;
int engineLevel = own.GetVehicleUpgradeLevel(guid, RCCPT_UpgradeKind.Engine);
own.SetVehicleUpgradeLevel(guid, RCCPT_UpgradeKind.Engine, 3);   // clamped 0-5

own.OnOwnershipChanged += RefreshMyGarageBadge;

Rather than hardcoding a key string, ask the catalog that owns the entry: myPaintCatalog.GetOwnershipKey(entry) builds the same "<prefix>:<id>" key from the entry you authored. The prefixes are paint, wheels, spoiler, siren, decal and neon; the one-time tuning unlock has its own constant, RCCPT_OwnershipStore.TuningUnlockKey ("tuning:unlock"). RCCPT_UpgradeKind is Engine, Brake, Handling, Speed.

Like the wallet, these writes are in-memory - flush them with tuner.AddCurrency(0) if the player might quit before the next checkout.

Asking "can the player use this?" is a different question. Ownership answers the literal save-file question and ignores the economy master toggle. tuner.Checkout.IsEffectivelyOwnedGlobal(key) and tuner.Checkout.GetEffectiveOwnedLevel(guid, kind) report everything as owned (and every level as maxed) while the economy is switched off, which is what the garage UI itself uses. Use those two if your code must agree with what the player sees on screen.

Post-Exit Vehicle Handoff

Three public controller fields (also in the inspector) control what happens to the vehicle when the garage closes. All default off, which restores the exact pre-garage state:

tuner.enableInputsAfterExit     = true;   // driving inputs on
tuner.startEngineAfterExit      = true;   // engine running
tuner.registerAsPlayerAfterExit = true;   // becomes RCCP's active player vehicle

When enabled they override the restored state for their one property; registration runs last since registering itself writes control and engine state.

Localizing the UI Strings

Every fixed UI string lives in the static class RCCPT_Strings - the deliberate localization seam. Content names (the paint, wheel and spoiler names the player reads) come from your catalogs, never from here.

The fields are plain public statics, so you assign them once per process, before the first garage opens - not once per scene, and not on the controller:

using UnityEngine;
using BoneCrackerGames.RCCP.Tuner;

public static class MyTunerLocalization {

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    private static void Apply() {

        RCCPT_Strings.Cart = "SEPET";
        RCCPT_Strings.Checkout = "SATIN AL";
        RCCPT_Strings.ToastInsufficientFunds = "Yetersiz bakiye.";
        // ...every label, toast, hint, and confirm-dialog text is a public static string

    }

}

[RuntimeInitializeOnLoadMethod] is Unity's "run this once when the game starts" attribute - no GameObject and no scene wiring needed. An Awake() on a bootstrap object in your first scene does the same job; what matters is only that it runs before the player opens the garage. Because the values are statics they survive scene loads (and survive a Play-Mode session with domain reload disabled), so you never need to re-apply them per scene.

Category names are separate fields (CategoryPaint, CategoryWheels, CategoryEngine, ...) that RCCPT_Strings.CategoryLabel(RCCPT_Category) resolves. Translate the fields; leave the method alone.

The strings a translator misses

Units, deltas and state chips are strings too, and they are the ones that get skipped because they do not read like sentences:

Field Ships as Where it shows
StatUnitTorque NM The engine and braking stat rows
StatUnitSpeed KM/H The top speed stat row
StatUnitPercent % The handling stat row
StatDeltaFormat {0} > {1} The "now, then after this upgrade" stat readout
CartRemaining REMAINING The cart footer, when the wallet covers the cart
CartMissing MISSING The cart footer, when it does not
PreviewBadge PREVIEW The chip shown while a hover try-on is on the car
TuningReset RESET The tuning panel's reset-to-stock button

StatAcceleration is the TORQUE label - its name no longer matches its value. The field ships as "TORQUE": that row reads RCCP's engine torque and never was an acceleration figure, so it was relabelled while the field name stayed. If you are hunting for the torque header, translate RCCPT_Strings.StatAcceleration - there is no StatTorque field.

The handling row is the reason StatUnitPercent exists: handling rides on RCCP's traction-helper strength, a 0-1 number that means nothing on screen, so that row prints a percentage of the stock value instead of a raw figure. Engine and braking print NM, top speed prints KM/H.

Showing Your Own Toasts

The garage's toast panel is public - useful for integration messages while the garage is open:

FindAnyObjectByType<RCCPT_Toast>().Show("Daily bonus added!");

The Vehicle Id Component

RCCPT_VehicleId is the small component that gives a vehicle a stable save identity - a 32-character GUID that keys both its Tuner ownership and its RCCP loadout (Scene Setup). Three members matter to your game code:

Member Type What it does
Guid string (read-only) The stable identity. Generated the first time it is asked for if the field is empty. This is the key you pass to the per-vehicle ownership calls
displayName string Optional. The name the garage's top bar shows for this car
writeThroughToSaveFileName bool (default on) Writes the GUID into RCCP_Customizer.saveFileName so the RCCP loadout and the Tuner ownership share one key. Leave it on unless you are doing custom keying

Fill in displayName on every vehicle you ship. When it is empty the top bar falls back to a cleaned-up transform name - (Clone) stripped, an RCCP_ / RCC_ / BCG_ prefix stripped, underscores and camel case split, the whole thing uppercased - so RCCP_PrototypeVehicle_Skyline reads as PROTOTYPE VEHICLE SKYLINE. That is a safety net, not a feature: it keeps raw prefab names off the screen, but only you know the car is meant to be called "Skyline GT-R".

var id = myVehicle.GetComponentInChildren<RCCPT_VehicleId>(true);

id.displayName = "Skyline GT-R";   // shown uppercased in the top bar
string key = id.Guid;              // per-vehicle ownership key

The Paint Finish Applier

RCCPT_PaintFinishApplier is a tiny runtime component for gameplay scenes: RCCP re-applies a vehicle's saved paint color on spawn, but the finish (matte/chrome) lives in the Tuner save. Add the component next to RCCPT_VehicleId on the vehicle prefab and the saved finish re-applies on spawn in any scene - no controller required, and it does nothing when no finish was ever bought. Calling Apply() again manually is safe.

A Note on the RCCP Seam

One class - RCCPT_CustomizerAdapter - is the single seam through which all garage runtime code (UI, economy, session) touches RCCP's customization API: paint, wheels, spoilers, sirens, decals, neons, upgrade levels and tuning values. It also carries every workaround for RCCP's customization quirks (upgrade level bridging, save suppression during previews, wheel-radius restore, paint throttling...). It stamps the RCCP version those workarounds were last verified against:

RCCPT_CustomizerAdapter.VerifiedAgainstRCCPVersion   // "V3.0.0"
RCCPT_CustomizerAdapter.MaxUpgradeLevel              // 5 - RCCP hardcodes this in every upgrade manager

Practical consequences for you: performance upgrades go 0-5, and if a future RCCP update renames a customization API, that one file absorbs the change for the whole garage.

"Single seam" means the customization API, not the whole of RCCP. Other Tuner runtime scripts do talk to RCCP directly, for work that has nothing to do with customization: the controller and session call RCCP.SetControl / RCCP.SetEngine / RCCP.RegisterPlayerVehicle and park the RCCP camera and canvas, the camera director and top bar read the bound RCCP_CarController, and RCCPT_VehicleId writes RCCP_Customizer.saveFileName. A project-wide search for RCCP_ under Assets/RCCP Tuner/Scripts/ returns several files, by design - it is the customization surface that funnels through one place.

The adapter is also where you ask "what was this value before anybody touched it": GetTuningFloatDefault(RCCPT_TuningFloatKind kind) returns the stock tuning value RCCP captured on Awake, falling back to the current live value when that capture has not run yet. RCCPT_TuningFloatKind is the enum naming one tuning slider each - FrontCamber, RearCamber, FrontSuspensionTarget, RearSuspensionTarget, FrontSuspensionDistance, RearSuspensionDistance, FrontSpringForce, RearSpringForce, FrontSpringDamper, RearSpringDamper. This is what backs the tuning panel's RESET button (Using the Garage).

Reach the live instance through tuner.Adapter. It exists for as long as the controller does, but it binds to a vehicle when the garage opens - so its per-vehicle queries only answer while a vehicle is bound.

Verifying an Integration

Demo/RCCPT_TunerProbe.cs is a play-mode verification harness: add the component to the showroom scene at runtime, call RunAll(), and read the JSON report at Application.persistentDataPath/RCCPT_TunerProbe/report_latest.json. It asserts the whole contract - session open/close restoration, cart semantics, ladder pricing, atomic checkout, persistence round-trips, revert correctness, economy-off behavior, camera takeover/release, audio lifecycle, and more. It wipes the Tuner save and the vehicle's RCCP save at start - never run it against progress you care about.

Next Steps