Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local MathParser = require(script.StringToMath) -- API to calculate operations from expressions
- local IdRetriever = require(script.IdRetriever) -- API to generate a unique ID for each blackhole (60,466,176 possible IDs)
- local blackHole = script.Parent -- Reference to the black hole
- --------------------------------------------------------------------------------------------------------------------
- -- This version is for purely technical & nerdy purposes. If you know how to code, you can screw around here, lol --
- --------------------------------------------------------------------------------------------------------------------
- -- Assume these are StringValue objects containing expressions
- local GString = blackHole:FindFirstChild("G_(gravity)", true) -- e.g. "6.67e-11"
- local cString = blackHole:FindFirstChild("c_(light_speed)", true) -- e.g. "3e8"
- local customMassString = blackHole:FindFirstChild("CustomMass", true) -- optional string expression
- local customMassEnabled = blackHole:FindFirstChild("UseCustomMass", true)
- local useEinsteinian = blackHole:FindFirstChild("UseEinsteinian", true)
- -- New: OverridePhysics Boolean and Magnitude StringValue
- local overridePhysics = blackHole:FindFirstChild("OverridePhysics", true)
- local magnitudeString = blackHole:FindFirstChild("Magnitude", true)
- -- Generate a unique identifier for this black hole
- local BHId = IdRetriever.FetchID(5) -- Number represents the length of the ID. longer means more unique IDs (MUST BE INTEGERS)
- -- Ensure blackHole has an attachment for the line force target
- local blackHoleAttachment = blackHole:FindFirstChild("LineForceAttachment")
- if not blackHoleAttachment then
- blackHoleAttachment = Instance.new("Attachment")
- blackHoleAttachment.Name = "LineForceAttachment"
- blackHoleAttachment.Parent = blackHole
- end
- -- Helper function: parse a StringValue's expression and update its value
- -- If an error occurs during evaluation, rename the constant by appending "[ERROR]"
- -- If a valid value is returned, remove the "[ERROR]" substring if present
- local function parseAndSetValue(strValue)
- local expr = strValue.Value
- -- Try a direct conversion first
- local numberVal = tonumber(expr)
- if numberVal then
- -- Update the string to the numeral equivalent
- strValue.Value = tostring(numberVal)
- -- Remove error indicator from the name if present
- strValue.Name = string.gsub(strValue.Name, "%s*%[ERROR%]", "")
- return numberVal
- else
- local success, result = pcall(function()
- return MathParser.evaluateExpression(expr)
- end)
- if success and type(result) == "number" then
- -- Replace the string with the computed number (as a string)
- strValue.Value = tostring(result)
- -- Remove error indicator from the name if present.
- strValue.Name = string.gsub(strValue.Name, "%s*%[ERROR%]", "")
- return result
- else
- -- Append "[ERROR]" to the current name if not already appended and set value to "ERROR - Unrecognized"
- if not string.find(strValue.Name, "%[ERROR%]") then
- strValue.Name = strValue.Name .. " [ERROR]"
- strValue.Value = "ERROR - Syntax"
- warn("{" .. BHId .. "} Unrecognized operation. Only accepts: ( ) ^ E * / + - [Example: (2E1)^4/2+2]. Mathematical rules apply!")
- print("{" .. BHId .. "} Accepts pi, tau, phi, e (Euler's Number), sin(), cos(), tan(), asin(), acos(), atan(), csc(), sec(), cot(), ceil(), floor() or //, round()")
- end
- return 0
- end
- end
- end
- -- Helper function to update the black hole's name and its attachment name
- local function updateBlackHoleName()
- local method = useEinsteinian.Value and "Einstein" or "Newton"
- local massType = customMassEnabled.Value and "Custom" or "Current"
- blackHole.Name = "{BHv5} " .. method .. " | " .. massType .. " {" .. BHId .. "}"
- -- Update blackHoleAttachment's name based on the gravity mode
- if useEinsteinian.Value then
- blackHoleAttachment.Name = "EinsteinianGravityAttachment"
- else
- blackHoleAttachment.Name = "NewtonianGravityAttachment"
- end
- end
- -- Call it once initially
- updateBlackHoleName()
- -- When the gravity mode changes, update the name and remove forces that don't match this black hole
- useEinsteinian.Changed:Connect(function(newValue)
- updateBlackHoleName()
- if newValue then
- -- Remove any Newtonian forces from objects that were created by this black hole
- for _, object in pairs(workspace:GetDescendants()) do
- if object:IsA("BasePart") then
- local force = object:FindFirstChild("NewtonianGravity_" .. BHId)
- if force then
- if force.Attachment0 then
- force.Attachment0:Destroy()
- end
- force:Destroy()
- end
- end
- end
- else
- -- Remove any Einsteinian forces from objects that were created by this black hole
- for _, object in pairs(workspace:GetDescendants()) do
- if object:IsA("BasePart") then
- local force = object:FindFirstChild("RelativisticGravity_" .. BHId)
- if force then
- if force.Attachment0 then
- force.Attachment0:Destroy()
- end
- force:Destroy()
- end
- end
- end
- end
- end)
- -- Also update the name if the CustomMassEnabled changes
- customMassEnabled.Changed:Connect(function()
- updateBlackHoleName()
- end)
- -- Function to get the current mass for the black hole
- local function getCurrentMass()
- if customMassEnabled.Value == true and customMassString then
- return parseAndSetValue(customMassString)
- else
- return blackHole:GetMass()
- end
- end
- -- Initially parse G and c
- local G_val = parseAndSetValue(GString)
- local c_val = parseAndSetValue(cString)
- -- Initial calculation of event horizon (Schwarzschild radius) in meters
- local eventHorizon = 2 * G_val * getCurrentMass() / (c_val^2)
- -- Convert Roblox scale to real-world scale (1 meter = 4 studs)
- local function studsToMeters(studs)
- return studs / 4
- end
- local function metersToStuds(meters)
- return meters * 4
- end
- -------------------------------------------------------------------
- -- Anchor/Unanchor Affected Objects on Parser Errors --
- -------------------------------------------------------------------
- local function checkAndToggleAnchors()
- -- Check if any of the crucial StringValues report an error:
- local hasError = false
- if string.find(GString.Name, "%[ERROR%]") or string.find(cString.Name, "%[ERROR%]") then
- hasError = true
- end
- if customMassEnabled.Value and customMassString then
- if string.find(customMassString.Name, "%[ERROR%]") then
- hasError = true
- end
- end
- if overridePhysics and overridePhysics.Value and magnitudeString then
- if string.find(magnitudeString.Name, "%[ERROR%]") then
- hasError = true
- end
- end
- -- Iterate over all BaseParts with line forces created by this black hole
- for _, object in pairs(workspace:GetDescendants()) do
- if object:IsA("BasePart") then
- -- Check for either Newtonian or Relativistic force from this black hole
- local newtonForce = object:FindFirstChild("NewtonianGravity_" .. BHId)
- local einsteinForce = object:FindFirstChild("RelativisticGravity_" .. BHId)
- if newtonForce or einsteinForce then
- -- If error exists, anchor the object if not already anchored
- if hasError and not object.Anchored then
- -- Mark that we anchored it due to BH error
- object:SetAttribute("WasUnanchoredByBH", true)
- object.Anchored = true
- elseif not hasError and object:GetAttribute("WasUnanchoredByBH") then
- -- Remove our attribute and unanchor the object
- object:SetAttribute("WasUnanchoredByBH", nil)
- object.Anchored = false
- end
- end
- end
- end
- end
- --------------------------------------------
- -- Update Event Horizon and Check Anchors --
- --------------------------------------------
- -- Function to update the event horizon when any number value changes
- local function updateEventHorizon()
- G_val = parseAndSetValue(GString)
- c_val = parseAndSetValue(cString)
- local M_val = getCurrentMass()
- eventHorizon = 2 * G_val * M_val / (c_val^2)
- -- After updating, check and toggle anchors based on parser errors
- checkAndToggleAnchors()
- end
- -- Attach event listeners to update when the string expressions change
- GString.Changed:Connect(updateEventHorizon)
- cString.Changed:Connect(updateEventHorizon)
- if customMassString then
- customMassString.Changed:Connect(updateEventHorizon)
- end
- -- Also, if custom mass enabled changes, update both the name and the event horizon check:
- customMassEnabled.Changed:Connect(function()
- updateBlackHoleName()
- updateEventHorizon()
- end)
- ----------------------------------------------------------
- -- Gravitational Force Functions (with OverridePhysics) --
- ----------------------------------------------------------
- local MIN_FORCE_THRESHOLD = 0.001
- -- Relativistic Gravity Function using the Schwarzschild approximation
- local function relativisticGravity(object)
- if not object:IsA("BasePart") or object == blackHole or object.Anchored then
- return -- Skip non-BaseParts, anchored objects, and the black hole itself
- end
- local r = (blackHole.Position - object.Position).Magnitude
- local r_meters = studsToMeters(r)
- if r_meters <= (blackHole.Size.X / 16) then
- -- Object has crossed the event horizon; in this example, we simply return
- return
- end
- local computedMagnitude = nil
- -- Check if OverridePhysics is enabled
- if overridePhysics and overridePhysics.Value and magnitudeString then
- -- Use the override magnitude (parsed from magnitudeString) scaled via inverse-square law
- local overrideForce = parseAndSetValue(magnitudeString)
- computedMagnitude = overrideForce / (r_meters^2)
- else
- -- Normal Einsteinian gravity calculation
- local g = (G_val * getCurrentMass()) / (r_meters^2) * (1 - (eventHorizon / r_meters))
- local g_studs = metersToStuds(g)
- computedMagnitude = g_studs * object:GetMass()
- end
- -- Unique force name for this black hole instance in Einstein mode
- local forceName = "RelativisticGravity_" .. BHId
- local existingForce = object:FindFirstChild(forceName)
- if math.abs(computedMagnitude) < MIN_FORCE_THRESHOLD then
- if existingForce then
- if existingForce.Attachment0 then
- existingForce.Attachment0:Destroy()
- end
- existingForce:Destroy()
- end
- return
- end
- local force = existingForce
- if not force then
- force = Instance.new("LineForce")
- force.Name = forceName
- local objectAttachment = Instance.new("Attachment")
- objectAttachment.Name = "EinsteinianGravityAttachment"
- objectAttachment.Parent = object
- force.Attachment0 = objectAttachment
- force.Attachment1 = blackHoleAttachment
- force.ApplyAtCenterOfMass = true
- force.Visible = true
- force.Parent = object
- end
- force.Magnitude = computedMagnitude
- end
- -- Newtonian Gravity Function using F = G * m1 * m2 / r^2
- local function newtonianGravity(object)
- if not object:IsA("BasePart") or object == blackHole or object.Anchored then
- return -- Skip non-BaseParts, anchored objects, and the black hole
- end
- local r = (blackHole.Position - object.Position).Magnitude / 4
- local r_meters = studsToMeters(r)
- if r_meters <= 0 then return end
- local computedMagnitude = nil
- -- Check if OverridePhysics is enabled
- if overridePhysics and overridePhysics.Value and magnitudeString then
- local overrideForce = parseAndSetValue(magnitudeString)
- computedMagnitude = overrideForce / (r_meters^2)
- else
- local F = (G_val * getCurrentMass() * object:GetMass()) / (r_meters^2)
- local F_studs = metersToStuds(F)
- computedMagnitude = F_studs
- end
- local forceName = "NewtonianGravity_" .. BHId
- local existingForce = object:FindFirstChild(forceName)
- if math.abs(computedMagnitude) < MIN_FORCE_THRESHOLD then
- if existingForce then
- if existingForce.Attachment0 then
- existingForce.Attachment0:Destroy()
- end
- existingForce:Destroy()
- end
- return
- end
- local force = existingForce
- if not force then
- force = Instance.new("LineForce")
- force.Name = forceName
- local objectAttachment = Instance.new("Attachment")
- objectAttachment.Name = "NewtonianGravityAttachment"
- objectAttachment.Parent = object
- force.Attachment0 = objectAttachment
- force.Attachment1 = blackHoleAttachment
- force.ApplyAtCenterOfMass = true
- force.Visible = true
- force.Parent = object
- end
- force.Magnitude = computedMagnitude
- end
- ------------------------------------------------------
- -- Apply gravitational effects on each heartbeat
- ------------------------------------------------------
- game:GetService("RunService").Heartbeat:Connect(function()
- for _, object in pairs(workspace:GetDescendants()) do
- if useEinsteinian.Value then
- relativisticGravity(object) -- Einsteinian (Schwarzschild) approximation
- else
- newtonianGravity(object) -- Newtonian gravitational calculation
- end
- end
- -- After applying forces, check and toggle anchors if needed
- checkAndToggleAnchors()
- end)
- ---------------------------------
- -- VFX Monitoring (Dont touch) --
- ---------------------------------
- local function updateBlackHoleColor()
- local vfx = blackHole:FindFirstChild("VFX")
- if not vfx then
- blackHole.Color = Color3.fromRGB(0, 0, 0)
- return
- end
- local highlight = vfx:FindFirstChild("Highlight")
- if not highlight or (highlight:IsA("Highlight") and not highlight.Enabled) then
- blackHole.Color = Color3.fromRGB(0, 0, 0)
- else
- blackHole.Color = Color3.fromRGB(226, 155, 64)
- end
- end
- blackHole.ChildRemoved:Connect(function(child)
- if child.Name == "VFX" then
- updateBlackHoleColor()
- end
- end)
- blackHole.ChildAdded:Connect(function(child)
- if child.Name == "VFX" then
- local highlight = child:FindFirstChild("Highlight")
- if highlight and highlight:IsA("Highlight") then
- highlight.Changed:Connect(function(prop)
- if prop == "Enabled" then
- updateBlackHoleColor()
- end
- end)
- end
- updateBlackHoleColor()
- end
- end)
- local vfx = blackHole:FindFirstChild("VFX")
- if vfx then
- local highlight = vfx:FindFirstChild("Highlight")
- if highlight and highlight:IsA("Highlight") then
- highlight.Changed:Connect(function(prop)
- if prop == "Enabled" then
- updateBlackHoleColor()
- end
- end)
- end
- end
- updateBlackHoleColor()
- -----------------------------
- -- ID Locking (Dont touch) --
- -----------------------------
- local CollectionService = game:GetService("CollectionService")
- local function enforceIDName(idObj)
- local identity = "ID: " .. BHId
- if idObj.Name ~= identity then
- idObj.Name = identity
- end
- -- Listen for changes to the Name property and reset if changed
- idObj:GetPropertyChangedSignal("Name"):Connect(function()
- if idObj.Name ~= identity then
- idObj.Name = identity
- end
- end)
- end
- for _, idObj in ipairs(CollectionService:GetTagged("ID")) do
- if idObj.Parent == blackHole then
- enforceIDName(idObj)
- end
- end
Advertisement
Comments
-
- -- MathParserModule.lua
- local MathParser = {}
- -- Step 1: Tokenizer
- local function tokenize(expr)
- local tokens = {}
- local i = 1
- while i <= #expr do
- local c = expr:sub(i, i)
- if c:match("%s") then
- i = i + 1
- elseif c:match("%d") or c == '.' then
- local num = ""
- -- Capture the base number (integer or float)
- while i <= #expr and expr:sub(i, i):match("[%d%.]") do
- num = num .. expr:sub(i, i)
- i = i + 1
- end
- -- Check for scientific notation (immediately following without space)
- if i <= #expr and (expr:sub(i, i) == "e" or expr:sub(i, i) == "E") then
- num = num .. expr:sub(i, i)
- i = i + 1
- -- Optional sign after e/E
- if i <= #expr and (expr:sub(i, i) == "+" or expr:sub(i, i) == "-") then
- num = num .. expr:sub(i, i)
- i = i + 1
- end
- -- Append the exponent digits
- while i <= #expr and expr:sub(i, i):match("%d") do
- num = num .. expr:sub(i, i)
- i = i + 1
- end
- end
- table.insert(tokens, {type = "number", value = tonumber(num)})
- elseif c == "(" or c == ")" then
- table.insert(tokens, {type = "paren", value = c})
- i = i + 1
- else
- -- Try to match multi-character operators
- local op = nil
- -- If the operator "E" appears separated by spaces, treat it as an operator token.
- if expr:sub(i, i) == "E" then
- op = "E"
- i = i + 1
- else
- op = c
- i = i + 1
- end
- table.insert(tokens, {type = "operator", value = op})
- end
- end
- return tokens
- end
- -- Operator precedence and associativity
- local operators = {
- ["E"] = {precedence = 5, associativity = "right"}, -- Scientific notation operator
- ["^"] = {precedence = 4, associativity = "right"},
- ["*"] = {precedence = 3, associativity = "left"},
- ["/"] = {precedence = 3, associativity = "left"},
- ["+"] = {precedence = 2, associativity = "left"},
- ["-"] = {precedence = 2, associativity = "left"},
- }
- -- Step 2: Parser using the shunting-yard algorithm
- local function shuntingYard(tokens)
- local output = {}
- local opStack = {}
- for _, token in ipairs(tokens) do
- if token.type == "number" then
- table.insert(output, token)
- elseif token.type == "operator" then
- local o1 = token.value
- while #opStack > 0 do
- local top = opStack[#opStack]
- if top.type ~= "operator" then break end
- local o2 = top.value
- if (operators[o1].associativity == "left" and operators[o1].precedence <= operators[o2].precedence) or
- (operators[o1].associativity == "right" and operators[o1].precedence < operators[o2].precedence) then
- table.insert(output, table.remove(opStack))
- else
- break
- end
- end
- table.insert(opStack, token)
- elseif token.type == "paren" then
- if token.value == "(" then
- table.insert(opStack, token)
- else -- token.value == ")"
- while #opStack > 0 and opStack[#opStack].value ~= "(" do
- table.insert(output, table.remove(opStack))
- end
- if #opStack == 0 then
- error("Mismatched parentheses")
- end
- table.remove(opStack) -- remove "("
- end
- end
- end
- while #opStack > 0 do
- local top = table.remove(opStack)
- if top.value == "(" or top.value == ")" then
- error("Mismatched parentheses")
- end
- table.insert(output, top)
- end
- return output
- end
- -- Step 3: Evaluate Reverse Polish Notation (RPN)
- local function evaluateRPN(rpn)
- local stack = {}
- for _, token in ipairs(rpn) do
- if token.type == "number" then
- table.insert(stack, token.value)
- elseif token.type == "operator" then
- local b = table.remove(stack)
- local a = table.remove(stack)
- local result = nil
- if token.value == "+" then
- result = a + b
- elseif token.value == "-" then
- result = a - b
- elseif token.value == "*" then
- result = a * b
- elseif token.value == "/" then
- result = a / b
- elseif token.value == "^" then
- result = a ^ b
- elseif token.value == "E" then
- -- Evaluate as: a * (10^b)
- result = a * (10 ^ b)
- else
- error("Unknown operator: " .. token.value)
- end
- table.insert(stack, result)
- end
- end
- if #stack ~= 1 then
- error("Invalid expression")
- end
- return stack[1]
- end
- -- The universal parser function that catches errors and returns an error message
- function MathParser.evaluateExpression(expr)
- local tokens = tokenize(expr)
- local status, result = pcall(function()
- local rpn = shuntingYard(tokens)
- return evaluateRPN(rpn)
- end)
- if not status then
- return "Error: " .. result
- else
- return result
- end
- end
- return MathParser
-
- local IDGenerator = {}
- local charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
- function IDGenerator.GenerateID(length)
- local id = ""
- for i = 1, length do
- local randIndex = math.random(#charset)
- id = id .. charset:sub(randIndex, randIndex)
- end
- return id
- end
- return IDGenerator
-
- [ Welcome to Blackhole 5.1 Experimental Branch! ]
- ----------
- Quick Desc
- ----------
- Blackhole 5.1 is a beta version of future blackholes, especially for effecient and accurate gravities!
- This Blackhole version does not support fancy absorptions like previous Blackhole versions, but has significant improvements on gravitation.
- Please do not remove any asset within the Blackhole as they will cause errors!
- ---------------
- Live Parameters
- ---------------
- `G_(gravity)` (1) = Gravitation constant, very sensitive to different values, try 0.1 incrementals.
- `UseCustomMass` (false) = Use custom mass as current mass has restrictions. this does not affect the ACTUAL mass, it's only pseudo.
- `CustomMass` (5236) = The custom mass (default is current mass, but it may not be if resized).
- `UseEinsteinian` (true) = Use Einstein's gravity field equation: (G*M/r^2)*(1-Rs/r) where Rs is the Schwarzschild radius, else use Newtonian: (Gm1m2/r^2).
- `OverridePhysics` (false) = If true, disregard the gravity formulas and simply use (`Magnitude`/r^2), more info on `Magnitude`
- `Magnitude` (1000000) = The gravitational strength. (also typed as 1e6).
- c_(light_speed) (300000000) = Speed of light (used for Schwarzschild radius in Einsteinian gravity, also typed as 3e8).
- Schwarzschild radius = Event Horizon (the actual radius of blackhole), which the script uses to cancel gravity as r->0 outputs infinity.
- ------------
- Math Parsing
- ------------
- Follows mathematical syntax!
- Non boolean parameters can be hard to type for precise and or large values like 300000000 or 12300, but they can be typed as exponentials or scientific notations:
- 1.23e4 = 1.23*(10^4) = 12300
- It may output an error, this is how to use the Math Parsing system:
- Accepts PEDMAS or BEDMAS (Parentheses, Exponents, Division, Multiplication, Addition & Subtraction)
- a^4 = a*a*a*a, for roots: a^(1/2) = square root of a, whatever number in a^(1/n) is the n-th root.
- a/b, but b =/= 0.
- a*b...
- etc.
- Example:
- (1e2)^(3/4)+5-6 = 30.62...
Add Comment
Please, Sign In to add comment
Advertisement