275 lines
7.8 KiB
Lua
275 lines
7.8 KiB
Lua
--!native
|
|
--!optimize 2
|
|
|
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|
local ServerStorage = game:GetService("ServerStorage")
|
|
local ClientStateService = require(script.Parent.ClientState)
|
|
|
|
local Shared = ReplicatedStorage:WaitForChild("Shared")
|
|
local ModsFolder = ReplicatedStorage:WaitForChild("Mods")
|
|
local BlocksFolderRS = ReplicatedStorage:FindFirstChild("Blocks") or Instance.new("Folder")
|
|
BlocksFolderRS.Name = "Blocks"
|
|
BlocksFolderRS.Parent = ReplicatedStorage
|
|
local BlocksFolderSS = ServerStorage:FindFirstChild("Blocks") or Instance.new("Folder")
|
|
BlocksFolderSS.Name = "Blocks"
|
|
BlocksFolderSS.Parent = ServerStorage
|
|
|
|
local Util = require(Shared.Util)
|
|
local TG = require(script.TerrainGen)
|
|
local Players = game:GetService("Players")
|
|
|
|
local blockIdMap = {}
|
|
local rebuildBlockIdMap
|
|
|
|
local function syncBlocksToServerStorage()
|
|
BlocksFolderSS:ClearAllChildren()
|
|
for _, child in ipairs(BlocksFolderRS:GetChildren()) do
|
|
child:Clone().Parent = BlocksFolderSS
|
|
end
|
|
ClientStateService:SetBlocksFolder(BlocksFolderSS)
|
|
if rebuildBlockIdMap then
|
|
rebuildBlockIdMap()
|
|
end
|
|
end
|
|
|
|
BlocksFolderRS.ChildAdded:Connect(syncBlocksToServerStorage)
|
|
BlocksFolderRS.ChildRemoved:Connect(syncBlocksToServerStorage)
|
|
|
|
do
|
|
local workspaceModFolder = game:GetService("Workspace"):WaitForChild("mods")
|
|
|
|
for _,v in pairs(workspaceModFolder:GetChildren()) do
|
|
v.Parent = ModsFolder
|
|
end
|
|
workspaceModFolder:Destroy()
|
|
end
|
|
|
|
local ML = require(Shared.ModLoader)
|
|
ML.loadModsS()
|
|
syncBlocksToServerStorage()
|
|
ClientStateService:Init()
|
|
|
|
do
|
|
local bv = Instance.new("BoolValue")
|
|
bv.Name = "MLLoaded"
|
|
bv.Value = true
|
|
bv.Parent = ReplicatedStorage:WaitForChild("Objects")
|
|
end
|
|
|
|
local MAX_CHUNK_DIST = 200
|
|
|
|
local FakeChunk = TG:GetFakeChunk(-5,-5,-5)
|
|
|
|
task.synchronize()
|
|
|
|
ReplicatedStorage.Tick.OnServerEvent:Connect(function(player: Player, v: string)
|
|
if TG.ServerChunkCache[v] then
|
|
pcall(function()
|
|
TG.ServerChunkCache[v].inhabitedTime = tick()
|
|
end)
|
|
end
|
|
end)
|
|
|
|
ReplicatedStorage.RecieveChunkPacket.OnServerInvoke = function(plr: Player, x: number, y: number, z: number)
|
|
-- validate xyz type and limit
|
|
if typeof(x) ~= "number" or typeof(y) ~= "number" or typeof(z) ~= "number" then
|
|
return {}
|
|
end
|
|
|
|
if math.abs(x) > MAX_CHUNK_DIST or math.abs(y) > MAX_CHUNK_DIST or math.abs(z) > MAX_CHUNK_DIST then
|
|
return FakeChunk.data
|
|
end
|
|
|
|
task.desynchronize()
|
|
local chunk = TG:GetChunk(x, y, z)
|
|
local chunkdata = chunk.data
|
|
task.synchronize()
|
|
|
|
return chunkdata
|
|
|
|
end
|
|
|
|
local tickRemote = ReplicatedStorage.Tick
|
|
local remotes = ReplicatedStorage:WaitForChild("Remotes")
|
|
local placeRemote = remotes:WaitForChild("PlaceBlock")
|
|
local breakRemote = remotes:WaitForChild("BreakBlock")
|
|
local blocksFolder = BlocksFolderSS
|
|
local function propogate(a, cx, cy, cz, x, y, z, bd)
|
|
task.synchronize()
|
|
tickRemote:FireAllClients(a, cx, cy, cz, x, y, z, bd)
|
|
task.desynchronize()
|
|
end
|
|
|
|
local MAX_REACH = 512
|
|
|
|
rebuildBlockIdMap = function()
|
|
table.clear(blockIdMap)
|
|
for _, block in ipairs(blocksFolder:GetChildren()) do
|
|
local id = block:GetAttribute("n")
|
|
if id ~= nil then
|
|
blockIdMap[id] = id
|
|
blockIdMap[tostring(id)] = id
|
|
end
|
|
end
|
|
end
|
|
|
|
rebuildBlockIdMap()
|
|
blocksFolder.ChildAdded:Connect(rebuildBlockIdMap)
|
|
blocksFolder.ChildRemoved:Connect(rebuildBlockIdMap)
|
|
|
|
local function getPlayerPosition(player: Player): Vector3?
|
|
local character = player.Character
|
|
if not character then
|
|
return nil
|
|
end
|
|
local root = character:FindFirstChild("HumanoidRootPart")
|
|
if not root then
|
|
return nil
|
|
end
|
|
return root.Position
|
|
end
|
|
|
|
local function isWithinReach(player: Player, cx: number, cy: number, cz: number, x: number, y: number, z: number): boolean
|
|
-- Relaxed reach; always true unless you want to re-enable limits
|
|
return true
|
|
end
|
|
|
|
local function resolveBlockId(blockId: any): string | number | nil
|
|
return blockIdMap[blockId]
|
|
end
|
|
|
|
local function playerCanUseBlock(player: Player, resolvedId: any): boolean
|
|
if not ClientStateService:HasInInventory(player, resolvedId) then
|
|
return false
|
|
end
|
|
local selected = ClientStateService:GetSelectedBlockId(player)
|
|
if not selected then
|
|
return false
|
|
end
|
|
return tostring(selected) == tostring(resolvedId)
|
|
end
|
|
|
|
local function getServerChunk(cx: number, cy: number, cz: number)
|
|
task.desynchronize()
|
|
local chunk = TG:GetChunk(cx, cy, cz)
|
|
task.synchronize()
|
|
return chunk
|
|
end
|
|
|
|
-- local PLAYER_BOX_SIZE = Vector3.new(3, 6, 3)
|
|
|
|
local function isBlockInsidePlayer(blockPos: Vector3): boolean
|
|
for _, player in ipairs(Players:GetPlayers()) do
|
|
local character = player.Character
|
|
if character then
|
|
local cf, size = character:GetBoundingBox()
|
|
local localPos = cf:PointToObjectSpace(blockPos)
|
|
if math.abs(localPos.X) <= size.X * 0.5
|
|
and math.abs(localPos.Y) <= size.Y * 0.5
|
|
and math.abs(localPos.Z) <= size.Z * 0.5 then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local DEBUG_PLACEMENT = false
|
|
local function debugPlacementLog(...: any)
|
|
if DEBUG_PLACEMENT then
|
|
Util.StudioLog(...)
|
|
end
|
|
end
|
|
|
|
local function debugPlacementWarn(...: any)
|
|
if DEBUG_PLACEMENT then
|
|
Util.StudioWarn(...)
|
|
end
|
|
end
|
|
|
|
placeRemote.OnServerEvent:Connect(function(player, cx, cy, cz, x, y, z, blockId)
|
|
local function reject(reason: string)
|
|
debugPlacementWarn("[PLACE][REJECT]", player.Name, reason, "chunk", cx, cy, cz, "block", x, y, z, "id", blockId)
|
|
return
|
|
end
|
|
|
|
if typeof(cx) ~= "number" or typeof(cy) ~= "number" or typeof(cz) ~= "number" then
|
|
return reject("chunk types")
|
|
end
|
|
if typeof(x) ~= "number" or typeof(y) ~= "number" or typeof(z) ~= "number" then
|
|
return reject("block types")
|
|
end
|
|
if x < 1 or x > 8 or y < 1 or y > 8 or z < 1 or z > 8 then
|
|
return reject("block bounds")
|
|
end
|
|
if math.abs(cx) > MAX_CHUNK_DIST or math.abs(cy) > MAX_CHUNK_DIST or math.abs(cz) > MAX_CHUNK_DIST then
|
|
return reject("chunk bounds")
|
|
end
|
|
if not isWithinReach(player, cx, cy, cz, x, y, z) then
|
|
return reject("out of reach")
|
|
end
|
|
local resolvedId = resolveBlockId(blockId)
|
|
if not resolvedId then
|
|
return reject("invalid id")
|
|
end
|
|
if not playerCanUseBlock(player, resolvedId) then
|
|
return reject("not in inventory/hotbar")
|
|
end
|
|
|
|
local blockPos = Util.ChunkPosToCFrame(Vector3.new(cx, cy, cz), Vector3.new(x, y, z)).Position
|
|
if isBlockInsidePlayer(blockPos) then
|
|
return reject("inside player")
|
|
end
|
|
|
|
local chunk = getServerChunk(cx, cy, cz)
|
|
local existing = chunk:GetBlockAt(x, y, z)
|
|
if existing and existing.id and existing.id ~= 0 then
|
|
if existing.id == resolvedId then
|
|
-- same block already there; treat as success without changes
|
|
debugPlacementLog("[PLACE][OK][NOOP]", player.Name, "chunk", cx, cy, cz, "block", x, y, z, "id", resolvedId)
|
|
return
|
|
end
|
|
-- allow replacement when different id: remove then place
|
|
chunk:RemoveBlock(x, y, z)
|
|
end
|
|
local data = {
|
|
id = resolvedId,
|
|
state = {}
|
|
}
|
|
chunk:CreateBlock(x, y, z, data)
|
|
propogate("B_C", cx, cy, cz, x, y, z, data)
|
|
debugPlacementLog("[PLACE][OK]", player.Name, "chunk", cx, cy, cz, "block", x, y, z, "id", resolvedId)
|
|
end)
|
|
|
|
breakRemote.OnServerEvent:Connect(function(player, cx, cy, cz, x, y, z)
|
|
if typeof(cx) ~= "number" or typeof(cy) ~= "number" or typeof(cz) ~= "number" then
|
|
return
|
|
end
|
|
if typeof(x) ~= "number" or typeof(y) ~= "number" or typeof(z) ~= "number" then
|
|
return
|
|
end
|
|
if x < 1 or x > 8 or y < 1 or y > 8 or z < 1 or z > 8 then
|
|
return
|
|
end
|
|
if math.abs(cx) > MAX_CHUNK_DIST or math.abs(cy) > MAX_CHUNK_DIST or math.abs(cz) > MAX_CHUNK_DIST then
|
|
return
|
|
end
|
|
if not isWithinReach(player, cx, cy, cz, x, y, z) then
|
|
return
|
|
end
|
|
|
|
local chunk = getServerChunk(cx, cy, cz)
|
|
if not chunk:GetBlockAt(x, y, z) then
|
|
task.synchronize()
|
|
tickRemote:FireClient(player, "C_R", cx, cy, cz, 0, 0, 0, 0)
|
|
task.desynchronize()
|
|
debugPlacementLog("[BREAK][NOOP]", player.Name, "chunk", cx, cy, cz, "block", x, y, z)
|
|
return
|
|
end
|
|
chunk:RemoveBlock(x, y, z)
|
|
propogate("B_D", cx, cy, cz, x, y, z, 0)
|
|
debugPlacementLog("[BREAK][OK]", player.Name, "chunk", cx, cy, cz, "block", x, y, z)
|
|
end)
|
|
|
|
task.desynchronize()
|