Inventory/Shop Not Working

You can write your topic however you want, but you need to answer these questions:

  1. What do you want to achieve? Keep it simple and clear!
    I want to fix my games inventory/shop system.

  2. What is the issue? Include screenshots / videos if possible!
    After opening the inventory and trying to equip a tower, the game errors and the tower isn’t equipped. “SelectedTowers is not a valid member of Player ‘Players.ParadoxumDoge’”
    This system used to work when I used arrays, but when I tried implementing skins I swapped arrays to dictionaries to look like this: [“Scout”] = {[“Skin”] = “Default”} causing it to break.




  3. What solutions have you tried so far? Did you look for solutions on the Developer Hub?
    I tried reading through all the scripts that could cause this but I couldn’t find anything.

After that, you should include more details if you have any. Try to make your topic as descriptive as possible, so that it’s easier for people to help you!

Shop Client
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local towers = require(ReplicatedStorage:WaitForChild("TowerShop"))
local getDataFunc = ReplicatedStorage:WaitForChild("GetData")
local interactItemFunc = ReplicatedStorage:WaitForChild("InteractItem")
local gui = script.Parent
local exit = gui.ShopContainer.Exit
local shop = gui.Shop
local coins = gui.ShopContainer.Coins
local itemsFrame = gui.ShopContainer.ItemsFrame
local playerData = {}
local function getItemStatus(itemName)
	if playerData.OwnedTowers[itemName] then
		return "Owned"
	else
		return "For Sale"
 	end
end
local function interactItem(itemName)
	local data = interactItemFunc:InvokeServer(itemName)
	if data then
		playerData = data
		updateItems()
	end
end
function updateItems()
	coins.Text = "$" .. playerData.Coins
	for i, tower in pairs(towers) do
		local oldButton = itemsFrame:FindFirstChild(tower.Name)
		if oldButton then
			oldButton:Destroy()
		end
		local newButton = itemsFrame.TemplateButton:Clone()
		newButton.Name = tower.Name
		newButton.TowerName.Text = tower.Name
		newButton.Image = tower.ImageAsset
		newButton.Visible = true
		newButton.LayoutOrder = tower.Price
		newButton.Parent = itemsFrame
		local status = getItemStatus(tower.Name)
		if status == "For Sale" then
			if typeof(tower.Price) == "number" then
				newButton.Status.Text = "$" .. tower.Price
			else
				newButton.Status.Text = tower.Price
			end
		elseif status == "Owned" then
			newButton.Status.Text = "Owned"
		end
		newButton.Activated:Connect(function()
			interactItem(tower.Name)
		end)
	end
end
local function toggleShop()
	gui.ShopContainer.Visible = not gui.ShopContainer.Visible
	if gui.ShopContainer.Visible then
		playerData = getDataFunc:InvokeServer()
		updateItems()
		gui.Parent.InventoryGui.InventoryContainer.Visible = false
		gui.Parent.MiscellaneousGui.CodeFrame.Visible = false
		gui.Parent.MiscellaneousGui.UpdateLogFrame.Visible = false
	end
end
shop.Activated:Connect(toggleShop)
exit.Activated:Connect(toggleShop)
Shop Server
local MarketplaceService = game:GetService("MarketplaceService")
local DataStoreService = game:GetService("DataStoreService")
local StarterGui = game:GetService("StarterGui")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local database = DataStoreService:GetDataStore("databaseone")
local towers = require(ReplicatedStorage:WaitForChild("TowerShop"))

local MAX_SELECTED_TOWERS = 5
local data = {}
local function LoadData(player)
	local success = nil
	local playerData = nil
	local attempt = 1

	repeat
		success, playerData = pcall(function()
			return database:GetAsync(player.UserId)
		end)
		attempt += 1
		if not success then
			task.wait()
		end
	until success or attempt == 3
	if success then
		if not playerData then
			playerData = {
				["Coins"] = 0,
				["SelectedTowers"] = {["Scout"] = {["Skin"] = "Default"}},
				["OwnedTowers"] = {["Scout"] = {["Skin"] = "Default"}},
				["AvailableCodes"] = {"Release", "update1hype"},
				["ExpiredCodes"] = {},
				["RedeemedCodes"] = {}
			}
		end
		data[player.UserId] = playerData
	else
		player:Kick("Error received while loading data, please rejoin!")
	end
end
Players.PlayerAdded:Connect(LoadData)
--[[local function SaveData(player)
	if data[player.UserId] then
		local success = nil
		local playerData = nil
		local attempt = 1

		repeat
			success, playerData = pcall(function()
				return database:UpdateAsync(player.UserId, function()
					return data[player.UserId]
				end)
			end)
			attempt += 1
			if not success then
				task.wait()
			end
		until success or attempt == 3
	end
end]]
Players.PlayerRemoving:Connect(function(player)
	--SaveData(player)
	data[player.UserId] = nil
end)

