-- Module for handling all game objects require(game.ReplicatedFirst.ReadyModule) local input = require(_G.Modules.InputModule) -- Note that closures absorb upvalues. I'm trying this as a programming paradigm here. local modulesByName = {} local modules = {} -- [Name] = {...module} local predicates = {} local objects = {} -- Table of globally considered parts (responsible for considering) (consider = input and background tasks) local owning = {} -- Table of locally owned parts (responsible for all processing) -- Get the object from a part and do something to it local function asObject(part,callback) if part.Parent:GetAttribute("PartId") then return callback(part.Parent) -- Since parts can be INSIDE of objects elseif part:GetAttribute("PartId") then return callback(part) end end local processOwnership local context = { controls = {}, root = nil -- Where the system is controlling from (Seat) } context.message = function(instance,name,...) local stuff = {...} asObject(instance,function(object) local data = objects[object] if not data then return end if name == "" then name = "default" end name = name or "default" for _,module in pairs(modules[object.Name] or {}) do if module.messages then if module.messages[name] then if objects[object] then module.messages[name](data,instance,context,table.unpack(stuff)) end end end end end) end context.connected = function(wire_tag,end_tag,root,callback) local searched = {} local search = {root} while #search > 0 do local new_search = {} for _,search_part in pairs(search) do local connected = search_part:GetConnectedParts() for _,part in pairs(connected) do if not searched[part] then searched[part] = true if part:HasTag(wire_tag) then table.insert(new_search,part) if part:GetAttribute("PartId") and part:HasTag("Glow") then part.Material = Enum.Material.Neon end end if (not end_tag) or part:HasTag(end_tag) or part.Parent:HasTag(end_tag) then asObject(part,function(object) callback(part) end) end end end end search = new_search end end local queues = {} context.queue = function(this,tag,delta,valid) -- Init valid = valid or owning local queue = queues[tag] if queue then queue = {last = tick()} queues[tag] = queue end if not queue[this] then queue[this] = this table.insert(queue,1,this) end -- Get next in line local first = queue[1] while not valid[first] do table.remove(queue,1) first = queue[1] end -- Remove removed objects (lol) -- Are you ready!? if tick() - queue.last > delta then -- Aye aye captain!! queue.last = tick() -- Oooo table.remove(queue,1) table.insert(queue,this) return true -- hhhh else return false end end context.frequency = function(this,tag,delta) if not this[tag] then this[tag] = tick() end if tick() - this[tag] > delta then this[tag] = tick() return true else return false end end context.asObject = asObject if _G.IsServer then context.replicate = function(this,object,module,...) module.replicate(this,object,context,...) end elseif _G.IsClient then context.replicate = function(this,object,module,...) if module.replicate then module.replicate(this,object,context,...) end if tick() > module.next_replication then module.next_replication = tick() + module.replication_delta _G.Remotes.ObjectReplicate:FireServer(object,module.name,...) end end end local thisPlayer if _G.IsServer then thisPlayer = nil elseif _G.IsClient then thisPlayer = game.Players.LocalPlayer end -- Make object data from an instance (it's shared, so modules can communicate easily) local function new(object) local this = {} for module,predicate in pairs(predicates) do if predicate(object) then local tab = modules[object.Name] if not tab then tab = {} modules[object.Name] = tab end tab[module] = module end end for _,module in pairs(modules[object.Name] or {}) do _G.Protected(module.new,this,object,context) end objects[object] = this return this end local function remove(object) local this = objects[object] if not this then return end for _,module in pairs(modules[object.Name] or {}) do if module.remove then _G.Protected(module.remove,this,object,context) end end objects[object] = nil owning[object] = nil end local own -- Add an object to be considered by this client or server local function add(instance,owner) -- If this is not an instance or we already considered it then ignore local data = objects[instance] if data then return data end if not instance:GetAttribute("PartId") then return end data = new(instance) objects[instance] = data instance.Destroying:Connect(function() remove(instance) end) if _G.IsServer then if owner then data.owner = owner _G.Protected(function() if instance:IsA("Model") then instance.PrimaryPart:SetNetworkOwner(owner) if instance:FindFirstChild("Hinge") then instance.Hinge:SetNetworkOwner(owner) end else instance:SetNetworkOwner(owner) end end) else own(instance) end else if owner then own(instance) end end return data end local function update(instance) local data = objects[instance] if data then for _,module in pairs(modules[instance.Name] or {}) do if module.update then _G.Protected(module.update,data,instance,context) end end end end -- Make the client drop ownership and continue to run idle tasks local function sleep(object) if add(object) then owning[object] = nil end end -- Make the client own the object and take responsibility for processing own = function(object) if add(object) then owning[object] = objects[object] end end -- Get all parts connected to an instance, determine if the player is still connected in any way local hrp = {HumanoidRootPart = true} local function playerInSeat(seat) if (seat:IsA("Seat") or seat:IsA("VehicleSeat")) and seat.Occupant then return game.Players:GetPlayerFromCharacter(seat.Occupant.Parent) else local seat = seat:FindFirstChildWhichIsA("Seat") or seat:FindFirstChildWhichIsA("VehicleSeat") if seat and seat.Occupant then return game.Players:GetPlayerFromCharacter(seat.Occupant.Parent) end end end local vs = {VehicleSeat = true} local function maxRankSeat(seat1,seat2) if not seat1 then return seat2 end if not seat2 then return seat1 end local priority1 = seat1:GetAttribute("Priority") or 0 local priority2 = seat2:GetAttribute("Priority") or 0 -- A seat has priority over the other if priority1 == priority2 then local class1 = vs[seat1.Name] local class2 = vs[seat2.Name] -- One seat is a VehicleSeat and the other isn't if class1 and not class2 then return seat1 elseif class2 and not class1 then return seat2 else local owner1 = game.Players:GetPlayerByUserId(seat1:GetAttribute("Owner") or 0) == playerInSeat(seat1) local owner2 = game.Players:GetPlayerByUserId(seat2:GetAttribute("Owner") or 0) == playerInSeat(seat2) -- One player is seating in a seat that they placed themselves if owner1 and not owner2 then return seat1 elseif owner2 and not owner1 then return seat2 else return seat1 -- Since it was probably closest end end elseif priority1 > priority2 then return seat1 else return seat2 end end local function getParts(instance) local searchedParts = {} -- Searched connected instance parts local objectsFound = {} -- Found connected objects local assemblyRoots = {} -- Found AssemblyRootParts local playersSeated = {} -- Players that are connected to this vehicle in some way and their associated seat -- Recursive function to look through all the parts local function search(part) -- DRY local function add(instance) searchedParts[instance] = true local root = instance.AssemblyRootPart if not assemblyRoots[root] then assemblyRoots[root] = root end asObject(instance,function(object) if instance:FindFirstChild("SeatWeld") then local hrp = instance.SeatWeld.Part1 local player = game.Players:GetPlayerFromCharacter(hrp.Parent) if player then -- Players could be in multiple seats so just pick the highest one playersSeated[player] = maxRankSeat(playersSeated[player],object) end end objectsFound[object] = object end) end -- If not found, add it, otherwise return if not searchedParts[part] then add(part) else return end -- For all the connected parts do the same thing, but not 'search' since it causes a bunch of excess checking for _,part in pairs(part:GetConnectedParts(true)) do -- If not found, add it, otherwise continue if not searchedParts[part] then add(part) else continue end -- If the part is connected to a Character local joints = part:GetJoints() for _,joint in pairs(joints) do if joint:IsA("HingeConstraint") or joint:IsA("PrismaticConstraint") then -- Also check joints since their network ownership is funky otherwise if joint.Attachment0 and joint.Attachment1 then search(joint.Attachment0.Parent) search(joint.Attachment1.Parent) end end end end end search(instance) return objectsFound,playersSeated,assemblyRoots end -- Out of a list of player-seats get the highest priority player seated (nil == server) local function maxPlayerSeated(playersSeated) local maxPlayer,maxSeat = nil for player,seat in pairs(playersSeated) do if maxRankSeat(maxSeat,seat) then maxPlayer = player maxSeat = seat end end return maxPlayer end local function mark(object) if object:FindFirstChild("Mark") then object.Mark:Destroy() end local box = Instance.new("SelectionBox") box.Parent = object box.Adornee = object box.Name = "Mark" return box end -- This sort of runs on every client, so maybe it's far too slow? processOwnership = function(seat) local objectsFound,playersSeated,assemblyRoots = getParts(seat) local ownerPlayer = maxPlayerSeated(playersSeated) -- The server needs to change the network owner in this case if _G.IsServer then _G.Remotes.OwnershipReplicate:FireAllClients(seat) for _,root in pairs(assemblyRoots) do local success,result = pcall(function() root:SetNetworkOwner(ownerPlayer) end) if not success then warn(result) end end for object in pairs(objectsFound) do local data = objects[object] if data then data.owner = ownerPlayer end end if ownerPlayer then for object in pairs(objectsFound) do sleep(object) end end end if ownerPlayer == thisPlayer then for _,object in pairs(objectsFound) do -- Connected and Owning --if _G.IsClient then mark(object).Color3 = Color3.fromRGB(0,255,0) end own(object) end elseif playersSeated[thisPlayer] then -- Connected and not Owning for _,object in pairs(objects) do if objectsFound[object] then --if _G.IsClient then mark(object).Color3 = Color3.fromRGB(255,0,0) end sleep(object) end end elseif _G.IsClient then -- Disconnected and not Owning for object,data in pairs(objects) do if objectsFound[object] then --if _G.IsClient then mark(object).Color3 = Color3.fromRGB(0,0,255) end remove(object) end end end if _G.IsClient then local ourSeat = playersSeated[thisPlayer] if ourSeat then input.seat = ourSeat context.seat = ourSeat elseif input.seat == asObject(seat,function(...) return ... end) then input.seat = nil context.seat = nil end end end local function objectsProcess(delta) context.delta = delta context.cursor = input.cursor for object,data in pairs(owning) do for _,module in pairs(modules[object.Name] or {}) do if module.run then if not module.run(data,object,context) then remove(object) end end end end for object,data in pairs(objects) do for _,module in pairs(modules[object.Name] or {}) do if module.idle then if not module.idle(data,object,context) then remove(object) end end end end for _,module in pairs(modulesByName) do if module.loop then module.loop(context) end end end if _G.IsServer then -- Clients and servers will send this to update information between each other (should be handled by values) _G.Remotes.ObjectReplicate.OnServerEvent:Connect(function(player,object,name,...) local data = objects[object] if data and data.owner == player then modulesByName[name].replicate(data,object,context,...) end end) game.Players.PlayerRemoving:Connect(function(player) for object,data in pairs(objects) do if data.owner == player then own(object) end end end) elseif _G.IsClient then -- Respond to processing events from the server _G.Remotes.OwnershipReplicate.OnClientEvent:Connect(function(seat) processOwnership(seat) end) end for _,instance in pairs(script.Parent.Objects:GetChildren()) do local module = require(instance) modulesByName[instance.Name] = module module.name = instance.Name module.next_replication = tick() module.replication_delta = module.replication_delta or 0.5 if module.manifest.predicate then predicates[module] = module.manifest.predicate else for _,name in pairs(module.manifest) do modules[name] = modules[name] or {} table.insert(modules[name],module) end end end if _G.IsServer then for _,instance in pairs(game.Workspace.Objects:GetChildren()) do add(instance) end end spawn(function() _G.Run.Heartbeat:Connect(function(delta) if _G.IsClient then context.controls = input.values end objectsProcess(delta) end) end) return {processOwnership = processOwnership, add = add, update = update, own = own}