I’m adding a revamp of the shop in my game, and one of the new features for it is a weekly shop, 3 UNIQUE skins in the shop per week. If you buy one of the skins, it’ll go into the limiteds tab in the shop. This is all fine until the weekly skins rotate. It’ll only show in the limiteds tabs your own weeklys if they are currently in the shop.
First image is my data, which shows evidence that I own pretty much all of the weeklys (ones circled are the weekly skins), second is it showing that only the ones in the weekly shop are in my inventory.
Shop script: (local script)
-- Muh Services
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")
local MarketService = game:GetService("MarketplaceService")
-- Meh Folders
local functions = ReplicatedStorage:WaitForChild("Functions")
local events = ReplicatedStorage:WaitForChild("Events")
local teddyFolder = ReplicatedStorage:WaitForChild("Teddy")
-- Mah Remotes
local getData = functions:WaitForChild("getData")
local selectCharacter = functions:WaitForChild("selectCharacter")
local buyCharacter = functions:WaitForChild("buyCharacter")
local buyCurrency = functions:WaitForChild("buyCurrency")
local resetWeekly = functions:WaitForChild("resetWeekly")
local togglePopup = events:WaitForChild("togglePopup")
local getWeeklySkins = functions:WaitForChild("getWeeklySkins")
-- Mih References
local shopFrame = script.Parent:WaitForChild("Frame")
local itemsFrame = shopFrame:WaitForChild("Items")
local player = game.Players.LocalPlayer
local shop = script.Parent
local popup = shop.Parent.Popup
local pieces = shopFrame.Pieces
local shopCurrencyStock = itemsFrame.CurrencyStock
local normalStock = itemsFrame.NormalStock
local weeklyStock = itemsFrame.WeeklyStock
local limitedStock = itemsFrame.LimitedStock
-- Moh Modules
local availableCharacters = require(teddyFolder:WaitForChild("CharacterList"))
local weeklySkins = {}
local ownedCharacters = {}
local teddySelection
local weeklySkinsButton = shopFrame:WaitForChild("WeeklySkins")
local normalSkinsButton = shopFrame:WaitForChild("NormalSkins")
local currencyButton = shopFrame:WaitForChild("CurrencyButton")
local limitedSkinsButton = shopFrame:WaitForChild("LimitedSkins")
local resetWeeklyButton = shopFrame:WaitForChild("ResetWeekly")
-- Different colours used to identify different item 'states'
local colors = {
red = Color3.fromRGB(170, 0, 0),
orange = Color3.fromRGB(223, 142, 12),
light_red = Color3.fromRGB(206, 27, 27),
green = Color3.fromRGB(100, 193, 66),
yellow = Color3.fromRGB(255, 216, 0),
neutral = Color3.fromRGB(225, 168, 139),
white = Color3.fromRGB(255, 255, 255)
}
-- Multi-purpose 'Are you sure' window
-- Used for confirming purchases
local function popupPrompt(msg, cancelOption)
local result
local cancel
local confirm
popup.Visible = true
popup.Title.Text = msg
popup.Buttons.Cancel.Visible = false
if cancelOption then
popup.Buttons.Cancel.Visible = true
cancel = popup.Buttons.Cancel.Activated:Connect(function()
if cancel then
cancel:Disconnect()
end
if confirm then
confirm:Disconnect()
end
popup.Visible = false
result = false
end)
end
confirm = popup.Buttons.Okay.Activated:Connect(function()
if cancel then
cancel:Disconnect()
end
if confirm then
confirm:Disconnect()
end
popup.Visible = false
result = true
end)
while not result do
wait()
end
return result
end
togglePopup.OnClientEvent:Connect(popupPrompt)
-- Changes how the shop icons will look
local function setStatus(btn, status, color)
-- DEFAULT 'FOR SALE' STATE
if status == "For Sale" then
btn.Info.Status.Size = UDim2.new(0.25,0,1,0)
btn.Info.Status.Position = UDim2.new(0.4,0,0,0)
btn.Info.Pieces.Visible = true
btn.Info.Status.TextColor3 = Color3.fromRGB(255, 216, 0)
btn.Title.TextStrokeColor3 = Color3.fromRGB(255, 216, 0)
else -- OWNED OR SELECTD
btn.Info.Status.Text = status
btn.Info.Status.Size = UDim2.new(1,0,1,0)
btn.Info.Status.Position = UDim2.new(0,0,0,0)
btn.Info.Pieces.Visible = false
btn.Info.Status.TextColor3 = colors[color]
btn.Title.TextStrokeColor3 = colors[color]
end
btn.ViewportFrame.BackgroundColor3 = colors[color]
end
-- Checks our local table of the players session data, reduces unnecessary server calls
local function playerOwnsCharacter(name)
if ownedCharacters then
for index, character in pairs(ownedCharacters) do
if character == name then
return true
end
end
end
return false
end
-- Updates store whenever anything changes
function updateStore()
setupCharacterShop()
-- Gets the latest player data
local data = getData:InvokeServer()
if data then
ownedCharacters = data.characters
teddySelection = data.teddy
end
-- Sets shop icons to appropriate status
for index, gui in pairs(normalStock:GetChildren()) do
if gui:IsA("TextButton") and gui.Visible == true then
if gui.Name == teddySelection then
setStatus(gui, "Selected", "red")
elseif playerOwnsCharacter(gui.Name) then
setStatus(gui, "Owned", "green")
end
end
end
for index, gui in pairs(limitedStock:GetChildren()) do
if gui:IsA("TextButton") and gui.Visible == true then
if gui.Name == teddySelection then
setStatus(gui, "Selected", "red")
elseif playerOwnsCharacter(gui.Name) then
setStatus(gui, "Owned", "green")
end
end
end
for index, gui in pairs(weeklyStock:GetChildren()) do
if gui:IsA("TextButton") and gui.Visible == true then
setStatus(gui, "For Sale", "orange")
end
end
for index, gui in pairs(shopCurrencyStock:GetChildren()) do
if gui:IsA("TextButton") and gui.Visible == true and not string.find(gui.Name, "CoinPurchase", 1) and gui.Name ~= "TeddyChance" then
if gui.Name == teddySelection then
setStatus(gui, "Selected", "red")
elseif playerOwnsCharacter(gui.Name) then
setStatus(gui, "Owned", "green")
end
end
end
-- Updates the players money tally
pieces.Amount.Text = player.Pieces.Value
end
player.Pieces.Changed:Connect(updateStore)
-- Different colours represent different states
-- Not used for proper authentication, but provides easy client-side check
local function checkStatus(btn)
if btn.Parent == weeklyStock then
return "For Sale"
else
if btn.Info.Status.TextColor3 == colors["red"] then
return "Selected"
elseif btn.Info.Status.TextColor3 == colors["green"] or btn.Info.Status.TextColor3 == colors["orange"] or btn.Info.Status.TextColor3 == colors["white"] or btn.Info.Status.TextColor3 == colors["light_red"] then
return "Owned"
elseif btn.Info.Status.TextColor3 == colors["yellow"] then
return "For Sale"
end
end
end
local function clickCharacter(btn)
local status = checkStatus(btn)
if btn.Parent == weeklyStock then
local price = tonumber(btn.Info.Status.Text)
if price and price > player.Pieces.Value then
popupPrompt("You cannot afford this", false)
else
local confirm = popupPrompt("Purchase " .. btn.Name .. "?", true)
if confirm then
local serialNumber = 0
local status = buyCharacter:InvokeServer(btn.Name, true)
if status == "Success" then
setupCharacterShop()
updateStore()
popupPrompt("You have successfully purchased the weekly skin " .. btn.Name .. --[[" #" .. serialNumber ..]] "!", false)
else
if not status then status = "Error" end
popupPrompt(status, false)
end
end
end
end
if playerOwnsCharacter(btn.Name) then
if status == "Owned" then
local status = selectCharacter:InvokeServer(btn.Name)
if status == "Success" then
updateStore()
else
if not status then status = "Error" end
popupPrompt(status, false)
end
end
else
if status == "For Sale" then
local price = tonumber(btn.Info.Status.Text)
if price and price > player.Pieces.Value then
popupPrompt("You cannot afford this", false)
else
local confirm = popupPrompt("Purchase " .. btn.Name .. "?", true)
if confirm then
local status = buyCharacter:InvokeServer(btn.Name)
if status == "Success" then
updateStore()
else
if not status then status = "Error" end
popupPrompt(status, false)
end
end
end
else
popupPrompt("You do not own this item", false)
end
end
end
local function createCharacterButton(character, parentContainer)
local name = character["Name"]
local characterModel = teddyFolder:FindFirstChild(name)
local btn = script.Template:Clone()
if characterModel then
if character["Weekly"] then
if not table.find(weeklySkins, name) then
print("Can't find " .. name .. " inside weekly skins")
btn:Destroy()
return
end
if playerOwnsCharacter(name) then
-- TODO make this into a function instead of cloning
-- i hate this
local ownedBtn = btn:Clone()
ownedBtn.Name = character["Name"]
ownedBtn.Visible = true
ownedBtn.LayoutOrder = character["Shop Index"]
ownedBtn.Parent = limitedStock
-- Setup text info
ownedBtn.Title.Text = character["Name"]
ownedBtn.Info.Status.Text = character["Price"]
ownedBtn.Info.Status.Size = UDim2.new(0.25, 0, 1, 0)
ownedBtn.Info.Status.Position = UDim2.new(0.4, 0, 0, 0)
-- Clone Teddy for viewportframe
local teddy = characterModel:Clone()
local cframe, size = teddy:GetBoundingBox()
teddy.Parent = ownedBtn.ViewportFrame
ownedBtn.ViewportFrame.BackgroundColor3 = colors["green"]
local viewportCamera = Instance.new("Camera")
ownedBtn.ViewportFrame.CurrentCamera = viewportCamera
viewportCamera.Parent = ownedBtn.ViewportFrame
viewportCamera.FieldOfView = 40
viewportCamera.CFrame = cframe * CFrame.new(0, 1, -8) * CFrame.Angles(0, math.rad(180), 0)
ownedBtn.Activated:Connect(function()
clickCharacter(ownedBtn)
end)
setStatus(ownedBtn, "Owned", "green")
end
local weeklyTimeLeftScript = script.WeeklyTimeLeft:Clone()
weeklyTimeLeftScript.Parent = btn
weeklyTimeLeftScript.Enabled = true
end
-- Create new button
btn.Name = character["Name"]
btn.Visible = true
btn.LayoutOrder = character["Shop Index"]
btn.Parent = parentContainer
-- Setup text info
btn.Title.Text = character["Name"]
btn.Info.Status.Text = character["Price"]
btn.Info.Status.Size = UDim2.new(0.25, 0, 1, 0)
btn.Info.Status.Position = UDim2.new(0.4, 0, 0, 0)
-- Clone Teddy for viewportframe
local teddy = characterModel:Clone()
local cframe, size = teddy:GetBoundingBox()
teddy.Parent = btn.ViewportFrame
-- Highlight if Limited Edition
if character["Limited Edition"] then
btn.ViewportFrame.BackgroundColor3 = colors["white"]
elseif character["VIP"] then
btn.ViewportFrame.BackgroundColor3 = colors["yellow"]
elseif character["Season Pass"] then
btn.ViewportFrame.BackgroundColor3 = colors["light_red"]
elseif character["Weekly"] then
btn.ViewportFrame.BackgroundColor3 = colors["orange"]
end
-- Setup viewportframe
local viewportCamera = Instance.new("Camera")
btn.ViewportFrame.CurrentCamera = viewportCamera
viewportCamera.Parent = btn.ViewportFrame
viewportCamera.FieldOfView = 40
viewportCamera.CFrame = cframe * CFrame.new(0, 1, -8) * CFrame.Angles(0, math.rad(180), 0)
-- Handle button activation
btn.Activated:Connect(function()
clickCharacter(btn)
end)
else
warn(name .. " doesn't have a corresponding model!")
end
end
function setupCharacterShop()
for _, v in pairs(normalStock:GetChildren()) do
if v:IsA("TextButton") then
v:Destroy()
end
end
for _, v in pairs(limitedStock:GetChildren()) do
if v:IsA("TextButton") then
v:Destroy()
end
end
for _, v in pairs(weeklyStock:GetChildren()) do
if v:IsA("TextButton") then
v:Destroy()
end
end
for _, v in pairs(shopCurrencyStock:GetChildren()) do
if v:IsA("TextButton") and not string.find(v.Name, "Coin") and v.Name ~= "TeddyChance" then
v:Destroy()
end
end
for index, character in pairs(availableCharacters) do
if character["For Sale"] or character["Shown If Purchased"] then
if character["VIP"] then
local ownsVIP = false
local success, err = pcall(function()
ownsVIP = MarketService:UserOwnsGamePassAsync(player.UserId, 27018651)
end)
if success and ownsVIP then
createCharacterButton(character, shopCurrencyStock)
else
warn("Error checking VIP ownership for", player.Name, err)
end
elseif character["Limited Edition"] then
createCharacterButton(character, limitedStock)
elseif character["Weekly"] then
createCharacterButton(character, weeklyStock)
else
createCharacterButton(character, normalStock)
end
end
end
end
-- Buy 100 Pieces
shopCurrencyStock.CoinPurchase100.Activated:Connect(function()
MarketService:PromptProductPurchase(player, 1162320218)
end)
-- Buy 1000 Pieces
shopCurrencyStock.CoinPurchase1000.Activated:Connect(function()
MarketService:PromptProductPurchase(player, 1162320376)
end)
currencyButton.Activated:Connect(function()
shopCurrencyStock.Visible = true
normalStock.Visible = false
weeklyStock.Visible = false
limitedStock.Visible = false
end)
normalSkinsButton.Activated:Connect(function()
shopCurrencyStock.Visible = false
normalStock.Visible = true
weeklyStock.Visible = false
limitedStock.Visible = false
end)
weeklySkinsButton.Activated:Connect(function()
shopCurrencyStock.Visible = false
normalStock.Visible = false
weeklyStock.Visible = true
limitedStock.Visible = false
end)
limitedSkinsButton.Activated:Connect(function()
shopCurrencyStock.Visible = false
normalStock.Visible = false
weeklyStock.Visible = false
limitedStock.Visible = true
end)
-- Buy Double Teddy Chance
local teddyChanceId = 14785149
shopCurrencyStock.TeddyChance.Activated:Connect(function()
local hasPass = false
local success, err = pcall(function()
hasPass = MarketService:UserOwnsGamePassAsync(player.UserId, teddyChanceId)
end)
if not success then
warn("Error getting gamepass info for", player.Name, err)
popupPrompt("Error getting gamepass info", false)
return
end
if hasPass then
popupPrompt("You already own this", false)
else
-- Player does NOT own the game pass; prompt them to purchase
MarketService:PromptGamePassPurchase(player, teddyChanceId)
end
end)
shop:GetPropertyChangedSignal("Visible"):Connect(function()
-- Update the store page when opened
if shop.Visible then
updateStore()
else
-- Hide any popups when closed
popup.Visible = false
end
end)
if game.Players.LocalPlayer.Name ~= "mlnitoon2" then
resetWeeklyButton.Visible = false
end
resetWeeklyButton.Activated:Connect(function()
local result = resetWeekly:InvokeServer()
if result then
weeklySkins = getWeeklySkins:InvokeServer()
updateStore()
end
end)
weeklySkins = getWeeklySkins:InvokeServer()
local data = getData:InvokeServer()
if data then
ownedCharacters = data.characters
teddySelection = data.teddy
end
setupCharacterShop()
updateStore()
--[[while task.wait(60) do -- do we need this?
setupCharacterShop()
updateStore()
end]]