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