Will this code cause performance issues

This is part of my script inside a sword, this will run every time a player dies. I was just wondering if calling the UserOwnsGamepassAsync every time a player dies will cause performance issues and if theres a better way to do it?

        humanoid.Died:Connect(function()
			
			deaths.Value += 1
			
			local killer = humanoid:FindFirstChild("creator")
			
			if killer and killer.Value then
				if game:GetService("MarketplaceService"):UserOwnsGamePassAsync(killer.Value.UserId, 98681915) then
					killer.Value.leaderstats.Kills.Value += 2
				else
					killer.Value.leaderstats.Kills.Value += 1
				end
			end
		end)
4 Likes

There’s definitely a better way.
When the player joins, use UserOwnsGamepassAsync (in a pcall). If it fails, kick the player.

If they own the pass, make a BoolValue in their player object, or set an attribute, or something like that. Then, you should check for that attribute/value instead of calling UserOwnsGamepassAsync each time.

3 Likes

Thank you very much, that all makes sense, the only thing is i’m not very familiar with pcalls and I don’t know how I would script that. Could I not just use an if statement?

1 Like

Yeah, but I’m pretty sure UserOwnsGamePassAsync can fail (nearly anything ending in Async can fail, it’s an indicator you need a pcall).

It can look something like this:

local mps = game:GetService("MarketplaceService")

local success, owned = pcall(mps.UserOwnsGamePassAsync, mps, player.UserId, PASS_ID_HERE)

if success and owned then
     --they own the pass
    player:SetAttribute("GamepassOwned", true)
elseif not success then
     player:Kick("Failed to retrieve gamepass data. Please rejoin!")
else
    --they don't own the pass
    player:SetAttribute("GamepassOwned", false)
end

or:

local owned
local success, err = pcall(function()
    owned = mps:UserOwnsGamePassAsync(player.UserId, PASS_ID_HERE)
end)

Here’s the documentation:
Lua Globals: pcall | Documentation - Roblox Creator Hub

2 Likes

Since UserOwnsGamePassAsync caches the result it returns and the script is a child of the sword tool, I think storing the value returned by the method in a constant variable at the top of the script, then checking the value in the if statement, should be enough to get the desired result without having to call it more than once


@rafferty05

LocalScript example:

local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")

local gamepassOwned do
	local success, result = pcall(
		MarketplaceService.UserOwnsGamePassAsync,
		MarketplaceService,
		Players.LocalPlayer.UserId, -- or: killer.Value.UserId
		98681915
	)

	if success then
		gamepassOwned = result
	end
end

Server script example:

local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")

local tool = script.Parent

local gamepassOwned = false

tool.Equipped:Once(function()
	local success, result = pcall(
		MarketplaceService.UserOwnsGamePassAsync,
		MarketplaceService,
		Players:GetPlayerFromCharacter(tool.Parent).UserId,
		98681915
	)

	if success then
		gamepassOwned = result
	end
end)
1 Like

Store the info on the gamepass as soon as they join, and only update that info after a gamepasspurchase happens. You don’t need to check every time they die because its not changing unless a gamepasspurchase happens, which you can code as an event.

The problem with that is the script is not running for the killer, it is running for the victim. That would be less efficient, because you would have to check for every single player when they join whether they own the gamepass, and store it as a variable and compare to it on humanoid death.

yeah, it is better than checking the actual state of the gamepass every death…

Why is the script responsible for awarding the killer points is running on the victim’s side, wouldn’t it be better to have it work on the killer’s sword?

JohhnyLegoKing is right. The damagescript should take place on the killer.

1 Like

I don’t know. I was just providing a sample that would go with what @rafferty05 already had, but it would be better on the killer’s sword.

I didn’t write it.

1 Like

I just use the basic roblox sword script. When a player dies to a sword it applies a tag to the dead player, which gives the player that killed them. I then just search for this tag when a player dies, and add leaderstats to the killer

You can keep the tag if you prefer, so that you can use it as a way to be able to inform the victim which player killed them in a Gui


@rafferty05 Something like this should work to achieve what you’re looking for (It needs to be a server Script that’s inside the sword tool):

Original code
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")

local tool = script.Parent
local animation = -- The sword animation
local handle = tool.Handle

local alreadyHit = {}

local debounce = false

local overlapParams

local player

local track

tool.Equipped:Once(function()
	local character = tool.Parent

	overlapParams = OverlapParams.new() -- Filters out the player themselves
	overlapParams.FilterDescendantsInstances = {character}
	overlapParams.FilterType = Enum.RaycastFilterType.Exclude

	player = Players:GetPlayerFromCharacter(character)

	track = character.Humanoid.Animator:LoadAnimation(animation) -- Loads the sword animation
end)

tool.Activated:Connect(function()
	if debounce then return end
	debounce = true

	track:Play()

	while true do
		if track.IsPlaying then
			for _, basePart in Workspace:GetPartsInPart(handle, overlapParams) do
				local model = basePart:FindFirstAncestorOfClass("Model")

				if model and not alreadyHit[model] then
					local humanoid = model:FindFirstChildWhichIsA("Humanoid")

					if humanoid then
						alreadyHit[model] = true -- This is a way to limit damage taken by other players until the swords' debounce is over

						humanoid:TakeDamage(20)

						if humanoid.Health <= 0 then
							humanoid:AddTag(player.UserId)
						end
					end
				end
			end
		else
			break
		end

		task.wait()
	end

	-- task.wait(2) (Optional extra wait time)

	table.clear(alreadyHit)

	debounce = false
end)

(RunService could be safely left out since I didn’t end up using it in this case)


@rafferty05 I modified the script to include the gamepass, changed the tag into an attribute, and added how to update the leaderstats:

local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local Workspace = game:GetService("Workspace")

