codex: stuff
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1 +1,3 @@
|
||||
place.rbxl.lock
|
||||
place.rbxl.lock
|
||||
Packages/
|
||||
ServerPackages/
|
||||
|
||||
@@ -5,11 +5,19 @@
|
||||
"ReplicatedStorage": {
|
||||
"$className": "ReplicatedStorage",
|
||||
"$ignoreUnknownInstances": true,
|
||||
"Packages": {
|
||||
"$className": "Folder",
|
||||
"$path": "Packages"
|
||||
},
|
||||
"$path": "src/ReplicatedStorage"
|
||||
},
|
||||
"ServerScriptService": {
|
||||
"$className": "ServerScriptService",
|
||||
"$ignoreUnknownInstances": true,
|
||||
"ServerPackages": {
|
||||
"$className": "Folder",
|
||||
"$path": "ServerPackages"
|
||||
},
|
||||
"$path": "src/ServerScriptService"
|
||||
},
|
||||
"StarterGui": {
|
||||
@@ -32,4 +40,4 @@
|
||||
"$path": "src/Workspace"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
local ChunkManager = {}
|
||||
|
||||
local RunService = game:GetService("RunService")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local Chunk = require("./ChunkManager/Chunk")
|
||||
local BlockManager = require("./ChunkManager/BlockManager")
|
||||
@@ -15,10 +16,10 @@ ChunkFolder.Name = "$blockscraft_client"
|
||||
ChunkManager.ChunkFolder = ChunkFolder
|
||||
|
||||
local CHUNK_RADIUS = 5
|
||||
local INITIAL_SYNC_RADIUS = 2
|
||||
local LOAD_BATCH = 8
|
||||
local FORCELOAD_CHUNKS = {
|
||||
{0, 1, 0}
|
||||
}
|
||||
local CHUNK_WORLD_SIZE = 32 -- 8 blocks * 4 studs
|
||||
local FORCELOAD_CHUNKS = {}
|
||||
|
||||
local unloadingChunks = {}
|
||||
local pendingChunkRequests = {}
|
||||
@@ -37,6 +38,15 @@ do
|
||||
end)
|
||||
end
|
||||
|
||||
local function worldToChunkCoords(pos: Vector3): { x: number, y: number, z: number }
|
||||
-- Align chunk boundaries so chunk 0 spans roughly [-16,16] with block centers every 4 studs.
|
||||
return {
|
||||
x = math.floor((pos.X + (CHUNK_WORLD_SIZE / 2)) / CHUNK_WORLD_SIZE),
|
||||
y = math.floor((pos.Y + (CHUNK_WORLD_SIZE / 2)) / CHUNK_WORLD_SIZE),
|
||||
z = math.floor((pos.Z + (CHUNK_WORLD_SIZE / 2)) / CHUNK_WORLD_SIZE),
|
||||
}
|
||||
end
|
||||
|
||||
local function Swait(l)
|
||||
task.synchronize()
|
||||
for _ = 1, l do
|
||||
@@ -139,11 +149,7 @@ function ChunkManager:Tick()
|
||||
end
|
||||
|
||||
local pos = player.Character:GetPivot().Position
|
||||
local chunkPos = {
|
||||
x = math.round(pos.X / 32),
|
||||
y = math.round(pos.Y / 32),
|
||||
z = math.round(pos.Z / 32)
|
||||
}
|
||||
local chunkPos = worldToChunkCoords(pos)
|
||||
|
||||
task.defer(function()
|
||||
local processed = 0
|
||||
@@ -207,6 +213,32 @@ function ChunkManager:Init()
|
||||
ChunkFolder.Parent = game:GetService("Workspace")
|
||||
ChunkManager:ForceTick()
|
||||
|
||||
-- Synchronously warm a small area around the spawn chunk to ensure visible terrain on join.
|
||||
local player = Players.LocalPlayer
|
||||
local function warmInitial(character)
|
||||
if not character then
|
||||
return
|
||||
end
|
||||
local chunkPos = worldToChunkCoords(character:GetPivot().Position)
|
||||
for _, offset in ipairs(CHUNK_OFFSETS) do
|
||||
if offset[4] <= (INITIAL_SYNC_RADIUS * INITIAL_SYNC_RADIUS) then
|
||||
local cx, cy, cz = chunkPos.x + offset[1], chunkPos.y + offset[2], chunkPos.z + offset[3]
|
||||
local chunk = ChunkManager:GetChunk(cx, cy, cz)
|
||||
chunk:Tick()
|
||||
if not chunk.loaded then
|
||||
ChunkManager:LoadChunk(cx, cy, cz)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if player.Character then
|
||||
warmInitial(player.Character)
|
||||
else
|
||||
player.CharacterAdded:Wait()
|
||||
warmInitial(player.Character)
|
||||
end
|
||||
|
||||
task.defer(function()
|
||||
while true do
|
||||
wait(2)
|
||||
|
||||
@@ -1,76 +1,137 @@
|
||||
local PlacementManager = {}
|
||||
|
||||
local Players = game:GetService("Players")
|
||||
local Workspace = game:GetService("Workspace")
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local RunService = game:GetService("RunService")
|
||||
|
||||
local ChunkManager = require("./ChunkManager")
|
||||
local Util = require("./Util")
|
||||
|
||||
PlacementManager.ChunkFolder = ChunkManager.ChunkFolder
|
||||
local remotes = ReplicatedStorage:WaitForChild("Remotes")
|
||||
local placeRemote = remotes:WaitForChild("PlaceBlock")
|
||||
local breakRemote = remotes:WaitForChild("BreakBlock")
|
||||
local tickRemote = ReplicatedStorage:WaitForChild("Tick")
|
||||
|
||||
local LOCAL_PLAYER = Players.LocalPlayer
|
||||
local CAMERA = Workspace.CurrentCamera
|
||||
|
||||
local CHUNK_SIZE = 8
|
||||
local BLOCK_SIZE = 4
|
||||
local CHUNK_WORLD_SIZE = CHUNK_SIZE * BLOCK_SIZE
|
||||
local MAX_RAY_DISTANCE = 1024
|
||||
|
||||
local raycastParams = RaycastParams.new()
|
||||
raycastParams.FilterDescendantsInstances = {PlacementManager.ChunkFolder}
|
||||
raycastParams.FilterDescendantsInstances = { ChunkManager.ChunkFolder }
|
||||
raycastParams.FilterType = Enum.RaycastFilterType.Include
|
||||
raycastParams.IgnoreWater = true
|
||||
|
||||
if _G.SB then return nil end
|
||||
_G.SB = true
|
||||
local selectionBox = Instance.new("SelectionBox")
|
||||
selectionBox.Name = "$SelectionBox"
|
||||
selectionBox.LineThickness = 0.03
|
||||
selectionBox.Color3 = Color3.new(1, 1, 0)
|
||||
selectionBox.SurfaceTransparency = 0.85
|
||||
selectionBox.Transparency = 0.25
|
||||
selectionBox.Adornee = nil
|
||||
selectionBox.Parent = Workspace:FindFirstChildOfClass("Terrain") or Workspace
|
||||
|
||||
PlacementManager.SelectionBox = script.SelectionBox:Clone()
|
||||
PlacementManager.SelectionBox.Name = "$SelectionBox"..(game:GetService("RunService"):IsServer() and "_SERVER" or "")
|
||||
PlacementManager.SelectionBox.Parent = game:GetService("Workspace"):FindFirstChildOfClass("Terrain")
|
||||
|
||||
-- Trash method TODO: Fix this
|
||||
local function findParent(i: Instance): Instance
|
||||
local f = i:FindFirstAncestorOfClass("Folder")
|
||||
local d = i
|
||||
repeat
|
||||
d = d.Parent
|
||||
until d.Parent == f
|
||||
return d
|
||||
end
|
||||
|
||||
local Mouse: Mouse = nil
|
||||
local lastNormalId: Enum.NormalId? = nil
|
||||
|
||||
local function normalIdToOffset(normal: Enum.NormalId): Vector3
|
||||
if normal == Enum.NormalId.Top then
|
||||
return Vector3.new(0, 1, 0)
|
||||
elseif normal == Enum.NormalId.Bottom then
|
||||
return Vector3.new(0, -1, 0)
|
||||
elseif normal == Enum.NormalId.Left then
|
||||
return Vector3.new(-1, 0, 0)
|
||||
elseif normal == Enum.NormalId.Right then
|
||||
return Vector3.new(1, 0, 0)
|
||||
elseif normal == Enum.NormalId.Back then
|
||||
return Vector3.new(0, 0, 1)
|
||||
elseif normal == Enum.NormalId.Front then
|
||||
return Vector3.new(0, 0, -1)
|
||||
local function normalToId(normal: Vector3): Enum.NormalId
|
||||
local absX, absY, absZ = math.abs(normal.X), math.abs(normal.Y), math.abs(normal.Z)
|
||||
if absX > absY and absX > absZ then
|
||||
return normal.X > 0 and Enum.NormalId.Right or Enum.NormalId.Left
|
||||
elseif absY > absX and absY > absZ then
|
||||
return normal.Y > 0 and Enum.NormalId.Top or Enum.NormalId.Bottom
|
||||
else
|
||||
return normal.Z > 0 and Enum.NormalId.Back or Enum.NormalId.Front
|
||||
end
|
||||
return Vector3.new(0, 0, 0)
|
||||
end
|
||||
|
||||
local function offsetChunkBlock(chunk: Vector3, block: Vector3, offset: Vector3)
|
||||
local cx, cy, cz = chunk.X, chunk.Y, chunk.Z
|
||||
local bx, by, bz = block.X + offset.X, block.Y + offset.Y, block.Z + offset.Z
|
||||
local function findChunkAndBlock(hitInstance: Instance)
|
||||
-- Find chunk container (child of ChunkFolder)
|
||||
local inst = hitInstance
|
||||
local chunkInstance = nil
|
||||
while inst and inst.Parent do
|
||||
if inst.Parent == ChunkManager.ChunkFolder then
|
||||
chunkInstance = inst
|
||||
break
|
||||
end
|
||||
if inst.Parent.Parent == ChunkManager.ChunkFolder then
|
||||
chunkInstance = inst.Parent
|
||||
break
|
||||
end
|
||||
inst = inst.Parent
|
||||
end
|
||||
if not chunkInstance or chunkInstance.Parent ~= ChunkManager.ChunkFolder then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Find block container (direct child of chunk)
|
||||
local blockInstance = nil
|
||||
inst = hitInstance
|
||||
while inst and inst.Parent do
|
||||
if inst.Parent == chunkInstance then
|
||||
blockInstance = inst
|
||||
break
|
||||
end
|
||||
inst = inst.Parent
|
||||
end
|
||||
if not blockInstance then
|
||||
return nil
|
||||
end
|
||||
|
||||
local chunkCoords = Util.BlockPosStringToCoords(chunkInstance.Name)
|
||||
local blockCoords = nil
|
||||
|
||||
if blockInstance:IsA("BasePart") or blockInstance:IsA("Model") then
|
||||
blockCoords = Util.BlockPosStringToCoords(blockInstance.Name)
|
||||
end
|
||||
|
||||
if not blockCoords then
|
||||
return nil
|
||||
end
|
||||
|
||||
return chunkCoords, blockCoords, blockInstance
|
||||
end
|
||||
|
||||
local function offsetForPlacement(chunk: Vector3, block: Vector3, normalId: Enum.NormalId)
|
||||
local cx, cy, cz = chunk.X, chunk.Y, chunk.Z
|
||||
local bx, by, bz = block.X, block.Y, block.Z
|
||||
|
||||
if normalId == Enum.NormalId.Top then
|
||||
by += 1
|
||||
elseif normalId == Enum.NormalId.Bottom then
|
||||
by -= 1
|
||||
elseif normalId == Enum.NormalId.Left then
|
||||
bx -= 1
|
||||
elseif normalId == Enum.NormalId.Right then
|
||||
bx += 1
|
||||
elseif normalId == Enum.NormalId.Front then
|
||||
bz -= 1
|
||||
elseif normalId == Enum.NormalId.Back then
|
||||
bz += 1
|
||||
end
|
||||
|
||||
-- Wrap across chunk boundaries (chunks are 1-indexed blocks 1..8)
|
||||
if bx < 1 then
|
||||
bx = 8
|
||||
bx = CHUNK_SIZE
|
||||
cx -= 1
|
||||
elseif bx > 8 then
|
||||
elseif bx > CHUNK_SIZE then
|
||||
bx = 1
|
||||
cx += 1
|
||||
end
|
||||
|
||||
if by < 1 then
|
||||
by = 8
|
||||
by = CHUNK_SIZE
|
||||
cy -= 1
|
||||
elseif by > 8 then
|
||||
elseif by > CHUNK_SIZE then
|
||||
by = 1
|
||||
cy += 1
|
||||
end
|
||||
|
||||
if bz < 1 then
|
||||
bz = 8
|
||||
bz = CHUNK_SIZE
|
||||
cz -= 1
|
||||
elseif bz > 8 then
|
||||
elseif bz > CHUNK_SIZE then
|
||||
bz = 1
|
||||
cz += 1
|
||||
end
|
||||
@@ -78,142 +139,107 @@ local function offsetChunkBlock(chunk: Vector3, block: Vector3, offset: Vector3)
|
||||
return Vector3.new(cx, cy, cz), Vector3.new(bx, by, bz)
|
||||
end
|
||||
|
||||
-- Gets the block and normalid of the block (and surface) the player is looking at
|
||||
function PlacementManager:Raycast()
|
||||
if not Mouse then
|
||||
Mouse = game:GetService("Players").LocalPlayer:GetMouse()
|
||||
local lastHit = nil
|
||||
|
||||
local function castFromCamera()
|
||||
if not LOCAL_PLAYER or not CAMERA then
|
||||
return nil
|
||||
end
|
||||
task.synchronize()
|
||||
local objLookingAt = Mouse.Target
|
||||
local dir = Mouse.TargetSurface
|
||||
if not objLookingAt then
|
||||
PlacementManager.SelectionBox.Adornee = nil
|
||||
script.RaycastResult.Value = nil
|
||||
lastNormalId = nil
|
||||
return
|
||||
|
||||
local mouse = LOCAL_PLAYER:GetMouse()
|
||||
if not mouse then
|
||||
return nil
|
||||
end
|
||||
|
||||
--if not objLookingAt:IsDescendantOf(ChunkManager.ChunkFolder) then return end
|
||||
local parent = findParent(objLookingAt)
|
||||
if parent:GetAttribute("ns") == true then
|
||||
PlacementManager.SelectionBox.Adornee = nil
|
||||
script.RaycastResult.Value = nil
|
||||
lastNormalId = nil
|
||||
return
|
||||
|
||||
local unitRay = mouse.UnitRay
|
||||
local result = Workspace:Raycast(unitRay.Origin, unitRay.Direction * MAX_RAY_DISTANCE, raycastParams)
|
||||
if not result then
|
||||
selectionBox.Adornee = nil
|
||||
lastHit = nil
|
||||
return nil
|
||||
end
|
||||
PlacementManager.SelectionBox.Adornee = parent
|
||||
script.RaycastResult.Value = parent
|
||||
lastNormalId = dir
|
||||
return parent, dir
|
||||
|
||||
local chunkCoords, blockCoords, blockInstance = findChunkAndBlock(result.Instance)
|
||||
if not chunkCoords or not blockCoords then
|
||||
selectionBox.Adornee = nil
|
||||
lastHit = nil
|
||||
return nil
|
||||
end
|
||||
|
||||
local hitNormalId = normalToId(result.Normal)
|
||||
local adornTarget = blockInstance
|
||||
if adornTarget and adornTarget:IsA("Model") then
|
||||
adornTarget = adornTarget.PrimaryPart or adornTarget:FindFirstChildWhichIsA("BasePart")
|
||||
end
|
||||
selectionBox.Adornee = adornTarget or result.Instance
|
||||
|
||||
lastHit = {
|
||||
chunk = chunkCoords,
|
||||
block = blockCoords,
|
||||
normal = hitNormalId,
|
||||
instance = adornTarget or result.Instance,
|
||||
}
|
||||
|
||||
return lastHit
|
||||
end
|
||||
|
||||
function PlacementManager:RaycastGetResult()
|
||||
return script.RaycastResult.Value
|
||||
-- Public API
|
||||
|
||||
function PlacementManager:GetBlockAtMouse()
|
||||
local hit = castFromCamera()
|
||||
if not hit then
|
||||
return nil
|
||||
end
|
||||
return {
|
||||
chunk = hit.chunk,
|
||||
block = hit.block,
|
||||
}
|
||||
end
|
||||
|
||||
local remotes = game:GetService("ReplicatedStorage"):WaitForChild("Remotes")
|
||||
local placeRemote = remotes:WaitForChild("PlaceBlock")
|
||||
local breakRemote = remotes:WaitForChild("BreakBlock")
|
||||
local tickRemote = game:GetService("ReplicatedStorage").Tick
|
||||
function PlacementManager:GetPlacementAtMouse()
|
||||
local hit = castFromCamera()
|
||||
if not hit then
|
||||
return nil
|
||||
end
|
||||
local placeChunk, placeBlock = offsetForPlacement(hit.chunk, hit.block, hit.normal)
|
||||
return {
|
||||
chunk = placeChunk,
|
||||
block = placeBlock,
|
||||
}
|
||||
end
|
||||
|
||||
-- FIRES REMOTE
|
||||
function PlacementManager:PlaceBlock(cx, cy, cz, x, y, z, blockId: string)
|
||||
--print("placeblock")
|
||||
--local chunk = ChunkManager:GetChunk(cx, cy, cz)
|
||||
--chunk:CreateBlock(x, y, z, blockData)
|
||||
placeRemote:FireServer(cx, cy, cz, x, y, z, blockId)
|
||||
end
|
||||
|
||||
-- FIRES REMOTE
|
||||
function PlacementManager:BreakBlock(cx, cy, cz, x, y, z)
|
||||
--print("breakblock")
|
||||
--local chunk = ChunkManager:GetChunk(cx, cy, cz)
|
||||
--chunk:RemoveBlock(x, y, z)
|
||||
breakRemote:FireServer(cx, cy, cz, x, y, z)
|
||||
end
|
||||
|
||||
-- CLIENTSIDED
|
||||
function PlacementManager:PlaceBlockLocal(cx, cy, cz, x, y, z, blockData)
|
||||
local chunk = ChunkManager:GetChunk(cx, cy, cz)
|
||||
chunk:CreateBlock(x, y, z, blockData)
|
||||
end
|
||||
|
||||
-- CLIENTSIDED
|
||||
function PlacementManager:BreakBlockLocal(cx, cy, cz, x, y, z)
|
||||
local chunk = ChunkManager:GetChunk(cx, cy, cz)
|
||||
chunk:RemoveBlock(x, y, z)
|
||||
end
|
||||
|
||||
function PlacementManager:GetBlockAtMouse(): nil | {chunk:Vector3, block: Vector3}
|
||||
local selectedPart = PlacementManager:RaycastGetResult()
|
||||
--print(selectedPart and selectedPart:GetFullName() or nil)
|
||||
if selectedPart == nil then
|
||||
PlacementManager.SelectionBox.Adornee = nil
|
||||
script.RaycastResult.Value = nil
|
||||
lastNormalId = nil
|
||||
return nil
|
||||
end
|
||||
if not selectedPart.Parent then
|
||||
PlacementManager.SelectionBox.Adornee = nil
|
||||
script.RaycastResult.Value = nil
|
||||
lastNormalId = nil
|
||||
return nil
|
||||
end
|
||||
local chunkCoords = Util.BlockPosStringToCoords(selectedPart.Parent.Name)
|
||||
local blockCoords = Util.BlockPosStringToCoords(selectedPart.Name)
|
||||
|
||||
return {
|
||||
chunk = chunkCoords,
|
||||
block = blockCoords
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
function PlacementManager:GetTargetAtMouse(): nil | {chunk:Vector3, block: Vector3, normal: Enum.NormalId}
|
||||
local hit = PlacementManager:GetBlockAtMouse()
|
||||
if not hit or not lastNormalId then
|
||||
return nil
|
||||
end
|
||||
|
||||
return {
|
||||
chunk = hit.chunk,
|
||||
block = hit.block,
|
||||
normal = lastNormalId
|
||||
}
|
||||
end
|
||||
|
||||
function PlacementManager:GetPlacementAtMouse(): nil | {chunk:Vector3, block: Vector3}
|
||||
local hit = PlacementManager:GetTargetAtMouse()
|
||||
if not hit then
|
||||
return nil
|
||||
end
|
||||
local offset = normalIdToOffset(hit.normal)
|
||||
local placeChunk, placeBlock = offsetChunkBlock(hit.chunk, hit.block, offset)
|
||||
return {
|
||||
chunk = placeChunk,
|
||||
block = placeBlock
|
||||
}
|
||||
end
|
||||
|
||||
function PlacementManager:Init()
|
||||
game:GetService("RunService").Heartbeat:Connect(function()
|
||||
local a,b = pcall(function()
|
||||
PlacementManager:Raycast()
|
||||
end)
|
||||
if not a then
|
||||
task.synchronize()
|
||||
PlacementManager.SelectionBox.Adornee = nil
|
||||
script.RaycastResult.Value = nil
|
||||
task.desynchronize()
|
||||
RunService.Heartbeat:Connect(function()
|
||||
local ok = pcall(castFromCamera)
|
||||
if not ok then
|
||||
selectionBox.Adornee = nil
|
||||
lastHit = nil
|
||||
end
|
||||
end)
|
||||
|
||||
tickRemote.OnClientEvent:Connect(function(m, cx, cy, cz, x, y, z, d)
|
||||
--warn("PROPOGATED TICK", m, cx, cy, cz, x, y, z, d)
|
||||
if m == "B_C" then
|
||||
PlacementManager:PlaceBlockLocal(cx, cy, cz, x, y ,z, d)
|
||||
end
|
||||
if m == "B_D" then
|
||||
PlacementManager:BreakBlockLocal(cx, cy, cz, x, y ,z)
|
||||
PlacementManager:PlaceBlockLocal(cx, cy, cz, x, y, z, d)
|
||||
elseif m == "B_D" then
|
||||
PlacementManager:BreakBlockLocal(cx, cy, cz, x, y, z)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -3,16 +3,62 @@ print("Hello world!")
|
||||
task.synchronize()
|
||||
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local Workspace = game:GetService("Workspace")
|
||||
local ServerScriptService = game:GetService("ServerScriptService")
|
||||
|
||||
local function getOrCreateFolder(parent: Instance, name: string): Folder
|
||||
local existing = parent:FindFirstChild(name)
|
||||
if existing then
|
||||
return existing
|
||||
end
|
||||
local folder = Instance.new("Folder")
|
||||
folder.Name = name
|
||||
folder.Parent = parent
|
||||
return folder
|
||||
end
|
||||
|
||||
local Shared = ReplicatedStorage:WaitForChild("Shared")
|
||||
local ModsFolder = ReplicatedStorage:WaitForChild("Mods")
|
||||
local ModsFolder = getOrCreateFolder(ReplicatedStorage, "Mods")
|
||||
local ObjectsFolder = getOrCreateFolder(ReplicatedStorage, "Objects")
|
||||
local BlocksFolder = getOrCreateFolder(ReplicatedStorage, "Blocks")
|
||||
local BlockUpdateOpsFolder = getOrCreateFolder(ReplicatedStorage, "BlockUpdateOperations")
|
||||
|
||||
local function ensureBlock(id: number, name: string, color: Color3)
|
||||
for _, child in ipairs(BlocksFolder:GetChildren()) do
|
||||
if child:GetAttribute("n") == id then
|
||||
return
|
||||
end
|
||||
end
|
||||
local part = Instance.new("Part")
|
||||
part.Name = name
|
||||
part.Anchored = true
|
||||
part.Size = Vector3.new(4, 4, 4)
|
||||
part.Material = Enum.Material.SmoothPlastic
|
||||
part.Color = color
|
||||
part:SetAttribute("n", id)
|
||||
part.Parent = BlocksFolder
|
||||
end
|
||||
|
||||
-- Seed minimal blocks so generation/placement never produce invalid ids.
|
||||
ensureBlock(1, "mc:grass_block", Color3.fromRGB(117, 201, 112))
|
||||
ensureBlock(2, "mc:dirt", Color3.fromRGB(134, 96, 67))
|
||||
|
||||
local ReplicaService = require(ServerScriptService:WaitForChild("ServerPackages"):WaitForChild("ReplicaService"))
|
||||
local InventoryClassToken = ReplicaService.NewClassToken("Inventory")
|
||||
|
||||
local Util = require(Shared.Util)
|
||||
local TG = require("./ServerChunkManager/TerrainGen")
|
||||
|
||||
local mlLoadedFlag = ObjectsFolder:FindFirstChild("MLLoaded")
|
||||
if not mlLoadedFlag then
|
||||
mlLoadedFlag = Instance.new("BoolValue")
|
||||
mlLoadedFlag.Name = "MLLoaded"
|
||||
mlLoadedFlag.Parent = ObjectsFolder
|
||||
end
|
||||
mlLoadedFlag.Value = false
|
||||
|
||||
do
|
||||
local workspaceModFolder = game:GetService("Workspace"):WaitForChild("mods")
|
||||
local workspaceModFolder = Workspace:FindFirstChild("mods") or getOrCreateFolder(Workspace, "mods")
|
||||
|
||||
for _,v in pairs(workspaceModFolder:GetChildren()) do
|
||||
v.Parent = ModsFolder
|
||||
@@ -23,12 +69,7 @@ end
|
||||
local ML = require(Shared.ModLoader)
|
||||
ML.loadModsS()
|
||||
|
||||
do
|
||||
local bv = Instance.new("BoolValue")
|
||||
bv.Name = "MLLoaded"
|
||||
bv.Value = true
|
||||
bv.Parent = ReplicatedStorage:WaitForChild("Objects")
|
||||
end
|
||||
mlLoadedFlag.Value = true
|
||||
|
||||
local MAX_CHUNK_DIST = 200
|
||||
|
||||
@@ -67,8 +108,6 @@ local tickRemote = ReplicatedStorage.Tick
|
||||
local remotes = ReplicatedStorage:WaitForChild("Remotes")
|
||||
local placeRemote = remotes:WaitForChild("PlaceBlock")
|
||||
local breakRemote = remotes:WaitForChild("BreakBlock")
|
||||
local inventorySync = remotes:WaitForChild("InventorySync")
|
||||
local inventoryRequest = remotes:WaitForChild("InventoryRequest")
|
||||
local blocksFolder = ReplicatedStorage:WaitForChild("Blocks")
|
||||
local function propogate(a, cx, cy, cz, x, y, z, bd)
|
||||
task.synchronize()
|
||||
@@ -76,10 +115,11 @@ local function propogate(a, cx, cy, cz, x, y, z, bd)
|
||||
task.desynchronize()
|
||||
end
|
||||
|
||||
local MAX_REACH = 24
|
||||
local MAX_REACH = math.huge
|
||||
local HOTBAR_SIZE = 9
|
||||
local blockIdMap = {}
|
||||
local playerInventories = {}
|
||||
local playerReplicas = {}
|
||||
|
||||
local function rebuildBlockIdMap()
|
||||
table.clear(blockIdMap)
|
||||
@@ -109,7 +149,11 @@ local function buildDefaultSlots(): {string}
|
||||
end)
|
||||
local slots = table.create(HOTBAR_SIZE, "")
|
||||
for i = 1, HOTBAR_SIZE do
|
||||
slots[i] = ids[i]
|
||||
local val = ids[i]
|
||||
if val == nil or val == "" then
|
||||
val = ""
|
||||
end
|
||||
slots[i] = val
|
||||
end
|
||||
return slots
|
||||
end
|
||||
@@ -119,8 +163,37 @@ local function syncInventory(player: Player)
|
||||
if not data then
|
||||
return
|
||||
end
|
||||
task.synchronize()
|
||||
inventorySync:FireClient(player, data.slots)
|
||||
if type(data.slots) ~= "table" then
|
||||
data.slots = buildDefaultSlots()
|
||||
end
|
||||
if type(data.allowed) ~= "table" then
|
||||
data.allowed = {}
|
||||
for i = 1, HOTBAR_SIZE do
|
||||
local id = data.slots[i] or ""
|
||||
if id ~= "" then
|
||||
data.allowed[id] = true
|
||||
end
|
||||
end
|
||||
playerInventories[player.UserId] = data
|
||||
end
|
||||
local replica = playerReplicas[player]
|
||||
if not replica then
|
||||
replica = ReplicaService.NewReplica({
|
||||
ClassToken = InventoryClassToken,
|
||||
Tags = {
|
||||
Player = player,
|
||||
},
|
||||
Data = {
|
||||
Slots = data.slots,
|
||||
Allowed = data.allowed,
|
||||
},
|
||||
Replication = player,
|
||||
})
|
||||
playerReplicas[player] = replica
|
||||
else
|
||||
replica:SetValue("Slots", data.slots)
|
||||
replica:SetValue("Allowed", data.allowed)
|
||||
end
|
||||
end
|
||||
|
||||
local function rebuildAllInventories()
|
||||
@@ -128,7 +201,7 @@ local function rebuildAllInventories()
|
||||
local slots = buildDefaultSlots()
|
||||
local allowed = {}
|
||||
for i = 1, HOTBAR_SIZE do
|
||||
local id = slots[i]
|
||||
local id = slots[i] or ""
|
||||
if id ~= "" then
|
||||
allowed[id] = true
|
||||
end
|
||||
@@ -147,12 +220,12 @@ blocksFolder.ChildRemoved:Connect(rebuildAllInventories)
|
||||
game:GetService("Players").PlayerAdded:Connect(function(player: Player)
|
||||
local slots = buildDefaultSlots()
|
||||
local allowed = {}
|
||||
for i = 1, HOTBAR_SIZE do
|
||||
local id = slots[i]
|
||||
if id ~= "" then
|
||||
allowed[id] = true
|
||||
end
|
||||
for i = 1, HOTBAR_SIZE do
|
||||
local id = slots[i] or ""
|
||||
if id ~= "" then
|
||||
allowed[id] = true
|
||||
end
|
||||
end
|
||||
playerInventories[player.UserId] = {
|
||||
slots = slots,
|
||||
allowed = allowed,
|
||||
@@ -162,17 +235,22 @@ end)
|
||||
|
||||
game:GetService("Players").PlayerRemoving:Connect(function(player: Player)
|
||||
playerInventories[player.UserId] = nil
|
||||
local replica = playerReplicas[player]
|
||||
if replica then
|
||||
replica:Destroy()
|
||||
playerReplicas[player] = nil
|
||||
end
|
||||
end)
|
||||
|
||||
for _, player in ipairs(game:GetService("Players"):GetPlayers()) do
|
||||
local slots = buildDefaultSlots()
|
||||
local allowed = {}
|
||||
for i = 1, HOTBAR_SIZE do
|
||||
local id = slots[i]
|
||||
if id ~= "" then
|
||||
allowed[id] = true
|
||||
end
|
||||
for i = 1, HOTBAR_SIZE do
|
||||
local id = slots[i] or ""
|
||||
if id ~= "" then
|
||||
allowed[id] = true
|
||||
end
|
||||
end
|
||||
playerInventories[player.UserId] = {
|
||||
slots = slots,
|
||||
allowed = allowed,
|
||||
@@ -180,10 +258,6 @@ for _, player in ipairs(game:GetService("Players"):GetPlayers()) do
|
||||
syncInventory(player)
|
||||
end
|
||||
|
||||
inventoryRequest.OnServerEvent:Connect(function(player: Player)
|
||||
syncInventory(player)
|
||||
end)
|
||||
|
||||
local function getPlayerPosition(player: Player): Vector3?
|
||||
local character = player.Character
|
||||
if not character then
|
||||
@@ -197,12 +271,8 @@ local function getPlayerPosition(player: Player): Vector3?
|
||||
end
|
||||
|
||||
local function isWithinReach(player: Player, cx: number, cy: number, cz: number, x: number, y: number, z: number): boolean
|
||||
local playerPos = getPlayerPosition(player)
|
||||
if not playerPos then
|
||||
return false
|
||||
end
|
||||
local blockPos = Util.ChunkPosToCFrame(Vector3.new(cx, cy, cz), Vector3.new(x, y, z)).Position
|
||||
return (blockPos - playerPos).Magnitude <= MAX_REACH
|
||||
-- Creative: disable reach limits.
|
||||
return true
|
||||
end
|
||||
|
||||
local function resolveBlockId(blockId: any): string | number | nil
|
||||
@@ -210,11 +280,8 @@ local function resolveBlockId(blockId: any): string | number | nil
|
||||
end
|
||||
|
||||
local function playerHasBlockId(player: Player, blockId: string | number): boolean
|
||||
local data = playerInventories[player.UserId]
|
||||
if not data then
|
||||
return false
|
||||
end
|
||||
return data.allowed[tostring(blockId)] == true
|
||||
-- Creative mode: allow all block ids.
|
||||
return true
|
||||
end
|
||||
|
||||
local function getServerChunk(cx: number, cy: number, cz: number)
|
||||
|
||||
@@ -7,7 +7,14 @@ end
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local UIS = game:GetService("UserInputService")
|
||||
|
||||
ReplicatedStorage:WaitForChild("Objects"):WaitForChild("MLLoaded")
|
||||
local function waitForModLoader()
|
||||
local marker = ReplicatedStorage:WaitForChild("Objects"):WaitForChild("MLLoaded")
|
||||
if marker:IsA("BoolValue") and marker.Value ~= true then
|
||||
marker:GetPropertyChangedSignal("Value"):Wait()
|
||||
end
|
||||
end
|
||||
|
||||
waitForModLoader()
|
||||
|
||||
game:GetService("Players").LocalPlayer.CameraMode = Enum.CameraMode.LockFirstPerson
|
||||
UIS.MouseIconEnabled = false
|
||||
@@ -19,4 +26,4 @@ UIS.InputEnded:Connect(function(k)
|
||||
script.Parent.CrosshairLabel.Visible = not v
|
||||
script.Parent.DummyButton.Modal = v
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
@@ -5,54 +5,48 @@ end
|
||||
local ui = script.Parent
|
||||
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local Players = game:GetService("Players")
|
||||
local RunService = game:GetService("RunService")
|
||||
local Roact = require(ReplicatedStorage:WaitForChild("Packages"):WaitForChild("Roact"))
|
||||
local ReplicaController = require(ReplicatedStorage:WaitForChild("Packages"):WaitForChild("ReplicaController"))
|
||||
local Inventory = require(ReplicatedStorage.Shared.Inventory)
|
||||
local Catppuccin = require(ReplicatedStorage.Shared.Catppuccin)
|
||||
local blocksFolder = ReplicatedStorage:WaitForChild("Blocks")
|
||||
|
||||
local mocha = Catppuccin.mocha
|
||||
|
||||
ReplicatedStorage:WaitForChild("Objects"):WaitForChild("MLLoaded")
|
||||
local function waitForModLoader()
|
||||
local marker = ReplicatedStorage:WaitForChild("Objects"):WaitForChild("MLLoaded")
|
||||
if marker:IsA("BoolValue") and marker.Value ~= true then
|
||||
marker:GetPropertyChangedSignal("Value"):Wait()
|
||||
end
|
||||
end
|
||||
|
||||
waitForModLoader()
|
||||
|
||||
local cd = ReplicatedStorage.Objects.ChunkDebug:Clone()
|
||||
local sky = ReplicatedStorage.Objects.Sky:Clone()
|
||||
local base = ReplicatedStorage.Objects.FakeBaseplate:Clone()
|
||||
|
||||
|
||||
cd.Parent = game:GetService("Workspace"):FindFirstChildOfClass("Terrain")
|
||||
sky.Parent = game:GetService("Workspace"):FindFirstChildOfClass("Terrain")
|
||||
base.Parent = game:GetService("Workspace"):FindFirstChildOfClass("Terrain")
|
||||
|
||||
local hotbarRoot = ui:WaitForChild("Hotbar")
|
||||
local hotbarSlotsRoot = hotbarRoot:WaitForChild("Frame")
|
||||
local hotbarSelectLabel = ui:WaitForChild("HotbarItemSelectLabel")
|
||||
local hotbarSelectText = hotbarSelectLabel:WaitForChild("TextLabel")
|
||||
local hotbarDebug = ui:WaitForChild("HotbarDebug")
|
||||
local hotbarDebugText = hotbarDebug:WaitForChild("TextLabel")
|
||||
local debugUpperText = ui:WaitForChild("DebugUpperText")
|
||||
local hotbarStroke = hotbarRoot:FindFirstChild("UIStroke")
|
||||
local hotbarSelectStroke = hotbarSelectLabel:FindFirstChild("UIStroke")
|
||||
local hotbarDebugStroke = hotbarDebug:FindFirstChild("UIStroke")
|
||||
local debugUpperText = ui:FindFirstChild("DebugUpperText")
|
||||
if debugUpperText then
|
||||
debugUpperText:Destroy()
|
||||
end
|
||||
|
||||
hotbarRoot.BackgroundColor3 = mocha.base
|
||||
if hotbarStroke then
|
||||
hotbarStroke.Color = mocha.blue
|
||||
local function destroyLegacyUi(name)
|
||||
local inst = ui:FindFirstChild(name)
|
||||
if inst then
|
||||
inst:Destroy()
|
||||
end
|
||||
end
|
||||
hotbarSelectLabel.BackgroundColor3 = mocha.base
|
||||
if hotbarSelectStroke then
|
||||
hotbarSelectStroke.Color = mocha.blue
|
||||
end
|
||||
hotbarDebug.BackgroundColor3 = mocha.base
|
||||
if hotbarDebugStroke then
|
||||
hotbarDebugStroke.Color = mocha.surface2
|
||||
end
|
||||
hotbarSelectText.TextColor3 = mocha.text
|
||||
hotbarDebugText.TextColor3 = mocha.text
|
||||
debugUpperText.TextColor3 = mocha.text
|
||||
|
||||
local slotFrames = {}
|
||||
local slotStrokes = {}
|
||||
local slotLabels = {}
|
||||
local renderHotbar = nil
|
||||
destroyLegacyUi("Hotbar")
|
||||
destroyLegacyUi("HotbarItemSelectLabel")
|
||||
destroyLegacyUi("HotbarDebug")
|
||||
|
||||
local blockDisplayNames = {}
|
||||
local blockIcons = {}
|
||||
@@ -63,164 +57,364 @@ local function rebuildBlockMappings()
|
||||
for _, block in ipairs(blocksFolder:GetChildren()) do
|
||||
local id = block:GetAttribute("n")
|
||||
if id ~= nil then
|
||||
blockDisplayNames[tostring(id)] = block.Name
|
||||
local idKey = tostring(id)
|
||||
blockDisplayNames[idKey] = block.Name
|
||||
local icon = block:GetAttribute("icon")
|
||||
if typeof(icon) == "string" and icon ~= "" then
|
||||
blockIcons[tostring(id)] = icon
|
||||
end
|
||||
end
|
||||
end
|
||||
if renderHotbar then
|
||||
renderHotbar()
|
||||
end
|
||||
end
|
||||
|
||||
rebuildBlockMappings()
|
||||
blocksFolder.ChildAdded:Connect(rebuildBlockMappings)
|
||||
blocksFolder.ChildRemoved:Connect(rebuildBlockMappings)
|
||||
|
||||
for i = 1, Inventory.GetHotbarSize() do
|
||||
local slotName = `HotbarSlot{i - 1}`
|
||||
local slot = hotbarSlotsRoot:WaitForChild(slotName)
|
||||
slotFrames[i] = slot
|
||||
local stroke = slot:FindFirstChild("UIStroke")
|
||||
if not stroke then
|
||||
stroke = Instance.new("UIStroke")
|
||||
stroke.Parent = slot
|
||||
end
|
||||
slotStrokes[i] = stroke
|
||||
|
||||
local imageLabel = slot:FindFirstChild("ImageLabel")
|
||||
if not imageLabel then
|
||||
imageLabel = Instance.new("ImageLabel")
|
||||
imageLabel.Name = "ImageLabel"
|
||||
imageLabel.BackgroundTransparency = 1
|
||||
imageLabel.Size = UDim2.fromScale(1, 1)
|
||||
imageLabel.ScaleType = Enum.ScaleType.Fit
|
||||
imageLabel.Parent = slot
|
||||
end
|
||||
|
||||
local label = slot:FindFirstChild("TextLabel")
|
||||
if not label then
|
||||
label = Instance.new("TextLabel")
|
||||
label.Name = "TextLabel"
|
||||
label.BackgroundTransparency = 1
|
||||
label.Size = UDim2.fromScale(1, 1)
|
||||
label.TextScaled = true
|
||||
label.Font = Enum.Font.Gotham
|
||||
label.TextColor3 = mocha.text
|
||||
label.TextWrapped = true
|
||||
label.ZIndex = 3
|
||||
label.Parent = slot
|
||||
end
|
||||
slotLabels[i] = label
|
||||
end
|
||||
|
||||
renderHotbar = function()
|
||||
for i = 1, Inventory.GetHotbarSize() do
|
||||
local slot = slotFrames[i]
|
||||
if slot then
|
||||
slot.BackgroundColor3 = mocha.surface0
|
||||
local stroke = slotStrokes[i]
|
||||
if stroke then
|
||||
if i == Inventory.GetSelectedIndex() then
|
||||
stroke.Color = mocha.blue
|
||||
stroke.Thickness = 2
|
||||
else
|
||||
stroke.Color = mocha.surface2
|
||||
stroke.Thickness = 1
|
||||
end
|
||||
end
|
||||
|
||||
local imageLabel = slot:FindFirstChild("ImageLabel")
|
||||
local id = Inventory.GetSlot(i)
|
||||
local displayName = id and (blockDisplayNames[tostring(id)] or tostring(id)) or ""
|
||||
if imageLabel then
|
||||
local icon = id and blockIcons[tostring(id)] or nil
|
||||
imageLabel.Visible = icon ~= nil
|
||||
imageLabel.Image = icon or ""
|
||||
imageLabel.BackgroundTransparency = 1
|
||||
imageLabel.ZIndex = 2
|
||||
end
|
||||
|
||||
local textLabel = slotLabels[i]
|
||||
if textLabel then
|
||||
textLabel.Text = displayName
|
||||
textLabel.TextColor3 = mocha.text
|
||||
textLabel.Visible = displayName ~= ""
|
||||
blockIcons[idKey] = icon
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function updateSelectedLabel()
|
||||
local id = Inventory.GetSelectedId()
|
||||
local displayName = id and (blockDisplayNames[tostring(id)] or tostring(id)) or nil
|
||||
if hotbarSelectText then
|
||||
hotbarSelectText.Text = displayName and `Selected: {displayName}` or "Selected: (empty)"
|
||||
end
|
||||
if hotbarDebugText then
|
||||
hotbarDebugText.Text = `Slot {Inventory.GetSelectedIndex()} Id {displayName or "empty"}`
|
||||
local function getDisplayName(id)
|
||||
if id == nil then
|
||||
return nil
|
||||
end
|
||||
return blockDisplayNames[tostring(id)] or tostring(id)
|
||||
end
|
||||
|
||||
local extraSlot = hotbarSlotsRoot and hotbarSlotsRoot:FindFirstChild("HotbarSlot9")
|
||||
if extraSlot then
|
||||
extraSlot.Visible = false
|
||||
local HOTBAR_SLOTS = Inventory.GetHotbarSize()
|
||||
local SLOT_SIZE = 40
|
||||
local SLOT_PADDING = 6
|
||||
local HOTBAR_PADDING = 6
|
||||
local HOTBAR_WIDTH = (SLOT_SIZE * HOTBAR_SLOTS) + (SLOT_PADDING * (HOTBAR_SLOTS - 1)) + (HOTBAR_PADDING * 2)
|
||||
local HOTBAR_HEIGHT = SLOT_SIZE + (HOTBAR_PADDING * 2)
|
||||
local LABEL_WIDTH = 150
|
||||
local LABEL_HEIGHT = 26
|
||||
local HOTBAR_BOTTOM_OFFSET = 20
|
||||
local LABEL_GAP = 8
|
||||
|
||||
local function isDevPlayer()
|
||||
if RunService:IsStudio() then
|
||||
return true
|
||||
end
|
||||
local player = Players.LocalPlayer
|
||||
if game.CreatorType == Enum.CreatorType.User then
|
||||
return player.UserId == game.CreatorId
|
||||
end
|
||||
if game.CreatorType == Enum.CreatorType.Group then
|
||||
return player:IsInGroup(game.CreatorId)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
renderHotbar()
|
||||
updateSelectedLabel()
|
||||
local HotbarApp = Roact.Component:extend("HotbarApp")
|
||||
|
||||
Inventory.OnChanged(function()
|
||||
renderHotbar()
|
||||
updateSelectedLabel()
|
||||
end)
|
||||
function HotbarApp:init()
|
||||
self.state = {
|
||||
slots = table.clone(Inventory.GetSlots()),
|
||||
selectedIndex = Inventory.GetSelectedIndex(),
|
||||
selectedName = getDisplayName(Inventory.GetSelectedId()) or "empty",
|
||||
positionDebug = "",
|
||||
inventoryDebug = "",
|
||||
}
|
||||
end
|
||||
|
||||
Inventory.OnSelected(function()
|
||||
renderHotbar()
|
||||
updateSelectedLabel()
|
||||
end)
|
||||
function HotbarApp:didMount()
|
||||
local function update()
|
||||
local selectedId = Inventory.GetSelectedId()
|
||||
local selectedDisplay = getDisplayName(selectedId) or "empty"
|
||||
local filled = 0
|
||||
for i = 1, HOTBAR_SLOTS do
|
||||
if Inventory.GetSlot(i) ~= nil then
|
||||
filled += 1
|
||||
end
|
||||
end
|
||||
self:setState({
|
||||
slots = table.clone(Inventory.GetSlots()),
|
||||
selectedIndex = Inventory.GetSelectedIndex(),
|
||||
selectedName = selectedDisplay,
|
||||
inventoryDebug = `Slot {Inventory.GetSelectedIndex()} Id {selectedDisplay} Filled {filled}/{HOTBAR_SLOTS}`,
|
||||
})
|
||||
end
|
||||
|
||||
game:GetService("RunService").RenderStepped:Connect(function(dt)
|
||||
local fps = math.round(1/dt)
|
||||
pcall(function()
|
||||
-- pos in chunks of 32 studs of char
|
||||
local pos = game:GetService("Players").LocalPlayer.Character:GetPivot()
|
||||
local chunk = {
|
||||
x = math.round(pos.X/32),
|
||||
y = math.round(pos.Y/32),
|
||||
z = math.round(pos.Z/32)
|
||||
}
|
||||
|
||||
if math.abs(chunk.x) == 0 then chunk.x = 0 end
|
||||
if math.abs(chunk.y) == 0 then chunk.y = 0 end
|
||||
if math.abs(chunk.z) == 0 then chunk.z = 0 end
|
||||
|
||||
local bpos = {
|
||||
x = math.round(pos.X/4),
|
||||
y = math.round(pos.Y/4),
|
||||
z = math.round(pos.Z/4)
|
||||
}
|
||||
rebuildBlockMappings()
|
||||
update()
|
||||
|
||||
if math.abs(bpos.x) == 0 then bpos.x = 0 end
|
||||
if math.abs(bpos.y) == 0 then bpos.y = 0 end
|
||||
if math.abs(bpos.z) == 0 then bpos.z = 0 end
|
||||
|
||||
sky.CFrame = pos
|
||||
ui.DebugUpperText.Text = `Chunk {chunk.x} {chunk.y} {chunk.z}\nPos {bpos.x} {bpos.y} {bpos.z}\n<b>{fps} FPS</b>`
|
||||
|
||||
cd:PivotTo(CFrame.new(
|
||||
chunk.x*32,
|
||||
chunk.y*32,
|
||||
chunk.z*32
|
||||
))
|
||||
|
||||
base.CFrame = CFrame.new(
|
||||
chunk.x*32,
|
||||
-24,
|
||||
chunk.z*32
|
||||
)
|
||||
|
||||
self._changedConn = Inventory.OnChanged(update)
|
||||
self._selectedConn = Inventory.OnSelected(update)
|
||||
self._blockAdded = blocksFolder.ChildAdded:Connect(function()
|
||||
rebuildBlockMappings()
|
||||
update()
|
||||
end)
|
||||
end)
|
||||
self._blockRemoved = blocksFolder.ChildRemoved:Connect(function()
|
||||
rebuildBlockMappings()
|
||||
update()
|
||||
end)
|
||||
|
||||
self._renderConn = RunService.RenderStepped:Connect(function(dt)
|
||||
local fps = math.round(1 / dt)
|
||||
pcall(function()
|
||||
local pos = Players.LocalPlayer.Character:GetPivot()
|
||||
local chunk = {
|
||||
x = math.round(pos.X / 32),
|
||||
y = math.round(pos.Y / 32),
|
||||
z = math.round(pos.Z / 32)
|
||||
}
|
||||
|
||||
if math.abs(chunk.x) == 0 then chunk.x = 0 end
|
||||
if math.abs(chunk.y) == 0 then chunk.y = 0 end
|
||||
if math.abs(chunk.z) == 0 then chunk.z = 0 end
|
||||
|
||||
local bpos = {
|
||||
x = math.round(pos.X / 4),
|
||||
y = math.round(pos.Y / 4),
|
||||
z = math.round(pos.Z / 4)
|
||||
}
|
||||
|
||||
if math.abs(bpos.x) == 0 then bpos.x = 0 end
|
||||
if math.abs(bpos.y) == 0 then bpos.y = 0 end
|
||||
if math.abs(bpos.z) == 0 then bpos.z = 0 end
|
||||
|
||||
sky.CFrame = pos
|
||||
cd:PivotTo(CFrame.new(
|
||||
chunk.x * 32,
|
||||
chunk.y * 32,
|
||||
chunk.z * 32
|
||||
))
|
||||
|
||||
base.CFrame = CFrame.new(
|
||||
chunk.x * 32,
|
||||
-24,
|
||||
chunk.z * 32
|
||||
)
|
||||
|
||||
self:setState({
|
||||
positionDebug = `Chunk {chunk.x} {chunk.y} {chunk.z}\nPos {bpos.x} {bpos.y} {bpos.z}\n<b>{fps} FPS</b>`,
|
||||
})
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
function HotbarApp:willUnmount()
|
||||
if self._changedConn then
|
||||
self._changedConn:Disconnect()
|
||||
self._changedConn = nil
|
||||
end
|
||||
if self._selectedConn then
|
||||
self._selectedConn:Disconnect()
|
||||
self._selectedConn = nil
|
||||
end
|
||||
if self._blockAdded then
|
||||
self._blockAdded:Disconnect()
|
||||
self._blockAdded = nil
|
||||
end
|
||||
if self._blockRemoved then
|
||||
self._blockRemoved:Disconnect()
|
||||
self._blockRemoved = nil
|
||||
end
|
||||
if self._renderConn then
|
||||
self._renderConn:Disconnect()
|
||||
self._renderConn = nil
|
||||
end
|
||||
end
|
||||
|
||||
function HotbarApp:render()
|
||||
local slots = self.state.slots or {}
|
||||
local selectedIndex = self.state.selectedIndex or 1
|
||||
local selectedName = self.state.selectedName or "empty"
|
||||
local positionDebug = self.state.positionDebug or ""
|
||||
local inventoryDebug = self.state.inventoryDebug or ""
|
||||
local debugText = positionDebug
|
||||
if inventoryDebug ~= "" then
|
||||
debugText ..= "\n" .. inventoryDebug
|
||||
end
|
||||
|
||||
local slotChildren = {
|
||||
Layout = Roact.createElement("UIListLayout", {
|
||||
FillDirection = Enum.FillDirection.Horizontal,
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, SLOT_PADDING),
|
||||
}),
|
||||
Padding = Roact.createElement("UIPadding", {
|
||||
PaddingLeft = UDim.new(0, HOTBAR_PADDING),
|
||||
PaddingRight = UDim.new(0, HOTBAR_PADDING),
|
||||
PaddingTop = UDim.new(0, HOTBAR_PADDING),
|
||||
PaddingBottom = UDim.new(0, HOTBAR_PADDING),
|
||||
}),
|
||||
}
|
||||
|
||||
for i = 1, HOTBAR_SLOTS do
|
||||
local id = slots[i]
|
||||
local displayName = getDisplayName(id) or ""
|
||||
local icon = id and blockIcons[tostring(id)] or nil
|
||||
local isSelected = i == selectedIndex
|
||||
|
||||
slotChildren["Slot" .. i] = Roact.createElement("Frame", {
|
||||
Name = "Slot" .. i,
|
||||
Size = UDim2.fromOffset(SLOT_SIZE, SLOT_SIZE),
|
||||
BackgroundColor3 = mocha.surface0,
|
||||
LayoutOrder = i,
|
||||
}, {
|
||||
Corner = Roact.createElement("UICorner", {
|
||||
CornerRadius = UDim.new(0, 8),
|
||||
}),
|
||||
Stroke = Roact.createElement("UIStroke", {
|
||||
Color = isSelected and mocha.blue or mocha.surface2,
|
||||
Thickness = isSelected and 2 or 1,
|
||||
}),
|
||||
Icon = Roact.createElement("ImageLabel", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.fromScale(1, 1),
|
||||
Image = icon or "",
|
||||
Visible = icon ~= nil,
|
||||
ScaleType = Enum.ScaleType.Fit,
|
||||
ZIndex = 2,
|
||||
}),
|
||||
Label = Roact.createElement("TextLabel", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.fromScale(1, 1),
|
||||
Text = displayName,
|
||||
TextColor3 = mocha.text,
|
||||
Font = Enum.Font.GothamMedium,
|
||||
TextSize = 12,
|
||||
TextWrapped = true,
|
||||
Visible = icon == nil and displayName ~= "",
|
||||
ZIndex = 3,
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
return Roact.createElement("Folder", nil, {
|
||||
DebugUpperText = Roact.createElement("TextLabel", {
|
||||
Name = "DebugUpperText",
|
||||
AnchorPoint = Vector2.new(0, 0),
|
||||
Position = UDim2.new(0, 12, 0, 12),
|
||||
Size = UDim2.fromOffset(300, 90),
|
||||
BackgroundTransparency = 1,
|
||||
Text = debugText,
|
||||
TextColor3 = mocha.red,
|
||||
Font = Enum.Font.GothamMedium,
|
||||
TextSize = 14,
|
||||
RichText = true,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextYAlignment = Enum.TextYAlignment.Top,
|
||||
}),
|
||||
SelectedLabel = Roact.createElement("TextLabel", {
|
||||
Name = "HotbarItemLabel",
|
||||
AnchorPoint = Vector2.new(0.5, 1),
|
||||
Position = UDim2.new(0.5, 0, 1, -(HOTBAR_BOTTOM_OFFSET + HOTBAR_HEIGHT + LABEL_GAP)),
|
||||
Size = UDim2.fromOffset(LABEL_WIDTH, LABEL_HEIGHT),
|
||||
BackgroundColor3 = mocha.base,
|
||||
Text = selectedName,
|
||||
TextColor3 = mocha.text,
|
||||
Font = Enum.Font.JosefinSans,
|
||||
TextSize = 14,
|
||||
TextTruncate = Enum.TextTruncate.AtEnd,
|
||||
}, {
|
||||
Corner = Roact.createElement("UICorner", {
|
||||
CornerRadius = UDim.new(0, 8),
|
||||
}),
|
||||
Stroke = Roact.createElement("UIStroke", {
|
||||
Color = mocha.blue,
|
||||
Thickness = 2,
|
||||
ApplyStrokeMode = Enum.ApplyStrokeMode.Border,
|
||||
}),
|
||||
Padding = Roact.createElement("UIPadding", {
|
||||
PaddingLeft = UDim.new(0, 10),
|
||||
PaddingRight = UDim.new(0, 10),
|
||||
}),
|
||||
}),
|
||||
Hotbar = Roact.createElement("Frame", {
|
||||
Name = "Hotbar",
|
||||
AnchorPoint = Vector2.new(0.5, 1),
|
||||
Position = UDim2.new(0.5, 0, 1, -HOTBAR_BOTTOM_OFFSET),
|
||||
Size = UDim2.fromOffset(HOTBAR_WIDTH, HOTBAR_HEIGHT),
|
||||
BackgroundColor3 = mocha.base,
|
||||
}, {
|
||||
Corner = Roact.createElement("UICorner", {
|
||||
CornerRadius = UDim.new(0, 12),
|
||||
}),
|
||||
Stroke = Roact.createElement("UIStroke", {
|
||||
Color = mocha.blue,
|
||||
Thickness = 2,
|
||||
ApplyStrokeMode = Enum.ApplyStrokeMode.Border,
|
||||
}),
|
||||
Slots = Roact.createElement("Frame", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.fromScale(1, 1),
|
||||
}, slotChildren),
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
local ReplicaDebugger = Roact.Component:extend("ReplicaDebugger")
|
||||
|
||||
function ReplicaDebugger:init()
|
||||
self._replicaCount = 0
|
||||
self.state = {
|
||||
replicaCount = 0,
|
||||
initialData = ReplicaController.InitialDataReceived == true,
|
||||
}
|
||||
end
|
||||
|
||||
function ReplicaDebugger:didMount()
|
||||
self._newReplicaConn = ReplicaController.NewReplicaSignal:Connect(function()
|
||||
self._replicaCount += 1
|
||||
self:setState({
|
||||
replicaCount = self._replicaCount,
|
||||
})
|
||||
end)
|
||||
self._initialConn = ReplicaController.InitialDataReceivedSignal:Connect(function()
|
||||
self:setState({
|
||||
initialData = true,
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
function ReplicaDebugger:willUnmount()
|
||||
if self._newReplicaConn then
|
||||
self._newReplicaConn:Disconnect()
|
||||
self._newReplicaConn = nil
|
||||
end
|
||||
if self._initialConn then
|
||||
self._initialConn:Disconnect()
|
||||
self._initialConn = nil
|
||||
end
|
||||
end
|
||||
|
||||
function ReplicaDebugger:render()
|
||||
return Roact.createElement("TextLabel", {
|
||||
Name = "ReplicaDebugger",
|
||||
AnchorPoint = Vector2.new(1, 0),
|
||||
Position = UDim2.new(1, -12, 0, 12),
|
||||
Size = UDim2.fromOffset(220, 60),
|
||||
BackgroundColor3 = mocha.mantle,
|
||||
BackgroundTransparency = 0.2,
|
||||
Text = `Replica (client)\nInitial: {tostring(self.state.initialData)}\nReplicas: {self.state.replicaCount}`,
|
||||
TextColor3 = mocha.text,
|
||||
Font = Enum.Font.GothamMedium,
|
||||
TextSize = 12,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextYAlignment = Enum.TextYAlignment.Top,
|
||||
}, {
|
||||
Corner = Roact.createElement("UICorner", {
|
||||
CornerRadius = UDim.new(0, 8),
|
||||
}),
|
||||
Padding = Roact.createElement("UIPadding", {
|
||||
PaddingLeft = UDim.new(0, 8),
|
||||
PaddingRight = UDim.new(0, 8),
|
||||
PaddingTop = UDim.new(0, 6),
|
||||
PaddingBottom = UDim.new(0, 6),
|
||||
}),
|
||||
Stroke = Roact.createElement("UIStroke", {
|
||||
Color = mocha.surface2,
|
||||
Thickness = 1,
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
Roact.mount(Roact.createElement(HotbarApp), ui, "HotbarRoact")
|
||||
|
||||
if isDevPlayer() then
|
||||
local playerGui = Players.LocalPlayer:WaitForChild("PlayerGui")
|
||||
local debugGui = Instance.new("ScreenGui")
|
||||
debugGui.Name = "ReplicaDebugger"
|
||||
debugGui.ResetOnSpawn = false
|
||||
debugGui.IgnoreGuiInset = true
|
||||
debugGui.Parent = playerGui
|
||||
Roact.mount(Roact.createElement(ReplicaDebugger), debugGui, "ReplicaDebugger")
|
||||
end
|
||||
|
||||
@@ -9,7 +9,14 @@ end)
|
||||
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
||||
ReplicatedStorage:WaitForChild("Objects"):WaitForChild("MLLoaded")
|
||||
local function waitForModLoader()
|
||||
local marker = ReplicatedStorage:WaitForChild("Objects"):WaitForChild("MLLoaded")
|
||||
if marker:IsA("BoolValue") and marker.Value ~= true then
|
||||
marker:GetPropertyChangedSignal("Value"):Wait()
|
||||
end
|
||||
end
|
||||
|
||||
waitForModLoader()
|
||||
|
||||
local ML = require(ReplicatedStorage:WaitForChild("Shared"):WaitForChild("ModLoader"))
|
||||
|
||||
@@ -5,13 +5,18 @@ end
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local UIS = game:GetService("UserInputService")
|
||||
|
||||
ReplicatedStorage:WaitForChild("Objects"):WaitForChild("MLLoaded")
|
||||
local function waitForModLoader()
|
||||
local marker = ReplicatedStorage:WaitForChild("Objects"):WaitForChild("MLLoaded")
|
||||
if marker:IsA("BoolValue") and marker.Value ~= true then
|
||||
marker:GetPropertyChangedSignal("Value"):Wait()
|
||||
end
|
||||
end
|
||||
|
||||
waitForModLoader()
|
||||
|
||||
local PM = require(ReplicatedStorage.Shared.PlacementManager)
|
||||
local Inventory = require(ReplicatedStorage.Shared.Inventory)
|
||||
local remotes = ReplicatedStorage:WaitForChild("Remotes")
|
||||
local inventorySync = remotes:WaitForChild("InventorySync")
|
||||
local inventoryRequest = remotes:WaitForChild("InventoryRequest")
|
||||
local ReplicaController = require(ReplicatedStorage:WaitForChild("Packages"):WaitForChild("ReplicaController"))
|
||||
|
||||
local keyToSlot = {
|
||||
[Enum.KeyCode.One] = 1,
|
||||
@@ -36,14 +41,22 @@ local function findNextFilledIndex(startIndex: number, direction: number): numbe
|
||||
return startIndex
|
||||
end
|
||||
|
||||
inventorySync.OnClientEvent:Connect(function(slots)
|
||||
local function applySlots(slots)
|
||||
if typeof(slots) ~= "table" then
|
||||
return
|
||||
end
|
||||
Inventory.SetSlots(slots)
|
||||
end
|
||||
|
||||
ReplicaController.ReplicaOfClassCreated("Inventory", function(replica)
|
||||
applySlots(replica.Data.Slots)
|
||||
local changeConn = replica:ListenToChange({"Slots"}, function(newSlots)
|
||||
applySlots(newSlots)
|
||||
end)
|
||||
replica:AddCleanupTask(changeConn)
|
||||
end)
|
||||
|
||||
inventoryRequest:FireServer()
|
||||
ReplicaController.RequestData()
|
||||
|
||||
UIS.InputBegan:Connect(function(input: InputObject, gameProcessedEvent: boolean)
|
||||
if gameProcessedEvent then
|
||||
@@ -52,9 +65,7 @@ UIS.InputBegan:Connect(function(input: InputObject, gameProcessedEvent: boolean)
|
||||
|
||||
local slot = keyToSlot[input.KeyCode]
|
||||
if slot then
|
||||
if Inventory.GetSlot(slot) ~= nil then
|
||||
Inventory.SetSelectedIndex(slot)
|
||||
end
|
||||
Inventory.SetSelectedIndex(slot)
|
||||
return
|
||||
end
|
||||
|
||||
@@ -87,7 +98,6 @@ UIS.InputChanged:Connect(function(input: InputObject, gameProcessedEvent: boolea
|
||||
return
|
||||
end
|
||||
local direction = delta > 0 and -1 or 1
|
||||
local nextIndex = findNextFilledIndex(Inventory.GetSelectedIndex(), direction)
|
||||
Inventory.SetSelectedIndex(nextIndex)
|
||||
Inventory.SetSelectedIndex(((Inventory.GetSelectedIndex() - 1 + direction) % Inventory.GetHotbarSize()) + 1)
|
||||
end
|
||||
end)
|
||||
|
||||
23
wally.lock
Normal file
23
wally.lock
Normal file
@@ -0,0 +1,23 @@
|
||||
# This file is automatically @generated by Wally.
|
||||
# It is not intended for manual editing.
|
||||
registry = "test"
|
||||
|
||||
[[package]]
|
||||
name = "etheroit/replicacontroller"
|
||||
version = "1.0.0"
|
||||
dependencies = []
|
||||
|
||||
[[package]]
|
||||
name = "etheroit/replicaservice"
|
||||
version = "1.0.2"
|
||||
dependencies = []
|
||||
|
||||
[[package]]
|
||||
name = "ocbwoy3/minecraft-roblox"
|
||||
version = "0.1.0"
|
||||
dependencies = [["ReplicaController", "etheroit/replicacontroller@1.0.0"], ["Roact", "roblox/roact@1.4.4"], ["ReplicaService", "etheroit/replicaservice@1.0.2"]]
|
||||
|
||||
[[package]]
|
||||
name = "roblox/roact"
|
||||
version = "1.4.4"
|
||||
dependencies = []
|
||||
15
wally.toml
Normal file
15
wally.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "ocbwoy3-development-studios/minecraft-roblox"
|
||||
description = "A Roblox game inspired by Minecraft's world building system"
|
||||
version = "0.1.0"
|
||||
registry = "https://github.com/UpliftGames/wally-index"
|
||||
realm = "shared"
|
||||
authors = ["ocbwoy3 <ocbwoy3@ocbwoy3.dev>"]
|
||||
private = true
|
||||
|
||||
[dependencies]
|
||||
Roact = "roblox/roact@1.4.4"
|
||||
ReplicaController = "etheroit/replicacontroller@1.0.0"
|
||||
|
||||
[server-dependencies]
|
||||
ReplicaService = "etheroit/replicaservice@1.0.2"
|
||||
Reference in New Issue
Block a user