codex: stuff

This commit is contained in:
2026-01-06 21:35:36 +02:00
parent 7969565cca
commit b3da32e27f
11 changed files with 778 additions and 387 deletions

2
.gitignore vendored
View File

@@ -1 +1,3 @@
place.rbxl.lock place.rbxl.lock
Packages/
ServerPackages/

View File

@@ -5,11 +5,19 @@
"ReplicatedStorage": { "ReplicatedStorage": {
"$className": "ReplicatedStorage", "$className": "ReplicatedStorage",
"$ignoreUnknownInstances": true, "$ignoreUnknownInstances": true,
"Packages": {
"$className": "Folder",
"$path": "Packages"
},
"$path": "src/ReplicatedStorage" "$path": "src/ReplicatedStorage"
}, },
"ServerScriptService": { "ServerScriptService": {
"$className": "ServerScriptService", "$className": "ServerScriptService",
"$ignoreUnknownInstances": true, "$ignoreUnknownInstances": true,
"ServerPackages": {
"$className": "Folder",
"$path": "ServerPackages"
},
"$path": "src/ServerScriptService" "$path": "src/ServerScriptService"
}, },
"StarterGui": { "StarterGui": {

View File

@@ -1,6 +1,7 @@
local ChunkManager = {} local ChunkManager = {}
local RunService = game:GetService("RunService") local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local Chunk = require("./ChunkManager/Chunk") local Chunk = require("./ChunkManager/Chunk")
local BlockManager = require("./ChunkManager/BlockManager") local BlockManager = require("./ChunkManager/BlockManager")
@@ -15,10 +16,10 @@ ChunkFolder.Name = "$blockscraft_client"
ChunkManager.ChunkFolder = ChunkFolder ChunkManager.ChunkFolder = ChunkFolder
local CHUNK_RADIUS = 5 local CHUNK_RADIUS = 5
local INITIAL_SYNC_RADIUS = 2
local LOAD_BATCH = 8 local LOAD_BATCH = 8
local FORCELOAD_CHUNKS = { local CHUNK_WORLD_SIZE = 32 -- 8 blocks * 4 studs
{0, 1, 0} local FORCELOAD_CHUNKS = {}
}
local unloadingChunks = {} local unloadingChunks = {}
local pendingChunkRequests = {} local pendingChunkRequests = {}
@@ -37,6 +38,15 @@ do
end) end)
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) local function Swait(l)
task.synchronize() task.synchronize()
for _ = 1, l do for _ = 1, l do
@@ -139,11 +149,7 @@ function ChunkManager:Tick()
end end
local pos = player.Character:GetPivot().Position local pos = player.Character:GetPivot().Position
local chunkPos = { local chunkPos = worldToChunkCoords(pos)
x = math.round(pos.X / 32),
y = math.round(pos.Y / 32),
z = math.round(pos.Z / 32)
}
task.defer(function() task.defer(function()
local processed = 0 local processed = 0
@@ -207,6 +213,32 @@ function ChunkManager:Init()
ChunkFolder.Parent = game:GetService("Workspace") ChunkFolder.Parent = game:GetService("Workspace")
ChunkManager:ForceTick() 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() task.defer(function()
while true do while true do
wait(2) wait(2)

View File

@@ -1,76 +1,137 @@
local PlacementManager = {} 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 ChunkManager = require("./ChunkManager")
local Util = require("./Util") 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() local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {PlacementManager.ChunkFolder} raycastParams.FilterDescendantsInstances = { ChunkManager.ChunkFolder }
raycastParams.FilterType = Enum.RaycastFilterType.Include raycastParams.FilterType = Enum.RaycastFilterType.Include
raycastParams.IgnoreWater = true raycastParams.IgnoreWater = true
if _G.SB then return nil end local selectionBox = Instance.new("SelectionBox")
_G.SB = true 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() local function normalToId(normal: Vector3): Enum.NormalId
PlacementManager.SelectionBox.Name = "$SelectionBox"..(game:GetService("RunService"):IsServer() and "_SERVER" or "") local absX, absY, absZ = math.abs(normal.X), math.abs(normal.Y), math.abs(normal.Z)
PlacementManager.SelectionBox.Parent = game:GetService("Workspace"):FindFirstChildOfClass("Terrain") if absX > absY and absX > absZ then
return normal.X > 0 and Enum.NormalId.Right or Enum.NormalId.Left
-- Trash method TODO: Fix this elseif absY > absX and absY > absZ then
local function findParent(i: Instance): Instance return normal.Y > 0 and Enum.NormalId.Top or Enum.NormalId.Bottom
local f = i:FindFirstAncestorOfClass("Folder") else
local d = i return normal.Z > 0 and Enum.NormalId.Back or Enum.NormalId.Front
repeat end
d = d.Parent
until d.Parent == f
return d
end end
local Mouse: Mouse = nil local function findChunkAndBlock(hitInstance: Instance)
local lastNormalId: Enum.NormalId? = nil -- Find chunk container (child of ChunkFolder)
local inst = hitInstance
local function normalIdToOffset(normal: Enum.NormalId): Vector3 local chunkInstance = nil
if normal == Enum.NormalId.Top then while inst and inst.Parent do
return Vector3.new(0, 1, 0) if inst.Parent == ChunkManager.ChunkFolder then
elseif normal == Enum.NormalId.Bottom then chunkInstance = inst
return Vector3.new(0, -1, 0) break
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)
end end
return Vector3.new(0, 0, 0) 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 end
local function offsetChunkBlock(chunk: Vector3, block: Vector3, offset: Vector3) -- 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 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 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 if bx < 1 then
bx = 8 bx = CHUNK_SIZE
cx -= 1 cx -= 1
elseif bx > 8 then elseif bx > CHUNK_SIZE then
bx = 1 bx = 1
cx += 1 cx += 1
end end
if by < 1 then if by < 1 then
by = 8 by = CHUNK_SIZE
cy -= 1 cy -= 1
elseif by > 8 then elseif by > CHUNK_SIZE then
by = 1 by = 1
cy += 1 cy += 1
end end
if bz < 1 then if bz < 1 then
bz = 8 bz = CHUNK_SIZE
cz -= 1 cz -= 1
elseif bz > 8 then elseif bz > CHUNK_SIZE then
bz = 1 bz = 1
cz += 1 cz += 1
end end
@@ -78,141 +139,106 @@ local function offsetChunkBlock(chunk: Vector3, block: Vector3, offset: Vector3)
return Vector3.new(cx, cy, cz), Vector3.new(bx, by, bz) return Vector3.new(cx, cy, cz), Vector3.new(bx, by, bz)
end end
-- Gets the block and normalid of the block (and surface) the player is looking at local lastHit = nil
function PlacementManager:Raycast()
if not Mouse then local function castFromCamera()
Mouse = game:GetService("Players").LocalPlayer:GetMouse() if not LOCAL_PLAYER or not CAMERA then
end return nil
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
end end
--if not objLookingAt:IsDescendantOf(ChunkManager.ChunkFolder) then return end local mouse = LOCAL_PLAYER:GetMouse()
local parent = findParent(objLookingAt) if not mouse then
if parent:GetAttribute("ns") == true then return nil
PlacementManager.SelectionBox.Adornee = nil
script.RaycastResult.Value = nil
lastNormalId = nil
return
end
PlacementManager.SelectionBox.Adornee = parent
script.RaycastResult.Value = parent
lastNormalId = dir
return parent, dir
end end
function PlacementManager:RaycastGetResult() local unitRay = mouse.UnitRay
return script.RaycastResult.Value local result = Workspace:Raycast(unitRay.Origin, unitRay.Direction * MAX_RAY_DISTANCE, raycastParams)
if not result then
selectionBox.Adornee = nil
lastHit = nil
return nil
end end
local remotes = game:GetService("ReplicatedStorage"):WaitForChild("Remotes") local chunkCoords, blockCoords, blockInstance = findChunkAndBlock(result.Instance)
local placeRemote = remotes:WaitForChild("PlaceBlock") if not chunkCoords or not blockCoords then
local breakRemote = remotes:WaitForChild("BreakBlock") selectionBox.Adornee = nil
local tickRemote = game:GetService("ReplicatedStorage").Tick 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
-- Public API
function PlacementManager:GetBlockAtMouse()
local hit = castFromCamera()
if not hit then
return nil
end
return {
chunk = hit.chunk,
block = hit.block,
}
end
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) 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) placeRemote:FireServer(cx, cy, cz, x, y, z, blockId)
end end
-- FIRES REMOTE
function PlacementManager:BreakBlock(cx, cy, cz, x, y, z) 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) breakRemote:FireServer(cx, cy, cz, x, y, z)
end end
-- CLIENTSIDED
function PlacementManager:PlaceBlockLocal(cx, cy, cz, x, y, z, blockData) function PlacementManager:PlaceBlockLocal(cx, cy, cz, x, y, z, blockData)
local chunk = ChunkManager:GetChunk(cx, cy, cz) local chunk = ChunkManager:GetChunk(cx, cy, cz)
chunk:CreateBlock(x, y, z, blockData) chunk:CreateBlock(x, y, z, blockData)
end end
-- CLIENTSIDED
function PlacementManager:BreakBlockLocal(cx, cy, cz, x, y, z) function PlacementManager:BreakBlockLocal(cx, cy, cz, x, y, z)
local chunk = ChunkManager:GetChunk(cx, cy, cz) local chunk = ChunkManager:GetChunk(cx, cy, cz)
chunk:RemoveBlock(x, y, z) chunk:RemoveBlock(x, y, z)
end 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() function PlacementManager:Init()
game:GetService("RunService").Heartbeat:Connect(function() RunService.Heartbeat:Connect(function()
local a,b = pcall(function() local ok = pcall(castFromCamera)
PlacementManager:Raycast() if not ok then
end) selectionBox.Adornee = nil
if not a then lastHit = nil
task.synchronize()
PlacementManager.SelectionBox.Adornee = nil
script.RaycastResult.Value = nil
task.desynchronize()
end end
end) end)
tickRemote.OnClientEvent:Connect(function(m, cx, cy, cz, x, y, z, d) 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 if m == "B_C" then
PlacementManager:PlaceBlockLocal(cx, cy, cz, x, y, z, d) PlacementManager:PlaceBlockLocal(cx, cy, cz, x, y, z, d)
end elseif m == "B_D" then
if m == "B_D" then
PlacementManager:BreakBlockLocal(cx, cy, cz, x, y, z) PlacementManager:BreakBlockLocal(cx, cy, cz, x, y, z)
end end
end) end)