local tool = script.Parent
local animation = -- The sword animation
local handle = tool.Handle

local alreadyHit = {}

local debounce = false

local gamepassOwned = false

local overlapParams

local player

local track

tool.Equipped:Once(function()
	local character = tool.Parent

	overlapParams = OverlapParams.new() -- Filters out the player themselves
	overlapParams.FilterDescendantsInstances = {character}
	overlapParams.FilterType = Enum.RaycastFilterType.Exclude

	player = Players:GetPlayerFromCharacter(character)

	track = character.Humanoid.Animator:LoadAnimation(animation) -- Loads the sword animation

	local success, result = pcall(
		MarketplaceService.UserOwnsGamePassAsync,
		MarketplaceService,
		player.UserId,
		98681915
	)

	if success then
		gamepassOwned = result
	end
end)

tool.Activated:Connect(function()
	if debounce then return end
	debounce = true

	track:Play()

	while true do
		if track.IsPlaying then
			for _, basePart in Workspace:GetPartsInPart(handle, overlapParams) do
				local model = basePart:FindFirstAncestorOfClass("Model")

				if model and not alreadyHit[model] then
					local humanoid = model:FindFirstChildWhichIsA("Humanoid")

					if humanoid then
						alreadyHit[model] = true -- This is a way to limit damage taken by other players until the swords' debounce is over

						humanoid:TakeDamage(20)

						if humanoid.Health <= 0 then
							Players:GetPlayerFromCharacter(model):SetAttribute("Killer", player)

							if gamepassOwned then
								player.leaderstats.Kills.Value += 2
							else
								player.leaderstats.Kills.Value += 1
							end
						end
					end
				end
			end
		else
			break
		end

		task.wait()
	end

	-- task.wait(2) (Optional extra wait time)

	table.clear(alreadyHit)

	debounce = false
end)
1 Like

Thanks, how would I actually check the attributes value inside the player as I keep getting an error message that it doesn’t exist

You should also add marketplaceService.PromptGamePassPurchaseFinished:Connect() and update the player’s Owned attribute, because if they’ve bought it they’ll have to rejoin to claim it.

2 Likes

You can take advantage of MarketplaceService events to avoid making multiple calls. Here’s an example generalized for all your game gamepasses:

--Gamepass handler inside ServerScriptService
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")

--your game gamepasses
local gamepasses = {}

--retry logic
local function gamepassOwned(userId: number, gamepassId: number): boolean
	local success, response = pcall(function()
		return MarketplaceService:UserOwnsGamePassAsync(userId, gamepassId)
	end)
	if success then return response end
	warn(response)
	task.wait(1)
	return gamepassOwned(userId, gamepassId)
end

local function addGamepass(player: Player, gamepass: number): ()
	local current = player:GetAttribute("gamepasses")
	if not current then
		player:SetAttribute("gamepasses", tostring(gamepass))
		return
	else
		if current:find(tostring(gamepass)) then return end
		player:SetAttribute("gamepasses", current..","..gamepass)
	end
end

Players.PlayerAdded:Connect(function(player)
	for _, gamepass in pairs(gamepasses) do 
		if not gamepassOwned(player.UserId, gamepass) then continue end
		addGamepass(player, gamepass)
	end
	if not player:GetAttribute("gamepasses") then
		player:SetAttribute("gamepasses", "")
	end
	player:SetAttribute("gpLoaded", true)
end)

MarketplaceService.PromptGamePassPurchaseFinished:Connect(function(plr, gamepass, success)
	if success then addGamepass(plr, gamepass) end
end)

Your new optimized code:

local function hasPass(plr: Player, gamepass: number): boolean
	--Waiting for gamepasses to finish loading, if they haven't
	while not plr:GetAttribute("gpLoaded") do
		task.wait(0.5)
	end
	return not not plr:GetAttribute("gamepasses"):find(tostring(gamepass))
end

humanoid.Died:Connect(function()
	deaths.Value += 1
	local killer = humanoid:FindFirstChild("creator")
	if not killer or not killer.Value then return end
	local owned = hasPass(killer.Value, 98681915)
	killer.Value.leaderstats.Kills.Value += owned and 2 or 1
end)
1 Like

Just set it on joining, or like @Sweetbunny12475 said, after PromptGamePassPurchaseFinished fires.

An example:

local mps = game:GetService("MarketplaceService")
local players = game:GetService("Players")
local pass = 00000000 --pass id here

local function addPassBenefits(player:Player, owns:boolean)
    player:SetAttribute("OwnsGamepass", owns)
end

local function checkOnPrompt(player, product, purchased)
    if purchased and product == pass then
        addPassBenefits(player, true)
    end
end

local function checkForPass(player:Player)
    local success, owned = pcall(mps.UserOwnsGamePassAsync, mps, player.UserId, pass)
    if success and owned then
        addPassBenefits(player, true)
    elseif success and not owned then
        addPassBenefits(player, false)
    else
        player:Kick("Failed to retrieve gamepass data. Please rejoin!")
    end
end

players.PlayerAdded:Connect(checkForPass)
mps.PromptGamePassPurchaseFinished:Connect(checkOnPrompt)
2 Likes

Thank you but sorry I meant how would I check the attributs value. Like would I do workspace[player.Name].GamepassOwned.Value == true

if workspace[player.Name].GamepassOwned.Value == true then
   --Code
end

This didn’t work so I’m working how to do it

Since we are assigning the attribute to the player, we can just use GetAttribute.

--Can't use "if player:GetAttribute('OwnsGamepass')" on its own because that would just check if the attribute exists.

if player:GetAttribute("OwnsGamepass") == true then
    --they own it :D
end
1 Like

It’s working perfectly, thank you very much :slight_smile:

1 Like