Easy Tool Shop with Tool Saving
About
This module is really easy to set up and it’s very useful for making Tool Shop with tool saving!
Tools are loaded by ToolID NumberValue that can be stored in (almost) every datastore.
If you get the Model or Install Place you will get simple DataStore with it. You can simply delete the datastore script if you already have one and just add ToolID NumberValue with Folder to your own!
Benefits
- Built in AdminGui for giving tools
- Very easy to set up!
- Can be used with (almost) every datastore only thing you need to do is create Folder for saving ToolD NumberValue and create NumberValue with name ToolID inside your DataStore
- Support for ClickDetectors!
Documentation/How to set up
- Get Model/Uncopylocked place/Install place below
- If you get model then ungroup models to Services by their model names
- Set up ToolModule whatever you like by notes that are literally everywhere to help you with setting up everything
- You are done! (Maybe make your own gui for Shop/Admin Gui because mine looks horrible)
ModuleScript (ToolModule)/ Settings
local module = {
Settings = { --Table with information about value and folder names
["ToolStorageFolder"] = {Name = "SkinStorage", Service = "ServerStorage"}, --First parameter 'Name' is Name of the Folder where we are storing tools, second parameter is Service where is the Folder for storing tool located Warning: Folder needs to be direct child of the Service (Recommended service to store tools is ServerStorage becase Exploiter can't access it)
["AdminGui"] = {Name = "AdminGui", Service = "ServerStorage"}, --First parameter is 'Name' of the gui for Admin's and second parameter is Service if you don't want to exploiter's clone Admin Gui for themselves then don't change the location!
["CloneInformation"] = true, --Optional if we want to access to tables with Client (set to false if you dont want this ModuleScript (ToolModule) to be cloned inside ReplicatedStorage)
["CanBuyPrevious"] = true, --If its on true then Player can buy previous tools or whatever tools Player wants, if its on false then Player can't buy anything else instead of Current ToolID + 1 (Next Tool)
["ClickDetectorMethod"] = true, --Optional if you want to be able to buy Tools with ClickDetectors (set to false if you dont want to use ClickDetectors)
Currencies = {
["Currency1"] = {Currency = "Cash", Folder = "leaderstats"}, --First Parameter is Currency Name that you want to buy tools for, second parameter is Folder Name where is the Currency located (parented) leaderstats etc.
["Currency2"] = {Currency = "Gems", Folder = "leaderstats"},
-->> ˇ Tool Data Values ˇ <<--
["ToolData"] = {Currency = "ToolSkin", ToolFolder = "StatFolder"}, --First Parameter is Name of Tool Storage Number Value and second parameter is Folder Name where is Tool Storage Number located (parented) StatFolder etc.
["ToolData2"] = {Currency = "BackpackTool", ToolFolder = "StatFolder"},
-->> ^ Tool Data Values ^ <<--
},
AdminCommands = { --Here you can customize if you want certain commands for Admins to work!
["GiveTools"] = true, --If it's on true it will work if it's on false it won't work, this command allows Admins to give Tools to anybody!
["RemoveData"] = true, --Note: Action made by this command can not be undone! - This will work only with normal DataStores at the moment!!! This command will remove all specified Player's Data (Not Data from all Players but only specified player!)
},
Admins = { --Table with information about Admins, be careful when you put here someone, Admins are able to change their ToolID value to whatever they want
[994223339] = {Name = "caviarbro"}, --Copy UserId of the player that should be Admin here, Name is optional you can leave it blank "", but it helps to indicate who is who (the name parameter is not used in any scripts so it can be the admin nickname or whatever)
},
DataStoreAdmin = { --This will work only with normal DataStores (at the moment)
["PlayerKey"] = "Id_", --Unique key for storing player's data this needs to be changed if you want certain admin function(s) to work, Note: Put there only string not whole Unique key with Player UserId (bad example: "Id_"..player.UserId) <<-- this is wrong you only want the string so it will be only "Id_" !!! (If you are using my Datastore then You can find this Key inside script called 'DataStore' on line 23)
["DataStoreName"] = "SkinStore", --Name of your DataStore (If you are using my version then you can find this information in script called 'DataStore' on line 1)
},
},
ClickParts = { --Table with ClickParts, remove everything from this table if you dont want to use ClickDetector buying method
["ClickPart"] = {ToolID = 3, Callback = true, DefaultText = "Buy Tool -With Callback", WaitTime = 2.5, TextLabelName = "TextLabel"}, --If Callback is true then the message will change to the return message of the Buy function, if you want Callback then set it to true if not then set it to false, if we have callback set to true then we will define DefaultText as the original Text that you want on the part, third parameter WaitTime if we have callback to true then we want to set time before it changes back to the DefaultText that we defined before WaitTime and the last parameter is the name of the TextLabel if we have callback set to true
["ClickPart2"] = {ToolID = 1, Callback = false, DefaultText = "", WaitTime = 0, TextLabelName = ""}, --Example of ClickPart without Callback Message
["ClickPart3"] = {ToolID = 2, Callback = true, DefaultText = "Buy Tool 3", WaitTime = 1, TextLabelName = "LabelText"}, --Example of ClickPart with Callback Message
},
TableWithTools = { --Table with informations about tools
["BasicTool"] = {ToolID = 0,Cost = 0, CurrencyName = "Cash", Type = "ToolSkin"}, --Default tool
["AdvancedTool"] = {ToolID = 1,Cost = 5, CurrencyName = "Gems", Type = "BackpackTool"}, --First argument ["AdvancedTool"] is a name of the tool, second the ToolID is the ID that will be indicating what to clone to player Backpack and the third parameter is Cost of the Tool
["AmazingTool"] = {ToolID = 2,Cost = 100, CurrencyName = "Cash", Type = "ToolSkin"},
["UltraTool"] = {ToolID = 3,Cost = 500, CurrencyName = "Cash", Type = "BackpackTool"},
}
}
--[[
⚠️ Code below is explaining how everything works so feel free to read it
but if you dont know what you are doing don't touch it!!!
]]
local TableWithTools = module["TableWithTools"] --Table with information about tools
local ToolModuleSettings = module["Settings"] --Table with Settings of whole Module
local ToolModuleCurrencies = ToolModuleSettings["Currencies"] --Table with information about Currencies that are used to buy tools for
function module:GetToolType(ToolID) --Getting Tool Type by ToolID this is useful for indicating what type is the tool
for i,v in pairs(TableWithTools) do --Loops through table
if v["ToolID"] == ToolID then --If we find a match then we will run the code below
return v["Type"] --If match was found then we return Type of the tool
end
end
return warn("Tool with Tool ID "..tostring(ToolID).." does not found!") --If match was not found then we return warning!
end
function module:StringConverter(args)
return string.gsub(args, '^%s*(.-)%s*$', '%1') --return string without unwanted spaces before and after args
end
function module:GetCurrencyName(player, ToolID) --function that will give us the currency
for i,v in pairs(TableWithTools) do --loop through table with tools
if module:StringConverter(v["ToolID"]) == module:StringConverter(ToolID) then --if find a match then it will run the code below
for index,toolcurrency in pairs(ToolModuleCurrencies) do --loops through table with currencies
if toolcurrency["Currency"] == v["CurrencyName"] then --if find a match then it will return currency
return player:FindFirstChild(toolcurrency["Folder"]):FindFirstChild(toolcurrency["Currency"]) --This line will return currency, line below this won't run
end
end
end
end
return warn("Tool ID does not exist or Currency is not defined/found") --Uh Oh, something went wrong (Probably ToolID Does not exist or Currency does not exist inside ToolModule (ModuleScript))
end
function module:GetToolDataName(player, ToolID) --This is useful for getting ToolData folder where we have our Tool Stored
for i,v in pairs(TableWithTools) do --loop through table with tools
if module:StringConverter(v["ToolID"]) == module:StringConverter(ToolID) then --If we find a match in ToolIds then we will continue with the code below
for index,ToolData in pairs(ToolModuleCurrencies) do --loops through table with currencies (also with tool data folders)
if ToolData["Currency"] == v["Type"] then --If ToolData (Type) Value is the same as the Type Value then we will run the code below
return player:FindFirstChild(ToolData["ToolFolder"]):FindFirstChild(ToolData["Currency"]) --If we found a match then we will return ToolData Type
end
end
end
end
end
function module:GetAllToolFolders(player) --This is useful for getting all folders that is used for Storing Tools!
local ToolFolderTable = {} --Blank table where we going to store our ToolData folders
for i,v in pairs(ToolModuleCurrencies) do --loop through table with Currencies
if v["ToolFolder"] then --If we find ToolFolder then we going to run the code belowe
table.insert(ToolFolderTable,player:FindFirstChild(v["ToolFolder"]):FindFirstChild(v["Currency"])) --We are inserting the ToolData Type to the blank Table now with this ToolData Type
end
end
return ToolFolderTable --Loop done we are returning ToolFolderTable
end
return module
ToolHandler/Shop Handler Script
--[[
-->> Easy Tool Shop with Tool Saving <<--
Creator: caviarbro
Discord: Caviarbro#1511
DevForum Post: https://devforum.roblox.com/t/easy-tool-shop-with-tool-saving/1160796
Last Update: 13/04/v1b
-->> Easy Tool Shop with Tool Saving <<--
⚠️ Code below is explaining how everything works so feel free to read it
but if you dont know what you are doing don't touch it!!!
Changing Tools only works with Admin Function AddNewTool
if tool is added manually then most likely expect unwanted behaviours!
Removing Player's data is currently in Beta Testing and this function can not be undone! ⚠️
Note(s): Everything you need is inside "ToolModule" (ModuleScript) with notes how to change it properly!
]]
local ReplicatedStorage = game:GetService("ReplicatedStorage") --Getting Service called ReplicatedStorage where we have RemoteFunction
local ServerStorage = game:GetService("ServerStorage") --Getting Service called ServerStorage where we have Stored our tools
local RunService = game:GetService("RunService") --Getting Service called RunService for loops
local Players = game:GetService("Players") --Getting Player Service for things such as getting UserId's etc.
local Workspace = game:GetService("Workspace") --Getting Service called Workspace
local RemoteFunction = ReplicatedStorage:WaitForChild("RemoteFunction") --Remote Function to run when player wants to buy Tool
local ToolModule = require(script.Parent) --Module with table that contains informations about tools
local TableWithTools = ToolModule["TableWithTools"] --Table with information about tools
local ToolModuleSettings = ToolModule["Settings"] --Table with Settings of whole Module
local ToolModuleCurrencies = ToolModuleSettings["Currencies"] --Table with information about Currencies that are used to buy tools for
local ToolModuleAdmins = ToolModuleSettings["Admins"] --Table with information about Admin(s) UserId(s)
local ToolModuleDataStore = ToolModuleSettings["DataStoreAdmin"] --Table with information about your DataStore
local ToolModuleCommands = ToolModuleSettings["AdminCommands"] --Table where you can customize your commands to be able to use or not!
local ToolStorage = game:GetService(ToolModuleSettings["ToolStorageFolder"]["Service"]):WaitForChild(ToolModuleSettings["ToolStorageFolder"]["Name"]) --Getting place where we have stored our Tools
-->> PlayerAdded function <<--
game.Players.PlayerAdded:Connect(function(player) --Player Added function
local TimeCounter = os.clock() + 2 --This variable is there to prevent this function to run before player loads, os.clock() is time now in seconds and the second parameter is number in seconds
local co = coroutine.create(function() --there we are creating new thread that we will be resuming after TimeCounter will be done
local ToolFolders = ToolModule:GetAllToolFolders(player) --Calling function to get All Folders with Tools in table
if ToolModuleAdmins[player.UserId] then --if we find player userid inside of Admins in ToolModule (ModuleScript) then we will clone the Gui to player's PlayerGui
local AdminGuiClone = game:GetService(ToolModuleSettings["AdminGui"]["Service"]):FindFirstChild(ToolModuleSettings["AdminGui"]["Name"]):Clone() --Getting clone of the gui
if AdminGuiClone then --if gui exist then we will run the code below
AdminGuiClone.Parent = player.PlayerGui --We are parenting the Admin Guí clone to admin's Player Gui
end
end
for index,ToolIndex in pairs(ToolFolders) do
for i,v in pairs(TableWithTools) do --looping through table to find the ToolSkin ID that player has
if ToolIndex.Value == v["ToolID"] then --if ToolID from the table is same as the ToolSkin Value then we will be cloning the Tool
local Tool = ToolStorage:FindFirstChild(i) --Variable of tool to index if the tool exists or not
if Tool then --Checking if the tool exist in Folder with tools
local CloneTool = Tool:Clone() --Variable that will be cloning the tool, the i is index in the table so basically name of the tool for example ["BasicTool"], ["AdvancedTool"] etc.
CloneTool.Parent = player.StarterGear --Setting the parent to StarterGear so it will appear also in player's Backpack
else --If Tool does not exist then we will warn the server that Tool does not exist
return warn("Tool with Name "..tostring(i).." does not exist in place where should be stored!") --Warning that can help us in debugging code if something unexpected happen!
end
end
end
end
return print("Tool(s) were loaded!") --If tools were loaded remotely then we get return print with this message.
end)
if ToolModuleSettings["CloneInformation"] == true then --Optional if we want to access to Information from ToolModule (ModuleScript) with Client
local ModuleClone = script.Parent:Clone() --Creating Clone of ToolModule (ModuleScript)
ModuleClone:FindFirstChildOfClass("Script"):Destroy() --Destroying the ToolHandler before parenting the Module to ReplicatedStorage to prevent unwanted things
ModuleClone.Parent = ReplicatedStorage --Parenting Clone of the ModuleScript to ReplicatedStorage
end
local Connection --Connection to disconnect Heartbeat even so it won't cause future problems (lags,etc.)
Connection = RunService.Heartbeat:Connect(function() --loop to indicate when the TimeCounter will be done
if os.clock() >= TimeCounter then --if os.clock() (current time) is higher than TimeCounter then we will be resuming the thread
coroutine.resume(co) --We are resuming (coroutine) thread with name "co" so the function inside will run now
Connection:Disconnect() --Disconnecting Connection so the Heartbeat event won't run again
end
end)
end)
-->> PlayerAdded function <<--
-->> Normal function(s) <<--
local function DestroyTool(player,ToolID) --function to destroy tool
for index,tool in pairs(TableWithTools) do --looping through table to find the ToolSkin ID that we want to destroy
if tool["ToolID"] == ToolID then --If the tool inside the table has same ID that we want to destroy then it will run the code below
if player.Backpack:FindFirstChild(index) then --We are finding through options where the tool can be located and if the tool is found then it will be destroyed!
player.Backpack:FindFirstChild(index):Destroy() --Destroying the tool
end
if player.StarterGear:FindFirstChild(index) then --We are finding through options where the tool can be located and if the tool is found then it will be destroyed!
player.StarterGear:FindFirstChild(index):Destroy() --Destroying the tool
end
if player.Character:FindFirstChild(index) then --We are finding through options where the tool can be located and if the tool is found then it will be destroyed!
player.Character:FindFirstChild(index):Destroy() --Destroying the tool
end
return "Destroyed" --Return message (callback) if we want to indicate if the function ended
end
end
return warn("Tool with ID "..tostring(ToolID).." does not found!") --If the tool is not found that we will warn the player
end
local function GetNewTool(player,ToolID) --Function that we will be calling when we want to buy new Tool first parameter is automatic player Instance and the second argument is ToolID that we want to buy
local Currency = ToolModule:GetCurrencyName(player, ToolID) --We are calling function to find Currency Name that we defined inside of the tool
local ToolVal = ToolModule:GetToolDataName(player,tonumber(ToolID)) --Number value that is storing ToolID
for i,v in pairs(TableWithTools) do --loop through table to find the ToolSkin ID that player want to buy
if ToolModule:StringConverter(v["ToolID"]) == ToolModule:StringConverter(ToolID) then --If the tool inside the table has same ID as the Tool we want to buy then it will run the code below if the Tool with this ToolID does not exist it will return warn, the v["ToolID"] and ToolID is wrapped to function that will remove unwanted spaces before and after the ToolID and in same time keep spaces between the ToolID so for example if we invoke server with ToolID '2 2' (22) it will be only 2 not 22 but if we invoke server with ToolID ' 22 ' it will still be 22
if ToolStorage:FindFirstChild(i) then --If the Tool exist inside place where we are storing our Tools then it will run the code below if the Tool does not exist it will return warn
if ToolVal.Value ~= tonumber(ToolID) and ToolModuleSettings["CanBuyPrevious"] == true or ToolVal.Value ~= tonumber(ToolID) and ToolModuleSettings["CanBuyPrevious"] == false and ToolVal.Value == (tonumber(ToolID) - 1) and ToolVal.Value < tonumber(ToolID) then --We are checking if player does not already owns the tool or if the CanBuyPrevious is false then player can buy only the next tool! You can change this option inside of the ToolModule (ModuleScript) in settings
if Currency.Value >= v["Cost"] then --Checking if the player Currency Value is higher than the cost of the tool that we have defined inside the table
if ToolModule:GetToolType(ToolVal.Value) == ToolModule:GetToolType(ToolID) then --If Tool Value is the same type as the ToolID then it will run the code below with tool destroy
local DestroyMessage = DestroyTool(player,ToolVal.Value) --Function that will destroy the current Tool
if DestroyMessage == "Destroyed" then --If the function return this message then we will run the code below this is to prevent "tool duplicating"
Currency.Value -= v["Cost"] --Removing Cost of the tool from player balance (Note: You can use Currency.Value = Currency.Value - v["Cost"] or just Currency.Value -= v["Cost"], the effect will be same)
local ToolClone = ToolStorage:FindFirstChild(i):Clone() --Variable that will be cloning the tool, the i is index in the table so basically name of the tool for example ["BasicTool"], ["AdvancedTool"] etc.
ToolVal.Value = v["ToolID"] --SettingValue of the ToolSkin to ID of the new tool
ToolClone.Parent = player.Backpack --Setting parent to StarterGear
return print("Successfully bought Tool with name "..tostring(i).."!"), "Successfully bought Tool with name "..tostring(i).." for "..v["Cost"].." "..tostring(Currency.Name).."!" --If player receive the tool then we will return print and this message (Note: Code below this won't run if the player receive this if not then the player will receive warn that the Tool does not exist!)
end
else --If Tool Value is not the same type as the ToolID then it will run the code below without destroying the tool
Currency.Value -= v["Cost"] --Removing Cost of the tool from player balance (Note: You can use Currency.Value = Currency.Value - v["Cost"] or just Currency.Value -= v["Cost"], the effect will be same)
local ToolClone = ToolStorage:FindFirstChild(i):Clone() --Variable that will be cloning the tool, the i is index in the table so basically name of the tool for example ["BasicTool"], ["AdvancedTool"] etc.
ToolVal.Value = v["ToolID"] --SettingValue of the ToolSkin to ID of the new tool
ToolClone.Parent = player.Backpack --Setting parent to StarterGear
return print("Successfully bought Tool with name "..tostring(i).."!"), "Successfully bought Tool with name "..tostring(i).." for "..v["Cost"].." "..tostring(Currency.Name).."!" --If player receive the tool then we will return print and this message (Note: Code below this won't run if the player receive this if not then the player will receive warn that the Tool does not exist!)
end
else
return warn(player.Name.." does not have enough "..tostring(Currency.Name).." to buy "..tostring(i).." for "..tostring(v["Cost"]).." "..tostring(Currency.Name).."!"),player.Name.." does not have enough "..tostring(Currency.Name).." to buy "..tostring(i).." for "..tostring(v["Cost"]).." "..tostring(Currency.Name).."!" --If player does not have enough Cash then it will return warn
end
else
return warn(player.Name.." already owns "..tostring(i).." or Tool with ID "..tostring(ToolID).." has higher or lower ID than "..player.Name.." has!"), player.Name.." already owns "..tostring(i).." or Tool with ID "..tostring(ToolID).." has higher or lower ID than "..player.Name.." has!" --If player already has the tool it will return this message so the player won't be able to buy the same tool again
end
else
return warn("Tool with name "..tostring(i).." does not exist in the place where the tool should be stored!"), "Tool with name "..tostring(i).." does not exist in the place where the tool should be stored!" --Return warn if the tool does not exist in the folder with Tools
end
end
end
return warn("Tool with ID "..tostring(ToolID).." does not found!"), "Tool with ID "..tostring(ToolID).." does not found!" --If the tool is not found that we will warn the player (this will help us with debugging the code if something like this happen)
end
-->> Normal function(s) <<--
-->> Admin Function(s) <<--
local function AddNewTool(player,ToolID,ToPlayer)
if ToolModuleCommands["GiveTools"] == true then --If Command is enabled in ToolModule (ModuleScript) then we will run the code below
if ToolID ~= nil and ToolID ~= "" then --We are checking if the ToolID that the function is getting is existing
local PlayerToAdd --Variable for player that should receive new Tool.
if ToPlayer ~= nil and game.Players:FindFirstChild(tostring(ToPlayer)) then --Check if the ToPlayer is not nil and if Player Exist, if ToPlayer is nil then it will set PlayerToAdd variable to player argument that is automatically sent from Remote Function invoke as a first argument
PlayerToAdd = game.Players:FindFirstChild(tostring(ToPlayer)) --If ToPlayer is not nil then we will set the variable of the PlayerToAdd that should receive new tool to ToPlayer argument from Remote Function Invoke
else
PlayerToAdd = game.Players:FindFirstChild(tostring(player)) --If ToPlayer is nil then we will set the variable of the PlayerToAdd that should receive new tool to player argument from Remote Function Invoke
end
local ToolVal = ToolModule:GetToolDataName(PlayerToAdd,tonumber(ToolID)) --Number value that is storing ToolID
if ToolModuleAdmins[player.UserId] then --We are checking if the UserId is inside of Admins table in ToolModule (ModuleScript)
for i,v in pairs(TableWithTools) do --loop through table to find the ToolSkin ID that player want to buy
if ToolModule:StringConverter(v["ToolID"]) == ToolModule:StringConverter(ToolID) then --If the tool inside the table has same ID as the Tool we want to buy then it will run the code below if the Tool with this ToolID does not exist it will return warn, the v["ToolID"] and ToolID is wrapped to function that will remove unwanted spaces before and after the ToolID and in same time keep spaces between the ToolID so for example if we invoke server with ToolID '2 2' (22) it will be only 2 not 22 but if we invoke server with ToolID ' 22 ' it will still be 22
if ToolStorage:FindFirstChild(i) then --If the Tool exist inside place where we are storing our Tools then it will run the code below if the Tool does not exist it will return warn
if ToolVal.Value ~= tonumber(ToolID) then --We are checking if player does not already owns the tool or if the CanBuyPrevious is false then player can buy only the next tool! You can change this option inside of the ToolModule (ModuleScript) in settings
if ToolModule:GetToolType(ToolVal.Value) == ToolModule:GetToolType(tonumber(ToolID)) then --If Tool Value is the same type as the ToolID then it will run the code below with tool destroy
local DestroyMessage = DestroyTool(PlayerToAdd,ToolVal.Value) --Function that will destroy the current Tool
if DestroyMessage == "Destroyed" then --If the function return this message then we will run the code below this is to prevent "tool duplicating"
local ToolClone = ToolStorage:FindFirstChild(i):Clone() --Variable that will be cloning the tool, the i is index in the table so basically name of the tool for example ["BasicTool"], ["AdvancedTool"] etc.
ToolVal.Value = v["ToolID"] --Setting Value of the ToolSkin to ID of the new tool
ToolClone.Parent = PlayerToAdd.Backpack --Setting parent to StarterGear
return print("Successfully changed Tool of Player "..tostring(PlayerToAdd.Name).." to Tool with name "..tostring(i).."!"), "Successfully changed Tool of Player "..tostring(PlayerToAdd.Name).." to Tool with name "..tostring(i).."!" --If player receive the tool then we will return print and this message (Note: Code below this won't run if the player receive this if not then the player will receive warn that the Tool does not exist!)
end
else --If Tool Value is not the same type as the ToolID then it will run the code below without destroying the tool
local ToolClone = ToolStorage:FindFirstChild(i):Clone() --Variable that will be cloning the tool, the i is index in the table so basically name of the tool for example ["BasicTool"], ["AdvancedTool"] etc.
ToolVal.Value = v["ToolID"] --Setting Value of the ToolSkin to ID of the new tool
ToolClone.Parent = PlayerToAdd.Backpack --Setting parent to StarterGear
return print("Successfully changed Tool of Player "..tostring(PlayerToAdd.Name).." to Tool with name "..tostring(i).."!"), "Successfully changed Tool of Player "..tostring(PlayerToAdd.Name).." to Tool with name "..tostring(i).."!" --If player receive the tool then we will return print and this message (Note: Code below this won't run if the player receive this if not then the player will receive warn that the Tool does not exist!)
end
else
return warn(PlayerToAdd.Name.." already owns "..tostring(i).."!"), PlayerToAdd.Name.." already owns "..tostring(i).."!" --If player already has the tool it will return this message so the player won't be able to get the same tool again
end
else
return warn("Tool with name "..tostring(i).." does not exist in the place where the tool should be stored!"), "Tool with name "..tostring(i).." does not exist in the place where the tool should be stored!" --Return warn if the tool does not exist in the folder with Tools
end
end
end
return warn("Tool with ID "..tostring(ToolID).." does not found!"), "Tool with ID "..tostring(ToolID).." does not found!" --If the tool is not found that we will warn the player (this will help us with debugging the code if something like this happen)
else
return warn("Player with name "..player.Name.." is not admin!"),"Player with name "..player.Name.." is not admin!" --If the player is not admin then it will return warn.
end
else
return warn("Invalid arguments Tool ID does not exist (nil)!)"), "Invalid arguments Tool ID does not exist (nil)!" --If ToolID does not exist then we will return warn.
end
else
return warn("This command is disabled if you want this command to work consider change it inside ToolModule (ModuleScript)"), "This command is disabled if you want this command to work consider change it inside ToolModule (ModuleScript)" --Return warn if command is disabled inside of ToolModule.
end
end
local function RemoveData(player, PlayerToRemove) --Remove Player's data very sensitive function if is this command executed changes can not be undone!
if ToolModuleCommands["RemoveData"] == true then --If command is enabled then it will run the code below
if ToolModuleAdmins[player.UserId] then --If player who executed this command is admin then it will run the code below
local Key
local suc, err = pcall(function() --We are wrapping this function to pcall to prevent possible unwanted behaviours!
local PlayerUserId = Players:GetUserIdFromNameAsync(tostring(PlayerToRemove)) --Getting UserId from PlayerToRemove Name variable, so we can add it into unique PlayerKey
if PlayerUserId ~= nil then --If Player UserId/Name exist then we will run the code below
Key = tostring(ToolModuleDataStore["PlayerKey"]..PlayerUserId) --Unique Player Key for Player that we want to Remove Data From
local DS = game:GetService("DataStoreService"):GetDataStore(tostring(ToolModuleDataStore["DataStoreName"])) --Getting DataStore
if DS ~= nil then --If DataStore exist then we will Remove Player's Data
DS:RemoveAsync(Key) --Now we run RemoveAsync to remove player's data
local ToFind = Players:FindFirstChild(tostring(PlayerToRemove)) --If player is in (your) game then it will run the code below
if ToFind then --If player Exist then
local Character = ToFind.Character --Character of the player that we want to remove Data from
local Humanoid = Character:FindFirstChildOfClass("Humanoid") --If Character finds Children with class Humanoid then we will run the code below
if Humanoid then --If Humanoid exists then we will run the code below
Humanoid:UnequipTools() --If Humanoid was found then we will unequip all tools
else --If Humanoid does not exist then we will return warn
return warn("Humanoid does not exist anymore!") --This line returns warning determining that player's character does not exist anymore.
end
for i,v in pairs(player.Backpack:GetChildren() or player.StarterGear:GetChildren() or player.Character:GetChildren()) do --Loop through all possible places where tool should be located
if v:IsA("Tool") then --We are checking if the tool has Class with name Tool
local Tool = ToolStorage:FindFirstChild(v.Name) --If Tool exist in ToolStorage folder where we are storing our tools then we will run the code below
if Tool then --If Tool Exist then we will destroy the tool
v:Destroy() --This line will destroy the tool
end
end
end
end
else
return warn("DataStore for Name "..tostring(ToolModuleDataStore["DataStoreName"]).." Does not exist!"), "DataStore for Name "..tostring(ToolModuleDataStore["DataStoreName"]).." Does not exist!" --DataStore does not exist
end
else
return warn("Player UserId or Name does not exist for Name "..tostring(PlayerToRemove).."!"), "Player UserId or Name does not exist for Name "..tostring(PlayerToRemove).."!" --Player UserId/Name does not exist.
end
end)
if suc then --If everything was successfull then we will return warn with success
return warn("All data for "..tostring(PlayerToRemove).."/"..Key.." were removed! This action can not be undone!"), "All data for "..tostring(PlayerToRemove).."/"..Key.." were removed! This action can not be undone!" --If everything works then we will return this warn.
else --If something went wrong (Uh, Oh) then we will return warn with error message this can be very useful for future code debugging
return warn("Error Message: "..err), "Error Message: "..err --Return warning with error message
end
else
return warn(player.Name.." is not admin!"), player.Name.." is not admin!" --If player is not admin!
end
else
return warn("This command is disabled if you want this command to work consider change it inside ToolModule (ModuleScript)"), "This command is disabled if you want this command to work consider change it inside ToolModule (ModuleScript)" --Return warn if command is disabled inside of ToolModule.
end
end
-->> Admin Function(s) <<--
-->> Remote Function (Invoke Server) Request(s) <<--
local function ToolRequest(player,args,ToolID,add) --Tool Request function first parameter is automatically sent from Remote Function Invoke, second parameter is type of argument (function) that we want to call, third parameter is ToolID, and fourth parameter is Optional (usable when you want to give someone new tool if you dont want to give someone new tool then leave it as nil when you are Invoking server)
if args == "GetTool" then --Normal function to buy tool
return GetNewTool(player,ToolID) --calling function with player parameter and ToolID parameter
elseif args == "AddTool" then --Admin function to add tool
return AddNewTool(player,ToolID,add) --calling function with player parameter, ToolID parameter and add parameter that should be player that you want to add tool for (if you don't Invoke server with this it will automatically set to your User)
elseif args == "RemoveData" then --Admin function to remove player data even if player is offline!
return RemoveData(player, add) --we are calling remove data function with player variable that is automatically done by Remote Function Server Invoke and with add - add is Username of the Player that we want to remove data!
else
return tostring(args).." is not valid argument!", tostring(args).." is not valid argument!" --if args does not exist, there's no warn because exploiters can abuse it and try to lag the server
end
end
RemoteFunction.OnServerInvoke = ToolRequest --If the RemoteFunction is invoked then it will run the function to get New Tool
-->> Remote Function (Invoke Server) Request(s) <<--
-->> ClickDetector Method (Optional) <<--
if ToolModuleSettings["ClickDetectorMethod"] == true then --Optional if you want to be able to buy Tools with ClickDetectors (set to false if you dont want to use ClickDetectors)
for i,v in pairs(ToolModule["ClickParts"]) do --loop through inside of ToolModule (ModuleScript) table to find Click Parts that we defined inside of ClickParts table in ToolModule (ModuleScript)
local ClickPart = Workspace:FindFirstChild(i) --Variable to define ClickPart inside Workspace
if ClickPart then --If the ClickPart is inside of workspace then it will run code below
local ClickDetector = ClickPart:FindFirstChildOfClass("ClickDetector") --Variable for ClickDetector
if ClickDetector then --If ClickDetector exist then we will run code below
ClickDetector.MouseClick:Connect(function(player) --If Click Part is clicked then it's connected to buy event in the code below
local Click, Message = GetNewTool(player,v["ToolID"]) --Buy function, we are getting the ToolID that we want to buy from ToolModule (ModuleScript)
if v["Callback"] == true then --If callback inside of ClickParts table in ToolModule is true then it will run code with "response"
for index,ClickTxt in pairs(ClickPart:GetDescendants()) do --Loop through Descendants of the part
if ClickTxt:IsA("TextLabel") and ClickTxt.Name == v["TextLabelName"] or ClickTxt:IsA("TextButton") and ClickTxt.Name == v["TextLabelName"] then --If we find TextLabel or TextButton inside the ClickPart with name that we defined inside of ClickParts table in ToolModule then it will run the code
ClickTxt.Text = tostring(Message) --Set Text to callback if the callback for the part is on true inside of ClickParts table in ToolModule (ModuleScript)
wait(v["WaitTime"]) --Wait time before the callback message on the TextLabel changes back to DefaultMessage
ClickTxt.Text = tostring(v["DefaultText"]) --Default Text inside inside of ClickParts table in ToolModule
return true --return true so code below won't run
end
end
return warn("TextLabel or TextButton for ClickPart does not found!") --If ClickText does not exist then it will return this warning.
else --If callback inside of ClickParts table in ToolModule is false then it will run code without "response"
return Click --Return buy function without (Text) response
end
end)
else
return warn("ClickDetector for Part "..tostring(i).." does not exist!") --If ClickDetector for part does not exist then we will return warning
end
else
return warn("ClickDetector Part does not exist in workspace!") --If ClickDetector part does not exist in workspace then we will return warning
end
end
end
-->> ClickDetector Method (Optional) <<--
Admin Gui Client Code
local Frame = script.Parent:WaitForChild("Frame")
local ConfirmButton = Frame:WaitForChild("ConfirmButton")
local ToolIDBox = Frame:WaitForChild("ToolIDBox")
local ToolPlayerNameBox = Frame:WaitForChild("ToolPlayerNameBox")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RemoteFunction = ReplicatedStorage:WaitForChild("RemoteFunction")
local OpenCloseButton = script.Parent:WaitForChild("OpenButton")
local CloseButton = Frame:WaitForChild("CloseButton")
local ArrowNext = Frame:WaitForChild("ArrowButtonNext")
local ArrowBack = Frame:WaitForChild("ArrowButtonBack")
local function GetAttributeFunc(AttID) --Function to get current attribute
if AttID == "Give" then
ToolIDBox.Visible = true
ConfirmButton.BackgroundColor3 = Color3.fromRGB(49, 147, 72)
ConfirmButton.Text = "Confirm give"
elseif AttID == "Remove" then
ToolIDBox.Visible = false
ConfirmButton.BackgroundColor3 = Color3.fromRGB(255, 0, 0)
ConfirmButton.Text = "Confirm Data Removal for Player ["..tostring(ToolPlayerNameBox.Text).."]!"
end
end
ToolPlayerNameBox:GetPropertyChangedSignal("Text"):Connect(function()
local Attribute
Attribute = Frame:GetAttribute("Mode")
GetAttributeFunc(Attribute)
end)
OpenCloseButton.MouseButton1Click:Connect(function()
if Frame.Visible == false then
Frame.Visible = true
else
Frame.Visible = false
end
end)
CloseButton.MouseButton1Click:Connect(function()
if Frame.Visible == true then
Frame.Visible = false
end
end)
ArrowNext.MouseButton1Click:Connect(function() --Arrow to click to change Functionalities
local Attribute
Attribute = Frame:GetAttribute("Mode")
if Attribute == "Give" then
Frame:SetAttribute("Mode","Remove")
GetAttributeFunc("Remove")
elseif Attribute == "Remove" then
Frame:SetAttribute("Mode","Give")
GetAttributeFunc("Give")
end
end)
ArrowBack.MouseButton1Click:Connect(function() --Arrow to click to change Functionalities
local Attribute
Attribute = Frame:GetAttribute("Mode")
if Attribute == "Give" then
Frame:SetAttribute("Mode","Remove")
GetAttributeFunc("Remove")
elseif Attribute == "Remove" then
Frame:SetAttribute("Mode","Give")
GetAttributeFunc("Give")
end
end)
ConfirmButton.MouseButton1Click:Connect(function() --If we click to the Confirm button it will run the code below
local Attribute
Attribute = Frame:GetAttribute("Mode")
if ToolIDBox.Text ~= "" and Attribute == "Give" then --we are checking if the text is not nothing
local Remote,Message = RemoteFunction:InvokeServer("AddTool", tonumber(ToolIDBox.Text), ToolPlayerNameBox.Text) --Invoking the server with ToolID from ToolIDBox text and ToolPlayerNameBox text to determine to who we want to add the tool
ConfirmButton.Text = tostring(Message) --Message that we get from the RemoteFunction (callback)
wait(2)
GetAttributeFunc(Attribute)
elseif ToolPlayerNameBox.Text ~= "" and Attribute == "Remove" then
local Remote,Message = RemoteFunction:InvokeServer("RemoveData", nil, ToolPlayerNameBox.Text) --Invoking the server with ToolPlayerNameBox text to determine to who we want to remove data
ConfirmButton.Text = tostring(Message) --Message that we get from the RemoteFunction (callback)
wait(2)
GetAttributeFunc(Attribute)
end
end)
Tool Shop Gui Client Code
local Frame = script.Parent:WaitForChild("Frame")
local FrameScrolling = Frame.ScrollingFrame
local ToolExample = FrameScrolling.ToolExample
local OpenShop = script.Parent:WaitForChild("OpenButton")
local CloseShop = Frame.CloseButton
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ToolShopModule = require(ReplicatedStorage:FindFirstChild("ToolModule"))
local RemoteFunction = ReplicatedStorage.RemoteFunction
local debounce = false
local player = game:GetService("Players").LocalPlayer
local TableWithTools = ToolShopModule["TableWithTools"] --Table with information about tools
local ToolModuleSettings = ToolShopModule["Settings"] --Table with Settings of whole Module
local ToolModuleCurrencies = ToolModuleSettings["Currencies"] --Table with information about Currencies that are used to buy tools for
OpenShop.MouseButton1Click:Connect(function() --Opening/Closing Shop
if Frame.Visible == false then
Frame.Visible = true
else
Frame.Visible = false
end
end)
CloseShop.MouseButton1Click:Connect(function() --Opening Shop
if Frame.Visible == true then
Frame.Visible = false
end
end)
for i,v in pairs(TableWithTools) do --loops through table and clone every member of the Tool Table inside ToolModule and create clone with its name and cost
if v["ToolID"] ~= 0 then
local FrameClone = ToolExample:Clone()
local FrameCloneToolName = FrameClone:FindFirstChild("ToolNameLabel")
local FrameCloneToolCost = FrameClone:FindFirstChild("CostLabel")
local NumberVal = Instance.new("NumberValue", FrameClone)
FrameClone.Name = i
NumberVal.Name = "ToolID"
NumberVal.Value = v["ToolID"]
FrameCloneToolName.Text = i
FrameCloneToolCost.Text = "Cost: "..v["Cost"].." "..v["CurrencyName"]
FrameClone.Visible = true
FrameClone.Parent = FrameScrolling
end
end
local function GetCurrencyName(player, ToolID) --this function helps to get currency that the Tool cost
for i,v in pairs(TableWithTools) do
if v["ToolID"] == ToolID then
for index,toolcurrency in pairs(ToolModuleCurrencies) do
if toolcurrency["Currency"] == v["CurrencyName"] then
return player:FindFirstChild(toolcurrency["Folder"]):FindFirstChild(toolcurrency["Currency"])
end
end
end
end
end
for i,v in pairs(FrameScrolling:GetDescendants()) do --Loops through all descendants if find a match then create MouseButton1Click event if MouseButton1Click event is fired then it will Invoke Server if player has enough Currency or if player doesn't have debounce to prevent spamming Server Invoke
if v:IsA("TextButton") then
if v.Name == "BuyButton" then
v.MouseButton1Click:Connect(function()
for index,tools in pairs(TableWithTools) do
local ToolIDNumberValue = v.Parent:FindFirstChild("ToolID")
if tools["ToolID"] == ToolIDNumberValue.Value then
if GetCurrencyName(player,tools["ToolID"]).Value >= tools["Cost"] and not debounce then
debounce = true
local BuyButton = v.Parent:FindFirstChild("BuyButton")
local Remote,Message = RemoteFunction:InvokeServer("GetTool", ToolIDNumberValue.Value)
BuyButton.Text = tostring(Message)
wait(2)
BuyButton.Text = "Buy"
debounce = false
elseif not debounce then
debounce = true
local BuyButton = v.Parent:FindFirstChild("BuyButton")
BuyButton.Text = "You don't have enough "..tools["CurrencyName"].."!"
wait(2)
BuyButton.Text = "Buy"
debounce = false
end
end
end
end)
end
end
end
UI(s)
Shop Gui
Image(s):
Installation/Model/Uncopylocked place
Model: EasyToolShop - Roblox
Uncopylocked place: Easy Tool Shop with Tool Saving - Roblox
Install place here: Easy Tool Shop with Tool Saving.rbxl (64.5 KB)
Note(s)
Thanks for using Easy Tool Shop!
If you found bug or way to improve something let me know down below or in my DMs!
That’s all I hope you will enjoy this Tool Shop and please if I did something wrong let me know this is my first Community Resource so any feedback is appreciated!