Files
block-game/ReplicatedStorage/Shared/ChunkManager/Chunk.lua
2026-01-08 22:58:58 +02:00

218 lines
4.9 KiB
Lua

--!native
--!optimize 2
local Chunk = {}
Chunk.__index = Chunk
Chunk.UpdateBlockBindable = Instance.new("BindableEvent") :: BindableEvent
Chunk.AllChunks = {} :: {[typeof("")]: typeof(Chunk.new(0,0,0))}
local RunService = game:GetService("RunService")
local function normalizeCoord(n)
if typeof(n) ~= "number" then
return n
end
if n >= 0 then
return math.floor(n + 0.5)
end
return math.ceil(n - 0.5)
end
local function keyFromCoords(x, y, z)
x = normalizeCoord(x)
y = normalizeCoord(y)
z = normalizeCoord(z)
return `{tostring(x)},{tostring(y)},{tostring(z)}`
end
local function Swait(l)
for i = 1,l do
RunService.Stepped:Wait()
end
end
export type BlockData = {
id: number,
state: {
[typeof("")]: string | boolean | number
}
}
function Chunk.new(x,y,z)
local self = setmetatable({}, Chunk)
self.pos = Vector3.new(x,y,z)
-- Tick ONLY in a 5 chunk distance of LP's char
self.inhabitedTime = tick()
self.instance = Instance.new("Folder")
self.unloadChunkHook = function() end
self.UpdateBlockBindableL = Instance.new("BindableEvent") :: BindableEvent
self.loaded = false
self.loading = false
self.delayedRemoval = {}
self.data = {} :: {[typeof("")]: BlockData} -- "X,Y,Z": BlockData ("-1,-1,1": BlockData)
return self
end
function Chunk.from(x,y,z,data)
local self = setmetatable({}, Chunk)
self.pos = Vector3.new(x,y,z)
-- Tick ONLY in a 5 chunk distance of LP's char
self.inhabitedTime = tick()
self.instance = Instance.new("Folder")
self.unloadChunkHook = function() end
self.UpdateBlockBindableL = Instance.new("BindableEvent") :: BindableEvent
self.data = data :: {[typeof("")]: BlockData} -- "X,Y,Z": BlockData ("-1,-1,1": BlockData)
self.delayedRemoval = {}
return self
end
function Chunk:DoesNeighboringBlockExist(rx, ry, rz, gx, gy, gz, offsetX, offsetY, offsetZ)
-- Calculate the local position of the neighboring block
local neighborRX, neighborRY, neighborRZ = rx + offsetX, ry + offsetY, rz + offsetZ
local neighborGX, neighborGY, neighborGZ = gx, gy, gz
-- Adjust for chunk boundaries
if neighborRX < 1 then
neighborRX = 8
neighborGX = gx - 1
elseif neighborRX > 8 then
neighborRX = 1
neighborGX = gx + 1
end
if neighborRY < 1 then
neighborRY = 8
neighborGY = gy - 1
elseif neighborRY > 8 then
neighborRY = 1
neighborGY = gy + 1
end
if neighborRZ < 1 then
neighborRZ = 8
neighborGZ = gz - 1
elseif neighborRZ > 8 then
neighborRZ = 1
neighborGZ = gz + 1
end
if neighborGY < 0 then
return true
end
-- Get the neighboring chunk
local neighborChunk = Chunk.AllChunks[`{neighborGX},{neighborGY},{neighborGZ}`]
if not neighborChunk then
return false -- Treat unloaded chunks as empty so edges render
end
-- Check if the block exists in the neighboring chunk
return neighborChunk:GetBlockAt(neighborRX, neighborRY, neighborRZ) ~= nil
end
function Chunk:IsBlockRenderable(rx, ry, rz)
local gx, gy, gz = self.pos.X, self.pos.Y, self.pos.Z
-- Check all six neighboring blocks
local d = not (
self:DoesNeighboringBlockExist(rx, ry, rz, gx, gy, gz, 1, 0, 0) and
self:DoesNeighboringBlockExist(rx, ry, rz, gx, gy, gz, -1, 0, 0) and
self:DoesNeighboringBlockExist(rx, ry, rz, gx, gy, gz, 0, 1, 0) and
self:DoesNeighboringBlockExist(rx, ry, rz, gx, gy, gz, 0, -1, 0) and
self:DoesNeighboringBlockExist(rx, ry, rz, gx, gy, gz, 0, 0, 1) and
self:DoesNeighboringBlockExist(rx, ry, rz, gx, gy, gz, 0, 0, -1)
)
return d
end
function Chunk:Tick()
self.inhabitedTime = tick()
end
function Chunk:PropogateChanges(x: number,y: number,z: number,d:BlockData)
self.UpdateBlockBindableL:Fire(x,y,z,d)
end
function Chunk:GetBlockAt(x,y,z)
if not self.data[keyFromCoords(x, y, z)] then
return nil
end
return self.data[keyFromCoords(x, y, z)]
end
function Chunk:CreateBlock(x: number,y: number,z: number,d:BlockData)
self.data[keyFromCoords(x, y, z)] = d
self:PropogateChanges(x,y,z,d)
return self:GetBlockAt(x,y,z)
end
function Chunk:RemoveBlock(x, y, z)
local blockKey = keyFromCoords(x, y, z)
self.data[blockKey] = nil
self:PropogateChanges(x,y,z,0)
end
function Chunk:RemoveBlockSmooth(x, y, z)
local blockKey = keyFromCoords(x, y, z)
self.data[blockKey] = nil
self.delayedRemoval[blockKey] = true
self:PropogateChanges(x,y,z,0)
end
-- unsure why this exists
function Chunk:Load()
end
function Chunk:Unload()
self.loaded = false
-- SLOWCLEAR
task.defer(function()
local g = 0
for _,v in pairs(self.instance:GetChildren()) do
pcall(function()
v:Destroy()
end)
g += 1
if g == 30 then
g = 0
Swait(2)
end
end
self.instance.Parent = nil
self.instance:Destroy()
self.unloadChunkHook()
end)
end
function Chunk:UnloadImmediate()
self.loaded = false
pcall(function()
self.unloadChunkHook()
end)
pcall(function()
if self.instance then
self.instance.Parent = nil
self.instance:Destroy()
end
end)
end
-- DO NOT INTERACT WITH CHUNK AFTER CALLING THIS
function Chunk:Destroy()
self.data = {}
end
return Chunk :: typeof(Chunk)