game:BindToClose(function()
	if not RunService:IsStudio() then
		for index, player in pairs(Players:GetPlayers()) do
			task.spawn(function()
				--SaveData(player)
			end)
		end
	end
end)
local function getItemStatus(player, itemName)
	local playerData = data[player.UserId]
	if playerData.SelectedTowers[itemName] then
		return "Equipped"
	elseif playerData.OwnedTowers[itemName] then
		return "Owned"
	else
		return "For Sale"
	end
end

ReplicatedStorage.InteractItem.OnServerInvoke = function(player, itemName)
	local shopItem = towers[itemName]
	local playerData = data[player.UserId]
	if shopItem and playerData then
		local status = getItemStatus(player, itemName)
		if status == "For Sale" and typeof(shopItem.Price) == "number" and shopItem.Price <= playerData.Coins then
			playerData.Coins -= shopItem.Price
			local newTable = {["Skin"] = "Default"}
			playerData.OwnedTowers[shopItem.Name] = newTable
		end
		return playerData
	else
		warn("Player/Tower data does not exist!")
	end
	return false
end
ReplicatedStorage.GetData.OnServerInvoke = function(player)
	return data[player.UserId]
end 
Inventory Client
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local towers = require(ReplicatedStorage:WaitForChild("TowerShop"))
local getDataFunc = ReplicatedStorage:WaitForChild("GetData")
local interactItemFunc = ReplicatedStorage:WaitForChild("InteractItem")
local gui = script.Parent
local exit = gui.InventoryContainer.Exit
local inventory = gui.Inventory
local limit = gui.InventoryContainer.Limit
local itemsFrame = gui.InventoryContainer.ItemsFrame
local playerData = {}
local function getItemStatus(itemName)
	if playerData.SelectedTowers[itemName] then
		return "Equipped"
	elseif playerData.OwnedTowers[itemName] then
		return "Owned"
	else
		return "For Sale"
	end
end
local function interactItem(itemName)
	local data = interactItemFunc:InvokeServer(itemName)
	if data then
		playerData = data
		updateItems()
	end
end
local function getDictionaryLength(dictionary)
	local length = 0
	for i, key in pairs(dictionary) do
		length += 1
	end
	return length
end
function updateItems()
	local length = getDictionaryLength(playerData.SelectedTowers)
	limit.Text = length .. "/5"
	for i, tower in pairs(towers) do
		if playerData.OwnedTowers[tower.Name] then
			local oldButton = itemsFrame:FindFirstChild(tower.Name)
			if oldButton then
				oldButton:Destroy()
			end
			local newButton = itemsFrame.TemplateButton:Clone()
			newButton.Name = tower.Name
			newButton.TowerName.Text = tower.Name
			newButton.Image = tower.ImageAsset
			newButton.Visible = true
			newButton.LayoutOrder = tower.Price
			newButton.Parent = itemsFrame
			local status = getItemStatus(tower.Name)
			if status == "Equipped" then
				newButton.Status.Text = "Unequip?"
			else
				newButton.Status.Text = "Equip?"
			end
			newButton.Activated:Connect(function()
				interactItem(tower.Name)
			end)
		end
	end
end
local function toggleInventory()
	gui.InventoryContainer.Visible = not gui.InventoryContainer.Visible
	if gui.InventoryContainer.Visible then
		playerData = getDataFunc:InvokeServer()
		updateItems()
		gui.Parent.ShopGui.ShopContainer.Visible = false
		gui.Parent.MiscellaneousGui.CodeFrame.Visible = false
		gui.Parent.MiscellaneousGui.UpdateLogFrame.Visible = false
	end
end
inventory.Activated:Connect(toggleInventory)
exit.Activated:Connect(toggleInventory)
Inventory Server
local MarketplaceService = game:GetService("MarketplaceService")
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local database = DataStoreService:GetDataStore("databaseone")
local towers = require(ReplicatedStorage:WaitForChild("TowerShop"))

local MAX_SELECTED_TOWERS = 5
local data = {}
local function LoadData(player)
	local success = nil
	local playerData = nil
	local attempt = 1

	repeat
		success, playerData = pcall(function()
			return database:GetAsync(player.UserId)
		end)
		attempt += 1
		if not success then
			task.wait()
		end
	until success or attempt == 3
	if success then
		if not playerData then
			playerData = {
				["Coins"] = 0,
				["SelectedTowers"] = {["Scout"] = {["Skin"] = "Default"}},
				["OwnedTowers"] = {["Scout"] = {["Skin"] = "Default"}},
				["AvailableCodes"] = {"Release", "update1hype"},
				["ExpiredCodes"] = {},
				["RedeemedCodes"] = {}
			}
		end
		data[player.UserId] = playerData
	else
		player:Kick("Error received while loading data, please rejoin!")
	end
