How do I add more randomized items?

  1. What do you want to achieve?
    I’d like to have the battery item (and potentially other items) spawn in randomized locations in the furniture pieces while also ensuring that if it’s a locked room, a key will also spawn.

  2. What is the issue?
    I’m quite new to scripting so I’m not entirely sure how to do what I’m trying to accomplish. I’ll insert the code I’ve written thus far but I would like some help with trying to figure out how I can add more items to the table within my script. (See below for the scripts)

  3. What solutions have you tried so far?
    I’ve posted in discord servers & tried to find videos to accomplish what I’m trying to accomplish; however, there is no specific resource I can find.

Workspace

ServerScriptsService

Screen Shot 2022-11-29 at 6.18.37 PM

Folders

Folders (includes Item Folder w/ Items)

Scripts

Item Script
local item = {}

function item.Interact(player, prompt, template, itemName)
	if player.Character then
		if itemName == "Key" then
			local tool = workspace.Items.Key:Clone()
			tool.Parent = player.Character
			tool.Handle.KeyJingle:Play()
		end
		
		prompt.Enabled = false
		template:Destroy()
	end
end
function item.New(location, itemName)
	
	local itemObject = workspace.Items:FindFirstChild(itemName)
	
	if itemObject then
		local itemHandle = itemObject.Handle:Clone()
		itemHandle.Position = location.WorldPosition
		
		local weld = Instance.new("WeldConstraint")
		weld.Part0 = itemHandle
		weld.Part1 = location.Parent
		weld.Parent = itemHandle
		
		itemHandle.Parent = location
		
		local prompt = Instance.new("ProximityPrompt")
		prompt.ActionText = ""
		prompt.MaxActivationDistance = 5
		prompt.Parent = location
		prompt.Style = "Custom" --added
		
		prompt.Triggered:Connect(function(player)
			item.Interact(player, prompt, itemHandle, itemName)
		end)
	end
end
return item
Furniture Script
local TweenService = game:GetService("TweenService")
local furniture = {}
local closet = require(script.Closet)

function furniture.OpenDrawer(drawer)
	drawer:SetAttribute("Moving", true)
	
	local isOpen = drawer:GetAttribute("Open")
	local direction = isOpen and 1 or -1
	
	local cframe = drawer.CFrame * CFrame.new(0,0,2 * direction)
	local drawerTween = TweenService:Create(drawer, TweenInfo.new(0.5),{CFrame =cframe})
	
	drawer.Move:Play()
	drawerTween:Play()
	drawerTween.Completed:Wait()
	drawer:SetAttribute("Moving", false)
	drawer:SetAttribute("Open", not isOpen)
end

function furniture.New (template, roomModel)
	local furnitureModel = workspace.Furniture:FindFirstChild(template.Name)
	
	if furnitureModel then
		furnitureModel = furnitureModel:Clone()
		furnitureModel:PivotTo(template.CFrame)
		
		local itemLocations = {}
		
		for _,descendant in furnitureModel:GetDescendants() do
			if descendant:IsA("Attachment") and descendant.Name=="Location" then
				table.insert(itemLocations, descendant)	
			end
		end
		
		
		if furnitureModel:FindFirstChild("Drawers") then
			for i, drawer in ipairs(furnitureModel.Drawers:GetChildren()) do
				
				drawer:SetAttribute("Open", false)
				drawer:SetAttribute("Moving", false)
				
				local prompt = Instance.new("ProximityPrompt")
				prompt.ActionText = ""
				prompt.MaxActivationDistance = 5
				prompt.Parent = drawer.Toggle
				prompt.Style = "Custom" --added
				
				prompt.Triggered:Connect(function()
					if drawer:GetAttribute("Moving") == false then
						furniture.OpenDrawer(drawer)
					end
				end)
			end
		end
		furnitureModel.Parent = template.Parent
		template:Destroy()
		
		return itemLocations
	end
end
function furniture.FurnishRoom(roomModel)
	
	local roomItemLocations = {}
	
	if roomModel:FindFirstChild("Furniture") then
		local templates = roomModel.Furniture:GetChildren()
		for i, part in ipairs(templates) do
			if part.Name == "Locker" then
				--create new closet
				closet.New(part)
				else
			local locations = furniture.New(part, roomModel)
			if locations then
				for index, value in ipairs(locations) do
				table.insert(roomItemLocations, value)
			end
		end
	end
		end
	end
		if #roomItemLocations > 0 then
			return roomItemLocations
		end
