How to make a "PLS DONATE!" Game Tutorial

Hey folks! Today I’m here writing my first tutorial along with TwinPlayz

Recently there’s been this popular game going around called “PLS Donate!” (created by haz3mn)
We’ve recreated it, but in a tutorial way!
Please note our intentions are not to get more copies out there, but rather get people a

Below this I will be explaining how it works.

For the server code we’ll be getting the users assets (in our case; shirts, tshirts and pants).
Ofcourse, you’ll need an API for that. We’ll be using the roproxy API.

Create a folder in ReplicatedStorage called “Events” and add a BindableEvent in there called “ClaimPlot”
Then create a server script in ServerScriptService, call it whatever you want. First you will need to define our locations, like so:

local Market = game:GetService("MarketplaceService")
local HTTPService = game:GetService("HttpService")

local BoothName = nil

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local EventsFolder = ReplicatedStorage:FindFirstChild("Events")

local Bindable = EventsFolder:FindFirstChild("ClaimPlot")

Now we’ll need to make a “GetContent” function which gets their shirts, pants and t-shirts.

First make a table, containing the subcategories (shirts, pants, t-shirts) like so;

local SubCategories = {
	"2";
	"11";
	"12"
}

We’ll be looping through these categories in our API.
Now as for the function:

	for i = 1,3 do --[ This is our loop ]
	local Contents = {} -- [ These are our items ]
	cursor = cursor or ""

Now we have that part, we’ll be adding the roproxy API.

local Url = "https://catalog.roproxy.com/v1/search/items/details?Category=3&Subcategory=".. SubCategories[i].. "&Sort=4&Limit=30&CreatorName=%s&cursor=%s"

SubCategories is our table which includes our subcategory ID, and the [i] is what we call in our loop.

local requestUrl = Url:format(username, cursor)

You’ll have to format the API link, otherwise, it’ll return nil.
Once you’ve formatted it, you’ll be getting the API data with HTTPServive:GetAsync(link, bool) just like this:

local success, result = pcall(function()
		return HTTPService:GetAsync(requestUrl, true)
	end)

We’ll be checking if it has success or not by checking if theres a result, and success.

	if success then
			if result then
				local success2, result2 = pcall(function()
					return HTTPService:JSONDecode(result)
				end)
				if not success then
					if not result then
						return GetContent(boothname, username, userid)
					end
				end
		

I’ll skip the last part since it’s self-explanatory, but here’s the finished code.

local Market = game:GetService("MarketplaceService")
local HTTPService = game:GetService("HttpService")

local BoothName = nil

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local EventsFolder = ReplicatedStorage:FindFirstChild("Events")

local Bindable = EventsFolder:FindFirstChild("ClaimPlot")

local function CloneAssets(BoothName, Contents)
	for _, player in pairs(game:GetService("Players"):GetPlayers()) do
		for _, Asset in pairs(Contents) do
			local data = Market:GetProductInfo(Asset, Enum.InfoType.Asset)
			if data and data.IsForSale then
				local Gui = script.Template:Clone()
				Gui.Parent = player.PlayerGui.SignPrices[BoothName].ScrollingFrame
				Gui.Name = table.find(Contents, Asset)
				Gui.PurchaseButton.Text = data.PriceInRobux .. "$"
				Gui.Visible = true
				Gui.ImportantValues:FindFirstChild("AssetId").Value = Asset
			end
		end
	end
	for _, Asset in ipairs(Contents) do
		local data = Market:GetProductInfo(Asset, Enum.InfoType.Asset)
		if data and data.IsForSale then
			local MainGui = script.Template:Clone()
			MainGui.Parent = game:GetService("StarterGui").SignPrices[BoothName].ScrollingFrame
			MainGui.Name = table.find(Contents, Asset)
			MainGui.PurchaseButton.Text = data.PriceInRobux .. "$"
			MainGui.Visible = true
			MainGui.ImportantValues:FindFirstChild("AssetId").Value = Asset
		end
	end
end


local SubCategories = {
	"2";
	"11";
	"12"
}

local function GetContent(boothname, username, userid, tshirts, cursor)
	for i = 1,3 do
	local Contents = {}
	cursor = cursor or ""
		
	local Url = "https://catalog.roproxy.com/v1/search/items/details?Category=3&Subcategory=".. SubCategories[i].. "&Sort=4&Limit=30&CreatorName=%s&cursor=%s"
	local requestUrl = Url:format(username, cursor)

	local success, result = pcall(function()
		return HTTPService:GetAsync(requestUrl, true)
	end)
		
		if success then
			if result then
				local success2, result2 = pcall(function()
					return HTTPService:JSONDecode(result)
				end)
		
		BoothName = boothname
		local suc, er = pcall(function()
		for _, tshirt in ipairs(result2.data) do
			table.insert(Contents, tshirt.id)
				end
			end)
		if not suc then
			warn(er)
	end
				return BoothName, Contents
			end
		end
	end
