diff --git a/src/ReplicatedStorage/Shared/ChunkManager/Chunk.lua b/src/ReplicatedStorage/Shared/ChunkManager/Chunk.lua index 8b019cc..bbe44f2 100644 --- a/src/ReplicatedStorage/Shared/ChunkManager/Chunk.lua +++ b/src/ReplicatedStorage/Shared/ChunkManager/Chunk.lua @@ -158,27 +158,13 @@ function Chunk:CreateBlock(x: number,y: number,z: number,d:BlockData) end function Chunk:RemoveBlock(x, y, z) - print("[DEBUG] Chunk:RemoveBlock called - Chunk:", self.pos, "Block coords:", x, y, z) local blockKey = keyFromCoords(x, y, z) - local existingBlock = self.data[blockKey] - if existingBlock then - print("[DEBUG] Removing existing block with ID:", existingBlock.id) - else - print("[DEBUG] No block found at coords", x, y, z) - end self.data[blockKey] = nil self:PropogateChanges(x,y,z,0) end function Chunk:RemoveBlockSmooth(x, y, z) - print("[DEBUG] Chunk:RemoveBlockSmooth called - Chunk:", self.pos, "Block coords:", x, y, z) local blockKey = keyFromCoords(x, y, z) - local existingBlock = self.data[blockKey] - if existingBlock then - print("[DEBUG] Smooth removing existing block with ID:", existingBlock.id) - else - print("[DEBUG] Smooth remove: no block found at coords", x, y, z) - end self.data[blockKey] = nil self.delayedRemoval[blockKey] = true self:PropogateChanges(x,y,z,0) diff --git a/src/ReplicatedStorage/Shared/PlacementManager.lua b/src/ReplicatedStorage/Shared/PlacementManager.lua index 1e7e710..965f02f 100644 --- a/src/ReplicatedStorage/Shared/PlacementManager.lua +++ b/src/ReplicatedStorage/Shared/PlacementManager.lua @@ -5,7 +5,6 @@ local PlacementManager = {} local ChunkManager = require("./ChunkManager") local Util = require("./Util") -local RunService = game:GetService("RunService") PlacementManager.ChunkFolder = ChunkManager.ChunkFolder @@ -33,7 +32,56 @@ end local Mouse: Mouse = nil local lastNormalId: Enum.NormalId? = nil -local pendingBreakResync = {} +local BREAK_ROLLBACK_TIMEOUT = 0.6 +local pendingBreaks = {} + +local function makeChunkKey(cx: number, cy: number, cz: number): string + return `{cx},{cy},{cz}` +end + +local function makeBlockKey(x: number, y: number, z: number): string + return `{x},{y},{z}` +end + +local function getPendingBreak(chunkKey: string, blockKey: string) + local chunkMap = pendingBreaks[chunkKey] + if not chunkMap then + return nil + end + return chunkMap[blockKey] +end + +local function clearPendingBreak(chunkKey: string, blockKey: string) + local chunkMap = pendingBreaks[chunkKey] + if not chunkMap then + return + end + chunkMap[blockKey] = nil + if not next(chunkMap) then + pendingBreaks[chunkKey] = nil + end +end + +local function clearPendingBreaksForChunk(chunkKey: string) + pendingBreaks[chunkKey] = nil +end + +local function scheduleBreakRollback(cx: number, cy: number, cz: number, x: number, y: number, z: number) + task.delay(BREAK_ROLLBACK_TIMEOUT, function() + local chunkKey = makeChunkKey(cx, cy, cz) + local blockKey = makeBlockKey(x, y, z) + local pending = getPendingBreak(chunkKey, blockKey) + if not pending then + return + end + clearPendingBreak(chunkKey, blockKey) + local chunk = ChunkManager:GetChunk(cx, cy, cz) + if pending.data and chunk then + chunk:CreateBlock(x, y, z, pending.data) + end + ChunkManager:RefreshChunk(cx, cy, cz) + end) +end local function normalIdToOffset(normal: Enum.NormalId): Vector3 if normal == Enum.NormalId.Top then @@ -83,6 +131,25 @@ local function offsetChunkBlock(chunk: Vector3, block: Vector3, offset: Vector3) return Vector3.new(cx, cy, cz), Vector3.new(bx, by, bz) end +local function getPlayerPosition(): Vector3? + local player = game:GetService("Players").LocalPlayer + local character = player and player.Character + if not character then + return nil + end + local root = character:FindFirstChild("HumanoidRootPart") + return root and root.Position or nil +end + +local function isWithinReach(cx: number, cy: number, cz: number, x: number, y: number, z: number): boolean + local playerPos = getPlayerPosition() + 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 <= 24 +end + -- Gets the block and normalid of the block (and surface) the player is looking at function PlacementManager:Raycast() if not Mouse then @@ -133,29 +200,38 @@ end -- FIRES REMOTE function PlacementManager:BreakBlock(cx, cy, cz, x, y, z) - print("[DEBUG] PlacementManager:BreakBlock called - Chunk:", cx, cy, cz, "Block:", x, y, z) - local chunk = ChunkManager:GetChunk(cx, cy, cz) - if chunk and not chunk:GetBlockAt(x, y, z) then - print("[DEBUG] Client missing block; resyncing nearby chunks") - ChunkManager:ResyncAroundChunk(cx, cy, cz, 1) - task.defer(function() - task.synchronize() - RunService.RenderStepped:Wait() - task.desynchronize() - local refreshed = ChunkManager:GetChunk(cx, cy, cz) - if refreshed and refreshed:GetBlockAt(x, y, z) then - task.synchronize() - breakRemote:FireServer(cx, cy, cz, x, y, z) - task.desynchronize() - print("[DEBUG] BreakBlock remote fired to server after resync") - end - end) + 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 not isWithinReach(cx, cy, cz, x, y, z) then + return + end + + local chunk = ChunkManager:GetChunk(cx, cy, cz) + local blockData = chunk and chunk:GetBlockAt(x, y, z) or nil + local chunkKey = makeChunkKey(cx, cy, cz) + local blockKey = makeBlockKey(x, y, z) + if getPendingBreak(chunkKey, blockKey) then + return + end + pendingBreaks[chunkKey] = pendingBreaks[chunkKey] or {} + pendingBreaks[chunkKey][blockKey] = { + data = blockData, + time = tick(), + } + if blockData then + chunk:RemoveBlock(x, y, z) + end + scheduleBreakRollback(cx, cy, cz, x, y, z) task.synchronize() breakRemote:FireServer(cx, cy, cz, x, y, z) task.desynchronize() - print("[DEBUG] BreakBlock remote fired to server") end -- CLIENTSIDED: only apply server-validated changes @@ -166,18 +242,17 @@ end -- CLIENTSIDED: only apply server-validated changes local function applyBreakBlockLocal(cx, cy, cz, x, y, z) - print("[DEBUG] PlacementManager:BreakBlockLocal called - Chunk:", cx, cy, cz, "Block:", x, y, z) local chunk = ChunkManager:GetChunk(cx, cy, cz) - if chunk then - print("[DEBUG] Found chunk, calling RemoveBlock") - if chunk.RemoveBlockSmooth then - chunk:RemoveBlockSmooth(x, y, z) - else - chunk:RemoveBlock(x, y, z) - end - else - print("[DEBUG] Chunk not found at coords:", cx, cy, cz) + if not chunk then + return end + local chunkKey = makeChunkKey(cx, cy, cz) + local blockKey = makeBlockKey(x, y, z) + if getPendingBreak(chunkKey, blockKey) then + clearPendingBreak(chunkKey, blockKey) + return + end + chunk:RemoveBlock(x, y, z) end function PlacementManager:GetBlockAtMouse(): nil | {chunk:Vector3, block: Vector3} @@ -250,19 +325,9 @@ function PlacementManager:Init() end if m == "B_D" then applyBreakBlockLocal(cx, cy, cz, x, y ,z) - local key = `{cx},{cy},{cz}` - if not pendingBreakResync[key] then - pendingBreakResync[key] = true - task.defer(function() - task.synchronize() - RunService.RenderStepped:Wait() - task.desynchronize() - pendingBreakResync[key] = nil - ChunkManager:ResyncAroundChunk(cx, cy, cz, 1) - end) - end end if m == "C_R" then + clearPendingBreaksForChunk(makeChunkKey(cx, cy, cz)) ChunkManager:RefreshChunk(cx, cy, cz) end end) diff --git a/src/ServerScriptService/Actor/PlayerScale.server.lua b/src/ServerScriptService/Actor/PlayerScale.server.lua new file mode 100644 index 0000000..ccec138 --- /dev/null +++ b/src/ServerScriptService/Actor/PlayerScale.server.lua @@ -0,0 +1,47 @@ +--!native +--!optimize 2 + +local Players = game:GetService("Players") + +local SCALE = 1.4 + +local function applyScale(character: Model) + if character.ScaleTo then + pcall(function() + character:ScaleTo(SCALE) + end) + return + end + + local humanoid = character:FindFirstChildOfClass("Humanoid") + if not humanoid then + return + end + + if humanoid.RigType == Enum.HumanoidRigType.R15 then + for _, name in ipairs({"BodyHeightScale", "BodyWidthScale", "BodyDepthScale", "HeadScale"}) do + local scaleValue = humanoid:FindFirstChild(name) + if scaleValue then + scaleValue.Value = SCALE + end + end + end +end + +local function onCharacterAdded(character: Model) + character:WaitForChild("Humanoid", 5) + applyScale(character) +end + +local function onPlayerAdded(player: Player) + player.CharacterAdded:Connect(onCharacterAdded) + if player.Character then + onCharacterAdded(player.Character) + end +end + +for _, player in ipairs(Players:GetPlayers()) do + onPlayerAdded(player) +end + +Players.PlayerAdded:Connect(onPlayerAdded) diff --git a/src/ServerScriptService/Actor/ServerChunkManager/init.server.lua b/src/ServerScriptService/Actor/ServerChunkManager/init.server.lua index 7216049..4f6617b 100644 --- a/src/ServerScriptService/Actor/ServerChunkManager/init.server.lua +++ b/src/ServerScriptService/Actor/ServerChunkManager/init.server.lua @@ -1,8 +1,6 @@ --!native --!optimize 2 -print("Hello world!") - task.synchronize() local ReplicatedStorage = game:GetService("ReplicatedStorage") @@ -13,6 +11,7 @@ local ModsFolder = ReplicatedStorage:WaitForChild("Mods") local Util = require(Shared.Util) local TG = require("./ServerChunkManager/TerrainGen") +local Players = game:GetService("Players") do local workspaceModFolder = game:GetService("Workspace"):WaitForChild("mods") @@ -127,6 +126,22 @@ local function getServerChunk(cx: number, cy: number, cz: number) return chunk end +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 + placeRemote.OnServerEvent:Connect(function(player, cx, cy, cz, x, y, z, blockId) --print("place",player, cx, cy, cz, x, y, z, blockData) @@ -150,6 +165,11 @@ placeRemote.OnServerEvent:Connect(function(player, cx, cy, cz, x, y, z, blockId) return end + local blockPos = Util.ChunkPosToCFrame(Vector3.new(cx, cy, cz), Vector3.new(x, y, z)).Position + if isBlockInsidePlayer(blockPos) then + return + end + local chunk = getServerChunk(cx, cy, cz) if chunk:GetBlockAt(x, y, z) then return @@ -163,41 +183,31 @@ placeRemote.OnServerEvent:Connect(function(player, cx, cy, cz, x, y, z, blockId) end) breakRemote.OnServerEvent:Connect(function(player, cx, cy, cz, x, y, z) - print("[DEBUG] Server breakRemote received - Player:", player.Name, "Chunk:", cx, cy, cz, "Block:", x, y, z) - if typeof(cx) ~= "number" or typeof(cy) ~= "number" or typeof(cz) ~= "number" then - print("[DEBUG] Invalid chunk coordinate types") return end if typeof(x) ~= "number" or typeof(y) ~= "number" or typeof(z) ~= "number" then - print("[DEBUG] Invalid block coordinate types") return end if x < 1 or x > 8 or y < 1 or y > 8 or z < 1 or z > 8 then - print("[DEBUG] Block coordinates out of range:", x, y, z) return end if math.abs(cx) > MAX_CHUNK_DIST or math.abs(cy) > MAX_CHUNK_DIST or math.abs(cz) > MAX_CHUNK_DIST then - print("[DEBUG] Chunk coordinates out of range:", cx, cy, cz) return end if not isWithinReach(player, cx, cy, cz, x, y, z) then - print("[DEBUG] Block not within player reach") return end local chunk = getServerChunk(cx, cy, cz) if not chunk:GetBlockAt(x, y, z) then - print("[DEBUG] No block found at specified location") task.synchronize() tickRemote:FireClient(player, "C_R", cx, cy, cz, 0, 0, 0, 0) task.desynchronize() return end - print("[DEBUG] All validations passed, removing block") chunk:RemoveBlock(x, y, z) propogate("B_D", cx, cy, cz, x, y, z, 0) - print("[DEBUG] Block removal propagated to clients") end) task.desynchronize()