Difference between revisions of "Script Modding Guide"
| Line 1: | Line 1: | ||
| + | {{ContentTemplateModernInline | ||
| + | | welcome = Getting Started | ||
| + | | blurb = Set up your environment, understand core concepts, and ship your first Holdfast script mod. | ||
| + | |||
| + | | sections_title = On this page | ||
| + | | sections_content = | ||
| + | * [[#Getting Started|Overview]] | ||
| + | * [[#Setting Up Your Environment|Setting Up Your Environment]] | ||
| + | ** [[#Connecting Unity to your editor|Connect Unity ↔ Editor]] | ||
| + | ** [[#Creating your mod|Create Your Mod]] | ||
| + | * [[#Core Concepts|Core Concepts]] | ||
| + | ** [[#Shared Interface|Shared Interface]] | ||
| + | ** [[#Client or Server, what are those?!|Client vs Server]] | ||
| + | ** [[#Mod Install and Load Types|Install & Load Types]] | ||
| + | ** [[#Mod Variables|Mod Variables]] | ||
| + | ** [[#Security Concerns|Security]] | ||
| + | ** [[#Gotchya's|Gotchas]] | ||
| + | * [[#RC commands specific for modding use|RC Commands for Modding]] | ||
| + | * [[#IHoldfastGame|IHoldfastGame]] | ||
| + | * [[#IHoldfastSharedMethods|IHoldfastSharedMethods]] | ||
| + | * [[#IHoldfastSharedMethods2|IHoldfastSharedMethods2]] | ||
| + | * [[#IHoldfastSharedMethods3|IHoldfastSharedMethods3]] | ||
| + | * [[#SharedMethods Copy/Paste|SharedMethods Copy/Paste]] | ||
| + | * [[#Key Scene GameObjects|Key Scene GameObjects]] | ||
| + | ** [[#ClientScene GameObjects|ClientScene]] | ||
| + | ** [[#Main Canvas GameObjects|Main Canvas]] | ||
| + | ** [[#Menu Canvas GameObjects|Menu Canvas]] | ||
| + | * [[#Code Examples & Resources|Code Examples & Resources]] | ||
| + | * [[#Support|Support]] | ||
| + | |||
| + | | featured_title = Gameplay | ||
| + | | featured_content = | ||
| + | |||
= Getting Started = | = Getting Started = | ||
'''''Basic knowledge of C# and Unity is required.''''' | '''''Basic knowledge of C# and Unity is required.''''' | ||
| Line 19: | Line 52: | ||
In this mod’s folder create a new script and name it what you named your mod. | In this mod’s folder create a new script and name it what you named your mod. | ||
| − | |||
[[File:Image 2024-12-03 105007341.png|center|thumb|700x700px]] | [[File:Image 2024-12-03 105007341.png|center|thumb|700x700px]] | ||
[[File:Image 2024-12-03 105229623.png|center|thumb|700x700px]] | [[File:Image 2024-12-03 105229623.png|center|thumb|700x700px]] | ||
| Line 30: | Line 62: | ||
The script should implement the shared interface that you're going to be using. Each interface has a few different functions but operates the same. | The script should implement the shared interface that you're going to be using. Each interface has a few different functions but operates the same. | ||
| − | [[Script Modding Guide#IHoldfastGame|IHoldfastGame]], [[Script_Modding_Guide#IHoldfastSharedMethods|IHoldfastSharedMethods]], | + | [[Script Modding Guide#IHoldfastGame|IHoldfastGame]], [[Script_Modding_Guide#IHoldfastSharedMethods|IHoldfastSharedMethods]], [[Script Modding Guide#IHoldfastSharedMethods2|IHoldfastSharedMethods2]], [[Script Modding Guide#IHoldfastSharedMethods3|IHoldfastSharedMethods3.]] |
* If you're using multiple IHoldfastSharedMethods, make sure you implement them together at the top of your script. | * If you're using multiple IHoldfastSharedMethods, make sure you implement them together at the top of your script. | ||
| Line 37: | Line 69: | ||
'''Example:''' Here’s a mod that’s called “LineFormer” that has the script called “LineFormer.cs” which implements the IHoldfastSharedMethods interface. | '''Example:''' Here’s a mod that’s called “LineFormer” that has the script called “LineFormer.cs” which implements the IHoldfastSharedMethods interface. | ||
| − | + | [[File:Script Modding Create Project.png|center| ]] | |
| − | [[File:Script Modding Create Project.png|center| | ||
| − | |||
| − | |||
| − | ]] | ||
| − | |||
'''Example 2:''' Here’s a mod that’s called “MeleeTrainer” that has the script called “MeleeTrainer.cs” which implements the [[Script_Modding_Guide#IHoldfastSharedMethods|IHoldfastSharedMethods]] & [[Script Modding Guide#IHoldfastSharedMethods2|IHoldfastSharedMethods2]] interfaces. | '''Example 2:''' Here’s a mod that’s called “MeleeTrainer” that has the script called “MeleeTrainer.cs” which implements the [[Script_Modding_Guide#IHoldfastSharedMethods|IHoldfastSharedMethods]] & [[Script Modding Guide#IHoldfastSharedMethods2|IHoldfastSharedMethods2]] interfaces. | ||
[[File:Image 2024-12-03 110459063.png|center|thumb|700x700px]] | [[File:Image 2024-12-03 110459063.png|center|thumb|700x700px]] | ||
| − | |||
=== Client or Server, what are those?! === | === Client or Server, what are those?! === | ||
| Line 52: | Line 78: | ||
The "Server" is the game server which handles all players verification data, networking, chat/voip, etc. | The "Server" is the game server which handles all players verification data, networking, chat/voip, etc. | ||
| − | |||
Whereas the "Client" is either your own client referred to as the "Owner/Local Player" or someone else referred to as a "Proxy". | Whereas the "Client" is either your own client referred to as the "Owner/Local Player" or someone else referred to as a "Proxy". | ||
| Line 78: | Line 103: | ||
* This script would parse this variable and use its value. This example shows how to safely check for your mod's specific variable and convert the text "true" into a boolean <code>true</code>. | * This script would parse this variable and use its value. This example shows how to safely check for your mod's specific variable and convert the text "true" into a boolean <code>true</code>. | ||
| − | |||
[[File:Mod Variables.png|center|thumb|500x500px]] | [[File:Mod Variables.png|center|thumb|500x500px]] | ||
=== Security Concerns === | === Security Concerns === | ||
| − | |||
As you might be aware, we have some security related scripts that, for example don't allow a modder to gain access to the server or client's machine (ie: System.IO, System.Reflection, etc), these typically will show up as an error on the server's console with something along the lines of | As you might be aware, we have some security related scripts that, for example don't allow a modder to gain access to the server or client's machine (ie: System.IO, System.Reflection, etc), these typically will show up as an error on the server's console with something along the lines of | ||
[UMod.Scripting.ScriptDomain]: Illegal reference to disallowed namespace: System.Reflection. | [UMod.Scripting.ScriptDomain]: Illegal reference to disallowed namespace: System.Reflection. | ||
| Line 98: | Line 121: | ||
=== Make carbon player bots not do any auto input=== | === Make carbon player bots not do any auto input=== | ||
If you plan on modifying bots position or rotation, call the following script to force bots to not auto move or rotate. Note: this is different than setting the carbonplayer [[Remote_Console_Commands#Bot_Commands|forceInputAxis/forceInputRotation]], since thoses ones will lock ALL the bots in the action, whereas this will allow you to individually control bots's facing rotation and movement system. | If you plan on modifying bots position or rotation, call the following script to force bots to not auto move or rotate. Note: this is different than setting the carbonplayer [[Remote_Console_Commands#Bot_Commands|forceInputAxis/forceInputRotation]], since thoses ones will lock ALL the bots in the action, whereas this will allow you to individually control bots's facing rotation and movement system. | ||
| − | |||
rc carbonPlayers ignoreAutoControls <true/false> | rc carbonPlayers ignoreAutoControls <true/false> | ||
=== Send network trajectory info to client without showing it to players=== | === Send network trajectory info to client without showing it to players=== | ||
Similar to [[Remote_Console_Commands#Game_Settings|drawFirearmTrajectories]], this will send the trajectory data from server to client. Unlike the aforementioned command, this will NOT display it to the player, but the mod will still be recieving the data. Enable this only if you're planning on using the "OnShotInfo" on a client side, for server side mods, this isn't required. | Similar to [[Remote_Console_Commands#Game_Settings|drawFirearmTrajectories]], this will send the trajectory data from server to client. Unlike the aforementioned command, this will NOT display it to the player, but the mod will still be recieving the data. Enable this only if you're planning on using the "OnShotInfo" on a client side, for server side mods, this isn't required. | ||
| − | |||
rc set drawFirearmTrajectoriesInfo <true/false> | rc set drawFirearmTrajectoriesInfo <true/false> | ||
| Line 110: | Line 131: | ||
* The communication flow is simple: | * The communication flow is simple: | ||
| − | |||
# The server executes an rc command to send a string of data. | # The server executes an rc command to send a string of data. | ||
# The client receives that string from the OnTextMessage event. | # The client receives that string from the OnTextMessage event. | ||
==== Sending Data from the Server ==== | ==== Sending Data from the Server ==== | ||
| − | |||
* Use one of the following commands: | * Use one of the following commands: | ||
rc serverAdmin quietPrivateMessage <player_id> <message> | rc serverAdmin quietPrivateMessage <player_id> <message> | ||
| + | rc serverAdmin quietBroadcastMessage <message> | ||
| − | |||
==== Receiving Data on The Client ==== | ==== Receiving Data on The Client ==== | ||
| − | |||
* Your client-side mod will receive the data in the OnTextMessage method. It's crucial to filter these messages to ensure you only process the ones intended for your mod. | * Your client-side mod will receive the data in the OnTextMessage method. It's crucial to filter these messages to ensure you only process the ones intended for your mod. | ||
| − | |||
* Because OnTextMessage can be used by many different mods simultaneously, you must create a unique message format to avoid conflicts. If you don't, your mod might try to process messages from another mod, leading to errors. A highly recommended pattern is to prefix your messages with a unique ID, like: <code>MOD_ID:COMMAND:VALUE.</code> | * Because OnTextMessage can be used by many different mods simultaneously, you must create a unique message format to avoid conflicts. If you don't, your mod might try to process messages from another mod, leading to errors. A highly recommended pattern is to prefix your messages with a unique ID, like: <code>MOD_ID:COMMAND:VALUE.</code> | ||
==== Practical Example: Welcome Message to a Debug.Log ==== | ==== Practical Example: Welcome Message to a Debug.Log ==== | ||
Part 1: The Server-Side Code (Sending the Message) - | Part 1: The Server-Side Code (Sending the Message) - | ||
| − | |||
* This code runs on the server. When a player spawns, it sends them a quiet message containing their own PlayerID. | * This code runs on the server. When a player spawns, it sends them a quiet message containing their own PlayerID. | ||
| + | [[File:OnPlayerSpawned.png|center|thumb|500x500px]] | ||
| − | |||
Part 2: The Client-Side Code (Receiving the Message) | Part 2: The Client-Side Code (Receiving the Message) | ||
| − | + | * This code runs on every client. It listens for the specific welcome message and logs it. | |
| − | * This code runs on every client. It listens for the specific welcome message and logs it.[[File:OnTextMessage.png|center|thumb|500x500px]] | + | [[File:OnTextMessage.png|center|thumb|500x500px]] |
= IHoldfastGame = | = IHoldfastGame = | ||
| Line 140: | Line 156: | ||
==== Implement the Interface ==== | ==== Implement the Interface ==== | ||
| − | |||
* First, ensure your script is using the HoldfastBridge namespace. | * First, ensure your script is using the HoldfastBridge namespace. | ||
| − | + | * Your main class signature must then use IHoldfastGame. | |
| − | * Your main class signature must then use IHoldfastGame.[[File:HoldfastBridge.png|center|thumb|600x600px]] | + | [[File:HoldfastBridge.png|center|thumb|600x600px]] |
==== Receive and Store the Game Methods ==== | ==== Receive and Store the Game Methods ==== | ||
| − | |||
* The IHoldfastGame interface requires you to implement the OnGameMethodsInitialized method. The game engine calls this method once during startup and provides an IHoldfastGameMethods object. | * The IHoldfastGame interface requires you to implement the OnGameMethodsInitialized method. The game engine calls this method once during startup and provides an IHoldfastGameMethods object. | ||
* The best practice is to store this object in a static property. This makes the game methods accessible from anywhere in your mod's codebase without needing to pass references around. | * The best practice is to store this object in a static property. This makes the game methods accessible from anywhere in your mod's codebase without needing to pass references around. | ||
| Line 152: | Line 166: | ||
==== Execute a Command ==== | ==== Execute a Command ==== | ||
| − | |||
* With the GameMethods stored, you can now call ExecuteConsoleCommand from any part of your mod. | * With the GameMethods stored, you can now call ExecuteConsoleCommand from any part of your mod. | ||
| − | + | void ExecuteConsoleCommand(string command, out string output, out bool success); | |
| − | void ExecuteConsoleCommand(string command, out string output, | ||
# <code>command</code>: The RC command string to execute. | # <code>command</code>: The RC command string to execute. | ||
| Line 162: | Line 174: | ||
===== Practical Example: Toggling Mouse Lock for a UI Panel ===== | ===== Practical Example: Toggling Mouse Lock for a UI Panel ===== | ||
| − | + | * This is a common use case. When you show a custom UI, you might want to unlock the mouse to the player can interact with it. | |
| − | * This is a common use case. When you show a custom UI, you might want to unlock the mouse to the player can interact with it.[[File:ShouldUnlockMouse true.png|center|thumb|600x600px]] | + | [[File:ShouldUnlockMouse true.png|center|thumb|600x600px]] |
= IHoldfastSharedMethods = | = IHoldfastSharedMethods = | ||
| Line 182: | Line 194: | ||
==== OnUpdateTimeRemaining ==== | ==== OnUpdateTimeRemaining ==== | ||
The game will share the current remaining time on every frame of the game. | The game will share the current remaining time on every frame of the game. | ||
| − | |||
void OnUpdateTimeRemaining(float time); | void OnUpdateTimeRemaining(float time); | ||
==== OnIsServer ==== | ==== OnIsServer ==== | ||
The game will return if the mod is loaded on a server. | The game will return if the mod is loaded on a server. | ||
| − | |||
void OnIsServer(bool server); | void OnIsServer(bool server); | ||
==== OnIsClient ==== | ==== OnIsClient ==== | ||
The game will return if the mod is loaded on a client, if it is, you'll retrieve the current player's steam id. | The game will return if the mod is loaded on a client, if it is, you'll retrieve the current player's steam id. | ||
| − | |||
void OnIsClient(bool client, ulong steamId); | void OnIsClient(bool client, ulong steamId); | ||
==== OnRoundDetails ==== | ==== OnRoundDetails ==== | ||
The game will call this on round start with details regarding the current round. | The game will call this on round start with details regarding the current round. | ||
| − | |||
void OnRoundDetails(int roundId, string serverName, string mapName, FactionCountry attackingFaction, FactionCountry defendingFaction, GameplayMode gameplayMode, GameType gameType); | void OnRoundDetails(int roundId, string serverName, string mapName, FactionCountry attackingFaction, FactionCountry defendingFaction, GameplayMode gameplayMode, GameType gameType); | ||
==== PassConfigVariables ==== | ==== PassConfigVariables ==== | ||
The game will call this once with all the config variables set on the config file of the server. | The game will call this once with all the config variables set on the config file of the server. | ||
| − | |||
Note: you will receive all variables, so make sure to filter only the ones you care about. | Note: you will receive all variables, so make sure to filter only the ones you care about. | ||
void PassConfigVariables(string[] value); | void PassConfigVariables(string[] value); | ||
| Line 208: | Line 215: | ||
==== OnPlayerJoined ==== | ==== OnPlayerJoined ==== | ||
The game will call this when a player or a bot joins the round. | The game will call this when a player or a bot joins the round. | ||
| − | |||
void OnPlayerJoined(int playerId, ulong steamId, string name, string regimentTag, bool isBot); | void OnPlayerJoined(int playerId, ulong steamId, string name, string regimentTag, bool isBot); | ||
==== OnPlayerLeft ==== | ==== OnPlayerLeft ==== | ||
The game will call this when a player leaves the round. | The game will call this when a player leaves the round. | ||
| − | |||
Note: Bots cannot leave, they'll keep respawning once dead. | Note: Bots cannot leave, they'll keep respawning once dead. | ||
| − | |||
void OnPlayerLeft(int playerId); | void OnPlayerLeft(int playerId); | ||
==== OnPlayerSpawned ==== | ==== OnPlayerSpawned ==== | ||
The game will call this when a player spawns in game. | The game will call this when a player spawns in game. | ||
| − | |||
void OnPlayerSpawned(int playerId, int spawnSectionId, FactionCountry playerFaction, PlayerClass playerClass, int uniformId, GameObject playerObject); | void OnPlayerSpawned(int playerId, int spawnSectionId, FactionCountry playerFaction, PlayerClass playerClass, int uniformId, GameObject playerObject); | ||
==== OnPlayerHurt ==== | ==== OnPlayerHurt ==== | ||
The game will call this when a player is hurt in game. | The game will call this when a player is hurt in game. | ||
| − | |||
void OnPlayerHurt(int playerId, byte oldHp, byte newHp, EntityHealthChangedReason reason); | void OnPlayerHurt(int playerId, byte oldHp, byte newHp, EntityHealthChangedReason reason); | ||
==== OnPlayerKilledPlayer ==== | ==== OnPlayerKilledPlayer ==== | ||
The game will call this when a player is killed by another player. | The game will call this when a player is killed by another player. | ||
| − | |||
void OnPlayerKilledPlayer(int killerPlayerId, int victimPlayerId, EntityHealthChangedReason reason, string details); | void OnPlayerKilledPlayer(int killerPlayerId, int victimPlayerId, EntityHealthChangedReason reason, string details); | ||
==== OnScorableAction ==== | ==== OnScorableAction ==== | ||
The game will call this when a player receives any score. | The game will call this when a player receives any score. | ||
| − | |||
void OnScorableAction(int playerId, int score, ScorableActionType reason); | void OnScorableAction(int playerId, int score, ScorableActionType reason); | ||
==== OnPlayerShoot ==== | ==== OnPlayerShoot ==== | ||
The game will call this when a player shoots his gun. | The game will call this when a player shoots his gun. | ||
| − | |||
void OnPlayerShoot(int playerId, bool dryShot); | void OnPlayerShoot(int playerId, bool dryShot); | ||
==== OnShotInfo ==== | ==== OnShotInfo ==== | ||
The game will call this when a bullet. | The game will call this when a bullet. | ||
| − | + | Note: This requires `rc set drawFirearmTajectories true` or `rc set drawFirearmTajectories | |
| − | Note: This requires `rc set drawFirearmTajectories true` or `rc set | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
Revision as of 10:23, 18 September 2025
{{ContentTemplateModernInline
| welcome = Getting Started | blurb = Set up your environment, understand core concepts, and ship your first Holdfast script mod.
| sections_title = On this page | sections_content =
- Overview
- Setting Up Your Environment
- Core Concepts
- RC Commands for Modding
- IHoldfastGame
- IHoldfastSharedMethods
- IHoldfastSharedMethods2
- IHoldfastSharedMethods3
- SharedMethods Copy/Paste
- Key Scene GameObjects
- Code Examples & Resources
- Support
| featured_title = Gameplay | featured_content =
Getting Started
Basic knowledge of C# and Unity is required.
Setting Up Your Environment
Connecting Unity to your editor
Make sure your Unity Editor is connected to whatever you're using to write the script. (Example: Visual Studio Community)
In the Unity Editor, click Edit -> Preferences -> External Tools -> External Script Editor -> Visual Studio Community.
Restart both your Unity Editor and your Script Editor to make sure these changes take affect.
Creating your mod
Create a new mod and name it whatever your script does.
In the Unity Editor click Holdfast SDK Tools -> Create Empty Mod
In this mod’s folder create a new script and name it what you named your mod.
Double click the script in your Unity Project to open it.
Core Concepts
The script should implement the shared interface that you're going to be using. Each interface has a few different functions but operates the same.
IHoldfastGame, IHoldfastSharedMethods, IHoldfastSharedMethods2, IHoldfastSharedMethods3.
- If you're using multiple IHoldfastSharedMethods, make sure you implement them together at the top of your script.
Example: public class LineFormer : IHoldfastSharedMethods, IHoldfastSharedMethods2, IHoldfastSharedMethods3
Example: Here’s a mod that’s called “LineFormer” that has the script called “LineFormer.cs” which implements the IHoldfastSharedMethods interface.
Example 2: Here’s a mod that’s called “MeleeTrainer” that has the script called “MeleeTrainer.cs” which implements the IHoldfastSharedMethods & IHoldfastSharedMethods2 interfaces.
Client or Server, what are those?!
One of the most important concepts to understand while script modifying Holdfast is the concept of "Client" and "Server".
The "Server" is the game server which handles all players verification data, networking, chat/voip, etc. Whereas the "Client" is either your own client referred to as the "Owner/Local Player" or someone else referred to as a "Proxy".
Mod Install and Load Types
Similarly to the normal process of using the load_mod <steam_id> load type for mods, we've added a load_mod_server_only <steamid> which loads a mod specifically only on server side (such as a custom stats tracking or auto admin mod), and a load_mod_client_only <steamid> which inversely only loads the mod on client side (such as a ui mod or a audio replacement mod). In addition mods_installed_server_only <steamid> was also added. The advantage of this is mods that the client does not need to join the server, the clients won't need to install the mod on their end.
The load order for this new feature is first all the Common Mods are loaded and than the client/server specific one's are loaded. As always, the internal order of each list, is dependent on the order of how they were typed in the config file.
Please note players can only load mods that the server allows on their list, so a mods_installed_client_only <steamid> doesn't make sense, so if you're going to be making a client only loaded mod, it needs to be installed on both the client and the server.
Mod Variables
Simply put, mods can receive variables through the PassConfigVariables() method. These variables are supplied in the server config file in the format mod_variable <string> for Global Scope (outside of the map rotations) or mod_variable_local <string> for Local Scope (inside the map rotation). Global Scope variables will always be loaded BEFORE any local scope variables.
Server config mod variables are sent to all mods running on the server. To prevent conflicts where one mod accidentally reads another mod's settings, it is highly recommended to prefix your variables. A common and effective pattern is modID:key:value, where modID is a unique identifier for your mod (like its Steam Workshop ID or a custom prefix).
Practical Example: Slay Players on Spawn
Part 1: The Mod_Variable
mod_variable HardcoreMod:EnableKillOnSpawn:true
HardcoreMod: This is the modID.EnableKillOnSpawn: This is the key.True: This is the value.
Part 2: Script Implementation
- This script would parse this variable and use its value. This example shows how to safely check for your mod's specific variable and convert the text "true" into a boolean
true.
Security Concerns
As you might be aware, we have some security related scripts that, for example don't allow a modder to gain access to the server or client's machine (ie: System.IO, System.Reflection, etc), these typically will show up as an error on the server's console with something along the lines of
[UMod.Scripting.ScriptDomain]: Illegal reference to disallowed namespace: System.Reflection.
We've added a feature for server hosts to be able to pass a "-allowRestrictedMods" in the launch parameters of the server. This will turn off the securities on the server machine ONLY, for ALL the mods. As such, if you rent your servers, you will need to talk to your host provider to enable this if they want to support it.
What this will allow you to do is hook directly with IO operations and other restricted features (writing files, databases, websites api's, etc) so for development this will open major features that will be able to be done easier or that weren't able to be done outright.
Gotchya's
It's highly recommended that most actions that a mod would do would be defered by a frame or a few ms when it comes to interacting with a player (healing, damaging, reviving, positioning, rotation, etc). If you code something, and it's logically sound, but ingame it's not doing exactly as expected or has bugs occuring due to it, after trying to delay the action, feel free to ask in mod-support.
Note, don't use a "static" member inside of the Interface classes. This confuses the dynamic loader of C# and it'll be bad times for you trying to figure out why.
RC commands specific for modding use
Make carbon player bots not do any auto input
If you plan on modifying bots position or rotation, call the following script to force bots to not auto move or rotate. Note: this is different than setting the carbonplayer forceInputAxis/forceInputRotation, since thoses ones will lock ALL the bots in the action, whereas this will allow you to individually control bots's facing rotation and movement system.
rc carbonPlayers ignoreAutoControls <true/false>
Send network trajectory info to client without showing it to players
Similar to drawFirearmTrajectories, this will send the trajectory data from server to client. Unlike the aforementioned command, this will NOT display it to the player, but the mod will still be recieving the data. Enable this only if you're planning on using the "OnShotInfo" on a client side, for server side mods, this isn't required.
rc set drawFirearmTrajectoriesInfo <true/false>
Quiet Messages: Server-to-Client Data Transfer
Quiet messages are a powerful tool for a server-side mod to send data to a client-side mod without showing anything in the player's chat. They are essential for any mod where the server needs to update a client's custom UI or trigger a client-side action.
- The communication flow is simple:
- The server executes an rc command to send a string of data.
- The client receives that string from the OnTextMessage event.
Sending Data from the Server
- Use one of the following commands:
rc serverAdmin quietPrivateMessage <player_id> <message> rc serverAdmin quietBroadcastMessage <message>
Receiving Data on The Client
- Your client-side mod will receive the data in the OnTextMessage method. It's crucial to filter these messages to ensure you only process the ones intended for your mod.
- Because OnTextMessage can be used by many different mods simultaneously, you must create a unique message format to avoid conflicts. If you don't, your mod might try to process messages from another mod, leading to errors. A highly recommended pattern is to prefix your messages with a unique ID, like:
MOD_ID:COMMAND:VALUE.
Practical Example: Welcome Message to a Debug.Log
Part 1: The Server-Side Code (Sending the Message) -
- This code runs on the server. When a player spawns, it sends them a quiet message containing their own PlayerID.
Part 2: The Client-Side Code (Receiving the Message)
- This code runs on every client. It listens for the specific welcome message and logs it.
IHoldfastGame
To execute RC commands from your script you must gain access one of the game's core methods. This is accomplished by implementing the "IHoldfastGame" interface.
Implement the Interface
- First, ensure your script is using the HoldfastBridge namespace.
- Your main class signature must then use IHoldfastGame.
Receive and Store the Game Methods
- The IHoldfastGame interface requires you to implement the OnGameMethodsInitialized method. The game engine calls this method once during startup and provides an IHoldfastGameMethods object.
- The best practice is to store this object in a static property. This makes the game methods accessible from anywhere in your mod's codebase without needing to pass references around.
Execute a Command
- With the GameMethods stored, you can now call ExecuteConsoleCommand from any part of your mod.
void ExecuteConsoleCommand(string command, out string output, out bool success);
command: The RC command string to execute.output: An out parameter that will be filled with any text the command returns. This is very useful for debugging.success: An out parameter that will be true if the command was recognized and executed successfully.
Practical Example: Toggling Mouse Lock for a UI Panel
- This is a common use case. When you show a custom UI, you might want to unlock the mouse to the player can interact with it.
If you hover over the interface's properties in visual studio, these can be found in the inspector too. In case visual studio doesn't link the dll correctly to inspect the data, feel free to refer to the raw cs files inside the DLL.
OnSyncValueState
The game will share an int when the mod loads. A random synchronized value (same on client and server), for example useful if you need a seed for a map generator.
void OnSyncValueState(int value);
OnUpdateSyncedTime
The game will share the current synchronized time (same on client and server) on every frame of the game.
void OnUpdateSyncedTime(double time);
OnUpdateElapsedTime
The game will return the seconds since the round started on every frame of the game.
void OnUpdateElapsedTime(float time);
OnUpdateTimeRemaining
The game will share the current remaining time on every frame of the game.
void OnUpdateTimeRemaining(float time);
OnIsServer
The game will return if the mod is loaded on a server.
void OnIsServer(bool server);
OnIsClient
The game will return if the mod is loaded on a client, if it is, you'll retrieve the current player's steam id.
void OnIsClient(bool client, ulong steamId);
OnRoundDetails
The game will call this on round start with details regarding the current round.
void OnRoundDetails(int roundId, string serverName, string mapName, FactionCountry attackingFaction, FactionCountry defendingFaction, GameplayMode gameplayMode, GameType gameType);
PassConfigVariables
The game will call this once with all the config variables set on the config file of the server. Note: you will receive all variables, so make sure to filter only the ones you care about.
void PassConfigVariables(string[] value);
OnPlayerJoined
The game will call this when a player or a bot joins the round.
void OnPlayerJoined(int playerId, ulong steamId, string name, string regimentTag, bool isBot);
OnPlayerLeft
The game will call this when a player leaves the round. Note: Bots cannot leave, they'll keep respawning once dead.
void OnPlayerLeft(int playerId);
OnPlayerSpawned
The game will call this when a player spawns in game.
void OnPlayerSpawned(int playerId, int spawnSectionId, FactionCountry playerFaction, PlayerClass playerClass, int uniformId, GameObject playerObject);
OnPlayerHurt
The game will call this when a player is hurt in game.
void OnPlayerHurt(int playerId, byte oldHp, byte newHp, EntityHealthChangedReason reason);
OnPlayerKilledPlayer
The game will call this when a player is killed by another player.
void OnPlayerKilledPlayer(int killerPlayerId, int victimPlayerId, EntityHealthChangedReason reason, string details);
OnScorableAction
The game will call this when a player receives any score.
void OnScorableAction(int playerId, int score, ScorableActionType reason);
OnPlayerShoot
The game will call this when a player shoots his gun.
void OnPlayerShoot(int playerId, bool dryShot);
OnShotInfo
The game will call this when a bullet. Note: This requires `rc set drawFirearmTajectories true` or `rc set drawFirearmTajectories











