While it's surely not possible to make all types of mods, it should offer a vast arsenal to make easy and more complex type of mods like the two provided mod examples. By providing both premade and generic nodes it pretty much depends on the user capabilities and understanding of the tool.
Can I still make something out of this if I don't know how to program and/or unity game development?
Absolutely yes! Some nodes may sound unfamiliar like the Unity node category, but a big chunk of them are self-explanatory and are shortly described when hovered.
How is this related to RedLoader?
The application was built with a focus on RedLoader by using it's functionalities and making nodes based on it. The mods themselves are read and resolved by a RedLoader mod (RedNodeLoader), which executes each node accordingly.
How can I use arrays/list?
Containers like arrays and list aren't currently supported and so aren't for loops.
Will an eventual application/loader update break my mods?
Depending if the mod used some nodes which where greatly changed in functionalities the mod may break or not. (Reverting to an older version of the loader is always possible to keep using it).
Mod menu mod example
To create a new node we need to make a new class which derives from the SonsNode class and populate it's informations like shown below.
Nodes having inputs only:
publicclassSetWalkSpeedNode:SonsNode{publicfloat Speed { get; set; } // the argument input displayed as an input fieldpublicSetWalkSpeedNode() { Name ="SetWalkSpeed"; // the name of the node which will be displayed in the editor Description ="Sets player walk speed"; // description about what the node does or how should be used NodeCategory =NodeCategories.Player; // the node category in which to put the node // the input argument displayed as a circle (ArgName is optional but preferred; nameof should always be used to maintain the right references)
ArgsIn.Add(newArgIn { Type =typeof(float), ArgName =nameof(Speed) }); }}
Nodes having an output only:
publicclassGetRunSpeedNode:SonsNode{publicGetRunSpeedNode() { Name ="GetRunSpeed"; // the name of the node which will be displayed in the editor Description ="Returns player run speed"; // description about what the node does or how should be used NodeCategory =NodeCategories.Player; // the node category in which to put the node // the output argument displayed as a circleArgsOut.Add(newArgOut { Type =typeof(float) }); }}
Nodes having both inputs and an output:
publicclassAmountOfNode:SonsNode{publicint ItemId { get; set; } // the argument input displayed as an input fieldpublicAmountOfNode() { Name ="AmountOf"; // the name of the node which will be displayed in the editor Description = "Gets the owned amount of the passed item id"; // description about what the node does or how should be used
NodeCategory =NodeCategories.Inventory; // the node category in which to put the nodeArgsIn.Add(newArgIn { Type =typeof(int), ArgName =nameof(ItemId) }); // input argument displayed as a circleArgsOut.Add(newArgOut { Type =typeof(int) }); // output argument displayed as a circle }}
Nodes using external types:
If the input argument isn't of the common types (int, float, bool, string, Vector3, Vector2) the property must still be written and marked with the [XmlIgnore] attribute like shown in the example below.
publicclassSetActiveNode:SonsNode{ [XmlIgnore] // use this attribute when the property type it's not (int, float, bool, string, Vector3, Vector2) public GameObject GameObject { get; set; } // GameObject type isn't a common type so no input field will be displayed
publicbool Value { get; set; } // Boolean type is a common type so a checkbox will be displayedpublicSetActiveNode() { Name ="SetActive"; // the name of the node which will be displayed in the editor Description = "Turns the passed GameObject on or off"; // description about what the node does or how should be used
NodeCategory =NodeCategories.Unity; // the node category in which to put the node ArgsIn.Add(new ArgIn { Type = typeof(GameObject), ArgName = nameof(GameObject) }); // 1° input argument displayed as a circle
ArgsIn.Add(new ArgIn { Type = typeof(bool), ArgName = nameof(Value) }); // 2° input argument displayed as a circle
}}
Implementing the new node
To make the node functional we need to implement it's behaviour in RedNodeLoader. To do so we need to create a new class which must have the same exact name, namespace and properties names as the one defined in the editor before. We then need to override the Execute method, which is where the node behaviour will happen, and add inputs and outputs of the node as properties if any.
The RedNodeLoader.GetArgumentsOf(this) method is used to retrieve both static inputs and passed arguments of a node.
Here is an example implementation for the SetWalkSpeed node we showed before.
publicclassSetWalkSpeedNode:SonsNode{ public float Speed { get; set; } // input arguments must be defined as public properties using the same name as chosed in the editor class
publicoverridevoidExecute() { List<object> args = RedNodeLoader.GetArgumentsOf(this); // retrieves the arguments of SetWalkSpeed (only Speed value in this case) and stores them as generic objects in a list in the same order as defined in the editor class
LocalPlayer.FpCharacter.SetWalkSpeed((float)args[0]); // each argument must be explicitly casted to it's proper type and index. Never use the property directly like "Speed" as it won't work if the value is passed
}}
Nodes having an output:
Nodes having an output argument must have a public property of whatever name we chose and as the same type defined in the editor class. The output property must have the [IsArgOut] attribute to define it's an output argument.
Here is an example implementation for the GetRunSpeed node we showed before.
publicclassGetRunSpeedNode:SonsNode{ [IsArgOut] // output argument must have this attribute public float Speed { get; set; } // output argument must be defined as a public property. The name can be whatever you want
publicoverridevoidExecute() { Speed = LocalPlayer.FpCharacter.RunSpeed; // getting the desidered value and storing it into the node output argument
}}
Nodes having both inputs and an output:
publicclassAmountOfNode:SonsNode{publicint ItemId { get; set; } // input argument property [IsArgOut]publicint OwnedAmount { get; set; } // output argument propertypublicoverridevoidExecute() {List<object> args =RedNodeLoader.GetArgumentsOf(this); // getting the value of "ItemId" input argument OwnedAmount = LocalPlayer.Inventory.AmountOf((int)args[0]); // getting and storing the output argument in the "OwnedAmount" property
}}
Nodes using external types:
publicclassSetActiveNode:SonsNode{ [XmlIgnore] // use this attribute when the property type it's not (int, float, bool, string, Vector3, Vector2)publicGameObject GameObject { get; set; } // 1° input argument propertypublicbool Value { get; set; } // 2° input argument propertypublicoverridevoidExecute() {List<object> args =RedNodeLoader.GetArgumentsOf(this); // getting value of "GameObject" and "Value"var go = (GameObject)args[0]; // casting and storing the GameObjectgo.SetActive((bool)args[1]); // using the casted GameObject and the boolean property }}
VECTORS: when working with vectors always use System.Numerics.Vector as the property type and not the UnityEngine one, both in the editor and RedNodeLoader class. In the implementation of the node you will then need to create a UnityEngine.Vector using the (X,Y,Z) values accordingly.