end
return furniture
Room Script
local TweenService = game:GetService("TweenService")

local door = require(script.Door)
local furniture = require(script.Furniture)
local item = require(script.Item)

local room = {}
room.info = require(script.RoomInfo)
room.lastTurnDirection = nil
room.random = Random.new()

function room.FlickerLight(lightPart)
	local info = TweenInfo.new(0.2, Enum.EasingStyle.Elastic, Enum.EasingDirection.InOut, 1, false)
	local flickOn = TweenService:Create(lightPart.PointLight, info, {Brightness = .5})
	local flickOff = TweenService:Create(lightPart.PointLight, info, {Brightness = 0})
	
	flickOff:Play()
	flickOff.Completed:Wait()
	lightPart.Material = Enum.Material.Glass
	
	flickOn:Play()
	flickOn.Completed:Wait()
	lightPart.Material = Enum.Material.Neon
	
	flickOff:Play()
	flickOff.Completed:Wait()
	lightPart.Material = Enum.Material.Glass
	
end

function room.Blackout (roomModel)
	local lights = roomModel.Lights:GetChildren()
	for i, light in ipairs(lights) do
		for i, obj in ipairs(light:GetChildren()) do
			if obj.Name == "Shade" then
				task.spawn(function()
				room.FlickerLight(obj)
					
				end)
			end
		end
	end
end
	
	function room.GetRandom(PrevRoom)
	local totalWeight = 0
	for i, info in pairs(room.info) do
		totalWeight += info.Weight
	end
	
	local randomWeight = room.random:NextNumber(0, totalWeight)
	local currentWeight = 0
	local randomRoom = nil
	for i, info in pairs(room.info) do
		currentWeight += info.Weight
		if randomWeight <= currentWeight then
			randomRoom = workspace.Rooms[i]
			break
		end
	end
		--[1]Nxt room must be dif than prev room
		--[2]if corner then next turn other way
		--[3]if prev room = stairs, then next cannot
		
		local direction = room.info[randomRoom.Name]["Direction"]
		local hasStairs = room.info[randomRoom.Name]["Stairs"]
		local prevHadStairs = room.info[PrevRoom.Name]["Stairs"]
		
		if (PrevRoom.Name == randomRoom.Name) 	
			or (direction and direction == room.lastTurnDirection)
			or (hasStairs and prevHadStairs) 
		then
			return room.GetRandom(PrevRoom)
		else
			if direction then 
				room.lastTurnDirection = direction
			end
			
			return randomRoom
		end
	end
