Infinite Mining Ore Generation

Hey. I’ve been working on a concept for an infinite mining game. I have done a fair share of research almost everywhere, and have run into a roadblock on ore generation. I’m using rays for when a player mines a block they will check if there are blocks around the player, and if there aren’t blocks around the player it will generate them. I’m sure most of you guys have seen this system somewhere, but if you haven’t here is a link to berezaa’s azure mines open source model. https://www.roblox.com/library/4920625917/Infinite-Mining-Game-Kit-v4-by-berezaa

Ok enough background. The problem I have run into is that when a player mines a block next to another player’s tunnel, it will generate the blocks in there tunnel. I haven’t figured a way around this that works. Should I switch to a different method of generating the blocks or is there a fix that I’m missing? I appreciate any help or ideas.

Here’s a gif of the problem: https://gyazo.com/63e49ca7400f5512611ffc7808cb561d

Here’s my code:

local Mine = game.Workspace.Mine
local Ores = game.ReplicatedStorage.Ores

Mine.ChildRemoved:Connect(function(ore)
	local Pos = ore.Position
	local NewBlockTop
	local NewBlockBottom
	local NewBlockRight
	local NewBlockLeft
	local NewBlockForward
	local NewBlockBackward
	
	--//Raycasting checking... Basically this checks what blocks are around the mined block. There are 6 total possibilities for blocks that can spawn, and this function will eliminate the blocks that are already there and then spawns in the blocks that aren't there
	
	--Ray 1 is checking for the block below
	local ray1 = Ray.new(ore.Position, Vector3.new(0, -5, 0))
	local part1, hitPos1 = workspace:FindPartOnRay(ray1)
	if part1 == nil then
		local math1 = math.random(1, 100)
		if math1 <= 70 then
			NewBlockBottom = Ores.Stone:Clone()
		elseif math1 >= 71 and math1 <=80 then
			NewBlockBottom = Ores.Coal:Clone()
		elseif math1 >= 81 and math1 <=85 then
			NewBlockBottom = Ores.Iron:Clone()
		elseif math1 >= 86 and math1 <=90 then
			NewBlockBottom = Ores.Gold:Clone()
		elseif math1 >= 91 and math1 <=95 then
			NewBlockBottom = Ores.Amythest:Clone()
		elseif math1 >= 96 and math1 <=98 then
			NewBlockBottom = Ores.Platinum:Clone()
		elseif math1 >= 99 then
			NewBlockBottom = Ores.Obisidian:Clone()
		end
 	end

	--Ray 2 is checking for the block above,
	
	local ray2 = Ray.new(ore.Position, Vector3.new(0, 5, 0))
	local part2, hitPos2 = workspace:FindPartOnRay(ray2)
	if part2 == nil and ore.Position.Y < 1534 then
		local math1 = math.random(1, 100)
		if math1 <= 70 then
			NewBlockTop = Ores.Stone:Clone()
		elseif math1 >= 71 and math1 <=80 then
			NewBlockTop = Ores.Coal:Clone()
		elseif math1 >= 81 and math1 <=85 then
			NewBlockTop = Ores.Iron:Clone()
		elseif math1 >= 86 and math1 <=90 then
			NewBlockTop = Ores.Gold:Clone()
		elseif math1 >= 91 and math1 <=95 then
			NewBlockTop = Ores.Amythest:Clone()
		elseif math1 >= 96 and math1 <=98 then
			NewBlockTop = Ores.Platinum:Clone()
		elseif math1 >= 99 then
			NewBlockTop = Ores.Obisidian:Clone()
		end
 	end

	--Ray 3 is checking on positive X(or right as im calling it)

	local ray3 = Ray.new(ore.Position, Vector3.new(5, 0, 0))
	local part3, hitPos3 = workspace:FindPartOnRay(ray3)
	if part3 == nil and ore.Position.Y < 1534 then
		local math1 = math.random(1, 100)
		if math1 <= 70 then
			NewBlockRight = Ores.Stone:Clone()
		elseif math1 >= 71 and math1 <=80 then
			NewBlockRight = Ores.Coal:Clone()
		elseif math1 >= 81 and math1 <=85 then
			NewBlockRight = Ores.Iron:Clone()
		elseif math1 >= 86 and math1 <=90 then
			NewBlockRight = Ores.Gold:Clone()
		elseif math1 >= 91 and math1 <=95 then
			NewBlockRight = Ores.Amythest:Clone()
		elseif math1 >= 96 and math1 <=98 then
			NewBlockRight = Ores.Platinum:Clone()
		elseif math1 >= 99 then
			NewBlockRight = Ores.Obisidian:Clone()
		end
 	end

	--Ray 4 is checking on the negative X (or left as im calling it)

	local ray4 = Ray.new(ore.Position, Vector3.new(-5, 0, 0))
	local part4, hitPos4 = workspace:FindPartOnRay(ray4)
	if part4 == nil and ore.Position.Y < 1534 then
		local math1 = math.random(1, 100)
		if math1 <= 70 then
			NewBlockLeft = Ores.Stone:Clone()
		elseif math1 >= 71 and math1 <=80 then
			NewBlockLeft = Ores.Coal:Clone()
		elseif math1 >= 81 and math1 <=85 then
			NewBlockLeft = Ores.Iron:Clone()
		elseif math1 >= 86 and math1 <=90 then
			NewBlockLeft = Ores.Gold:Clone()
		elseif math1 >= 91 and math1 <=95 then
			NewBlockLeft = Ores.Amythest:Clone()
		elseif math1 >= 96 and math1 <=98 then
			NewBlockLeft = Ores.Platinum:Clone()
		elseif math1 >= 99 then
			NewBlockLeft = Ores.Obisidian:Clone()
		end
 	end

	--Ray 5 is checking on the positive Y (or forward as im calling it)

	local ray5 = Ray.new(ore.Position, Vector3.new(0, 0, 5))
	local part5, hitPos5 = workspace:FindPartOnRay(ray5)
	if part5 == nil and ore.Position.Y < 1534 then
		local math1 = math.random(1, 100)
		if math1 <= 70 then
			NewBlockForward = Ores.Stone:Clone()
		elseif math1 >= 71 and math1 <=80 then
			NewBlockForward = Ores.Coal:Clone()
		elseif math1 >= 81 and math1 <=85 then
			NewBlockForward = Ores.Iron:Clone()
		elseif math1 >= 86 and math1 <=90 then
			NewBlockForward = Ores.Gold:Clone()
		elseif math1 >= 91 and math1 <=95 then
			NewBlockForward = Ores.Amythest:Clone()
		elseif math1 >= 96 and math1 <=98 then
			NewBlockForward = Ores.Platinum:Clone()
		elseif math1 >= 99 then
			NewBlockForward = Ores.Obisidian:Clone()
		end
 	end

	--Ray 6 is checking on the negative Y (or backward as im calling it)

	local ray6 = Ray.new(ore.Position, Vector3.new(0, 0, -5))
	local part6, hitPos6 = workspace:FindPartOnRay(ray6)
	if part6 == nil and ore.Position.Y < 1534 then
		local math1 = math.random(1, 100)
		if math1 <= 70 then
			NewBlockBackward = Ores.Stone:Clone()
		elseif math1 >= 71 and math1 <=80 then
			NewBlockBackward = Ores.Coal:Clone()
		elseif math1 >= 81 and math1 <=85 then
			NewBlockBackward = Ores.Iron:Clone()
		elseif math1 >= 86 and math1 <=90 then
			NewBlockBackward = Ores.Gold:Clone()
		elseif math1 >= 91 and math1 <=95 then
			NewBlockBackward = Ores.Amythest:Clone()
		elseif math1 >= 96 and math1 <=98 then
			NewBlockBackward = Ores.Platinum:Clone()
		elseif math1 >= 99 then
			NewBlockBackward = Ores.Obisidian:Clone()
		end
 	end
	
	
	
	if NewBlockBottom then
		NewBlockBottom.Parent = Mine
		NewBlockBottom.Position = Vector3.new(Pos.X, Pos.Y - 5, Pos.Z)	
	end
	
	--[[if NewBlockTop then
		NewBlockTop.Parent = Mine
		NewBlockTop.Position = Vector3.new(Pos.X, Pos.Y + 5, Pos.Z)
	end]]--
	
	if NewBlockRight then
		NewBlockRight.Parent = Mine
		NewBlockRight.Position = Vector3.new(Pos.X + 5, Pos.Y, Pos.Z)
	end
	
	if NewBlockLeft then
		NewBlockLeft.Parent = Mine
		NewBlockLeft.Position = Vector3.new(Pos.X - 5, Pos.Y, Pos.Z)
	end
	
	if NewBlockForward then
		NewBlockForward.Parent = Mine
		NewBlockForward.Position = Vector3.new(Pos.X, Pos.Y, Pos.Z + 5)
	end
	
	if NewBlockBackward then
		NewBlockBackward.Parent = Mine
		NewBlockBackward.Position = Vector3.new(Pos.X, Pos.Y, Pos.Z - 5)
	end
end)