end



Bindable.Event:Connect(function(boothname, username, userid)
	local Boothname, Contents = GetContent(boothname, username, userid)
	if Contents then
		CloneAssets(BoothName, Contents)
	end
end)

Now for the booth, take this template

Ungroup the Workspace model, make sure it’s parented to workspace.
And put the UI folder inside of StarterGui.

Now for the main part like data, particles and purchases;

Here’s the code for the DataStore2 data:

-- Services --

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

-- Module --

local Datastore2 = require(ReplicatedStorage.Modules.MainModule)
Datastore2.Combine("DATA", "Donated", "Raised") -- This is like the data. 'DATA' is your MASTER KEY!

-- Main --

Players.PlayerAdded:Connect(function(plr)
	local DonatedStore = Datastore2("Donated", plr) 
	local RaisedStore = Datastore2("Raised", plr)

	local Leaderstats = Instance.new("Folder") -- Create leaderstats obv
	Leaderstats.Name = 'leaderstats'

	local Donated = Instance.new("IntValue", Leaderstats)
	Donated.Value = DonatedStore:Get(0) -- Get is basically adding the argumented amount to your saved data
	Donated.Name = "Donated"

	local Raised = Instance.new("IntValue", Leaderstats)
	Raised.Name = "Raised"
	Raised.Value = RaisedStore:Get(0) -- Same thing as above


	DonatedStore:OnUpdate(function(NewDonated)
		Donated.Value = NewDonated
	end)

	RaisedStore:OnUpdate(function(NewRaised) -- Same as above
		Raised.Value = NewRaised
	end)

	Leaderstats.Parent = plr -- Set leaderstats parent AFTER assigning everything

end)

There isn’t much to explain to that tbh.

Now for the particles and purchases handling:

