Shop Script Support: concerned for exploiters

I recently wrote a PurchaseScript for a sword shop, where players can click the purchase button, be shown a confirmationUI and then confirm the purchase or not. However, the problem is the script is a LocalScript, so gem deductions for purchases aren’t recognized by the server. The script I wrote is below;

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")
local currentEquipped = game.Players.LocalPlayer.EquippedSword

local buyButton = script.Parent
local equipButton = script.Parent.EquipButton
local clickSound = ReplicatedStorage.GameSounds.ClickSound
local hoverSound = ReplicatedStorage.GameSounds.HoverSound

local sword_to_give = script.Parent.Name
local sword = ReplicatedStorage.Swords:FindFirstChild(sword_to_give)
local gemCost = script.Parent.Price.Value

local buyButtonConnection
local yesButtonConnection
local uiElement = Players.LocalPlayer.PlayerGui:WaitForChild("User Interface"):WaitForChild("UserUI").SwordPurchaseUI

local player = Players.LocalPlayer
local swordStorage = game.ReplicatedStorage:WaitForChild("Swords")
local equippedSwordChangedEvent = game.ReplicatedStorage.RemoteFunctions:WaitForChild("EquippedSwordChangedEvent")
local swordPurchasedEvent = game.ReplicatedStorage.RemoteFunctions:WaitForChild("SwordPurchasedEvent")
local childrenNames = {}

for _, child in ipairs(swordStorage:GetChildren()) do
	childrenNames[child.Name] = true
end

local function checkAndReplaceSword()
	local backpack = player.Backpack
	for _, tool in ipairs(backpack:GetChildren()) do
		if childrenNames[tool.Name] then
			tool:Destroy()
		end
	end
end

local function changeImage()
	local imageLink = "rbxassetid://17316025753"
	local parent = script.Parent.Parent

	for _, child in ipairs(parent:GetChildren()) do
		local image = child:FindFirstChild("EquipButton")

		if (image == nil) then
			continue
		end

		image.Image = imageLink
	end
end

local function TweenUI()
	uiElement.Position = UDim2.new(0.5, 0, 1.5, 0)
	uiElement.Size = UDim2.new(0, 0, 0, 0)
	uiElement.Visible = true
	game.Lighting.Blur.Enabled = true

	local tweenInfo = TweenInfo.new(
		1,
		Enum.EasingStyle.Quad,
		Enum.EasingDirection.Out,
		0,
		false,
		0
	)

	local targetProperties = {
		Position = UDim2.new(0.275, 0,0.258, 0),
		Size = UDim2.new(0, 589,0, 387),
	}

	local tween = TweenService:Create(uiElement, tweenInfo, targetProperties)
	tween:Play()
end

script.Parent.MouseEnter:Connect(function()
	hoverSound:Play()
end)

local function buyItem()
	clickSound:Play()
	script.Parent.Parent.Parent.Visible = false

	local confirmationUI = player.PlayerGui["User Interface"].UserUI.SwordConfirmationUI
	confirmationUI.SwordName.Text = script.Parent.Name

	if confirmationUI then
		confirmationUI.Visible = true
		confirmationUI.GemsCost.Text = gemCost
		confirmationUI.SwordImage.Image = script.Parent.ImageLabel.Image

		local yesButton = confirmationUI:FindFirstChild("YesButton")
		if yesButton then
			yesButton.MouseButton1Click:Connect(function()
				player.leaderstats.Gems.Value -= gemCost
				confirmationUI.Visible = false
				equipButton.Visible = true
				-- Update UI, play sounds, etc.
				TweenUI()
			end)
		end

		local noButton = confirmationUI:FindFirstChild("NoButton")
		if noButton then
			noButton.MouseButton1Click:Connect(function()
				confirmationUI.Visible = false
				clickSound:Play()
				script.Parent.Parent.Parent.Visible = true
			end)
		end
	end
end

buyButton.MouseButton1Click:Connect(buyItem)

buyButton.MouseButton1Click:Connect(buyItem)

equipButton.MouseEnter:Connect(function()
	hoverSound:Play()
end)

equipButton.MouseButton1Click:Connect(function()
	clickSound:Play()
	if equipButton.Image == "rbxassetid://17316025753" then
		if sword_to_give then
			changeImage()
			equippedSwordChangedEvent:FireServer(sword_to_give) 
			equipButton.Image = "rbxassetid://17316048949"
		end
	elseif equipButton.Image == "rbxassetid://17316048949" then
		equipButton.Image = "rbxassetid://17316025753"    
	end
end)

:ohmy: Please ignore the script dis-organization and I understand there are alot of better ways to format this, but I’m new to scripting. Moving back onto the topic, to make sure gem deductions are recognized by the server I fire a RemoteEvent and use that to handle gem deductions.

Someone informed me that client to server events especially for gem deductions can be manipulated by exploiters, so I was wondering if anybody could offer any advice or solutions on what I should add to the script or what I should alternate a RemoteEvent with to ensure that gem deductions can’t be manipulated or invoked by exploiters? Is it possible to turn this script into a server script? Please let me know, thanks alot.

You just need to sanitize the incoming data. Ie:

Make sure the value being sent from the client is > 0, make sure they aren’t sending a value higher, than the amount they currently have (assuming your handling the gem data on the server side, if not, yikes). Make sure they aren’t sending any weird letters, etc.

Those are a few examples to reduce the chance of an exploit.

Adding onto this, you can also eliminate the price parameter entirely and replace it with the item the player wants to buy instead. Then you just check the price and if the player has enough money on the server.

I changed the server sided script that listens for the RemoteEvent being fired, it now checks the item the player wants to buy and checks the price to see if the player has enough. How would I handle the deduction then without risk, do you have any recommendations?

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local swordPurchasedEvent = ReplicatedStorage.RemoteFunctions.SwordPurchasedEvent

swordPurchasedEvent.OnServerEvent:Connect(function(player, gemCost, sword)
	local swordToBuy = sword
	if player.leaderstats.Gems.Value >= gemCost then
		-- blank space
	end
end)

This video may help:

just skip to 1:20 and watch to 5:10. It contains most of what you need, the rest of the video is good but for your concerns, you should just watch the the designated part.

1 Like

the deduction should be handled on the server, which, you do the buy/deduction logic there, then relay that info back to the client (if any).

Does the player have enough gems? is the value sent from the client valid? If so, the deduction should be fine, and proceed with said deduction. In theory, there should be relatively no way the reduction is exposed to exploitation after the client value data is sanitized.

Thanks alot, watched it over and understand it better.