View File

@@ -3,16 +3,62 @@ print("Hello world!")
task.synchronize() task.synchronize()
local ReplicatedStorage = game:GetService("ReplicatedStorage") 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 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 Util = require(Shared.Util)
local TG = require("./ServerChunkManager/TerrainGen") 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 do
local workspaceModFolder = game:GetService("Workspace"):WaitForChild("mods") local workspaceModFolder = Workspace:FindFirstChild("mods") or getOrCreateFolder(Workspace, "mods")
for _,v in pairs(workspaceModFolder:GetChildren()) do for _,v in pairs(workspaceModFolder:GetChildren()) do
v.Parent = ModsFolder v.Parent = ModsFolder
@@ -23,12 +69,7 @@ end
local ML = require(Shared.ModLoader) local ML = require(Shared.ModLoader)
ML.loadModsS() ML.loadModsS()
do mlLoadedFlag.Value = true
local bv = Instance.new("BoolValue")
bv.Name = "MLLoaded"
bv.Value = true
bv.Parent = ReplicatedStorage:WaitForChild("Objects")
end
local MAX_CHUNK_DIST = 200 local MAX_CHUNK_DIST = 200
@@ -67,8 +108,6 @@ local tickRemote = ReplicatedStorage.Tick
local remotes = ReplicatedStorage:WaitForChild("Remotes") local remotes = ReplicatedStorage:WaitForChild("Remotes")
local placeRemote = remotes:WaitForChild("PlaceBlock") local placeRemote = remotes:WaitForChild("PlaceBlock")
local breakRemote = remotes:WaitForChild("BreakBlock") local breakRemote = remotes:WaitForChild("BreakBlock")
local inventorySync = remotes:WaitForChild("InventorySync")
local inventoryRequest = remotes:WaitForChild("InventoryRequest")
local blocksFolder = ReplicatedStorage:WaitForChild("Blocks") local blocksFolder = ReplicatedStorage:WaitForChild("Blocks")
local function propogate(a, cx, cy, cz, x, y, z, bd) local function propogate(a, cx, cy, cz, x, y, z, bd)
task.synchronize() task.synchronize()
@@ -76,10 +115,11 @@ local function propogate(a, cx, cy, cz, x, y, z, bd)
task.desynchronize() task.desynchronize()
end end
local MAX_REACH = 24 local MAX_REACH = math.huge
local HOTBAR_SIZE = 9 local HOTBAR_SIZE = 9
local blockIdMap = {} local blockIdMap = {}
local playerInventories = {} local playerInventories = {}
local playerReplicas = {}
local function rebuildBlockIdMap() local function rebuildBlockIdMap()
table.clear(blockIdMap) table.clear(blockIdMap)
@@ -109,7 +149,11 @@ local function buildDefaultSlots(): {string}
end) end)
local slots = table.create(HOTBAR_SIZE, "") local slots = table.create(HOTBAR_SIZE, "")
for i = 1, HOTBAR_SIZE do 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 end
return slots return slots
end end
@@ -119,8 +163,37 @@ local function syncInventory(player: Player)
if not data then if not data then
return return
end end
task.synchronize() if type(data.slots) ~= "table" then
inventorySync:FireClient(player, data.slots) 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 end
local function rebuildAllInventories() local function rebuildAllInventories()
@@ -128,7 +201,7 @@ local function rebuildAllInventories()
local slots = buildDefaultSlots() local slots = buildDefaultSlots()
local allowed = {} local allowed = {}
for i = 1, HOTBAR_SIZE do for i = 1, HOTBAR_SIZE do
local id = slots[i] local id = slots[i] or ""
if id ~= "" then if id ~= "" then
allowed[id] = true allowed[id] = true
end end
@@ -148,7 +221,7 @@ game:GetService("Players").PlayerAdded:Connect(function(player: Player)
local slots = buildDefaultSlots() local slots = buildDefaultSlots()
local allowed = {} local allowed = {}
for i = 1, HOTBAR_SIZE do for i = 1, HOTBAR_SIZE do
local id = slots[i] local id = slots[i] or ""
if id ~= "" then if id ~= "" then
allowed[id] = true allowed[id] = true
end end
@@ -162,13 +235,18 @@ end)
game:GetService("Players").PlayerRemoving:Connect(function(player: Player) game:GetService("Players").PlayerRemoving:Connect(function(player: Player)
playerInventories[player.UserId] = nil playerInventories[player.UserId] = nil
local replica = playerReplicas[player]
if replica then
replica:Destroy()
playerReplicas[player] = nil
end
end) end)
for _, player in ipairs(game:GetService("Players"):GetPlayers()) do for _, player in ipairs(game:GetService("Players"):GetPlayers()) do
local slots = buildDefaultSlots() local slots = buildDefaultSlots()
local allowed = {} local allowed = {}
for i = 1, HOTBAR_SIZE do for i = 1, HOTBAR_SIZE do
local id = slots[i] local id = slots[i] or ""
if id ~= "" then if id ~= "" then
allowed[id] = true allowed[id] = true
end end
@@ -180,10 +258,6 @@ for _, player in ipairs(game:GetService("Players"):GetPlayers()) do
syncInventory(player) syncInventory(player)
end end
inventoryRequest.OnServerEvent:Connect(function(player: Player)
syncInventory(player)
end)
local function getPlayerPosition(player: Player): Vector3? local function getPlayerPosition(player: Player): Vector3?
local character = player.Character local character = player.Character
if not character then if not character then
@@ -197,12 +271,8 @@ local function getPlayerPosition(player: Player): Vector3?
end end
local function isWithinReach(player: Player, cx: number, cy: number, cz: number, x: number, y: number, z: number): boolean local function isWithinReach(player: Player, cx: number, cy: number, cz: number, x: number, y: number, z: number): boolean
local playerPos = getPlayerPosition(player) -- Creative: disable reach limits.
if not playerPos then return true
return false
end
local blockPos = Util.ChunkPosToCFrame(Vector3.new(cx, cy, cz), Vector3.new(x, y, z)).Position
return (blockPos - playerPos).Magnitude <= MAX_REACH
end end
local function resolveBlockId(blockId: any): string | number | nil local function resolveBlockId(blockId: any): string | number | nil
@@ -210,11 +280,8 @@ local function resolveBlockId(blockId: any): string | number | nil
end end
local function playerHasBlockId(player: Player, blockId: string | number): boolean local function playerHasBlockId(player: Player, blockId: string | number): boolean
local data = playerInventories[player.UserId] -- Creative mode: allow all block ids.
if not data then return true
return false
end
return data.allowed[tostring(blockId)] == true
end end
local function getServerChunk(cx: number, cy: number, cz: number) local function getServerChunk(cx: number, cy: number, cz: number)