function room.Generate(PrevRoom, number)
	local randomRoom = room.GetRandom(PrevRoom)
	local newRoom = randomRoom:Clone()

	newRoom.PrimaryPart = newRoom.Entrance
	newRoom:PivotTo (PrevRoom.Exit.CFrame)
	newRoom.Entrance.Transparency = 1
	newRoom.Exit.Transparency = 1
	
	local requiresKey = false
	local locations = furniture.FurnishRoom(newRoom)
	if locations then
		if room.random:NextInteger(1, 3) == 3 then
			local random = room.random:NextInteger(1, #locations)
			local randomLocation = locations[random]
			requiresKey = true
			
			item.New(randomLocation, "Key")	
		end
	end
	local newDoor = door.New(newRoom, number, requiresKey)
	
	newRoom.Parent = workspace.GeneratedRooms

	return newRoom
	end

	return room

Let me know if you’d like me to provide more information that would be useful to help solve my issue! :slight_smile:

This previous forum post of mine (related to a similar question) may also provide some more context to this issue!

I REALLY appreciate your assistance and help :heart: You are all geniuses & have helped me learn so much already about scripting :smiley:

Just do it how you did it before simple

1 Like

Thank you for your feedback; however, it’s a bit more complicated (in my opinion).

The ‘Key’ item is a tool, while the ‘Battery’ item is just a model that if the player clicks on, the flashlight battery will recharge.

Therefore, I don’t believe I can just do it as I did before — also, I’m not entirely sure where to add the name of the item or which script to insert that information based on the answer you’ve provided.

I’m guessing the part that you’ll need to change is this one:

local locations = furniture.FurnishRoom(newRoom)
if locations then
	if room.random:NextInteger(1, 3) == 3 then
		local random = room.random:NextInteger(1, #locations)
		local randomLocation = locations[random]
		requiresKey = true

		item.New(randomLocation, "Key")
	end
end

One issue I see here is that a room can be made locked, even if there’s no locations for a key to spawn. So before any items are generated, you’ll need to check to see if there’s at least one spawn location. One way is to just generate the key first inside a loop. If the loop never runs because there’s no spawn locations, then the key will never spawn and the room will never be locked (which is what you’d want):

local itemsThatCanSpawn = {"Battery"}

local locations = furniture.FurnishRoom(newRoom)
if locations then
	local shouldBeLockedRoomed = room.random:NextNumber() < (1/4)
	-- the odds of a room being locked is 1/4
	
	for index,itemLocation in locations do
		--guarantees that there will always be a key
		--if the room is supposed to be locked
		if index==1 and shouldBeLockedRoomed then
			requiresKey = true
			item.New(itemLocation, "Key")

			--moves on to trying to spawn the next item
			continue
		end

		--spawns the other items like batteries
		local shouldSpawnAnItem = room.random:NextNumber() < (1/3)
		if shouldSpawnAnItem then
			local itemToSpawn = itemsThatCanSpawn[room.random:NextInteger(1, #itemsThatCanSpawn)]
			item.New(itemLocation, itemToSpawn)
		end
	end
end

I haven’t tested it, but something like above should work.

EDIT:

The only small problem with this method is that the key will more than most likely always spawn at the same location. One way you can prevent this is by shuffling/mixing the locations array before the loop:

for i = #locations, 2, -1 do
	local j = room.random:NextInteger(1, i)
	locations[i], locations[j] = locations[j], locations[i]
end
1 Like

I see, thank you!

I’ll try to insert that script within the room script now & let you know if I have any issues. Or should I place that into the Item script instead?

It makes more sense to be inside the room script. However, you might want to find an automated way to create the itemsThatCanSpawn array. I’d personally use attributes on the items and have a function inside the item module that looks at those attributes and returns the names of non-special items (meaning the key item would be excluded). The attribute could be something like IsSpecial and you just put that on the key item with a value of true.

1 Like

Okay, so instead of

function room.Generate(PrevRoom, number)
	local randomRoom = room.GetRandom(PrevRoom)
	local newRoom = randomRoom:Clone()

	newRoom.PrimaryPart = newRoom.Entrance
	newRoom:PivotTo (PrevRoom.Exit.CFrame)
	newRoom.Entrance.Transparency = 1
	newRoom.Exit.Transparency = 1
	
	local requiresKey = false
	local locations = furniture.FurnishRoom(newRoom)
	if locations then
		if room.random:NextInteger(1, 3) == 3 then
			local random = room.random:NextInteger(1, #locations)
			local randomLocation = locations[random]
			requiresKey = true
			
			item.New(randomLocation, "Key")	
		end
	end
	local newDoor = door.New(newRoom, number, requiresKey)
	
	newRoom.Parent = workspace.GeneratedRooms

	return newRoom
	end

	return room

It should look like this:

function room.Generate(PrevRoom, number)
	local randomRoom = room.GetRandom(PrevRoom)
	local newRoom = randomRoom:Clone()

	newRoom.PrimaryPart = newRoom.Entrance
	newRoom:PivotTo (PrevRoom.Exit.CFrame)
	newRoom.Entrance.Transparency = 1
	newRoom.Exit.Transparency = 1
	
	local requiresKey = false


	local locations = furniture.FurnishRoom(newRoom)
	if locations then
	local shouldBeLockedRoomed = room.random:NextNumber() < (1/4)
	-- the odds of a room being locked is 1/4

	for i = #locations, 2, -1 do
	local j = room.random:NextInteger(1, i)
	locations[i], locations[j] = locations[j], locations[i]
end

	for index,itemLocation in locations do
		--guarantees that there will always be a key
		--if the room is supposed to be locked
		if index==1 and shouldBeLockedRoomed then
			requiresKey = true
			item.New(itemLocation, "Key")

			--moves on to trying to spawn the next item
			continue
		end

		--spawns the other items like batteries
		local shouldSpawnAnItem = room.random:NextNumber() < (1/3)
		if shouldSpawnAnItem then
			local itemToSpawn = itemsThatCanSpawn[room.random:NextInteger(1, #itemsThatCanSpawn)]
			item.New(itemLocation, itemToSpawn)
		end
	end
end
	local newDoor = door.New(newRoom, number, requiresKey)
	
	newRoom.Parent = workspace.GeneratedRooms

	return newRoom
	end

	return room

And the items should look like this?

Screen Shot 2022-12-01 at 1.03.37 PM

Looks about right. I think you’d be better off using an attribute instead of an value object though.

1 Like

Thanks, I’ll use a string!

Also, in regards to the itemsThatCanSpawn array … how would that look & would that go into the Item script?

This is my current Item script:

local item = {}

function item.Interact(player, prompt, template, itemName)
	if player.Character then
		if itemName == "Key" then
			local tool = workspace.Items.Key:Clone()
			tool.Parent = player.Character
			tool.Handle.KeyJingle:Play()
		end
		
		prompt.Enabled = false
		template:Destroy()
	end
end
function item.New(location, itemName)
	
	local itemObject = workspace.Items:FindFirstChild(itemName)
	
	if itemObject then
		local itemHandle = itemObject.Handle:Clone()
		itemHandle.Position = location.WorldPosition
		
		local weld = Instance.new("WeldConstraint")
		weld.Part0 = itemHandle
		weld.Part1 = location.Parent
		weld.Parent = itemHandle
		
		itemHandle.Parent = location
		
		local prompt = Instance.new("ProximityPrompt")
		prompt.ActionText = ""
		prompt.MaxActivationDistance = 5
		prompt.Parent = location
		prompt.Style = "Custom" --added
		
		prompt.Triggered:Connect(function(player)
			item.Interact(player, prompt, itemHandle, itemName)
		end)
	end
end
return item

The function could look like this:

function item.GetItemNames(includeSpecialItems: boolean): {string}
	local itemNames = {}
	
	local itemFolder = workspace.Items
	for _,item in itemFolder:GetChildren() do
		if item:GetAttribute("IsSpecial") and not includeSpecialItems then
			continue
		end
		table.insert(itemNames, item.Name)
	end
	
	return itemNames
end

and then itemsThatCanSpawn would become:

local itemsThatCanSpawn = item.GetItemNames(false)
1 Like
-- Create a table of items to spawn
local itemsToSpawn = {
	"Battery",
	"Key"
}
 
-- Create a function to spawn the items in random locations within the furniture pieces 
function SpawnItems(furniture) 
	for _, item in pairs(itemsToSpawn) do 
		local randomPosition = Vector3.new(math.random(-10, 10), math.random(-10, 10), math.random(-10, 10))  -- Generate a random position within the furniture piece 

		-- Spawn the item at the random position 
		local spawnedItem = Instance.new("Part")  -- Create a part instance to represent the item being spawned 
		spawnedItem.Name = item  -- Set the name of the part instance to match the name of the item being spawned 

		-- Set properties of the part instance to match that of an actual item in Roblox Studio  
	    spawnedItem.Size = Vector3.new(1, 1, 1)   -- Set size of part instance  

        -- Parent and position part instance relative to furniture piece  
        spawnedItem.Parent = furniture   -- Parent part instance to furniture piece  
        spawnedItem.Position = randomPosition + furniture.Position   -- Position part instance relative to furniture piece's position  

    end    
end

Okie dokie!

So this is the current Item Script then,

local item = {}

function item.GetItems(includeSpecialItems: boolean): {string}
	local itemNames = {}

	local itemFolder = workspace.Items
	for _,item in itemFolder:GetChildren() do
		if item:GetAttribute("IsSpecial") and not includeSpecialItems then
			continue
		end
		table.insert(itemNames, item.Name)



	end

	return itemNames
end
local itemsThatCanSpawn = item.GetItemNames(false)
--NOT SURE IF THIS IS WHERE TO ADD THIS? 

function item.Interact(player, prompt, template, itemName)
	if player.Character then
		if itemName == "Key" then
			local tool = workspace.Items.Key:Clone()
			tool.Parent = player.Character
			tool.Handle.KeyJingle:Play()
		end
		
		prompt.Enabled = false
		template:Destroy()
	end
end
function item.New(location, itemName)
	
	local itemObject = workspace.Items:FindFirstChild(itemName)
	
	if itemObject then
		local itemHandle = itemObject.Handle:Clone()
		itemHandle.Position = location.WorldPosition
		
		local weld = Instance.new("WeldConstraint")
		weld.Part0 = itemHandle
		weld.Part1 = location.Parent
		weld.Parent = itemHandle
		
		itemHandle.Parent = location
		
		local prompt = Instance.new("ProximityPrompt")
		prompt.ActionText = ""
		prompt.MaxActivationDistance = 5
		prompt.Parent = location
		prompt.Style = "Custom" --added
		
		prompt.Triggered:Connect(function(player)
			item.Interact(player, prompt, itemHandle, itemName)
		end)
	end
end
return item

However, I am getting an error on Line 34 stating

Handle is not a valid member of UnionOperation “Workspace.Items.Battery”

This makes sense as the Battery doesn’t have a handle; however, that’s because it isn’t a tool.

Also, I’m getting an error on Line 16

ServerScriptService.Server.Room.Item:16: attempt to call a nil value

ServerScriptService.Server.Room.Item:16: attempt to call a nil value

itemThatCanSpawn should go inside the Room module where you’re spawning items. You can define it after shouldBeLockedRoom, but not inside the loop.

local shouldBeLockedRoomed = room.random:NextNumber() < (1/4)
local itemsThatCanSpawn = item.GetItemNames(false)

Yeah, it looks like you’re going to have to change your spawning code to handle items other than tools.

That’s because you’re calling a function named GetItemNames, but you called it GetItems.

1 Like

Would this work? To change the spawning code

function item.New(location, itemName)
	
	local itemObject = workspace.Items:FindFirstChild(itemName)
	
	local itemHandle = itemObject.Handle:Clone()
	if itemObject then
		if itemObject:FindFirstChild("Handle") then
			itemObject.Handle:Clone()
	
		end
		itemHandle.Position = location.WorldPosition
		
		local weld = Instance.new("WeldConstraint")
		weld.Part0 = itemHandle
		weld.Part1 = location.Parent
		weld.Parent = itemHandle
		
		itemHandle.Parent = location
		
		local prompt = Instance.new("ProximityPrompt")
		prompt.ActionText = ""
		prompt.MaxActivationDistance = 5
		prompt.Parent = location
		prompt.Style = "Custom" --added
		
		prompt.Triggered:Connect(function(player)
			item.Interact(player, prompt, itemHandle, itemName)
			
	elseif itemObject == not itemObject:FindFirstChild("Handle") then
		itemObject = itemObject
		itemObject.Position = location.WorldPosition

		local weld = Instance.new("WeldConstraint")
		weld.Part0 = itemObject
		weld.Part1 = location.Parent
		weld.Parent = itemObject

		itemHandle.Parent = location
			
	end
		end)
	end
end
return item

That wouldn’t work because you’re trying to clone an object that might not exist. Something more like this would might work:

function item.New(location, itemName)
	local itemObject = workspace.Items:FindFirstChild(itemName)
	if itemObject==nil then
		return
	end
	
	local objectToDuplicate = (itemObject:IsA("Tool") and itemObject:FindFirstChild("Handle")) or itemObject
	
	local newObject = objectToDuplicate:Clone()
	newObject:PivotTo(location.WorldCFrame)
	
	local partToWeldTo = if newObject:IsA("Model") then newObject.PrimaryPart else newObject
	if partToWeldTo:IsA("BasePart") then
		local weld = Instance.new("WeldConstraint")
		weld.Part0 = partToWeldTo
		weld.Part1 = location.Parent
		weld.Parent = partToWeldTo
	end

	newObject.Parent = location

	local prompt = Instance.new("ProximityPrompt")
	prompt.ActionText = ""
	prompt.MaxActivationDistance = 5
	prompt.Parent = location
	prompt.Style = Enum.ProximityPromptStyle.Custom

	prompt.Triggered:Connect(function(player)
		item.Interact(player, prompt, newObject, itemName)
	end)
end)

That code above assumes all objects are one of the following: BasePart, Model, or Tool.