xyzster

WormHandler Module for Be Mole

Jul 22nd, 2025
939
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 12.98 KB | None | 0 0
  1. local WormManager = {}
  2.  
  3. -- Get required services
  4. local CollectionService = game:GetService("CollectionService")
  5. local ReplicatedStorage = game:GetService("ReplicatedStorage")
  6. local DataStoreService = game:GetService("DataStoreService")
  7. local Players = game:GetService("Players")
  8. local DWPevent = ReplicatedStorage:WaitForChild("Events"):WaitForChild("DoubleWormPotionEvent")
  9. local MarketPlaceService = game:GetService("MarketplaceService")
  10. local ServerStorage = game:GetService("ServerStorage")
  11. local wormStore = DataStoreService:GetDataStore("PlayerWorms")
  12.  
  13.  
  14. local teleportModule = require(script.Parent.TeleportHandler)
  15.  
  16. -- Golden Worm function
  17. local FoundGoldenWorm = ReplicatedStorage.Functions.FoundGoldenWorm
  18.  
  19. -- events
  20. local eventsFolder = ReplicatedStorage.Events
  21. local uiEvent = eventsFolder.UIEvent
  22. local pickUpEvent = ReplicatedStorage.Events.PickUpSound
  23. local wormCountDown = ReplicatedStorage.Events.WormCountdown
  24.  
  25.  
  26. -- positioning for worms
  27. local random = Random.new()
  28.  
  29. -- store of bools if number of worms has changed
  30. local dataChanged = {}
  31.  
  32. local playerWorms = {}
  33.  
  34. -- for storing players with double worm potion or gamepass
  35. local playersWithPotionActive = {}
  36.  
  37. local playersWithGamepass = {}
  38.  
  39. -- worm values
  40. local WORM_VALUE = 1
  41.  
  42. local GOLDEN_WORM_VALUE = 200
  43.  
  44.  
  45. -- xyz coords for terrain to spawn worms
  46. local areaForWorms = workspace["Dig site mountain"].AreaForWorms  
  47.  
  48. local cf = areaForWorms.CFrame
  49. local size = areaForWorms.Size
  50.  
  51. -- Calculate bounds
  52. local WORM_GENERATION_BOUNDS = {
  53.     minX = cf.X - size.X/2,
  54.     maxX = cf.X + size.X/2,
  55.     minZ = cf.Z - size.Z/2,
  56.     maxZ = cf.Z + size.Z/2,
  57.     minY = cf.Y - size.Y/2,
  58.     maxY = cf.Y + size.Y/2
  59. }
  60.  
  61. areaForWorms:Destroy()
  62. -- when re adding worm >> collectedWorms[worm] = nil
  63.  
  64. -- how often worm count should be saved
  65. local AUTOSAVE_INTERVAL = 120
  66.  
  67. -- used to keep track of worms to stop multiple touched fires unintentionally giving more than one worm
  68. local collectedWorms = {}
  69.  
  70.  
  71. -- worm templates
  72. local worm = workspace.Worms.Worm
  73. local goldWorm = workspace.Worms.Golden_Worm
  74.  
  75. -- gamepass stuff
  76. local purchasedGamePass = eventsFolder.PurchasedGamePass
  77. local GAMEPASS_ID = 1261762621
  78.  
  79. -- for reseting the dig area
  80. local wormResetFolder = ServerStorage.WormResetObjects
  81. local Terrain = workspace.Terrain
  82. local AreaForResetTp = workspace["Dig site mountain"].AreaForResetTP
  83.  
  84. -- area for top worms on server leaderboard
  85. local textOfLeaderBoard = workspace:WaitForChild("LeaderBoards"):WaitForChild("TWLeaderboard"):WaitForChild("SurfaceGui"):WaitForChild("TextLabel")
  86.  
  87. -- cache for player names
  88. local playerNameCache = {}
  89.  
  90.  
  91. -- function to find spawn position of worm and spawn
  92. local function findSpawnPosition(newWorm)
  93.     local hitbox = newWorm:FindFirstChild("Hitbox")
  94.     if hitbox and hitbox:IsA("BasePart") then
  95.         newWorm.PrimaryPart = hitbox  
  96.  
  97.         newWorm.Parent = workspace.Worms.CopyOfWorms
  98.  
  99.         -- finding random position to spawn worm at
  100.         local newPos = Vector3.new(
  101.             random:NextInteger(WORM_GENERATION_BOUNDS.minX, WORM_GENERATION_BOUNDS.maxX),
  102.             random:NextInteger(WORM_GENERATION_BOUNDS.minY, WORM_GENERATION_BOUNDS.maxY),
  103.             random:NextInteger(WORM_GENERATION_BOUNDS.minZ, WORM_GENERATION_BOUNDS.maxZ)
  104.         )
  105.  
  106.         -- Use SetPrimaryPartCFrame instead of MoveTo to set exact position
  107.         newWorm:SetPrimaryPartCFrame(CFrame.new(newPos))
  108.     end
  109. end
  110.  
  111.  
  112. -- spawining worms function
  113. local function spawnWorms()
  114.     for i = 1, 2000 do
  115.         -- clones worms
  116.         local newWorm = worm:Clone()
  117.        
  118.         findSpawnPosition(newWorm)
  119.        
  120.         -- spawn golden worms for every three hundered worms
  121.         if (i % 300) == 0 then
  122.             local newWorm = goldWorm:Clone()
  123.  
  124.             findSpawnPosition(newWorm)
  125.         end
  126.     end
  127. end
  128.  
  129.  
  130. -- get players in dig area
  131. local function getPlayersInsideDigArea(area)
  132.     -- get dimensions of part parsed (part encompassing dig area)
  133.     local regionMin = (area.CFrame * CFrame.new(-area.Size / 2)).Position
  134.     local regionMax = (area.CFrame * CFrame.new(area.Size / 2)).Position
  135.  
  136.     -- loop through all players and storea and return ones who are in those dimensions
  137.     local playersInside = {}
  138.     for _, player in pairs(game.Players:GetPlayers()) do
  139.         local character = player.Character
  140.         if character and character:FindFirstChild("HumanoidRootPart") then
  141.             local pos = character.HumanoidRootPart.Position
  142.  
  143.             if pos.X >= math.min(regionMin.X, regionMax.X) and pos.X <= math.max(regionMin.X, regionMax.X)
  144.                 and pos.Y >= math.min(regionMin.Y, regionMax.Y) and pos.Y <= math.max(regionMin.Y, regionMax.Y)
  145.                 and pos.Z >= math.min(regionMin.Z, regionMax.Z) and pos.Z <= math.max(regionMin.Z, regionMax.Z) then
  146.                 table.insert(playersInside, player)
  147.             end
  148.         end
  149.     end
  150.     return playersInside
  151. end
  152.  
  153.  
  154. -- worm spawner
  155. local function wormSpawnAndReset()
  156.     -- infinitely call
  157.     while true do
  158.         -- spawn worms
  159.         spawnWorms()
  160.         -- every 15 minutes fire countdown
  161.         task.wait(895)
  162.         wormCountDown:FireAllClients()
  163.         task.wait(5)
  164.        
  165.         -- destroy all current worm clones
  166.         for _, worm in ipairs(workspace.Worms.CopyOfWorms:GetChildren()) do
  167.             worm:Destroy()
  168.         end
  169.        
  170.         -- clone grass and ground parts
  171.         local grassClone = wormResetFolder.Grass
  172.         local groundClone = wormResetFolder.Ground
  173.        
  174.         -- get players to tp and send them back to the lobby
  175.         local playersToTP = getPlayersInsideDigArea(AreaForResetTp)
  176.        
  177.         for _, Aplayer in ipairs(playersToTP) do
  178.             teleportModule.teleportPlayerToLobby(Aplayer)
  179.         end
  180.        
  181.         grassClone.Parent = workspace
  182.         groundClone.Parent = workspace
  183.         -- get info from ground
  184.         local groundCFrame = groundClone.CFrame
  185.         local groundSize = groundClone.Size
  186.  
  187.         --get info from grass
  188.         local grassCFrame = grassClone.CFrame
  189.         local grassSize = grassClone.Size
  190.  
  191.         -- fill aresa with grass and ground (cframe, size, material)
  192.         Terrain:FillBlock(groundCFrame, groundSize, Enum.Material.Ground)
  193.         Terrain:FillBlock(grassCFrame, grassSize, Enum.Material.Grass)
  194.  
  195.  
  196.         -- delete parts
  197.         groundClone:Destroy()
  198.         grassClone:Destroy()
  199.     end
  200. end
  201.  
  202.  
  203. -- spawn worms on loop in anoter thread
  204. task.spawn(wormSpawnAndReset)
  205.  
  206.  
  207.  
  208. -- function for updating ui
  209. local function updateWormUI(player, wormAmount, doTween, showAlert)
  210.     uiEvent:FireClient(player, {
  211.         sentworm = wormAmount,
  212.         doTween = doTween,
  213.         showAlert = showAlert
  214.     })
  215. end
  216.  
  217.  
  218. -- touching worms function
  219. local function processWormCollect(worm, wormValue, player)
  220.     -- removes the worm to simulate it being collected
  221.     worm.Parent = nil
  222.    
  223.  
  224.     -- add worm to players worm count
  225.     local playerID = player.UserId
  226.     if playerWorms[playerID] then
  227.         -- check if worm is golden by value and makes clean variables for logic
  228.         local isGoldenWorm = wormValue == GOLDEN_WORM_VALUE
  229.         local playerHasPotion = playersWithPotionActive[tostring(player.UserId)]
  230.         local playerOwnsGamepass = playersWithGamepass[tostring(player.UserId)]
  231.        
  232.         -- create and change multiplier depending on what potion/gamepass
  233.         local multiplier = 1
  234.        
  235.         if not isGoldenWorm then
  236.             if playerHasPotion and playerOwnsGamepass then
  237.                 multiplier = 4
  238.             elseif playerHasPotion or playerOwnsGamepass then
  239.                 multiplier = 2
  240.             end
  241.         end
  242.        
  243.         -- update worms
  244.         playerWorms[playerID] += wormValue * multiplier
  245.        
  246.     end
  247.  
  248.     -- update ui to reflect new worm count
  249.     local uiValue = playerWorms[playerID] or 0
  250.     updateWormUI(player, uiValue, true, false)
  251. end
  252.  
  253.  
  254. -- find worms and connect touch detection
  255.  
  256. local goldColour = "#ffcc00"
  257.  
  258. for _, worm in pairs(CollectionService:GetTagged("Worm")) do
  259.     -- get hitbox of worm
  260.     local hitbox = worm:FindFirstChild("Hitbox")
  261.  
  262.     if hitbox then
  263.         hitbox.Touched:Connect(function(otherPart)
  264.             -- find player for part that touched
  265.             local partParent = otherPart.Parent
  266.             local player = Players:GetPlayerFromCharacter(partParent)
  267.            
  268.             -- double check worm hasn't been touched before
  269.             if player and not collectedWorms[worm] then
  270.                 collectedWorms[worm] = true
  271.                
  272.                 -- send message to server if player found a golden worm
  273.                 if CollectionService:HasTag(worm, "GoldenWorm") then
  274.                     local message = "[" .. player.Name .. " has found a golden worm!]"
  275.                     ReplicatedStorage.Events.ChatEvent:FireAllClients(message, goldColour)
  276.                     FoundGoldenWorm:Invoke(player, 1)
  277.                     processWormCollect(worm, GOLDEN_WORM_VALUE, player)
  278.                 else
  279.                     -- plays sound on pickup
  280.                     pickUpEvent:FireClient(player)
  281.                     processWormCollect(worm, WORM_VALUE, player)
  282.                 end
  283.             end
  284.         end)
  285.     end
  286. end
  287.  
  288.  
  289.  
  290. -- saves a player's worm count
  291. local function saveData(dataStorekey, value)
  292.     local success, err = pcall(function()
  293.         wormStore:SetAsync(dataStorekey, value)
  294.     end)
  295.     if not success then
  296.         warn("Error saving worm count")
  297.     end
  298. end
  299.  
  300.  
  301.  
  302. -- Auto saves player worm count when they leave the server
  303. function WormManager.SavePlayer(player)
  304.     local playerID = player.UserId
  305.     if playerWorms[playerID] then
  306.         saveData(playerID, playerWorms[playerID])
  307.     end
  308. end
  309.  
  310.  
  311.  
  312.  
  313. -- sets worm count when a player joins
  314.  
  315. function WormManager.LoadPlayer(player)
  316.     -- check at start if the player has the gamepass
  317.     if MarketPlaceService:UserOwnsGamePassAsync(player.UserId, GAMEPASS_ID) then
  318.         playersWithGamepass[tostring(player.UserId)] = true
  319.     end
  320.    
  321.     local playerID = player.UserId
  322.    
  323.     -- retrive previous worm count
  324.     local success, storedWormCount = pcall(function()
  325.         return wormStore:GetAsync(playerID)
  326.     end)
  327.  
  328.     if success then
  329.         local currentWorms = storedWormCount or 0
  330.         playerWorms[playerID] = currentWorms
  331.  
  332.         -- Initial UI update
  333.         updateWormUI(player, currentWorms, false, false)
  334.  
  335.         -- if the player somehow dies or resets
  336.         player.CharacterAdded:Connect(function()
  337.             task.wait(1)
  338.             local currentWorms = playerWorms[playerID] or 0
  339.             --update ui
  340.             updateWormUI(player, currentWorms, false, false)
  341.         end)
  342.     else
  343.         uiEvent:FireClient(player, { showAlert = true })
  344.     end
  345.  
  346.     -- auto-save every 60 seconds
  347.     coroutine.wrap(function()
  348.         while player and player.Parent do
  349.             task.wait(AUTOSAVE_INTERVAL)
  350.             if dataChanged[playerID] then
  351.                 saveData(playerID, playerWorms[playerID])
  352.                 dataChanged[playerID] = false
  353.             end
  354.         end
  355.     end)()
  356. end
  357.  
  358.  
  359.  
  360. -- Connect player join/leave events, potion and purchasing of gamepass
  361. function WormManager.Init()
  362.     -- double potion
  363.     DWPevent.OnServerEvent:Connect(function(player)
  364.         -- give for 5 mins then remove potion effect of doubling worm
  365.         playersWithPotionActive[tostring(player.UserId)] = true
  366.         task.wait(300)
  367.         playersWithPotionActive[tostring(player.UserId)] = nil
  368.     end)
  369.    
  370.     -- game pass event firing
  371.     purchasedGamePass.OnServerEvent:Connect(function(player)
  372.         -- double check they have the game pass
  373.         if MarketPlaceService:UserOwnsGamePassAsync(player.UserId, GAMEPASS_ID) then
  374.             playersWithGamepass[tostring(player.UserId)] = true
  375.         end
  376.     end)
  377.    
  378.    
  379.     Players.PlayerAdded:Connect(WormManager.LoadPlayer)
  380.     Players.PlayerRemoving:Connect(WormManager.SavePlayer)
  381. end
  382.  
  383.  
  384. -- adding worms ie when buying in shop
  385. function WormManager.AddWorms(player, amount)
  386.     local playerID = player.UserId
  387.     playerWorms[playerID] = (playerWorms[playerID] or 0) + amount
  388.     dataChanged[playerID] = true
  389.  
  390.     -- update UI
  391.     updateWormUI(player, playerWorms[playerID], true, false)
  392. end
  393.  
  394. -- when trying to buy items in shop
  395. function WormManager.DeductWorms(player, amount)
  396.     local playerID = player.UserId
  397.     if not playerWorms[playerID] then
  398.         return false
  399.     end
  400.    
  401.     -- check players worms against amount, if the player has enough deducts and returns true, else returns false
  402.     if playerWorms[playerID] >= amount then
  403.         playerWorms[playerID] -= amount
  404.         dataChanged[playerID] = true
  405.  
  406.         -- update ui
  407.         updateWormUI(player, playerWorms[playerID], true, false)
  408.    
  409.         return true
  410.     else
  411.         return false -- not enough worms
  412.     end
  413. end
  414.  
  415.  
  416.  
  417. -- get name of player from id
  418. local function getPlayerName(userId)
  419.     if playerNameCache[userId] then
  420.         return playerNameCache[userId]
  421.     end
  422.  
  423.     local success, name = pcall(function()
  424.         return Players:GetNameFromUserIdAsync(userId)
  425.     end)
  426.  
  427.     if success and name then
  428.         playerNameCache[userId] = name
  429.         return name
  430.     else
  431.         return "Unknown"
  432.     end
  433. end
  434.  
  435. -- get top five players by worm count
  436. local function getTop5Players(playerWorms)
  437.     local topList = {}
  438.  
  439.     -- Convert dictionary to sortable array
  440.     for userId, wormCount in pairs(playerWorms) do
  441.         local playerName = getPlayerName(userId)
  442.  
  443.         table.insert(topList, {
  444.             PlayerName = playerName,
  445.             Worms = wormCount
  446.         })
  447.     end
  448.  
  449.     -- Sort descending by worm count
  450.     table.sort(topList, function(a, b)
  451.         return a.Worms > b.Worms
  452.     end)
  453.  
  454.     -- Return only the top 5
  455.     local top5 = {}
  456.     for i = 1, math.min(5, #topList) do
  457.         table.insert(top5, topList[i])
  458.     end
  459.  
  460.     return top5
  461. end
  462.  
  463. -- updates teh leaderboard every 30 seconds
  464. local function updateLeaderBoard()
  465.     while true do
  466.         textOfLeaderBoard.Text = ""
  467.         local topFive = getTop5Players(playerWorms)
  468.         for rank, data in ipairs(topFive) do
  469.             textOfLeaderBoard.Text = textOfLeaderBoard.Text .. string.format("%d. %s - %d\n", rank, data.PlayerName, data.Worms)
  470.         end
  471.  
  472.         task.wait(30)
  473.     end
  474. end
  475.  
  476. -- run leaderboard updater in parallel thread
  477. task.spawn(updateLeaderBoard)
  478.  
  479.  
  480.  
  481. return WormManager
Advertisement
Add Comment
Please, Sign In to add comment