View File

@@ -7,7 +7,14 @@ end
local ReplicatedStorage = game:GetService("ReplicatedStorage") local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UIS = game:GetService("UserInputService") 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 game:GetService("Players").LocalPlayer.CameraMode = Enum.CameraMode.LockFirstPerson
UIS.MouseIconEnabled = false UIS.MouseIconEnabled = false

View File

@@ -5,54 +5,48 @@ end
local ui = script.Parent local ui = script.Parent
local ReplicatedStorage = game:GetService("ReplicatedStorage") 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 Inventory = require(ReplicatedStorage.Shared.Inventory)
local Catppuccin = require(ReplicatedStorage.Shared.Catppuccin) local Catppuccin = require(ReplicatedStorage.Shared.Catppuccin)
local blocksFolder = ReplicatedStorage:WaitForChild("Blocks") local blocksFolder = ReplicatedStorage:WaitForChild("Blocks")
local mocha = Catppuccin.mocha 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 cd = ReplicatedStorage.Objects.ChunkDebug:Clone()
local sky = ReplicatedStorage.Objects.Sky:Clone() local sky = ReplicatedStorage.Objects.Sky:Clone()
local base = ReplicatedStorage.Objects.FakeBaseplate:Clone() local base = ReplicatedStorage.Objects.FakeBaseplate:Clone()
cd.Parent = game:GetService("Workspace"):FindFirstChildOfClass("Terrain") cd.Parent = game:GetService("Workspace"):FindFirstChildOfClass("Terrain")
sky.Parent = game:GetService("Workspace"):FindFirstChildOfClass("Terrain") sky.Parent = game:GetService("Workspace"):FindFirstChildOfClass("Terrain")
base.Parent = game:GetService("Workspace"):FindFirstChildOfClass("Terrain") base.Parent = game:GetService("Workspace"):FindFirstChildOfClass("Terrain")
local hotbarRoot = ui:WaitForChild("Hotbar") local debugUpperText = ui:FindFirstChild("DebugUpperText")
local hotbarSlotsRoot = hotbarRoot:WaitForChild("Frame") if debugUpperText then
local hotbarSelectLabel = ui:WaitForChild("HotbarItemSelectLabel") debugUpperText:Destroy()
local hotbarSelectText = hotbarSelectLabel:WaitForChild("TextLabel") end
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")
hotbarRoot.BackgroundColor3 = mocha.base local function destroyLegacyUi(name)
if hotbarStroke then local inst = ui:FindFirstChild(name)
hotbarStroke.Color = mocha.blue if inst then
inst:Destroy()
end end
hotbarSelectLabel.BackgroundColor3 = mocha.base
if hotbarSelectStroke then
hotbarSelectStroke.Color = mocha.blue
end 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 = {} destroyLegacyUi("Hotbar")
local slotStrokes = {} destroyLegacyUi("HotbarItemSelectLabel")
local slotLabels = {} destroyLegacyUi("HotbarDebug")
local renderHotbar = nil
local blockDisplayNames = {} local blockDisplayNames = {}
local blockIcons = {} local blockIcons = {}
@@ -63,130 +57,96 @@ local function rebuildBlockMappings()
for _, block in ipairs(blocksFolder:GetChildren()) do for _, block in ipairs(blocksFolder:GetChildren()) do
local id = block:GetAttribute("n") local id = block:GetAttribute("n")
if id ~= nil then if id ~= nil then
blockDisplayNames[tostring(id)] = block.Name local idKey = tostring(id)
blockDisplayNames[idKey] = block.Name
local icon = block:GetAttribute("icon") local icon = block:GetAttribute("icon")
if typeof(icon) == "string" and icon ~= "" then if typeof(icon) == "string" and icon ~= "" then
blockIcons[tostring(id)] = icon blockIcons[idKey] = icon
end end
end end
end end
if renderHotbar then
renderHotbar()
end end
local function getDisplayName(id)
if id == nil then
return nil
end
return blockDisplayNames[tostring(id)] or tostring(id)
end
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
local HotbarApp = Roact.Component:extend("HotbarApp")
function HotbarApp:init()
self.state = {
slots = table.clone(Inventory.GetSlots()),
selectedIndex = Inventory.GetSelectedIndex(),
selectedName = getDisplayName(Inventory.GetSelectedId()) or "empty",
positionDebug = "",
inventoryDebug = "",
}
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 end
rebuildBlockMappings() rebuildBlockMappings()
blocksFolder.ChildAdded:Connect(rebuildBlockMappings) update()
blocksFolder.ChildRemoved:Connect(rebuildBlockMappings)
for i = 1, Inventory.GetHotbarSize() do self._changedConn = Inventory.OnChanged(update)
local slotName = `HotbarSlot{i - 1}` self._selectedConn = Inventory.OnSelected(update)
local slot = hotbarSlotsRoot:WaitForChild(slotName) self._blockAdded = blocksFolder.ChildAdded:Connect(function()
slotFrames[i] = slot rebuildBlockMappings()
local stroke = slot:FindFirstChild("UIStroke") update()
if not stroke then end)
stroke = Instance.new("UIStroke") self._blockRemoved = blocksFolder.ChildRemoved:Connect(function()
stroke.Parent = slot rebuildBlockMappings()
end update()
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 ~= ""
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"}`
end
end
local extraSlot = hotbarSlotsRoot and hotbarSlotsRoot:FindFirstChild("HotbarSlot9")
if extraSlot then
extraSlot.Visible = false
end
renderHotbar()
updateSelectedLabel()
Inventory.OnChanged(function()
renderHotbar()
updateSelectedLabel()
end) end)
Inventory.OnSelected(function() self._renderConn = RunService.RenderStepped:Connect(function(dt)
renderHotbar()
updateSelectedLabel()
end)
game:GetService("RunService").RenderStepped:Connect(function(dt)
local fps = math.round(1 / dt) local fps = math.round(1 / dt)
pcall(function() pcall(function()
-- pos in chunks of 32 studs of char local pos = Players.LocalPlayer.Character:GetPivot()
local pos = game:GetService("Players").LocalPlayer.Character:GetPivot()
local chunk = { local chunk = {
x = math.round(pos.X / 32), x = math.round(pos.X / 32),
y = math.round(pos.Y / 32), y = math.round(pos.Y / 32),
@@ -208,8 +168,6 @@ game:GetService("RunService").RenderStepped:Connect(function(dt)
if math.abs(bpos.z) == 0 then bpos.z = 0 end if math.abs(bpos.z) == 0 then bpos.z = 0 end
sky.CFrame = pos 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( cd:PivotTo(CFrame.new(
chunk.x * 32, chunk.x * 32,
chunk.y * 32, chunk.y * 32,
@@ -222,5 +180,241 @@ game:GetService("RunService").RenderStepped:Connect(function(dt)
chunk.z * 32 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) 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

View File

@@ -9,7 +9,14 @@ end)
local ReplicatedStorage = game:GetService("ReplicatedStorage") 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")) local ML = require(ReplicatedStorage:WaitForChild("Shared"):WaitForChild("ModLoader"))

View File

@@ -5,13 +5,18 @@ end
local ReplicatedStorage = game:GetService("ReplicatedStorage") local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UIS = game:GetService("UserInputService") 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 PM = require(ReplicatedStorage.Shared.PlacementManager)
local Inventory = require(ReplicatedStorage.Shared.Inventory) local Inventory = require(ReplicatedStorage.Shared.Inventory)
local remotes = ReplicatedStorage:WaitForChild("Remotes") local ReplicaController = require(ReplicatedStorage:WaitForChild("Packages"):WaitForChild("ReplicaController"))
local inventorySync = remotes:WaitForChild("InventorySync")
local inventoryRequest = remotes:WaitForChild("InventoryRequest")
local keyToSlot = { local keyToSlot = {
[Enum.KeyCode.One] = 1, [Enum.KeyCode.One] = 1,
@@ -36,14 +41,22 @@ local function findNextFilledIndex(startIndex: number, direction: number): numbe
return startIndex return startIndex
end end
inventorySync.OnClientEvent:Connect(function(slots) local function applySlots(slots)
if typeof(slots) ~= "table" then if typeof(slots) ~= "table" then
return return
end end
Inventory.SetSlots(slots) 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) end)
inventoryRequest:FireServer() ReplicaController.RequestData()
UIS.InputBegan:Connect(function(input: InputObject, gameProcessedEvent: boolean) UIS.InputBegan:Connect(function(input: InputObject, gameProcessedEvent: boolean)
if gameProcessedEvent then if gameProcessedEvent then
@@ -52,9 +65,7 @@ UIS.InputBegan:Connect(function(input: InputObject, gameProcessedEvent: boolean)
local slot = keyToSlot[input.KeyCode] local slot = keyToSlot[input.KeyCode]
if slot then if slot then
if Inventory.GetSlot(slot) ~= nil then
Inventory.SetSelectedIndex(slot) Inventory.SetSelectedIndex(slot)
end
return return
end end
@@ -87,7 +98,6 @@ UIS.InputChanged:Connect(function(input: InputObject, gameProcessedEvent: boolea
return return
end end
local direction = delta > 0 and -1 or 1 local direction = delta > 0 and -1 or 1
local nextIndex = findNextFilledIndex(Inventory.GetSelectedIndex(), direction) Inventory.SetSelectedIndex(((Inventory.GetSelectedIndex() - 1 + direction) % Inventory.GetHotbarSize()) + 1)
Inventory.SetSelectedIndex(nextIndex)
end end
end) end)

23
wally.lock Normal file
View 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
View 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"