--================================================================================ -- KentoHub / ToyMod (Obsidian + ドット絵 & カスタム & Wing & 67) --================================================================================ -- 1. Obsidian の読み込み local repo = "https://raw.githubusercontent.com/deividcomsono/Obsidian/main/" local Library = loadstring(game:HttpGet(repo .. "Library.lua"))() local ThemeManager = loadstring(game:HttpGet(repo .. "addons/ThemeManager.lua"))() local SaveManager = loadstring(game:HttpGet(repo .. "addons/SaveManager.lua"))() -- 2. サービス local Services = { Players = game:GetService("Players"), ReplicatedStorage = game:GetService("ReplicatedStorage"), RunService = game:GetService("RunService"), Workspace = game:GetService("Workspace"), Camera = game:GetService("Workspace").CurrentCamera, StarterGui = game:GetService("StarterGui") } local LocalPlayer = Services.Players.LocalPlayer --================================================================================ -- 3. リモートイベント --================================================================================ local Events = { GrabEvents = Services.ReplicatedStorage:WaitForChild("GrabEvents", 5), DataEvents = Services.ReplicatedStorage:WaitForChild("DataEvents", 5) } local SetNetworkOwnerEvent = Events.GrabEvents and Events.GrabEvents:WaitForChild("SetNetworkOwner", 5) local UpdateLineColorsEvent = Events.DataEvents and Events.DataEvents:WaitForChild("UpdateLineColorsEvent", 5) -- 4. カラー定義 local COLORS = { RED = Color3.fromRGB(255, 0, 0), YELLOW = Color3.fromRGB(255, 255, 0), BLACK = Color3.fromRGB(0, 0, 0), WHITE = Color3.fromRGB(255, 255, 255), BLUE = Color3.fromRGB(0, 100, 255), GREEN = Color3.fromRGB(0, 255, 0), PURPLE = Color3.fromRGB(180, 0, 255), ORANGE = Color3.fromRGB(255, 140, 0), PINK = Color3.fromRGB(255, 100, 180), CYAN = Color3.fromRGB(0, 255, 255), BROWN = Color3.fromRGB(139, 69, 19), DEFAULT = Color3.fromRGB(255, 170, 0) } -- 5. スクリプト状態 local HUB_STATE = { AnimationTimeAccumulator = 0, MaxDiceLimit = 20, PixelSpacing = 2.0, CurrentArt = "NONE", ArtBuildProgress = 0, ArtHoldTime = 0, ThirdPersonEnabled = false, ThirdPersonDistance = 15.0, WingEnabled = false, GlassWingEnabled = false, TetracubeWingEnabled = false, Dice67Enabled = false, Dice67Color = COLORS.WHITE, } -- 6. データ保管庫 local DiceStorage = { All = {}, HoveringMap = {}, ColorMap = {}, TargetPositions = {}, OriginalProperties = {} } local WingStorage = { All = {}, IsMyPart = {} } local GlassStorage = { Models = {}, AllParts = {}, IsMyPart = {} } local TetracubeStorage = { All = {}, IsMyPart = {} } local Dice67Storage = { All = {}, IsMyPart = {}, TargetPositions = {} } -- 7. カラーマップ local COLOR_MAP = { ["R"] = COLORS.RED, ["Y"] = COLORS.YELLOW, ["B"] = COLORS.BLACK, ["W"] = COLORS.WHITE, ["Bl"] = COLORS.BLUE, ["G"] = COLORS.GREEN, ["P"] = COLORS.PURPLE, ["O"] = COLORS.ORANGE, ["Pk"] = COLORS.PINK, ["C"] = COLORS.CYAN, ["Br"] = COLORS.BROWN, } --================================================================================ -- ドット絵パターン --================================================================================ local PATTERNS = { HEART = { {"R","R",0,"R","R"}, {"R","R","R","R","R"}, {0,"R","R","R",0}, {0,0,"R",0,0} }, SMILE = { {"Y","B","Y","B","Y"}, {"Y","Y","Y","Y","Y"}, {"Y","B","B","B","Y"}, {"Y","Y","Y","Y","Y"} }, CRY = { {"Y","B","Y","B","Y"}, {"Bl","Y","Y","Y","Bl"}, {"Y","Y","B","Y","Y"}, {"Y","B","Y","B","Y"} }, ANGRY = { {"B","Y","Y","Y","B"}, {"Y","B","Y","B","Y"}, {"Y","Y","Y","Y","Y"}, {"Y","B","B","B","Y"} }, COOL = { {"B","B","B","B","B"}, {"Y","Y","Y","Y","Y"}, {"Y","B","B","B","Y"}, {"Y","Y","Y","Y","Y"} }, STAR = { {0,0,"Y",0,0}, {"Y","Y","Y","Y","Y"}, {0,"Y","Y","Y",0}, {0,"Y",0,"Y",0} }, MOON = { {0,"Y","Y",0,0}, {"Y",0,0,0,0}, {"Y",0,0,0,0}, {0,"Y","Y",0,0} }, SUN = { {0,"Y","Y","Y",0}, {"Y","O","O","O","Y"}, {"Y","O","O","O","Y"}, {0,"Y","Y","Y",0} }, FLOWER = { {0,"Pk","Pk","Pk",0}, {"Pk","Y","Y","Y","Pk"}, {"Pk","Y","Y","Y","Pk"}, {0,"Pk","Pk","Pk",0} }, CAT = { {"O",0,"O",0,"O"}, {"O","O","O","O","O"}, {"O","B","O","B","O"}, {"O","O","Pk","O","O"} }, DOG = { {"Br",0,"Br",0,"Br"}, {"Br","Br","Br","Br","Br"}, {"Br","B","Br","B","Br"}, {"Br","W","W","W","Br"} }, PIZZA = { {"O","O","O","O","O"}, {"O","R","O","R","O"}, {"O","O","R","O","O"}, {0,"O","O","O",0} }, APPLE = { {0,0,"G",0,0}, {0,"R","R","R",0}, {"R","R","R","R","R"}, {0,"R","R","R",0} }, SKULL = { {"W","W","W","W","W"}, {"W","B","W","B","W"}, {"W","W","W","W","W"}, {0,"B","B","B",0} }, GHOST = { {"W","W","W","W","W"}, {"W","B","W","B","W"}, {"W","W","B","W","W"}, {"W",0,"W",0,"W"} }, PUMPKIN = { {0,"O",0,"O",0}, {"O","O","O","O","O"}, {"O","B","O","B","O"}, {"O","B","B","B","O"} }, BLUE_HEART = { {"Bl","Bl",0,"Bl","Bl"}, {"Bl","Bl","Bl","Bl","Bl"}, {0,"Bl","Bl","Bl",0}, {0,0,"Bl",0,0} }, GREEN_HEART = { {"G","G",0,"G","G"}, {"G","G","G","G","G"}, {0,"G","G","G",0}, {0,0,"G",0,0} }, PURPLE_HEART = { {"P","P",0,"P","P"}, {"P","P","P","P","P"}, {0,"P","P","P",0}, {0,0,"P",0,0} }, LIGHTNING = { {0,0,0,"Y","Y"}, {0,0,"Y","Y",0}, {"Y","Y","Y","Y",0}, {"Y","Y",0,0,0} }, FIRE = { {0,0,"R",0,0}, {0,"O","R","O",0}, {"O","O","Y","O","O"}, {"O","Y","Y","Y","O"} }, SNOWFLAKE = { {0,"C",0,"C",0}, {"C","C","C","C","C"}, {0,0,"C",0,0}, {"C","C","C","C","C"} }, NOTE = { {0,0,0,0,"B"}, {0,0,0,"B","B"}, {0,0,"B",0,"B"}, {0,"B","B","B",0} }, HOUSE = { {0,0,"R",0,0}, {0,"R","R","R",0}, {"R","R","R","R","R"}, {"Br","B",0,"B","Br"} }, CAR = { {0,0,"R","R","R"}, {"R","R","R","R","R"}, {"B","R","R","R","B"}, {0,"B",0,"B",0} }, RAINBOW = { {0,"R","R","R",0}, {"O","Y","Y","Y","O"}, {"G","C","C","C","G"}, {"Bl","P","P","P","Bl"} }, DANGER = { {0,0,"B",0,0}, {0,"B","Y","B",0}, {"B","Y","Y","Y","B"}, {"Y","B","Y","B","Y"} }, TARGET = { {0,"R","R","R",0}, {"R","W","W","W","R"}, {"R","W","R","W","R"}, {0,"R","R","R",0} }, SWORD = { {0,0,"W",0,0}, {0,0,"W",0,0}, {"Y","Y","W","Y","Y"}, {0,0,"Br",0,0} }, SHIELD = { {"Bl","Bl","Bl","Bl","Bl"}, {"Bl","Y","Y","Y","Bl"}, {0,"Bl","Y","Bl",0}, {0,0,"Bl",0,0} }, GIFT = { {0,"R","Y","R",0}, {"R","R","Y","R","R"}, {"R","R","Y","R","R"}, {0,"R","Y","R",0} }, CLOVER = { {"G",0,"G",0,0}, {"G","G","G","G",0}, {0,"G","G","G","G"}, {0,0,"G",0,"G"} }, } -- 67のパターン (計20個) local PATTERN_67 = { { "W", "W", "W", 0, "W", "W", "W" }, { "W", 0, 0, 0, 0, 0, "W" }, { "W", "W", "W", 0, 0, 0, "W" }, { "W", 0, "W", 0, 0, 0, "W" }, { "W", "W", "W", 0, 0, 0, "W" }, } --================================================================================ -- 判定関数 --================================================================================ local function IsOwnedByOtherPlayer(part) if not part or not part:IsA("BasePart") then return true end for _, player in ipairs(Services.Players:GetPlayers()) do if player ~= LocalPlayer then local pChar = player.Character if pChar and part:IsDescendantOf(pChar) then return true end local pBackpack = player:FindFirstChild("Backpack") if pBackpack and part:IsDescendantOf(pBackpack) then return true end end end local success, owner = pcall(function() return part:GetNetworkOwner() end) if success and owner ~= nil and owner ~= LocalPlayer then return true end return false end local function IsMyOnlyToyPart(part) if not part or not part:IsA("BasePart") or part.Anchored then return false end local myChar = LocalPlayer.Character if myChar and part:IsDescendantOf(myChar) then return false end if IsOwnedByOtherPlayer(part) then return false end local pName = part.Name local parentName = part.Parent and part.Parent.Name or "" if pName:find("Dice") or parentName:find("Dice") or pName:find("Toy") or parentName:find("Toy") then return true end return false end local PALLET_NAME = "PalletLightBrown" local function IsMyPallet(part) if not part or not part:IsA("BasePart") then return false end if IsOwnedByOtherPlayer(part) then return false end local parent = part while parent and parent ~= Services.Workspace do if parent.Name == PALLET_NAME then return true end parent = parent.Parent end return false end local GLASS_NAME = "GlassBoxGray" local function GetMyGlassModel(part) if not part then return nil end if IsOwnedByOtherPlayer(part) then return nil end local current = part while current and current ~= Services.Workspace do if current.Name == GLASS_NAME then return current end current = current.Parent end return nil end local TETRACUBE_NAME = "TetracubeI" local function IsMyTetracube(part) if not part or not part:IsA("BasePart") then return false end if IsOwnedByOtherPlayer(part) then return false end local parent = part while parent and parent ~= Services.Workspace do if parent.Name == TETRACUBE_NAME then return true end parent = parent.Parent end return false end local DICE_BIG_NAME = "DiceBig" local function IsMyDice67(part) if not part or not part:IsA("BasePart") then return false end if IsOwnedByOtherPlayer(part) then return false end local parent = part while parent and parent ~= Services.Workspace do if parent.Name == DICE_BIG_NAME then return true end parent = parent.Parent end return false end --================================================================================ -- 物理制御 --================================================================================ local function SetupDicePhysics(part) if not part or not part:IsA("BasePart") then return end if not DiceStorage.OriginalProperties[part] then DiceStorage.OriginalProperties[part] = { Color = part.Color, CFrame = part.CFrame, CanCollide = part.CanCollide } end part.CanCollide = false part.Anchored = false for _, obj in ipairs(part:GetChildren()) do if obj:IsA("BodyVelocity") or obj:IsA("BodyGyro") then obj:Destroy() end end local bv = Instance.new("BodyVelocity") bv.Name = "DiceForce" bv.MaxForce = Vector3.new(1, 1, 1) * 1e7 bv.P = 12500 bv.Parent = part local bg = Instance.new("BodyGyro") bg.Name = "DiceGyro" bg.MaxTorque = Vector3.new(1, 1, 1) * 1e7 bg.P = 12500 bg.Parent = part end local function RemoveDicePhysics(part) if not part then return end local bv = part:FindFirstChild("DiceForce") local bg = part:FindFirstChild("DiceGyro") if bv then bv:Destroy() end if bg then bg:Destroy() end part.CanCollide = true end local function SetupToyPhysics(part, prefix) if not part or not part:IsA("BasePart") then return end part.CanCollide = false part.Anchored = false for _, obj in ipairs(part:GetChildren()) do if obj:IsA("BodyVelocity") or obj:IsA("BodyGyro") then obj:Destroy() end end local bv = Instance.new("BodyVelocity") bv.Name = prefix .. "Force" bv.MaxForce = Vector3.new(1, 1, 1) * 1e7 bv.P = 12500 bv.Parent = part local bg = Instance.new("BodyGyro") bg.Name = prefix .. "Gyro" bg.MaxTorque = Vector3.new(1, 1, 1) * 1e7 bg.P = 12500 bg.Parent = part end local function CleanupDiceDatabase() for i = #DiceStorage.All, 1, -1 do local part = DiceStorage.All[i] if not part or not part.Parent or not part:IsDescendantOf(Services.Workspace) or IsOwnedByOtherPlayer(part) then DiceStorage.HoveringMap[part] = nil; DiceStorage.ColorMap[part] = nil; DiceStorage.TargetPositions[part] = nil RemoveDicePhysics(part) table.remove(DiceStorage.All, i) end end end local function CollectDice() CleanupDiceDatabase() local char = LocalPlayer.Character local root = char and (char:FindFirstChild("HumanoidRootPart") or char:FindFirstChild("Torso")) if not root then return end for _, part in ipairs(Services.Workspace:GetPartBoundsInRadius(root.Position, 120)) do if #DiceStorage.All >= HUB_STATE.MaxDiceLimit then break end if IsMyOnlyToyPart(part) and not DiceStorage.HoveringMap[part] then DiceStorage.HoveringMap[part] = true SetupDicePhysics(part) table.insert(DiceStorage.All, part) if not DiceStorage.ColorMap[part] then DiceStorage.ColorMap[part] = COLORS.DEFAULT end end end end local function CollectPallets() local char = LocalPlayer.Character local root = char and (char:FindFirstChild("HumanoidRootPart") or char:FindFirstChild("Torso")) if not root then return end for i = #WingStorage.All, 1, -1 do local p = WingStorage.All[i] if not p or not p.Parent or not p:IsDescendantOf(Services.Workspace) then table.remove(WingStorage.All, i); WingStorage.IsMyPart[p] = nil end end for _, part in ipairs(Services.Workspace:GetPartBoundsInRadius(root.Position, 300)) do if #WingStorage.All >= 20 then break end if not WingStorage.IsMyPart[part] and IsMyPallet(part) then WingStorage.IsMyPart[part] = true SetupToyPhysics(part, "Wing") table.insert(WingStorage.All, part) end end end local function CollectGlasses() local char = LocalPlayer.Character local root = char and (char:FindFirstChild("HumanoidRootPart") or char:FindFirstChild("Torso")) if not root then return end for i = #GlassStorage.Models, 1, -1 do local model = GlassStorage.Models[i] if not model or not model.Parent or not model:IsDescendantOf(Services.Workspace) then table.remove(GlassStorage.Models, i) end end GlassStorage.AllParts = {} local foundModels = {} for _, part in ipairs(Services.Workspace:GetPartBoundsInRadius(root.Position, 300)) do if part:IsA("BasePart") then local model = GetMyGlassModel(part) if model and not foundModels[model] then foundModels[model] = true if #GlassStorage.Models < 16 then table.insert(GlassStorage.Models, model) end end end end for _, model in ipairs(GlassStorage.Models) do local parts = model:GetDescendants() for _, p in ipairs(parts) do if p:IsA("BasePart") and not GlassStorage.IsMyPart[p] then GlassStorage.IsMyPart[p] = true SetupToyPhysics(p, "Glass") table.insert(GlassStorage.AllParts, p) end end end end local function CollectTetracubes() local char = LocalPlayer.Character local root = char and (char:FindFirstChild("HumanoidRootPart") or char:FindFirstChild("Torso")) if not root then return end for i = #TetracubeStorage.All, 1, -1 do local p = TetracubeStorage.All[i] if not p or not p.Parent or not p:IsDescendantOf(Services.Workspace) then table.remove(TetracubeStorage.All, i); TetracubeStorage.IsMyPart[p] = nil end end for _, part in ipairs(Services.Workspace:GetPartBoundsInRadius(root.Position, 300)) do if #TetracubeStorage.All >= 20 then break end if not TetracubeStorage.IsMyPart[part] and IsMyTetracube(part) then TetracubeStorage.IsMyPart[part] = true SetupToyPhysics(part, "Tet") table.insert(TetracubeStorage.All, part) end end end local function CollectDice67() local char = LocalPlayer.Character local root = char and (char:FindFirstChild("HumanoidRootPart") or char:FindFirstChild("Torso")) if not root then return end for i = #Dice67Storage.All, 1, -1 do local p = Dice67Storage.All[i] if not p or not p.Parent or not p:IsDescendantOf(Services.Workspace) then table.remove(Dice67Storage.All, i); Dice67Storage.IsMyPart[p] = nil end end if #Dice67Storage.All >= 20 then return end for _, part in ipairs(Services.Workspace:GetPartBoundsInRadius(root.Position, 300)) do if #Dice67Storage.All >= 20 then break end if not Dice67Storage.IsMyPart[part] and IsMyDice67(part) then Dice67Storage.IsMyPart[part] = true SetupToyPhysics(part, "D67") table.insert(Dice67Storage.All, part) end end end --================================================================================ -- ドット絵配置 --================================================================================ local function GetColorFromPixel(pixel) if pixel == 0 then return nil end if type(pixel) == "string" and COLOR_MAP[pixel] then return COLOR_MAP[pixel] end return COLORS.DEFAULT end local function PlacePixelArt(pattern) CollectDice() local char = LocalPlayer.Character local root = char and (char:FindFirstChild("HumanoidRootPart") or char:FindFirstChild("Torso")) if not root then return end local rows, cols = #pattern, #pattern[1] local spacing = HUB_STATE.PixelSpacing local baseCF = root.CFrame * CFrame.new(0, 8, -10) local right, up = baseCF.RightVector, baseCF.UpVector local centerX, centerY = (cols - 1) / 2, (rows - 1) / 2 DiceStorage.TargetPositions = {} local diceIndex = 1 for row = 1, rows do for col = 1, cols do local pixel = pattern[row][col] if pixel ~= 0 then if diceIndex > #DiceStorage.All then break end local targetPos = baseCF.Position + (right * ((col - 1 - centerX) * spacing)) + (up * (-(row - 1 - centerY) * spacing)) local d = DiceStorage.All[diceIndex] if d and d.Parent then DiceStorage.TargetPositions[d] = targetPos local color = GetColorFromPixel(pixel) if color then DiceStorage.ColorMap[d] = color end end diceIndex = diceIndex + 1 end end end for _, d in ipairs(DiceStorage.All) do if d and d.Parent and not DiceStorage.TargetPositions[d] then DiceStorage.TargetPositions[d] = Vector3.new(0, 500, 0) end end end local function Place67WithDice() CollectDice67() local char = LocalPlayer.Character local root = char and (char:FindFirstChild("HumanoidRootPart") or char:FindFirstChild("Torso")) if not root then return end local rows, cols = #PATTERN_67, #PATTERN_67[1] local spacing = 7.0 local baseCF = root.CFrame * CFrame.new(0, 20, -20) local right, up = baseCF.RightVector, baseCF.UpVector local centerX, centerY = (cols - 1) / 2, (rows - 1) / 2 Dice67Storage.TargetPositions = {} local diceIndex = 1 for row = 1, rows do for col = 1, cols do local pixel = PATTERN_67[row][col] if pixel ~= 0 then if diceIndex > #Dice67Storage.All then break end local targetPos = baseCF.Position + (right * ((col - 1 - centerX) * spacing)) + (up * (-(row - 1 - centerY) * spacing)) local s = Dice67Storage.All[diceIndex] if s and s.Parent then Dice67Storage.TargetPositions[s] = targetPos if s:IsA("BasePart") then s.Color = HUB_STATE.Dice67Color end end diceIndex = diceIndex + 1 end end end for _, s in ipairs(Dice67Storage.All) do if s and s.Parent and not Dice67Storage.TargetPositions[s] then Dice67Storage.TargetPositions[s] = Vector3.new(0, 500, 0) end end HUB_STATE.Dice67Enabled = true Library:Notify("ダイスで「67」を形成しました", 3) end local function ResetDice() HUB_STATE.CurrentArt = "NONE" DiceStorage.TargetPositions = {} for _, d in ipairs(DiceStorage.All) do if d and d.Parent then local orig = DiceStorage.OriginalProperties[d] if orig then DiceStorage.ColorMap[d] = orig.Color else DiceStorage.ColorMap[d] = COLORS.DEFAULT end end end end --================================================================================ -- 羽ばたき配置ロジック (パレット & ガラス箱 & Iブロック) --================================================================================ local function UpdateWings(dt) local char = LocalPlayer.Character local root = char and (char:FindFirstChild("HumanoidRootPart") or char:FindFirstChild("Torso")) if not root then return end local flapPhase = math.sin(HUB_STATE.AnimationTimeAccumulator * 5) local flapAngle = flapPhase * math.rad(30) local baseCF = root.CFrame -- 1. パレットの翼 if HUB_STATE.WingEnabled then local total = #WingStorage.All if total > 0 then local rightCount = math.min(10, total) local leftCount = math.min(10, total - rightCount) for i = 1, rightCount do local part = WingStorage.All[i] if part and part.Parent then local spread = 2.5 + (i * 1.8) local height = 1.5 + (i * 1.5) local localOffset = CFrame.new(spread, height, 1) local rotatedOffset = baseCF * CFrame.Angles(0, math.rad(-15), flapAngle) * localOffset local targetPos = rotatedOffset.Position local bv, bg = part:FindFirstChild("WingForce"), part:FindFirstChild("WingGyro") if bv then bv.Velocity = (targetPos - part.Position) * 15 end if bg then bg.CFrame = CFrame.lookAt(part.Position, root.Position) * CFrame.Angles(0, 0, flapAngle) end if (targetPos - part.Position).Magnitude > 25 then part.CFrame = CFrame.new(targetPos) * CFrame.Angles(0, 0, flapAngle) end end end for i = 1, leftCount do local part = WingStorage.All[rightCount + i] if part and part.Parent then local spread = 2.5 + (i * 1.8) local height = 1.5 + (i * 1.5) local localOffset = CFrame.new(-spread, height, 1) local rotatedOffset = baseCF * CFrame.Angles(0, math.rad(15), -flapAngle) * localOffset local targetPos = rotatedOffset.Position local bv, bg = part:FindFirstChild("WingForce"), part:FindFirstChild("WingGyro") if bv then bv.Velocity = (targetPos - part.Position) * 15 end if bg then bg.CFrame = CFrame.lookAt(part.Position, root.Position) * CFrame.Angles(0, 0, -flapAngle) end if (targetPos - part.Position).Magnitude > 25 then part.CFrame = CFrame.new(targetPos) * CFrame.Angles(0, 0, -flapAngle) end end end end end -- 2. ガラス箱の翼 if HUB_STATE.GlassWingEnabled then local totalModels = #GlassStorage.Models if totalModels > 0 then local rightCountGlass = math.min(8, totalModels) local leftCountGlass = math.min(8, totalModels - rightCountGlass) local glassFlapAngle = math.sin(HUB_STATE.AnimationTimeAccumulator * 4) * math.rad(35) for i = 1, rightCountGlass do local model = GlassStorage.Models[i] if model and model.Parent then local spread = 8.0 + (i * 5.0) local height = 3.0 + (i * 2.5) local localOffset = CFrame.new(spread, height, 1.5) local rotatedOffset = baseCF * CFrame.Angles(math.rad(10), math.rad(-25), glassFlapAngle) * localOffset local targetPos = rotatedOffset.Position local targetRot = CFrame.lookAt(targetPos, root.Position) * CFrame.Angles(0, 0, glassFlapAngle) * CFrame.Angles(math.rad(15), 0, 0) for _, p in ipairs(model:GetDescendants()) do if p:IsA("BasePart") then local bv, bg = p:FindFirstChild("GlassForce"), p:FindFirstChild("GlassGyro") if bv then bv.Velocity = (targetPos - p.Position) * 15 end if bg then bg.CFrame = targetRot end if (targetPos - p.Position).Magnitude > 30 then p.CFrame = targetRot end end end end end for i = 1, leftCountGlass do local model = GlassStorage.Models[rightCountGlass + i] if model and model.Parent then local spread = 8.0 + (i * 5.0) local height = 3.0 + (i * 2.5) local localOffset = CFrame.new(-spread, height, 1.5) local rotatedOffset = baseCF * CFrame.Angles(math.rad(10), math.rad(25), -glassFlapAngle) * localOffset local targetPos = rotatedOffset.Position local targetRot = CFrame.lookAt(targetPos, root.Position) * CFrame.Angles(0, 0, -glassFlapAngle) * CFrame.Angles(math.rad(15), 0, 0) for _, p in ipairs(model:GetDescendants()) do if p:IsA("BasePart") then local bv, bg = p:FindFirstChild("GlassForce"), p:FindFirstChild("GlassGyro") if bv then bv.Velocity = (targetPos - p.Position) * 15 end if bg then bg.CFrame = targetRot end if (targetPos - p.Position).Magnitude > 30 then p.CFrame = targetRot end end end end end end end -- 3. Iブロックの翼 (横向きにして羽ばたく) if HUB_STATE.TetracubeWingEnabled then local totalTet = #TetracubeStorage.All if totalTet > 0 then local rightCountTet = math.min(10, totalTet) local leftCountTet = math.min(10, totalTet - rightCountTet) local tetFlapAngle = math.sin(HUB_STATE.AnimationTimeAccumulator * 4) * math.rad(30) for i = 1, rightCountTet do local part = TetracubeStorage.All[i] if part and part.Parent then local spread = 3.0 + (i * 2.5) local height = 2.0 + (i * 1.5) local localOffset = CFrame.new(spread, height, 1.5) local rotatedOffset = baseCF * CFrame.Angles(math.rad(10), math.rad(-20), tetFlapAngle) * localOffset local targetPos = rotatedOffset.Position -- Iブロックを横向き(90度回転)にする local targetRot = CFrame.lookAt(targetPos, root.Position) * CFrame.Angles(0, 0, tetFlapAngle) * CFrame.Angles(0, 0, math.rad(90)) local bv, bg = part:FindFirstChild("TetForce"), part:FindFirstChild("TetGyro") if bv then bv.Velocity = (targetPos - part.Position) * 15 end if bg then bg.CFrame = targetRot end if (targetPos - part.Position).Magnitude > 30 then part.CFrame = targetRot end end end for i = 1, leftCountTet do local part = TetracubeStorage.All[rightCountTet + i] if part and part.Parent then local spread = 3.0 + (i * 2.5) local height = 2.0 + (i * 1.5) local localOffset = CFrame.new(-spread, height, 1.5) local rotatedOffset = baseCF * CFrame.Angles(math.rad(10), math.rad(20), -tetFlapAngle) * localOffset local targetPos = rotatedOffset.Position local targetRot = CFrame.lookAt(targetPos, root.Position) * CFrame.Angles(0, 0, -tetFlapAngle) * CFrame.Angles(0, 0, math.rad(90)) local bv, bg = part:FindFirstChild("TetForce"), part:FindFirstChild("TetGyro") if bv then bv.Velocity = (targetPos - part.Position) * 15 end if bg then bg.CFrame = targetRot end if (targetPos - part.Position).Magnitude > 30 then part.CFrame = targetRot end end end end end end --================================================================================ -- GUI 構築 (Obsidian) --================================================================================ local Window = Library:CreateWindow({ Title = 'KentoHub / ToyMod', Center = true, AutoShow = true, TabPadding = 8, MenuFadeTime = 0.2 }) local Tabs = { Art = Window:AddTab('🎨 ドット絵'), Editor = Window:AddTab('🖌️ カスタム'), Wings = Window:AddTab('🕊️ Wing'), Dice67 = Window:AddTab('🎲 67'), Camera = Window:AddTab('🎥 3人称視点'), Settings = Window:AddTab('⚙️ 設定') } -- 🎨 ドット絵タブ local ArtLeft = Tabs.Art:AddLeftGroupbox('顔・ハート・自然') local ArtRight = Tabs.Art:AddRightGroupbox('アイコン・その他') local function AddArtButton(groupbox, name, patternKey) groupbox:AddButton({ Text = name, Func = function() HUB_STATE.CurrentArt = patternKey HUB_STATE.ArtBuildProgress = 0 PlacePixelArt(PATTERNS[patternKey]) Library:Notify(name .. " を描画中...", 2) end }) end ArtLeft:AddLabel('😊 顔・動物・ホラー') AddArtButton(ArtLeft, "😊 スマイル", "SMILE") AddArtButton(ArtLeft, "😢 泣き顔", "CRY") AddArtButton(ArtLeft, "😠 怒り顔", "ANGRY") AddArtButton(ArtLeft, "😎 クール顔", "COOL") AddArtButton(ArtLeft, "🐱 猫", "CAT") AddArtButton(ArtLeft, "🐶 犬", "DOG") AddArtButton(ArtLeft, "💀 ドクロ", "SKULL") AddArtButton(ArtLeft, "👻 お化け", "GHOST") AddArtButton(ArtLeft, "🎃 カボチャ", "PUMPKIN") ArtLeft:AddLabel('💖 ハート・自然') AddArtButton(ArtLeft, "❤️ 赤ハート", "HEART") AddArtButton(ArtLeft, "💙 青ハート", "BLUE_HEART") AddArtButton(ArtLeft, "💚 緑ハート", "GREEN_HEART") AddArtButton(ArtLeft, "💜 紫ハート", "PURPLE_HEART") AddArtButton(ArtLeft, "⭐ 星", "STAR") AddArtButton(ArtLeft, "🌙 月", "MOON") AddArtButton(ArtLeft, "☀️ 太陽", "SUN") AddArtButton(ArtLeft, "🌸 花", "FLOWER") AddArtButton(ArtLeft, "🍀 クローバー", "CLOVER") ArtRight:AddLabel('⚔️ アイコン') AddArtButton(ArtRight, "⚔️ 剣", "SWORD") AddArtButton(ArtRight, "🛡️ 盾", "SHIELD") AddArtButton(ArtRight, "🎯 的", "TARGET") AddArtButton(ArtRight, "☠️ 危険", "DANGER") ArtRight:AddLabel('🍕 食べ物・その他') AddArtButton(ArtRight, "🍕 ピザ", "PIZZA") AddArtButton(ArtRight, "🍎 りんご", "APPLE") AddArtButton(ArtRight, "🎁 プレゼント", "GIFT") AddArtButton(ArtRight, "🏠 家", "HOUSE") AddArtButton(ArtRight, "🚗 車", "CAR") AddArtButton(ArtRight, "🎵 音符", "NOTE") ArtRight:AddLabel('🎲 ダイス操作') local function Spawn20Dice() local MenuToys = Services.ReplicatedStorage:FindFirstChild("MenuToys") local SpawnToy = MenuToys and MenuToys:FindFirstChild("SpawnToyRemoteFunction") if not SpawnToy then Library:Notify("エラー: リモートが見つかりません", 5) return end local char = LocalPlayer.Character local root = char and char:FindFirstChild("HumanoidRootPart") if not root then return end local spawnedCount = 0 local canSpawn = LocalPlayer:FindFirstChild("CanSpawnToy") local cooldownTimer, spawnTimer, isSpawning = 0, 0, true local spawnConn spawnConn = Services.RunService.Heartbeat:Connect(function(dt) if not isSpawning then return end if canSpawn and not canSpawn.Value then cooldownTimer = cooldownTimer + dt if cooldownTimer >= 5 then isSpawning = false; if spawnConn then spawnConn:Disconnect() end end return end spawnTimer = spawnTimer + dt if spawnTimer < 0.15 then return end spawnTimer, cooldownTimer = 0, 0 local spawnCF = root.CFrame * CFrame.new(math.random(-3, 3), 2, math.random(-3, 3)) pcall(function() SpawnToy:InvokeServer("DiceSmall", spawnCF, Vector3.zero) end) spawnedCount = spawnedCount + 1 if spawnedCount >= 20 then isSpawning = false; if spawnConn then spawnConn:Disconnect() end end end) end ArtRight:AddButton({ Text = '🎲 DiceSmallを20個出す', Func = function() Spawn20Dice() end }) -- 🕊️ Wingタブ (ここにIブロックを統合) local WingBox = Tabs.Wings:AddLeftGroupbox('PalletLightBrown Wing') local GlassBox = Tabs.Wings:AddRightGroupbox('GlassBoxGray 翼') local TetracubeBox = Tabs.Wings:AddLeftGroupbox('Iブロック') -- ★ Iブロック用ボックス追加 local function Spawn20Pallets() local MenuToys = Services.ReplicatedStorage:FindFirstChild("MenuToys") local SpawnToy = MenuToys and MenuToys:FindFirstChild("SpawnToyRemoteFunction") if not SpawnToy then Library:Notify("エラー: リモートが見つかりません", 5) return end local char = LocalPlayer.Character local root = char and char:FindFirstChild("HumanoidRootPart") if not root then return end Library:Notify("パレットを20個出しています...", 3) task.spawn(function() for i = 1, 20 do local spawnCF = root.CFrame * CFrame.new(math.random(-5, 5), 5, math.random(-5, 5)) pcall(function() SpawnToy:InvokeServer(PALLET_NAME, spawnCF, Vector3.zero) end) task.wait(0.5) end Library:Notify("パレットを20個出しました", 3) end) end WingBox:AddToggle('WingEnabledToggle', { Text = '🕊️ パレットの翼を展開', Default = false, Callback = function(Value) HUB_STATE.WingEnabled = Value if Value then CollectPallets() Library:Notify("パレットの翼を展開します (" .. #WingStorage.All .. "枚)", 2) else Library:Notify("パレットの翼を解除しました", 2) end end }) WingBox:AddButton({ Text = '🪶 パレットを20個出す', Func = function() Spawn20Pallets() end }) WingBox:AddLabel('※起動時は自動で出ません。') GlassBox:AddToggle('GlassEnabledToggle', { Text = '🧊 ガラス箱の翼を展開', Default = false, Callback = function(Value) HUB_STATE.GlassWingEnabled = Value if Value then CollectGlasses() Library:Notify("ガラス箱の翼を展開します (" .. #GlassStorage.Models .. "個)", 2) else Library:Notify("ガラス箱の翼を解除しました", 2) end end }) GlassBox:AddButton({ Text = '🧊 ガラス箱を16個出す', Func = function() local MenuToys = Services.ReplicatedStorage:FindFirstChild("MenuToys") local SpawnToy = MenuToys and MenuToys:FindFirstChild("SpawnToyRemoteFunction") if not SpawnToy then Library:Notify("エラー: リモートが見つかりません", 5) return end local char = LocalPlayer.Character local root = char and char:FindFirstChild("HumanoidRootPart") if not root then return end task.spawn(function() for i = 1, 16 do local spawnCF = root.CFrame * CFrame.new(math.random(-20, 20), 10, math.random(-20, 20)) pcall(function() SpawnToy:InvokeServer(GLASS_NAME, spawnCF, Vector3.zero) end) task.wait(0.5) end Library:Notify("ガラス箱を16個出しました", 3) end) end }) GlassBox:AddLabel('※ガラス箱は最大16個まで出せます。') -- ★ IブロックのUI (Wingタブ内) local function Spawn20Tetracubes() local MenuToys = Services.ReplicatedStorage:FindFirstChild("MenuToys") local SpawnToy = MenuToys and MenuToys:FindFirstChild("SpawnToyRemoteFunction") if not SpawnToy then Library:Notify("エラー: リモートが見つかりません", 5) return end local char = LocalPlayer.Character local root = char and char:FindFirstChild("HumanoidRootPart") if not root then return end Library:Notify("Iブロックを20個出しています...", 3) task.spawn(function() for i = 1, 20 do local spawnCF = root.CFrame * CFrame.new(math.random(-5, 5), 5, math.random(-5, 5)) pcall(function() SpawnToy:InvokeServer(TETRACUBE_NAME, spawnCF, Vector3.zero) end) task.wait(0.5) end Library:Notify("Iブロックを20個出しました", 3) end) end TetracubeBox:AddToggle('TetracubeEnabledToggle', { Text = '🧱 Iブロックの翼を展開', Default = false, Callback = function(Value) HUB_STATE.TetracubeWingEnabled = Value if Value then CollectTetracubes() Library:Notify("Iブロックの翼を展開します (" .. #TetracubeStorage.All .. "個)", 2) else Library:Notify("Iブロックの翼を解除しました", 2) end end }) TetracubeBox:AddButton({ Text = '🧱 Iブロックを20個出す', Func = function() Spawn20Tetracubes() end }) TetracubeBox:AddLabel('※起動時は自動で出ません。') -- 🎲 67タブ local Dice67Box = Tabs.Dice67:AddLeftGroupbox('DiceBig (ビッグダイス)') Dice67Box:AddDropdown('Dice67ColorSelect', { Text = '67の色', Values = {'白', '黒', '赤', '青', '緑', '黄', '紫', 'ピンク', 'オレンジ', '水色'}, Default = '白', Multi = false, Callback = function(Value) if Value == '白' then HUB_STATE.Dice67Color = COLORS.WHITE elseif Value == '黒' then HUB_STATE.Dice67Color = COLORS.BLACK elseif Value == '赤' then HUB_STATE.Dice67Color = COLORS.RED elseif Value == '青' then HUB_STATE.Dice67Color = COLORS.BLUE elseif Value == '緑' then HUB_STATE.Dice67Color = COLORS.GREEN elseif Value == '黄' then HUB_STATE.Dice67Color = COLORS.YELLOW elseif Value == '紫' then HUB_STATE.Dice67Color = COLORS.PURPLE elseif Value == 'ピンク' then HUB_STATE.Dice67Color = COLORS.PINK elseif Value == 'オレンジ' then HUB_STATE.Dice67Color = COLORS.ORANGE elseif Value == '水色' then HUB_STATE.Dice67Color = COLORS.CYAN end end }) local function Spawn20DiceFor67() local MenuToys = Services.ReplicatedStorage:FindFirstChild("MenuToys") local SpawnToy = MenuToys and MenuToys:FindFirstChild("SpawnToyRemoteFunction") if not SpawnToy then Library:Notify("エラー: SpawnToyRemoteFunction が見つかりません", 5) return end local char = LocalPlayer.Character local root = char and char:FindFirstChild("HumanoidRootPart") if not root then return end HUB_STATE.Dice67Enabled = false Dice67Storage.TargetPositions = {} Dice67Storage.All = {} Dice67Storage.IsMyPart = {} Library:Notify("DiceBigを20個出しています...", 3) task.spawn(function() for i = 1, 20 do local spawnCF = root.CFrame * CFrame.new(math.random(-5, 5), 5, math.random(-5, 5)) pcall(function() SpawnToy:InvokeServer(DICE_BIG_NAME, spawnCF, Vector3.zero) end) task.wait(0.5) end Library:Notify("DiceBigを20個出しました", 3) end) end Dice67Box:AddButton({ Text = '🎲 DiceBigを20個出す', Func = function() Spawn20DiceFor67() end }) Dice67Box:AddButton({ Text = '🔢 DiceBigで「67」を作る', Func = function() Place67WithDice() end }) Dice67Box:AddLabel('※まずダイスを出してから「67」を作ってください。') -- 🎥 3人称視点タブ local CameraBox = Tabs.Camera:AddLeftGroupbox('3人称視点') CameraBox:AddToggle('TPSEnabled', { Text = '3人称視点(TPS)有効化', Default = false, Callback = function(Value) HUB_STATE.ThirdPersonEnabled = Value if not Value then Services.Camera.CameraType = Enum.CameraType.Custom end end }) CameraBox:AddSlider('CameraDistance', { Text = 'カメラ距離', Default = 15, Min = 5, Max = 50, Rounding = 1, Callback = function(Value) HUB_STATE.ThirdPersonDistance = Value end }) -- 🖌️ カスタムタブ local EDITOR_SIZE = 10 local EDITOR_COLOR_LIST = { { name = "赤", key = "R" }, { name = "黄", key = "Y" }, { name = "黒", key = "B" }, { name = "白", key = "W" }, { name = "青", key = "Bl" }, { name = "緑", key = "G" }, { name = "紫", key = "P" }, { name = "橙", key = "O" }, { name = "桃", key = "Pk" }, { name = "水", key = "C" }, { name = "茶", key = "Br" } } local CustomEditor = { Grid = {}, SelectedColor = "R", Tiles = {} } for row = 1, EDITOR_SIZE do CustomEditor.Grid[row] = {}; CustomEditor.Tiles[row] = {} for col = 1, EDITOR_SIZE do CustomEditor.Grid[row][col] = 0 end end local function BuildCustomPattern() local pattern = {} for row = 1, EDITOR_SIZE do pattern[row] = {} for col = 1, EDITOR_SIZE do pattern[row][col] = CustomEditor.Grid[row][col] end end return pattern end local function RefreshEditorTile(row, col) local tile = CustomEditor.Tiles[row][col] if tile then tile.BackgroundColor3 = CustomEditor.Grid[row][col] == 0 and Color3.fromRGB(45, 45, 45) or (COLOR_MAP[CustomEditor.Grid[row][col]] or COLORS.DEFAULT) end end local function RefreshEditorAll() for r = 1, EDITOR_SIZE do for c = 1, EDITOR_SIZE do RefreshEditorTile(r, c) end end end local editorGui = Instance.new("ScreenGui") editorGui.Name = "KentoHubEditor" editorGui.ResetOnSpawn = false editorGui.DisplayOrder = 999 editorGui.Parent = game:GetService("CoreGui") local editorFrame = Instance.new("Frame") editorFrame.Size = UDim2.new(0, 240, 0, 240) editorFrame.AnchorPoint = Vector2.new(0.5, 0.5) editorFrame.Position = UDim2.new(0.5, 0, 0.5, 0) editorFrame.BackgroundColor3 = Color3.fromRGB(25, 25, 25) editorFrame.Visible = false editorFrame.Draggable = true editorFrame.Parent = editorGui local gridUI = Instance.new("UIGridLayout") gridUI.CellSize = UDim2.new(0, 20, 0, 20) gridUI.CellPadding = UDim2.new(0, 2, 0, 2) gridUI.SortOrder = Enum.SortOrder.LayoutOrder gridUI.Parent = editorFrame for row = 1, EDITOR_SIZE do for col = 1, EDITOR_SIZE do local tile = Instance.new("TextButton") tile.Name = "Tile_" .. row .. "_" .. col tile.Text = "" tile.BackgroundColor3 = Color3.fromRGB(45, 45, 45) tile.AutoButtonColor = false tile.LayoutOrder = (row - 1) * EDITOR_SIZE + col tile.Parent = editorFrame tile.MouseButton1Click:Connect(function() local v = CustomEditor.Grid[row][col] CustomEditor.Grid[row][col] = (v == CustomEditor.SelectedColor) and 0 or CustomEditor.SelectedColor RefreshEditorTile(row, col) end) CustomEditor.Tiles[row][col] = tile end end local EditorColorBox = Tabs.Editor:AddLeftGroupbox('色選択') local colorNames = {} for _, c in ipairs(EDITOR_COLOR_LIST) do table.insert(colorNames, c.name) end EditorColorBox:AddDropdown('EditorColor', { Text = '描画する色', Values = colorNames, Default = '赤', Multi = false, Callback = function(Value) for _, c in ipairs(EDITOR_COLOR_LIST) do if c.name == Value then CustomEditor.SelectedColor = c.key break end end end }) local EditorGridBox = Tabs.Editor:AddRightGroupbox('10×10 キャンバス') EditorGridBox:AddToggle('EditorShowGrid', { Text = 'グリッドを画面に表示', Default = false, Callback = function(Value) editorFrame.Visible = Value end }) EditorGridBox:AddButton({ Text = '🎨 この絵を描く', Func = function() HUB_STATE.CurrentArt = "CUSTOM" HUB_STATE.ArtBuildProgress = 0 PlacePixelArt(BuildCustomPattern()) Library:Notify("カスタム絵を描画中...", 2) end }) EditorGridBox:AddButton({ Text = '🗑️ キャンバスをクリア', Func = function() for r = 1, EDITOR_SIZE do for c = 1, EDITOR_SIZE do CustomEditor.Grid[r][c] = 0 end end RefreshEditorAll() end }) RefreshEditorAll() -- ⚙️ 設定タブ local SettingsBox = Tabs.Settings:AddLeftGroupbox('ダイス操作') SettingsBox:AddButton({ Text = '🔄 ダイスを集める', Func = function() CollectDice() Library:Notify("🎲 " .. #DiceStorage.All .. "個集めました", 2) end }) SettingsBox:AddButton({ Text = '↩️ リセット', Func = function() ResetDice() Library:Notify("↩️ リセットしました", 2) end }) SettingsBox:AddButton({ Text = '🚫 演出停止', Func = function() HUB_STATE.CurrentArt = "NONE" for _, d in ipairs(DiceStorage.All) do if d and d.Parent then DiceStorage.TargetPositions[d] = Vector3.new(0, 500, 0) end end Library:Notify("🚫 停止しました", 2) end }) local ParamBox = Tabs.Settings:AddRightGroupbox('パラメータ') ParamBox:AddSlider('PixelSpacing', { Text = 'ドット間隔', Default = 2, Min = 1, Max = 5, Rounding = 1, Callback = function(Value) HUB_STATE.PixelSpacing = Value end }) ParamBox:AddSlider('ArtHoldTime', { Text = '保持時間 (0=無限)', Default = 0, Min = 0, Max = 30, Rounding = 0, Callback = function(Value) HUB_STATE.ArtHoldTime = Value end }) --================================================================================ -- メインループ (Heartbeat) --================================================================================ local syncTicker, collectPalletsTicker = 0, 0 Services.RunService.Heartbeat:Connect(function(dt) HUB_STATE.AnimationTimeAccumulator = HUB_STATE.AnimationTimeAccumulator + dt if #DiceStorage.All < HUB_STATE.MaxDiceLimit then CollectDice() end local char = LocalPlayer.Character if not char then return end local root = char:FindFirstChild("HumanoidRootPart") or char:FindFirstChild("Torso") if not root then return end if HUB_STATE.ThirdPersonEnabled then Services.Camera.CameraType = Enum.CameraType.Scriptable local targetCamPos = root.Position - (root.CFrame.LookVector * HUB_STATE.ThirdPersonDistance) + Vector3.new(0, 4, 0) Services.Camera.CFrame = CFrame.new(targetCamPos, root.Position + Vector3.new(0, 2, 0)) end if HUB_STATE.CurrentArt ~= "NONE" then for _, d in ipairs(DiceStorage.All) do if d and d.Parent then local targetPos = DiceStorage.TargetPositions[d] local bv, bg = d:FindFirstChild("DiceForce"), d:FindFirstChild("DiceGyro") if targetPos then if bv then bv.Velocity = (targetPos - d.Position) * 25 end if bg then bg.CFrame = CFrame.lookAt(d.Position, targetPos) * CFrame.Angles(0, HUB_STATE.AnimationTimeAccumulator * 2, 0) end if (targetPos - d.Position).Magnitude > 15 then d.CFrame = CFrame.new(targetPos) end else if bv then bv.Velocity = Vector3.zero end end end end if HUB_STATE.ArtHoldTime > 0 then HUB_STATE.ArtBuildProgress = HUB_STATE.ArtBuildProgress + dt if HUB_STATE.ArtBuildProgress >= HUB_STATE.ArtHoldTime then ResetDice() HUB_STATE.ArtBuildProgress = 0 end end else for _, d in ipairs(DiceStorage.All) do if d and d.Parent then local bv, bg = d:FindFirstChild("DiceForce"), d:FindFirstChild("DiceGyro") if bv then bv.Velocity = Vector3.zero end if bg then bg.CFrame = CFrame.new(d.CFrame.Position) end end end end if HUB_STATE.WingEnabled or HUB_STATE.GlassWingEnabled or HUB_STATE.TetracubeWingEnabled then collectPalletsTicker = collectPalletsTicker + dt if collectPalletsTicker >= 0.5 then collectPalletsTicker = 0 if HUB_STATE.WingEnabled and #WingStorage.All < 20 then CollectPallets() end if HUB_STATE.GlassWingEnabled and #GlassStorage.Models < 16 then CollectGlasses() end if HUB_STATE.TetracubeWingEnabled and #TetracubeStorage.All < 20 then CollectTetracubes() end end UpdateWings(dt) else for _, p in ipairs(WingStorage.All) do if p and p.Parent then local bv, bg = p:FindFirstChild("WingForce"), p:FindFirstChild("WingGyro") if bv then bv.Velocity = Vector3.zero end if bg then bg.CFrame = CFrame.new(p.CFrame.Position) end end end for _, model in ipairs(GlassStorage.Models) do if model and model.Parent then for _, p in ipairs(model:GetDescendants()) do if p:IsA("BasePart") then local bv, bg = p:FindFirstChild("GlassForce"), p:FindFirstChild("GlassGyro") if bv then bv.Velocity = Vector3.zero end if bg then bg.CFrame = CFrame.new(p.CFrame.Position) end end end end end for _, p in ipairs(TetracubeStorage.All) do if p and p.Parent then local bv, bg = p:FindFirstChild("TetForce"), p:FindFirstChild("TetGyro") if bv then bv.Velocity = Vector3.zero end if bg then bg.CFrame = CFrame.new(p.CFrame.Position) end end end end if HUB_STATE.Dice67Enabled then if #Dice67Storage.All < 20 then CollectDice67() end for _, s in ipairs(Dice67Storage.All) do if s and s.Parent then local targetPos = Dice67Storage.TargetPositions[s] local bv, bg = s:FindFirstChild("D67Force"), s:FindFirstChild("D67Gyro") if targetPos then local floatOffset = Vector3.new(0, math.sin(HUB_STATE.AnimationTimeAccumulator * 2 + s.Position.X) * 0.5, 0) local finalPos = targetPos + floatOffset if bv then bv.Velocity = (finalPos - s.Position) * 20 end if bg then bg.CFrame = CFrame.new(s.Position, finalPos) end if (finalPos - s.Position).Magnitude > 20 then s.CFrame = CFrame.new(finalPos) end if s:IsA("BasePart") and s.Color ~= HUB_STATE.Dice67Color then s.Color = HUB_STATE.Dice67Color end else if bv then bv.Velocity = Vector3.zero end end end end end syncTicker = syncTicker + dt if syncTicker >= 0.2 then syncTicker = 0 for _, d in ipairs(DiceStorage.All) do if d and d.Parent then local targetColor = DiceStorage.ColorMap[d] or COLORS.DEFAULT local seq = ColorSequence.new(targetColor) if UpdateLineColorsEvent then pcall(function() UpdateLineColorsEvent:FireServer(seq, targetColor, targetColor, targetColor, targetColor, targetColor, targetColor, targetColor, targetColor, targetColor) end) end if SetNetworkOwnerEvent then pcall(function() SetNetworkOwnerEvent:FireServer(d, d.CFrame) end) end end end end end) -- テーマ・セーブ設定 ThemeManager:SetLibrary(Library) SaveManager:SetLibrary(Library) SaveManager:IgnoreThemeSettings() SaveManager:SetIgnoreIndexes({ 'MenuKeybind' }) ThemeManager:SetFolder('KentoHubToyMod') SaveManager:SetFolder('KentoHubToyMod') SaveManager:BuildConfigSection(Tabs.Settings) ThemeManager:ApplyToTab(Tabs.Settings) Library:Notify("KentoHub / ToyMod", "読み込み完了!", 3) print("🧡 KentoHub / ToyMod 読み込み完了!")