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

4
.gitignore vendored
View File

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

View File

@@ -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"
}
}
}
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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"))

View File

@@ -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
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"