Hello, I am trying to make a basic UI shop system with map skins and items. However, something does not really work and I do not know why. I am a beginner at this, so please bare with me. Also, please tell me if this way of doing things for this kind of shop system is OK/not! I have:
- a datstore module (I used Supphi’s datastore module);
- a module script used for storing info about the skins/items;
- a server script handling purchases;
- a localscript in StarterPlayerScripts that should update the UI for all of the skins.
I say should because it only updates 2 skins out of them all and I think there’s a race condition where I try to access the data of the player faster than I should? But I do not know how I can improve this/if this is the issue.
Here is the client script that should update the UI:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local MarketplaceService = game:GetService("MarketplaceService")
local Player = Players.LocalPlayer
local PlayerGui = Player:WaitForChild("PlayerGui")
local SkinAndItemData = require(ReplicatedStorage.MapSkinsAndItemsData)
local PurchaseEvent = ReplicatedStorage.NewRemoteEvents:WaitForChild("PurchaseSkinEvent")
local UpdatePlayerInventory = ReplicatedStorage.NewRemoteEvents:WaitForChild("UpdatePlayerInventory")
local shopUI = PlayerGui:WaitForChild("ShopUI")
local skinShopUI = shopUI.BGFrame.ScrollingFrame:WaitForChild("MapSkins")
local skinContainer = skinShopUI:WaitForChild("Container")
local currentPlayerData = {}
local connectedButtons = {} -- track connected buttons
local function UpdateSkinFrame(frame, skinName)
local skinData = SkinAndItemData:GetSkinData(skinName)
if not skinData then return end
local isOwned = currentPlayerData.MapSkins
and currentPlayerData.MapSkins.Skins
and currentPlayerData.MapSkins.Skins[skinName]
and currentPlayerData.MapSkins.Skins[skinName].Owned
local isDefault = skinData.isDefault
frame.SkinName.Text = skinData.displayName
frame.SkinDescription.Text = skinData.description
frame.BuyButton.Visible = not isOwned and not isDefault
frame.EquipButton.Visible = isOwned
frame.PriceDisplay.Visible = not isOwned and not isDefault
if frame.PriceDisplay.Visible then
local priceTextLabel = frame.PriceDisplay:FindFirstChildWhichIsA("TextLabel")
local priceImage = frame.PriceDisplay:FindFirstChild("ImageLabel")
if skinData.gamepassId then
priceTextLabel.Text = skinData.gamepassPrice
priceTextLabel.TextColor3 = Color3.fromRGB(85, 255, 127)
priceImage.Image = skinData.robuxImage
elseif skinData.price then
priceTextLabel.Text = tostring(skinData.price)
priceTextLabel.TextColor3 = Color3.fromRGB(255, 255, 0)
priceImage.Image = skinData.coinImage
end
end
if not connectedButtons[frame.BuyButton] then
connectedButtons[frame.BuyButton] = true
frame.BuyButton.Activated:Connect(function()
PurchaseEvent:FireServer(skinName)
end)
end
end
local function UpdateAllSkins()
for skinName, _ in pairs(SkinAndItemData:GetAllSkins()) do
local frame = skinContainer:FindFirstChild(skinName)
if frame then
UpdateSkinFrame(frame, skinName)
end
end
end
UpdatePlayerInventory.OnClientEvent:Connect(function(playerData)
currentPlayerData = playerData
UpdateAllSkins()
end)
shopUI:GetPropertyChangedSignal("Enabled"):Connect(function()
if shopUI.Enabled then
UpdateAllSkins()
end
end)
here is my server script:
local Players = game:GetService("Players")
local MarketplaceService = game:GetService("MarketplaceService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local SkinAndItemData = require(ReplicatedStorage.MapSkinsAndItemsData)
local DataStoreModule = require(game.ServerScriptService.MainModule)
local PurchaseEvent = ReplicatedStorage.NewRemoteEvents:WaitForChild("PurchaseSkinEvent")
local UpdatePlayerInventory = ReplicatedStorage.NewRemoteEvents:WaitForChild("UpdatePlayerInventory")
local function GrantSkin(playerData, skinName)
playerData.MapSkins.Skins[skinName] = {
Owned = true,
Equipped = false
}
end
local function ProcessPurchase(player, skinName)
local dataStore = DataStoreModule.find("PlyrData", player.UserId)
if not dataStore or not dataStore.Value then
return false, "Data not loaded"
end
local playerData = dataStore.Value
local skinData = SkinAndItemData:GetSkinData(skinName)
if not skinData then return false, "Invalid skin" end
if playerData.MapSkins.Skins[skinName] and playerData.MapSkins.Skins[skinName].Owned then
return false, "Already owned"
end
if skinData.gamepassId then
local ownsPass = false
pcall(function()
ownsPass = MarketplaceService:UserOwnsGamePassAsync(player.UserId, skinData.gamepassId)
end)
if not ownsPass then
MarketplaceService:PromptGamePassPurchase(player, skinData.gamepassId)
return false, "Gamepass required"
end
end
if skinData.price and not skinData.gamepassId then
if playerData.Currencies.Coins < skinData.price then
return false, "Not enough coins"
end
playerData.Currencies.Coins -= skinData.price
end
GrantSkin(playerData, skinName)
UpdatePlayerInventory:FireClient(player, playerData)
return true, skinName
end
PurchaseEvent.OnServerEvent:Connect(function(player, skinName)
local success, result = ProcessPurchase(player, skinName)
end)
There are no errors, it just does not work as expected.