Notes on code:

  • I have commented out the code that creates a block on top as it creates the most problem
  • 1534 is the height the start of the mine is at
  • Don’t mind the pickaxe, I made it in 5 seconds and I don’t need a good looking one. It’s there as a placeholder

I would do this with a 3D array, you wouldn’t have to cast rays and it would be much easier to detect if there’s a block on a side. Also you would be able to easily mark if there was a block in one place and it was dug, so the generator wouldn’t spawn the unwanted blocks which happens in your case.

1 Like

Alright, thanks for responding quickly. I’ll research 3d arrays and try to put something together.

Try making a non-collidable, transparent placeholder block, that is created when you mine a block. This will trick the ray into thinking that there is a block there, so will not make s new block.

I found this multi-dimensional array code (autotables) on StackOverflow. It allows you to create tables with varying amounts of dimensions, but for your purpose you only need 3.

Once you’ve implemented 3D tables, you can use them to easily check if there is another part in the area you want to create parts in. If there is, you can just skip that part and continue creating parts in another area.

By using 3D tables, you won’t need raycasting at all.

Auto-tables generate sub-tables transparently using metatables and essentially after creating it you should be able to forget about them.

function newAutotable(dim)
    local MT = {};
    for i=1, dim do
        MT[i] = {__index = function(t, k)
            if i < dim then
                t[k] = setmetatable({}, MT[i+1])
                return t[k];
            end
        end}
    end

    return setmetatable({}, MT[1]);
