This repository has been archived by the owner on Feb 16, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 7
/
ModRemote.lua
393 lines (313 loc) · 10.7 KB
/
ModRemote.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
-- @Author Vorlias
-- Edited by Narrev
--[[
ModRemote v4.00
ModuleScript for handling networking via client/server
Documentation for this ModuleScript can be found at
https://github.com/VoidKnight/ROBLOX-RemoteModule/tree/master/Version-3.x
]]
-- Constants
local client_Max_Wait_For_Remotes = 1
local default_Client_Cache = 10
-- Services
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local server = game:FindService("NetworkServer")
local remote = {remoteEvent = {}; remoteFunction = {}}
-- Localize Tables
local remoteEvent, remoteFunction, FuncCache, RemoteEvents, RemoteFunctions = remote.remoteEvent, remote.remoteFunction, {}, {}, {}
-- Localize Functions
local time, newInstance, traceback = os.time, Instance.new, debug.traceback
assert(workspace.FilteringEnabled or not server, "[ModRemote] ModRemote 4.0 does not work with filterless games due to security vulnerabilties. Please consider using Filtering or use ModRemote 2.7x")
-- Utility functions
local function Make(ClassType, Properties)
-- @param ClassType The type of class to instantiate
-- @param Properties The properties to use
assert(type(Properties) == "table", "Properties is not a table")
local Object = newInstance(ClassType)
for Index, Value in next, Properties do
Object[Index] = Value
end
return Object
end
local function WaitForChild(Parent, Name, TimeLimit)
-- Waits for a child to appear. Not efficient, but it shouldn't have to be. It helps with
-- debugging. Useful when ROBLOX lags out, and doesn't replicate quickly. Will warn
-- @param Parent The Parent to search in for the child.
-- @param Name The name of the child to search for
-- @param TimeLimit If TimeLimit is given, then it will return after the timelimit, even if it
-- hasn't found the child.
assert(Parent, "Parent is nil")
assert(type(Name) == "string", "Name is not a string.")
local Child = Parent:FindFirstChild(Name)
local StartTime = tick()
local Warned = false
while not Child do
wait()
Child = Parent:FindFirstChild(Name)
if not Warned and StartTime + (TimeLimit or 5) <= tick() then
Warned = true
warn("[WaitForChild] - Infinite yield possible for WaitForChild(" .. Parent:GetFullName() .. ", " .. Name .. ")\n" .. traceback())
if TimeLimit then
return Parent:FindFirstChild(Name)
end
end
end
return Child
end
-- Get storage or create if nonexistent
local functionStorage = ReplicatedStorage:FindFirstChild("RemoteFunctions") or Make("Folder" , {
Parent = ReplicatedStorage;
Name = "RemoteFunctions";
})
local eventStorage = ReplicatedStorage:FindFirstChild("RemoteEvents") or Make("Folder", {
Parent = ReplicatedStorage;
Name = "RemoteEvents";
})
-- Metatables
local functionMetatable = {
__index = function(self, i)
if rawget(remoteFunction, i) then
return rawget(remoteFunction, i)
else
return rawget(self, i)
end
end;
__newindex = function(self, i, v)
if i == 'OnCallback' and type(v) == 'function' then
self:Callback(v)
end
end;
__call = function(self, ...)
if server then
return self:CallPlayer(...)
else
return self:CallServer(...)
end
end;
}
local eventMetatable = {
__index = function(self, i)
if rawget(remoteEvent, i) then
return rawget(remoteEvent, i)
else
return rawget(self, i)
end
end;
__newindex = function(self, i, v)
if (i == 'OnRecieved' and type(v) == 'function') then
self:Listen(v)
end
end;
}
local remoteMetatable = {
__call = function(self, ...)
assert(server, "ModRemote can only be called from server.")
local args = {...}
if #args > 0 then
for a = 1, #args do
remote:RegisterChildren(args[a])
end
else
remote:RegisterChildren()
end
return self
end;
}
-- Helper Functions
local function CreateFunctionMetatable(instance)
return setmetatable({Instance = instance}, functionMetatable)
end
local function CreateEventMetatable(instance)
return setmetatable({Instance = instance}, eventMetatable)
end
local function CreateFunction(name, instance)
local instance = instance or functionStorage:FindFirstChild(name) or newInstance("RemoteFunction")
instance.Parent = functionStorage
instance.Name = name
local _event = CreateFunctionMetatable(instance)
RemoteFunctions[name] = _event
return _event
end
local function CreateEvent(name, instance)
local instance = instance or eventStorage:FindFirstChild(name) or newInstance("RemoteEvent")
instance.Parent = eventStorage
instance.Name = name
local _event = CreateEventMetatable(instance)
RemoteEvents[name] = _event
return _event
end
-- remote Object Methods
function remote:RegisterChildren(instance)
--- Registers the Children inside of an instance
-- @param Instance instance the object with Remotes in
-- @default the script this was imported in to
assert(server, "RegisterChildren can only be called from the server.")
local parent = instance or getfenv(0).script
if parent then
local children = parent:GetChildren()
for a = 1, #children do
local child = children[a]
if child:IsA("RemoteEvent") then
CreateEvent(child.Name, child)
elseif child:IsA("RemoteFunction") then
CreateFunction(child.Name, child)
end
end
end
end
function remote:GetFunctionFromInstance(instance)
return CreateFunctionMetatable(instance)
end
function remote:GetEventFromInstance(instance)
return CreateEventMetatable(instance)
end
function remote:GetFunction(name)
--- Gets a function if it exists, otherwise errors
-- @param string name - the name of the function.
assert(type(name) == 'string', "[ModRemote] GetFunction - Name must be a string")
assert(WaitForChild(functionStorage, name, client_Max_Wait_For_Remotes), "[ModRemote] GetFunction - Function " .. name .. " not found, create it using CreateFunction.")
return RemoteFunctions[name] or CreateFunction(name)
end
function remote:GetEvent(name)
--- Gets an event if it exists, otherwise errors
-- @param string name - the name of the event.
assert(type(name) == 'string', "[ModRemote] GetEvent - Name must be a string")
assert(WaitForChild(eventStorage, name, client_Max_Wait_For_Remotes), "[ModRemote] GetEvent - Event " .. name .. " not found, create it using CreateEvent.")
return RemoteEvents[name] or CreateEvent(name)
end
function remote:CreateFunction(name)
--- Creates a function
-- @param string name - the name of the function.
if not server then warn("[ModRemote] CreateFunction should be used by the server.") end
return CreateFunction(name)
end
function remote:CreateEvent(name)
--- Creates an event
-- @param string name - the name of the event.
if not server then warn("[ModRemote] CreateEvent should be used by the server.") end
return CreateEvent(name)
end
-- RemoteEvent Object Methods
function remoteEvent:SendToPlayers(playerList, ...)
assert(server, "[ModRemote] SendToPlayers should be called from the Server side.")
for a = 1, #playerList do
self.Instance:FireClient(playerList[a], ...)
end
end
function remoteEvent:SendToPlayer(player, ...)
assert(server, "[ModRemote] SendToPlayers should be called from the Server side.")
self.Instance:FireClient(player, ...)
end
function remoteEvent:SendToServer(...)
assert(not server, "SendToServer should be called from the Client side.")
self.Instance:FireServer(...)
end
function remoteEvent:SendToAllPlayers(...)
assert(server, "[ModRemote] SendToPlayers should be called from the Server side.")
self.Instance:FireAllClients(...)
end
function remoteEvent:Listen(func)
if server then
self.Instance.OnServerEvent:connect(func)
else
self.Instance.OnClientEvent:connect(func)
end
end
function remoteEvent:Wait()
if server then
self.Instance.OnServerEvent:wait()
else
self.Instance.OnClientEvent:wait()
end
end
function remoteEvent:GetInstance()
return self.Instance
end
function remoteEvent:Destroy()
self.Instance:Destroy()
end
-- RemoteFunction Object Methods
function remoteFunction:CallPlayer(player, ...)
assert(server, "[ModRemote] CallPlayer should be called from the server side.")
local args = {...}
local attempt, err = pcall(function()
return self.Instance:InvokeClient(player, unpack(args))
end)
if not attempt then
return warn("[ModRemote] CallPlayer - Failed to recieve response from " .. player.Name)
end
end
function remoteFunction:Callback(func)
if server then
self.Instance.OnServerInvoke = func
else
self.Instance.OnClientInvoke = func
end
end
function remoteFunction:GetInstance()
return self.Instance
end
function remoteFunction:Destroy()
self.Instance:Destroy()
end
function remoteFunction:SetClientCache(seconds, useAction)
local seconds = seconds or default_Client_Cache
assert(server, "SetClientCache must be called on the server.")
local instance = self.Instance
if seconds <= 0 then
local cache = instance:FindFirstChild("ClientCache")
if cache then cache:Destroy() end
else
local cache = instance:FindFirstChild("ClientCache") or Make("IntValue", {
Parent = instance;
Name = "ClientCache";
Value = seconds;
})
end
if useAction then
-- Put a BoolValue object inside of self.Instance to mark that we are UseActionCaching
-- Possible Future Update: Come up with a better way to mark we are UseActionCaching
-- We could change the ClientCache string, but that might complicate things
-- *We could try using the Value of the ClientCache object inside the remoteFunction
local cache = instance:FindFirstChild("UseActionCaching") or Make("BoolValue", {
Parent = instance;
Name = "UseActionCaching";
})
else
local cache = instance:FindFirstChild("UseActionCaching")
if cache then cache:Destroy() end
end
end
function remoteFunction:ResetClientCache()
assert(not server, "ResetClientCache must be used on the client.")
local instance = self.Instance
if instance:FindFirstChild("ClientCache") then
FuncCache[instance:GetFullName()] = {Expires = 0, Value = nil}
else
warn(instance:GetFullName() .. " does not have a cache.")
end
end
function remoteFunction:CallServer(...)
assert(not server, "[ModRemote] CallServer should be called from the client side.")
local instance = self.Instance
local clientCache = instance:FindFirstChild("ClientCache")
if clientCache then
local cacheName = instance:GetFullName() .. (instance:FindFirstChild("UseActionCaching") and tostring(({...})[1]) or "")
local cache = FuncCache[cacheName]
if cache and time() < cache.Expires then
-- If the cache exists in FuncCache and the time hasn't expired
-- Return cached arguments
return unpack(cache.Value)
else
-- The cache isn't in FuncCache or time has expired
-- Invoke the server with the arguments
-- Cache Arguments
local cacheValue = {instance:InvokeServer(...)}
FuncCache[cacheName] = {Expires = time() + clientCache.Value, Value = cacheValue}
return unpack(cacheValue)
end
else
return instance:InvokeServer(...)
end
end
return setmetatable(remote, remoteMetatable)