In short
A tycoon is five pieces run by server Scripts: a claim pad that sets an owner, droppers that spawn parts on a timer, a conveyor (an anchored part whose AssemblyLinearVelocity carries whatever sits on it), a collector that destroys each drop and adds its value to the owner's Cash, and buttons that check the owner and the price on the server before they unlock the next upgrade.
- Store the owner as an
OwnerIdattribute on the tycoon model, allow one tycoon per player, and set it back to 0 inPlayers.PlayerRemovingso the plot frees up. - Pay only for drops this tycoon made: keep them in the tycoon's own Drops folder, give each a
Valueattribute, and let the server own their physics. - Cap the number of live drops, destroy every drop the collector takes, and save Cash with a data store when the player leaves.
On this page
How a tycoon works
Every tycoon runs the same loop. A player claims an empty plot, the plot starts making money, and the player spends it on upgrades that make money faster. Each piece of that loop is one part (or folder of parts) and one short script.
| Piece | What it does | Script |
|---|---|---|
| Claim pad | The first player to step on it owns the plot until they leave | TycoonOwnership |
| Droppers | Each one spawns a drop every few seconds while the plot has an owner | TycoonDroppers |
| Conveyor | An anchored part with a velocity that slides drops to the collector | TycoonConveyors |
| Collector | Destroys each drop that reaches it and adds the drop's value to the owner's Cash | TycoonCollectors |
| Buttons | Take Cash and reveal the next item, after the server checks owner and price | TycoonButtons |
| Cash | A leaderstats value, saved with a data store | Leaderstats |
The scripts share one piece of state: an OwnerId attribute on each tycoon model. The ownership script writes it and the others read it, so each script stays short and you can change one without touching the rest. Each script also handles every plot in a Tycoons folder, so a second plot is a copy of the first, not a copy of the code.
All of it runs on the server. Cash, purchases and drops are worth something, and anything a LocalScript changes is seen by that one player only, so the server must make every decision about money. If the split between server and client is new to you, read Script vs LocalScript vs ModuleScript first.
Step 1: Build the plot and the Cash stat
- In the Explorer, hover over Workspace, click the + button and insert a Folder named
Tycoons. Inside it, insert a Model namedTycoon1. This model is one plot. - Inside
Tycoon1, add the parts and folders from the tree below. Turn on Anchored for every part you build, including the parts insideWalls. - Lay out the line:
Conveyoris a long flat part (for example 4 by 1 by 20 studs), both droppers hang 3 or 4 studs above one end of it, andCollectorsits against the other end with its top level with the conveyor's top, so drops slide onto it. PutClaimPadand the two buttons where a player can walk onto them. - Build everything you plan to sell where it should finally stand, such as
Dropper2and aWallsmodel. The buttons script hides each one until it is bought. - Add the attributes from the table: select the object, scroll to Attributes at the bottom of the Properties window, click +, enter the name and type, click Save, then set the value.
Workspace
Tycoons Folder new
Tycoon1 Model new
ClaimPad Part new
Conveyor Part new
Collector Part new
Droppers Folder new
Dropper1 Part new
Dropper2 Part new
Buttons Folder new
BuyDropper2 Part new
BuyWalls Part new
Walls Model new
ServerScriptService
Leaderstats Script new
TycoonOwnership Script new
TycoonDroppers Script new
TycoonConveyors Script new
TycoonCollectors Script new
TycoonButtons Script new
| Object | Attribute (type) | Example | What it does |
|---|---|---|---|
| Each dropper | Interval (number) | 2 | Seconds between drops. Optional, default 2. |
| Each dropper | DropValue (number) | 1 | Cash each drop pays at the collector. Optional, default 1. |
Conveyor | Speed (number) | 8 | Studs per second towards the part's front face. A negative number runs it backwards. Optional, default 8. |
BuyDropper2 | Price (number) | 50 | Cash the button costs. |
BuyDropper2 | Unlocks (string) | Dropper2 | Name of the object in this plot that the button reveals. |
BuyWalls | Price, Unlocks | 150, Walls | As above. |
BuyWalls | Requires (string) | BuyDropper2 | Optional. The button stays hidden until the named button is bought. |
Give everything a button can unlock a name that is unique inside its plot, because the buttons script finds it by name. You do not add OwnerId yourself: the ownership script creates it.
Cash is a normal leaderstats value, so it appears in the player list. Insert a Script in ServerScriptService named Leaderstats. The leaderboard guide explains how leaderstats works.
local Players = game:GetService("Players")
local STARTING_CASH = 0
local function setupLeaderstats(player: Player)
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats" -- must be exactly this name, all lowercase
local cash = Instance.new("IntValue")
cash.Name = "Cash"
cash.Value = STARTING_CASH
cash.Parent = leaderstats
leaderstats.Parent = player
end
Players.PlayerAdded:Connect(setupLeaderstats)
-- In Studio you can join before this script runs, so set up anyone already here
for _, player in Players:GetPlayers() do
setupLeaderstats(player)
end
If your game already has a leaderstats script, add the Cash value to it instead. Two scripts that each make a leaderstats folder is a common reason a stat never changes on screen.
Step 2: Claim a tycoon, one per player
The claim pad gives the plot to the first player who steps on it, as long as that player does not already own one. It writes their UserId to the model's OwnerId attribute, puts their name on a sign above the pad, and frees the plot again when they leave. Insert a Script in ServerScriptService named TycoonOwnership.
local Players = game:GetService("Players")
local tycoons = workspace:WaitForChild("Tycoons")
local function ownerIdOf(tycoon: Instance): number
local id = tycoon:GetAttribute("OwnerId")
return if typeof(id) == "number" then id else 0
end
-- The living player whose character touched the part, or nil for anything else
local function playerFromHit(hit: BasePart): Player?
local character = hit:FindFirstAncestorOfClass("Model")
if not character then
return nil
end
local humanoid = character:FindFirstChildOfClass("Humanoid")
if not humanoid or humanoid.Health <= 0 then
return nil
end
return Players:GetPlayerFromCharacter(character)
end
local function ownedTycoon(player: Player): Model?
for _, tycoon in tycoons:GetChildren() do
if tycoon:IsA("Model") and ownerIdOf(tycoon) == player.UserId then
return tycoon
end
end
return nil
end
-- The sign above the pad, created the first time it is needed
local function signLabel(pad: BasePart): TextLabel
local existing = pad:FindFirstChild("OwnerLabel", true)
if existing and existing:IsA("TextLabel") then
return existing
end
local billboard = Instance.new("BillboardGui")
billboard.Size = UDim2.fromOffset(240, 50)
billboard.StudsOffset = Vector3.new(0, 4, 0)
billboard.MaxDistance = 80
local label = Instance.new("TextLabel")
label.Name = "OwnerLabel"
label.Size = UDim2.fromScale(1, 1)
label.BackgroundTransparency = 1
label.TextColor3 = Color3.new(1, 1, 1)
label.TextStrokeTransparency = 0.4
label.TextScaled = true
label.Parent = billboard
billboard.Parent = pad
return label
end
local function release(tycoon: Model, pad: BasePart)
tycoon:SetAttribute("OwnerId", 0)
pad.Transparency = 0
signLabel(pad).Text = "Free tycoon: step here to claim"
end
local function claim(tycoon: Model, pad: BasePart, player: Player)
tycoon:SetAttribute("OwnerId", player.UserId)
pad.Transparency = 0.7
signLabel(pad).Text = `{player.DisplayName}'s tycoon`
end
local function setUpPad(tycoon: Model, pad: BasePart)
release(tycoon, pad) -- every plot starts free
pad.Touched:Connect(function(hit: BasePart)
if ownerIdOf(tycoon) ~= 0 then
return -- someone owns it already
end
local player = playerFromHit(hit)
if player and not ownedTycoon(player) then -- one tycoon per player
claim(tycoon, pad, player)
end
end)
end
for _, tycoon in tycoons:GetChildren() do
local pad = tycoon:FindFirstChild("ClaimPad")
if tycoon:IsA("Model") and pad and pad:IsA("BasePart") then
setUpPad(tycoon, pad)
else
warn(`{tycoon:GetFullName()} needs to be a Model with a Part named ClaimPad`)
end
end
-- Free the plot when its owner leaves, so the next player can claim it
Players.PlayerRemoving:Connect(function(player: Player)
local tycoon = ownedTycoon(player)
local pad = tycoon and tycoon:FindFirstChild("ClaimPad")
if tycoon and pad and pad:IsA("BasePart") then
release(tycoon, pad)
end
end)
Touched can fire many times while a character stands on the pad. That is harmless here: after the first touch the plot is owned, and every later touch returns at the first check.
The owner is stored as a number, not a Player, because attributes cannot hold an object. A value of 0 means the plot is free. Attributes replicate, so a LocalScript can read OwnerId too, for example to point the player towards their own plot.
Step 3: Make a tycoon dropper that spawns drops
One script runs every dropper in every plot. It loops with task.wait, and on each pass it spawns a drop under each dropper whose interval is up, but only in plots that have an owner. Insert a Script in ServerScriptService named TycoonDroppers.
local Debris = game:GetService("Debris")
local tycoons = workspace:WaitForChild("Tycoons")
local MAX_DROPS_PER_TYCOON = 50 -- live drops at once; a dropper skips its turn at the cap
local DROP_LIFETIME = 30 -- seconds; removes drops that fall off or get stuck
local TICK = 0.2 -- how often the loop checks the droppers
-- When each dropper may drop next. Bought droppers are hidden, never destroyed,
-- so these entries stay valid for the life of the server.
local nextDropAt: { [BasePart]: number } = {}
local function positiveNumber(instance: Instance, name: string, default: number): number
local value = instance:GetAttribute(name)
if typeof(value) == "number" and value > 0 then
return value
end
return default
end
local function ownerIdOf(tycoon: Instance): number
local id = tycoon:GetAttribute("OwnerId")
return if typeof(id) == "number" then id else 0
end
-- Each plot keeps its drops in its own folder. The collector pays only for drops in it.
local function dropsFolder(tycoon: Model): Folder
local existing = tycoon:FindFirstChild("Drops")
if existing and existing:IsA("Folder") then
return existing
end
local folder = Instance.new("Folder")
folder.Name = "Drops"
folder.Parent = tycoon
return folder
end
local function spawnDrop(tycoon: Model, dropper: BasePart)
local drops = dropsFolder(tycoon)
if #drops:GetChildren() >= MAX_DROPS_PER_TYCOON then
return
end
local drop = Instance.new("Part")
drop.Name = "Drop"
drop.Size = Vector3.new(1, 1, 1)
drop.Color = dropper.Color
drop.Material = Enum.Material.SmoothPlastic
drop.CFrame = dropper.CFrame * CFrame.new(0, -(dropper.Size.Y / 2 + 0.6), 0) -- just under the dropper
drop:SetAttribute("Value", positiveNumber(dropper, "DropValue", 1))
drop.Parent = drops
-- The server simulates the drop, so no player's device can move it or fake its touches
if drop:CanSetNetworkOwnership() then
drop:SetNetworkOwner(nil)
end
Debris:AddItem(drop, DROP_LIFETIME)
end
-- Clear a plot's drops when its owner leaves
local function watchOwner(tycoon: Model)
tycoon:GetAttributeChangedSignal("OwnerId"):Connect(function()
if ownerIdOf(tycoon) == 0 then
dropsFolder(tycoon):ClearAllChildren()
end
end)
end
for _, tycoon in tycoons:GetChildren() do
if tycoon:IsA("Model") then
watchOwner(tycoon)
end
end
while true do
local now = os.clock()
for _, tycoon in tycoons:GetChildren() do
local droppers = tycoon:FindFirstChild("Droppers")
if tycoon:IsA("Model") and droppers and ownerIdOf(tycoon) ~= 0 then
for _, dropper in droppers:GetChildren() do
if dropper:IsA("BasePart") and now >= (nextDropAt[dropper] or 0) then
nextDropAt[dropper] = now + positiveNumber(dropper, "Interval", 2)
spawnDrop(tycoon, dropper)
end
end
end
end
task.wait(TICK)
end
The loop checks five times a second, so an Interval below 0.2 behaves like 0.2. Only droppers inside the Droppers folder run: an unbought Dropper2 is in ServerStorage until its button is bought, so the loop never sees it.
Three details in spawnDrop matter later. The Value attribute is what the collector pays. The Drops folder is how the collector knows the drop came from this plot. And SetNetworkOwner(nil) keeps the drop's physics on the server: otherwise Roblox can hand an unanchored part near a player's character to that player's device to simulate, and a device that simulates a part can teleport it or report touches that never happened.
Step 4: Move drops with a conveyor
A conveyor is an anchored part with a velocity. An anchored part never moves, but Roblox's API reference notes, on the older Velocity property, that giving an anchored part a velocity makes it act like a conveyor belt: anything touching it starts to move with that velocity. Velocity is now deprecated, and its deprecation message says to use AssemblyLinearVelocity instead, so that is the property this guide sets. Older tutorials still set Velocity.
Set it from a server Script. Anchored parts are always owned by the server, and the reference says a server-owned part's AssemblyLinearVelocity must be changed from a server Script, not a LocalScript. Insert a Script in ServerScriptService named TycoonConveyors.
local tycoons = workspace:WaitForChild("Tycoons")
local DEFAULT_SPEED = 8 -- studs per second
-- An anchored part never moves, but a velocity on it carries whatever rests on top
local function startConveyor(part: BasePart)
local speed = part:GetAttribute("Speed")
local studsPerSecond = if typeof(speed) == "number" then speed else DEFAULT_SPEED
part.Anchored = true
part.AssemblyLinearVelocity = part.CFrame.LookVector * studsPerSecond
end
local function onDescendant(instance: Instance)
if instance.Name == "Conveyor" and instance:IsA("BasePart") then
startConveyor(instance)
end
end
for _, instance in tycoons:GetDescendants() do
onDescendant(instance)
end
-- A conveyor sold by a button comes back into its plot later, so watch for it too
tycoons.DescendantAdded:Connect(onDescendant)
LookVector is the direction of the part's front face, so the belt runs the way the part faces. If drops travel the wrong way, rotate the conveyor 180 degrees or make its Speed negative.
Because the direction comes from the part, you can build a bend from two or more Conveyor parts, each turned to face the next. Every part named Conveyor anywhere inside Tycoons is picked up.
Step 5: Pay the owner at the collector
The collector listens for Touched, checks that the part is a drop from this plot, destroys it and pays its value to the plot's owner. It pays the owner, not whoever touches the collector, so walking onto it does nothing and a neighbour's stray drop earns nobody anything. Insert a Script in ServerScriptService named TycoonCollectors.
local Players = game:GetService("Players")
local tycoons = workspace:WaitForChild("Tycoons")
local function ownerIdOf(tycoon: Instance): number
local id = tycoon:GetAttribute("OwnerId")
return if typeof(id) == "number" then id else 0
end
local function cashOf(player: Player): IntValue?
local leaderstats = player:FindFirstChild("leaderstats")
local cash = leaderstats and leaderstats:FindFirstChild("Cash")
if cash and cash:IsA("IntValue") then
return cash
end
return nil
end
local function setUpCollector(tycoon: Model, collector: BasePart)
collector.Touched:Connect(function(hit: BasePart)
-- Only drops made by this plot's droppers count. Characters, tools and
-- drops from a neighbour's plot are ignored.
local drops = tycoon:FindFirstChild("Drops")
if drops == nil or hit.Parent ~= drops then
return
end
local value = hit:GetAttribute("Value")
-- Destroy sets Parent to nil, so a second touch from the same drop fails the check above
hit:Destroy()
local owner = Players:GetPlayerByUserId(ownerIdOf(tycoon))
local cash = owner and cashOf(owner)
if cash and typeof(value) == "number" then
cash.Value += math.floor(value)
end
end)
end
for _, tycoon in tycoons:GetChildren() do
local collector = tycoon:FindFirstChild("Collector")
if tycoon:IsA("Model") and collector and collector:IsA("BasePart") then
setUpCollector(tycoon, collector)
else
warn(`{tycoon:GetFullName()} needs a Part named Collector`)
end
end
Touched only fires when at least one of the two parts is not anchored. The collector is anchored, so this works only because drops are not. An anchored drop never fires Touched on the collector, and never rides the conveyor either.
Reading the value from the drop is safe because the server set it. A client that changes an attribute changes its own copy only; the server's copy, which this script reads, stays as it was.
Step 7: Save Cash between sessions
leaderstats only lasts as long as the server. To keep Cash, load it from a data store when the player joins and save it when they leave or the server shuts down. Replace the contents of the Leaderstats script from Step 1 with this version. It never saves a player whose Cash failed to load, so a data store outage cannot overwrite real progress with 0.
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local cashStore = DataStoreService:GetDataStore("TycoonCash_v1")
-- Players whose Cash loaded. Anyone missing from here is never saved,
-- so a failed load cannot overwrite their real Cash with 0.
local loaded: { [Player]: boolean } = {}
local savesInProgress = 0
local function keyFor(player: Player): string
return "player_" .. player.UserId
end
local function onPlayerAdded(player: Player)
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
local cash = Instance.new("IntValue")
cash.Name = "Cash"
cash.Parent = leaderstats
local ok, saved = pcall(function()
return cashStore:GetAsync(keyFor(player))
end)
if player.Parent ~= Players then
return -- they left while their Cash was loading
end
if ok then
cash.Value = if typeof(saved) == "number" then math.floor(saved) else 0
loaded[player] = true
else
warn(`Could not load Cash for {player.Name}: {saved}`)
end
leaderstats.Parent = player
end
local function save(player: Player)
if not loaded[player] then
return
end
loaded[player] = nil -- save once, whether leaving or shutdown comes first
local leaderstats = player:FindFirstChild("leaderstats")
local cash = leaderstats and leaderstats:FindFirstChild("Cash")
if not (cash and cash:IsA("IntValue")) then
return
end
local amount = cash.Value
savesInProgress += 1
local ok, err = pcall(function()
cashStore:SetAsync(keyFor(player), amount)
end)
savesInProgress -= 1
if not ok then
warn(`Could not save Cash for {player.Name}: {err}`)
end
end
Players.PlayerAdded:Connect(onPlayerAdded)
for _, player in Players:GetPlayers() do
task.spawn(onPlayerAdded, player)
end
Players.PlayerRemoving:Connect(save)
-- When the server shuts down, save everyone still here and wait for every save to finish
game:BindToClose(function()
for _, player in Players:GetPlayers() do
task.spawn(save, player)
end
while savesInProgress > 0 do
task.wait()
end
end)
If a load fails, the player keeps playing with 0 Cash that is never saved. In a real game, tell them, as the DataStore tutorial shows, so they can rejoin before they spend time earning.
This is enough for Cash in a small game. For retries, UpdateAsync, request limits and stopping two servers from writing the same player, continue with the DataStore tutorial. To save what a player bought as well, see the questions at the end of this page.
Test the tycoon
- Press Play. The sign above the pad reads "Free tycoon". Step on the pad: the sign changes to your name,
Dropper1starts dropping and theBuyDropper2button appears with its price. - Watch a few drops ride the conveyor and vanish at the collector. Cash in the player list goes up by each drop's
DropValue. - At 50 Cash, step on
BuyDropper2. Cash drops by 50,Dropper2appears and starts dropping, andBuyWallsappears. - Duplicate
Tycoon1(Ctrl+D, or Cmd+D on Mac), move the copy aside and rename itTycoon2. In the testing modes drop-down choose Server & Clients with 2 clients and press Play. A player who owns a plot cannot claim the second one, and nobody can take a plot someone else owns.
If something does not happen, open the Output window first. Every script above warns by name when a part is missing or a button is set up wrong.
Keep a tycoon fast: drops, parts and cleanup
A tycoon makes new parts for as long as the server runs, so a missing Destroy makes the server slower the longer it runs. The scripts above already do the following. Keep doing them as the game grows.
- Destroy every drop. The collector destroys each drop it pays for, and
Debris:AddItemremoves any drop that falls off after 30 seconds. Debris has a fixed limit of 1,000 items: when more are added, the oldest are destroyed at once, even if they are still riding the conveyor. If you add many fast droppers, keep the number of drops the whole server makes in 30 seconds well under 1,000. - Cap live drops. Every unanchored drop is one more assembly for the physics engine, and Roblox names the number of simulated assemblies as a cause of slow physics frames.
MAX_DROPS_PER_TYCOONstops a fast dropper from filling the plot when the conveyor jams. With 8 plots and a cap of 50, the worst case is 400 drops. - Anchor everything else. The conveyor, collector, droppers, buttons and decorations never need physics. Roblox's performance guide recommends anchoring every part that does not need simulating.
- Keep drops on the server.
SetNetworkOwner(nil)stops players' devices from simulating drops. Roblox advises using it sparingly because it can make physics look jittery to players, which is a fair trade for parts that are worth money. - Use one loop for all droppers. A single script checks every dropper five times a second, so adding a dropper means adding a part, not another script with its own loop.
- Clean up per player and per plot. When an owner leaves, their plot's drops are cleared, their purchases go back into storage and the save script removes them from its table. Tables keyed by player that are never cleared are a common slow memory leak on long-running servers.
Common errors and fixes
These are common reasons a tycoon does not work. Check the Output window first: a script that errored names the line, and the scripts above warn when a part is missing.
Drops do not move on the conveyor
Check that the part is named exactly Conveyor, is inside a plot in Tycoons, and that TycoonConveyors is a Script in ServerScriptService. If drops move the wrong way, rotate the conveyor 180 degrees or make Speed negative. Older tutorials set Velocity, which Roblox has deprecated: use AssemblyLinearVelocity.
Drops reach the collector but Cash does not go up
Touched needs at least one unanchored part, so drops must not be anchored, and CanTouch must be on for both parts. The collector only pays for parts inside its own plot's Drops folder, so drops made by another script are ignored. The owner also needs a leaderstats folder with an IntValue named exactly Cash.
Cash goes to whoever walks onto the collector
The collector script is paying whoever touches it. Pay the plot's owner instead, looked up from OwnerId, and pay only for parts in the plot's Drops folder, as in Step 5.
A player can own two tycoons, or a plot stays locked after its owner leaves
The claim needs a check that the player owns no other plot, and Players.PlayerRemoving must set OwnerId back to 0. Both are in Step 2.
A button takes Cash twice, or buys when the player cannot afford it
The purchase must happen in a server Script that checks Cash and marks the button bought before anything can run again. A LocalScript that changes Cash only changes it on that player's screen, which is why the number jumps back.
A button never appears
Buttons only show after a player claims the plot. If it still does not appear, its Requires attribute names a button that does not exist or is not bought yet; the Output window shows a warning for a missing one.
Infinite yield possible on 'Workspace:WaitForChild("Tycoons")'
The scripts cannot find the folder. It must be a Folder named exactly Tycoons directly inside Workspace. The script errors guide explains this warning.
The game gets slower the longer it runs
Drops are not being destroyed. Make sure the collector destroys each drop, every drop goes to Debris:AddItem, and the live-drop cap is in place.
Build it with RoCode
If you would rather not place every part and attribute by hand, RoCode can build this in the place you have open. It is an AI agent that works through a Roblox Studio plugin, so it creates the scripts, parts and attributes in your Explorer instead of giving you code to paste.
Build a tycoon plot in Workspace with a claim pad, two droppers, a conveyor and a collector that pays Cash to the owner, plus buttons that unlock the second dropper and then some walls. Save Cash between sessions.
- Can search your place first for an existing leaderstats script or tycoon parts, so it extends what you have instead of adding a second Cash stat
- Creates the server scripts in ServerScriptService and the parts in Workspace, and sets attributes such as Price and Interval on them
- Compile-checks each script it wrote inside Studio before it finishes
- Sends its changes in batches, and each batch is marked as an undo point in Studio's history
You still playtest it yourself: RoCode works in edit mode and does not press Play. If drops get stuck, describe what you saw and it can read the Output from your last playtest. How RoCode connects to Studio.
Questions
Can I make a tycoon without scripting?
Ready-made tycoon kits and templates are shared by other creators, and they let you start without writing code. They are still scripts someone else wrote, so read every script in a kit before you publish, and expect to read them anyway when you want to change prices or add droppers. Building it yourself, as in this guide, takes six short scripts.
How do I add more plots?
Select Tycoon1, duplicate it with Ctrl+D (Cmd+D on Mac), move the copy and rename it. Every script handles every Model inside Tycoons, so no code changes are needed.
How do I save what a player bought?
Save a table instead of a number, such as Cash plus a list of bought button names. When that player claims a plot, mark those buttons as bought and refresh the plot; if they claim before their data has loaded, apply it when it arrives. The DataStore tutorial covers saving tables safely.
How do I add rebirths?
Add a Rebirths IntValue to leaderstats. A rebirth button checks on the server that the player has enough Cash, sets Cash to 0, resets their plot's purchases (the same reset that runs when an owner leaves) and adds 1 to Rebirths. The collector then multiplies each drop's value, for example by 1 + 0.5 × Rebirths. Save Rebirths with Cash.
How do I add a 2x Cash game pass?
Check pass ownership on the server, as the game pass guide does: its server script sets an attribute on the player once it has confirmed they own the pass or have just bought it. In the collector, look up the plot's owner as before and double the drop's value when that attribute is true. Read it only in a server Script: a player can change the attribute on their own copy, but that change never reaches the server.
Can a LocalScript run the dropper?
No. A part made by a LocalScript exists only on that player's device. The server never sees it, so the collector never pays for it and other players never see it. Droppers, the collector and buttons all belong in server Scripts.
Sources and further reading
- Roblox Engine API Reference: BasePart. Anchored, AssemblyLinearVelocity, the deprecated Velocity and its conveyor note, Touched, CanTouch and SetNetworkOwner
- Roblox Creator Docs: Network ownership. who simulates unanchored parts, SetNetworkOwner(nil), and why client-owned parts can fake touches
- Roblox Engine API Reference: Debris. AddItem and the 1,000 item limit
- Roblox Creator Docs: Attributes. SetAttribute, GetAttribute and GetAttributeChangedSignal
- Roblox Creator Docs: Improve performance. physics cost of simulated assemblies, anchoring, and memory leaks from tables
- Roblox Creator Docs: Data stores. GetAsync, SetAsync and Studio access to API services
How the code was checked: every script on this page passes Luau's strict type checker against Roblox's API definitions (luau-lsp, 9 October 2026). A type check catches misspelt APIs and wrong types, not game logic, so playtest in Studio before you publish.