RAGE Multiplayer 0.3.7 is another iteration of the 0.4 major features testing backport, this update is to assure the smoothest transition to 0.4.
0.3.7 introduces backports of major client-side scripting subsystem improvements, such as C# implementation (.NET Core, just like server-side) bringing new possibilities for C# game mode developers, but enhancements for the JavaScript runtime as well.
C#
C# scripts are stored in the /client_packages/cs_packages/ folder. Once a player connects, it collects all the scripts, checks its integrity and compiles into an in-memory assembly. Thanks to a number of compilation-time checks, there's no way to allow C# client-side scripts to maliciously hurt users.
Despite the community concerns, all events and functions are available in C#!
Here's a quick peek of basic C# stuff:
EVENTS
public class EventsExample : RAGE.Events.Script@@@WCF_PRE_LINEBREAK@@@{@@@WCF_PRE_LINEBREAK@@@ public EventsExample()@@@WCF_PRE_LINEBREAK@@@ {@@@WCF_PRE_LINEBREAK@@@ // this kind of events receives mp.trigger, mp.events.callLocal, but also remote events@@@WCF_PRE_LINEBREAK@@@ RAGE.Events.AddEvent("remote_triggerable_event", SomeEvent);@@@WCF_PRE_LINEBREAK@@@ @@@WCF_PRE_LINEBREAK@@@ RAGE.Events.AddDataHandler("some_data", SomeDataHandler);@@@WCF_PRE_LINEBREAK@@@ @@@WCF_PRE_LINEBREAK@@@ RAGE.Events.Tick += Tick;@@@WCF_PRE_LINEBREAK@@@ RAGE.Events.OnPlayerChat += ChatHandler;@@@WCF_PRE_LINEBREAK@@@ @@@WCF_PRE_LINEBREAK@@@ // trigger a js event@@@WCF_PRE_LINEBREAK@@@ RAGE.Events.CallLocal("eventName", 1, "someString", 1.0f);@@@WCF_PRE_LINEBREAK@@@ }@@@WCF_PRE_LINEBREAK@@@ @@@WCF_PRE_LINEBREAK@@@ public void SomeEvent(object[] args)@@@WCF_PRE_LINEBREAK@@@ {@@@WCF_PRE_LINEBREAK@@@ }@@@WCF_PRE_LINEBREAK@@@ @@@WCF_PRE_LINEBREAK@@@ public void SomeDataHandler(RAGE.Elements.Entity entity, object value)@@@WCF_PRE_LINEBREAK@@@ {@@@WCF_PRE_LINEBREAK@@@ }@@@WCF_PRE_LINEBREAK@@@ @@@WCF_PRE_LINEBREAK@@@ public void ChatHandler(string text, RAGE.Events.CancelEventArgs cancel)@@@WCF_PRE_LINEBREAK@@@ {@@@WCF_PRE_LINEBREAK@@@ if(text == "cancelme")@@@WCF_PRE_LINEBREAK@@@ {@@@WCF_PRE_LINEBREAK@@@ cancel.Cancel = true;@@@WCF_PRE_LINEBREAK@@@ }@@@WCF_PRE_LINEBREAK@@@ }@@@WCF_PRE_LINEBREAK@@@ @@@WCF_PRE_LINEBREAK@@@ // known as "render" in JS@@@WCF_PRE_LINEBREAK@@@ public void Tick(System.Collections.Generic.List<RAGE.Events.TickNametagData> nametags)@@@WCF_PRE_LINEBREAK@@@ {@@@WCF_PRE_LINEBREAK@@@ }@@@WCF_PRE_LINEBREAK@@@}
GAME INTERACTION
// trivial game stuff@@@WCF_PRE_LINEBREAK@@@int interior = RAGE.Game.Interior.GetInteriorFromCollision(0.0f, 0.0f, 0.0f);@@@WCF_PRE_LINEBREAK@@@@@@WCF_PRE_LINEBREAK@@@// player interaction@@@WCF_PRE_LINEBREAK@@@RAGE.Elements.Entities.Players.GetAtRemote(1).ClearDecorations();@@@WCF_PRE_LINEBREAK@@@@@@WCF_PRE_LINEBREAK@@@// player interaction using a game entity handle@@@WCF_PRE_LINEBREAK@@@RAGE.Game.Ped.ClearPedDecorations(RAGE.Elements.Player.LocalPlayer.Handle);@@@WCF_PRE_LINEBREAK@@@@@@WCF_PRE_LINEBREAK@@@// ped creation@@@WCF_PRE_LINEBREAK@@@uint freeroamHash = RAGE.Game.Misc.GetHashKey("mp_m_freemode_01");@@@WCF_PRE_LINEBREAK@@@RAGE.Elements.Ped ped = new RAGE.Elements.Ped(freeroamHash, new RAGE.Vector3(0.0f, 0.0f, 0.0f), dimension: 5);
CEF
...@@@WCF_PRE_LINEBREAK@@@ @@@WCF_PRE_LINEBREAK@@@public void OurEventHandler(object[] args)@@@WCF_PRE_LINEBREAK@@@{@@@WCF_PRE_LINEBREAK@@@ RAGE.Chat.Output("Got actually called! {0}", (string)args[0]);@@@WCF_PRE_LINEBREAK@@@}@@@WCF_PRE_LINEBREAK@@@ @@@WCF_PRE_LINEBREAK@@@public void TriggerMe()@@@WCF_PRE_LINEBREAK@@@{@@@WCF_PRE_LINEBREAK@@@ RAGE.Events.Add("eventExample", OurEventHandler);@@@WCF_PRE_LINEBREAK@@@ @@@WCF_PRE_LINEBREAK@@@ RAGE.Ui.HtmlWindow wnd = new RAGE.Ui.HtmlWindow("package://index.html");@@@WCF_PRE_LINEBREAK@@@ wnd.ExecuteJs("mp.trigger('eventExample', 'yep')");@@@WCF_PRE_LINEBREAK@@@ @@@WCF_PRE_LINEBREAK@@@ // "mp.gui.execute"@@@WCF_PRE_LINEBREAK@@@ RAGE.Ui.DefaultWindow.ExecuteJs("test()");@@@WCF_PRE_LINEBREAK@@@}
BUILT-IN NATIVEUI
using System;@@@WCF_PRE_LINEBREAK@@@using System.Collections.Generic;@@@WCF_PRE_LINEBREAK@@@@@@WCF_PRE_LINEBREAK@@@using RAGE.NUI;@@@WCF_PRE_LINEBREAK@@@@@@WCF_PRE_LINEBREAK@@@public class MenuExample@@@WCF_PRE_LINEBREAK@@@ : RAGE.Events.Script@@@WCF_PRE_LINEBREAK@@@{@@@WCF_PRE_LINEBREAK@@@ private bool ketchup = false;@@@WCF_PRE_LINEBREAK@@@ private string dish = "Banana";@@@WCF_PRE_LINEBREAK@@@ private MenuPool _menuPool;@@@WCF_PRE_LINEBREAK@@@@@@WCF_PRE_LINEBREAK@@@ public void AddMenuKetchup(UIMenu menu)@@@WCF_PRE_LINEBREAK@@@ {@@@WCF_PRE_LINEBREAK@@@ var newitem = new UIMenuCheckboxItem("Add ketchup?", ketchup, "Do you wish to add ketchup?");@@@WCF_PRE_LINEBREAK@@@ menu.AddItem(newitem);@@@WCF_PRE_LINEBREAK@@@ menu.OnCheckboxChange += (sender, item, checked_) =>@@@WCF_PRE_LINEBREAK@@@ {@@@WCF_PRE_LINEBREAK@@@ if (item == newitem)@@@WCF_PRE_LINEBREAK@@@ {@@@WCF_PRE_LINEBREAK@@@ ketchup = checked_;@@@WCF_PRE_LINEBREAK@@@ Notify("~r~Ketchup status: ~b~" + ketchup);@@@WCF_PRE_LINEBREAK@@@ }@@@WCF_PRE_LINEBREAK@@@ };@@@WCF_PRE_LINEBREAK@@@ }@@@WCF_PRE_LINEBREAK@@@@@@WCF_PRE_LINEBREAK@@@ public void AddMenuFoods(UIMenu menu)@@@WCF_PRE_LINEBREAK@@@ {@@@WCF_PRE_LINEBREAK@@@ var foods = new List@@@WCF_PRE_LINEBREAK@@@ {@@@WCF_PRE_LINEBREAK@@@ "Banana",@@@WCF_PRE_LINEBREAK@@@ "Apple",@@@WCF_PRE_LINEBREAK@@@ "Pizza",@@@WCF_PRE_LINEBREAK@@@ "Quartilicious",@@@WCF_PRE_LINEBREAK@@@ 0xF00D, // Dynamic!@@@WCF_PRE_LINEBREAK@@@ };@@@WCF_PRE_LINEBREAK@@@ var newitem = new UIMenuListItem("Food", foods, 0);@@@WCF_PRE_LINEBREAK@@@ menu.AddItem(newitem);@@@WCF_PRE_LINEBREAK@@@ menu.OnListChange += (sender, item, index) =>@@@WCF_PRE_LINEBREAK@@@ {@@@WCF_PRE_LINEBREAK@@@ if (item == newitem)@@@WCF_PRE_LINEBREAK@@@ {@@@WCF_PRE_LINEBREAK@@@ dish = item.IndexToItem(index).ToString();@@@WCF_PRE_LINEBREAK@@@ Notify("Preparing ~b~" + dish + "~w~...");@@@WCF_PRE_LINEBREAK@@@ }@@@WCF_PRE_LINEBREAK@@@@@@WCF_PRE_LINEBREAK@@@ };@@@WCF_PRE_LINEBREAK@@@ }@@@WCF_PRE_LINEBREAK@@@@@@WCF_PRE_LINEBREAK@@@ public void AddMenuCook(UIMenu menu)@@@WCF_PRE_LINEBREAK@@@ {@@@WCF_PRE_LINEBREAK@@@ var newitem = new UIMenuItem("Cook!", "Cook the dish with the appropiate ingredients and ketchup.");@@@WCF_PRE_LINEBREAK@@@ newitem.SetLeftBadge(UIMenuItem.BadgeStyle.Star);@@@WCF_PRE_LINEBREAK@@@ newitem.SetRightBadge(UIMenuItem.BadgeStyle.Tick);@@@WCF_PRE_LINEBREAK@@@ menu.AddItem(newitem);@@@WCF_PRE_LINEBREAK@@@ menu.OnItemSelect += (sender, item, index) =>@@@WCF_PRE_LINEBREAK@@@ {@@@WCF_PRE_LINEBREAK@@@ if (item == newitem)@@@WCF_PRE_LINEBREAK@@@ {@@@WCF_PRE_LINEBREAK@@@ string output = ketchup ? "You have ordered ~b~{0}~w~ ~r~with~w~ ketchup." : "You have ordered ~b~{0}~w~ ~r~without~w~ ketchup.";@@@WCF_PRE_LINEBREAK@@@ Notify(String.Format(output, dish));@@@WCF_PRE_LINEBREAK@@@ }@@@WCF_PRE_LINEBREAK@@@ };@@@WCF_PRE_LINEBREAK@@@ menu.OnIndexChange += (sender, index) =>@@@WCF_PRE_LINEBREAK@@@ {@@@WCF_PRE_LINEBREAK@@@ if (sender.MenuItems[index] == newitem)@@@WCF_PRE_LINEBREAK@@@ newitem.SetLeftBadge(UIMenuItem.BadgeStyle.None);@@@WCF_PRE_LINEBREAK@@@ };@@@WCF_PRE_LINEBREAK@@@ }@@@WCF_PRE_LINEBREAK@@@@@@WCF_PRE_LINEBREAK@@@ public void AddMenuAnotherMenu(UIMenu menu)@@@WCF_PRE_LINEBREAK@@@ {@@@WCF_PRE_LINEBREAK@@@ var submenu = _menuPool.AddSubMenu(menu, "Another Menu");@@@WCF_PRE_LINEBREAK@@@ for (int i = 0; i < 20; i++)@@@WCF_PRE_LINEBREAK@@@ submenu.AddItem(new UIMenuItem("PageFiller", "Sample description that takes more than one line. Moreso, it takes way more than two lines since it's so long. Wow, check out this length!"));@@@WCF_PRE_LINEBREAK@@@ }@@@WCF_PRE_LINEBREAK@@@@@@WCF_PRE_LINEBREAK@@@ public void DrawMenu(System.Collections.Generic.List<RAGE.Events.TickNametagData> nametags)@@@WCF_PRE_LINEBREAK@@@ {@@@WCF_PRE_LINEBREAK@@@ _menuPool.ProcessMenus();@@@WCF_PRE_LINEBREAK@@@ }@@@WCF_PRE_LINEBREAK@@@@@@WCF_PRE_LINEBREAK@@@ public MenuExample()@@@WCF_PRE_LINEBREAK@@@ {@@@WCF_PRE_LINEBREAK@@@ _menuPool = new MenuPool();@@@WCF_PRE_LINEBREAK@@@ var mainMenu = new UIMenu("Native UI", "~b~NATIVEUI SHOWCASE");@@@WCF_PRE_LINEBREAK@@@ @@@WCF_PRE_LINEBREAK@@@ // original NativeUI replicates GTA V "interaction menu", @@@WCF_PRE_LINEBREAK@@@ //changing FreezeAllInput to true makes the player completely frozen@@@WCF_PRE_LINEBREAK@@@ // while the menu is active@@@WCF_PRE_LINEBREAK@@@ mainMenu.FreezeAllInput = true;@@@WCF_PRE_LINEBREAK@@@ @@@WCF_PRE_LINEBREAK@@@ _menuPool.Add(mainMenu);@@@WCF_PRE_LINEBREAK@@@ AddMenuKetchup(mainMenu);@@@WCF_PRE_LINEBREAK@@@ AddMenuFoods(mainMenu);@@@WCF_PRE_LINEBREAK@@@ AddMenuCook(mainMenu);@@@WCF_PRE_LINEBREAK@@@ AddMenuAnotherMenu(mainMenu);@@@WCF_PRE_LINEBREAK@@@ _menuPool.RefreshIndex();@@@WCF_PRE_LINEBREAK@@@@@@WCF_PRE_LINEBREAK@@@ RAGE.Events.Tick += DrawMenu;@@@WCF_PRE_LINEBREAK@@@@@@WCF_PRE_LINEBREAK@@@ mainMenu.Visible = true;@@@WCF_PRE_LINEBREAK@@@ }@@@WCF_PRE_LINEBREAK@@@@@@WCF_PRE_LINEBREAK@@@ public static void Notify(string text)@@@WCF_PRE_LINEBREAK@@@ {@@@WCF_PRE_LINEBREAK@@@ RAGE.Game.Ui.SetNotificationTextEntry("STRING");@@@WCF_PRE_LINEBREAK@@@ RAGE.Game.Ui.AddTextComponentSubstringPlayerName(text);@@@WCF_PRE_LINEBREAK@@@ RAGE.Game.Ui.DrawNotification(false, false);@@@WCF_PRE_LINEBREAK@@@ }@@@WCF_PRE_LINEBREAK@@@}
Here's how that NativeUI example looks in the game:
General Changes
- JS: "entityDataChange" event has been replaced with "mp.events.addDataHandler(key, handler)"
- JS: added mp.events.callLocal
- Improvements on initial server loading
- Fix voice chat not getting cleared properly after setting "voice3d" to false
- Improve voice chat playback thread synchronization mechanism, so it doesn't affect anything else
- Fix reported voice chat crashes
- 0.4's game interaction performance improvements backport
- Fix in-game UI not saving "latest IP connected to" correctly
- DataStorage UTF-8 support fixes
- Added a smoother voice chat packet loss handling
- Fixed reported voice chat stability issues
- Fixed some specific remote player tasks stuck after finished
- Added "experimental web platform features" flag to the in-game CEF
- Fixed key binding issues with isDown param = false
Downloads
You can download the update using the regular RAGE Multiplayer updater. Open config.xml and set your updater branch to 037_testing. Once you restart the client, it will download the update. We don't recommend to use the testing release build to play on 3rd-party servers.
To reference the client-side C# API you should add "dotnet/rage-sharp.dll" as a reference. You can also reference "dotnet/Newtonsoft.Json.dll" to get JSON functionalities.