Copy salient and essential scripts and models from Plane Building Roblox
This commit is contained in:
@@ -0,0 +1,435 @@
|
||||
-- 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}
|
||||
Reference in New Issue
Block a user