end
Players.PlayerAdded:Connect(LoadData)
--[[local function SaveData(player)
	if data[player.UserId] then
		local success = nil
		local playerData = nil
		local attempt = 1

		repeat
			success, playerData = pcall(function()
				return database:UpdateAsync(player.UserId, function()
					return data[player.UserId]
				end)
			end)
			attempt += 1
			if not success then
				task.wait()
			end
		until success or attempt == 3
	end
end]]
Players.PlayerRemoving:Connect(function(player)
	--SaveData(player)
	data[player.UserId] = nil
end)

game:BindToClose(function()
	if not RunService:IsStudio() then
		for index, player in pairs(Players:GetPlayers()) do
			task.spawn(function()
				--SaveData(player)
			end)
		end
	end
end)
local function getItemStatus(player, itemName)
	local playerData = data[player.UserId]
	if playerData.SelectedTowers[itemName] then
		return "Equipped"
	elseif playerData.OwnedTowers[itemName] then
		return "Owned"
	else
		return "For Sale"
	end
end
local function getDictionaryLength(dictionary)
	local length = 0
	for i, key in pairs(dictionary) do
		length += 1
	end
	return length
end
ReplicatedStorage.InteractItem.OnServerInvoke = function(player, itemName)
	local shopItem = towers[itemName]
	local playerData = data[player.UserId]
	if shopItem and playerData then
		local status = getItemStatus(player, itemName)
		if status == "Equipped" then
			if getDictionaryLength(playerData.SelectedTowers) > 1 then
				playerData.SelectedTowers[itemName] = nil
			end
		elseif status == "Owned" then
			local newTable = {["Skin"] = "Default"}
			playerData.OwnedTowers[shopItem.Name] = newTable
			if getDictionaryLength(player.SelectedTowers) > MAX_SELECTED_TOWERS then
				playerData.SelectedTowers[1] = nil
			end
		end
		return playerData
	else
		warn("Player/Tower data does not exist!")
	end
	return false
end
ReplicatedStorage.GetData.OnServerInvoke = function(player, code)
	local playerData = data[player.UserId]
	if table.find(playerData.AvailableCodes, code) then
		local codeToRemove = table.find(playerData.AvailableCodes, code)
		table.remove(playerData.AvailableCodes, codeToRemove)
		table.insert(playerData.RedeemedCodes, code)
		if code == "Release" then
			playerData.Coins += 1000
		elseif code == "update1hype" then
			playerData.Coins += 1000
		end
		return "Successfully Redeemed"
	elseif table.find(playerData.ExpiredCodes, code) then
		return "Expired"
	elseif table.find(playerData.RedeemedCodes, code) then
		return "Already Redeemed"
	elseif not code then
		return playerData
	else
		return "Invalid"
	end
end
Players.PlayerAdded:Connect(function(player)
	if MarketplaceService:UserOwnsGamePassAsync(player.UserId, 117471311) then
		task.wait(2)
		local playerData = data[player.UserId]
		if playerData.OwnedTowers["Agent"] == nil then
			local newTable = {["Skin"] = "Default"}
			playerData.OwnedTowers["Agent"] = newTable
		end
	end
end)
Code Client (Code Server is inventory)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local codeFrame = script.Parent.CodeFrame
local codeButton = script.Parent.Codes
local exitButton = codeFrame.Exit
local codeBox = codeFrame.CodeBox
local getDataFunc = ReplicatedStorage:WaitForChild("GetData")
local playerData = {}
codeButton.Activated:Connect(function()
	codeFrame.Visible = not codeFrame.Visible
	script.Parent.Parent.InventoryGui.InventoryContainer.Visible = false
	script.Parent.UpdateLogFrame.Visible = false
	script.Parent.Parent.ShopGui.ShopContainer.Visible = false
end)
exitButton.Activated:Connect(function()
	codeFrame.Visible = false
end)
codeBox.FocusLost:Connect(function()
	local status = getDataFunc:InvokeServer(codeBox.Text)
	if typeof(status) == "string" then
		if status == "Already Redeemed" then
			codeBox.Text = "Code is already redeemed!"
			task.wait(1)
			codeBox.Text = "Enter code here"
		elseif status == "Invalid" then
			codeBox.Text = "Code is invalid!"
			task.wait(1)
			codeBox.Text = "Enter code here"
		elseif status == "Expired" then
			codeBox.Text = "Code is expired!"
			task.wait(1)
			codeBox.Text = "Enter code here"
		else
			codeBox.Text = "Code successfully redeemed!"
			task.wait(1)
			codeBox.Text = "Enter code here"
		end
	end
end)

(this is my first time posting on devforum; please tell me if I did anything wrong)
Please do not ask people to write entire scripts or design entire systems for you. If you can’t answer the three questions above, you should probably pick a different category.

1 Like

edit: had 4 different screenshots but for some reason it uploaded this even when i selected 4 different screenshots to paste anyways sorry for that and data save function is – out because im testing and i dont want data saving

1 Like