diff --git a/assets/reference/rx7-reference.png b/assets/reference/rx7-reference.png new file mode 100755 index 0000000..86707a6 Binary files /dev/null and b/assets/reference/rx7-reference.png differ diff --git a/src/client/init.client.luau b/src/client/init.client.luau index 42f3a61..97ce903 100644 --- a/src/client/init.client.luau +++ b/src/client/init.client.luau @@ -231,6 +231,11 @@ local function rebuildGarage() local owned = ownedIds[car.Id] local equipped = latest.EquippedCarId == car.Id + -- Hide restricted (allowlist-only) cars from anyone who doesn't own them. + if car.Restricted and not owned then + continue + end + local row = make("Frame", { Size = UDim2.new(1, 0, 0, 72), BackgroundColor3 = Color3.fromRGB(26, 26, 38), diff --git a/src/server/CarManager.luau b/src/server/CarManager.luau index 8f00b30..4e27c95 100644 --- a/src/server/CarManager.luau +++ b/src/server/CarManager.luau @@ -19,6 +19,7 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage") local Shared = ReplicatedStorage.Shared local Cars = require(Shared.Cars) +local GameConfig = require(Shared.GameConfig) local ProfileTemplate = require(Shared.ProfileTemplate) local DataManager = require(script.Parent.DataManager) @@ -125,6 +126,11 @@ function CarManager.spawn(player: Player, carId: string?) if not car then return end + -- Defense in depth: never spawn the restricted RX-7 for a non-allowlisted user, + -- even if their save somehow contained it. + if car.Id == GameConfig.RestrictedStarterCarId and not GameConfig.isRX7Allowed(player.UserId) then + return + end -- Only spawn cars the player owns (anti-cheat: no spawning unowned exotics). local owned for _, o in ipairs(data.OwnedCars) do diff --git a/src/server/DataManager.luau b/src/server/DataManager.luau index 140a0a3..a7bd26f 100644 --- a/src/server/DataManager.luau +++ b/src/server/DataManager.luau @@ -71,6 +71,22 @@ local function onPlayerAdded(player: Player) profile:AddUserId(player.UserId) -- GDPR-friendly association profile:Reconcile() -- fill missing template keys + -- Grant the correct starter car for THIS player (RX-7 only for allowlisted + -- UserIds; everyone else gets the public starter). Idempotent: only grants + -- if the player has no cars yet (i.e. genuinely first join). + local data = profile.Data + if #data.OwnedCars == 0 then + local starterId = GameConfig.starterCarFor(player.UserId) + table.insert(data.OwnedCars, { + Id = starterId, + Upgrades = { Engine = 0, Tires = 0, Nitro = 0 }, + }) + data.EquippedCarId = starterId + elseif data.EquippedCarId == "" then + -- Safety: if somehow they own cars but have nothing equipped, equip the first. + data.EquippedCarId = data.OwnedCars[1].Id + end + profile.OnSessionEnd:Connect(function() -- The session was lost (another server stole it, or we released it). profiles[player] = nil diff --git a/src/server/Economy.luau b/src/server/Economy.luau index ea3a73d..b6d4520 100644 --- a/src/server/Economy.luau +++ b/src/server/Economy.luau @@ -88,6 +88,10 @@ function Economy.buyCar(player: Player, carId: string): Result if not car then return { ok = false, reason = "Unknown car" } end + -- Restricted demo car: the RX-7 can only ever be owned by allowlisted UserIds. + if car.Id == GameConfig.RestrictedStarterCarId and not GameConfig.isRX7Allowed(player.UserId) then + return { ok = false, reason = "This car is not available" } + end if ownsCar(data, carId) then return { ok = false, reason = "You already own this car" } end diff --git a/src/shared/Cars.luau b/src/shared/Cars.luau index 639ac9e..8782769 100644 --- a/src/shared/Cars.luau +++ b/src/shared/Cars.luau @@ -27,6 +27,7 @@ export type Car = { Color: { number }, -- {r,g,b} 0-1 for the placeholder body until real mesh exists Description: string, Starter: boolean?, -- true = granted free on first join + Restricted: boolean?, -- true = allowlist-only; hidden from dealership for others } local Cars = {} @@ -44,6 +45,7 @@ Cars.List = { Color = { 0.85, 0.15, 0.12 }, -- classic RX-7 red until the real mesh is imported Description = "The one that started it all. A rotary-hearted FD RX-7 — light, balanced, and iconic. Your daily driver and first weapon on the expressway.", Starter = true, + Restricted = true, -- demo-locked to the owner + son during testing }, { Id = "hatch_junk", diff --git a/src/shared/GameConfig.luau b/src/shared/GameConfig.luau index cc19243..e9c1b0d 100644 --- a/src/shared/GameConfig.luau +++ b/src/shared/GameConfig.luau @@ -10,7 +10,33 @@ local GameConfig = {} -- Economy ------------------------------------------------------------------- GameConfig.StartingCash = 1500 -- enough to feel, not enough to skip the RX-7 GameConfig.StartingRep = 0 -GameConfig.StartingCarId = "rx7_fc" -- the player's real 1993 Mazda RX-7 + +-- Starter cars --------------------------------------------------------------- +-- The RX-7 is a RESTRICTED demo car for the owner + son during testing. Only +-- UserIds in the allowlist below ever receive / can buy / can spawn it. +-- Everyone else starts in the public starter car instead. +GameConfig.PublicStarterCarId = "hatch_junk" -- default car for non-allowlisted players +GameConfig.RestrictedStarterCarId = "rx7_fc" -- the real 1993 Mazda RX-7 (demo-locked) + +-- >>> FILL THESE IN: Roblox UserIds allowed to own the RX-7 (numbers, not names). +-- Find yours at roblox.com/users//profile — the number in the URL. +GameConfig.RX7AllowedUserIds = { + -- [1234567] = true, -- R3AL.CH1CK3N (replace with your UserId) + -- [7654321] = true, -- son (replace with his UserId) +} + +--- True if this player is allowed to own the restricted RX-7. +function GameConfig.isRX7Allowed(userId: number): boolean + return GameConfig.RX7AllowedUserIds[userId] == true +end + +--- The starter car id this specific player should receive on first join. +function GameConfig.starterCarFor(userId: number): string + if GameConfig.isRX7Allowed(userId) then + return GameConfig.RestrictedStarterCarId + end + return GameConfig.PublicStarterCarId +end -- Race rewards (flat MVP model; tune per-race later) ------------------------- GameConfig.RaceBaseReward = 250 diff --git a/src/shared/ProfileTemplate.luau b/src/shared/ProfileTemplate.luau index 89e9550..fa4374a 100644 --- a/src/shared/ProfileTemplate.luau +++ b/src/shared/ProfileTemplate.luau @@ -35,13 +35,10 @@ export type ProfileData = { local ProfileTemplate: ProfileData = { Cash = GameConfig.StartingCash, Rep = GameConfig.StartingRep, - OwnedCars = { - { - Id = GameConfig.StartingCarId, - Upgrades = { Engine = 0, Tires = 0, Nitro = 0 }, - }, - }, - EquippedCarId = GameConfig.StartingCarId, + -- Starter car is granted per-player by DataManager after load (depends on + -- UserId: allowlisted players get the RX-7, everyone else the public car). + OwnedCars = {}, + EquippedCarId = "", LastDailyClaim = 0, TotalRaces = 0, Wins = 0,