Developer zarklord_klei Posted October 15, 2020 Developer Share Posted October 15, 2020 With the forgotten knowledge beta released, we have added a couple new modding API features, namely the ability to send RPCs from servers to clients, and to send RPCs from servers to other servers. All three RPC systems use a near identical API, so let's go over the existing API first, and then I'll explain the changes for the two new systems.Creating RPCs: AddModRPCHandler(namespace, name, fn) This is the function to add RPCs to be called, lets go over the arguments namespace - this argument is some unique identifier that only your mod uses, generally just using your modname is fine, so if your mod was called "The Hunt", you could do "TheHunt", "The Hunt", or even "The_Hunt", whatever namespace you use, just keep it consistent, your gonna end up using that same name to call your RPCs later. name - this argument is the name of the RPC, it should be descriptive of what the RPC is doing, but it can really be whatever you want. fn - this is the function that will get executed on the server, it will get called with its first argument being the player who sent the rpc, and then all other arguments passed from when you called it. Sending RPCs: SendModRPCToServer(GetModRPC(namespace, name), ...) This is the function to send RPCs, while it looks scary, it's really simple, here is the arguments: GetModRPC(namespace, name) - This should look slightly familiar, just use the exact same namespace and name arguments as you used in AddModRPCHandler. ... - every other argument in this function call will get forwarded to the fn you added in AddModRPCHandler, so in the end it would call fn(player, ...). Putting it all together: So how does this all look put together, lets see: --This is the function we'll call remotely to do it's thing, in this case make you giant! local function GrowGiant(player, size) local valid_grow_state = true -- todo: make sure the player is allowed to grow right now if valid_grow_state then player.Transform:SetScale(size,size,size) end end --This adds the handler, which means that if the server gets told "GrowGiant", -- it will call our function, GrowGiant, above AddModRPCHandler("GrowGiantRPC", "GrowGiant", GrowGiant) --This has it send the RPC to the server when you press "v" local size = 1 local function SendGrowGiantRPC() size = size + 1 if size == 6 then size = 1 end SendModRPCToServer(GetModRPC("GrowGiantRPC", "GrowGiant"), size) end GLOBAL.TheInput:AddKeyDownHandler(GLOBAL.KEY_V, SendGrowGiantRPC) So what does this mod do? This would make the player grow in size by 1 every time the player pressed the V key, at a size of 5, the players size will reset back to 1 when the V key is pressed. In your mod, you should make sure to add appropriate checks so that a client cannot abuse your RPC, whether this is checking the prefab via player.prefab to limit it to a specific character, or check if they are a server admin with player.Network:IsServerAdmin(), purely depending on what its supposed to do. So what about the new RPC systems? let's take a look at those now. Shard RPCs: Shard RPCs work largely the same as normal RPCs with a couple small differences: Instead of AddModRPCHandler, we use AddShardModRPCHandler, all the arguments are the same. Instead of SendModRPCToServer, we use SendModRPCToShard, this has one difference I'll explain in the following paragraph. Instead of GetModRPC, we call GetShardModRPC, all the arguments are the same. SendModRPCToShard has one key difference, which is that it takes a list of Shards to send to, so its arguments look like this: SendModRPCToShard(GetShardModRPC(namespace, name), sender_list, ...) sender_list can be a number of different values: if sender_list is nil, all connected shards(including the one this is called on) will have this RPC executed. if sender_list is a table, it will be iterated upon and send it to every shard that's ID is in the table. if sender_list is either a string or a number, it will send that RPC to the specific shard with that ID. In a lot of cases, you can just pass nil, but if you need to get a list of shard IDs(and view information about them), you can get them by calling: local connected_shards = Shard_GetConnectedShards() That returns a table where each key is the shard ID, and the value contains a bit of info about the connected world, for information about what data is in the table, you can view scripts/shardnetworking.lua. The only other change, is that instead of getting a player as the first argument of the RPC Handler fn, you get the shard ID of the shard that sent the RPC, so it would look like this: fn(sending_shard_id, ...). Client RPCs: Client RPCs also work largely the same as normal RPCs with a couple small differences: Instead of AddModRPCHandler, we use AddClientModRPCHandler, all the arguments are the same. Instead of SendModRPCToServer, we use SendModRPCToClient, this has one difference I'll explain in the following paragraph. Instead of GetModRPC, we call GetClientModRPC, all the arguments are the same. SendModRPCToClient has one key difference, which is that it takes a list of Clients to send to, so its arguments look like this: SendModRPCToClient(GetClientModRPC(namespace, name), sender_list, ...) sender_list can be a number of different values: if sender_list is nil, all clients connected to the current shard will execute the RPC. if sender_list is a table, it will be iterated upon and send it to every client that is connected to the current shard thats UserID is in the table. if sender_list is a string, it will send the RPC to the specific client with that UserID, as long as they are connected to that shard. To get a list of players connected to the current shard, you can iterate over AllPlayers and get UserIDs like this: local userids = {} for i, player in ipairs(AllPlayers) do table.insert(userids, player.userid) end Then userids would contain the userids of all players connected to that shard, if you wanted to send that to a subset of players, say for example only wilsons, you could do this: local userids = {} for i, player in ipairs(AllPlayers) do if player.prefab == "wilson" then table.insert(userids, player.userid) end end And now you would only have the userids of anybody playing wilson. Just make sure you do this every time right before sending your RPC, otherwise the information could be out of date! The only other change, is that Client RPCs have no first argument like player or sending_shard_id, since the sending shard would always just be the current world the player is connected to, so there is no first argument, making it look like this: fn(...). If you have any questions, please ask them, and I'll do my best to answer them. 4 10 Link to comment Share on other sites More sharing options...
penguin0616 Posted October 15, 2020 Share Posted October 15, 2020 :DDDDDDD 1 Link to comment Share on other sites More sharing options...
Cunning fox Posted October 15, 2020 Share Posted October 15, 2020 (edited) Will client RPC work if the player is in the lobby? EDIT: It won't work for players that are not in the world. Zark confirmed it. Edited October 24, 2020 by Cunning fox Link to comment Share on other sites More sharing options...
Developer zarklord_klei Posted October 15, 2020 Author Developer Share Posted October 15, 2020 32 minutes ago, Cunning fox said: Will client RPC work if the player is in the lobby? as long as you can get their userid, it should! 2 1 1 Link to comment Share on other sites More sharing options...
Cunning fox Posted October 15, 2020 Share Posted October 15, 2020 (edited) 6 minutes ago, zarklord_klei said: as long as you can get their userid, it should! Why Klei haven't hired you sooner?? I made a while system that dynamically spawns classified prefabs only to send data to a certain player in lobby. This would make it SO MUCH easier Edited October 15, 2020 by Cunning fox 2 1 Link to comment Share on other sites More sharing options...
Kova_ Posted October 16, 2020 Share Posted October 16, 2020 So with RPC calls, will I be able to pass commands (e.g. kill or revive player) from Master to Shard? Link to comment Share on other sites More sharing options...
Developer zarklord_klei Posted October 16, 2020 Author Developer Share Posted October 16, 2020 1 hour ago, Kova_ said: So with RPC calls, will I be able to pass commands (e.g. kill or revive player) from Master to Shard? you should be able to. 2 Link to comment Share on other sites More sharing options...
Tykvesh Posted October 16, 2020 Share Posted October 16, 2020 For some reason, ShardRPC fails if sender_list is anything but nil (Invalid RPC sender list). And when it is nil, the rpc won't fire on the shard it's called from. Also I wonder why tables aren't supported as valid arguments (Invalid RPC data type)? Link to comment Share on other sites More sharing options...
Developer zarklord_klei Posted October 17, 2020 Author Developer Share Posted October 17, 2020 2 hours ago, Tykvesh said: For some reason, ShardRPC fails if sender_list is anything but nil (Invalid RPC sender list). And when it is nil, the rpc won't fire on the shard it's called from. Also I wonder why tables aren't supported as valid arguments (Invalid RPC data type)? tables have always been invalid data types, would you mind sharing the relevant code snippet? 1 1 Link to comment Share on other sites More sharing options...
Tykvesh Posted October 17, 2020 Share Posted October 17, 2020 This code sends a RPC with player's position each time they locomote. After that the RPC prints the received position: Spoiler AddShardModRPCHandler("test_namespace", "test_name", function(shardid, x, y, z) print("(Received from a ShardRPC)", x, y, z) end) local function OnLocomote(inst) local x, y, z = inst.Transform:GetWorldPosition() print("(Sending a ShardRPC)", x, y, z) SendModRPCToShard(GetShardModRPC("test_namespace", "test_name"), nil, x, y, z) end AddComponentPostInit("playercontroller", function(self, inst) inst:ListenForEvent("locomote", OnLocomote) end) And here's how it appears in the logs: From the shard from which the rpc was sent: [00:10:14]: (Sending a ShardRPC) 355.60037231445 0 349.40600585938 [00:10:14]: (Sending a ShardRPC) 355.72143554688 0 349.40570068359 [00:10:15]: (Sending a ShardRPC) 355.72143554688 0 349.40570068359 From the other shard that received the rpc: [00:10:14]: (Received from a ShardRPC) 355.60037231445 0 349.40600585938 [00:10:15]: (Received from a ShardRPC) 355.72143554688 0 349.40570068359 [00:10:15]: (Received from a ShardRPC) 355.72143554688 0 349.40570068359 1 Link to comment Share on other sites More sharing options...
Cunning fox Posted October 18, 2020 Share Posted October 18, 2020 (edited) For some reason when I call ClientRPC, I always get [00:13:35]: Invalid RPC namespace: 1 table: 0x40915618 My RPC: AddClientModRPCHandler("HG", "TEAM_MSG", function(...) printwrap("", {...}) end) How I call it SendModRPCToClient(GetClientModRPC("HG", "TEAM_MSG"), nil) Edit: Zarklord found a bug and this should be fixed in the next patch. Edited October 19, 2020 by Cunning fox Link to comment Share on other sites More sharing options...
Wonderlarr Posted November 27, 2020 Share Posted November 27, 2020 Oh my god you have no idea how much confusion RPC's were causing for me, I'm glad you made this! Link to comment Share on other sites More sharing options...
Monti18 Posted July 23, 2021 Share Posted July 23, 2021 I made a mod named Craftable Wormholes, where I added wormhole icon support in the last update. I use SendModRPCToClient to make the client delete the icons. This works if I use newly created wormholes, but if I try to do it when an original wormhole that was created with the world is connected to a new wormhole, nothing happens. I get no error in the client or server log, just nothing. If I only do it with the new wormholes, it works without problems. I added prints to check if the RPC is indeed sent and they appear in the log. The code in question: Spoiler local function onhammered(inst, worker) GLOBAL.TheWorld:PushEvent("wormhole_destroyed",{wormhole = inst}) if GetModConfigData("ENABLED") then if inst.components.teleporter.targetTeleporter ~= nil then local userids = {} for i, player in ipairs(GLOBAL.AllPlayers) do table.insert(userids, player.userid) end SendModRPCToClient(GetClientModRPC("Wormhole_Crafter", "RemoveWormhole"),userids,inst,inst.components.teleporter.targetTeleporter) end end inst:DoTaskInTime(0.3, function() inst:Remove() end) end local function RemoveWormhole(wormhole_removed,wormhole_still_here) local hole_removed = {inst=wormhole_removed,pos = wormhole_removed:GetPosition()} local hole_still_here = {inst=wormhole_still_here,pos = wormhole_still_here:GetPosition()} RemoveWormholePair(hole_removed,hole_still_here) end AddClientModRPCHandler("Wormhole_Crafter", "RemoveWormhole", RemoveWormhole) Does someone have an idea why this doesn't work? 1 Link to comment Share on other sites More sharing options...
IceNine99 Posted October 9, 2021 Share Posted October 9, 2021 On 10/18/2020 at 8:07 AM, Cunning fox said: For some reason when I call ClientRPC, I always get [00:13:35]: Invalid RPC namespace: 1 table: 0x40915618 My RPC: AddClientModRPCHandler("HG", "TEAM_MSG", function(...) printwrap("", {...}) end) How I call it SendModRPCToClient(GetClientModRPC("HG", "TEAM_MSG"), nil) Edit: Zarklord found a bug and this should be fixed in the next patch. Do you have confirmation it's been fixed? I have a similar issue right now, though it's not quite the same, but I'm wondering if they're related. At any rate, I'm getting the invalid RPC namespace error message too, but mine prints out the namespace I used in the error message. If anyone can help. Here's some of my code. The following is in a mod-specific component's constructor, inside an if TheWorld.ismastersim. local function ClientcideReorient(angle) TheCamera:SetHeadingTarget(angle) end AddClientModRPCHandler("CameraSinker", "reorient", ClientcideReorient) self.inst.ListenForEvent("camsync_synchrotate", function(world, data) -- stuff, including making a list of clients, which I name "tracked" SendModRPCToClient(GetClientModRPC("CameraSinker", "reorient"), tracked, self.angle) end) (... How do you make monospaced text in these forum posts?) And for completeness, I get an error message on the client saying Invalid RPC namespace: camera sinker 1 Any ideas, anyone? I'm about to resign to using net_events for now, since I got that mostly working before I found this post saying RPC's can go the other way now. Link to comment Share on other sites More sharing options...
Liquid Ocelot Posted August 14, 2022 Share Posted August 14, 2022 (edited) I'm working on a part of my mod----an interior room which is totally dark.To do this, I need to push the "overrideambientlighting" event in player's client side(I just need a visual effect,so I have no need to do this in server),so I use the ClientModRPC, whenever a player enters the dark room, I will let server send the RPC to the player's client side, which make the player feels dark(visually). This method works fine when I start the game with cave. However, when I start the game without cave and I enter the dark room, it seems the whole server in affected by the "overrideambientlighting" (I find my character is attacked by Charile,which means the whole server becomes dark), but I just need to "overrideambientlighting" in my client side !!!!! What's going on ??? Shouldn't the ClientModRPC works only in client side ??? And Here is my code,would you like to help me to find the problem ? (or solution ?) thank you very much XD. -- Should be client side override ambient lighting AddClientModRPCHandler("gale_rpc","check_gale_interior_ambientlighting",function() if ThePlayer then ThePlayer:CheckInteriorAmbientLightAndOcean() else print("check_gale_interior_ambientlighting FAILED !!!") end end) AddPlayerPostInit(function(inst) if not TheNet:IsDedicated() then -- overrideambientlighting functions inst.InteriorAmbientLightAndOceanEnabled = false inst.EnableInteriorAmbientLightAndOcean = function(inst,enable) local oceancolor =TheWorld.components.oceancolor if enable then TheWorld:PushEvent("overrideambientlighting",Point(0 / 255, 0 / 255, 0 / 255)) if oceancolor ~= nil then TheWorld:StopWallUpdatingComponent(oceancolor) oceancolor:Initialize(not enable and TheWorld.has_ocean) end else TheWorld:PushEvent("overrideambientlighting",nil) if oceancolor ~= nil then TheWorld:StartWallUpdatingComponent(oceancolor) oceancolor:Initialize(not enable and TheWorld.has_ocean) end end inst.InteriorAmbientLightAndOceanEnabled = enable end -- Check functions inst.CheckInteriorAmbientLightAndOcean = function(inst) local room = TheWorld.components.gale_interior_room_manager:GetRoom(inst:GetPosition()) if room and room:IsValid() then if not inst.InteriorAmbientLightAndOceanEnabled then inst:EnableInteriorAmbientLightAndOcean(true) end else if inst.InteriorAmbientLightAndOceanEnabled then inst:EnableInteriorAmbientLightAndOcean(false) end end end -- Used for initialize ambient lighting inst:DoTaskInTime(0.1,function() TheCamera:CheckGaleInteriorCamera() -- camera or sth inst:CheckInteriorAmbientLightAndOcean() end) end end) -- Sample: Server send rpc command to client side -- SendModRPCToClient(CLIENT_MOD_RPC["gale_rpc"]["check_gale_interior_ambientlighting"],inst.userid) Edited August 14, 2022 by Collecter Link to comment Share on other sites More sharing options...
zyzs Posted September 30, 2022 Share Posted September 30, 2022 On 10/16/2020 at 3:27 AM, zarklord_klei said: Shard RPCs what`s the shard means? Link to comment Share on other sites More sharing options...
Wonderlarr Posted October 13, 2022 Share Posted October 13, 2022 On 9/30/2022 at 7:28 AM, zyzs said: what`s the shard means? Forest and Caves both run on individual servers called "shards". Shard RPCs can be used to communicate data between Forest and Caves, since they aren't the same server. Link to comment Share on other sites More sharing options...
zyzs Posted October 15, 2022 Share Posted October 15, 2022 On 10/14/2022 at 1:36 AM, TheSkylarr said: Forest and Caves both run on individual servers called "shards". Shard RPCs can be used to communicate data between Forest and Caves, since they aren't the same server. Thank you. bro Link to comment Share on other sites More sharing options...
Philip. Posted January 21, 2023 Share Posted January 21, 2023 On 10/16/2020 at 1:22 AM, zarklord_klei said: as long as you can get their userid, it should! It looks like that if all players are in the lobby - the simulation is paused, the RPCs sent to the server are not handled until the simulation is resumed. This happens on dedicated servers because the RPC queue uses simulation ticks. Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now