local MarketplaceService = game:GetService("MarketplaceService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local DataStore2 = require(ReplicatedStorage.Modules.MainModule) -- DataStore 2 module
local Players = game:GetService("Players")

Define our locations
DataStore2

You’ll need to check for the .PromptPurchaseFinished event on MarketplaceService.

MarketplaceService.PromptPurchaseFinished:Connect(function(player, assetId, isPurchased)

Then check if the player purchased the item by doing

if isPurchased then```

Now the last part is just solely getting the asset data and setting the players stats:
		local data = MarketplaceService:GetProductInfo(assetId, Enum.InfoType.Asset)

		if data then
			player.leaderstats.Donated.Value += data.PriceInRobux
			local DonatedValue = player.leaderstats.Donated.Value
			local DonatedStore = DataStore2("Donated", player) 
			DonatedStore:Increment(data.PriceInRobux,DonatedValue)

			local GotDonatedName = data.Creator.Name
			local GotDonatedPlr = game:GetService("Players")[GotDonatedName]
			GotDonatedPlr.leaderstats.Raised.Value += data.PriceInRobux

			local RaisedValue = GotDonatedPlr.leaderstats.Raised.Value
			local RaisedStore = DataStore2("Raised", GotDonatedPlr)

			RaisedStore:Increment(data.PriceInRobux,RaisedValue)

			local Stands = workspace:FindFirstChild("Stands")
			local StandDescendants = Stands:GetDescendants()
			--local Owner

			for _, Object in ipairs(StandDescendants) do
				
				--if Object:IsA("StringValue") and Object.Name == "Owner" then
				--	Owner = Object.Value 
				--end
				
				--if Owner == player.Name then
				if Object:IsA("TextLabel") and Object.Name == "MoneyRaised" and Object.Parent.Parent.Parent.Parent.Proximity.Owner.Value == GotDonatedName then
						local Amount = RaisedStore:Get()
						print(Amount)
						Object.Text = Amount .. "$ Raised"
				elseif Object:IsA("ParticleEmitter") and Object.Name == "Money" and Object.Parent.Parent.Proximity.Owner.Value == GotDonatedName then
						Object:Emit(15)
						local Sound = Object.Parent:FindFirstChildOfClass("Sound") 
						Sound:Play()
					end
				end
				
			--end

		end
		
	end
end)

Our final code:

local MarketplaceService = game:GetService("MarketplaceService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local DataStore2 = require(ReplicatedStorage.Modules.MainModule)
local Players = game:GetService("Players")

MarketplaceService.PromptPurchaseFinished:Connect(function(player, assetId, isPurchased)
	if isPurchased then   
		
		local data = MarketplaceService:GetProductInfo(assetId, Enum.InfoType.Asset)

		if data then
			player.leaderstats.Donated.Value += data.PriceInRobux
			local DonatedValue = player.leaderstats.Donated.Value
			local DonatedStore = DataStore2("Donated", player) 
			DonatedStore:Increment(data.PriceInRobux,DonatedValue)

			local GotDonatedName = data.Creator.Name
			local GotDonatedPlr = game:GetService("Players")[GotDonatedName]
			GotDonatedPlr.leaderstats.Raised.Value += data.PriceInRobux

			local RaisedValue = GotDonatedPlr.leaderstats.Raised.Value
			local RaisedStore = DataStore2("Raised", GotDonatedPlr)

			RaisedStore:Increment(data.PriceInRobux,RaisedValue)

			local Stands = workspace:FindFirstChild("Stands")
			local StandDescendants = Stands:GetDescendants()
			--local Owner

			for _, Object in ipairs(StandDescendants) do
				
				--if Object:IsA("StringValue") and Object.Name == "Owner" then
				--	Owner = Object.Value 
				--end
				
				--if Owner == player.Name then
				if Object:IsA("TextLabel") and Object.Name == "MoneyRaised" and Object.Parent.Parent.Parent.Parent.Proximity.Owner.Value == GotDonatedName then
						local Amount = RaisedStore:Get()
						print(Amount)
						Object.Text = Amount .. "$ Raised"
				elseif Object:IsA("ParticleEmitter") and Object.Name == "Money" and Object.Parent.Parent.Proximity.Owner.Value == GotDonatedName then
						Object:Emit(15)
						local Sound = Object.Parent:FindFirstChildOfClass("Sound") 
						Sound:Play()
					end
				end
				
			--end

		end	
	end
end)

The other code is included in the kit, which is explained in TwinPlayz’s video!

Apologies for the short explanation in this tutorial, it’s a lot of code and there’s more to say than to write.

Thank you TwinPlayz for coding and starting on this project, thank you to sharxkzz for the building!

The code might go out of the brackets, no idea why that issue is occuring but enjoy.

102 Likes

stop
dont make the original game deal with more copy n pasted games

50 Likes

I understand that it’s an original idea but our goal wasn’t for more copies to start existing. Our goal was to teach users how to work with APIs, format code and work with Remote Events.

We didn’t randomly pick this game either, it was picked by the community; there was a bunch of requests for it everyday.

I’m completely behind teaching others to code, and our intentions are never to make more copies exist of a specific game.

Besides, AlvinBlox has done the same with Piggy, had near to no backlash on it either.

But once again, I’m deeply sorry if people see this as a way for others to grab it and republish it like that.

27 Likes

I can see that your aims were too teach people how to use the methods you used in the tutorial. But the sad truth is that most of the people who will be clicking on this will be those who want to copy the game.
Great, informative tutorial though!

15 Likes

I think you shouldn’t have an uncopylocked game kit because people won’t read the tutorial and will just copy the game.

16 Likes

Yeah, we’ve talked about that before but sadly that’s just how the Roblox community moves.

3 Likes

You’re just teaching people how to be unoriginal, and even worse, you’re giving it to them open sourced. I’m sorry, but people should be original. You don’t know how many “how to make game like pls donate ?!?!?!” posts I’ve seen.

10 Likes

Hey @solvuntur, i think this post is a good initiative to teach others how to use httpservice and other roblox functions. But, I think you should make an informative section about the reason of using a proxy and not roblox domain, the positive and negative points as well as sharing a solution that can help to create its own proxy for more “security”. The only other negative point I see in this project, is that there will be some people using this post to make simple copies to try to make robux without doing anything. But this argument can be countered by the fact that it will help others to learn and understand the functions of roblox. Anyways, have a good day.

6 Likes

(post deleted by author)

62 Likes

LOL, will pass it through to twin.

5 Likes

We were thinking of using ProfileService before this.

Yes, the RoProxy is not really the best most secretive place and very unreliable. It was just hard to depict wither to stay with a free site and use it for the tutorial than going the hard paid way. This was all just for fun, and a little informational. In the future tutorials yes, I plan on teaching them about how these websites are not to be used frequently as of right now its already being overloaded and crashing. Another infomational source about this.

1 Like

It’s a basic tutorial, you could use anything. Datastore, Profile Service, etc.

1 Like

I’ve removed the open source link so people can figure the rest out themselves. Once again, my sincere apologies.

I’ll have them watch the video instead lol.

2 Likes

market service is just something here to donate

you should let other devs create their own system

it’s not an original game since I’ve made this before lol

(sorry if off topic)

2 Likes

no thanks, pls donate games are not games, they are just pathetic. don’t encourage people to make anymore of these games

5 Likes

thats bc alvinblox taught people how to make a game, not a greedy effortless cash grab.

2 Likes

and wheres the cash we’re getting here? nowhere.

1 Like

im not saying this tutorial is a cash grab, im saying the tutorial is essentially how to make an effortless cash grab.