Server Side Scripting – Arma 3
(Add information about onPlayerJoinAttempt and new 2.16 capabilities) |
(2.16 -> 2.18) |
||
(3 intermediate revisions by 2 users not shown) | |||
Line 7: | Line 7: | ||
== Event Handlers == | == Event Handlers == | ||
{| class="wikitable sortable" | {| class="wikitable sortable valign-top" | ||
! Event Name | ! Event Name | ||
! Description | ! Description | ||
Line 44: | Line 44: | ||
| user id, test index | | user id, test index | ||
|- | |- | ||
| onPlayerJoinAttempt {{GVI|arma3|2. | | onPlayerJoinAttempt {{GVI|arma3|2.18}} | ||
| Called repeatedly while a new player is trying to join the server. Must return either "ACCEPT", "DELAY" or "REFUSE" | | Called repeatedly while a new player is trying to join the server. Must return either "ACCEPT", "DELAY" or "REFUSE": | ||
* "ACCEPT": the player is let through into the server. | |||
* "DELAY": the player stays in the loading screen and is not let into the server. | |||
* "REFUSE": the player is kicked; a kick message can be appended by sending "REFUSE_You were kicked because reason". | |||
| | |||
* userId: [[String]] - the DirectPlay ID of the connecting player, see [[getPlayerID]] | |||
* waitTime: [[Scalar]] - the time in second since when the players connection attempt has started. How long the player has been waiting in loading screen. | |||
|- | |||
| sendChatMessage{{GVI|arma3|2.18}} | |||
| Send a "System" chat message to the specified user | |||
| | | | ||
* userId: [[String]] - | * userId: [[String]] - the DirectPlay ID of the connecting player, see [[getPlayerID]] | ||
* | * message: [[String]] - Text message to be sent (If message starts with $, it will be sent as localized chat message and displayed in the receivers language) | ||
|} | |} | ||
Line 98: | Line 104: | ||
| level checkFile [userId, fileIndex] | | level checkFile [userId, fileIndex] | ||
| File | | File | ||
| See | | See {{Link|ArmA: Armed Assault: Addon Signatures|Addon Signature}} for a file with given index on given user computer, level determines test depth (0 = default, 1 = deep. Note: deep can be very slow) | ||
|- | |- | ||
| level checkExe userId | | level checkExe userId | ||
Line 118: | Line 124: | ||
* [[abs]] | * [[abs]] | ||
* [[acos]] | * [[acos]] | ||
* {{GVI|arma3|2.18|size= 0.75}} [[allUsers]] | |||
* [[append]] | * [[append]] | ||
* [[apply]] | * [[apply]] | ||
Line 127: | Line 134: | ||
* [[breakOut]] | * [[breakOut]] | ||
* [[call]] | * [[call]] | ||
* {{GVI|arma3|2.18|size= 0.75}} [[callExtension]] | |||
* [[case]] | * [[case]] | ||
* [[ceil]] | * [[ceil]] | ||
Line 133: | Line 141: | ||
* [[compileFinal]] | * [[compileFinal]] | ||
* [[cos]] | * [[cos]] | ||
* [[count]] | * [[count]] | ||
* [[deg]] | * [[deg]] | ||
Line 152: | Line 159: | ||
* [[forEach]] | * [[forEach]] | ||
* [[for]] | * [[for]] | ||
* {{GVI|arma3|2.18|size= 0.75}} [[getUserInfo]] | |||
* {{GVI|arma3|2.18|size= 0.75}} [[getVariable]] (namespace only) | |||
* [[if]] | * [[if]] | ||
* [[in]] | * [[in]] | ||
Line 158: | Line 167: | ||
* [[ln]] | * [[ln]] | ||
* [[log]] | * [[log]] | ||
* {{GVI|arma3|2.18|size= 0.75}} [[missionNamespace]] | |||
* [[nil]] | * [[nil]] | ||
* [[not]] | * [[not]] | ||
Line 178: | Line 188: | ||
* [[selectRandomWeighted]] | * [[selectRandomWeighted]] | ||
* [[set]] | * [[set]] | ||
* {{GVI|arma3|2.18|size= 0.75}} [[setVariable]] (namespace only) | |||
* [[sin]] | * [[sin]] | ||
* [[sort]] | * [[sort]] | ||
Line 197: | Line 208: | ||
* [[try]] | * [[try]] | ||
* [[typeName]] | * [[typeName]] | ||
* {{GVI|arma3|2.18|size= 0.75}} [[uiNamespace]] | |||
* [[waitUntil]] | * [[waitUntil]] | ||
* [[while]] | * [[while]] | ||
Line 202: | Line 214: | ||
}} | }} | ||
{{ArgTitle|2|Mission Scripts Interaction|{{GVI|arma3|2.18}}}} | |||
It is possible to interface with mission scripts that are not limited to the server scripts command set. There are two main approaches for this detailed below.<br> | |||
One major thing to keep in mind is that both these approaches depend on mission scripts being present. | |||
There are two main approaches for this detailed below.<br> | Which is not the case when a server is freshly started and didn't load a mission yet. So you need to account for the handlers not being present. | ||
One major thing to keep in mind is that both these approaches depend on mission scripts being present. Which is not the case when a server is freshly started and didn't load a mission yet. So you need to account for the handlers not being present. | |||
=== CallExtension with | === CallExtension with Callbacks === | ||
[[callExtension]] being available offers the option to communicate through the Extension. As extension callbacks are executed in mission scripts.<br> | [[callExtension]] being available offers the option to communicate through the Extension. As extension callbacks are executed in mission scripts.<br> | ||
When the extension receives a request, it can trigger a callback to send information to be handled inside a mission script.<br> | When the extension receives a request, it can trigger a callback to send information to be handled inside a mission script.<br> | ||
Here is an example of a (pseudocode) extension utilizing this pattern for a "onPlayerJoinAttempt" event. | |||
Here is an example of a (pseudocode) extension utilizing this pattern for a "onPlayerJoinAttempt" event. | |||
Extension code: | Extension code: | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="C#"> | ||
bool goodToGo = true; | bool goodToGo = true; | ||
bool goodToKick = false; | bool goodToKick = false; | ||
Line 237: | Line 239: | ||
if (goodToGo) | if (goodToGo) | ||
{ | { | ||
goodToGo = false; // | goodToGo = false; // only once | ||
output.Append("ACCEPT"); | output.Append("ACCEPT"); | ||
return; | return; | ||
Line 244: | Line 246: | ||
if (goodToKick) | if (goodToKick) | ||
{ | { | ||
goodToKick = false; // | goodToKick = false; // only once | ||
output.Append("REFUSE"); | output.Append("REFUSE"); | ||
output.Append(kickReason); | output.Append(kickReason); | ||
Line 253: | Line 255: | ||
callback("ext", "joinRequest", args[0]); | callback("ext", "joinRequest", args[0]); | ||
output.Append("DELAY"); // | output.Append("DELAY"); // wait till our script tells us what to do | ||
} | } | ||
Line 272: | Line 274: | ||
onPlayerJoinAttempt = " if (missionNamespace getVariable ['ext_ready', false]) then { ('extension' callExtension ['joinRequest', _this]) select 0 } else { 'ACCEPT' } "; | onPlayerJoinAttempt = " if (missionNamespace getVariable ['ext_ready', false]) then { ('extension' callExtension ['joinRequest', _this]) select 0 } else { 'ACCEPT' } "; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Notice how we specifically check for a missionNamespace variable to indicate whether the actual callback handler is ready. | Notice how we specifically check for a missionNamespace variable to indicate whether the actual callback handler is ready. | ||
Then we have this mission script: | Then we have this mission script: | ||
<sqf> | <sqf> | ||
addMissionEventHandler ["ExtensionCallback", { | addMissionEventHandler ["ExtensionCallback", { | ||
"extension" callExtension ["go", []]; | |||
}]; | }]; | ||
ExtensionReady = true; | |||
</sqf> | </sqf> | ||
This | This does not actually implement any logic for handling player joins, but you can see how you could pass data through the callback and handle it accordingly. | ||
=== Call missionNamespace Functions === | |||
Since [[getVariable]] has become available, and [[call]] has already been available previously, this means we can now get functions from missionNamespace and execute them from our server scripts environment. Example: | |||
Server config: | Server config: | ||
<syntaxhighlight lang="cpp"> | <syntaxhighlight lang="cpp"> | ||
Line 300: | Line 299: | ||
PlayerJoinHandler = { | PlayerJoinHandler = { | ||
params ["_playerId"]; | params ["_playerId"]; | ||
"ACCEPT" // return | "ACCEPT"; // return value | ||
}; | }; | ||
</sqf> | </sqf> | ||
Note a major thing here, we call a function from server-side script, that uses [[params]], even though params is a command that is not available to server side scripts.<br> | Note a major thing here, we call a function from server-side script, that uses [[params]], even though params is a command that is not available to server side scripts.<br> | ||
This is because we are executing a normal script function, inside the server side scripting environment. The function was compiled in normal environment so it knew the script commands, they were compiled into the script function and thus they now work.<br> | This is because we are executing a normal script function, inside the server side scripting environment. | ||
But we are still executing in server environment. Meaning if we execute things like [[compile]] the command itself will execute inside the server environment, and we wouldn't be able to compile a script containing [[params]] because the server environment would not know the script command.<br> | The function was compiled in normal environment so it knew the script commands, they were compiled into the script function and thus they now work.<br> | ||
But we are still executing in server environment. | |||
Meaning if we execute things like [[compile]] the command itself will execute inside the server environment, and we wouldn't be able to compile a script containing [[params]] because the server environment would not know the script command.<br> | |||
<br> | <br> | ||
This also applies in the opposite direction. Mission scripts cannot use server side scripting commands | This also applies in the opposite direction. Mission scripts cannot use server side scripting commands, but if the mission script [[compile]]s a new script, we could be using server commands like "kick" or "ban".<br> | ||
<br> | <br> | ||
Care must be taken with scripting like this, it is best to do as little logic as possible in script functions that are accessed this way. There might be many bugs here that likely will not get fixed. The basics work, so keep it basic. | Care must be taken with scripting like this, it is best to do as little logic as possible in script functions that are accessed this way. | ||
There might be many bugs here that likely will not get fixed. The basics work, so keep it basic. | |||
{{GameCategory|arma3|Multiplayer}} | {{GameCategory|arma3|Multiplayer}} |
Latest revision as of 13:29, 8 February 2024
The server has a separate Virtual Machine (VM) running administration scripts. This VM is completely independent on the game scripting environment and is designed to automate some administration tasks related to player administration and cheat detection. The basic way how scripts are executed is via event handlers reacting to some typical events, server admin can also execute individual commands using a chat command #exec.
Event handlers are defined in the server.cfg file.
Event Handlers
Event Name | Description | Handler Parameters |
---|---|---|
doubleIdDetected | 2nd user with the same ID detected | user id |
onUserConnected | user has connected | user id |
onUserDisconnected | user has disconnected | user id |
onHackedData | modification of signed pbo detected | user id, file name |
onDifferentData | signed pbo detected with a valid signature, but a different version than a server has (very strict test, use sparingly, no longer supported in Arma 2 OA 95232) | user id, file name |
onUnsignedData | unsigned data detected | user id, file name |
onUserKicked | if a user is kicked | user id, kick type ID, reason |
regularCheck | called time by time for each user, test index is growing, example: 0 checkFile _this
|
user id, test index |
onPlayerJoinAttempt 2.18 | Called repeatedly while a new player is trying to join the server. Must return either "ACCEPT", "DELAY" or "REFUSE":
|
|
sendChatMessage2.18 | Send a "System" chat message to the specified user |
|
Commands
Specific Commands
Command Syntax | Category | Description |
---|---|---|
users | User | List users, each user is described as an array in format [id, name] |
kick userId reason | User | Kick off given user id from the server, with an optional text |
ban userId reason | User | Add given user id into the ban list, with an optional text |
unkick userId | User | Remove a kick from the server's list |
unban userId | User | Remove a ban from the server's list |
clearKicks | User | Clear all existing kicks from the server's list |
clearBans | User | Clear all existing bans from the server's list |
numberOfFiles userId | File | number of addons files used for given player - can be used to determine suitable values for checkFile |
level checkFile [userId, fileIndex] | File | See Addon Signature for a file with given index on given user computer, level determines test depth (0 = default, 1 = deep. Note: deep can be very slow) |
level checkExe userId | File | Start the integrity check of game executable for given user. Level defines how exhaustive the test should be |
lock | Game | Lock/unlock the session |
- !
- +
- -
- :
- abs
- acos
- 2.18 allUsers
- append
- apply
- arrayIntersect
- asin
- assert
- atan
- atg
- breakOut
- call
- 2.18 callExtension
- case
- ceil
- comment
- compile
- compileFinal
- cos
- count
- deg
- deleteAt
- deleteRange
- diag_log
- disableSerialization
- do
- echo
- else
- exitWith
- exp
- false
- find
- findIf
- finite
- floor
- forEach
- for
- 2.18 getUserInfo
- 2.18 getVariable (namespace only)
- if
- in
- isFinal
- joinString
- ln
- log
- 2.18 missionNamespace
- nil
- not
- parseNumber
- parseSimpleArray
- pi
- private
- pushBack
- pushBackUnique
- rad
- random
- resize
- reverse
- round
- scriptName
- select
- selectMax
- selectMin
- selectRandom
- selectRandomWeighted
- set
- 2.18 setVariable (namespace only)
- sin
- sort
- splitString
- sqrt
- str
- switch
- tan
- tg
- then
- throw
- toArray
- toLower
- toLowerANSI
- toString
- toUpper
- toUpperANSI
- true
- try
- typeName
- 2.18 uiNamespace
- waitUntil
- while
- with
Mission Scripts Interaction
It is possible to interface with mission scripts that are not limited to the server scripts command set. There are two main approaches for this detailed below.
One major thing to keep in mind is that both these approaches depend on mission scripts being present.
Which is not the case when a server is freshly started and didn't load a mission yet. So you need to account for the handlers not being present.
CallExtension with Callbacks
callExtension being available offers the option to communicate through the Extension. As extension callbacks are executed in mission scripts.
When the extension receives a request, it can trigger a callback to send information to be handled inside a mission script.
Here is an example of a (pseudocode) extension utilizing this pattern for a "onPlayerJoinAttempt" event.
Extension code:
bool goodToGo = true;
bool goodToKick = false;
string kickReason = "";
RvExtensionArgs(string output, string method, string[] args)
{
if (method == "joinRequest")
{
if (goodToGo)
{
goodToGo = false; // only once
output.Append("ACCEPT");
return;
}
if (goodToKick)
{
goodToKick = false; // only once
output.Append("REFUSE");
output.Append(kickReason);
return;
}
// Tell our script handler about this
callback("ext", "joinRequest", args[0]);
output.Append("DELAY"); // wait till our script tells us what to do
}
if (method == "go")
goodToGo = true;
if (method == "kick")
{
goodToKick = true;
if (args.Length >= 1)
kickReason = args[0];
}
}
The server config side looks like this
onPlayerJoinAttempt = " if (missionNamespace getVariable ['ext_ready', false]) then { ('extension' callExtension ['joinRequest', _this]) select 0 } else { 'ACCEPT' } ";
Notice how we specifically check for a missionNamespace variable to indicate whether the actual callback handler is ready.
Then we have this mission script:
This does not actually implement any logic for handling player joins, but you can see how you could pass data through the callback and handle it accordingly.
Call missionNamespace Functions
Since getVariable has become available, and call has already been available previously, this means we can now get functions from missionNamespace and execute them from our server scripts environment. Example:
Server config:
onPlayerJoinAttempt = " call (missionNamespace getVariable ['PlayerJoinHandler', {'ACCEPT'}]) ";
Mission script:
Note a major thing here, we call a function from server-side script, that uses params, even though params is a command that is not available to server side scripts.
This is because we are executing a normal script function, inside the server side scripting environment.
The function was compiled in normal environment so it knew the script commands, they were compiled into the script function and thus they now work.
But we are still executing in server environment.
Meaning if we execute things like compile the command itself will execute inside the server environment, and we wouldn't be able to compile a script containing params because the server environment would not know the script command.
This also applies in the opposite direction. Mission scripts cannot use server side scripting commands, but if the mission script compiles a new script, we could be using server commands like "kick" or "ban".
Care must be taken with scripting like this, it is best to do as little logic as possible in script functions that are accessed this way.
There might be many bugs here that likely will not get fixed. The basics work, so keep it basic.