-- src/ServerScriptService/ShopManager.server.lua local CollectionService = game:GetService("CollectionService") local ReplicatedStorage = game:GetService("ReplicatedStorage") local Players = game:GetService("Players") local ShopConfig = require(ReplicatedStorage:WaitForChild("Shared"):WaitForChild("ShopConfig")) local PurchaseEvent = ReplicatedStorage:WaitForChild("Events"):WaitForChild("PurchaseEvent") local ShopEvent = ReplicatedStorage:WaitForChild("Events"):WaitForChild("ShopEvent") local NotificationEvent = ReplicatedStorage:WaitForChild("Events"):WaitForChild("NotificationEvent") local SoundEvent = ReplicatedStorage:WaitForChild("Events"):WaitForChild("SoundEvent") local counters = {} local function getPlayerCash(player) local leaderstats = player:FindFirstChild("leaderstats") if leaderstats then local cash = leaderstats:FindFirstChild("Cash") if cash then return cash.Value, cash end end return 0, nil end -- Direct purchase handler (from GUI) local function handleDirectPurchase(player, itemId) if not itemId or type(itemId) ~= "string" then return end if not ShopConfig.Items[itemId] then return end -- Anti-cheat if _G.IsRateLimited and _G.IsRateLimited(player, "Purchase") then return end local item = ShopConfig.Items[itemId] local currentCash, cashObj = getPlayerCash(player) if currentCash < item.Price then NotificationEvent:FireClient(player, "Error", "Not enough cash! Need $" .. tostring(item.Price)) SoundEvent:FireClient(player, "PurchaseFail", nil) return end -- Check if already owned (for tools) if item.Type == "Tool" and _G.HasInInventory and _G.HasInInventory(player, "Tools", itemId) then NotificationEvent:FireClient(player, "Info", "You already own this tool!") return end -- Deduct cash cashObj.Value = cashObj.Value - item.Price -- Add to inventory local categoryMap = { Tool = "Tools", Machine = "Machines", Vehicle = "Vehicles", Automation = "Automation", Structure = "Machines", Utility = "Machines", Component = "Machines", } local category = categoryMap[item.Type] or "Machines" if _G.AddToInventory then _G.AddToInventory(player, category, itemId) end -- Progress quests if _G.ProgressQuest then _G.ProgressQuest(player, "Purchase", 1) end NotificationEvent:FireClient(player, "Purchase", "Purchased " .. item.Name .. " for $" .. tostring(item.Price)) SoundEvent:FireClient(player, "PurchaseSuccess", nil) -- Send updated inventory to client local data = _G.GetPlayerData and _G.GetPlayerData(player) if data then ShopEvent:FireClient(player, "InventoryUpdate", data.Inventory) end end -- Counter-based purchase (legacy physical shop) local function handleCounterPurchase(player, counterZone) if not counters[counterZone] then return end local totalCost = 0 local itemsToBuy = {} for boxedItem, _ in pairs(counters[counterZone]) do if boxedItem.Parent then local itemId = boxedItem:GetAttribute("ItemId") if itemId and ShopConfig.Items[itemId] then totalCost = totalCost + ShopConfig.Items[itemId].Price table.insert(itemsToBuy, {item = boxedItem, id = itemId}) end else counters[counterZone][boxedItem] = nil end end if #itemsToBuy == 0 then return end local currentCash, cashObj = getPlayerCash(player) if currentCash >= totalCost then cashObj.Value = cashObj.Value - totalCost for _, buyData in ipairs(itemsToBuy) do buyData.item:SetAttribute("Purchased", true) buyData.item.BrickColor = BrickColor.new("Bright green") counters[counterZone][buyData.item] = nil if _G.AddToInventory then _G.AddToInventory(player, "Machines", buyData.id) end end NotificationEvent:FireClient(player, "Purchase", "Transaction complete! Total: $" .. tostring(totalCost)) SoundEvent:FireClient(player, "PurchaseSuccess", nil) if _G.ProgressQuest then _G.ProgressQuest(player, "Purchase", #itemsToBuy) end else NotificationEvent:FireClient(player, "Error", "Not enough cash! Need $" .. tostring(totalCost)) SoundEvent:FireClient(player, "PurchaseFail", nil) end end local function bindCounterZone(zonePart) counters[zonePart] = {} zonePart.Touched:Connect(function(hit) if CollectionService:HasTag(hit, "BoxedItem") and not hit:GetAttribute("Purchased") then counters[zonePart][hit] = true end end) zonePart.TouchEnded:Connect(function(hit) if counters[zonePart][hit] then counters[zonePart][hit] = nil end end) end CollectionService:GetInstanceAddedSignal("ShopCounter"):Connect(bindCounterZone) for _, zone in pairs(CollectionService:GetTagged("ShopCounter")) do bindCounterZone(zone) end -- Handle both types of purchase events PurchaseEvent.OnServerEvent:Connect(function(player, target) if type(target) == "string" then -- Direct purchase by item ID (from GUI) handleDirectPurchase(player, target) elseif typeof(target) == "Instance" and CollectionService:HasTag(target, "ShopCounter") then -- Counter-based purchase handleCounterPurchase(player, target) end end)