Possible bug? Model Boolean Attribute Not Changing With :SetAttribute()

Hi.

I have made an item system in my game that edits attributes within the item’s model on the server side.

When the player clicks on the item, the model goes invisible and an attribute called “clickable” is supposed to change to “false”. This used to work, but is currently not working.

I think this is a potential “bug”, because the other attribute called “Cost” that holds a number DOES change as it should.

Here is a video of what is happening:

Here is the script where the Boolean is supposed to change upon a remote fire. The “item”, is the correct item according to what the client and server prints out.

if item:GetAttribute("Clickable") and not item:HasTag("Money") then
		player:SetAttribute("Item", item.Name)
		item:SetAttribute("Clickable", false) -- Sets here
		
		print(item, item.Name, item:GetAttribute("Clickable"), item.Parent)
		
		local itemClone = getItem(item.Name);
		itemClone.Parent = character
		local itemPrimary = itemClone.PrimaryPart;
		toggleAnchor(false, itemClone)
		
		local motor6D = Instance.new("Motor6D");
		motor6D.Name = "HeadAttachment"
		motor6D.Parent = itemClone
		motor6D.Part0 = playerHead
		motor6D.Part1 = itemPrimary
		motor6D.C0 = CFrame.new(0,0,0)
		local x,y,z = playerHrp.CFrame:ToOrientation();
		motor6D.C1 = CFrame.new(0,-1.35 - itemPrimary.Size.Y/2, 0) * CFrame.Angles(0,math.rad(y),0)
		
		toggleTransparency(1, item)
		
		for i, v in pairs(item:GetDescendants()) do
			if v:IsA("TextLabel") then
				v.TextTransparency = 1
			end
		end
	end

EDIT: I’m tired and what I did in that video with the cost attribute wasn’t what I meant to show. If I set the cost value via server script, it still changes, unlike the Boolean.

When I print the attributes before and after, they both change. But, the model does not change for the boolean.
RobloxStudioBeta_nUMBkRUhTa

Thank you for any possible solutions and assistance.

1 Like

The only thing that comes to mind is another script resetting the attribute. If that occurs it won’t be picked up by your print because you aren’t yielding the code, and because at least one frame is required for the event to detect the change, it will appear as false in the print even if exactly after it resets to true. To check for that add a task.wait(2) above the print and see if it keeps saying false or it changes to true.

If it keeps being false then perhaps a replication issue or checking a different instance/item when checking the model directly in the browser?

1 Like

So I just wrote a test bed to see if there actually was any live bug, but it all looks good from my end, e.g.

Server Example
local Players = game:GetService('Players')
local ReplicatedStorage = game:GetService('ReplicatedStorage')
local CollectionService = game:GetService('CollectionService')


--[!] const
local CLICKABLE_TAG = 'ClickableItem'   -- i.e. the CollectionTag associated with these item(s)
local CLICKABLE_MAX_DIST = 30           -- i.e. max distance the character can be away from the item
local CLICKABLE_RESET_TIME = 5          -- i.e. how long to wait, in seconds, before we reset the clickable state / transparency


--[!] decl
local RemoteFolder
local RemoteClickable

local ClickableItems = { }


--[!] setup
RemoteFolder = Instance.new('Folder')
RemoteFolder.Name = 'Networking'
RemoteFolder.Parent = ReplicatedStorage

RemoteClickable = Instance.new('RemoteEvent')
RemoteClickable.Name = 'ClickableItemRemote'
RemoteClickable.Parent = RemoteFolder


--[!] utils
local function validatePlayerCharacter(player)
  if typeof(player) ~= 'Instance' or not player:IsDescendantOf(Players) then
    return false
  end

  local character = player.Character
  if typeof(character) ~= 'Instance' or not character:IsA('Model') or not character:IsDescendantOf(workspace) then
    return false
  end

  local humanoid = character:FindFirstChildOfClass('Humanoid')
  local humanoidState = humanoid and humanoid:GetState() or Enum.HumanoidStateType.Dead
  local humanoidRootPart = humanoid and humanoid.RootPart or nil
  if humanoidState == Enum.HumanoidStateType.Dead or not humanoidRootPart then
    return false
  end

  return true, character, humanoid, humanoidRootPart
