···11+local BlockManager = {}
22+33+BlockManager.BlockIdMappings = {} :: {BasePart}
44+55+for i,v in pairs(game:GetService("ReplicatedStorage"):WaitForChild("Blocks"):GetChildren()) do
66+ BlockManager.BlockIdMappings[v:GetAttribute("n")] = v
77+end
88+99+BlockManager.UpdateIdMappings = {} :: {BasePart}
1010+1111+for i,v in pairs(game:GetService("ReplicatedStorage"):WaitForChild("BlockUpdateOperations"):GetChildren()) do
1212+ local success, reason = pcall(function()
1313+ BlockManager.UpdateIdMappings[v:GetAttribute("n")] = require(v)
1414+ end)
1515+ if not success then
1616+ warn("[BLOCKMANAGER] Invalid update operation",v:GetAttribute("n"),":",reason)
1717+ end
1818+end
1919+2020+local warnedBlockIds = {}
2121+2222+function BlockManager:GetBlock(blockId: number, attr: {[typeof("")]: any}?)
2323+2424+ task.synchronize()
2525+2626+ if not BlockManager.BlockIdMappings[blockId] then
2727+ if not warnedBlockIds[blockId] then
2828+ warnedBlockIds[blockId] = true
2929+ warn("[BLOCKMANAGER] Invalid block id",blockId)
3030+ end
3131+ return script.invalid:Clone()
3232+ end
3333+3434+ local b = BlockManager.BlockIdMappings[blockId]:Clone()
3535+ b.Size = Vector3.new(3.95,3.95,3.95)
3636+3737+ for i,v in pairs(attr or {}) do
3838+ b:SetAttribute(i,v)
3939+ end
4040+4141+ if BlockManager.UpdateIdMappings[blockId] then
4242+ local success, reason = pcall(function()
4343+ BlockManager.UpdateIdMappings[blockId](b)
4444+ end)
4545+ if not success then
4646+ warn("[BLOCKMANAGER] Failed update operation",blockId,":",reason)
4747+ end
4848+ end
4949+5050+ return b
5151+end
5252+5353+-- ChatGPT Generated Func!!!!
5454+function BlockManager:GetBlockRotated(blockId: number, face: Enum.NormalId, attr: {[typeof("")]: any}?)
5555+ -- Returns block with id blockId, rotated so the given face (NormalId) points north (+X).
5656+ local block = BlockManager:GetBlock(blockId, attr)
5757+ local rot = CFrame.new()
5858+5959+ task.synchronize()
6060+6161+ if face == Enum.NormalId.Front then
6262+ rot = CFrame.Angles(0, 0, 0) -- no rot
6363+ elseif face == Enum.NormalId.Back then
6464+ rot = CFrame.Angles(0, math.rad(180), 0)
6565+ elseif face == Enum.NormalId.Left then
6666+ rot = CFrame.Angles(0, math.rad(90), 0)
6767+ elseif face == Enum.NormalId.Right then
6868+ rot = CFrame.Angles(0, math.rad(-90), 0)
6969+ elseif face == Enum.NormalId.Top then
7070+ rot = CFrame.Angles(math.rad(-90), 0, 0) -- top +x
7171+ elseif face == Enum.NormalId.Bottom then
7272+ rot = CFrame.Angles(math.rad(90), 0, 0) -- bottom +x
7373+ end
7474+7575+7676+ -- ocbwoy3's fix
7777+ block:PivotTo(rot)
7878+ block.CFrame = CFrame.new(rot.Position)
7979+8080+ return block
8181+end
8282+8383+8484+8585+8686+return BlockManager
···11+local ChunkBuilder = {}
22+33+local Chunk = require("./Chunk")
44+local BlockManager = require("./BlockManager")
55+local util = require("../Util")
66+77+local objects = script.Parent.Parent.Parent.Objects
88+99+local RunService = game:GetService("RunService")
1010+1111+local ChunkBorderFolder = game:GetService("Workspace"):FindFirstChildOfClass("Terrain")
1212+1313+local NEIGHBOR_OFFSETS = {
1414+ {-1, 0, 0}, {1, 0, 0},
1515+ {0, -1, 0}, {0, 1, 0},
1616+ {0, 0, -1}, {0, 0, 1}
1717+}
1818+1919+-- TODO: Move to Chunk
2020+type BlockData = {
2121+ id: number,
2222+ state: {
2323+ [typeof("")]: string | boolean | number
2424+ }
2525+}
2626+2727+local function placeBorder(a,b,c)
2828+ local pos = util.ChunkPosToCFrame(Vector3.new(a,b,c),Vector3.new(1,1,1)).Position - Vector3.new(2,2,2)
2929+ local d = objects.ChunkLoading:Clone()
3030+ d:PivotTo(CFrame.new(pos))
3131+ d.Parent = ChunkBorderFolder
3232+ return d
3333+end
3434+3535+local function Swait(l)
3636+ for i = 1,l do
3737+ RunService.Stepped:Wait()
3838+ end
3939+end
4040+4141+local function propogateNeighboringBlockChanges(cx, cy, cz, x, y, z)
4242+ --warn("propogateNeighboringBlockChanges",cx,cy,cz,x,y,z)
4343+ -- updates block in another chunk
4444+ local c = Chunk.AllChunks[`{cx},{cy},{cz}`]
4545+ if not c then return end
4646+4747+ local d = c.data[`{x},{y},{z}`]
4848+ if not d then return end
4949+5050+ if c:IsBlockRenderable(x, y, z) then
5151+ if c.instance:FindFirstChild(`{x},{y},{z}`) then return end
5252+ task.synchronize()
5353+ local block = BlockManager:GetBlockRotated(d.id, util.RotationStringToNormalId(d.state["r"] or "f"), d.state)
5454+ block.Name = `{x},{y},{z}`
5555+ block:PivotTo(util.ChunkPosToCFrame(c.pos, Vector3.new(x, y, z)))
5656+ block.Parent = c.instance
5757+ task.desynchronize()
5858+ else
5959+ local existing = c.instance:FindFirstChild(`{x},{y},{z}`)
6060+ if existing then
6161+ task.synchronize()
6262+ existing:Destroy()
6363+ task.desynchronize()
6464+ end
6565+ end
6666+end
6767+6868+function ChunkBuilder:BuildChunk(c: typeof(Chunk.new(0,0,0)),parent: Instance?)
6969+7070+ if c.loaded then
7171+ return c.instance
7272+ end
7373+7474+ local blocks = c.data
7575+ local newcache = {} :: {[typeof("")]: BlockData}
7676+7777+ local finished = false
7878+7979+8080+ local ch = Instance.new("Folder")
8181+ ch.Parent = parent
8282+ ch.Name = `{c.pos.X},{c.pos.Y},{c.pos.Z}`
8383+8484+ local conn = c.UpdateBlockBindableL.Event:Connect(function(x: number, y: number, z: number, d: BlockData)
8585+ task.desynchronize()
8686+ if finished == false then
8787+ newcache[`{x},{y},{z}`] = d
8888+ return
8989+ end
9090+ task.synchronize()
9191+ for _, o in pairs(NEIGHBOR_OFFSETS) do
9292+ --warn("propogate",o[1],o[2],o[3])
9393+ -- Adjust for chunk boundaries
9494+ local b = {x = x + o[1], y = y + o[2], z = z + o[3]}
9595+ local ch = {x = c.pos.X, y = c.pos.Y, z = c.pos.Z}
9696+9797+ if b.x < 1 then ch.x = c.pos.X - 1 b.x = 8 end
9898+ if b.x > 8 then ch.x = c.pos.X + 1 b.x = 1 end
9999+ if b.y < 1 then ch.y = c.pos.Y - 1 b.y = 8 end
100100+ if b.y > 8 then ch.y = c.pos.Y + 1 b.y = 1 end
101101+ if b.z < 1 then ch.z = c.pos.Z - 1 b.z = 8 end
102102+ if b.z > 8 then ch.z = c.pos.Z + 1 b.z = 1 end
103103+104104+ propogateNeighboringBlockChanges(ch.x, ch.y, ch.z, b.x, b.y, b.z)
105105+ --BlockManager:GetBlock(ch.x)
106106+ end
107107+108108+ local blockName = `{x},{y},{z}`
109109+ local existing = ch:FindFirstChild(blockName)
110110+ if d == 0 then
111111+ if existing then
112112+ task.synchronize()
113113+ existing:Destroy()
114114+ task.desynchronize()
115115+ end
116116+ return
117117+ end
118118+ if not c:IsBlockRenderable(x, y, z) then
119119+ if existing then
120120+ task.synchronize()
121121+ existing:Destroy()
122122+ task.desynchronize()
123123+ end
124124+ return
125125+ end
126126+ if existing then
127127+ task.synchronize()
128128+ existing:Destroy()
129129+ task.desynchronize()
130130+ end
131131+ if not d then return end
132132+ if d.id == 0 then return end
133133+ local N = util.RotationStringToNormalId(d.state["r"] or "f")
134134+ task.synchronize()
135135+ local block = BlockManager:GetBlockRotated(d.id, N, d.state)
136136+ block.Name = blockName
137137+ block:PivotTo(util.ChunkPosToCFrame(c.pos, Vector3.new(x, y, z)))
138138+ block.Parent = ch
139139+ task.desynchronize()
140140+ end)
141141+142142+ c.unloadChunkHook = function()
143143+ conn:Disconnect()
144144+ blocks = nil
145145+ c = nil
146146+ end
147147+148148+ task.defer(function()
149149+150150+ local p = 0
151151+152152+ task.synchronize()
153153+154154+ local hb = false
155155+156156+ for a,b in pairs(blocks) do
157157+ hb = true
158158+ end
159159+160160+ local border = Instance.new("Part")
161161+ if hb == true then
162162+ border:Destroy()
163163+ border = placeBorder(c.pos.X, c.pos.Y, c.pos.Z)
164164+ end
165165+166166+ for a,b in pairs(blocks) do
167167+ task.desynchronize()
168168+ local coords = util.BlockPosStringToCoords(a)
169169+ if not c:IsBlockRenderable(coords.X, coords.Y, coords.Z) then
170170+ if ch:FindFirstChild(a) then
171171+ task.synchronize()
172172+ ch:FindFirstChild(a):Destroy()
173173+ task.desynchronize()
174174+ end
175175+ continue
176176+ end
177177+ task.desynchronize()
178178+ local N = util.RotationStringToNormalId(b.state["r"] or "f")
179179+ task.synchronize()
180180+ local block = BlockManager:GetBlockRotated(b.id, N, b.state)
181181+ if ch:FindFirstChild(a) then
182182+ ch:FindFirstChild(a):Destroy()
183183+ end
184184+ block.Name = a
185185+ block:PivotTo(util.ChunkPosToCFrame(c.pos, coords))
186186+ block.Parent = ch
187187+ p += 1
188188+ if p == 15 then
189189+ p = 0
190190+ Swait(1)
191191+ end
192192+ end
193193+194194+ finished = true
195195+196196+ task.synchronize()
197197+ border:Destroy()
198198+ task.desynchronize()
199199+200200+ task.defer(function()
201201+ task.synchronize()
202202+ for key, data in pairs(newcache) do
203203+ local coords = util.BlockPosStringToCoords(key)
204204+ for _, o in pairs(NEIGHBOR_OFFSETS) do
205205+ -- chunks are 8x8x8
206206+ local nb = {x = coords.X + o[1], y = coords.Y + o[2], z = coords.Z + o[3]}
207207+ local chCoords = {x = c.pos.X, y = c.pos.Y, z = c.pos.Z}
208208+ if nb.x == 0 then chCoords.x = c.pos.X - 1 nb.x = 8 end
209209+ if nb.x == 9 then chCoords.x = c.pos.X + 1 nb.x = 1 end
210210+211211+ if nb.y == 0 then chCoords.y = c.pos.Y - 1 nb.y = 8 end
212212+ if nb.y == 9 then chCoords.y = c.pos.Y + 1 nb.y = 1 end
213213+214214+ if nb.z == 0 then chCoords.z = c.pos.Z - 1 nb.z = 8 end
215215+ if nb.z == 9 then chCoords.z = c.pos.Z + 1 nb.z = 1 end
216216+217217+ propogateNeighboringBlockChanges(chCoords.x, chCoords.y, chCoords.z, nb.x, nb.y, nb.z)
218218+ end
219219+220220+ local existing = ch:FindFirstChild(key)
221221+ if data == 0 or (data and data.id == 0) then
222222+ if existing then
223223+ existing:Destroy()
224224+ end
225225+ continue
226226+ end
227227+ if not c:IsBlockRenderable(coords.X, coords.Y, coords.Z) then
228228+ if existing then
229229+ existing:Destroy()
230230+ end
231231+ continue
232232+ end
233233+ if existing then
234234+ existing:Destroy()
235235+ end
236236+ if not data then
237237+ continue
238238+ end
239239+ local N = util.RotationStringToNormalId(data.state["r"] or "f")
240240+ local block = BlockManager:GetBlockRotated(data.id, N, data.state)
241241+ block.Name = key
242242+ block:PivotTo(util.ChunkPosToCFrame(c.pos, coords))
243243+ block.Parent = ch
244244+ end
245245+ newcache = nil
246246+ blocks = nil
247247+ end)
248248+ task.desynchronize()
249249+ end)
250250+251251+ c.loaded = true
252252+253253+ return ch
254254+255255+end
256256+257257+return ChunkBuilder
···11+local ChunkManager = {}
22+33+local RunService = game:GetService("RunService")
44+55+local Chunk = require("./ChunkManager/Chunk")
66+local BlockManager = require("./ChunkManager/BlockManager")
77+local ChunkBuilder = require("./ChunkManager/ChunkBuilder")
88+99+local remote = game:GetService("ReplicatedStorage"):WaitForChild("RecieveChunkPacket")
1010+local tickremote = game:GetService("ReplicatedStorage"):WaitForChild("Tick")
1111+1212+local ChunkFolder = Instance.new("Folder")
1313+ChunkFolder.Name = "$blockscraft_client"
1414+1515+ChunkManager.ChunkFolder = ChunkFolder
1616+1717+local CHUNK_RADIUS = 5
1818+local LOAD_BATCH = 8
1919+local FORCELOAD_CHUNKS = {
2020+ {0, 1, 0}
2121+}
2222+2323+local unloadingChunks = {}
2424+local pendingChunkRequests = {}
2525+2626+local CHUNK_OFFSETS = {}
2727+do
2828+ for y = -CHUNK_RADIUS, CHUNK_RADIUS do
2929+ for x = -CHUNK_RADIUS, CHUNK_RADIUS do
3030+ for z = -CHUNK_RADIUS, CHUNK_RADIUS do
3131+ table.insert(CHUNK_OFFSETS, {x, y, z, (x * x) + (y * y) + (z * z)})
3232+ end
3333+ end
3434+ end
3535+ table.sort(CHUNK_OFFSETS, function(a, b)
3636+ return a[4] < b[4]
3737+ end)
3838+end
3939+4040+local function Swait(l)
4141+ task.synchronize()
4242+ for _ = 1, l do
4343+ RunService.Stepped:Wait()
4444+ end
4545+end
4646+4747+function ChunkManager:GetChunk(x, y, z)
4848+ local key = `{x},{y},{z}`
4949+ if Chunk.AllChunks[key] then
5050+ return Chunk.AllChunks[key]
5151+ end
5252+5353+ if pendingChunkRequests[key] then
5454+ task.synchronize()
5555+ while pendingChunkRequests[key] do
5656+ task.wait()
5757+ end
5858+ return Chunk.AllChunks[key]
5959+ end
6060+6161+ task.synchronize()
6262+ pendingChunkRequests[key] = true
6363+ local ok, data = pcall(function()
6464+ return remote:InvokeServer(x, y, z)
6565+ end)
6666+ if not ok then
6767+ data = {}
6868+ end
6969+ task.synchronize()
7070+ local ch = Chunk.from(x, y, z, data)
7171+ Chunk.AllChunks[key] = ch
7272+ pendingChunkRequests[key] = nil
7373+ return ch
7474+end
7575+7676+local function ensureNeighboringChunksLoaded(x, y, z)
7777+ local offsets = {
7878+ {1, 0, 0}, {-1, 0, 0},
7979+ {0, 1, 0}, {0, -1, 0},
8080+ {0, 0, 1}, {0, 0, -1}
8181+ }
8282+8383+ for _, offset in ipairs(offsets) do
8484+ local nx, ny, nz = x + offset[1], y + offset[2], z + offset[3]
8585+ ChunkManager:GetChunk(nx, ny, nz):Tick()
8686+ end
8787+end
8888+8989+function ChunkManager:LoadChunk(x, y, z)
9090+ local key = `{x},{y},{z}`
9191+ if unloadingChunks[key] or not Chunk.AllChunks[key] or Chunk.AllChunks[key].loaded then
9292+ return
9393+ end
9494+9595+ unloadingChunks[key] = true
9696+ task.defer(function()
9797+ task.desynchronize()
9898+ ensureNeighboringChunksLoaded(x, y, z)
9999+100100+ local chunk = Chunk.AllChunks[key]
101101+ if not chunk then
102102+ chunk = ChunkManager:GetChunk(x, y, z)
103103+ Chunk.AllChunks[key] = chunk
104104+ end
105105+106106+ task.synchronize()
107107+ local instance = ChunkBuilder:BuildChunk(chunk, ChunkFolder)
108108+ chunk.instance = instance
109109+ chunk.loaded = true
110110+ unloadingChunks[key] = nil
111111+ end)
112112+end
113113+114114+function ChunkManager:ForceTick()
115115+ for _, coords in ipairs(FORCELOAD_CHUNKS) do
116116+ local key = `{coords[1]},{coords[2]},{coords[3]}`
117117+ local chunk = Chunk.AllChunks[key]
118118+ if not chunk then
119119+ ChunkManager:LoadChunk(coords[1], coords[2], coords[3])
120120+ else
121121+ chunk:Tick()
122122+ end
123123+ end
124124+end
125125+126126+function ChunkManager:TickI()
127127+ for key, chunk in pairs(Chunk.AllChunks) do
128128+ if tick() - chunk.inhabitedTime <= 5 then
129129+ tickremote:FireServer(key)
130130+ end
131131+ end
132132+end
133133+134134+function ChunkManager:Tick()
135135+ ChunkManager:ForceTick()
136136+ local player = game:GetService("Players").LocalPlayer
137137+ if not player.Character then
138138+ return
139139+ end
140140+141141+ local pos = player.Character:GetPivot().Position
142142+ local chunkPos = {
143143+ x = math.round(pos.X / 32),
144144+ y = math.round(pos.Y / 32),
145145+ z = math.round(pos.Z / 32)
146146+ }
147147+148148+ task.defer(function()
149149+ local processed = 0
150150+ for _, offset in ipairs(CHUNK_OFFSETS) do
151151+ local cx, cy, cz = chunkPos.x + offset[1], chunkPos.y + offset[2], chunkPos.z + offset[3]
152152+ local chunk = ChunkManager:GetChunk(cx, cy, cz)
153153+ chunk.inhabitedTime = tick()
154154+ if not chunk.loaded then
155155+ ChunkManager:LoadChunk(cx, cy, cz)
156156+ processed += 1
157157+ if processed % LOAD_BATCH == 0 then
158158+ Swait(1)
159159+ end
160160+ end
161161+ end
162162+ end)
163163+164164+ --[[
165165+ task.defer(function()
166166+ for y = 0, 2 do
167167+ task.defer(function()
168168+ for x = -CHUNK_RADIUS, CHUNK_RADIUS do
169169+ task.desynchronize()
170170+ for z = -CHUNK_RADIUS, CHUNK_RADIUS do
171171+ local cx, cy, cz = chunkPos.x + x, y, chunkPos.z + z
172172+ local key = `{cx},{cy},{cz}`
173173+ local chunk = ChunkManager:GetChunk(cx, cy, cz)
174174+ chunk.inhabitedTime = tick()
175175+ if not chunk.loaded then
176176+ ChunkManager:LoadChunk(cx, cy, cz)
177177+ Swait(2)
178178+ end
179179+ end
180180+ task.synchronize()
181181+ end
182182+ end)
183183+ Swait(10)
184184+ end
185185+ end)
186186+ --]]
187187+188188+ for key, loadedChunk in pairs(Chunk.AllChunks) do
189189+ if tick() - loadedChunk.inhabitedTime > 15 and not unloadingChunks[key] then
190190+ unloadingChunks[key] = true
191191+ task.defer(function()
192192+ task.synchronize()
193193+ loadedChunk:Unload()
194194+ loadedChunk:Destroy()
195195+ Chunk.AllChunks[key] = nil
196196+ unloadingChunks[key] = nil
197197+ end)
198198+ end
199199+ end
200200+end
201201+202202+function ChunkManager:Init()
203203+ if not RunService:IsClient() then
204204+ error("ChunkManager:Init can only be called on the client")
205205+ end
206206+207207+ ChunkFolder.Parent = game:GetService("Workspace")
208208+ ChunkManager:ForceTick()
209209+210210+ task.defer(function()
211211+ while true do
212212+ wait(2)
213213+ ChunkManager:TickI()
214214+ end
215215+ end)
216216+217217+ task.defer(function()
218218+ while true do
219219+ task.defer(function()
220220+ local success, err = pcall(function()
221221+ ChunkManager:Tick()
222222+ end)
223223+ if not success then
224224+ warn("[CHUNKMANAGER]", err)
225225+ end
226226+ end)
227227+ Swait(20)
228228+ end
229229+ end)
230230+end
231231+232232+return ChunkManager
+47
src/ReplicatedStorage/Shared/ModLoader.lua
···11+local ML = {}
22+33+type modContext = {
44+ name: string,
55+ description: string,
66+ ns: string,
77+ author: {string},
88+ init: typeof(function()end)
99+}
1010+1111+local ReplicatedStorage = game:GetService("ReplicatedStorage")
1212+local Shared = ReplicatedStorage:WaitForChild("Shared")
1313+local ModsFolder = ReplicatedStorage:WaitForChild("Mods")
1414+1515+function ML.loadModsS()
1616+ print("[SSModLoader] Loading Mods")
1717+1818+ for _, m in pairs(ModsFolder:GetChildren()) do
1919+ local success, reason = pcall(function()
2020+ -- ignore type err
2121+ local mod: modContext = require(m)
2222+ mod.init()
2323+ print(`[SSModLoader] Loaded {mod.name} ({mod.ns}) by {table.concat(mod.author,", ")}`)
2424+ end)
2525+ if not success then
2626+ warn(`[CSModLoader] Error loading {m.Name}: {reason}`)
2727+ end
2828+ end
2929+end
3030+3131+function ML.loadModsC()
3232+ print("[CSModLoader] Loading Mods")
3333+3434+ for _, m in pairs(ModsFolder:GetChildren()) do
3535+ local success, reason = pcall(function()
3636+ -- ignore type err
3737+ local mod: modContext = require(m)
3838+ mod.init()
3939+ print(`[CSModLoader] Loaded {mod.name} ({mod.ns}) by {table.concat(mod.author,", ")}`)
4040+ end)
4141+ if not success then
4242+ warn(`[CSModLoader] Error loading {m.Name}: {reason}`)
4343+ end
4444+ end
4545+end
4646+4747+return ML
+221
src/ReplicatedStorage/Shared/PlacementManager.lua
···11+local PlacementManager = {}
22+33+local ChunkManager = require("./ChunkManager")
44+local Util = require("./Util")
55+66+PlacementManager.ChunkFolder = ChunkManager.ChunkFolder
77+88+local raycastParams = RaycastParams.new()
99+raycastParams.FilterDescendantsInstances = {PlacementManager.ChunkFolder}
1010+raycastParams.FilterType = Enum.RaycastFilterType.Include
1111+raycastParams.IgnoreWater = true
1212+1313+if _G.SB then return nil end
1414+_G.SB = true
1515+1616+PlacementManager.SelectionBox = script.SelectionBox:Clone()
1717+PlacementManager.SelectionBox.Name = "$SelectionBox"..(game:GetService("RunService"):IsServer() and "_SERVER" or "")
1818+PlacementManager.SelectionBox.Parent = game:GetService("Workspace"):FindFirstChildOfClass("Terrain")
1919+2020+-- Trash method TODO: Fix this
2121+local function findParent(i: Instance): Instance
2222+ local f = i:FindFirstAncestorOfClass("Folder")
2323+ local d = i
2424+ repeat
2525+ d = d.Parent
2626+ until d.Parent == f
2727+ return d
2828+end
2929+3030+local Mouse: Mouse = nil
3131+local lastNormalId: Enum.NormalId? = nil
3232+3333+local function normalIdToOffset(normal: Enum.NormalId): Vector3
3434+ if normal == Enum.NormalId.Top then
3535+ return Vector3.new(0, 1, 0)
3636+ elseif normal == Enum.NormalId.Bottom then
3737+ return Vector3.new(0, -1, 0)
3838+ elseif normal == Enum.NormalId.Left then
3939+ return Vector3.new(-1, 0, 0)
4040+ elseif normal == Enum.NormalId.Right then
4141+ return Vector3.new(1, 0, 0)
4242+ elseif normal == Enum.NormalId.Back then
4343+ return Vector3.new(0, 0, 1)
4444+ elseif normal == Enum.NormalId.Front then
4545+ return Vector3.new(0, 0, -1)
4646+ end
4747+ return Vector3.new(0, 0, 0)
4848+end
4949+5050+local function offsetChunkBlock(chunk: Vector3, block: Vector3, offset: Vector3)
5151+ local cx, cy, cz = chunk.X, chunk.Y, chunk.Z
5252+ local bx, by, bz = block.X + offset.X, block.Y + offset.Y, block.Z + offset.Z
5353+5454+ if bx < 1 then
5555+ bx = 8
5656+ cx -= 1
5757+ elseif bx > 8 then
5858+ bx = 1
5959+ cx += 1
6060+ end
6161+6262+ if by < 1 then
6363+ by = 8
6464+ cy -= 1
6565+ elseif by > 8 then
6666+ by = 1
6767+ cy += 1
6868+ end
6969+7070+ if bz < 1 then
7171+ bz = 8
7272+ cz -= 1
7373+ elseif bz > 8 then
7474+ bz = 1
7575+ cz += 1
7676+ end
7777+7878+ return Vector3.new(cx, cy, cz), Vector3.new(bx, by, bz)
7979+end
8080+8181+-- Gets the block and normalid of the block (and surface) the player is looking at
8282+function PlacementManager:Raycast()
8383+ if not Mouse then
8484+ Mouse = game:GetService("Players").LocalPlayer:GetMouse()
8585+ end
8686+ task.synchronize()
8787+ local objLookingAt = Mouse.Target
8888+ local dir = Mouse.TargetSurface
8989+ if not objLookingAt then
9090+ PlacementManager.SelectionBox.Adornee = nil
9191+ script.RaycastResult.Value = nil
9292+ lastNormalId = nil
9393+ return
9494+ end
9595+9696+ --if not objLookingAt:IsDescendantOf(ChunkManager.ChunkFolder) then return end
9797+ local parent = findParent(objLookingAt)
9898+ if parent:GetAttribute("ns") == true then
9999+ PlacementManager.SelectionBox.Adornee = nil
100100+ script.RaycastResult.Value = nil
101101+ lastNormalId = nil
102102+ return
103103+ end
104104+ PlacementManager.SelectionBox.Adornee = parent
105105+ script.RaycastResult.Value = parent
106106+ lastNormalId = dir
107107+ return parent, dir
108108+end
109109+110110+function PlacementManager:RaycastGetResult()
111111+ return script.RaycastResult.Value
112112+end
113113+114114+local remotes = game:GetService("ReplicatedStorage"):WaitForChild("Remotes")
115115+local placeRemote = remotes:WaitForChild("PlaceBlock")
116116+local breakRemote = remotes:WaitForChild("BreakBlock")
117117+local tickRemote = game:GetService("ReplicatedStorage").Tick
118118+119119+-- FIRES REMOTE
120120+function PlacementManager:PlaceBlock(cx, cy, cz, x, y, z, blockId: string)
121121+ --print("placeblock")
122122+ --local chunk = ChunkManager:GetChunk(cx, cy, cz)
123123+ --chunk:CreateBlock(x, y, z, blockData)
124124+ placeRemote:FireServer(cx, cy, cz, x, y, z, blockId)
125125+end
126126+127127+-- FIRES REMOTE
128128+function PlacementManager:BreakBlock(cx, cy, cz, x, y, z)
129129+ --print("breakblock")
130130+ --local chunk = ChunkManager:GetChunk(cx, cy, cz)
131131+ --chunk:RemoveBlock(x, y, z)
132132+ breakRemote:FireServer(cx, cy, cz, x, y, z)
133133+end
134134+135135+-- CLIENTSIDED
136136+function PlacementManager:PlaceBlockLocal(cx, cy, cz, x, y, z, blockData)
137137+ local chunk = ChunkManager:GetChunk(cx, cy, cz)
138138+ chunk:CreateBlock(x, y, z, blockData)
139139+end
140140+141141+-- CLIENTSIDED
142142+function PlacementManager:BreakBlockLocal(cx, cy, cz, x, y, z)
143143+ local chunk = ChunkManager:GetChunk(cx, cy, cz)
144144+ chunk:RemoveBlock(x, y, z)
145145+end
146146+147147+function PlacementManager:GetBlockAtMouse(): nil | {chunk:Vector3, block: Vector3}
148148+ local selectedPart = PlacementManager:RaycastGetResult()
149149+ --print(selectedPart and selectedPart:GetFullName() or nil)
150150+ if selectedPart == nil then
151151+ PlacementManager.SelectionBox.Adornee = nil
152152+ script.RaycastResult.Value = nil
153153+ lastNormalId = nil
154154+ return nil
155155+ end
156156+ if not selectedPart.Parent then
157157+ PlacementManager.SelectionBox.Adornee = nil
158158+ script.RaycastResult.Value = nil
159159+ lastNormalId = nil
160160+ return nil
161161+ end
162162+ local chunkCoords = Util.BlockPosStringToCoords(selectedPart.Parent.Name)
163163+ local blockCoords = Util.BlockPosStringToCoords(selectedPart.Name)
164164+165165+ return {
166166+ chunk = chunkCoords,
167167+ block = blockCoords
168168+ }
169169+170170+end
171171+172172+function PlacementManager:GetTargetAtMouse(): nil | {chunk:Vector3, block: Vector3, normal: Enum.NormalId}
173173+ local hit = PlacementManager:GetBlockAtMouse()
174174+ if not hit or not lastNormalId then
175175+ return nil
176176+ end
177177+178178+ return {
179179+ chunk = hit.chunk,
180180+ block = hit.block,
181181+ normal = lastNormalId
182182+ }
183183+end
184184+185185+function PlacementManager:GetPlacementAtMouse(): nil | {chunk:Vector3, block: Vector3}
186186+ local hit = PlacementManager:GetTargetAtMouse()
187187+ if not hit then
188188+ return nil
189189+ end
190190+ local offset = normalIdToOffset(hit.normal)
191191+ local placeChunk, placeBlock = offsetChunkBlock(hit.chunk, hit.block, offset)
192192+ return {
193193+ chunk = placeChunk,
194194+ block = placeBlock
195195+ }
196196+end
197197+198198+function PlacementManager:Init()
199199+ game:GetService("RunService").Heartbeat:Connect(function()
200200+ local a,b = pcall(function()
201201+ PlacementManager:Raycast()
202202+ end)
203203+ if not a then
204204+ task.synchronize()
205205+ PlacementManager.SelectionBox.Adornee = nil
206206+ script.RaycastResult.Value = nil
207207+ task.desynchronize()
208208+ end
209209+ end)
210210+ tickRemote.OnClientEvent:Connect(function(m, cx, cy, cz, x, y, z, d)
211211+ --warn("PROPOGATED TICK", m, cx, cy, cz, x, y, z, d)
212212+ if m == "B_C" then
213213+ PlacementManager:PlaceBlockLocal(cx, cy, cz, x, y ,z, d)
214214+ end
215215+ if m == "B_D" then
216216+ PlacementManager:BreakBlockLocal(cx, cy, cz, x, y ,z)
217217+ end
218218+ end)
219219+end
220220+221221+return PlacementManager
···11+local module = {}
22+33+function module.BlockPosStringToCoords(s: string): Vector3
44+ -- a,b,c
55+ local split = string.split(s,",")
66+ return Vector3.new(tonumber(split[1]), tonumber(split[2]), tonumber(split[3]))
77+end
88+99+function module.RotationStringToNormalId(s: string): Enum.NormalId
1010+ if not s then return Enum.NormalId.Front end
1111+ if s == "f" then
1212+ return Enum.NormalId.Front
1313+ end
1414+ if s == "b" then
1515+ return Enum.NormalId.Back
1616+ end
1717+ if s == "l" then
1818+ return Enum.NormalId.Left
1919+ end
2020+ if s == "r" then
2121+ return Enum.NormalId.Right
2222+ end
2323+ if s == "t" then
2424+ return Enum.NormalId.Top
2525+ end
2626+ if s == "b" then
2727+ return Enum.NormalId.Bottom
2828+ end
2929+ warn("Could not convert",s,"to Enum.NormalId")
3030+ return Enum.NormalId.Front
3131+end
3232+3333+-- chunk size 8x8x8 relative block indexs 1-8
3434+function module.GlobalBlockPosToRelative(x: number,y:number,z:number)
3535+ return x%8,y%8,z%8
3636+end
3737+3838+function module.ChunkPosToCFrame(chunk: Vector3, block: Vector3): CFrame
3939+ return CFrame.new(
4040+ CFrame.new(
4141+ (32*chunk.X)+(block.X*4)-18,
4242+ (32*chunk.Y)+(block.Y*4)-18,
4343+ (32*chunk.Z)+(block.Z*4)-18
4444+ ).Position
4545+ )
4646+end
4747+4848+return module
···11+local CHAR_SET = [[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/]]
22+33+-- Tradition is to use chars for the lookup table instead of codepoints.
44+-- But due to how we're running the encode function, it's faster to use codepoints.
55+local encode_char_set = {}
66+local decode_char_set = {}
77+for i = 1, 64 do
88+ encode_char_set[i - 1] = string.byte(CHAR_SET, i, i)
99+ decode_char_set[string.byte(CHAR_SET, i, i)] = i - 1
1010+end
1111+1212+-- stylua: ignore
1313+local HEX_TO_BIN = {
1414+ ["0"] = "0000", ["1"] = "0001", ["2"] = "0010", ["3"] = "0011",
1515+ ["4"] = "0100", ["5"] = "0101", ["6"] = "0110", ["7"] = "0111",
1616+ ["8"] = "1000", ["9"] = "1001", ["a"] = "1010", ["b"] = "1011",
1717+ ["c"] = "1100", ["d"] = "1101", ["e"] = "1110", ["f"] = "1111"
1818+}
1919+2020+-- stylua: ignore
2121+local NORMAL_ID_VECTORS = { -- [Enum.Value] = Vector3.fromNormalId(Enum)
2222+ [0] = Vector3.new(1, 0, 0), -- Enum.NormalId.Right
2323+ [1] = Vector3.new(0, 1, 0), -- Enum.NormalId.Top
2424+ [2] = Vector3.new(0, 0, 1), -- Enum.NormalId.Back
2525+ [3] = Vector3.new(-1, 0, 0), -- Enum.NormalId.Left
2626+ [4] = Vector3.new(0, -1, 0), -- Enum.NormalId.Bottom
2727+ [5] = Vector3.new(0, 0, -1) -- Enum.NormalId.Front
2828+}
2929+3030+local ONES_VECTOR = Vector3.new(1, 1, 1)
3131+3232+local BOOL_TO_BIT = { [true] = 1, [false] = 0 }
3333+3434+local CRC32_POLYNOMIAL = 0xedb88320
3535+3636+local crc32_poly_lookup = {}
3737+for i = 0, 255 do
3838+ local crc = i
3939+ for _ = 1, 8 do
4040+ local mask = -bit32.band(crc, 1)
4141+ crc = bit32.bxor(bit32.rshift(crc, 1), bit32.band(CRC32_POLYNOMIAL, mask))
4242+ end
4343+ crc32_poly_lookup[i] = crc
4444+end
4545+4646+local powers_of_2 = {}
4747+for i = 0, 64 do
4848+ powers_of_2[i] = 2 ^ i
4949+end
5050+5151+local byte_to_hex = {}
5252+for i = 0, 255 do
5353+ byte_to_hex[i] = string.format("%02x", i)
5454+end
5555+5656+local function bitBuffer(stream)
5757+ if stream ~= nil then
5858+ assert(type(stream) == "string", "argument to BitBuffer constructor must be either nil or a string")
5959+ end
6060+6161+ -- The bit buffer works by keeping an array of bytes, a 'final' byte, and how many bits are currently in that last byte
6262+ -- Bits are not kept track of on their own, and are instead combined to form a byte, which is stored in the last space in the array.
6363+ -- This byte is also stored seperately, so that table operations aren't needed to read or modify its value.
6464+ -- The byte array is called `bytes`. The last byte is stored in `lastByte`. The bit counter is stored in `bits`.
6565+6666+ local bits = 0 -- How many free floating bits there are.
6767+ local bytes = {} --! -- Array of bytes currently in the buffer
6868+ local lastByte = 0 -- The most recent byte in the buffer, made up of free floating bits
6969+7070+ local byteCount = 0 -- This variable keeps track of how many bytes there are total in the bit buffer.
7171+ local bitCount = 0 -- This variable keeps track of how many bits there are total in the bit buffer
7272+7373+ local pointer = 0 -- This variable keeps track of what bit the read functions start at
7474+ local pointerByte = 1 -- This variable keeps track of what byte the pointer is at. It starts at 1 since the byte array starts at 1.
7575+7676+ if stream then
7777+ byteCount = #stream
7878+ bitCount = byteCount * 8
7979+8080+ bytes = table.create(#stream)
8181+8282+ for i = 1, byteCount do
8383+ bytes[i] = string.byte(stream, i, i)
8484+ end
8585+ end
8686+8787+ local function dumpBinary()
8888+ -- This function is for debugging or analysis purposes.
8989+ -- It dumps the contents of the byte array and the remaining bits into a string of binary digits.
9090+ -- Thus, bytes [97, 101] with bits [1, 1, 0] would output "01100001 01100101 110"
9191+ local output = table.create(byteCount) --!
9292+ for i, v in ipairs(bytes) do
9393+ output[i] = string.gsub(byte_to_hex[v], "%x", HEX_TO_BIN)
9494+ end
9595+ if bits ~= 0 then
9696+ -- Because the last byte (where the free floating bits are stored) is in the byte array, it has to be overwritten.
9797+ output[byteCount] = string.sub(output[byteCount], 1, bits)
9898+ end
9999+100100+ return table.concat(output, " ")
101101+ end
102102+103103+ local function dumpStringOld()
104104+ -- This function is for accessing the total contents of the bitbuffer.
105105+ -- This function combines all the bytes, including the last byte, into a string of binary data.
106106+ -- Thus, bytes [97, 101] and bits [1, 1, 0] would become (in hex) "0x61 0x65 0x06"
107107+108108+ -- It's substantially faster to create several smaller strings before using table.concat. (well maybe it was, but it isn't now post 2022)
109109+ local output = table.create(math.ceil(byteCount / 4096)) --!
110110+ local c = 1
111111+ for i = 1, byteCount, 4096 do -- groups of 4096 bytes is the point at which there are diminishing returns
112112+ output[c] = string.char(table.unpack(bytes, i, math.min(byteCount, i + 4095)))
113113+ c = c + 1
114114+ end
115115+116116+ return table.concat(output, "")
117117+ end
118118+119119+ --Let lua be lua
120120+ local function dumpString()
121121+ return string.char(table.unpack(bytes))
122122+ end
123123+124124+125125+ local function dumpHex()
126126+ -- This function is for getting the hex of the bitbuffer's contents, should that be desired
127127+ local output = table.create(byteCount) --!
128128+ for i, v in ipairs(bytes) do
129129+ output[i] = byte_to_hex[v]
130130+ end
131131+132132+ return table.concat(output, "")
133133+ end
134134+135135+ local function dumpBase64()
136136+ -- Base64 is a safe and easy way to convert binary data to be entirely printable
137137+ -- It works on the principle that groups of 3 bytes (24 bits) can evenly be divided into 4 groups of 6
138138+ -- And 2^6 is a mere 64, far less than the number of printable characters.
139139+ -- If there are any missing bytes, `=` is added to the end as padding.
140140+ -- Base64 increases the size of its input by 33%.
141141+ local output = table.create(math.ceil(byteCount * 1.333)) --!
142142+143143+ local c = 1
144144+ for i = 1, byteCount, 3 do
145145+ local b1, b2, b3 = bytes[i], bytes[i + 1], bytes[i + 2]
146146+ local packed = bit32.bor(bit32.lshift(b1, 16), bit32.lshift(b2 or 0, 8), b3 or 0)
147147+148148+ -- This can be done with bit32.extract (and/or bit32.lshift, bit32.band, bit32.rshift)
149149+ -- But bit masking and shifting is more eloquent in my opinion.
150150+ output[c] = encode_char_set[bit32.rshift(bit32.band(packed, 0xfc0000), 0x12)]
151151+ output[c + 1] = encode_char_set[bit32.rshift(bit32.band(packed, 0x3f000), 0xc)]
152152+ output[c + 2] = b2 and encode_char_set[bit32.rshift(bit32.band(packed, 0xfc0), 0x6)] or 0x3d -- 0x3d == "="
153153+ output[c + 3] = b3 and encode_char_set[bit32.band(packed, 0x3f)] or 0x3d
154154+155155+ c = c + 4
156156+ end
157157+ c = c - 1 -- c will always be 1 more than the length of `output`
158158+159159+ local realOutput = table.create(math.ceil(c / 0x1000)) --!
160160+ local k = 1
161161+ for i = 1, c, 0x1000 do
162162+ realOutput[k] = string.char(table.unpack(output, i, math.min(c, i + 0xfff)))
163163+ k = k + 1
164164+ end
165165+166166+ return table.concat(realOutput, "")
167167+ end
168168+169169+ local function exportChunk(chunkLength)
170170+ assert(type(chunkLength) == "number", "argument #1 to BitBuffer.exportChunk should be a number")
171171+ assert(chunkLength > 0, "argument #1 to BitBuffer.exportChunk should be above zero")
172172+ assert(chunkLength % 1 == 0, "argument #1 to BitBuffer.exportChunk should be an integer")
173173+174174+ -- Since `i` is being returned, the most eloquent way to handle this is with a coroutine
175175+ -- This allows returning the existing value of `i` without having to increment it first.
176176+ -- The alternative was starting at `i = -(chunkLength-1)` and incrementing at the start of the iterator function.
177177+ return coroutine.wrap(function()
178178+ local realChunkLength = chunkLength - 1
179179+ -- Since this function only has one 'state', it's perfectly fine to use a for-loop.
180180+ for i = 1, byteCount, chunkLength do
181181+ local chunk = string.char(table.unpack(bytes, i, math.min(byteCount, i + realChunkLength)))
182182+ coroutine.yield(i, chunk)
183183+ end
184184+ end)
185185+ end
186186+187187+ local function exportBase64Chunk(chunkLength)
188188+ chunkLength = chunkLength or 76
189189+ assert(type(chunkLength) == "number", "argument #1 to BitBuffer.exportBase64Chunk should be a number")
190190+ assert(chunkLength > 0, "argument #1 to BitBuffer.exportBase64Chunk should be above zero")
191191+ assert(chunkLength % 1 == 0, "argument #1 to BitBuffer.exportBase64Chunk should be an integer")
192192+193193+ local output = table.create(math.ceil(byteCount * 0.333)) --!
194194+195195+ local c = 1
196196+ for i = 1, byteCount, 3 do
197197+ local b1, b2, b3 = bytes[i], bytes[i + 1], bytes[i + 2]
198198+ local packed = bit32.bor(bit32.lshift(b1, 16), bit32.lshift(b2 or 0, 8), b3 or 0)
199199+200200+ output[c] = encode_char_set[bit32.rshift(bit32.band(packed, 0xfc0000), 0x12)]
201201+ output[c + 1] = encode_char_set[bit32.rshift(bit32.band(packed, 0x3f000), 0xc)]
202202+ output[c + 2] = b2 and encode_char_set[bit32.rshift(bit32.band(packed, 0xfc0), 0x6)] or 0x3d
203203+ output[c + 3] = b3 and encode_char_set[bit32.band(packed, 0x3f)] or 0x3d
204204+205205+ c = c + 4
206206+ end
207207+ c = c - 1
208208+209209+ return coroutine.wrap(function()
210210+ local realChunkLength = chunkLength - 1
211211+ for i = 1, c, chunkLength do
212212+ local chunk = string.char(table.unpack(output, i, math.min(c, i + realChunkLength)))
213213+ coroutine.yield(chunk)
214214+ end
215215+ end)
216216+ end
217217+218218+ local function exportHexChunk(chunkLength)
219219+ assert(type(chunkLength) == "number", "argument #1 to BitBuffer.exportHexChunk should be a number")
220220+ assert(chunkLength > 0, "argument #1 to BitBuffer.exportHexChunk should be above zero")
221221+ assert(chunkLength % 1 == 0, "argument #1 to BitBuffer.exportHexChunk should be an integer")
222222+223223+ local halfLength = math.floor(chunkLength / 2)
224224+225225+ if chunkLength % 2 == 0 then
226226+ return coroutine.wrap(function()
227227+ local output = {} --!
228228+ for i = 1, byteCount, halfLength do
229229+ for c = 0, halfLength - 1 do
230230+ output[c] = byte_to_hex[bytes[i + c]]
231231+ end
232232+ coroutine.yield(table.concat(output, "", 0))
233233+ end
234234+ end)
235235+ else
236236+ return coroutine.wrap(function()
237237+ local output = { [0] = "" } --!
238238+ local remainder = ""
239239+240240+ local i = 1
241241+ while i <= byteCount do
242242+ if remainder == "" then
243243+ output[0] = ""
244244+ for c = 0, halfLength - 1 do
245245+ output[c + 1] = byte_to_hex[bytes[i + c]]
246246+ end
247247+ local endByte = byte_to_hex[bytes[i + halfLength]]
248248+ if endByte then
249249+ output[halfLength + 1] = string.sub(endByte, 1, 1)
250250+ remainder = string.sub(endByte, 2, 2)
251251+ end
252252+ i = i + 1
253253+ else
254254+ output[0] = remainder
255255+ for c = 0, halfLength - 1 do
256256+ output[c + 1] = byte_to_hex[bytes[i + c]]
257257+ end
258258+ output[halfLength + 1] = ""
259259+ remainder = ""
260260+ end
261261+262262+ coroutine.yield(table.concat(output, "", 0))
263263+ i = i + halfLength
264264+ end
265265+ end)
266266+ end
267267+ end
268268+269269+ local function crc32()
270270+ local crc = 0xffffffff -- 2^32
271271+272272+ for _, v in ipairs(bytes) do
273273+ local poly = crc32_poly_lookup[bit32.band(bit32.bxor(crc, v), 255)]
274274+ crc = bit32.bxor(bit32.rshift(crc, 8), poly)
275275+ end
276276+277277+ return bit32.bnot(crc) % 0xffffffff -- 2^32
278278+ end
279279+280280+ local function getLength()
281281+ return bitCount
282282+ end
283283+284284+ local function getByteLength()
285285+ return byteCount
286286+ end
287287+288288+ local function getPointer()
289289+ -- This function gets the value of the pointer. This is self-explanatory.
290290+ return pointer
291291+ end
292292+293293+ local function setPointer(n)
294294+ assert(type(n) == "number", "argument #1 to BitBuffer.setPointer should be a number")
295295+ assert(n >= 0, "argument #1 to BitBuffer.setPointer should be zero or higher")
296296+ assert(n % 1 == 0, "argument #1 to BitBuffer.setPointer should be an integer")
297297+ assert(n <= bitCount, "argument #1 to BitBuffer.setPointerByte should within range of the buffer")
298298+ -- This function sets the value of pointer. This is self-explanatory.
299299+ pointer = n
300300+ pointerByte = math.floor(n / 8) + 1
301301+ end
302302+303303+ local function setPointerFromEnd(n)
304304+ assert(type(n) == "number", "argument #1 to BitBuffer.setPointerFromEnd should be a number")
305305+ assert(n >= 0, "argument #1 to BitBuffer.setPointerFromEnd should be zero or higher")
306306+ assert(n % 1 == 0, "argument #1 to BitBuffer.setPointerFromEnd should be an integer")
307307+ assert(n <= bitCount, "argument #1 to BitBuffer.setPointerFromEnd should within range of the buffer")
308308+309309+ pointer = bitCount - n
310310+ pointerByte = math.floor(pointer / 8 + 1)
311311+ end
312312+313313+ local function getPointerByte()
314314+ return pointerByte
315315+ end
316316+317317+ local function setPointerByte(n)
318318+ assert(type(n) == "number", "argument #1 to BitBuffer.setPointerByte should be a number")
319319+ assert(n > 0, "argument #1 to BitBuffer.setPointerByte should be positive")
320320+ assert(n % 1 == 0, "argument #1 to BitBuffer.setPointerByte should be an integer")
321321+ assert(n <= byteCount, "argument #1 to BitBuffer.setPointerByte should be within range of the buffer")
322322+ -- Sets the value of the pointer in bytes instead of bits
323323+ pointer = n * 8
324324+ pointerByte = n
325325+ end
326326+327327+ local function setPointerByteFromEnd(n)
328328+ assert(type(n) == "number", "argument #1 to BitBuffer.setPointerByteFromEnd should be a number")
329329+ assert(n >= 0, "argument #1 to BitBuffer.setPointerByteFromEnd should be zero or higher")
330330+ assert(n % 1 == 0, "argument #1 to BitBuffer.setPointerByteFromEnd should be an integer")
331331+ assert(n <= byteCount, "argument #1 to BitBuffer.setPointerByteFromEnd should be within range of the buffer")
332332+333333+ pointerByte = byteCount - n
334334+ pointer = pointerByte * 8
335335+ end
336336+337337+ local function isFinished()
338338+ return pointer == bitCount
339339+ end
340340+341341+ local function writeBits(...)
342342+ -- The first of two main functions for the actual 'writing' of the bitbuffer.
343343+ -- This function takes a vararg of 1s and 0s and writes them to the buffer.
344344+ local bitN = select("#", ...)
345345+ if bitN == 0 then
346346+ return
347347+ end -- Throwing here seems unnecessary
348348+ bitCount = bitCount + bitN
349349+ local packed = table.pack(...)
350350+ for _, v in ipairs(packed) do
351351+ assert(v == 1 or v == 0, "arguments to BitBuffer.writeBits should be either 1 or 0")
352352+ if bits == 0 then -- If the bit count is 0, increment the byteCount
353353+ -- This is the case at the beginning of the buffer as well as when the the buffer reaches 7 bits,
354354+ -- so it's done at the beginning of the loop.
355355+ byteCount = byteCount + 1
356356+ end
357357+ lastByte = lastByte + (v == 1 and powers_of_2[7 - bits] or 0) -- Add the current bit to lastByte, from right to left
358358+ bits = bits + 1
359359+ if bits == 8 then -- If the bit count is 8, set it to 0, write lastByte to the byte list, and set lastByte to 0
360360+ bits = 0
361361+ bytes[byteCount] = lastByte
362362+ lastByte = 0
363363+ end
364364+ end
365365+ if bits ~= 0 then -- If there are some bits in lastByte, it has to be put into lastByte
366366+ -- If this is done regardless of the bit count, there might be a trailing zero byte
367367+ bytes[byteCount] = lastByte
368368+ end
369369+ end
370370+371371+ local function writeByte(n)
372372+ --assert(type(n) == "number", "argument #1 to BitBuffer.writeByte should be a number")
373373+ --assert(n >= 0 and n <= 255, "argument #1 to BitBuffer.writeByte should be in the range [0, 255]")
374374+ --assert(n % 1 == 0, "argument #1 to BitBuffer.writeByte should be an integer")
375375+376376+ -- The second of two main functions for the actual 'writing' of the bitbuffer.
377377+ -- This function takes a byte (an 8-bit integer) and writes it to the buffer.
378378+ if bits == 0 then
379379+ -- If there aren't any free-floating bits, this is easy.
380380+ byteCount = byteCount + 1
381381+ bytes[byteCount] = n
382382+ else
383383+ local nibble = bit32.rshift(n, bits) -- Shift `bits` number of bits out of `n` (they go into the aether)
384384+ bytes[byteCount] = lastByte + nibble -- Manually set the most recent byte to the lastByte + the front part of `n`
385385+ byteCount = byteCount + 1
386386+ lastByte = bit32.band(bit32.lshift(n, 8 - bits), 255) -- Shift `n` forward `8-bits` and get what remains in the first 8 bits
387387+ bytes[byteCount] = lastByte
388388+ end
389389+ bitCount = bitCount + 8 -- Increment the bit counter
390390+ end
391391+392392+ local function writeBytesFast(tab)
393393+ assert(bits == 0, "writeBytesFast can only work for whole byte streams")
394394+ local count = #tab
395395+ table.move(tab, 1 , count, byteCount + 1, bytes)
396396+ byteCount+= count
397397+ bitCount += count * 8
398398+ end
399399+400400+ local function writeUnsigned(width, n)
401401+ assert(type(width) == "number", "argument #1 to BitBuffer.writeUnsigned should be a number")
402402+ assert(width >= 1 and width <= 64, "argument #1 to BitBuffer.writeUnsigned should be in the range [1, 64]")
403403+ assert(width % 1 == 0, "argument #1 to BitBuffer.writeUnsigned should be an integer")
404404+405405+ assert(type(n) == "number", "argument #2 to BitBuffer.writeUnsigned should be a number")
406406+ assert(n >= 0 and n <= powers_of_2[width] - 1, "argument #2 to BitBuffer.writeUnsigned is out of range")
407407+ assert(n % 1 == 0, "argument #2 to BitBuffer.writeUnsigned should be an integer")
408408+ -- Writes unsigned integers of arbitrary length to the buffer.
409409+ -- This is the first function that uses other functions in the buffer to function.
410410+ -- This is done because the space taken up would be rather large for very little performance gain.
411411+412412+ -- Get the number of bytes and number of floating bits in the specified width
413413+ local bytesInN, bitsInN = math.floor(width / 8), width % 8
414414+ local extractedBits = table.create(bitsInN) --!
415415+416416+ -- If the width is less than or equal to 32-bits, bit32 can be used without any problem.
417417+ if width <= 32 then
418418+ -- Counting down from the left side, the bytes are written to the buffer
419419+ local c = width
420420+ for _ = 1, bytesInN do
421421+ c = c - 8
422422+ writeByte(bit32.extract(n, c, 8))
423423+ end
424424+ -- Any remaining bits are stored in an array
425425+ for i = bitsInN - 1, 0, -1 do
426426+ extractedBits[bitsInN - i] = BOOL_TO_BIT[bit32.btest(n, powers_of_2[i])]
427427+ end
428428+ -- Said array is then used to write them to the buffer
429429+ writeBits(table.unpack(extractedBits))
430430+ else
431431+ -- If the width is greater than 32, the number has to be divided up into a few 32-bit or less numbers
432432+ local leastSignificantChunk = n % 0x100000000 -- Get bits 0-31 (counting from the right side). 0x100000000 is 2^32.
433433+ local mostSignificantChunk = math.floor(n / 0x100000000) -- Get any remaining bits by manually right shifting by 32 bits
434434+435435+ local c = width - 32 -- The number of bits in mostSignificantChunk is variable, but a counter is still needed
436436+ for _ = 1, bytesInN - 4 do -- 32 bits is 4 bytes
437437+ c = c - 8
438438+ writeByte(bit32.extract(mostSignificantChunk, c, 8))
439439+ end
440440+ -- `bitsInN` is always going to be the number of spare bits in `mostSignificantChunk`
441441+ -- which comes before `leastSignificantChunk`
442442+ for i = bitsInN - 1, 0, -1 do
443443+ extractedBits[bitsInN - i] = BOOL_TO_BIT[bit32.btest(mostSignificantChunk, powers_of_2[i])]
444444+ end
445445+ writeBits(table.unpack(extractedBits))
446446+447447+ for i = 3, 0, -1 do -- Then of course, write all 4 bytes of leastSignificantChunk
448448+ writeByte(bit32.extract(leastSignificantChunk, i * 8, 8))
449449+ end
450450+ end
451451+ end
452452+453453+ local function writeSigned(width, n)
454454+ assert(type(width) == "number", "argument #1 to BitBuffer.writeSigned should be a number")
455455+ assert(width >= 2 and width <= 64, "argument #1 to BitBuffer.writeSigned should be in the range [2, 64]")
456456+ assert(width % 1 == 0, "argument #1 to BitBuffer.writeSigned should be an integer")
457457+458458+ assert(type(n) == "number", "argument #2 to BitBuffer.writeSigned should be a number")
459459+ assert(
460460+ n >= -powers_of_2[width - 1] and n <= powers_of_2[width - 1] - 1,
461461+ "argument #2 to BitBuffer.writeSigned is out of range"
462462+ )
463463+ assert(n % 1 == 0, "argument #2 to BitBuffer.writeSigned should be an integer")
464464+ -- Writes signed integers of arbitrary length to the buffer.
465465+ -- These integers are stored using two's complement.
466466+ -- Essentially, this means the first bit in the number is used to store whether it's positive or negative
467467+ -- If the number is positive, it's stored normally.
468468+ -- If it's negative, the number that's stored is equivalent to the max value of the width + the number
469469+ if n >= 0 then
470470+ writeBits(0)
471471+ writeUnsigned(width - 1, n) -- One bit is used for the sign, so the stored number's width is actually width-1
472472+ else
473473+ writeBits(1)
474474+ writeUnsigned(width - 1, powers_of_2[width - 1] + n)
475475+ end
476476+ end
477477+478478+ local function writeFloat(exponentWidth, mantissaWidth, n)
479479+ assert(type(exponentWidth) == "number", "argument #1 to BitBuffer.writeFloat should be a number")
480480+ assert(
481481+ exponentWidth >= 1 and exponentWidth <= 64,
482482+ "argument #1 to BitBuffer.writeFloat should be in the range [1, 64]"
483483+ )
484484+ assert(exponentWidth % 1 == 0, "argument #1 to BitBuffer.writeFloat should be an integer")
485485+486486+ assert(type(mantissaWidth) == "number", "argument #2 to BitBuffer.writeFloat should be a number")
487487+ assert(
488488+ mantissaWidth >= 1 and mantissaWidth <= 64,
489489+ "argument #2 to BitBuffer.writeFloat should be in the range [1, 64]"
490490+ )
491491+ assert(mantissaWidth % 1 == 0, "argument #2 to BitBuffer.writeFloat should be an integer")
492492+493493+ assert(type(n) == "number", "argument #3 to BitBuffer.writeFloat should be a number")
494494+495495+ -- Given that floating point numbers are particularly hard to grasp, this function is annotated heavily.
496496+ -- This stackoverflow answer is a great help if you just want an overview:
497497+ -- https://stackoverflow.com/a/7645264
498498+ -- Essentially, floating point numbers are scientific notation in binary.
499499+ -- Instead of expressing numbers like 10^e*m, floating points instead use 2^e*m.
500500+ -- For the sake of this function, `e` is referred to as `exponent` and `m` is referred to as `mantissa`.
501501+502502+ -- Floating point numbers are stored in memory as a sequence of bitfields.
503503+ -- Every float has a set number of bits assigned for exponent values and mantissa values, along with one bit for the sign.
504504+ -- The order of the bits in the memory is: sign, exponent, mantissa.
505505+506506+ -- Given that floating points have to represent numbers less than zero as well as those above them,
507507+ -- some parts of the exponent are set aside to be negative exponents. In the case of floats,
508508+ -- this is about half of the values. To calculate the 'real' value of an exponent a number that's half of the max exponent
509509+ -- is added to the exponent. More info can be found here: https://stackoverflow.com/q/2835278
510510+ -- This number is called the 'bias'.
511511+ local bias = powers_of_2[exponentWidth - 1] - 1
512512+513513+ local sign = n < 0 -- The sign of a number is important.
514514+ -- In this case, since we're using a lookup table for the sign bit, we want `sign` to indicate if the number is negative or not.
515515+ n = math.abs(n) -- But it's annoying to work with negative numbers and the sign isn't important for decomposition.
516516+517517+ -- Lua has a function specifically for decomposing (or taking apart) a floating point number into its pieces.
518518+ -- These pieces, as listed above, are the mantissa and exponent.
519519+ local mantissa, exponent = math.frexp(n)
520520+521521+ -- Before we go further, there are some concepts that get special treatment in the floating point format.
522522+ -- These have to be accounted for before normal floats are written to the buffer.
523523+524524+ if n == math.huge then
525525+ -- Positive and negative infinities are specifically indicated with an exponent that's all 1s
526526+ -- and a mantissa that's all 0s.
527527+ writeBits(BOOL_TO_BIT[sign]) -- As previously said, there's a bit for the sign
528528+ writeUnsigned(exponentWidth, powers_of_2[exponentWidth] - 1) -- Then comes the exponent
529529+ writeUnsigned(mantissaWidth, 0) -- And finally the mantissa
530530+ return
531531+ elseif n ~= n then
532532+ -- NaN is indicated with an exponent that's all 1s and a mantissa that isn't 0.
533533+ -- In theory, the individual bits of NaN should be maintained but Lua doesn't allow that,
534534+ -- so the mantissa is just being set to 10 for no particular reason.
535535+ writeBits(BOOL_TO_BIT[sign])
536536+ writeUnsigned(exponentWidth, powers_of_2[exponentWidth] - 1)
537537+ writeUnsigned(mantissaWidth, 10)
538538+ return
539539+ elseif n == 0 then
540540+ -- Zero is represented with an exponent that's zero and a mantissa that's also zero.
541541+ -- Lua doesn't have a signed zero, so that translates to the entire number being all 0s.
542542+ writeUnsigned(exponentWidth + mantissaWidth + 1, 0)
543543+ return
544544+ elseif exponent + bias <= 1 then
545545+ -- Subnormal numbers are a number that's exponent (when biased) is zero.
546546+ -- Because of a quirk with the way Lua and C decompose numbers, subnormal numbers actually have an exponent of one when biased.
547547+548548+ -- The process behind this is explained below, so for the sake of brevity it isn't explained here.
549549+ -- The only difference between processing subnormal and normal numbers is with the mantissa.
550550+ -- As subnormal numbers always start with a 0 (in binary), it doesn't need to be removed or shifted out
551551+ -- so it's a simple shift and round.
552552+ mantissa = math.floor(mantissa * powers_of_2[mantissaWidth] + 0.5)
553553+554554+ writeBits(BOOL_TO_BIT[sign])
555555+ writeUnsigned(exponentWidth, 0) -- Subnormal numbers always have zero for an exponent
556556+ writeUnsigned(mantissaWidth, mantissa)
557557+ return
558558+ end
559559+560560+ -- In every normal case, the mantissa of a number will have a 1 directly after the decimal point (in binary).
561561+ -- As an example, 0.15625 has a mantissa of 0.625, which is 0.101 in binary. The 1 after the decimal point is always there.
562562+ -- That means that for the sake of space efficiency that can be left out.
563563+ -- The bit has to be removed. This uses subtraction and multiplication to do it since bit32 is for integers only.
564564+ -- The mantissa is then shifted up by the width of the mantissa field and rounded.
565565+ mantissa = math.floor((mantissa - 0.5) * 2 * powers_of_2[mantissaWidth] + 0.5)
566566+ -- (The first fraction bit is equivalent to 0.5 in decimal)
567567+568568+ -- After that, it's just a matter of writing to the stream:
569569+ writeBits(BOOL_TO_BIT[sign])
570570+ writeUnsigned(exponentWidth, exponent + bias - 1) -- The bias is added to the exponent to properly offset it
571571+ -- The extra -1 is added because Lua, for whatever reason, doesn't normalize its results
572572+ -- This is the cause of the 'quirk' mentioned when handling subnormal number
573573+ -- As an example, math.frexp(0.15625) = 0.625, -2
574574+ -- This means that 0.15625 = 0.625*2^-2
575575+ -- Or, in binary: 0.00101 = 0.101 >> 2
576576+ -- This is a correct statement but the actual result is meant to be:
577577+ -- 0.00101 = 1.01 >> 3, or 0.15625 = 1.25*2^-3
578578+ -- A small but important distinction that has made writing this module frustrating because no documentation notates this.
579579+ writeUnsigned(mantissaWidth, mantissa)
580580+ end
581581+582582+ local function writeBase64(input)
583583+ assert(type(input) == "string", "argument #1 to BitBuffer.writeBase64 should be a string")
584584+ assert(
585585+ not string.find(input, "[^%w%+/=]"),
586586+ "argument #1 to BitBuffer.writeBase64 should only contain valid base64 characters"
587587+ )
588588+589589+ for i = 1, #input, 4 do
590590+ local b1, b2, b3, b4 = string.byte(input, i, i + 3)
591591+592592+ b1 = decode_char_set[b1]
593593+ b2 = decode_char_set[b2]
594594+ b3 = decode_char_set[b3]
595595+ b4 = decode_char_set[b4]
596596+597597+ local packed = bit32.bor(bit32.lshift(b1, 18), bit32.lshift(b2, 12), bit32.lshift(b3 or 0, 6), b4 or 0)
598598+599599+ writeByte(bit32.rshift(packed, 16))
600600+ if not b3 then
601601+ break
602602+ end
603603+ writeByte(bit32.band(bit32.rshift(packed, 8), 0xff))
604604+ if not b4 then
605605+ break
606606+ end
607607+ writeByte(bit32.band(packed, 0xff))
608608+ end
609609+ end
610610+611611+ local function writeString(str)
612612+ assert(type(str) == "string", "argument #1 to BitBuffer.writeString should be a string")
613613+ -- The default mode of writing strings is length-prefixed.
614614+ -- This means that the length of the string is written before the contents of the string.
615615+ -- For the sake of speed it has to be an even byte.
616616+ -- One and two bytes is too few characters (255 bytes and 65535 bytes respectively), so it has to be higher.
617617+ -- Three bytes is roughly 16.77mb, and four is roughly 4.295gb. Given this is Lua and is thus unlikely to be processing strings
618618+ -- that large, this function uses three bytes, or 24 bits for the length
619619+620620+ writeUnsigned(24, #str)
621621+622622+ for i = 1, #str do
623623+ writeByte(string.byte(str, i, i))
624624+ end
625625+ end
626626+627627+ local function writeTerminatedString(str)
628628+ assert(type(str) == "string", "argument #1 to BitBuffer.writeTerminatedString should be a string")
629629+ -- This function writes strings that are null-terminated.
630630+ -- Null-terminated strings are strings of bytes that end in a 0 byte (\0)
631631+ -- This isn't the default because it doesn't allow for binary data to be written cleanly.
632632+633633+ for i = 1, #str do
634634+ writeByte(string.byte(str, i, i))
635635+ end
636636+ writeByte(0)
637637+ end
638638+639639+ local function writeSetLengthString(str)
640640+ assert(type(str) == "string", "argument #1 to BitBuffer.writeSetLengthString should be a string")
641641+ -- This function writes strings as a pure string of bytes
642642+ -- It doesn't store any data about the length of the string,
643643+ -- so reading it requires knowledge of how many characters were stored
644644+645645+ for i = 1, #str do
646646+ writeByte(string.byte(str, i, i))
647647+ end
648648+ end
649649+650650+ local function writeField(...)
651651+ -- This is equivalent to having a writeBitfield function.
652652+ -- It combines all of the passed 'bits' into an unsigned number, then writes it.
653653+ local field = 0
654654+ local bools = table.pack(...)
655655+ for i = 1, bools.n do
656656+ field = field * 2 -- Shift `field`. Equivalent to field<<1. At the beginning of the loop to avoid an extra shift.
657657+658658+ local v = bools[i]
659659+ if v then
660660+ field = field + 1 -- If the bit is truthy, turn it on (it defaults to off so it's fine to not have a branch)
661661+ end
662662+ end
663663+664664+ writeUnsigned(bools.n, field)
665665+ end
666666+667667+ -- All write functions below here are shorthands. For the sake of performance, these functions are implemented manually.
668668+ -- As an example, while it would certainly be easier to make `writeInt16(n)` just call `writeUnsigned(16, n),
669669+ -- it's more performant to just manually call writeByte twice for it.
670670+671671+ local function writeUInt8(n)
672672+ assert(type(n) == "number", "argument #1 to BitBuffer.writeUInt8 should be a number")
673673+ assert(n >= 0 and n <= 255, "argument #1 to BitBuffer.writeUInt8 should be in the range [0, 255]")
674674+ assert(n % 1 == 0, "argument #1 to BitBuffer.writeUInt8 should be an integer")
675675+676676+ writeByte(n)
677677+ end
678678+679679+ local function writeUInt16(n)
680680+ assert(type(n) == "number", "argument #1 to BitBuffer.writeUInt16 should be a number")
681681+ assert(n >= 0 and n <= 65535, "argument #1 to BitBuffer.writeInt16 should be in the range [0, 65535]")
682682+ assert(n % 1 == 0, "argument #1 to BitBuffer.writeUInt16 should be an integer")
683683+684684+ writeByte(bit32.rshift(n, 8))
685685+ writeByte(bit32.band(n, 255))
686686+ end
687687+688688+ local function writeUInt32(n)
689689+ assert(type(n) == "number", "argument #1 to BitBuffer.writeUInt32 should be a number")
690690+ assert(
691691+ n >= 0 and n <= 4294967295,
692692+ "argument #1 to BitBuffer.writeUInt32 should be in the range [0, 4294967295]"
693693+ )
694694+ assert(n % 1 == 0, "argument #1 to BitBuffer.writeUInt32 should be an integer")
695695+696696+ writeByte(bit32.rshift(n, 24))
697697+ writeByte(bit32.band(bit32.rshift(n, 16), 255))
698698+ writeByte(bit32.band(bit32.rshift(n, 8), 255))
699699+ writeByte(bit32.band(n, 255))
700700+ end
701701+702702+ local function writeInt8(n)
703703+ assert(type(n) == "number", "argument #1 to BitBuffer.writeInt8 should be a number")
704704+ assert(n >= -128 and n <= 127, "argument #1 to BitBuffer.writeInt8 should be in the range [-128, 127]")
705705+ assert(n % 1 == 0, "argument #1 to BitBuffer.writeInt8 should be an integer")
706706+707707+ if n < 0 then
708708+ n = (128 + n) + 128
709709+ end
710710+711711+ writeByte(n)
712712+ end
713713+714714+ local function writeInt16(n)
715715+ assert(type(n) == "number", "argument #1 to BitBuffer.writeInt16 should be a number")
716716+ assert(n >= -32768 and n <= 32767, "argument #1 to BitBuffer.writeInt16 should be in the range [-32768, 32767]")
717717+ assert(n % 1 == 0, "argument #1 to BitBuffer.writeInt16 should be an integer")
718718+719719+ if n < 0 then
720720+ n = (32768 + n) + 32768
721721+ end
722722+723723+ writeByte(bit32.rshift(n, 8))
724724+ writeByte(bit32.band(n, 255))
725725+ end
726726+727727+ local function writeInt32(n)
728728+ assert(type(n) == "number", "argument #1 to BitBuffer.writeInt32 should be a number")
729729+ assert(
730730+ n >= -2147483648 and n <= 2147483647,
731731+ "argument #1 to BitBuffer.writeInt32 should be in the range [-2147483648, 2147483647]"
732732+ )
733733+ assert(n % 1 == 0, "argument #1 to BitBuffer.writeInt32 should be an integer")
734734+735735+ if n < 0 then
736736+ n = (2147483648 + n) + 2147483648
737737+ end
738738+739739+ writeByte(bit32.rshift(n, 24))
740740+ writeByte(bit32.band(bit32.rshift(n, 16), 255))
741741+ writeByte(bit32.band(bit32.rshift(n, 8), 255))
742742+ writeByte(bit32.band(n, 255))
743743+ end
744744+745745+ local function writeFloat16(n)
746746+ --assert(type(n) == "number", "argument #1 to BitBuffer.writeFloat16 should be a number")
747747+748748+ local sign = n < 0
749749+ n = math.abs(n)
750750+751751+ local mantissa, exponent = math.frexp(n)
752752+753753+ if n == math.huge then
754754+ if sign then
755755+ writeByte(252) -- 11111100
756756+ else
757757+ writeByte(124) -- 01111100
758758+ end
759759+ writeByte(0) -- 00000000
760760+ return
761761+ elseif n ~= n then
762762+ -- 01111111 11111111
763763+ writeByte(127)
764764+ writeByte(255)
765765+ return
766766+ elseif n == 0 then
767767+ writeByte(0)
768768+ writeByte(0)
769769+ return
770770+ elseif exponent + 15 <= 1 then -- Bias for halfs is 15
771771+ mantissa = math.floor(mantissa * 1024 + 0.5)
772772+ if sign then
773773+ writeByte(128 + bit32.rshift(mantissa, 8)) -- Sign bit, 5 empty bits, 2 from mantissa
774774+ else
775775+ writeByte(bit32.rshift(mantissa, 8))
776776+ end
777777+ writeByte(bit32.band(mantissa, 255)) -- Get last 8 bits from mantissa
778778+ return
779779+ end
780780+781781+ mantissa = math.floor((mantissa - 0.5) * 2048 + 0.5)
782782+783783+ -- The bias for halfs is 15, 15-1 is 14
784784+ if sign then
785785+ writeByte(128 + bit32.lshift(exponent + 14, 2) + bit32.rshift(mantissa, 8))
786786+ else
787787+ writeByte(bit32.lshift(exponent + 14, 2) + bit32.rshift(mantissa, 8))
788788+ end
789789+ writeByte(bit32.band(mantissa, 255))
790790+ end
791791+792792+ local function writeFloat32(n)
793793+ --assert(type(n) == "number", "argument #1 to BitBuffer.writeFloat32 should be a number")
794794+795795+ local sign = n < 0
796796+ n = math.abs(n)
797797+798798+ local mantissa, exponent = math.frexp(n)
799799+800800+ if n == math.huge then
801801+ if sign then
802802+ writeByte(255) -- 11111111
803803+ else
804804+ writeByte(127) -- 01111111
805805+ end
806806+ writeByte(128) -- 10000000
807807+ writeByte(0) -- 00000000
808808+ writeByte(0) -- 00000000
809809+ return
810810+ elseif n ~= n then
811811+ -- 01111111 11111111 11111111 11111111
812812+ writeByte(127)
813813+ writeByte(255)
814814+ writeByte(255)
815815+ writeByte(255)
816816+ return
817817+ elseif n == 0 then
818818+ writeByte(0)
819819+ writeByte(0)
820820+ writeByte(0)
821821+ writeByte(0)
822822+ return
823823+ elseif exponent + 127 <= 1 then -- bias for singles is 127
824824+ mantissa = math.floor(mantissa * 8388608 + 0.5)
825825+ if sign then
826826+ writeByte(128) -- Sign bit, 7 empty bits for exponent
827827+ else
828828+ writeByte(0)
829829+ end
830830+ writeByte(bit32.rshift(mantissa, 16))
831831+ writeByte(bit32.band(bit32.rshift(mantissa, 8), 255))
832832+ writeByte(bit32.band(mantissa, 255))
833833+ return
834834+ end
835835+836836+ mantissa = math.floor((mantissa - 0.5) * 16777216 + 0.5)
837837+838838+ -- 127-1 = 126
839839+ if sign then -- sign + 7 exponent
840840+ writeByte(128 + bit32.rshift(exponent + 126, 1))
841841+ else
842842+ writeByte(bit32.rshift(exponent + 126, 1))
843843+ end
844844+ writeByte(bit32.band(bit32.lshift(exponent + 126, 7), 255) + bit32.rshift(mantissa, 16)) -- 1 exponent + 7 mantissa
845845+ writeByte(bit32.band(bit32.rshift(mantissa, 8), 255)) -- 8 mantissa
846846+ writeByte(bit32.band(mantissa, 255)) -- 8 mantissa
847847+ end
848848+849849+ local function writeFloat64(n)
850850+ assert(type(n) == "number", "argument #1 to BitBuffer.writeFloat64 should be a number")
851851+852852+ local sign = n < 0
853853+ n = math.abs(n)
854854+855855+ local mantissa, exponent = math.frexp(n)
856856+857857+ if n == math.huge then
858858+ if sign then
859859+ writeByte(255) -- 11111111
860860+ else
861861+ writeByte(127) -- 01111111
862862+ end
863863+ writeByte(240) -- 11110000
864864+ writeByte(0) -- 00000000
865865+ writeByte(0) -- 00000000
866866+ writeByte(0) -- 00000000
867867+ writeByte(0) -- 00000000
868868+ writeByte(0) -- 00000000
869869+ writeByte(0) -- 00000000
870870+ return
871871+ elseif n ~= n then
872872+ -- 01111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
873873+ writeByte(127)
874874+ writeByte(255)
875875+ writeByte(255)
876876+ writeByte(255)
877877+ writeByte(255)
878878+ writeByte(255)
879879+ writeByte(255)
880880+ writeByte(255)
881881+ return
882882+ elseif n == 0 then
883883+ writeByte(0)
884884+ return
885885+ elseif exponent + 1023 <= 1 then -- bias for doubles is 1023
886886+ mantissa = math.floor(mantissa * 4503599627370496 + 0.5)
887887+ if sign then
888888+ writeByte(128) -- Sign bit, 7 empty bits for exponent
889889+ else
890890+ writeByte(0)
891891+ end
892892+893893+ -- This is labeled better below
894894+ local leastSignificantChunk = mantissa % 0x100000000 -- 32 bits
895895+ local mostSignificantChunk = math.floor(mantissa / 0x100000000) -- 20 bits
896896+897897+ writeByte(bit32.rshift(mostSignificantChunk, 16))
898898+ writeByte(bit32.band(bit32.rshift(mostSignificantChunk, 8), 255))
899899+ writeByte(bit32.band(mostSignificantChunk, 255))
900900+ writeByte(bit32.rshift(leastSignificantChunk, 24))
901901+ writeByte(bit32.band(bit32.rshift(leastSignificantChunk, 16), 255))
902902+ writeByte(bit32.band(bit32.rshift(leastSignificantChunk, 8), 255))
903903+ writeByte(bit32.band(leastSignificantChunk, 255))
904904+ return
905905+ end
906906+907907+ mantissa = math.floor((mantissa - 0.5) * 9007199254740992 + 0.5)
908908+909909+ --1023-1 = 1022
910910+ if sign then
911911+ writeByte(128 + bit32.rshift(exponent + 1022, 4)) -- shift out 4 of the bits in exponent
912912+ else
913913+ writeByte(bit32.rshift(exponent + 1022, 4)) -- 01000001 0110
914914+ end
915915+ -- Things start to get a bit wack here because the mantissa is 52 bits, so bit32 *can't* be used.
916916+ -- As the Offspring once said... You gotta keep 'em seperated.
917917+ local leastSignificantChunk = mantissa % 0x100000000 -- 32 bits
918918+ local mostSignificantChunk = math.floor(mantissa / 0x100000000) -- 20 bits
919919+920920+ -- First, the last 4 bits of the exponent and the first 4 bits of the mostSignificantChunk:
921921+ writeByte(bit32.band(bit32.lshift(exponent + 1022, 4), 255) + bit32.rshift(mostSignificantChunk, 16))
922922+ -- Then, the next 16 bits:
923923+ writeByte(bit32.band(bit32.rshift(mostSignificantChunk, 8), 255))
924924+ writeByte(bit32.band(mostSignificantChunk, 255))
925925+ -- Then... 4 bytes of the leastSignificantChunk
926926+ writeByte(bit32.rshift(leastSignificantChunk, 24))
927927+ writeByte(bit32.band(bit32.rshift(leastSignificantChunk, 16), 255))
928928+ writeByte(bit32.band(bit32.rshift(leastSignificantChunk, 8), 255))
929929+ writeByte(bit32.band(leastSignificantChunk, 255))
930930+ end
931931+932932+ -- All write functions below here are Roblox specific datatypes.
933933+934934+ local function writeBrickColor(n)
935935+ assert(typeof(n) == "BrickColor", "argument #1 to BitBuffer.writeBrickColor should be a BrickColor")
936936+937937+ writeUInt16(n.Number)
938938+ end
939939+940940+ local function writeColor3(c3)
941941+ assert(typeof(c3) == "Color3", "argument #1 to BitBuffer.writeColor3 should be a Color3")
942942+943943+ writeByte(math.floor(c3.R * 0xff + 0.5))
944944+ writeByte(math.floor(c3.G * 0xff + 0.5))
945945+ writeByte(math.floor(c3.B * 0xff + 0.5))
946946+ end
947947+948948+ local function writeCFrame(cf)
949949+ assert(typeof(cf) == "CFrame", "argument #1 to BitBuffer.writeCFrame should be a CFrame")
950950+ -- CFrames can be rather lengthy (if stored naively, they would each be 48 bytes long) so some optimization is done here.
951951+ -- Specifically, if a CFrame is axis-aligned (it's only rotated in 90 degree increments), the rotation matrix isn't stored.
952952+ -- Instead, an 'id' for its orientation is generated and that's stored instead of the rotation.
953953+ -- This means that for the most common rotations, only 13 bytes are used.
954954+ -- The downside is that non-axis-aligned CFrames use 49 bytes instead of 48, but that's a small price to pay.
955955+956956+ local upVector = cf.UpVector
957957+ local rightVector = cf.RightVector
958958+959959+ -- This is an easy trick to check if a CFrame is axis-aligned:
960960+ -- Essentially, in order for a vector to be axis-aligned, two of the components have to be 0
961961+ -- This means that the dot product between the vector and a vector of all 1s will be 1 (0*x = 0)
962962+ -- Since these are all unit vectors, there is no other combination that results in 1.
963963+ local rightAligned = math.abs(rightVector:Dot(ONES_VECTOR))
964964+ local upAligned = math.abs(upVector:Dot(ONES_VECTOR))
965965+ -- At least one of these two vectors is guaranteed to not result in 0.
966966+967967+ local axisAligned = (math.abs(1 - rightAligned) < 0.00001 or rightAligned == 0)
968968+ and (math.abs(1 - upAligned) < 0.00001 or upAligned == 0)
969969+ -- There are limitations to `math.abs(a-b) < epsilon` but they're not relevant:
970970+ -- The range of numbers is [0, 1] and this just needs to know if the number is approximately 1
971971+972972+ --todo special code for quaternions (0x01 in Roblox's format, would clash with 0x00 here)
973973+ if axisAligned then
974974+ local position = cf.Position
975975+ -- The ID of an orientation is generated through what can best be described as 'hand waving';
976976+ -- This is how Roblox does it and it works, so it was chosen to do it this way too.
977977+ local rightNormal, upNormal
978978+ for i = 0, 5 do
979979+ local v = NORMAL_ID_VECTORS[i]
980980+ if 1 - v:Dot(rightVector) < 0.00001 then
981981+ rightNormal = i
982982+ end
983983+ if 1 - v:Dot(upVector) < 0.00001 then
984984+ upNormal = i
985985+ end
986986+ end
987987+ -- The ID generated here is technically off by 1 from what Roblox would store, but that's not important
988988+ -- It just means that 0x02 is actually 0x01 for the purposes of this module's implementation.
989989+ writeByte(rightNormal * 6 + upNormal)
990990+ writeFloat32(position.X)
991991+ writeFloat32(position.Y)
992992+ writeFloat32(position.Z)
993993+ else
994994+ -- If the CFrame isn't axis-aligned, the entire rotation matrix has to be written...
995995+ writeByte(0) -- Along with a byte to indicate the matrix was written.
996996+ local x, y, z, r00, r01, r02, r10, r11, r12, r20, r21, r22 = cf:GetComponents()
997997+ writeFloat32(x)
998998+ writeFloat32(y)
999999+ writeFloat32(z)
10001000+ writeFloat32(r00)
10011001+ writeFloat32(r01)
10021002+ writeFloat32(r02)
10031003+ writeFloat32(r10)
10041004+ writeFloat32(r11)
10051005+ writeFloat32(r12)
10061006+ writeFloat32(r20)
10071007+ writeFloat32(r21)
10081008+ writeFloat32(r22)
10091009+ end
10101010+ end
10111011+10121012+ local function writeVector3(v3)
10131013+ --assert(typeof(v3) == "Vector3", "argument #1 to BitBuffer.writeVector3 should be a Vector3")
10141014+10151015+ writeFloat32(v3.X)
10161016+ writeFloat32(v3.Y)
10171017+ writeFloat32(v3.Z)
10181018+ end
10191019+10201020+ local function writeVector2(v2)
10211021+ assert(typeof(v2) == "Vector2", "argument #1 to BitBuffer.writeVector2 should be a Vector2")
10221022+10231023+ writeFloat32(v2.X)
10241024+ writeFloat32(v2.Y)
10251025+ end
10261026+10271027+ local function writeUDim2(u2)
10281028+ assert(typeof(u2) == "UDim2", "argument #1 to BitBuffer.writeUDim2 should be a UDim2")
10291029+10301030+ writeFloat32(u2.X.Scale)
10311031+ writeInt32(u2.X.Offset)
10321032+ writeFloat32(u2.Y.Scale)
10331033+ writeInt32(u2.Y.Offset)
10341034+ end
10351035+10361036+ local function writeUDim(u)
10371037+ assert(typeof(u) == "UDim", "argument #1 to BitBuffer.writeUDim should be a UDim")
10381038+10391039+ writeFloat32(u.Scale)
10401040+ writeInt32(u.Offset)
10411041+ end
10421042+10431043+ local function writeRay(ray)
10441044+ assert(typeof(ray) == "Ray", "argument #1 to BitBuffer.writeRay should be a Ray")
10451045+10461046+ writeFloat32(ray.Origin.X)
10471047+ writeFloat32(ray.Origin.Y)
10481048+ writeFloat32(ray.Origin.Z)
10491049+10501050+ writeFloat32(ray.Direction.X)
10511051+ writeFloat32(ray.Direction.Y)
10521052+ writeFloat32(ray.Direction.Z)
10531053+ end
10541054+10551055+ local function writeRect(rect)
10561056+ assert(typeof(rect) == "Rect", "argument #1 to BitBuffer.writeRect should be a Rect")
10571057+10581058+ writeFloat32(rect.Min.X)
10591059+ writeFloat32(rect.Min.Y)
10601060+10611061+ writeFloat32(rect.Max.X)
10621062+ writeFloat32(rect.Max.Y)
10631063+ end
10641064+10651065+ local function writeRegion3(region)
10661066+ assert(typeof(region) == "Region3", "argument #1 to BitBuffer.writeRegion3 should be a Region3")
10671067+10681068+ local min = region.CFrame.Position - (region.Size / 2)
10691069+ local max = region.CFrame.Position + (region.Size / 2)
10701070+10711071+ writeFloat32(min.X)
10721072+ writeFloat32(min.Y)
10731073+ writeFloat32(min.Z)
10741074+10751075+ writeFloat32(max.X)
10761076+ writeFloat32(max.Y)
10771077+ writeFloat32(max.Z)
10781078+ end
10791079+10801080+ local function writeEnum(enum)
10811081+ assert(typeof(enum) == "EnumItem", "argument #1 to BitBuffer.writeEnum should be an EnumItem")
10821082+10831083+ -- Relying upon tostring is generally not good, but there's not any other options for this.
10841084+ writeTerminatedString(tostring(enum.EnumType))
10851085+ writeUInt16(enum.Value) -- Optimistically assuming no Roblox Enum value will ever pass 65,535
10861086+ end
10871087+10881088+ local function writeNumberRange(range)
10891089+ assert(typeof(range) == "NumberRange", "argument #1 to BitBuffer.writeNumberRange should be a NumberRange")
10901090+10911091+ writeFloat32(range.Min)
10921092+ writeFloat32(range.Max)
10931093+ end
10941094+10951095+ local function writeNumberSequence(sequence)
10961096+ assert(
10971097+ typeof(sequence) == "NumberSequence",
10981098+ "argument #1 to BitBuffer.writeNumberSequence should be a NumberSequence"
10991099+ )
11001100+11011101+ writeUInt32(#sequence.Keypoints)
11021102+ for _, keypoint in ipairs(sequence.Keypoints) do
11031103+ writeFloat32(keypoint.Time)
11041104+ writeFloat32(keypoint.Value)
11051105+ writeFloat32(keypoint.Envelope)
11061106+ end
11071107+ end
11081108+11091109+ local function writeColorSequence(sequence)
11101110+ assert(
11111111+ typeof(sequence) == "ColorSequence",
11121112+ "argument #1 to BitBuffer.writeColorSequence should be a ColorSequence"
11131113+ )
11141114+11151115+ writeUInt32(#sequence.Keypoints)
11161116+ for _, keypoint in ipairs(sequence.Keypoints) do
11171117+ local c3 = keypoint.Value
11181118+ writeFloat32(keypoint.Time)
11191119+ writeByte(math.floor(c3.R * 0xff + 0.5))
11201120+ writeByte(math.floor(c3.G * 0xff + 0.5))
11211121+ writeByte(math.floor(c3.B * 0xff + 0.5))
11221122+ end
11231123+ end
11241124+11251125+ -- These are the read functions for the 'abstract' data types. At the bottom, there are shorthand read functions.
11261126+11271127+ local function readBits(n)
11281128+ assert(type(n) == "number", "argument #1 to BitBuffer.readBits should be a number")
11291129+ assert(n > 0, "argument #1 to BitBuffer.readBits should be greater than zero")
11301130+ assert(n % 1 == 0, "argument #1 to BitBuffer.readBits should be an integer")
11311131+11321132+ assert(pointer + n <= bitCount, "BitBuffer.readBits cannot read past the end of the stream")
11331133+11341134+ -- The first of two main functions for the actual 'reading' of the bitbuffer.
11351135+ -- Reads `n` bits and returns an array of their values.
11361136+ local output = table.create(n) --!
11371137+ local byte = bytes[pointerByte] -- For the sake of efficiency, the current byte that the bits are coming from is stored
11381138+ local c = pointer % 8 -- A counter is set with the current position of the pointer in the byte
11391139+ for i = 1, n do
11401140+ -- Then, it's as easy as moving through the bits of the byte
11411141+ -- And getting the individiual bit values
11421142+ local pow = powers_of_2[7 - c]
11431143+ output[i] = BOOL_TO_BIT[bit32.btest(byte, pow)] -- Test if a bit is on by &ing it by 2^[bit position]
11441144+ c = c + 1
11451145+ if c == 8 then -- If the byte boundary is reached, increment pointerByte and store the new byte in `byte`
11461146+ pointerByte = pointerByte + 1
11471147+ byte = bytes[pointerByte]
11481148+ c = 0
11491149+ end
11501150+ end
11511151+ pointer = pointer + n -- Move the pointer forward
11521152+ return output
11531153+ end
11541154+11551155+ --Skip to the end of the current byte
11561156+ local function skipStrayBits()
11571157+ local c = pointer % 8
11581158+ if (c > 0) then
11591159+ pointer += 8 - c
11601160+ pointerByte += 1
11611161+ end
11621162+ end
11631163+11641164+ local function readBytesFast()
11651165+ return bytes
11661166+ end
11671167+11681168+11691169+ local function readByte()
11701170+ assert(pointer + 8 <= bitCount, "BitBuffer.readByte cannot read past the end of the stream")
11711171+ -- The second of two main functions for the actual 'reading' of the bitbuffer.
11721172+ -- Reads a byte and returns it
11731173+ local c = pointer % 8 -- How far into the pointerByte the pointer is
11741174+ local byte1 = bytes[pointerByte] -- The pointerByte
11751175+ pointer = pointer + 8
11761176+ if c == 0 then -- Trivial if the pointer is at the beginning of the pointerByte
11771177+ pointerByte = pointerByte + 1
11781178+ return byte1
11791179+ else
11801180+ pointerByte = pointerByte + 1
11811181+ -- Get the remainder of the first pointerByte and add it to the part of the new pointerByte that's required
11821182+ -- Both these methods are explained in writeByte
11831183+ return bit32.band(bit32.lshift(byte1, c), 255) + bit32.rshift(bytes[pointerByte], 8 - c)
11841184+ end
11851185+ end
11861186+11871187+ local function readUnsigned(width)
11881188+ assert(type(width) == "number", "argument #1 to BitBuffer.readUnsigned should be a number")
11891189+ assert(width >= 1 and width <= 64, "argument #1 to BitBuffer.readUnsigned should be in the range [1, 64]")
11901190+ assert(width % 1 == 0, "argument #1 to BitBuffer.readUnsigned should be an integer")
11911191+11921192+ assert(pointer + width <= bitCount, "BitBuffer.readUnsigned cannot read past the end of the stream")
11931193+ -- Implementing this on its own was considered because of a worry that it would be inefficient to call
11941194+ -- readByte and readBit several times, but it was decided the simplicity is worth a minor performance hit.
11951195+ local bytesInN, bitsInN = math.floor(width / 8), width % 8
11961196+11971197+ -- No check is required for if the width is greater than 32 because bit32 isn't used.
11981198+ local n = 0
11991199+ -- Shift and add a read byte however many times is necessary
12001200+ -- Adding after shifting is importnat - it prevents there from being 8 empty bits of space
12011201+ for _ = 1, bytesInN do
12021202+ n = n * 0x100 -- 2^8; equivalent to n << 8
12031203+ n = n + readByte()
12041204+ end
12051205+ -- The bits are then read and added to the number
12061206+ if bitsInN ~= 0 then
12071207+ for _, v in ipairs(readBits(width % 8)) do --todo benchmark against concat+tonumber; might be worth the code smell
12081208+ n = n * 2
12091209+ n = n + v
12101210+ end
12111211+ end
12121212+ return n
12131213+ end
12141214+12151215+ local function readSigned(width)
12161216+ assert(type(width) == "number", "argument #1 to BitBuffer.readSigned should be a number")
12171217+ assert(width >= 2 and width <= 64, "argument #1 to BitBuffer.readSigned should be in the range [2, 64]")
12181218+ assert(width % 1 == 0, "argument #1 to BitBuffer.readSigned should be an integer")
12191219+12201220+ assert(pointer + 8 <= bitCount, "BitBuffer.readSigned cannot read past the end of the stream")
12211221+ local sign = readBits(1)[1]
12221222+ local n = readUnsigned(width - 1) -- Again, width-1 is because one bit is used for the sign
12231223+12241224+ -- As said in writeSigned, the written number is unmodified if the number is positive (the sign bit is 0)
12251225+ if sign == 0 then
12261226+ return n
12271227+ else
12281228+ -- And the number is equal to max value of the width + the number if the number is negative (the sign bit is 1)
12291229+ -- To reverse that, the max value is subtracted from the stored number.
12301230+ return n - powers_of_2[width - 1]
12311231+ end
12321232+ end
12331233+12341234+ local function readFloat(exponentWidth, mantissaWidth)
12351235+ assert(type(exponentWidth) == "number", "argument #1 to BitBuffer.readFloat should be a number")
12361236+ assert(
12371237+ exponentWidth >= 1 and exponentWidth <= 64,
12381238+ "argument #1 to BitBuffer.readFloat should be in the range [1, 64]"
12391239+ )
12401240+ assert(exponentWidth % 1 == 0, "argument #1 to BitBuffer.readFloat should be an integer")
12411241+12421242+ assert(type(mantissaWidth) == "number", "argument #2 to BitBuffer.readFloat should be a number")
12431243+ assert(
12441244+ mantissaWidth >= 1 and mantissaWidth <= 64,
12451245+ "argument #2 to BitBuffer.readFloat should be in the range [1, 64]"
12461246+ )
12471247+ assert(mantissaWidth % 1 == 0, "argument #2 to BitBuffer.readFloat should be an integer")
12481248+12491249+ assert(
12501250+ pointer + exponentWidth + mantissaWidth + 1 <= bitCount,
12511251+ "BitBuffer.readFloat cannot read past the end of the stream"
12521252+ )
12531253+ -- Recomposing floats is rather straightfoward.
12541254+ -- The bias is subtracted from the exponent, the mantissa is shifted back by mantissaWidth, one is added to the mantissa
12551255+ -- and the whole thing is recomposed with math.ldexp (this is identical to mantissa*(2^exponent)).
12561256+12571257+ local bias = powers_of_2[exponentWidth - 1] - 1
12581258+12591259+ local sign = readBits(1)[1]
12601260+ local exponent = readUnsigned(exponentWidth)
12611261+ local mantissa = readUnsigned(mantissaWidth)
12621262+12631263+ -- Before normal numbers are handled though, special cases and subnormal numbers are once again handled seperately
12641264+ if exponent == powers_of_2[exponentWidth] - 1 then
12651265+ if mantissa ~= 0 then -- If the exponent is all 1s and the mantissa isn't zero, the number is NaN
12661266+ return 0 / 0
12671267+ else -- Otherwise, it's positive or negative infinity
12681268+ return sign == 0 and math.huge or -math.huge
12691269+ end
12701270+ elseif exponent == 0 then
12711271+ if mantissa == 0 then -- If the exponent and mantissa are both zero, the number is zero.
12721272+ return 0
12731273+ else -- If the exponent is zero and the mantissa is not zero, the number is subnormal
12741274+ -- Subnormal numbers are straightforward: shifting the mantissa so that it's a fraction is all that's required
12751275+ mantissa = mantissa / powers_of_2[mantissaWidth]
12761276+12771277+ -- Since the exponent is 0, it's actual value is just -bias (it would be exponent-bias)
12781278+ -- As previously touched on in writeFloat, the exponent value is off by 1 in Lua though.
12791279+ return sign == 1 and -math.ldexp(mantissa, -bias + 1) or math.ldexp(mantissa, -bias + 1)
12801280+ end
12811281+ end
12821282+12831283+ -- First, the mantissa is shifted back by the mantissaWidth
12841284+ -- Then, 1 is added to it to 'normalize' it.
12851285+ mantissa = (mantissa / powers_of_2[mantissaWidth]) + 1
12861286+12871287+ -- Because the mantissa is normalized above (the leading 1 is in the ones place), it's accurate to say exponent-bias
12881288+ return sign == 1 and -math.ldexp(mantissa, exponent - bias) or math.ldexp(mantissa, exponent - bias)
12891289+ end
12901290+12911291+ local function readString()
12921292+ assert(pointer + 24 <= bitCount, "BitBuffer.readString cannot read past the end of the stream")
12931293+ -- Reading a length-prefixed string is rather straight forward.
12941294+ -- The length is read, then that many bytes are read and put in a string.
12951295+12961296+ local stringLength = readUnsigned(24)
12971297+ assert(pointer + (stringLength * 8) <= bitCount, "BitBuffer.readString cannot read past the end of the stream")
12981298+12991299+ local outputCharacters = table.create(stringLength) --!
13001300+13011301+ for i = 1, stringLength do
13021302+ outputCharacters[i] = readByte()
13031303+ end
13041304+13051305+ local output = table.create(math.ceil(stringLength / 4096))
13061306+ local k = 1
13071307+ for i = 1, stringLength, 4096 do
13081308+ output[k] = string.char(table.unpack(outputCharacters, i, math.min(stringLength, i + 4095)))
13091309+ k = k + 1
13101310+ end
13111311+13121312+ return table.concat(output)
13131313+ end
13141314+13151315+ local function readTerminatedString()
13161316+ local outputCharacters = {}
13171317+13181318+ -- Bytes are read continuously until either a nul-character is reached or until the stream runs out.
13191319+ local length = 0
13201320+ while true do
13211321+ local byte = readByte()
13221322+ if not byte then -- Stream has ended
13231323+ error("BitBuffer.readTerminatedString cannot read past the end of the stream", 2)
13241324+ elseif byte == 0 then -- String has ended
13251325+ break
13261326+ else -- Add byte to string
13271327+ length = length + 1
13281328+ outputCharacters[length] = byte
13291329+ end
13301330+ end
13311331+13321332+ local output = table.create(math.ceil(length / 4096))
13331333+ local k = 1
13341334+ for l = 1, length, 4096 do
13351335+ output[k] = string.char(table.unpack(outputCharacters, l, math.min(length, l + 4095)))
13361336+ k = k + 1
13371337+ end
13381338+13391339+ return table.concat(output)
13401340+ end
13411341+13421342+ local function readSetLengthString(length)
13431343+ assert(type(length) == "number", "argument #1 to BitBuffer.readSetLengthString should be a number")
13441344+ assert(length >= 0, "argument #1 to BitBuffer.readSetLengthString should be zero or higher.")
13451345+ assert(length % 1 == 0, "argument #1 to BitBuffer.readSetLengthString should be an integer")
13461346+13471347+ assert(
13481348+ pointer + (length * 8) <= bitCount,
13491349+ "BitBuffer.readSetLengthString cannot read past the end of the stream"
13501350+ )
13511351+ -- `length` number of bytes are read and put into a string
13521352+13531353+ local outputCharacters = table.create(length) --!
13541354+13551355+ for i = 1, length do
13561356+ outputCharacters[i] = readByte()
13571357+ end
13581358+13591359+ local output = table.create(math.ceil(length / 4096))
13601360+ local k = 1
13611361+ for i = 1, length, 4096 do
13621362+ output[k] = string.char(table.unpack(outputCharacters, i, math.min(length, i + 4095)))
13631363+ k = k + 1
13641364+ end
13651365+13661366+ return table.concat(output)
13671367+ end
13681368+13691369+ local function readField(n)
13701370+ assert(type(n) == "number", "argument #1 to BitBuffer.readField should be a number")
13711371+ assert(n > 0, "argument #1 to BitBuffer.readField should be above 0")
13721372+ assert(n % 1 == 0, "argument #1 to BitBuffer.readField should be an integer")
13731373+13741374+ assert(pointer + n <= bitCount, "BitBuffer.readField cannot read past the end of the stream")
13751375+ -- Reading a bit field is again rather simple. You read the actual field, then take the bits out.
13761376+ local readInt = readUnsigned(n)
13771377+ local output = table.create(n) --!
13781378+13791379+ for i = n, 1, -1 do -- In reverse order since we're pulling bits out from lsb to msb
13801380+ output[i] = readInt % 2 == 1 -- Equivalent to an extraction of the lsb
13811381+ readInt = math.floor(readInt / 2) -- Equivalent to readInt>>1
13821382+ end
13831383+13841384+ return output
13851385+ end
13861386+13871387+ -- All read functions below here are shorthands.
13881388+ -- As with their write variants, these functions are implemented manually using readByte for performance reasons.
13891389+13901390+ local function readUInt8()
13911391+ assert(pointer + 8 <= bitCount, "BitBuffer.readUInt8 cannot read past the end of the stream")
13921392+13931393+ return readByte()
13941394+ end
13951395+13961396+ local function readUInt16()
13971397+ assert(pointer + 16 <= bitCount, "BitBuffer.readUInt16 cannot read past the end of the stream")
13981398+13991399+ return bit32.lshift(readByte(), 8) + readByte()
14001400+ end
14011401+14021402+ local function readUInt32()
14031403+ assert(pointer + 32 <= bitCount, "BitBuffer.readUInt32 cannot read past the end of the stream")
14041404+14051405+ return bit32.lshift(readByte(), 24) + bit32.lshift(readByte(), 16) + bit32.lshift(readByte(), 8) + readByte()
14061406+ end
14071407+14081408+ local function readInt8()
14091409+ assert(pointer + 8 <= bitCount, "BitBuffer.readInt8 cannot read past the end of the stream")
14101410+14111411+ local n = readByte()
14121412+ local sign = bit32.btest(n, 128)
14131413+ n = bit32.band(n, 127)
14141414+14151415+ if sign then
14161416+ return n - 128
14171417+ else
14181418+ return n
14191419+ end
14201420+ end
14211421+14221422+ local function readInt16()
14231423+ assert(pointer + 16 <= bitCount, "BitBuffer.readInt16 cannot read past the end of the stream")
14241424+14251425+ local n = bit32.lshift(readByte(), 8) + readByte()
14261426+ local sign = bit32.btest(n, 32768)
14271427+ n = bit32.band(n, 32767)
14281428+14291429+ if sign then
14301430+ return n - 32768
14311431+ else
14321432+ return n
14331433+ end
14341434+ end
14351435+14361436+ local function readInt32()
14371437+ assert(pointer + 32 <= bitCount, "BitBuffer.readInt32 cannot read past the end of the stream")
14381438+14391439+ local n = bit32.lshift(readByte(), 24) + bit32.lshift(readByte(), 16) + bit32.lshift(readByte(), 8) + readByte()
14401440+ local sign = bit32.btest(n, 2147483648)
14411441+ n = bit32.band(n, 2147483647)
14421442+14431443+ if sign then
14441444+ return n - 2147483648
14451445+ else
14461446+ return n
14471447+ end
14481448+ end
14491449+14501450+ local function readFloat16()
14511451+ assert(pointer + 16 <= bitCount, "BitBuffer.readFloat16 cannot read past the end of the stream")
14521452+14531453+ local b0 = readByte()
14541454+ local sign = bit32.btest(b0, 128)
14551455+ local exponent = bit32.rshift(bit32.band(b0, 127), 2)
14561456+ local mantissa = bit32.lshift(bit32.band(b0, 3), 8) + readByte()
14571457+14581458+ if exponent == 31 then --2^5-1
14591459+ if mantissa ~= 0 then
14601460+ return 0 / 0
14611461+ else
14621462+ return sign and -math.huge or math.huge
14631463+ end
14641464+ elseif exponent == 0 then
14651465+ if mantissa == 0 then
14661466+ return 0
14671467+ else
14681468+ return sign and -math.ldexp(mantissa / 1024, -14) or math.ldexp(mantissa / 1024, -14)
14691469+ end
14701470+ end
14711471+14721472+ mantissa = (mantissa / 1024) + 1
14731473+14741474+ return sign and -math.ldexp(mantissa, exponent - 15) or math.ldexp(mantissa, exponent - 15)
14751475+ end
14761476+14771477+ local function readFloat32()
14781478+ assert(pointer + 32 <= bitCount, "BitBuffer.readFloat32 cannot read past the end of the stream")
14791479+14801480+ local b0 = readByte()
14811481+ local b1 = readByte()
14821482+ local sign = bit32.btest(b0, 128)
14831483+ local exponent = bit32.band(bit32.lshift(b0, 1), 255) + bit32.rshift(b1, 7)
14841484+ local mantissa = bit32.lshift(bit32.band(b1, 127), 23 - 7)
14851485+ + bit32.lshift(readByte(), 23 - 7 - 8)
14861486+ + bit32.lshift(readByte(), 23 - 7 - 8 - 8)
14871487+14881488+ if exponent == 255 then -- 2^8-1
14891489+ if mantissa ~= 0 then
14901490+ return 0 / 0
14911491+ else
14921492+ return sign and -math.huge or math.huge
14931493+ end
14941494+ elseif exponent == 0 then
14951495+ if mantissa == 0 then
14961496+ return 0
14971497+ else
14981498+ -- -126 is the 0-bias+1
14991499+ return sign and -math.ldexp(mantissa / 8388608, -126) or math.ldexp(mantissa / 8388608, -126)
15001500+ end
15011501+ end
15021502+15031503+ mantissa = (mantissa / 8388608) + 1
15041504+15051505+ return sign and -math.ldexp(mantissa, exponent - 127) or math.ldexp(mantissa, exponent - 127)
15061506+ end
15071507+15081508+ local function readFloat64()
15091509+ assert(pointer + 64 <= bitCount, "BitBuffer.readFloat64 cannot read past the end of the stream")
15101510+15111511+ local b0 = readByte()
15121512+ local b1 = readByte()
15131513+15141514+ local sign = bit32.btest(b0, 128)
15151515+ local exponent = bit32.lshift(bit32.band(b0, 127), 4) + bit32.rshift(b1, 4)
15161516+ local mostSignificantChunk = bit32.lshift(bit32.band(b1, 15), 16) + bit32.lshift(readByte(), 8) + readByte()
15171517+ local leastSignificantChunk = bit32.lshift(readByte(), 24)
15181518+ + bit32.lshift(readByte(), 16)
15191519+ + bit32.lshift(readByte(), 8)
15201520+ + readByte()
15211521+15221522+ -- local mantissa = (bit32.lshift(bit32.band(b1, 15), 16)+bit32.lshift(readByte(), 8)+readByte())*0x100000000+
15231523+ -- bit32.lshift(readByte(), 24)+bit32.lshift(readByte(), 16)+bit32.lshift(readByte(), 8)+readByte()
15241524+15251525+ local mantissa = mostSignificantChunk * 0x100000000 + leastSignificantChunk
15261526+15271527+ if exponent == 2047 then -- 2^11-1
15281528+ if mantissa ~= 0 then
15291529+ return 0 / 0
15301530+ else
15311531+ return sign and -math.huge or math.huge
15321532+ end
15331533+ elseif exponent == 0 then
15341534+ if mantissa == 0 then
15351535+ return 0
15361536+ else
15371537+ return sign and -math.ldexp(mantissa / 4503599627370496, -1022)
15381538+ or math.ldexp(mantissa / 4503599627370496, -1022)
15391539+ end
15401540+ end
15411541+15421542+ mantissa = (mantissa / 4503599627370496) + 1
15431543+15441544+ return sign and -math.ldexp(mantissa, exponent - 1023) or math.ldexp(mantissa, exponent - 1023)
15451545+ end
15461546+15471547+ -- All read functions below here are Roblox specific datatypes.
15481548+15491549+ local function readBrickColor()
15501550+ assert(pointer + 16 <= bitCount, "BitBuffer.readBrickColor cannot read past the end of the stream")
15511551+15521552+ return BrickColor.new(readUInt16())
15531553+ end
15541554+15551555+ local function readColor3()
15561556+ assert(pointer + 24 <= bitCount, "BitBuffer.readColor3 cannot read past the end of the stream")
15571557+15581558+ return Color3.fromRGB(readByte(), readByte(), readByte())
15591559+ end
15601560+15611561+ local function readCFrame()
15621562+ assert(pointer + 8 <= bitCount, "BitBuffer.readCFrame cannot read past the end of the stream")
15631563+15641564+ local id = readByte()
15651565+15661566+ if id == 0 then
15671567+ assert(pointer + 384 <= bitCount, "BitBuffer.readCFrame cannot read past the end of the stream") -- 4*12 bytes = 383 bits
15681568+15691569+ -- stylua: ignore
15701570+ return CFrame.new(
15711571+ readFloat32(), readFloat32(), readFloat32(),
15721572+ readFloat32(), readFloat32(), readFloat32(),
15731573+ readFloat32(), readFloat32(), readFloat32(),
15741574+ readFloat32(), readFloat32(), readFloat32()
15751575+ )
15761576+ else
15771577+ assert(pointer + 96 <= bitCount, "BitBuffer.readCFrame cannot read past the end of the stream") -- 4*3 bytes = 96 bits
15781578+15791579+ local rightVector = NORMAL_ID_VECTORS[math.floor(id / 6)]
15801580+ local upVector = NORMAL_ID_VECTORS[id % 6]
15811581+ local lookVector = rightVector:Cross(upVector)
15821582+15831583+ -- CFrame's full-matrix constructor takes right/up/look vectors as columns...
15841584+ -- stylua: ignore
15851585+ return CFrame.new(
15861586+ readFloat32(), readFloat32(), readFloat32(),
15871587+ rightVector.X, upVector.X, lookVector.X,
15881588+ rightVector.Y, upVector.Y, lookVector.Y,
15891589+ rightVector.Z, upVector.Z, lookVector.Z
15901590+ )
15911591+ end
15921592+ end
15931593+15941594+ local function readVector3()
15951595+ assert(pointer + 96 <= bitCount, "BitBuffer.readVector3 cannot read past the end of the stream")
15961596+15971597+ return Vector3.new(readFloat32(), readFloat32(), readFloat32())
15981598+ end
15991599+16001600+ local function readVector2()
16011601+ assert(pointer + 64 <= bitCount, "BitBuffer.readVector2 cannot read past the end of the stream")
16021602+16031603+ return Vector2.new(readFloat32(), readFloat32())
16041604+ end
16051605+16061606+ local function readUDim2()
16071607+ assert(pointer + 128 <= bitCount, "BitBuffer.readUDim2 cannot read past the end of the stream")
16081608+16091609+ return UDim2.new(readFloat32(), readInt32(), readFloat32(), readInt32())
16101610+ end
16111611+16121612+ local function readUDim()
16131613+ assert(pointer + 64 <= bitCount, "BitBuffer.readUDim cannot read past the end of the stream")
16141614+16151615+ return UDim.new(readFloat32(), readInt32())
16161616+ end
16171617+16181618+ local function readRay()
16191619+ assert(pointer + 192 <= bitCount, "BitBuffer.readRay cannot read past the end of the stream")
16201620+16211621+ return Ray.new(
16221622+ Vector3.new(readFloat32(), readFloat32(), readFloat32()),
16231623+ Vector3.new(readFloat32(), readFloat32(), readFloat32())
16241624+ )
16251625+ end
16261626+16271627+ local function readRect()
16281628+ assert(pointer + 128 <= bitCount, "BitBuffer.readRect cannot read past the end of the stream")
16291629+16301630+ return Rect.new(readFloat32(), readFloat32(), readFloat32(), readFloat32())
16311631+ end
16321632+16331633+ local function readRegion3()
16341634+ assert(pointer + 192 <= bitCount, "BitBuffer.readRegion3 cannot read past the end of the stream")
16351635+16361636+ return Region3.new(
16371637+ Vector3.new(readFloat32(), readFloat32(), readFloat32()),
16381638+ Vector3.new(readFloat32(), readFloat32(), readFloat32())
16391639+ )
16401640+ end
16411641+16421642+ local function readEnum()
16431643+ assert(pointer + 8 <= bitCount, "BitBuffer.readEnum cannot read past the end of the stream")
16441644+16451645+ local name = readTerminatedString() -- This might expose an error from readString to the end-user but it's not worth the hassle to fix.
16461646+16471647+ assert(pointer + 16 <= bitCount, "BitBuffer.readEnum cannot read past the end of the stream")
16481648+16491649+ local value = readUInt16() -- Again, optimistically assuming no Roblox Enum value will ever pass 65,535
16501650+16511651+ -- Catching a potential error only to throw it with different formatting seems... Superfluous.
16521652+ -- Open an issue on github if you feel otherwise.
16531653+ for _, v in ipairs(Enum[name]:GetEnumItems()) do
16541654+ if v.Value == value then
16551655+ return v
16561656+ end
16571657+ end
16581658+16591659+ error(
16601660+ "BitBuffer.readEnum could not get value: `"
16611661+ .. tostring(value)
16621662+ .. "` is not a valid member of `"
16631663+ .. name
16641664+ .. "`",
16651665+ 2
16661666+ )
16671667+ end
16681668+16691669+ local function readNumberRange()
16701670+ assert(pointer + 64 <= bitCount, "BitBuffer.readNumberRange cannot read past the end of the stream")
16711671+16721672+ return NumberRange.new(readFloat32(), readFloat32())
16731673+ end
16741674+16751675+ local function readNumberSequence()
16761676+ assert(pointer + 32 <= bitCount, "BitBuffer.readNumberSequence cannot read past the end of the stream")
16771677+16781678+ local keypointCount = readUInt32()
16791679+16801680+ assert(pointer + keypointCount * 96, "BitBuffer.readColorSequence cannot read past the end of the stream")
16811681+16821682+ local keypoints = table.create(keypointCount)
16831683+16841684+ -- As it turns out, creating a NumberSequence with a negative value as its first argument (in the first and second constructor)
16851685+ -- creates NumberSequenceKeypoints with negative envelopes. The envelope is read and saved properly, as you would expect,
16861686+ -- but you can't create a NumberSequence with a negative envelope if you're using a table of keypoints (which is happening here).
16871687+ -- If you're confused, run this snippet: NumberSequence.new(NumberSequence.new(-1).Keypoints)
16881688+ -- As a result, there has to be some branching logic in this function.
16891689+ -- ColorSequences don't have envelopes so it's not necessary for them.
16901690+16911691+ for i = 1, keypointCount do
16921692+ local time, value, envelope = readFloat32(), readFloat32(), readFloat32()
16931693+ if value < 0 then
16941694+ envelope = nil
16951695+ end
16961696+ keypoints[i] = NumberSequenceKeypoint.new(time, value, envelope)
16971697+ end
16981698+16991699+ return NumberSequence.new(keypoints)
17001700+ end
17011701+17021702+ local function readColorSequence()
17031703+ assert(pointer + 32 <= bitCount, "BitBuffer.readColorSequence cannot read past the end of the stream")
17041704+17051705+ local keypointCount = readUInt32()
17061706+17071707+ assert(pointer + keypointCount * 56, "BitBuffer.readColorSequence cannot read past the end of the stream")
17081708+17091709+ local keypoints = table.create(keypointCount)
17101710+17111711+ for i = 1, keypointCount do
17121712+ keypoints[i] = ColorSequenceKeypoint.new(readFloat32(), Color3.fromRGB(readByte(), readByte(), readByte()))
17131713+ end
17141714+17151715+ return ColorSequence.new(keypoints)
17161716+ end
17171717+17181718+ return {
17191719+ dumpBinary = dumpBinary,
17201720+ dumpString = dumpString,
17211721+ dumpHex = dumpHex,
17221722+ dumpBase64 = dumpBase64,
17231723+ exportChunk = exportChunk,
17241724+ exportBase64Chunk = exportBase64Chunk,
17251725+ exportHexChunk = exportHexChunk,
17261726+17271727+ crc32 = crc32,
17281728+ getLength = getLength,
17291729+ getByteLength = getByteLength,
17301730+ getPointer = getPointer,
17311731+ setPointer = setPointer,
17321732+ setPointerFromEnd = setPointerFromEnd,
17331733+ getPointerByte = getPointerByte,
17341734+ setPointerByte = setPointerByte,
17351735+ setPointerByteFromEnd = setPointerByteFromEnd,
17361736+ isFinished = isFinished,
17371737+17381738+ writeBits = writeBits,
17391739+ writeByte = writeByte,
17401740+ writeBytesFast = writeBytesFast,
17411741+ writeUnsigned = writeUnsigned,
17421742+ writeSigned = writeSigned,
17431743+ writeFloat = writeFloat,
17441744+ writeBase64 = writeBase64,
17451745+ writeString = writeString,
17461746+ writeTerminatedString = writeTerminatedString,
17471747+ writeSetLengthString = writeSetLengthString,
17481748+ writeField = writeField,
17491749+17501750+ writeUInt8 = writeUInt8,
17511751+ writeUInt16 = writeUInt16,
17521752+ writeUInt32 = writeUInt32,
17531753+ writeInt8 = writeInt8,
17541754+ writeInt16 = writeInt16,
17551755+ writeInt32 = writeInt32,
17561756+17571757+ writeFloat16 = writeFloat16,
17581758+ writeFloat32 = writeFloat32,
17591759+ writeFloat64 = writeFloat64,
17601760+17611761+ writeBrickColor = writeBrickColor,
17621762+ writeColor3 = writeColor3,
17631763+ writeCFrame = writeCFrame,
17641764+ writeVector3 = writeVector3,
17651765+ writeVector2 = writeVector2,
17661766+ writeUDim2 = writeUDim2,
17671767+ writeUDim = writeUDim,
17681768+ writeRay = writeRay,
17691769+ writeRect = writeRect,
17701770+ writeRegion3 = writeRegion3,
17711771+ writeEnum = writeEnum,
17721772+ writeNumberRange = writeNumberRange,
17731773+ writeNumberSequence = writeNumberSequence,
17741774+ writeColorSequence = writeColorSequence,
17751775+17761776+ readBits = readBits,
17771777+ readByte = readByte,
17781778+ readUnsigned = readUnsigned,
17791779+ readSigned = readSigned,
17801780+ readFloat = readFloat,
17811781+ readString = readString,
17821782+ readTerminatedString = readTerminatedString,
17831783+ readSetLengthString = readSetLengthString,
17841784+ readField = readField,
17851785+ readBytesFast = readBytesFast,
17861786+ skipStrayBits = skipStrayBits,
17871787+17881788+ readUInt8 = readUInt8,
17891789+ readUInt16 = readUInt16,
17901790+ readUInt32 = readUInt32,
17911791+ readInt8 = readInt8,
17921792+ readInt16 = readInt16,
17931793+ readInt32 = readInt32,
17941794+17951795+ readFloat16 = readFloat16,
17961796+ readFloat32 = readFloat32,
17971797+ readFloat64 = readFloat64,
17981798+17991799+ readBrickColor = readBrickColor,
18001800+ readColor3 = readColor3,
18011801+ readCFrame = readCFrame,
18021802+ readVector3 = readVector3,
18031803+ readVector2 = readVector2,
18041804+ readUDim2 = readUDim2,
18051805+ readUDim = readUDim,
18061806+ readRay = readRay,
18071807+ readRect = readRect,
18081808+ readRegion3 = readRegion3,
18091809+ readEnum = readEnum,
18101810+ readNumberRange = readNumberRange,
18111811+ readNumberSequence = readNumberSequence,
18121812+ readColorSequence = readColorSequence,
18131813+ }
18141814+end
18151815+18161816+return bitBuffer
···11+local Node = {}
22+Node.__index = Node
33+44+function Node.new(data)
55+ local node = setmetatable({}, Node)
66+ node.data = data
77+ node.left = nil
88+ node.right = nil
99+ return node
1010+end
1111+1212+-- return an iterator that traverses the tree in order
1313+function Node:inorder()
1414+ local stack = {}
1515+ local current = {self, ""}
1616+ table.insert(stack, current)
1717+1818+ return function()
1919+ while current[1].left do
2020+ local parent = current
2121+ current = {parent[1].left, parent[2] .. "0"}
2222+ table.insert(stack, current)
2323+ end
2424+2525+ if #stack > 0 then
2626+ local node = table.remove(stack)
2727+2828+ if node[1].right then
2929+ local parent = node
3030+ current = {parent[1].right, parent[2] .. "1"}
3131+ table.insert(stack, current)
3232+ end
3333+ return node[1], node[2]
3434+ end
3535+ end
3636+end
3737+3838+return Node
···11+--[[
22+33+ PriorityQueue - v1.0.1 - public domain Lua priority queue
44+ implemented with indirect binary heap
55+ no warranty implied; use at your own risk
66+77+ based on binaryheap library (github.com/iskolbin/binaryheap)
88+99+ author: Ilya Kolbin (iskolbin@gmail.com)
1010+ url: github.com/iskolbin/priorityqueue
1111+1212+ See documentation in README file.
1313+1414+ COMPATIBILITY
1515+1616+ Lua 5.1, 5.2, 5.3, LuaJIT 1, 2
1717+1818+ LICENSE
1919+2020+ This software is dual-licensed to the public domain and under the following
2121+ license: you are granted a perpetual, irrevocable license to copy, modify,
2222+ publish, and distribute this file as you see fit.
2323+2424+--]]
2525+2626+local floor, setmetatable = math.floor, setmetatable
2727+2828+local function siftup( self, from )
2929+ local items, priorities, indices, higherpriority = self, self._priorities, self._indices, self._higherpriority
3030+ local index = from
3131+ local parent = floor( index / 2 )
3232+ while index > 1 and higherpriority( priorities[index], priorities[parent] ) do
3333+ priorities[index], priorities[parent] = priorities[parent], priorities[index]
3434+ items[index], items[parent] = items[parent], items[index]
3535+ indices[items[index]], indices[items[parent]] = index, parent
3636+ index = parent
3737+ parent = floor( index / 2 )
3838+ end
3939+ return index
4040+end
4141+4242+local function siftdown( self, limit )
4343+ local items, priorities, indices, higherpriority, size = self, self._priorities, self._indices, self._higherpriority, self._size
4444+ for index = limit, 1, -1 do
4545+ local left = index + index
4646+ local right = left + 1
4747+ while left <= size do
4848+ local smaller = left
4949+ if right <= size and higherpriority( priorities[right], priorities[left] ) then
5050+ smaller = right
5151+ end
5252+ if higherpriority( priorities[smaller], priorities[index] ) then
5353+ items[index], items[smaller] = items[smaller], items[index]
5454+ priorities[index], priorities[smaller] = priorities[smaller], priorities[index]
5555+ indices[items[index]], indices[items[smaller]] = index, smaller
5656+ else
5757+ break
5858+ end
5959+ index = smaller
6060+ left = index + index
6161+ right = left + 1
6262+ end
6363+ end
6464+end
6565+6666+local PriorityQueueMt
6767+6868+local PriorityQueue = {}
6969+7070+local function minishigher( a, b )
7171+ return a < b
7272+end
7373+7474+local function maxishigher( a, b )
7575+ return a > b
7676+end
7777+7878+function PriorityQueue.new( priority_or_array )
7979+ local t = type( priority_or_array )
8080+ local higherpriority = minishigher
8181+8282+ if t == 'table' then
8383+ higherpriority = priority_or_array.higherpriority or higherpriority
8484+ elseif t == 'function' or t == 'string' then
8585+ higherpriority = priority_or_array
8686+ elseif t ~= 'nil' then
8787+ local msg = 'Wrong argument type to PriorityQueue.new, it must be table or function or string, has: %q'
8888+ error( msg:format( t ))
8989+ end
9090+9191+ if type( higherpriority ) == 'string' then
9292+ if higherpriority == 'min' then
9393+ higherpriority = minishigher
9494+ elseif higherpriority == 'max' then
9595+ higherpriority = maxishigher
9696+ else
9797+ local msg = 'Wrong string argument to PriorityQueue.new, it must be "min" or "max", has: %q'
9898+ error( msg:format( tostring( higherpriority )))
9999+ end
100100+ end
101101+102102+ local self = setmetatable( {
103103+ _priorities = {},
104104+ _indices = {},
105105+ _size = 0,
106106+ _higherpriority = higherpriority or minishigher
107107+ }, PriorityQueueMt )
108108+109109+ if t == 'table' then
110110+ self:batchenq( priority_or_array )
111111+ end
112112+113113+ return self
114114+end
115115+116116+function PriorityQueue:enqueue( item, priority )
117117+ local items, priorities, indices = self, self._priorities, self._indices
118118+ if indices[item] ~= nil then
119119+ error( 'Item ' .. tostring(indices[item]) .. ' is already in the heap' )
120120+ end
121121+ local size = self._size + 1
122122+ self._size = size
123123+ items[size], priorities[size], indices[item] = item, priority, size
124124+ siftup( self, size )
125125+ return self
126126+end
127127+128128+function PriorityQueue:remove( item )
129129+ local index = self._indices[item]
130130+ if index ~= nil then
131131+ local size = self._size
132132+ local items, priorities, indices = self, self._priorities, self._indices
133133+ indices[item] = nil
134134+ if size == index then
135135+ items[size], priorities[size] = nil, nil
136136+ self._size = size - 1
137137+ else
138138+ local lastitem = items[size]
139139+ items[index], priorities[index] = items[size], priorities[size]
140140+ items[size], priorities[size] = nil, nil
141141+ indices[lastitem] = index
142142+ size = size - 1
143143+ self._size = size
144144+ if size > 1 then
145145+ siftdown( self, siftup( self, index ))
146146+ end
147147+ end
148148+ return true
149149+ else
150150+ return false
151151+ end
152152+end
153153+154154+function PriorityQueue:contains( item )
155155+ return self._indices[item] ~= nil
156156+end
157157+158158+function PriorityQueue:update( item, priority )
159159+ local ok = self:remove( item )
160160+ if ok then
161161+ self:enqueue( item, priority )
162162+ return true
163163+ else
164164+ return false
165165+ end
166166+end
167167+168168+function PriorityQueue:dequeue()
169169+ local size = self._size
170170+171171+ assert( size > 0, 'Heap is empty' )
172172+173173+ local items, priorities, indices = self, self._priorities, self._indices
174174+ local item, priority = items[1], priorities[1]
175175+ indices[item] = nil
176176+177177+ if size > 1 then
178178+ local newitem = items[size]
179179+ items[1], priorities[1] = newitem, priorities[size]
180180+ items[size], priorities[size] = nil, nil
181181+ indices[newitem] = 1
182182+ size = size - 1
183183+ self._size = size
184184+ siftdown( self, 1 )
185185+ else
186186+ items[1], priorities[1] = nil, nil
187187+ self._size = 0
188188+ end
189189+190190+ return item, priority
191191+end
192192+193193+function PriorityQueue:peek()
194194+ return self[1], self._priorities[1]
195195+end
196196+197197+function PriorityQueue:len()
198198+ return self._size
199199+end
200200+201201+function PriorityQueue:empty()
202202+ return self._size <= 0
203203+end
204204+205205+function PriorityQueue:batchenq( iparray )
206206+ local items, priorities, indices = self, self._priorities, self._indices
207207+ local size = self._size
208208+ for i = 1, #iparray, 2 do
209209+ local item, priority = iparray[i], iparray[i+1]
210210+ if indices[item] ~= nil then
211211+ error( 'Item ' .. tostring(indices[item]) .. ' is already in the heap' )
212212+ end
213213+ size = size + 1
214214+ items[size], priorities[size] = item, priority
215215+ indices[item] = size
216216+ end
217217+ self._size = size
218218+ if size > 1 then
219219+ siftdown( self, floor( size / 2 ))
220220+ end
221221+end
222222+223223+PriorityQueueMt = {
224224+ __index = PriorityQueue,
225225+ __len = PriorityQueue.len,
226226+}
227227+228228+return setmetatable( PriorityQueue, {
229229+ __call = function( _, ... )
230230+ return PriorityQueue.new( ... )
231231+ end
232232+} )
···11+--Huffman.lua
22+--iiau, Sat May 18 2024
33+--Implementation of huffman coding algorithm for use in Roblox
44+55+local Huffman = {}
66+local PriorityQueue = require(script.PriorityQueue)
77+local Node = require(script.Node)
88+local BitBuffer = require(script.BitBuffer)
99+1010+local CHUNK_SIZE = 256
1111+1212+--thanks to https://stackoverflow.com/a/32220398 for helping me with this
1313+local function to_bin(n)
1414+ local t = {}
1515+ for _ = 1, 32 do
1616+ n = bit32.rrotate(n, -1)
1717+ table.insert(t, bit32.band(n, 1))
1818+ end
1919+ return table.concat(t)
2020+end
2121+2222+-- Encode a string to huffman coded string. Limitation is that the data should not be more than 16777215 bytes.
2323+-- @param data The string to encode
2424+-- @return The encoded string
2525+Huffman.encode = function(data: string) : string
2626+ assert(#data > 0, "Data must not be empty")
2727+ local buffer = BitBuffer()
2828+2929+ -- get the frequency of each character in the string
3030+ local freq, dict, size = {}, {}, 0
3131+ for c in data:gmatch(".") do
3232+ freq[c] = (freq[c] or 0) + 1
3333+ end
3434+ for _ in pairs(freq) do
3535+ size += 1
3636+ end
3737+3838+ local q = PriorityQueue.new 'min'
3939+ for k: string, v: number in pairs(freq) do
4040+ local leaf = Node.new(string.byte(k))
4141+ q:enqueue(leaf, v)
4242+ end
4343+4444+ while q:len() > 1 do
4545+ local left, freq_l = q:dequeue()
4646+ local right, freq_r = q:dequeue()
4747+ local parent = Node.new()
4848+ parent.left = left
4949+ parent.right = right
5050+5151+ q:enqueue(parent, freq_l + freq_r)
5252+ end
5353+ local tree = q:dequeue()
5454+ buffer.writeUInt8(size-1)
5555+ buffer.writeUnsigned(24, #data)
5656+ for node, bits: string in tree:inorder() do
5757+ if not node.data then
5858+ continue
5959+ end
6060+ local number = tonumber(bits, 2)
6161+ local bit_array = string.split(bits, "")
6262+ for i = 1, #bit_array do
6363+ bit_array[i] = tonumber(bit_array[i])
6464+ end
6565+6666+ dict[string.char(node.data)] = bit_array
6767+ buffer.writeUInt8(node.data) -- char
6868+ buffer.writeUnsigned(5, #bits) -- number of bits
6969+ buffer.writeUnsigned(#bits, number) -- bits
7070+ end
7171+ for c in data:gmatch(".") do
7272+ buffer.writeBits(table.unpack(dict[c]))
7373+ end
7474+7575+ -- to avoid the dreaded too many results to unpack error
7676+ local chunks = {}
7777+ for _, chunk in buffer.exportChunk(CHUNK_SIZE) do
7878+ table.insert(chunks, chunk)
7979+ end
8080+ return table.concat(chunks)
8181+end
8282+8383+-- Decode a string from huffman coded string
8484+-- @param data The string to decode
8585+-- @return The decoded string
8686+Huffman.decode = function(data: string) : string
8787+ assert(#data > 0, "Data must not be empty")
8888+ local buffer = BitBuffer(data)
8989+9090+ local dict_size = buffer.readUInt8()+1
9191+ local len_data = buffer.readUnsigned(24)
9292+ local dict, read = {}, 0
9393+9494+ for i = 1, dict_size do
9595+ local char = buffer.readUInt8()
9696+ local digits = buffer.readUnsigned(5)
9797+ local bits = buffer.readUnsigned(digits)
9898+ dict[to_bin(bits):sub(-digits)] = char
9999+ end
100100+ local decoded = {}
101101+ local bits = ""
102102+ while read < len_data do
103103+ bits ..= buffer.readBits(1)[1]
104104+ local char = dict[bits]
105105+ if char then
106106+ table.insert(decoded, string.char(char))
107107+ bits = ""
108108+ read += 1
109109+ end
110110+ end
111111+ return table.concat(decoded)
112112+end
113113+114114+return Huffman
···11+-- Module by 1waffle1 and boatbomber, optimized and fixed by iiau
22+-- https://devforum.roblox.com/t/text-compression/163637/37
33+44+local dictionary = {} -- key to len
55+66+do -- populate dictionary
77+ local length = 0
88+ for i = 32, 127 do
99+ if i ~= 34 and i ~= 92 then
1010+ local c = string.char(i)
1111+ dictionary[c] = length
1212+ dictionary[length] = c
1313+ length = length + 1
1414+ end
1515+ end
1616+end
1717+1818+local escapemap_126, escapemap_127 = {}, {}
1919+local unescapemap_126, unescapemap_127 = {}, {}
2020+2121+local blacklisted_126 = { 34, 92 }
2222+for i = 126, 180 do
2323+ table.insert(blacklisted_126, i)
2424+end
2525+2626+do -- Populate escape map
2727+ -- represents the numbers 0-31, 34, 92, 126, 127 (36 characters)
2828+ -- and 128-180 (52 characters)
2929+ -- https://devforum.roblox.com/t/text-compression/163637/5
3030+ for i = 0, 31 + #blacklisted_126 do
3131+ local b = blacklisted_126[i - 31]
3232+ local s = i + 32
3333+3434+ -- Note: 126 and 127 are magic numbers
3535+ local c = string.char(b or i)
3636+ local e = string.char(s + (s >= 34 and 1 or 0) + (s >= 91 and 1 or 0))
3737+3838+ escapemap_126[c] = e
3939+ unescapemap_126[e] = c
4040+ end
4141+4242+ for i = 1, 255 - 180 do
4343+ local c = string.char(i + 180)
4444+ local s = i + 34
4545+ local e = string.char(s + (s >= 92 and 1 or 0))
4646+4747+ escapemap_127[c] = e
4848+ unescapemap_127[e] = c
4949+ end
5050+end
5151+5252+local function escape(s)
5353+ -- escape the control characters 0-31, double quote 34, backslash 92, Tilde 126, and DEL 127 (36 chars)
5454+ -- escape characters 128-180 (53 chars)
5555+ return string.gsub(string.gsub(s, '[%c"\\\126-\180]', function(c)
5656+ return "\126" .. escapemap_126[c]
5757+ end), '[\181-\255]', function(c)
5858+ return "\127" .. escapemap_127[c]
5959+ end)
6060+end
6161+local function unescape(s)
6262+ return string.gsub(string.gsub(s, "\127(.)", function(e)
6363+ return unescapemap_127[e]
6464+ end), "\126(.)", function(e)
6565+ return unescapemap_126[e]
6666+ end)
6767+end
6868+6969+local b93Cache = {}
7070+local function tobase93(n)
7171+ local value = b93Cache[n]
7272+ if value then
7373+ return value
7474+ end
7575+7676+ local c = n
7777+ value = ""
7878+ repeat
7979+ local remainder = n % 93
8080+ value = dictionary[remainder] .. value
8181+ n = (n - remainder) / 93
8282+ until n == 0
8383+8484+ b93Cache[c] = value
8585+ return value
8686+end
8787+8888+local b10Cache = {}
8989+local function tobase10(value)
9090+ local n = b10Cache[value]
9191+ if n then
9292+ return n
9393+ end
9494+9595+ n = 0
9696+ for i = 1, #value do
9797+ n = n + math.pow(93, i - 1) * dictionary[string.sub(value, -i, -i)]
9898+ end
9999+100100+ b10Cache[value] = n
101101+ return n
102102+end
103103+104104+local function compress(text)
105105+ assert(type(text) == "string", "bad argument #1 to 'compress' (string expected, got " .. typeof(text) .. ")")
106106+ local dictionaryCopy = table.clone(dictionary)
107107+ local key, sequence, size = "", {}, #dictionary
108108+109109+ local width, spans, span = 1, {}, 0
110110+ local function listkey(k)
111111+ local value = tobase93(dictionaryCopy[k])
112112+ local valueLength = #value
113113+ if valueLength > width then
114114+ width, span, spans[width] = valueLength, 0, span
115115+ end
116116+ table.insert(sequence, string.rep(" ", width - valueLength) .. value)
117117+ span += 1
118118+ end
119119+ text = escape(text)
120120+ for i = 1, #text do
121121+ local c = string.sub(text, i, i)
122122+ local new = key .. c
123123+ if dictionaryCopy[new] then
124124+ key = new
125125+ else
126126+ listkey(key)
127127+ key = c
128128+ size += 1
129129+ dictionaryCopy[new] = size
130130+ dictionaryCopy[size] = new
131131+ end
132132+ end
133133+ listkey(key)
134134+ spans[width] = span
135135+ return table.concat(spans, ",") .. "|" .. table.concat(sequence)
136136+end
137137+138138+local function decompress(text)
139139+ assert(type(text) == "string", "bad argument #1 to 'decompress' (string expected, got " .. typeof(text) .. ")")
140140+ local dictionaryCopy = table.clone(dictionary)
141141+ local sequence, spans, content = {}, string.match(text, "(.-)|(.*)")
142142+ local groups, start = {}, 1
143143+ for span in string.gmatch(spans, "%d+") do
144144+ local width = #groups + 1
145145+ groups[width] = string.sub(content, start, start + span * width - 1)
146146+ start = start + span * width
147147+ end
148148+ local previous
149149+150150+ for width, group in ipairs(groups) do
151151+ for value in string.gmatch(group, string.rep(".", width)) do
152152+ local entry = dictionaryCopy[tobase10(value)]
153153+ if previous then
154154+ if entry then
155155+ table.insert(dictionaryCopy, previous .. string.sub(entry, 1, 1))
156156+ else
157157+ entry = previous .. string.sub(previous, 1, 1)
158158+ table.insert(dictionaryCopy, entry)
159159+ end
160160+ table.insert(sequence, entry)
161161+ else
162162+ sequence[1] = entry
163163+ end
164164+ previous = entry
165165+ end
166166+ end
167167+ return unescape(table.concat(sequence))
168168+end
169169+170170+return { compress = compress, decompress = decompress }
···11+local TerrainGen = {}
22+33+local deflate = require("./TerrainGen/Deflate")
44+55+local DSS = game:GetService("DataStoreService")
66+local WORLDNAME = "DEFAULT"
77+local WORLDID = "b73bb5a6-297d-4352-b637-daec7e8c8f3e"
88+local Store = DSS:GetDataStore("BlockscraftWorldV1", WORLDID)
99+1010+local ChunkManager = require(game:GetService("ReplicatedStorage"):WaitForChild("Shared").ChunkManager)
1111+local Chunk = require(game:GetService("ReplicatedStorage"):WaitForChild("Shared").ChunkManager.Chunk)
1212+1313+TerrainGen.ServerChunkCache = {} :: {[typeof("")]: typeof(Chunk.new(0,0,0))}
1414+1515+-- Load a chunk from the DataStore or generate it if not found
1616+function TerrainGen:GetChunk(x, y, z)
1717+ local key = `{x},{y},{z}`
1818+ if TerrainGen.ServerChunkCache[key] then
1919+ return TerrainGen.ServerChunkCache[key]
2020+ end
2121+2222+ -- Generate a new chunk if it doesn't exist
2323+ local chunk = Chunk.new(x, y, z)
2424+ if y == 1 then
2525+ for cx = 1, 8 do
2626+ for cz = 1, 8 do
2727+ --local perlin = math.noise(((x*8)+cx)/100,((z*8)+cz)/100)
2828+ chunk:CreateBlock(cx, 1, cz, { id = 1, state = {} })
2929+ --chunk:CreateBlock(x, 2, z, { id = 1, state = {} })
3030+ end
3131+ end
3232+ end
3333+ if y == 0 then
3434+ for cx = 1, 8 do
3535+ for cy = 1, 8 do
3636+ for cz = 1, 8 do
3737+ --local perlin = math.noise(((x*8)+cx)/100,((z*8)+cz)/100)
3838+ chunk:CreateBlock(cx, cy, cz, { id = 2, state = {} })
3939+ --chunk:CreateBlock(x, 2, z, { id = 1, state = {} })
4040+ end
4141+ end
4242+ end
4343+ end
4444+4545+ TerrainGen.ServerChunkCache[key] = chunk
4646+ return chunk
4747+end
4848+4949+-- Fake Chunk
5050+function TerrainGen:GetFakeChunk(x, y, z)
5151+5252+ -- Generate a new chunk if it doesn't exist
5353+ local chunk = Chunk.new(x, y, z)
5454+ for cy = 1,8 do
5555+ for cx = 1, 8 do
5656+ for cz = 1, 8 do
5757+ --local perlin = math.noise(((x*8)+cx)/100,((z*8)+cz)/100)
5858+ chunk:CreateBlock(cx, cy, cz, { id = -2, state = {} })
5959+ --chunk:CreateBlock(x, 2, z, { id = 1, state = {} })
6060+ end
6161+ end
6262+ end
6363+6464+ return chunk
6565+end
6666+6767+6868+TerrainGen.CM = ChunkManager
6969+7070+return TerrainGen
···11+print("Hello world!")
22+33+task.synchronize()
44+55+local ReplicatedStorage = game:GetService("ReplicatedStorage")
66+77+88+local Shared = ReplicatedStorage:WaitForChild("Shared")
99+local ModsFolder = ReplicatedStorage:WaitForChild("Mods")
1010+1111+local Util = require(Shared.Util)
1212+local TG = require("./ServerChunkManager/TerrainGen")
1313+1414+do
1515+ local workspaceModFolder = game:GetService("Workspace"):WaitForChild("mods")
1616+1717+ for _,v in pairs(workspaceModFolder:GetChildren()) do
1818+ v.Parent = ModsFolder
1919+ end
2020+ workspaceModFolder:Destroy()
2121+end
2222+2323+local ML = require(Shared.ModLoader)
2424+ML.loadModsS()
2525+2626+do
2727+ local bv = Instance.new("BoolValue")
2828+ bv.Name = "MLLoaded"
2929+ bv.Value = true
3030+ bv.Parent = ReplicatedStorage:WaitForChild("Objects")
3131+end
3232+3333+local MAX_CHUNK_DIST = 200
3434+3535+local FakeChunk = TG:GetFakeChunk(-5,-5,-5)
3636+3737+task.synchronize()
3838+3939+ReplicatedStorage.Tick.OnServerEvent:Connect(function(player: Player, v: string)
4040+ if TG.ServerChunkCache[v] then
4141+ pcall(function()
4242+ TG.ServerChunkCache[v].inhabitedTime = tick()
4343+ end)
4444+ end
4545+end)
4646+4747+ReplicatedStorage.RecieveChunkPacket.OnServerInvoke = function(plr: Player, x: number, y: number, z: number)
4848+ -- validate xyz type and limit
4949+ if typeof(x) ~= "number" or typeof(y) ~= "number" or typeof(z) ~= "number" then
5050+ return {}
5151+ end
5252+5353+ if math.abs(x) > MAX_CHUNK_DIST or math.abs(y) > MAX_CHUNK_DIST or math.abs(z) > MAX_CHUNK_DIST then
5454+ return FakeChunk.data
5555+ end
5656+5757+ task.desynchronize()
5858+ local chunk = TG:GetChunk(x, y, z)
5959+ local chunkdata = chunk.data
6060+ task.synchronize()
6161+6262+ return chunkdata
6363+6464+end
6565+6666+local tickRemote = ReplicatedStorage.Tick
6767+local remotes = ReplicatedStorage:WaitForChild("Remotes")
6868+local placeRemote = remotes:WaitForChild("PlaceBlock")
6969+local breakRemote = remotes:WaitForChild("BreakBlock")
7070+local blocksFolder = ReplicatedStorage:WaitForChild("Blocks")
7171+local function propogate(a, cx, cy, cz, x, y, z, bd)
7272+ task.synchronize()
7373+ tickRemote:FireAllClients(a, cx, cy, cz, x, y, z, bd)
7474+ task.desynchronize()
7575+end
7676+7777+local MAX_REACH = 24
7878+local blockIdMap = {}
7979+8080+local function rebuildBlockIdMap()
8181+ table.clear(blockIdMap)
8282+ for _, block in ipairs(blocksFolder:GetChildren()) do
8383+ local id = block:GetAttribute("n")
8484+ if id ~= nil then
8585+ blockIdMap[id] = id
8686+ blockIdMap[tostring(id)] = id
8787+ end
8888+ end
8989+end
9090+9191+rebuildBlockIdMap()
9292+blocksFolder.ChildAdded:Connect(rebuildBlockIdMap)
9393+blocksFolder.ChildRemoved:Connect(rebuildBlockIdMap)
9494+9595+local function getPlayerPosition(player: Player): Vector3?
9696+ local character = player.Character
9797+ if not character then
9898+ return nil
9999+ end
100100+ local root = character:FindFirstChild("HumanoidRootPart")
101101+ if not root then
102102+ return nil
103103+ end
104104+ return root.Position
105105+end
106106+107107+local function isWithinReach(player: Player, cx: number, cy: number, cz: number, x: number, y: number, z: number): boolean
108108+ local playerPos = getPlayerPosition(player)
109109+ if not playerPos then
110110+ return false
111111+ end
112112+ local blockPos = Util.ChunkPosToCFrame(Vector3.new(cx, cy, cz), Vector3.new(x, y, z)).Position
113113+ return (blockPos - playerPos).Magnitude <= MAX_REACH
114114+end
115115+116116+local function resolveBlockId(blockId: any): string | number | nil
117117+ return blockIdMap[blockId]
118118+end
119119+120120+local function getServerChunk(cx: number, cy: number, cz: number)
121121+ task.desynchronize()
122122+ local chunk = TG:GetChunk(cx, cy, cz)
123123+ task.synchronize()
124124+ return chunk
125125+end
126126+127127+placeRemote.OnServerEvent:Connect(function(player, cx, cy, cz, x, y, z, blockId)
128128+ --print("place",player, cx, cy, cz, x, y, z, blockData)
129129+130130+ if typeof(cx) ~= "number" or typeof(cy) ~= "number" or typeof(cz) ~= "number" then
131131+ return
132132+ end
133133+ if typeof(x) ~= "number" or typeof(y) ~= "number" or typeof(z) ~= "number" then
134134+ return
135135+ end
136136+ if x < 1 or x > 8 or y < 1 or y > 8 or z < 1 or z > 8 then
137137+ return
138138+ end
139139+ if math.abs(cx) > MAX_CHUNK_DIST or math.abs(cy) > MAX_CHUNK_DIST or math.abs(cz) > MAX_CHUNK_DIST then
140140+ --return
141141+ end
142142+ if not isWithinReach(player, cx, cy, cz, x, y, z) then
143143+ return
144144+ end
145145+ local resolvedId = resolveBlockId(blockId)
146146+ if not resolvedId then
147147+ return
148148+ end
149149+150150+ local chunk = getServerChunk(cx, cy, cz)
151151+ if chunk:GetBlockAt(x, y, z) then
152152+ return
153153+ end
154154+ local data = {
155155+ id = resolvedId,
156156+ state = {}
157157+ }
158158+ chunk:CreateBlock(x, y, z, data)
159159+ propogate("B_C", cx, cy, cz, x, y, z, data)
160160+end)
161161+162162+breakRemote.OnServerEvent:Connect(function(player, cx, cy, cz, x, y, z)
163163+ --print("del",player, cx, cy, cz, x, y, z)
164164+165165+ if typeof(cx) ~= "number" or typeof(cy) ~= "number" or typeof(cz) ~= "number" then
166166+ return
167167+ end
168168+ if typeof(x) ~= "number" or typeof(y) ~= "number" or typeof(z) ~= "number" then
169169+ return
170170+ end
171171+ if x < 1 or x > 8 or y < 1 or y > 8 or z < 1 or z > 8 then
172172+ return
173173+ end
174174+ if math.abs(cx) > MAX_CHUNK_DIST or math.abs(cy) > MAX_CHUNK_DIST or math.abs(cz) > MAX_CHUNK_DIST then
175175+ return
176176+ end
177177+ if not isWithinReach(player, cx, cy, cz, x, y, z) then
178178+ return
179179+ end
180180+181181+ local chunk = getServerChunk(cx, cy, cz)
182182+ if not chunk:GetBlockAt(x, y, z) then
183183+ return
184184+ end
185185+ chunk:RemoveBlock(x, y, z)
186186+ propogate("B_D", cx, cy, cz, x, y, z, 0)
187187+end)
188188+189189+task.desynchronize()
···11+-- force first person mode on the person's camera
22+33+if not game:IsLoaded() then
44+ game.Loaded:Wait()
55+end
66+77+local ReplicatedStorage = game:GetService("ReplicatedStorage")
88+local UIS = game:GetService("UserInputService")
99+1010+ReplicatedStorage:WaitForChild("Objects"):WaitForChild("MLLoaded")
1111+1212+game:GetService("Players").LocalPlayer.CameraMode = Enum.CameraMode.LockFirstPerson
1313+UIS.MouseIconEnabled = false
1414+1515+UIS.InputEnded:Connect(function(k)
1616+ if k.KeyCode == Enum.KeyCode.M then
1717+ local v = not script.Parent.DummyButton.Modal
1818+ UIS.MouseIconEnabled = v
1919+ script.Parent.CrosshairLabel.Visible = not v
2020+ script.Parent.DummyButton.Modal = v
2121+ end
2222+end)
···11+if not game:IsLoaded() then
22+ game.Loaded:Wait()
33+end
44+55+local ReplicatedStorage = game:GetService("ReplicatedStorage")
66+local UIS = game:GetService("UserInputService")
77+88+ReplicatedStorage:WaitForChild("Objects"):WaitForChild("MLLoaded")
99+1010+local blocksFolder = ReplicatedStorage:WaitForChild("Blocks")
1111+local PM = require(ReplicatedStorage.Shared.PlacementManager)
1212+1313+local HOTBAR_SIZE = 9
1414+local hotbar = table.create(HOTBAR_SIZE)
1515+local selectedSlot = 1
1616+1717+local keyToSlot = {
1818+ [Enum.KeyCode.One] = 1,
1919+ [Enum.KeyCode.Two] = 2,
2020+ [Enum.KeyCode.Three] = 3,
2121+ [Enum.KeyCode.Four] = 4,
2222+ [Enum.KeyCode.Five] = 5,
2323+ [Enum.KeyCode.Six] = 6,
2424+ [Enum.KeyCode.Seven] = 7,
2525+ [Enum.KeyCode.Eight] = 8,
2626+ [Enum.KeyCode.Nine] = 9,
2727+}
2828+2929+local function rebuildHotbar()
3030+ local ids = {}
3131+ for _, block in ipairs(blocksFolder:GetChildren()) do
3232+ local id = block:GetAttribute("n")
3333+ if id ~= nil then
3434+ table.insert(ids, tostring(id))
3535+ end
3636+ end
3737+3838+ table.sort(ids)
3939+ for i = 1, HOTBAR_SIZE do
4040+ hotbar[i] = ids[i] or ""
4141+ end
4242+ selectedSlot = math.clamp(selectedSlot, 1, HOTBAR_SIZE)
4343+end
4444+4545+local function getSelectedBlockId(): string?
4646+ local id = hotbar[selectedSlot]
4747+ if id == "" then
4848+ return nil
4949+ end
5050+ return id
5151+end
5252+5353+local function setSelectedSlot(slot: number)
5454+ if slot < 1 or slot > HOTBAR_SIZE then
5555+ return
5656+ end
5757+ selectedSlot = slot
5858+end
5959+6060+rebuildHotbar()
6161+blocksFolder.ChildAdded:Connect(rebuildHotbar)
6262+blocksFolder.ChildRemoved:Connect(rebuildHotbar)
6363+6464+UIS.InputBegan:Connect(function(input: InputObject, gameProcessedEvent: boolean)
6565+ if gameProcessedEvent then
6666+ return
6767+ end
6868+6969+ local slot = keyToSlot[input.KeyCode]
7070+ if slot then
7171+ setSelectedSlot(slot)
7272+ return
7373+ end
7474+7575+ if input.UserInputType == Enum.UserInputType.MouseButton1 then
7676+ local mouseBlock = PM:GetBlockAtMouse()
7777+ if not mouseBlock then
7878+ return
7979+ end
8080+ PM:BreakBlock(mouseBlock.chunk.X, mouseBlock.chunk.Y, mouseBlock.chunk.Z, mouseBlock.block.X, mouseBlock.block.Y, mouseBlock.block.Z)
8181+ elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
8282+ local mouseBlock = PM:GetPlacementAtMouse()
8383+ if not mouseBlock then
8484+ return
8585+ end
8686+ local blockId = getSelectedBlockId()
8787+ if not blockId then
8888+ return
8989+ end
9090+ PM:PlaceBlock(mouseBlock.chunk.X, mouseBlock.chunk.Y, mouseBlock.chunk.Z, mouseBlock.block.X, mouseBlock.block.Y, mouseBlock.block.Z, blockId)
9191+ end
9292+end)