end

-- Usage
local at = newAutotable(3);
print(at[0]) -- returns table
print(at[0][1]) -- returns table
print(at[0][1][2]) -- returns nil
at[0][1][2] = 2;
print(at[0][1][2]) -- returns value
print(at[0][1][3][3]) -- error, because only 3 dimensions set

I already tried this actually. Before I executed the idea I thought it was genius. There’s a but. The player can’t mine the blocks under the secret transparent block because the mouse is still targeting it.

I think this’ll be the best solution considering Toko also said this’ll work. When I next start working I’m gunna try it out.

Then just make the mouse ignore it.
Mouse.TargetFilter

I just tried this and it worked. The thing is the player still can’t click through the “dug”(or tunnel in my code) parts. Here’s a gif of what’s happening and the code.

https://gyazo.com/ff92fda395a3d3779736f91b71bef006

client side pickaxe defining the filter:

local mouse = player:GetMouse()
local filter = mouse.TargetFilter
local TunnelStorage = game.Workspace.TunnelStorage
filter = TunnelStorage
TunnelStorage.ChildAdded:Connect(function()
	filter = TunnelStorage
end)
TunnelStorage.ChildRemoved:Connect(function()
	filter = TunnelStorage
end)

Try this.

local mouse  = game.Players.LocalPlayer:GetMouse()
mouse.TargetFilter = game.Workspace.TunnelStorage

Don’t forgot to put the blocks into the storage, and target filter automatically makes its children ignored.

I thought it had something to do with the variables getting mixed up, anyways I realize that the mouse doesn’t really x-ray ignored parts, it appears. I can’t think of any more solutions out of this, sorry.

You should try using UserInputService and raycasting instead.

local Player = game:GetService("Players").LocalPlayer
local UserInputService = game:GetService("UserInputService")
local Camera = workspace.CurrentCamera

local RANGE = 1e4
local GUI_INSET = Vector2.new(0, 36)

local RAYCAST_PARAMS = RaycastParams.new()
RAYCAST_PARAMS.IgnoreWater = false
RAYCAST_PARAMS.FilterDescendantsInstances = {Player.Character or Player.CharacterAdded:Wait()} 
-- update this above table[2] with the part you want to ignore.
RAYCAST_PARAMS.FilterType = Enum.RaycastFilterType.Blacklist

local function get3DPosition()
    local screenPosition = UserInputService:GetMouseLocation() - GUI_INSET
    local oray = Camera:ScreenPointToRay(screenPosition.X, screenPosition.Y, 0)
    return workspace:Raycast(oray.Origin, oray.Direction * RANGE, RAYCAST_PARAMS)
end

local selectedInput = Enum.UserInputType.MouseButton1
UserInputService.InputBegan:Connect(function(input, ignore)
    if not ignore and input.UserInputType == selectedInput then
        local raycastResult = get3DPosition()
        if not raycastResult then return end -- it didn't hit anything

        local hit, pos, normal = raycastResult.Instance, raycastResult.Position, raycastResult.Normal
        -- use the hitPart, hitPosition, and the surface normal however u want
    end
end)

Player.CharacterAdded:Connect(function(character)
    RAYCAST_PARAMS.FilterDescendantsInstances[1] = character
    -- update with the new character to ignore after they respawn
end)

That’s because you didn’t even use TargetFilter, you just redefined the variable you set TargetFilter to. That isn’t how variables work in lua.

Still, I’d recommend using UserInputService with raycasting over mouse, and using a 3D array over either of those.