# Making Okeanskiy trash generation seed-based

(continued from this topic)

1. What do you want to achieve?
Last time from tinkering with my modified version of Okeanskiy’s Terrain Generation, SquarePapyrus12 suggested,

if the generation is different on all clients and you don’t want that…then make a seed system and all clients should have the same seed

Taking the advice into consideration, I began to tinker with the terrain generation a little more, so that the trash generation is consistent for all clients.
1. What is the issue?
However, the attempts I made to utilize seeds in overhauling the generation resulted in… this:

Expectation

Reality

2. What solutions have you tried so far?
From last topic, I added a NumberValue into the Workspace that denotes the seed for the trash.

``````SEED = game.Workspace.SEED				--	Get the IntValue in the Workspace.
SEED.Value = math.random(1000000)		--	Make its value a random number between 1 and a million.

TRASH_SEED = game.Workspace.TRASH_SEED	--	Get the NumberValue in the Workspace.
TRASH_SEED.Value = SEED.Value + 0.01	--	Make its value the SEED's value, plus a hundredth.

--	This counts how many unique trash types are there in the game.
t = game.ReplicatedStorage.Garbage:GetChildren()
print("Trash types: "..#t)
``````

Afterwards, I modified the addTrash function in the Chunk Module.

``````local function addTrash(chunk)
local posGrid = chunk.positionGrid
local instances = chunk.instances
local chunkPosX = chunk.x
local chunkPosZ = chunk.z

for x = 0, X-1 do
for z = 0, Z-1 do
local pos = posGrid[x][z]

if pos.Y >= MIN_TRASH_SPAWN_HEIGHT and pos.Y <= MAX_TRASH_SPAWN_HEIGHT then

math.randomseed((x * (chunkPosX+SEED))+(z * (chunkPosZ+SEED)))
if math.random() < TRASH_DENSITY then
local p = math.noise(TRASH_SEED) + 0.25
local garbage = game.ReplicatedStorage.Garbage:GetChildren()
local q = (p/0.75) * #garbage
local r = math.ceil(q+1)
if not r then
r = #garbage
elseif r then
continue
end
local trash = garbage[r]:Clone()

local cframe = CFrame.new(pos)
* CFrame.new(
math.random()*math.random(-10,10),
0,
math.random()*math.random(-10,10)
)
* CFrame.Angles(
2*math.pi*math.random(),
)

trash:SetPrimaryPartCFrame(cframe)
trash.Parent = TrashModels

table.insert(instances, trash)
TRASH_SEED += 0.01
end
end
end
end
end
``````

Running the game, I ended up with a landscape devoid of any trash. Just by looking at the script, we both see that I am doing something wrong. What am I missing?

3 Likes

You don’t generally don’t call randomseed every time you want to get a new number. You usually call it once at the beginning of the overall process of generating e.g. an entire level.

Another issue is that you always call it with the same number (well actually multiple numbers because of the above problem), causing you to always get the same sequence of random numbers, so the level is always the same. If the only thing you want is a different level each time, just put this at the top of your script:

``````math.randomseed(os.time())
``````

math.random isn’t the best, you should look into using the “new” Random type, just look it up on the wiki.

The old trash generation did use `math.randomseed((x * (chunkPosX+SEED)+(z * (chunkPosZ+SEED)))`, as it is based off of Okeanskiy’s Terrain Generation. Here is the old script:

``````local TERRAIN_HEIGHT_COLORS	= {
[-50] = Color3.fromRGB(169,88,33);	-- Dark orange-ish
[-10] = Color3.fromRGB(197,140,69);	-- Orange-ish
[0] = Color3.fromRGB(212,173,97);	-- Yellow orange-ish
[75] = Color3.fromRGB(223,210,158)	-- Pale yellowish
}
local X, Z = 4,4
local WIDTH_SCALE = 16
local HEIGHT_SCALE = 100
local TERRAIN_SMOOTHNESS = 20
local MIN_TRASH_SPAWN_HEIGHT = -15
local MAX_TRASH_SPAWN_HEIGHT = 30
local TRASH_DENSITY = 0.5				-- Number between 0 and 1
local SEED = game.Workspace.SEED.Value

local TerrainModel = game.Workspace.TerrainModel
local TrashModels = game.Workspace.TrashModels
local Terrain = game.Workspace.Terrain

local wedge = Instance.new("WedgePart")
wedge.Anchored = true
wedge.TopSurface = Enum.SurfaceType.Smooth
wedge.BottomSurface = Enum.SurfaceType.Smooth

local function draw3DTriangle(a, b, c)
local ab,ac,bc = b-a, c-a, c-b
local abd,acd,bcd = ab:Dot(ab), ac:Dot(ac), bc:Dot(bc)

if (abd > acd) and (abd > bcd) then
c,a = a,c
elseif (acd > bcd) and (acd > abd) then
a,b = b,a
end

ab,ac,bc = b-a, c-a, c-b

local right = ac:Cross(ab).unit
local up = bc:Cross(right).unit
local back = bc.unit

local height = math.abs(ab:Dot(up))

local w1 = wedge:Clone()
w1.Size = Vector3.new(0,height,math.abs(ab:Dot(back)))
w1.CFrame = CFrame.fromMatrix((a+b)/2, right, up, back)
w1.Parent = TerrainModel

local w2 = wedge:Clone()
w2.Size = Vector3.new(0,height,math.abs(ac:Dot(back)))
w2.CFrame = CFrame.fromMatrix((a+c)/2, -(right), up, -(back))
w2.Parent = TerrainModel

return w1, w2
end

local function getHeight(chunkPosX, chunkPosZ, x, z)
local height = math.noise(
(X/TERRAIN_SMOOTHNESS * chunkPosX) + x/TERRAIN_SMOOTHNESS,
(Z/TERRAIN_SMOOTHNESS * chunkPosZ) + z/TERRAIN_SMOOTHNESS,
SEED
) * HEIGHT_SCALE

if height > 20 then
local difference = height - 20
height += (difference * 1.2)
end

if height < -20 then
local difference = height - -20
height += (difference * 1.2)
end

return height
end

local function getPosition(chunkPosX, chunkPosZ, x, z)
return Vector3.new(
(chunkPosX*X*WIDTH_SCALE) + x*WIDTH_SCALE,
getHeight(chunkPosX, chunkPosZ, x, z),
(chunkPosZ*Z*WIDTH_SCALE) + z*WIDTH_SCALE
)
end

local function paintWedge(wedge)
local wedgeHeight = wedge.Position.Y

local color
local lowerColorHeight
local higherColorHeight

for height,heightColor in pairs(TERRAIN_HEIGHT_COLORS) do
if wedgeHeight == height then
color = heightColor
break
end

if (wedgeHeight < height) and (not higherColorHeight or height < higherColorHeight) then
higherColorHeight = height
end

if (wedgeHeight > height) and (not lowerColorHeight or height > lowerColorHeight) then
lowerColorHeight = height
end
end

if not color then
if higherColorHeight == nil then
color = TERRAIN_HEIGHT_COLORS[lowerColorHeight]
elseif lowerColorHeight == nil then
color = TERRAIN_HEIGHT_COLORS[higherColorHeight]
else
local alpha = (wedgeHeight - lowerColorHeight) / (higherColorHeight - lowerColorHeight)
local lowerColor = TERRAIN_HEIGHT_COLORS[lowerColorHeight]
local higherColor = TERRAIN_HEIGHT_COLORS[higherColorHeight]

color = lowerColor:lerp(higherColor, alpha)
end
end

wedge.Material = Enum.Material.Sand
wedge.Color = color
end

local cframe = CFrame.new(
(chunk.x + 0.5) * (chunk.WIDTH_SIZE_X),
-70,
(chunk.z + 0.5) * (chunk.WIDTH_SIZE_Z)
)

local size = Vector3.new(
chunk.WIDTH_SIZE_X,
100,
chunk.WIDTH_SIZE_Z
)

game.Workspace.Terrain:FillBlock(cframe, size, Enum.Material.Water)

chunk.waterCFrame = cframe
chunk.waterSize = size
end

local posGrid = chunk.positionGrid
local instances = chunk.instances
local chunkPosX = chunk.x
local chunkPosZ = chunk.z

for x = 0, X-1 do
for z = 0, Z-1 do
local pos = posGrid[x][z]

if pos.Y >= MIN_TRASH_SPAWN_HEIGHT and pos.Y <= MAX_TRASH_SPAWN_HEIGHT then

math.randomseed((x * (chunkPosX+SEED))+(z * (chunkPosZ+SEED)))
if math.random() < TRASH_DENSITY then
local garbage = game.ReplicatedStorage.Garbage:GetChildren()
local trash = garbage[math.random(1,#garbage)]:Clone()

local cframe = CFrame.new(pos)
* CFrame.new(
math.random()*math.random(-10,10),
0,
math.random()*math.random(-10,10)
)
* CFrame.Angles(
2*math.pi*math.random(),
)

trash:SetPrimaryPartCFrame(cframe)
trash.Parent = TrashModels

table.insert(instances, trash)
end

end
end
end
end

local Chunk = {}
Chunk.__index = Chunk

Chunk.WIDTH_SIZE_X = X * WIDTH_SCALE
Chunk.WIDTH_SIZE_Z = Z * WIDTH_SCALE

function Chunk.new(chunkPosX, chunkPosZ)
local chunk = {
instances = {};
positionGrid = {};
x = chunkPosX;
z = chunkPosZ;
}

setmetatable(chunk,Chunk)

local positionGrid = chunk.positionGrid

for x = 0, X do
positionGrid[x] = {}

for z = 0, Z do
positionGrid[x][z] = getPosition(chunkPosX,chunkPosZ,x,z)
end
end

for x = 0, X-1 do
for z = 0, Z-1 do
local a = positionGrid[x][z]
local b = positionGrid[x+1][z]
local c = positionGrid[x][z+1]
local d = positionGrid[x+1][z+1]

local wedgeA, wedgeB = draw3DTriangle(a,b,c)
local wedgeC, wedgeD = draw3DTriangle(b,c,d)

paintWedge(wedgeA)
paintWedge(wedgeB)
paintWedge(wedgeC)
paintWedge(wedgeD)

table.insert(chunk.instances, wedgeA)
table.insert(chunk.instances, wedgeB)
table.insert(chunk.instances, wedgeC)
table.insert(chunk.instances, wedgeD)
--[[
Terrain:FillBlock(wedgeA.CFrame,wedgeA.Size,Enum.Material.Sand)
Terrain:FillBlock(wedgeB.CFrame,wedgeB.Size,Enum.Material.Sand)
Terrain:FillBlock(wedgeC.CFrame,wedgeC.Size,Enum.Material.Sand)
Terrain:FillBlock(wedgeD.CFrame,wedgeD.Size,Enum.Material.Sand)
]]--
end
end

return chunk
end

function Chunk:Destroy()
for index, instance in ipairs(self.instances) do
instance:Destroy()
end

game.Workspace.Terrain:FillBlock(self.waterCFrame, self.waterSize, Enum.Material.Air)
end

return Chunk
``````

The problem actually lies in the first few lines until defining the CFrame.

Ah, okay.

I’d place a breakpoint on the line immediately after `if math.random() < TRASH_DENSITY then`, to see if any trash actually gets generated. It may be that it does get generated but you can’t see it because it’s positioned incorrectly. If that’s no the case, you know that your “should I place trash or not” logic is the issue.

`p` will always be the same for EVERY call, because math.noise doesn’t return random numbers. It always returns the same output if you give it the same input, which is exactly what you’re doing (assuming TRASH_SEED is a constant).

`r` will always be a number, and all numbers are truthy, so the only branch that can ever be reached is the r = #garbage branch. It’s also confusing to have a “not something” as the first branch, and you don’t need an elseif because (not not r) guarantees (r). I’d change it to this:

``````local r = math.ceil(q+1)
if r then
continue
else
r = #garbage
end
``````
1 Like

At the end of the `if math.random() < TRASH_DENSITY then` block, I increment the TRASH_SEED by one hundredth. Let’s say that…

``````TRASH_SEED = 69420.01	--	(nice)
``````

then…

``````math.noise(TRASH_SEED)	--	0.0078171389177442
TRASH_SEED += 0.01		--	new TRASH_SEED value: 69420.02
math.noise(TRASH_SEED)	--	0.023555938154459
``````

This is to counteract the constant “p will always be the same for EVERY call” problem.

As for the different `r` value, thanks for clearing up the conditional statement. At least it seems clearer and that I do not have to set `continue` before `end`.