end


--[!] methods
local function handleClickableItem(obj)
  -- record our object
  local transform
  if obj:IsA('Model') then
    transform = obj:GetPivot()
  elseif obj:IsA('BasePart') then
    transform = obj.CFrame
  end

  if not transform then
    return
  end

  local isClickable = not not obj:GetAttribute('Clickable')
  table.insert(ClickableItems, obj)
  table.insert(ClickableItems, isClickable)
  table.insert(ClickableItems, transform)

  -- i.e. just to see if things are changing
  --      in reality we should clean up these connections when the instance tag is removed
  local connection
  connection = obj:GetAttributeChangedSignal('Clickable'):Connect(function ()
    local clickable = obj:GetAttribute('Clickable')
    print('[Server: CHANGE]', obj:GetFullName(), '->', clickable)
  end)
end

local function handleClickEvent(player, obj)
  -- confirm the player is alive
  local validated, character, humanoid, rootPart = validatePlayerCharacter(player)
  if not validated then
    return false
  end

  -- validate the object
  if typeof(obj) ~= 'Instance' then
    return false
  end

  local index = table.find(ClickableItems, obj)
  if index then
    -- confirm we have all record(s) required
    local clickable = ClickableItems[index + 1]
    local transform = ClickableItems[index + 2]
    if typeof(transform) ~= 'CFrame' then
      return false
    end

    -- confirm that it's currently clickable
    if not clickable then
      return false
    end

    -- validate the click object
    --   i.e. is it close enough to the player to be clickable?
    local distance = (transform.Position - rootPart.Position).Magnitude
    if distance > CLICKABLE_MAX_DIST then
      return false
    end

    -- make it no longer clickable
    ClickableItems[index + 1] = false
    obj:SetAttribute('Clickable', false)

    -- make our object and any descendants transparent
    local transparency = { }
    local objs = obj:GetDescendants()
    table.insert(objs, 1, obj)

    for _, inst in next, objs do
      if not inst:IsA('BasePart') and not inst:IsA('Texture') and not inst:IsA('Decal') then
        continue
      end

      transparency[inst] = inst.Transparency
      inst.Transparency = 1
    end

    -- reset the click state & make them visible again
    task.delay(CLICKABLE_RESET_TIME, function ()
      for inst, value in next, transparency do
        inst.Transparency = value
      end

      obj:SetAttribute('Clickable', true)
      ClickableItems[index + 1] = true
    end)

    return true
  end

  return false
end


--[!] init

-- handle clickable item(s)
for _, obj in next, CollectionService:GetTagged(CLICKABLE_TAG) do
  handleClickableItem(obj)
end

CollectionService:GetInstanceAddedSignal(CLICKABLE_TAG):Connect(handleClickableItem)

-- handle event(s)
RemoteClickable.OnServerEvent:Connect(handleClickEvent)

Client Example
local Players = game:GetService('Players')
local CollectionService = game:GetService('CollectionService')
local ReplicatedStorage = game:GetService('ReplicatedStorage')
local ContextActionService = game:GetService('ContextActionService')


--[!] const
local CLICKABLE_TAG = 'ClickableItem'   -- i.e. the CollectionTag associated with these item(s)
local CLICKABLE_MAX_DIST = 30           -- i.e. max distance the character can be away from the item
local CLICKABLE_RAY_DIST = 100          -- i.e. the maximum distance of the ray when clicking an item
local CLICKABLE_BINDINGS = {            -- i.e. the keybindings associated with the click action
  Enum.KeyCode.ButtonR2,                --  -> gamepad
  Enum.UserInputType.Touch,             --  -> mobile
  Enum.UserInputType.MouseButton1,      --  -> desktop
}


--[!] utils
local function tryGetCharacter(player)
  if typeof(player) ~= 'Instance' or not player:IsDescendantOf(Players) then
    return nil
  end

  local character = player.Character
  if typeof(character) ~= 'Instance' or not character:IsA('Model') or not character:IsDescendantOf(workspace) then
    return nil
  end

  local humanoid = character:FindFirstChildOfClass('Humanoid')
  local humanoidState = humanoid and humanoid:GetState() or Enum.HumanoidStateType.Dead
  local humanoidRootPart = humanoid and humanoid.RootPart or nil
  if humanoidState == Enum.HumanoidStateType.Dead or not humanoidRootPart then
    return false
  end

  return character, humanoid, humanoidRootPart
