File size: 5,169 Bytes
0712d5f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | -- 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)
|