793 lines
24 KiB
Plaintext
793 lines
24 KiB
Plaintext
require(game.ReplicatedFirst.ReadyModule)("ServerScript")
|
|
|
|
local objectModule = require(_G.Modules.ObjectModule)
|
|
--local objectInteraction = require(_G.Modules.ObjectInteraction.Main)
|
|
local rules = require(_G.Modules.RuleModule)
|
|
local commands = require(_G.Modules.CommandsModule)
|
|
local partStateModule = require(_G.Modules.PartStateModule)
|
|
|
|
DEFAULT_MAX_SAVES = 20
|
|
SAVE_DATA_LIMIT = 5*10^6
|
|
PLAYER_INTERACTION_DISTANCE = 100
|
|
PLAYER_MAX_FLOOD = 10
|
|
PLAYER_FLOOD_TICK = 4
|
|
VEHICLE_SAVE_STORE = "VehicleSaveStore"
|
|
VEHICLE_SAVE_INFO_STORE = "VehicleSaveInfo"
|
|
USER_MOD_STORE = "ModStore"
|
|
MAIN_GAME_PLACEID = 11370438752
|
|
PLACE_COOLDOWN = 0.75
|
|
|
|
--[[if game.PlaceId ~= MAIN_GAME_PLACEID then
|
|
VEHICLE_SAVE_STORE = "VehicleSaveStoreTest"
|
|
VEHICLE_SAVE_INFO_STORE = "VehicleSaveInfoTest"
|
|
USER_MOD_STORE = "ModStoreTest"
|
|
end]]
|
|
|
|
for index,value in pairs(_G.Rules[_G.RuleMode]) do
|
|
rules.setRule(index,value)
|
|
end
|
|
|
|
local function GetStoreData(store,key,ver)
|
|
local success,value = pcall(function() -- Protection call
|
|
local data
|
|
if ver then -- When a version is provided get it based on that
|
|
data = store:GetVersionAsync(key,ver)
|
|
else
|
|
data = store:GetAsync(key)
|
|
end
|
|
return data
|
|
end)
|
|
if ver then print(store,key,ver) end
|
|
if not success then warn(value) end
|
|
return success,value
|
|
end
|
|
|
|
local function SetStoreData(store,key,val)
|
|
local success = pcall(function()
|
|
store:SetAsync(key,val)
|
|
end)
|
|
return success
|
|
end
|
|
|
|
PartExperience = {}
|
|
PartCost = {}
|
|
|
|
local saves = {}
|
|
|
|
local players = {}
|
|
|
|
local admins = {
|
|
[558665455] = true
|
|
}
|
|
|
|
local _player = {} -- Metatable for player class
|
|
_player.__index = _player
|
|
|
|
_player.Remove = function(this) -- Clear data
|
|
players[this.userid] = nil
|
|
players[this.instance] = nil
|
|
end
|
|
|
|
_player.IsAdmin = function(this) -- Has commands permissions
|
|
if admins[this.instance.UserId] then return true end
|
|
if this.admin then return true end
|
|
end
|
|
|
|
_player.DrainFloodGate = function(this,amount)
|
|
this.flood = this.flood - amount
|
|
if this.flood < 0 then this.flood = 0 end
|
|
end
|
|
|
|
_player.CheckLastPlaced = function(this)
|
|
local possible = ((tick() - this.last_placed) > PLACE_COOLDOWN)
|
|
if possible then this.last_placed = tick() end
|
|
return possible
|
|
end
|
|
|
|
_player.CheckFloodGate = function(this,amount)
|
|
this.flood = this.flood + amount
|
|
if this.flood > PLAYER_MAX_FLOOD then return false else return true end
|
|
end
|
|
|
|
_player.OwnsPart = function(this,part)
|
|
return _G.Methods.playerOwnsPart(this.instance,part)
|
|
end
|
|
|
|
_player.OwnsSave = function(this,group) -- Owns a save DataStore if...
|
|
if this:IsAdmin() or group:match("public") or group == this.userid then
|
|
return true
|
|
else
|
|
this:Notify("Permission Error","You do not own this save: "..group)
|
|
warn("Permission error: "..group.." "..this.userid)
|
|
return false
|
|
end
|
|
end
|
|
|
|
_player.InRange = function(this,cframe) -- Is the player close enough to interact
|
|
this = this.instance
|
|
if
|
|
not this or
|
|
not cframe or
|
|
not this.Character or
|
|
not this.Character.PrimaryPart
|
|
then
|
|
print("Bad parameters",this,cframe,this.Character)
|
|
end
|
|
return (cframe-this.Character.PrimaryPart.Position).Position.Magnitude < PLAYER_INTERACTION_DISTANCE
|
|
end
|
|
|
|
_player.CanAfford = function(this,part)
|
|
end
|
|
|
|
_player.CanUsePart = function(this,part)
|
|
local experience = PartExperience[part]
|
|
if experience and experience > this.stats.experience then return false end
|
|
local cost = PartCost[part]
|
|
if cost and cost > this.stats.money then return false end
|
|
return not not part:IsDescendantOf(_G.Storage.Parts)
|
|
end
|
|
|
|
_player.Notify = function(this,title,message)
|
|
if this.dummy then return end
|
|
_G.Remotes.Notification:FireClient(this.instance,title,message)
|
|
end
|
|
|
|
_player.GetParts = function(this)
|
|
return _G.Tags:GetTagged("P_"..this.userid)
|
|
end
|
|
|
|
_player.WaitForStatistics = function(this)
|
|
repeat
|
|
wait()
|
|
until this.stats
|
|
end
|
|
|
|
_player.EvaluateSaves = function(this)
|
|
this:WaitForStatistics()
|
|
if this.evaluate_save_lock then repeat wait() until not this.evaluate_save_lock end
|
|
this.evaluate_save_lock = true
|
|
this.max_saves = DEFAULT_MAX_SAVES + (this.stats.additional_saves or 0)
|
|
if game:GetService("MarketplaceService"):UserOwnsGamePassAsync(tonumber(this.userid),1014956678) then
|
|
this.max_saves = this.max_saves + 20
|
|
end
|
|
this.evaluate_save_lock = false
|
|
end
|
|
|
|
_player.LoadSave = function(this,group,key)
|
|
if not this:CheckFloodGate(1) then return end
|
|
|
|
--print(string.format("pg:%s,pk:%s",group,key))
|
|
|
|
local ver
|
|
local _group,_key = group:match("(.-):(.+)")
|
|
if _group and _key then ver = key key = _key group = _group end
|
|
|
|
if not players[group] then return end
|
|
if not players[group].saves then error(group,key) end
|
|
|
|
if not this:OwnsSave(group) then return end
|
|
|
|
--print(string.format("k:%s,g:%s,v:%s",key,group,ver or "nil"))
|
|
if players[group].saves[key..(ver or "")] then
|
|
return players[group].saves[key]
|
|
else
|
|
local success,data = GetStoreData(players[group].stores.saves,key,ver)
|
|
if success then
|
|
if not ver then players[group].saves[key] = data end
|
|
return data
|
|
end
|
|
end
|
|
end
|
|
|
|
players = {
|
|
public = setmetatable({
|
|
parttag = "P_ublic",
|
|
userid = "public",
|
|
flood = -math.huge,
|
|
saves = {},
|
|
stores = {
|
|
saves = _G.Data:GetDataStore(VEHICLE_SAVE_STORE,"public"),
|
|
save_info = _G.Data:GetDataStore(VEHICLE_SAVE_INFO_STORE,"public")
|
|
}
|
|
},_player)
|
|
}
|
|
|
|
local function GetPlayer(id) -- Get a player by their id AS A STRING
|
|
local player = players[id] or players[tostring(id)]
|
|
if not player then error("No player data identified by "..tostring(id),2) end
|
|
return player
|
|
end
|
|
|
|
local function AddPlayer(instance)
|
|
local data = { -- The player data...
|
|
instance = instance, -- Player instance in game.Players
|
|
userid = tostring(instance.UserId),
|
|
dummy = not instance.Parent,
|
|
name = instance.DisplayName,
|
|
saves = {}, -- Vehicle{} []
|
|
flood = 0, -- If this gets too high, block remotes
|
|
stats = nil,
|
|
join = tick(),
|
|
last_placed = tick(),
|
|
max_saves = DEFAULT_MAX_SAVES,
|
|
parttag = "P_"..tostring(instance.UserId),
|
|
stores = {} -- DataStore
|
|
}
|
|
setmetatable(data,_player) -- ...made into a class
|
|
players[instance] = data -- Register them by id and instance
|
|
players[data.userid] = data
|
|
data.stores.saves = _G.Data:GetDataStore(VEHICLE_SAVE_STORE,data.userid) -- Vehicles
|
|
data.stores.mods = _G.Data:GetDataStore(USER_MOD_STORE,data.userid) -- Properties
|
|
data.stores.save_info = _G.Data:GetDataStore(VEHICLE_SAVE_INFO_STORE,data.userid) -- Vehicle Save Info
|
|
_,data.stats = GetStoreData(data.stores.mods,"stats")
|
|
if data.stats then
|
|
data.stats = _G.Http:JSONDecode(data.stats)
|
|
else
|
|
data.stats = {}
|
|
end
|
|
_G.Default(data.stats,{
|
|
experience = 0,
|
|
money = 100,
|
|
extra_saves = 0,
|
|
playtime = 0,
|
|
color = require(_G.Modules.NameColorModule).ComputeNameColor(instance.Name)
|
|
})
|
|
wait(2)
|
|
local playtime = data.stats.playtime
|
|
if playtime ~= 0 then
|
|
local ending = "seconds"
|
|
if playtime > 60 then ending = "minutes" playtime = playtime / 60
|
|
if playtime > 60 then ending = "hours" playtime = playtime / 60
|
|
if playtime > 24 then ending = "days" playtime = playtime / 24
|
|
end
|
|
end
|
|
end
|
|
data:Notify("Welcome",string.format("Welcome back to Plane Building! You've been playing for %d %s!",math.floor(playtime),ending))
|
|
if game.PlaceId ~= MAIN_GAME_PLACEID then
|
|
data:Notify("Warning",string.format("You are not in the main game place! Irreversible save corruption and gameplay bugs are likely!"))
|
|
end
|
|
else
|
|
data:Notify("Welcome","Hey newbie, have a warm welcome to Plane Building! Thanks so much for playing!")
|
|
end
|
|
end
|
|
game.Players.PlayerAdded:Connect(AddPlayer)
|
|
|
|
local function RemovePlayer(instance) -- For the event
|
|
local player = GetPlayer(instance)
|
|
player.stats.playtime = player.stats.playtime + (tick() - player.join)
|
|
SetStoreData(player.stores.mods,"stats",_G.Http:JSONEncode(player.stats))
|
|
player:Remove()
|
|
end
|
|
game.Players.PlayerRemoving:Connect(RemovePlayer)
|
|
|
|
local function Weld(part1,part2,temporary) -- Make a BUILDING weld
|
|
local weld = Instance.new("WeldConstraint")
|
|
weld.Name = "BuiltWeldConstraint"
|
|
weld.Parent = part1
|
|
weld.Part0 = part1
|
|
weld.Part1 = part2
|
|
if temporary then weld.Name = "WeldConstraint" end -- Or not.
|
|
return weld
|
|
end
|
|
|
|
local function SetupPart(player,part)
|
|
part = part:Clone()
|
|
part.Parent = game.Workspace.Objects
|
|
_G.Tags:AddTag(part,"P_"..player.userid)
|
|
part:SetAttribute("NetworkOwner",player.userid)
|
|
return part
|
|
end
|
|
|
|
local function CopyTags(part1,part2)
|
|
for _,tag in pairs(_G.Tags:GetTags(part2)) do
|
|
if not part1:HasTag(tag) and string.sub(tag,1,2) == "P_" then
|
|
part1:AddTag(tag)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function AttemptWeld(player,part,connection)
|
|
local new_weld
|
|
if connection and
|
|
(player:OwnsPart(connection) or player:OwnsPart(connection.Parent))
|
|
then
|
|
local attach = part
|
|
if part:IsA("Model") then attach = attach.PrimaryPart end
|
|
new_weld = Weld(attach,connection)
|
|
new_weld.Parent = part
|
|
end
|
|
CopyTags(part,connection)
|
|
return new_weld
|
|
end
|
|
|
|
local function ResizePart(part,size)
|
|
if part:IsA("Model") then
|
|
part:ScaleTo(size.Magnitude)
|
|
else
|
|
part.Size = size
|
|
end
|
|
part:SetAttribute("Size",size)
|
|
end
|
|
|
|
local function _CreatePart(player,part,cframe,connection,scale)
|
|
local player = GetPlayer(player)
|
|
player:CheckFloodGate(4)
|
|
if
|
|
not player:CheckLastPlaced() or
|
|
not player:CanUsePart(part)
|
|
then
|
|
return
|
|
end
|
|
if connection then cframe = connection.CFrame:ToWorldSpace(cframe) end
|
|
if not player:InRange(cframe) then return end
|
|
part = part:Clone()
|
|
part.Parent = game.Workspace.Objects
|
|
if scale then ResizePart(part,scale) end
|
|
_G.Methods.setItemCFrame(part,cframe)
|
|
AttemptWeld(player,part,connection)
|
|
--[[if part:IsA("Model") then
|
|
_G.Methods.setNetworkOwner(part.PrimaryPart,player.instance)
|
|
else
|
|
_G.Methods.setNetworkOwner(part,player.instance)
|
|
end]]
|
|
part:AddTag(player.parttag)
|
|
wait()
|
|
return part
|
|
end
|
|
|
|
local function ConfigurePart(player,part,own)
|
|
part:AddTag(player.parttag)
|
|
part.Parent = game.Workspace.Objects
|
|
local owner
|
|
if own then owner = player.instance end
|
|
objectModule.add(part,owner)
|
|
part:SetAttribute("Owner",player.instance.UserId)
|
|
--local modules = objectInteraction.modulesByName[part.Name]
|
|
--if modules then for _,module in pairs(modules) do if module.setup then module.setup(part) end end end
|
|
end
|
|
|
|
local function CreatePart(player,id,...)
|
|
local player = GetPlayer(player)
|
|
player:CheckFloodGate(4)
|
|
local template = partStateModule.get_part_by_id(id)
|
|
if not template then error(string.format("No part for id: "..tostring(id))) return end
|
|
if
|
|
not player:CheckLastPlaced() or
|
|
not player:CanUsePart(template)
|
|
then
|
|
return
|
|
end
|
|
local state = partStateModule.new()
|
|
state.owner = player.instance
|
|
state:deserialise(id,...)
|
|
local part = state:create()
|
|
ConfigurePart(player,part,true)
|
|
wait()
|
|
return part
|
|
end
|
|
_G.Remotes.Create.OnServerInvoke = CreatePart
|
|
|
|
local function _CreatePoly(player,part,nodes,connections)
|
|
local player = GetPlayer(player)
|
|
if
|
|
player:CheckFloodGate(6) and
|
|
player:CheckLastPlaced() and
|
|
player:CanUsePart(part)
|
|
then
|
|
for index,node in pairs(nodes) do
|
|
if node.connection then node.cframe = node.connection.CFrame:ToWorldSpace(node.cframe) end
|
|
end
|
|
for index,node in pairs(nodes) do
|
|
if (nodes[1].cframe.Position - node.cframe.Position).Magnitude > 80 then return end
|
|
if not player:InRange(node.cframe) then return end
|
|
end
|
|
part = part:Clone()
|
|
part.Parent = game.Workspace.Objects
|
|
part:AddTag(player.parttag)
|
|
if #nodes == 2 then
|
|
local a = nodes[1].cframe
|
|
local b = nodes[2].cframe
|
|
local d = (a-b).Magnitude
|
|
part.CFrame = CFrame.lookAt(a.Position,b.Position,a.UpVector) * CFrame.new(d * 0.5,0,0)
|
|
elseif #nodes == 3 then
|
|
_G.Methods.form_triangle(part.A,part.B,nodes[1].cframe.Position,nodes[2].cframe.Position,nodes[3].cframe.Position)
|
|
end
|
|
Weld(part.A,part.B)
|
|
for _,connection in pairs(connections) do
|
|
AttemptWeld(player,part.PrimaryPart,connection)
|
|
end
|
|
return part
|
|
end
|
|
end
|
|
--_G.Remotes.Poly.OnServerInvoke = CreatePoly
|
|
|
|
local function TweakEffect(player,effect,rate,enabled)
|
|
effect.Enabled = enabled
|
|
effect.Rate = rate
|
|
end
|
|
_G.Remotes.Effects.OnServerEvent:Connect(TweakEffect)
|
|
|
|
|
|
local function ExplodePart(player,part)
|
|
local player = GetPlayer(player)
|
|
if
|
|
not player:CheckFloodGate(1) or
|
|
not (player:OwnsPart(part) or
|
|
player:OwnsPart(part.Parent))
|
|
then
|
|
return
|
|
end
|
|
_G.Methods.explode(part)
|
|
end
|
|
_G.Remotes.Explode.OnServerEvent:Connect(ExplodePart)
|
|
|
|
local function DeletePart(player,part,destruct)
|
|
local player = GetPlayer(player)
|
|
if
|
|
part and
|
|
player:CheckFloodGate(1) and
|
|
player:OwnsPart(part)
|
|
then
|
|
if destruct then
|
|
for _,part in pairs(part:GetChildren()) do
|
|
if part:IsA("BasePart") then
|
|
for _,joint in pairs(part:GetJoints()) do
|
|
if joint.Name == "BuiltWeldConstraint" then joint:Destroy() end
|
|
end
|
|
end
|
|
end
|
|
else
|
|
part:Destroy()
|
|
end
|
|
end
|
|
end
|
|
_G.Remotes.Delete.OnServerInvoke = DeletePart
|
|
|
|
local function ClearParts(player)
|
|
local player = GetPlayer(player)
|
|
if not player:CheckFloodGate(1) then return end
|
|
for _,part in pairs(player:GetParts()) do
|
|
part:Destroy()
|
|
end
|
|
end
|
|
_G.Remotes.Clear.OnServerInvoke = ClearParts
|
|
|
|
local function ShareParts(player,id,parts)
|
|
local player = GetPlayer(player)
|
|
if not player:CheckFloodGate(1) then return end
|
|
if not parts then parts = player:GetParts() end
|
|
for _,part in pairs(parts) do
|
|
_G.Storage.TeamBox:Clone().Parent = part
|
|
part.TeamBox.Adornee = part
|
|
_G.Debris:AddItem(part.TeamBox,0.5)
|
|
if not id then
|
|
part:AddTag(players.public.parttag)
|
|
else
|
|
part:AddTag(GetPlayer(id).parttag)
|
|
end
|
|
end
|
|
end
|
|
_G.Remotes.Collaborate.OnServerInvoke = ShareParts
|
|
|
|
local function EnableCombat(player,parts)
|
|
local player = GetPlayer(player)
|
|
if not player:CheckFloodGate(1) then return end
|
|
if not parts then parts = player:GetParts() end
|
|
for _,part in pairs(parts) do
|
|
_G.Storage.DangerBox:Clone().Parent = part
|
|
part.DangerBox.Adornee = part
|
|
_G.Debris:AddItem(part.DangerBox,0.5)
|
|
part:AddTag("Combat")
|
|
end
|
|
end
|
|
_G.Remotes.Combat.OnServerInvoke = EnableCombat
|
|
|
|
local function SaveVehicle(player,data,key,group)
|
|
player = GetPlayer(player)
|
|
player:EvaluateSaves()
|
|
if ((key ~= "autosave") and (player.max_saves < tonumber(key))) then
|
|
player:Notify("Save Error","You do not have enough space.")
|
|
return
|
|
end
|
|
if not player:OwnsSave(group) then return end
|
|
local success,value = SetStoreData(players[group].stores.saves,key,data)
|
|
if not success then player:Notify("Vehicle Save Error",value) error(value) return end
|
|
local length = #data
|
|
local suffix = ""
|
|
if length > 5000 then suffix = "K" length = math.floor(length/1000) end
|
|
player:Notify("Vehicle Saved","Save success, remember to save regularly! Size: "..length..suffix.."C")
|
|
players[group].saves[key] = data
|
|
return success
|
|
end
|
|
_G.Remotes.Save.OnServerInvoke = SaveVehicle
|
|
|
|
local function GetSaveVersionInfo(group,key)
|
|
local store = players[group].stores.saves
|
|
local listSuccess, pages = pcall(function()
|
|
return store:ListVersionsAsync(key)
|
|
end)
|
|
local result = {}
|
|
if listSuccess then
|
|
local items
|
|
while true do
|
|
items = pages:GetCurrentPage()
|
|
for key, info in pairs(items) do
|
|
if not info.IsDeleted then
|
|
result[info.Version] = os.date("%c",info.CreatedTime/1000)
|
|
end
|
|
end
|
|
if pages.IsFinished then break else pages:AdvanceToNextPageAsync() end
|
|
end
|
|
else
|
|
warn(pages)
|
|
end
|
|
return result
|
|
end
|
|
|
|
SAVE_INFO_EMPTY = "Empty"
|
|
EMPTY_SAVE_INFO = {autosave = SAVE_INFO_EMPTY}
|
|
for i=1,DEFAULT_MAX_SAVES do EMPTY_SAVE_INFO[tostring(i)] = SAVE_INFO_EMPTY end
|
|
local function GetSaveInfo(player,group)
|
|
player = GetPlayer(player)
|
|
group = group or player.userid
|
|
local key
|
|
if group:match("(.-):(.+)") then group,key = group:match("(.-):(.+)") end
|
|
if not player:OwnsSave(group) then return end
|
|
local max_saves = DEFAULT_MAX_SAVES
|
|
if group == player.userid then player:EvaluateSaves() max_saves = player.max_saves end
|
|
if key then
|
|
return GetSaveVersionInfo(group,key)
|
|
else
|
|
local success,data = GetStoreData(players[group].stores.save_info,"info")
|
|
if not success then
|
|
player:Notify("Save Info Error: ","Your save info failed to load, this is a fatal error, try rejoining.")
|
|
return
|
|
end
|
|
local save_info = data or {}
|
|
if not save_info.autosave then save_info.autosave = "Unused" end
|
|
for index,item in ipairs(save_info) do
|
|
local data = save_info[index]
|
|
save_info[index] = nil
|
|
save_info[tostring(index)] = data
|
|
end
|
|
for i = 1,max_saves do if not save_info[tostring(i)] then save_info[tostring(i)] = "Empty" end end -- fill any missing slots
|
|
return save_info
|
|
end
|
|
end
|
|
_G.Remotes.GetSaveInfo.OnServerInvoke = GetSaveInfo
|
|
|
|
local function SetSaveInfo(player,data,group)
|
|
player = GetPlayer(player)
|
|
if not player:OwnsSave(group) then return end
|
|
player:EvaluateSaves()
|
|
for index,item in pairs(data) do
|
|
if not tonumber(index) and index ~= "autosave" then
|
|
player:Notify("Malformed save data! Invalid string: "..index)
|
|
return
|
|
end
|
|
end
|
|
if #data > player.max_saves then
|
|
player:Notify("You do not have enough save slots! "..tostring(#data-1))
|
|
return
|
|
end
|
|
SetStoreData(players[group].stores.save_info,"info",data)
|
|
return data
|
|
end
|
|
_G.Remotes.SetSaveInfo.OnServerInvoke = SetSaveInfo
|
|
|
|
local function LoadPlayerSave(player,...)
|
|
return GetPlayer(player):LoadSave(...)
|
|
end
|
|
_G.Remotes.Load.OnServerInvoke = LoadPlayerSave
|
|
|
|
local function ConfigurePlayerVehicle(player,model)
|
|
for _,part in pairs(model:GetChildren()) do
|
|
ConfigurePart(player,part)
|
|
end
|
|
end
|
|
|
|
local function PlaceVehicle(player,cframe,key,group)
|
|
local player = GetPlayer(player)
|
|
if not player:CheckFloodGate(3) then return end
|
|
for _,part in pairs(_G.Tags:GetTagged("P_"..player.userid)) do
|
|
part:Destroy()
|
|
end
|
|
local raw = player:LoadSave(group,key)
|
|
if not raw then return end
|
|
local model = _G.Methods.saves.dataToModel(raw,true)
|
|
if not model then return end
|
|
model.Parent = game.Workspace
|
|
local primary = model:FindFirstChildWhichIsA("VehicleSeat",true)
|
|
if primary then
|
|
model.PrimaryPart = primary
|
|
end
|
|
model:PivotTo(cframe)
|
|
--[[task.defer(function()
|
|
if primary then _G.Methods.setNetworkOwner(primary,player.instance) end
|
|
end)]]
|
|
ConfigurePlayerVehicle(player,model)
|
|
end
|
|
_G.Remotes.Place.OnServerInvoke = PlaceVehicle
|
|
|
|
-- i hate this...
|
|
local function PlaceSubassembly(player,cframe,data,connection)
|
|
local player = GetPlayer(player)
|
|
if not player:CheckFloodGate(10) then return end
|
|
local model = _G.Methods.saves.dataToModel(data,true)
|
|
model:PivotTo(cframe)
|
|
if player:OwnsPart(connection) then Weld(model.PrimaryPart,connection) end
|
|
ConfigurePlayerVehicle(player,model)
|
|
end
|
|
_G.Remotes.PlaceSubassembly.OnServerInvoke = PlaceSubassembly
|
|
|
|
local function PlaySoundEffect(player,sound)
|
|
if not GetPlayer(player):CheckFloodGate(1) then return end
|
|
sound:Play()
|
|
end
|
|
_G.Remotes.PlaySoundEffect.OnServerEvent:Connect(PlaySoundEffect)
|
|
|
|
local function ChangeSoundEffect(player,sound,volume,pitch)
|
|
if not GetPlayer(player):CheckFloodGate(1) then return end
|
|
if sound then
|
|
if volume then sound.Volume = volume end
|
|
if pitch then sound.Pitch = pitch end
|
|
end
|
|
end
|
|
_G.Remotes.ChangeSoundEffect.OnServerEvent:Connect(ChangeSoundEffect)
|
|
|
|
local function Paint(player,part,colour) -- Colour is spelt with a 'u' (pun intended)
|
|
player = GetPlayer(player)
|
|
if not player:CheckFloodGate(1) then return end
|
|
if not player:OwnsPart(part) then return end
|
|
_G.Methods.colorPart(part,colour)
|
|
end
|
|
_G.Remotes.Paint.OnServerEvent:Connect(Paint)
|
|
|
|
local function Admin(player)
|
|
player = GetPlayer(player)
|
|
if not player:CheckFloodGate(1) then return end
|
|
return player:IsAdmin()
|
|
end
|
|
_G.Remotes.Admin.OnServerInvoke = Admin
|
|
|
|
local function SnapPart(player,weld)
|
|
local player = GetPlayer(player)
|
|
if not player:CheckFloodGate(1) then return end
|
|
if weld.Parent and (player:OwnsPart(weld.Parent) or player:OwnsPart(weld.Parent.Parent)) and weld.Name == "BuiltWeldConstraint" then
|
|
weld:Destroy()
|
|
if weld.Part0 and weld.Part0.Parent:FindFirstChild("BombScript") then weld.Part0.Parent.BombScript.Enabled = true end
|
|
if weld.Part1 and weld.Part1.Parent:FindFirstChild("BombScript") then weld.Part1.Parent.BombScript.Enabled = true end
|
|
else
|
|
print("failed",weld:GetFullName())
|
|
end
|
|
end
|
|
_G.Remotes.Snap.OnServerEvent:Connect(SnapPart)
|
|
|
|
local function EditPart(player,part,edit,value)
|
|
if not GetPlayer(player):CheckFloodGate(1) then return end
|
|
if GetPlayer(player):OwnsPart(part) then
|
|
if edit == "Size" then
|
|
ResizePart(part,value)
|
|
end
|
|
if _G.IsNumber(value) then
|
|
local min = edit:GetAttribute("Lower") or -math.huge
|
|
local max = edit:GetAttribute("Upper") or math.huge
|
|
edit.Value = math.clamp(value,min,max)
|
|
else
|
|
edit.Value = value
|
|
end
|
|
objectModule.update(part)
|
|
--[[local modules = objectInteraction.modulesByName[part.Name] or {}
|
|
for _,module in pairs(modules) do
|
|
if module.setup then module.setup(part) end
|
|
end]]
|
|
end
|
|
end
|
|
_G.Remotes.Edit.OnServerInvoke = EditPart
|
|
|
|
--_G.Remotes.Sell
|
|
|
|
commands.new("get user id (.+)","get user id <name>: Get's a player's userid by name.",function(player,name)
|
|
local player = GetPlayer(player)
|
|
if name then
|
|
player:Notify("Get User Id",game.Players:GetUserIdFromNameAsync(name))
|
|
end
|
|
end)
|
|
commands.new("open player (.+)","open player <userid>: Load a player's data on the server.",function(player,userid)
|
|
local player = GetPlayer(player)
|
|
if not player:IsAdmin() then player:Kick("Security") return end
|
|
if userid then
|
|
local name = game.Players:GetNameFromUserIdAsync(tonumber(userid))
|
|
AddPlayer({UserId = tonumber(userid),DisplayName = name,Name = name})
|
|
player:Notify("Loading Virtual Player",name)
|
|
end
|
|
end)
|
|
commands.new("set save ([^%s]+) (%d+) ([^%s+]) data:(.*)","set store <store> <userid> <key> <value>: Set the save slot data.",function(player,store,userid,key,value)
|
|
local player = GetPlayer(player)
|
|
if not player:IsAdmin() then player:Kick("Security") return end
|
|
print("Modifying store: ",store,userid,key,value)
|
|
SetStoreData(players[userid].stores.saves,key,value)
|
|
player:Notify("Set Store Value","Success?")
|
|
end)
|
|
commands.new("read player stats%((.-)%)","",function(player,userid)
|
|
local player = GetPlayer(player)
|
|
if userid then
|
|
local other = GetPlayer(tostring(userid))
|
|
player:Notify(other.instance.DisplayName.." Statistics",_G.Http:JSONEncode(other.stats))
|
|
end
|
|
end)
|
|
commands.new("restart server","restart server: Update to latest version.",function(player,userid)
|
|
local player = GetPlayer(player)
|
|
if not player:IsAdmin() then player:Kick("Security") return end
|
|
_G.Remotes.Notification:FireAllClients("Server Restarting","Hold tight!")
|
|
game:GetService('TeleportService'):TeleportPartyAsync(137149259787276,game.Players:GetPlayers(),{moveTo = game.PlaceId})
|
|
end)
|
|
commands.new("rejoin","rejoin: Makes you rejoin.",function(player,userid)
|
|
local player = GetPlayer(player)
|
|
_G.Remotes.Notification:FireClient(player.instance,"Rejoining","Hold tight!")
|
|
game:GetService('TeleportService'):TeleportAsync(game.PlaceId,{player.instance})
|
|
end)
|
|
|
|
|
|
--[[local store,userid,key,value = message:match("setstore%((.-),(%d-),(.-),(.-)%)")
|
|
if store then
|
|
if player then
|
|
print("Modifying store: ",store,userid,key,value)
|
|
player.stores.saves:SetAsync(key,value)
|
|
end
|
|
end]]
|
|
|
|
local function SetMotor(character,motor,C0,C1) -- Allow clients to adjust their character motors
|
|
if not character:FindFirstChildWhichIsA("Humanoid") then
|
|
character = character.Character
|
|
end
|
|
if
|
|
not character or
|
|
not motor or
|
|
not motor:IsDescendantOf(character)
|
|
then
|
|
return
|
|
end
|
|
|
|
motor.C0 = C0 motor.C1 = C1
|
|
end
|
|
_G.Remotes.MotorMoveClient.OnServerEvent:Connect(SetMotor)
|
|
|
|
local textService = game:GetService("TextService")
|
|
local function Chat(instance,message)
|
|
local player = GetPlayer(instance)
|
|
if not player:CheckFloodGate(5) then return end
|
|
message = textService:FilterStringAsync(message,instance.UserId,Enum.TextFilterContext.PublicChat):GetNonChatStringForBroadcastAsync()
|
|
_G.Remotes.Chat:FireAllClients(instance.DisplayName.." | "..instance.Name,message,player.stats.color)
|
|
game.Chat:Chat(instance.Character,message)
|
|
end
|
|
_G.Remotes.Chat.OnServerEvent:Connect(Chat)
|
|
|
|
local function ApplyState(player,part,...)
|
|
local player = GetPlayer(player)
|
|
if not player:CheckFloodGate(2) then return end
|
|
if not player:OwnsPart(part) then return end
|
|
local state = partStateModule.new()
|
|
state.owner = player.instance
|
|
state.part = part
|
|
state:deserialise(...)
|
|
state:apply()
|
|
end
|
|
_G.Remotes.Apply.OnServerEvent:Connect(ApplyState)
|
|
|
|
spawn(function() -- Drain all flood gates every heartbeat
|
|
while wait() do
|
|
for id,player in pairs(players) do
|
|
if _G.IsString(id) then player:DrainFloodGate(PLAYER_FLOOD_TICK) end
|
|
end
|
|
end
|
|
end)
|
|
|
|
spawn(function() -- Make the game rain for players every now and then
|
|
while true do
|
|
_G.Remotes.Rain:FireAllClients(false)
|
|
wait(math.random(1,720))
|
|
_G.Remotes.Rain:FireAllClients(true)
|
|
wait(math.random(1,60))
|
|
end
|
|
end) |