end

local function getClickableFromInst(obj)
  -- i.e. get the model / part with the `CLICKABLE_TAG`
  --      as well as one that has a currently clickable attribute
  if typeof(obj) ~= 'Instance' or not obj:IsDescendantOf(workspace) then
    return false, nil
  end

  local target
  while not target and obj do
    if obj:HasTag(CLICKABLE_TAG) then
      target = obj
      break
    end

    obj = obj.Parent
  end

  if target and target:GetAttribute('Clickable') then
    return true, target
  end

  return false, nil
end


--[!] decl
local player = Players.LocalPlayer
local camera = workspace.CurrentCamera

local RemoteFolder = ReplicatedStorage:WaitForChild('Networking')
local RemoteClickable = RemoteFolder:WaitForChild('ClickableItemRemote')


--[!] state
local clickableTarget


--[!] methods
local function handleClickableItem(obj)
  -- i.e. just to see if things are changing
  --      in reality we should clean up these connections when the instance tag is removed
  local connection
  connection = obj:GetAttributeChangedSignal('Clickable'):Connect(function ()
    local clickable = obj:GetAttribute('Clickable')
    print('[CLIENT: CHANGE]', obj:GetFullName(), '->', clickable)
  end)
end

local function handleAction(action, inputState, inputObject)
  if inputState == Enum.UserInputState.Change then
    return Enum.ContextActionResult.Pass
  end

  local character, humanoid, rootPart = tryGetCharacter(player)
  if not character then
    return Enum.ContextActionResult.Pass
  end

  local inputDown = inputState == Enum.UserInputState.Begin
  local inputType = inputObject.UserInputType

  -- i.e. get our mouse/touch position
  --      or center of screen gamepad
  local position
  if inputType == Enum.UserInputType.Gamepad1 then
    position = camera.ViewportSize * 0.5
  else
    position = inputObject.Position
  end

  -- cast a ray to find the clickable obj (if any)
  local rayParams = RaycastParams.new()
  rayParams.FilterType = Enum.RaycastFilterType.Exclude
  rayParams.FilterDescendantsInstances = { character }

  local ray = camera:ScreenPointToRay(position.X, position.Y)
  local result = workspace:Raycast(ray.Origin, ray.Direction*CLICKABLE_RAY_DIST, rayParams)
  local instance = result and result.Instance or nil
  if not instance then
    return Enum.ContextActionResult.Pass
  end

  -- validate the object
  local distance = (instance.Position - rootPart.Position).Magnitude
  if distance > CLICKABLE_MAX_DIST then
    return Enum.ContextActionResult.Pass
  end

  local isClickable, object = getClickableFromInst(instance)
  if not isClickable then
    return Enum.ContextActionResult.Pass
  end

  -- start click by setting our target to the object
  if inputDown then
    clickableTarget = object
    return Enum.ContextActionResult.Sink
  end

  -- finalise click
  --   i.e. make sure it's the same object (in case player moves mouse away during click)
  if clickableTarget == object then
    RemoteClickable:FireServer(object)
  end

  return Enum.ContextActionResult.Sink
end

--[!] init

-- handle clickable item(s)
for _, obj in next, CollectionService:GetTagged(CLICKABLE_TAG) do
  handleClickableItem(obj)
end

CollectionService:GetInstanceAddedSignal(CLICKABLE_TAG):Connect(handleClickableItem)

-- handle action(s)
ContextActionService:BindAction('ClickItemAction', handleAction, false, table.unpack(CLICKABLE_BINDINGS))


  1. Does your cheese model have decals/textures that aren’t being made transparent?

  2. Based on the output you showed: it does look like the clickable attribute is actually changing?

  3. What do you mean by “the model does not change for the boolean”? Did you mean the property window? Or do you mean the fact that you can still see the “price” GUI that pops up when you hover?

I tried this and it does come back as true. Thank you, I must be changing it somewhere, I just don’t know where exactly yet.

1 Like

Thank you for the reply! It seems I am changing the value back to true somewhere else.