-- src/ServerScriptService/MarketManager.server.lua local CollectionService = game:GetService("CollectionService") local ReplicatedStorage = game:GetService("ReplicatedStorage") local Players = game:GetService("Players") local EconomyConfig = require(ReplicatedStorage:WaitForChild("Shared"):WaitForChild("EconomyConfig")) local NotificationEvent = ReplicatedStorage:WaitForChild("Events"):WaitForChild("NotificationEvent") local MarketUpdateEvent = ReplicatedStorage:WaitForChild("Events"):WaitForChild("MarketUpdateEvent") local SoundEvent = ReplicatedStorage:WaitForChild("Events"):WaitForChild("SoundEvent") -- Global current market multipliers local currentMarketRates = {} -- Initialize base rates for all wood types for woodType, _ in pairs(EconomyConfig.WoodBaseValues) do currentMarketRates[woodType] = 1.0 end local function updateMarketRates() for woodType, _ in pairs(currentMarketRates) do local randShift = (math.random(-10, 10) / 100) local newRate = currentMarketRates[woodType] + randShift currentMarketRates[woodType] = math.clamp( newRate, EconomyConfig.MarketFluctuations.MaxDecrease, EconomyConfig.MarketFluctuations.MaxIncrease ) end -- Broadcast updated rates to all clients for _, player in ipairs(Players:GetPlayers()) do MarketUpdateEvent:FireClient(player, currentMarketRates) end end -- Start Market Cycle task.spawn(function() if EconomyConfig.MarketFluctuations.Enabled then while true do task.wait(EconomyConfig.MarketFluctuations.UpdateInterval) updateMarketRates() end end end) local function getPartVolume(part) return part.Size.X * part.Size.Y * part.Size.Z end local function calculateWoodValue(logPart) local treeType = logPart:GetAttribute("TreeType") local state = logPart:GetAttribute("ProcessState") or "Raw" if not treeType or not EconomyConfig.WoodBaseValues[treeType] then return 0 end local baseValue = EconomyConfig.WoodBaseValues[treeType] local marketMult = currentMarketRates[treeType] or 1.0 local processMult = EconomyConfig.ProcessingMultipliers[state] or 1.0 local degradedMult = logPart:GetAttribute("DegradedMult") or 1.0 local volume = getPartVolume(logPart) -- Formula: Volume * BaseValue * ProcessingState * MarketDemand * Degradation return math.floor(volume * baseValue * processMult * marketMult * degradedMult) end local function rewardPlayer(player, amount) if not player then return end local leaderstats = player:FindFirstChild("leaderstats") if leaderstats then local cash = leaderstats:FindFirstChild("Cash") if cash then cash.Value = cash.Value + amount end end -- Track total earned if _G.IncrementStat then _G.IncrementStat(player, "TotalEarned", amount) end -- Progress quests if _G.ProgressQuest then _G.ProgressQuest(player, "Sell", 1) _G.ProgressQuest(player, "EarnCash", amount) end -- Check achievements if _G.CheckAchievements then task.defer(function() _G.CheckAchievements(player) end) end end local function bindDropoffZone(zonePart) local debounce = {} zonePart.Touched:Connect(function(hit) if CollectionService:HasTag(hit, "TreeSegment") then if not debounce[hit] then debounce[hit] = true local woodValue = calculateWoodValue(hit) local ownerId = hit:GetAttribute("OwnerId") local playerToReward = nil if ownerId then playerToReward = Players:GetPlayerByUserId(ownerId) else pcall(function() playerToReward = hit:GetNetworkOwner() end) end if playerToReward and woodValue > 0 then rewardPlayer(playerToReward, woodValue) -- Notification with value local treeType = hit:GetAttribute("TreeType") or "Unknown" local state = hit:GetAttribute("ProcessState") or "Raw" NotificationEvent:FireClient(playerToReward, "Sale", "Sold " .. state .. " " .. treeType .. " for $" .. tostring(woodValue)) SoundEvent:FireClient(playerToReward, "CashRegister", nil) end hit:Destroy() end end end) end CollectionService:GetInstanceAddedSignal("MarketDropoff"):Connect(bindDropoffZone) for _, zone in pairs(CollectionService:GetTagged("MarketDropoff")) do bindDropoffZone(zone) end -- Expose market rates for billboard GUI _G.GetMarketRates = function() return currentMarketRates end -- Send initial market rates to new players Players.PlayerAdded:Connect(function(player) task.delay(5, function() if player.Parent then MarketUpdateEvent:FireClient(player, currentMarketRates) end end) end)