Eymens

hiddendevs.com / rb:@64khyraaa / dc:@arquez application 2nd try

Jul 19th, 2025 (edited)
546
0
6 days
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 28.75 KB | None | 0 0
  1. -- services
  2. local Players = game:GetService("Players")
  3. local ReplicatedStorage = game:GetService("ReplicatedStorage")
  4. local ServerScriptService = game:GetService("ServerScriptService")
  5. local Workspace = game:GetService("Workspace")
  6. local Debris = game:GetService("Debris")
  7.  
  8. -- modules
  9. -- References to various server-side modules are defined here.
  10. -- These modules manage different aspects of the game, such as item carrying, paper tracking, plot management, upgrades, and settings.
  11. local CarryEvent = ReplicatedStorage.Remotes:WaitForChild("CarryEvent")
  12. local PaperEvent = ReplicatedStorage.Remotes:WaitForChild("PaperEvent")
  13. local UpgradeServer = require(ServerScriptService.Modules.UpgradeServer)
  14. local PaperTracker = require(ServerScriptService.Modules.PaperTracker)
  15. local PlotManager = require(ServerScriptService.Modules.PlotManager)
  16. local Settings = require(ServerScriptService.Modules.Settings)
  17. local Chance = require(ReplicatedStorage.Modules.Chance)
  18.  
  19. -- constants and state variables
  20. local papersFolder = Workspace:WaitForChild("Papers") -- The folder in the game world where all paper instances are stored.
  21. local pickupdist = 15 -- The maximum distance a player can be from a paper to pick it up.
  22. local recyclercooldown = 1.2 -- The cooldown duration between consecutive sales at the recycler (selling mechanism).
  23.  
  24. -- tables to track player state
  25. -- These tables are used to track players' current states and the papers they are carrying on the server side.
  26. local serverCarriedPapers = {} -- [player] = { {paper=instance}, ... } -> Stores a list of paper instances currently carried by each player.
  27. local allCarriedPaperInstances = {} -- [paperInstance] = true -> Used as a set (hashmap) for quick lookup of all paper instances currently being carried across the server.
  28. local originalPlayerSpeeds = {} -- [player] = originalSpeed -> Stores players' original walk speeds, as their speed is reduced when carrying papers.
  29. local sellCooldowns = {} -- [player] = true -> Tracks selling cooldowns for players to prevent spamming the selling mechanism.
  30. local promptConnections = {} -- [player] = {Prompt=conn, Touch=conn} -> Stores connections for selling triggers (ProximityPrompt and Touched event) associated with each player's plot. This is crucial for properly disconnecting connections when a player leaves or their plot changes.
  31.  
  32. -- caches for better performance
  33. -- Tables used to improve performance by caching frequently calculated values.
  34. local paperCapacityCache = {} -- [player] = capacity -> Caches the maximum paper carrying capacity for each player.
  35. local speedReductionCache = {} -- [player] = reduction -> Caches the speed reduction per paper carried for each player.
  36. local sellBonusChanceTables = {} -- [level] = chanceTable -> Caches chance tables for different sell bonus levels, avoiding their recreation on every sale.
  37.  
  38. -- This function retrieves the maximum number of papers a player can carry.
  39. -- It utilizes a cache to avoid recalculating the value repeatedly, which is beneficial for performance.
  40. local function getMaxPapers(player)
  41.     if not player then return 5 end -- Returns a default value if the player is invalid.
  42.     if paperCapacityCache[player] then return paperCapacityCache[player] end -- Returns from cache if the value is already present.
  43.  
  44.     local maxPapers = UpgradeServer.GetValue(player, "CarryCapacity") or 5 -- Retrieves the carry capacity from UpgradeServer, defaults to 5 if not found.
  45.     paperCapacityCache[player] = maxPapers -- Caches the calculated value.
  46.     return maxPapers
  47. end
  48.  
  49. -- This function determines how much a player slows down per paper carried.
  50. -- It also uses a cache, similar to the previous function, for performance optimization.
  51. local function getSpeedReduction(player)
  52.     if not player then return 1.5 end -- Returns a default value if the player is invalid.
  53.     if speedReductionCache[player] then return speedReductionCache[player] end -- Returns from cache if the value is already present.
  54.  
  55.     local speedReduction = UpgradeServer.GetValue(player, "CarryBalance") or 1.5 -- Retrieves the balance (speed reduction) value from UpgradeServer, defaults to 1.5 if not found.
  56.     speedReductionCache[player] = speedReduction -- Caches the calculated value.
  57.     return speedReduction
  58. end
  59.  
  60. -- Clears the relevant caches when a player upgrades something.
  61. -- This ensures that the next time the `getMaxPapers` or `getSpeedReduction` functions are called, they re-calculate the updated values.
  62. local function clearCachesOnUpgrade(player, key)
  63.     if key == "CarryCapacity" then
  64.         paperCapacityCache[player] = nil -- Invalidates the paper capacity cache.
  65.     elseif key == "CarryBalance" then
  66.         speedReductionCache[player] = nil -- Invalidates the speed reduction cache.
  67.     end
  68. end
  69.  
  70. --visual and audio effects
  71.  
  72. -- This function makes the gears on the recycler model spin.
  73. -- It runs in a new thread (task.spawn) to prevent blocking the main script execution.
  74. local function spinRecyclerGears(recyclerModel, recyclerUnion)
  75.     task.spawn(function()
  76.         local gears = {}
  77.         -- Finds all "Gear" named UnionOperation parts within the recycler model.
  78.         for _, child in ipairs(recyclerModel:GetDescendants()) do
  79.             if child.Name == "Gear" and child:IsA("UnionOperation") then
  80.                 table.insert(gears, child)
  81.             end
  82.         end
  83.  
  84.         if #gears == 0 then return end
  85.  
  86.         -- start sound
  87.         -- Plays the recycler start-up sound. The sound is cloned and then cleaned up by the Debris service after playback.
  88.         local sndMachineStart = recyclerModel:FindFirstChild("MachineStart", true)
  89.         if sndMachineStart and recyclerUnion then
  90.             local sound = sndMachineStart:Clone()
  91.             sound.Parent = recyclerUnion
  92.             sound:Play()
  93.             Debris:AddItem(sound, 1)
  94.         end
  95.  
  96.         -- spin them
  97.         -- Rotates the gears for a specified duration.
  98.         local startTime, duration, ROTATION_SPEED = os.clock(), 1.0, 120
  99.         while os.clock() - startTime < duration do
  100.             local dt = task.wait() -- Waits for the next frame and gets the elapsed time.
  101.             local rotationCFrame = CFrame.Angles(0, 0, math.rad(ROTATION_SPEED * dt)) -- Calculates how much the gears should rotate in each step.
  102.             for _, gear in ipairs(gears) do
  103.                 gear.CFrame = gear.CFrame * rotationCFrame -- Updates the gear's CFrame to apply the rotation.
  104.             end
  105.         end
  106.  
  107.         -- end sound
  108.         -- Plays the recycler shut-down sound.
  109.         local sndMachineEnd = recyclerModel:FindFirstChild("MachineEnd", true)
  110.         if sndMachineEnd and recyclerUnion then
  111.             local sound = sndMachineEnd:Clone()
  112.             sound.Parent = recyclerUnion
  113.             sound:Play()
  114.             Debris:AddItem(sound, sound.TimeLength + 1)
  115.         end
  116.     end)
  117. end
  118.  
  119. -- This function triggers all visual and audio effects when a player sells paper.
  120. -- These include playing a sell sound, triggering a local screen shake for the player, and spinning the recycler gears.
  121. local function triggerRecyclerEffects(player)
  122.     local playerPlot = PlotManager.GetPlayerPlot(player) -- Retrieves the player's plot.
  123.     if not playerPlot then return end
  124.  
  125.     local recyclerModel = playerPlot:FindFirstChild("Recycler") -- Finds the recycler model within the plot.
  126.     if not recyclerModel then return end
  127.  
  128.     local recyclerUnion = recyclerModel:FindFirstChild("Recycler") -- Finds the main union part of the recycler.
  129.     if recyclerUnion then
  130.         local sndSell = recyclerModel:FindFirstChild("Sell", true) -- Finds the sell sound.
  131.         if sndSell then
  132.             local sound = sndSell:Clone()
  133.             sound.Parent = recyclerUnion
  134.             sound.PlaybackSpeed = 0.9 + math.random() * (1.05 - 0.9) -- Randomizes the playback speed of the sound.
  135.             sound:Play()
  136.             Debris:AddItem(sound, sound.TimeLength + 1) -- Ensures the sound is automatically cleaned up after playing.
  137.         end
  138.     end
  139.  
  140.     CarryEvent:FireClient(player, "PlayLocalShake", recyclerModel) -- Fires a client event to play a local screen shake effect for the player.
  141.     spinRecyclerGears(recyclerModel, recyclerUnion) -- Initiates the gear spinning animation for the recycler.
  142. end
  143.  
  144. --selling logic
  145.  
  146. -- This function calculates the total money multiplier from all boosts and mutations.
  147. -- It accounts for cash boosts from player upgrades, mutation multipliers of the paper, and a chance-based sell bonus.
  148. local function calculateSaleMultiplier(player, paperInstance)
  149.     local multiplier = 1.0
  150.  
  151.     -- 1. cash boost from upgrades
  152.     local boostLvl = UpgradeServer.GetLevel(player, "CashBoost") -- Gets the player's "CashBoost" upgrade level.
  153.     multiplier += (0.05 * boostLvl) -- Adds a 5% bonus for each level.
  154.  
  155.     -- 2. mutations multiplier
  156.     local mutation = paperInstance:GetAttribute("Mutation") -- Checks the mutation attribute of the paper.
  157.     if mutation == "Rainbow" then
  158.         multiplier *= Settings.Mutations.Rainbow.ValueMultiplier -- Applies a specific multiplier for "Rainbow" mutation.
  159.     elseif mutation == "Shiny" then
  160.         multiplier *= Settings.Mutations.Shiny.ValueMultiplier -- Applies a specific multiplier for "Shiny" mutation.
  161.     end
  162.  
  163.     -- 3. sell bonus chance
  164.     local sellBonusLevel = UpgradeServer.GetLevel(player, "SellBonus") -- Gets the player's "SellBonus" upgrade level.
  165.     local sellBonusActivated = false
  166.     if sellBonusLevel > 0 then
  167.         -- Uses the `sellBonusChanceTables` cache to create or retrieve a chance table for each bonus level.
  168.         if not sellBonusChanceTables[sellBonusLevel] then
  169.             sellBonusChanceTables[sellBonusLevel] = Chance.new({
  170.                 ["Success"] = sellBonusLevel, -- Success chance is equal to the upgrade level.
  171.                 ["Fail"] = 100 - sellBonusLevel, -- Fail chance is 100 minus the upgrade level.
  172.             })
  173.         end
  174.  
  175.         if sellBonusChanceTables[sellBonusLevel]:Run() == "Success" then -- Runs the chance table to check if the bonus is triggered.
  176.             multiplier *= 2 -- Doubles the multiplier if the bonus is activated.
  177.             sellBonusActivated = true
  178.         end
  179.     end
  180.  
  181.     return multiplier, sellBonusActivated, mutation -- Returns the calculated multiplier, whether the bonus was activated, and the mutation type.
  182. end
  183.  
  184. -- This is the main sale function that performs all the core selling logic.
  185. -- It is called by other selling functions (e.g., when a paper is touched or a prompt is triggered).
  186. local function processPaperSale(player, paperInstance)
  187.     local guid = paperInstance:GetAttribute("GUID") -- Retrieves the unique ID of the paper.
  188.     local baseCash = PaperTracker.SellPaper(player, guid) -- Sells the paper via the PaperTracker module and gets its base cash value.
  189.     if not baseCash then return end -- Exits if the sale fails (e.g., invalid paper).
  190.  
  191.     -- Get all the bonuses
  192.     local multiplier, sellBonusActivated, mutation = calculateSaleMultiplier(player, paperInstance) -- Calculates all multipliers for the paper.
  193.     local totalCashFromSale = math.floor(baseCash * multiplier + 0.5) -- Calculates the total cash amount and rounds it.
  194.  
  195.     -- Give the player money
  196.     UpgradeServer.AddCoins(player, totalCashFromSale) -- Adds the earned coins to the player.
  197.  
  198.     -- Inform the player they received money with cool popups
  199.     PaperEvent:FireClient(player, "showIncome", totalCashFromSale) -- Sends a client event to show an income popup.
  200.     if sellBonusActivated then
  201.         PaperEvent:FireClient(player, "showBonus", "2x BONUS!") -- Shows a 2x bonus popup if the sell bonus was activated.
  202.     end
  203.     if mutation then
  204.         PaperEvent:FireClient(player, "showMutation", mutation .. " PAPER!") -- Shows a mutation popup if a mutated paper was sold.
  205.     end
  206.  
  207.     -- Play sounds and effects and destroy the paper
  208.     triggerRecyclerEffects(player) -- Triggers the selling effects (sound, screen shake, gear animation).
  209.     paperInstance:Destroy() -- Removes the sold paper instance from the game world.
  210.  
  211.     return true -- Indicates that the sale was successful.
  212. end
  213.  
  214. -- This function sells a paper when it touches the recycler's hitbox.
  215. local function sellTouchedPaper(paperInstance)
  216.     local ownerId = paperInstance:GetAttribute("OwnerId") -- Gets the owner's User ID from the paper.
  217.     if not ownerId then return end
  218.  
  219.     local player = Players:GetPlayerByUserId(ownerId) -- Retrieves the player object using the owner ID.
  220.     if not player then return end
  221.  
  222.     -- Check if the paper is already being sold or carried, or if the machine is on cooldown.
  223.     if paperInstance:GetAttribute("IsBeingSold") or paperInstance:GetAttribute("IsCarried") then return end
  224.     if sellCooldowns[player] then return end
  225.  
  226.     -- Set cooldown to prevent spamming.
  227.     sellCooldowns[player] = true -- Activates the selling cooldown for the player.
  228.     task.delay(recyclercooldown, function() sellCooldowns[player] = nil end) -- Resets the cooldown after a specified duration.
  229.  
  230.     paperInstance:SetAttribute("IsBeingSold", true) -- Sets an attribute on the paper indicating it's being sold.
  231.  
  232.     if not processPaperSale(player, paperInstance) then -- Calls the main paper selling function.
  233.         -- If the sale fails for some reason, unset the flag and reset the cooldown.
  234.         paperInstance:SetAttribute("IsBeingSold", nil)
  235.         sellCooldowns[player] = nil
  236.     end
  237. end
  238.  
  239. -- This function sells the last paper a player picked up when they activate a ProximityPrompt.
  240. local function sellLastCarriedPaper(player)
  241.     if sellCooldowns[player] then return end -- Exits if the selling cooldown is active.
  242.  
  243.     local stack = serverCarriedPapers[player] -- Retrieves the player's stack of carried papers.
  244.     if not stack or #stack == 0 then return end -- Exits if the stack is empty.
  245.  
  246.     -- Cooldown to prevent spamming.
  247.     sellCooldowns[player] = true -- Activates the selling cooldown for the player.
  248.     task.delay(recyclercooldown, function() sellCooldowns[player] = nil end) -- Resets the cooldown after a specified duration.
  249.  
  250.     local paperData = table.remove(stack) -- Removes the topmost (most recently picked up) paper from the stack.
  251.     local paper = paperData.paper
  252.     if not paper or not paper.Parent then -- Exits if the paper is invalid or no longer exists in the workspace.
  253.         sellCooldowns[player] = nil
  254.         return
  255.     end
  256.  
  257.     paper:SetAttribute("IsBeingSold", true) -- Sets an attribute on the paper indicating it's being sold.
  258.     local success = processPaperSale(player, paper) -- Calls the main paper selling function.
  259.  
  260.     if not success then
  261.         -- If the sale failed, re-insert the paper back into the stack.
  262.         table.insert(stack, paperData)
  263.         paper:SetAttribute("IsBeingSold", nil)
  264.         sellCooldowns[player] = nil
  265.         return
  266.     end
  267.  
  268.     -- Update player speed since they are carrying one less item.
  269.     local character = player.Character
  270.     if not character or not originalPlayerSpeeds[player] then return end
  271.     local humanoid = character:FindFirstChildOfClass("Humanoid")
  272.     if not humanoid then return end
  273.  
  274.     if #stack == 0 then -- If the player is no longer carrying any papers, reset their speed to original and stop the carry animation.
  275.         humanoid.WalkSpeed = originalPlayerSpeeds[player]
  276.         originalPlayerSpeeds[player] = nil
  277.         CarryEvent:FireClient(player, "StopCarryAnim")
  278.     else -- If they are still carrying papers, adjust their speed based on the updated stack size.
  279.         humanoid.WalkSpeed = originalPlayerSpeeds[player] - (#stack * getSpeedReduction(player))
  280.     end
  281. end
  282.  
  283.  
  284. -- carrying and dropping logic
  285.  
  286. -- This function handles a player's request to pick up a paper.
  287. -- It performs validation checks and then attaches the paper to the player's character, adjusting their speed.
  288. local function handlePickupRequest(player, targetPaper)
  289.     local character = player.Character
  290.     local humanoid = character and character:FindFirstChildOfClass("Humanoid")
  291.     local playerRoot = character and character.HumanoidRootPart
  292.     if not humanoid or not playerRoot then return end -- Exits if the player's character or essential parts are missing.
  293.  
  294.     -- Validation checks
  295.     if not targetPaper or not targetPaper:IsDescendantOf(papersFolder) then return end -- Checks if the target paper is valid and in the correct folder.
  296.     if targetPaper:GetAttribute("OwnerId") ~= player.UserId then return end -- Ensures the paper belongs to the requesting player.
  297.     if allCarriedPaperInstances[targetPaper] then return end -- Checks if the paper is already being carried.
  298.     if (playerRoot.Position - targetPaper.Position).Magnitude > pickupdist then return end -- Checks if the player is within pickup distance.
  299.  
  300.     local playerStack = serverCarriedPapers[player] -- Retrieves the player's paper stack.
  301.     if #playerStack >= getMaxPapers(player) then return end -- Checks if the player has reached their maximum carrying capacity.
  302.  
  303.     -- Save old speed if not already saved.
  304.     -- Stores the player's original speed, so it can be restored when papers are dropped.
  305.     if not originalPlayerSpeeds[player] then
  306.         originalPlayerSpeeds[player] = humanoid.WalkSpeed
  307.     end
  308.  
  309.     -- Attach the paper to the player.
  310.     -- Welds the paper to the player (using WeldConstraint) and sets its physical properties.
  311.     local mainPart = targetPaper:IsA("Model") and targetPaper.PrimaryPart or targetPaper -- Determines the main part of the paper (PrimaryPart if a model, or the part itself).
  312.     mainPart.CanCollide = false -- Disables collisions for the paper.
  313.     mainPart.Massless = true -- Sets the paper's mass to zero, so it doesn't affect player movement.
  314.     targetPaper:SetAttribute("IsCarried", true) -- Sets an attribute indicating the paper is being carried.
  315.  
  316.     local anchorPart = (#playerStack == 0) and playerRoot or playerStack[#playerStack].paper -- Determines the part to which the paper will be welded (player's HumanoidRootPart for the first paper, or the previous paper in the stack).
  317.     local randomRotation = CFrame.Angles(0, math.rad(math.random(-5, 5)), 0) -- Adds a slight random rotation for visual variety.
  318.     local anchorCFrame = anchorPart:IsA("Model") and anchorPart.PrimaryPart.CFrame or anchorPart.CFrame -- Gets the CFrame of the anchor part.
  319.  
  320.     if #playerStack == 0 then -- If it's the first paper, position it in front of the character.
  321.         mainPart.CFrame = anchorCFrame * CFrame.new(0, 1, -2.1) * randomRotation
  322.     else -- Position subsequent papers slightly above the previous one in the stack.
  323.         mainPart.CFrame = anchorCFrame * CFrame.new(0, 0.1, 0) * randomRotation
  324.     end
  325.  
  326.     local weld = Instance.new("WeldConstraint") -- Creates a WeldConstraint to physically attach the paper.
  327.     weld.Part0 = mainPart
  328.     weld.Part1 = anchorPart:IsA("Model") and anchorPart.PrimaryPart or anchorPart
  329.     weld.Parent = mainPart
  330.  
  331.     -- Add to our tracking tables.
  332.     table.insert(playerStack, { paper = targetPaper }) -- Adds the paper to the player's stack.
  333.     allCarriedPaperInstances[targetPaper] = true -- Adds to the global set of carried paper instances.
  334.  
  335.     -- Slow the player down.
  336.     humanoid.WalkSpeed = originalPlayerSpeeds[player] - (#playerStack * getSpeedReduction(player)) -- Reduces the player's walk speed based on the number of papers carried.
  337.  
  338.     -- Play animations and sounds.
  339.     if #playerStack == 1 then -- If it's the first paper, start the carry animation.
  340.         CarryEvent:FireClient(player, "PlayCarryAnim")
  341.     end
  342.     CarryEvent:FireClient(player, "PlaySound", "Pickup") -- Plays a pickup sound.
  343. end
  344.  
  345. -- This function handles a player's request to drop a paper.
  346. -- It detaches the paper from the player, restores its physical properties, and adjusts the player's speed.
  347. local function handleDropRequest(player, dropCFrame)
  348.     local character = player.Character
  349.     local humanoid = character and character:FindFirstChildOfClass("Humanoid")
  350.     if not humanoid then return end
  351.  
  352.     local playerStack = serverCarriedPapers[player] -- Retrieves the player's paper stack.
  353.     if #playerStack == 0 then return end -- Exits if the stack is empty.
  354.  
  355.     local paperData = table.remove(playerStack) -- Removes the topmost paper from the stack.
  356.     local paperToDrop = paperData.paper
  357.  
  358.     if not paperToDrop or not paperToDrop.Parent then return end
  359.  
  360.     -- Unweld the paper and make it a normal object again.
  361.     -- Detaches the paper from the player and restores its original physical properties.
  362.     local mainPart = paperToDrop:IsA("Model") and paperToDrop.PrimaryPart or paperToDrop
  363.     local weld = mainPart:FindFirstChildOfClass("WeldConstraint") -- Finds the WeldConstraint on the paper.
  364.     if weld then weld:Destroy() end -- Destroys the WeldConstraint, freeing the paper.
  365.  
  366.     mainPart.CanCollide = true -- Re-enables collisions for the paper.
  367.     mainPart.Massless = false -- Restores the paper's mass.
  368.     paperToDrop:SetAttribute("IsCarried", nil) -- Clears the attribute indicating the paper is carried.
  369.     mainPart:PivotTo(dropCFrame) -- Moves the paper to the specified drop location.
  370.  
  371.     -- Remove from tracking table.
  372.     allCarriedPaperInstances[paperToDrop] = nil -- Removes from the global set of carried paper instances.
  373.  
  374.     -- Fix player speed.
  375.     -- Adjusts the player's speed based on the remaining number of papers.
  376.     if #playerStack == 0 and originalPlayerSpeeds[player] then -- If the player is no longer carrying any papers, restore their original speed.
  377.         humanoid.WalkSpeed = originalPlayerSpeeds[player]
  378.         originalPlayerSpeeds[player] = nil
  379.     elseif originalPlayerSpeeds[player] then -- If still carrying papers, adjust speed based on the updated stack size.
  380.         humanoid.WalkSpeed = originalPlayerSpeeds[player] - (#playerStack * getSpeedReduction(player))
  381.     end
  382.  
  383.     -- Play animations and sounds.
  384.     CarryEvent:FireClient(player, "PlayDropAnim") -- Plays a drop animation.
  385.     CarryEvent:FireClient(player, "PlaySound", "Drop") -- Plays a drop sound.
  386.     if #playerStack == 0 then -- If the player has dropped all papers, stop the carry animation.
  387.         CarryEvent:FireClient(player, "StopCarryAnim")
  388.     end
  389. end
  390.  
  391. -- Listens for events from the client (player) via the CarryEvent RemoteEvent.
  392. CarryEvent.OnServerEvent:Connect(function(player, action, data)
  393.     if action == "RequestPickup" then
  394.         handlePickupRequest(player, data) -- Handles a paper pickup request.
  395.     elseif action == "RequestDrop" then
  396.         handleDropRequest(player, data) -- Handles a paper drop request.
  397.     end
  398. end)
  399.  
  400.  
  401. --player and connection management
  402.  
  403. -- This function is called when a player dies or leaves the game.
  404. -- It ensures that any papers they were carrying are automatically dropped (and destroyed).
  405. -- This prevents inconsistent states and "stuck" papers in the game world.
  406. local function forceDropAll(player)
  407.     local stack = serverCarriedPapers[player]
  408.     if not stack or #stack == 0 then return end
  409.  
  410.     local character = player.Character
  411.     if character then
  412.         local humanoid = character:FindFirstChildOfClass("Humanoid")
  413.         if humanoid and originalPlayerSpeeds[player] then
  414.             humanoid.WalkSpeed = originalPlayerSpeeds[player] -- Restores the player's speed to their original value.
  415.         end
  416.     end
  417.  
  418.     originalPlayerSpeeds[player] = nil -- Clears the original speed record.
  419.  
  420.     for _, paperData in ipairs(stack) do -- Iterates through each paper in the stack.
  421.         if paperData.paper and paperData.paper.Parent then
  422.             allCarriedPaperInstances[paperData.paper] = nil -- Removes from the global set of carried paper instances.
  423.             paperData.paper:Destroy() -- Destroys the paper instance.
  424.         end
  425.     end
  426.  
  427.     serverCarriedPapers[player] = {} -- Resets the player's paper stack.
  428. end
  429.  
  430. -- This function sets up the sell prompt and hitbox for a player's plot.
  431. -- It includes a delay if the plot hasn't loaded yet, accommodating the asynchronous nature of plot loading.
  432. local function connectSellTriggersForPlayer(player)
  433.     -- Clears existing connections to prevent duplicates and ensure fresh connections are established.
  434.     if promptConnections[player] then
  435.         if promptConnections[player].Prompt then promptConnections[player].Prompt:Disconnect() end
  436.         if promptConnections[player].Touch then promptConnections[player].Touch:Disconnect() end
  437.         promptConnections[player] = nil
  438.     end
  439.  
  440.     local playerPlot = PlotManager.GetPlayerPlot(player) -- Retrieves the player's plot.
  441.     if not playerPlot then
  442.         task.delay(2, function() connectSellTriggersForPlayer(player) end) -- If the plot is not found, retries after 2 seconds.
  443.         return
  444.     end
  445.  
  446.     local recyclerModel = playerPlot:FindFirstChild("Recycler") -- Finds the recycler model within the plot.
  447.     if not recyclerModel then return end
  448.  
  449.     local prompt = recyclerModel:FindFirstChildOfClass("ProximityPrompt") -- Finds the ProximityPrompt within the recycler.
  450.     local hitbox = recyclerModel:FindFirstChild("RecycleHitbox") -- Finds the touch hitbox within the recycler.
  451.     local connections = {}
  452.  
  453.     if prompt then
  454.         -- Connects the ProximityPrompt's Triggered event to the `sellLastCarriedPaper` function.
  455.         connections.Prompt = prompt.Triggered:Connect(function() sellLastCarriedPaper(player) end)
  456.     end
  457.  
  458.     if hitbox then
  459.         -- Connects the hitbox's Touched event to the `sellTouchedPaper` function.
  460.         connections.Touch = hitbox.Touched:Connect(function(hit)
  461.             -- Validates if the touched part is a valid paper instance.
  462.             if hit and hit.Parent and hit:IsDescendantOf(papersFolder) and hit:GetAttribute("GUID") then
  463.                 sellTouchedPaper(hit)
  464.             end
  465.         end)
  466.     end
  467.  
  468.     promptConnections[player] = connections -- Stores the created connections.
  469. end
  470.  
  471. -- This function sets up a player when they join the game.
  472. -- It initializes player-specific data tables, connects attribute change signals to clear caches, and sets up selling triggers.
  473. local function onPlayerAdded(player)
  474.     -- Create tables to store their data.
  475.     serverCarriedPapers[player] = {} -- Creates an empty paper stack table for the player.
  476.     paperCapacityCache[player] = nil -- Clears player-specific caches.
  477.     speedReductionCache[player] = nil
  478.     sellCooldowns[player] = nil
  479.  
  480.     -- If they upgrade, we need to clear the cache.
  481.     -- Connects signals to clear caches when "CarryCapacity" or "CarryBalance" attributes change.
  482.     player:GetAttributeChangedSignal("CarryCapacity"):Connect(function() clearCachesOnUpgrade(player, "CarryCapacity") end)
  483.     player:GetAttributeChangedSignal("CarryBalance"):Connect(function() clearCachesOnUpgrade(player, "CarryBalance") end)
  484.  
  485.     task.spawn(connectSellTriggersForPlayer, player) -- Spawns a new task to set up the player's selling triggers.
  486.  
  487.     player.CharacterAdded:Connect(function(character) -- Connects to the CharacterAdded event (triggered when a player's character spawns or respawns).
  488.         forceDropAll(player) -- Forces dropping any leftover papers from a previous life (for a clean start).
  489.         local humanoid = character:WaitForChild("Humanoid")
  490.         humanoid.Died:Connect(function() -- Connects to the Humanoid.Died event (triggered when the player's character dies).
  491.             forceDropAll(player) -- Ensures papers are dropped upon death.
  492.         end)
  493.     end)
  494. end
  495.  
  496. -- This function cleans up data when a player leaves the game.
  497. -- It ensures all associated data and connections are cleared to prevent memory leaks and unnecessary processing.
  498. local function onPlayerRemoving(player)
  499.     forceDropAll(player) -- Forces dropping all carried papers before the player leaves.
  500.  
  501.     -- Clear all data associated with the player.
  502.     serverCarriedPapers[player] = nil
  503.     originalPlayerSpeeds[player] = nil
  504.     sellCooldowns[player] = nil
  505.     paperCapacityCache[player] = nil
  506.     speedReductionCache[player] = nil
  507.  
  508.     -- Disconnects and cleans up selling trigger connections.
  509.     if promptConnections[player] then
  510.         if promptConnections[player].Prompt then promptConnections[player].Prompt:Disconnect() end
  511.         if promptConnections[player].Touch then promptConnections[player].Touch:Disconnect() end
  512.         promptConnections[player] = nil
  513.     end
  514. end
  515.  
  516. -- Connect the functions to game events.
  517. -- Binds the `onPlayerAdded` and `onPlayerRemoving` functions to the respective player events.
  518. Players.PlayerAdded:Connect(onPlayerAdded)
  519. Players.PlayerRemoving:Connect(onPlayerRemoving)
  520.  
  521. -- Set up for any players who are already in the game when the script runs.
  522. -- This loop ensures that `onPlayerAdded` is called for any players already present when the script is initialized,
  523. -- which is important for hot reloads or if the script starts late.
  524. for _, player in ipairs(Players:GetPlayers()) do
  525.     task.spawn(onPlayerAdded, player) -- Spawns a new task to set up each existing player.
  526. end
Advertisement
Add Comment
Please, Sign In to add comment