Hypoxic125/Sandbox – User

From Bohemia Interactive Community
Jump to navigation Jump to search
No edit summary
No edit summary
 
(16 intermediate revisions by the same user not shown)
Line 1: Line 1:
== Creating A Multiplayer Campaign ==
{{TOC|side}}


A multiplayer campaign is a series of interconnected missions that follow a sequential order, creating a storyline that groups of players can enjoy together.
[[Debugging Techniques]] are ways for a developers to debug (narrow down/determine the root source), or to learn and understand where and why errors or unexpected outcomes are occurring within their code.


Key things that a multiplayer campaign will include:
{{Feature|quote|Debugging is the process of finding and resolving defects or problems within a computer program that prevent correct operation of computer software or a system.|Wikipedia|https://en.wikipedia.org/w/index.php?title{{=}}Debugging}}
*Interconnected sequential missions
*Centralized location of common mission files to save on storage space
*Utilizes {{Link|createMPCampaignDisplay|MPCampaignDisplay}}
*Missions can be selected in the hosted server mission selection menu


== Creating the Missions ==


If new to creating missions, it is advised that you ignore concerns about disk space and make each mission independent of the campaign mod. When you get more comfortable with how the file structure works, and have more confidence in your code, you can then move on to centralizing common files. Debugging missions within the mod structure is much more annoying, especially if your code is more prone to bugs/errors.
== Code basics ==


Be sure you structure your missions with multiplayer {{Link|Multiplayer_Scripting#Locality|locality}} in mind from the start. It is much more time-consuming to convert a single-player mission into a multiplayer mission.
Various links about code and how to write it:
* [[SQF Syntax]]
* [[SQS Syntax]]
* [[Code Optimisation]]


== File Structure ==
=== IDE and Syntax Highlight ===


Now that you have your missions created, we can move on to the mod's file structure.
Syntax errors are a frequent occurrence when developing scripts. '''Syntax highlight''' will help you find typos in commands and in your scripts.


<span style="color: blue;">'''myModName'''</span>
An '''I'''ntegrated '''D'''evelopment '''E'''nvironment (short '''IDE''') is a development environment software that provides among other things '''code analysis''', '''code completion''' and '''syntax highlight'''  to help you write better, more error free code.
|--<span style="color: red;">addons</span>
    |--<span style="color: red;">myCampaignName</span>
      |--<span style="color: green;">data</span>
      |--<span style="color: green;">functions</span>
      |--<span style="color: red;">campaign</span>
      |  |--<span style="color: red;">missions</span>
      |  |  |--myMission01.worldName
      |  |  |--myMission02.worldName
      |  |  |--myMission03.worldName
      | |--<span style="color: red;">description.ext</span>
      |--<span style="color: red;">$PBOPREFIX$</span>
      |--<span style="color: red;">config.cpp</span>


<span style="color: red;">Red</span> highlights indicate required files/folders
See further [[#Debugging Tools|Debugging Tools]] page section for download links.


<span style="color: green;">Green</span> highlights indicate optional organization files/folders for shared campaign files
=== Errors ===


== Campaign Description.ext ==
An error in SQF '''will''' purely and simply '''halt''' the current script, leading to unpredictable behavior.
Error are not to happen as it may reduce performance as well as break a mission/feature.
So if you notice an issue, address it as your script '''will''' stop.


The campaign description file will dictate how the missions are structured within the campaign menus. Unlike a single-player campaign, we are going to be ignoring the mission flow aspect of the campaign description file so that the multiplayer campaign menu functions correctly.
=== Finding Errors ===


<syntaxhighlight lang="cpp">
==== Game Errors ====
/* --------------------------------------------------
    Defining Templates for Inheriting Later
-------------------------------------------------- */


// Setting up no endings, cutscenes, or rewards (stuff for single-player campaigns).
Debugging is usually as complicated as writing the actual code itself. You usually get hinted by the game, where some error happened, and what it was.<br>
There are instances where actual error reason might be something completely different than announced (eg. you get the error on a variable being [[nil]] somewhere, but the actual error is that you mistyped it where you set it initially).


class NoEndings                                    // For use in chapter class and class MissionDefault.
{{Feature|important|Make sure you enable ''Show Script Errors'' in the Launcher (for {{arma3}}) or set the corresponding [[Arma_3_Startup_Parameters|-showScriptErrors]] startup parameter.}}
{
 
     endDefault = "";
A script error will greet you with this box:
[[File:A3_ScriptErrorMsg.jpg|thumb|Script error message|center|500px]]
 
It tells you what went wrong, shows a snippet of the code that failed and what file and line it occurred in (unless you used a command combination like eg. <sqf inline>compile loadFile "filename";</sqf>).
Knowing this, you now can proceed to find the actual reason why that specific piece of code errored.
 
{{Feature|informative|This information can also be found in the [[arma.RPT|RPT log]].}}
 
To solve an issue, you must first to became aware that of it. Aside from your script not having the desired result/effect (if any), an error message can tell you about the problem.
* Be sure to use [[Arma 3: Startup Parameters#Developer Options|{{hl|-showScriptErrors}}&nbsp;startup&nbsp;parameter]] to display the error on-screen when it happens
* To learn about the location in your script use [[preprocessFileLineNumbers]] to [[preprocessFile]]
* Read [[Crash Files|RPT files]] for more information (make sure [[Arma 3: Startup Parameters#Performance|{{hl|-noLogs}} startup parameter]] is '''not''' active - otherwise the log will empty)
** Scripts running on a server use the '''Arma''X''Server.RPT''' file which has varying location depending upon the type of server being run.
** Scripts running on a client use the '''Arma''X''.RPT''' file.
 
==== Silent Failing ====
 
Not all script errors trigger a clear error message or the helpful black box indicating where the issue is. Some commands can silently fail, meaning they don’t produce any error message—they simply are ignored by the script.
 
'''Example of a Silent Fail:'''
 
<sqf>
private _group = createGroup [civ, false];
_group createUnit ["B_Soldier_F", [0,0,0], [], 0, "NONE"];
_group addWaypoint [[100,100,100], -1];
</sqf>
 
This script contains an error, but no error message appears to indicate it. The commands <sqf inline>createGroup</sqf>, <sqf inline>createUnit</sqf>, and <sqf inline>addWaypoint</sqf> all silently fail (they actually return <sqf inline>objNull</sqf> --more on that later). When you run the script, you’ll notice that no unit is created, and no waypoint is assigned. The issue lies in the creation of the group: an invalid side <sqf inline>civ</sqf> was used instead of the correct <sqf inline>civilian</sqf>.
 
To solve errors of this type, refer to the "Debugging Specific Seconds Of Code".
 
==== Scripter Assumption Error ====
{{Feature|quote|"Computers are good at following instructions, but not at reading your mind. — Donald Knuth"}}
 
Consider a scripter who wants to modify the damage of a unit's current ammo based on the unit's rank, increasing it by 10% for each rank value. Their script looks like this:
 
'''Example of Assumption Error:'''
<sqf>
private _calculateScaledDamage = {
     params ["_unit"];
 
    private _rank = rankId _unit;
    private _currentMagazine = currentMagazine _unit;
if (_currentMagazine == "") exitWith { 0 };
    private _magazineAmmo = getText (configFile >> "CfgMagazines" >> _currentMagazine >> "ammo");
    private _baseAmmoDamage = getNumber (configFile >> "CfgAmmo" >> _magazineAmmo >> "hit");
 
    private _damageMultiplier = 1 + (_rank * 10);
   
    // return
    if (_rank == 0) then { _baseAmmoDamage } else { _baseAmmoDamage * _damageMultiplier };
};
};
</sqf>
They integrate this function into their damage script, and while no errors occur, they notice their units deal far more damage than expected. The issue is that the script is working exactly as written—multiplying the unit's rank by <code>10</code>. However, this isn't what the scripter intended. They forgot to include the decimal, so the multiplier should have been <code>0.10</code> instead of <code>10</code>.
To solve errors of this type, refer to the "Debugging Specific Seconds Of Code".
==== RPT files ====
The [[arma.RPT|RPT]] is also used by the game's engine log other type of problems (low level engine issues, or content problems for example). Hence it will dump all kinds of debug information, warnings, errors as well as encountered script errors.
One can also write information to it by using commands like [[diag_log]].


class MissionDefault : NoEndings                    // For use in individual mission classes.
{{Feature|important|The RPT file might be disabled using the Arma 3 launcher or by setting the corresponding [[Arma_3_Startup_Parameters|-noLogs]] startup parameter ({{arma3}} or {{arma2oa}} option)}}
{
 
    lives = -1;                                     // Not important with/without "Tickets" respawn template.
{| class="wikitable"
    noAward = 1;
|+ RPT File location per game (See [[Crash Files]] for more information)
    cutscene = "";
!Game
};
!Location
!Files
|-
| {{GVI|arma3|1.00}} {{arma3}}
| <code style="display: block">'''%localappdata%'''\Arma 3</code>
| <code style="display: block">Arma3_x64_yyyy-mm-dd_hh-mm-ss.rpt</code>
|-
| {{GVI|arma2|1.00}} {{arma2}}
| <code style="display: block">'''%localappdata%'''\Arma 2</code>
| <code style="display: block">arma2.rpt</code>
|-
| {{GVI|arma1|1.00}} {{arma1}}
| <code style="display: block">'''%localappdata%'''\ArmA</code>
| <code style="display: block">arma.rpt</code>
|-
| {{GVI|ofp|1.00}} {{ofp}}
| <code style="display: block">'''''OFP root directory'''''</code>
| <code style="display: block">flashpoint.rpt<br>context.bin</code>
|}
 
=== Solving Errors ===
 
Once the script error is located: '''Make sure to check first the BiKi page corresponding to the command/function you are using to learn about potential misuse or other important information mentioned on the page!'''
 
The most simple thing you can do is to output expected values. This can be done using for example [[diag_log]] or [[systemChat]].
Output yourself a few [[Variables|variables]] that relate to your problem (for example: When the error occurs because you land in some [[if]] block, output the corresponding variables inside of the if).
You continue doing this until you hit the actual problem: When [[Variables|variable]] A is not set, go to where [[Variables|variable]] A gets set and check around there, repeat and continue.
 
The same can be done for non-critical errors like when a method is "just" computing the wrong values.
 
{{Feature|informative|
If you're having great difficulty solving a problem, the good way is to narrow down the issue to the simplest code block possible;
or take the mission part that doesn't work, paste it into a new test mission and go from here.
Doing so will save you dealing with other scripts' potential side effects!}}
 
==== Common errors ====
{| class="wikitable"
! Error message
! Cause
! Solution
|-
| {{hl|Error Undefined variable in expression: _varName}}
| Variable ''_varName'' has not been initialised properly in this context.
<sqf>hint _nonExistentVar;</sqf>
|
* in the case of a [[spawn]]ed code, previous local variables are '''not''' accessible and must be passed as arguments in order to access them.
* a variable may have been ''undefined'' (<sqf inline>_varName = nil;</sqf>). Unset it obviously ''after'' you are done using it.
|-
| {{hl|Error x elements provided, y is expected}}
| A wrong number of arguments in array was provided
<sqf>_unit setPos [0]; // setPos only takes an array of 2 to 3 elements</sqf>
|rowspan="2" align="center"|'''Read the wiki about said command'''.<br><br>Use [[typeName]] to output a variable type and compare it to the command's wiki page.
|-
| {{hl|Error type x, expected y}}
| An ill-typed argument was provided.
<sqf>hint 5; // must be String or Structured Text, Number provided</sqf>
|-
| rowspan="2" | {{hl|Error Zero divisor}}
| Pretty self-explanatory, somewhere in the code is a division by zero.
<sqf>
private _number = 0;
private _result = 100/_number;
</sqf>
|
* Make sure to check that your divisor is different from zero before dividing.
<sqf>private _result = if (_number != 0) then { 100 / _number } else { 100 };</sqf>
|-
| Also happens when using an invalid [[select]] index.
<sqf>["a", "b", "c"] select 20; // index can only be 0, 1 or 2</sqf>
|
* Ensure your [[select]] happens within the array boundaries.
* Use [[selectRandom]] to pick a random item in an array.
|-
| {{hl|Local variable in global space}}
| An attempt to [[private]] a public variable happened
<sqf>private "myVar"; // should be "_myVar"</sqf>
|
* Replace public variable usage with private variables.
|-
| rowspan="2" | {{hl|Error Generic error in expression}}
| A [[sleep]]/[[uiSleep]]/[[waitUntil]] command has been used in an [[Scheduler#Unscheduled Environment|unscheduled environment]].
<sqf>player addEventHandler ["Fired", { sleep 1; hint "bang"; }];</sqf>
|
* Do '''not''' use such suspending commands in unscheduled environment.
* For an '''FSM''' condition, a common workaround would be:
<sqf>private _t = time + 5; // code - having to wait for 5 seconds</sqf>
<sqf>time > _t // condition</sqf>
* [[spawn]] code: <sqf>player addEventHandler ["Fired", { 0 spawn { sleep 1; hint "bang"; }; }];</sqf>
|-
| Further code reading is required.
|
* Cut your code in smaller pieces and locate the concerned line(s).
|}
 
=== Working with Addons ===
 
If you are working on an addon, repacking a PBO can be time-intensive.
This operation can be replaced simply by creating a basic mission in the "Missions" or "MPmissions" (if your feature is multiplayer-specific) folder of your game installation, and running the mission locally.
The easiest way of accomplishing this is by the use of [[Event Scripts]] to run your code such as [[init.sqf]].
Once you have tested your code this way, you can then sequentially pack your PBO when major changes have been made, rather than for each debug session of a script or piece of code.
 
=== Debugging Specific Sections of Code ===
 
Although primitive, the combined use of [[diag_log]], [[systemChat]]/[[hint]] and [[format]] can help to debug the content of function arguments.
In the case that specific pieces of code do not run, or if specific ''if'' conditions don't appear to fire, debugging the variable content with [[diag_log]] can be useful.
As with all debugging, as long as the developer is methodical and logical in checking each section of code that runs, finding bugs and resolving them can be straightforward.
 
 
== Debugging Tools ==
 
=== Code Editing ===


/* --------------------------------------------------
See {{Link|Category:Community Tools#Code Editing}}.
    Actual Campaign Class
-------------------------------------------------- */


class Campaign                                      // Contained inside CfgMissions - Holds other campaign classes such as "Apex", "Bootcamp", "EastWind".
=== Debug Console/System ===
{
    firstBattle = "Missions";                      // This will point to class Missions below which contains no information on purpose
                                                    // so that the mp campaign menu will load all missions.
                                                    // If info is given, no MPCampaignDisplay will be created.
    name = "$STR_CAMPAIGN_TITLE";
    briefingName = "$STR_CAMPAIGN_TITLE";
    author = "Hypoxic";
    overviewPicture = "myOverViewPicture.jpg";
    overviewText = "$STR_CAMPAIGN_DESCRIPTION";
    disableMP = 0;


    class myCampaign : NoEndings                    // Chapter class - Typically only use one chapter when dealing with MP Campaigns.
See {{Link|Category:Community Tools#Debug Console/System}}.
    {
        firstMission = "myMission01";
        name = "$STR_CAMPAIGN_TITLE";
        cutscene = "";
        end1 = "";


        class myMission01 : MissionDefault          // Mission class - Inherits default settings from class MissionDefault above.
=== SQF and debug Tools ===
        {
            end1 = "myMission02";                  // Default Ending - "end1" call BIS_fnc_endMission will use this mission as next mission.
            myCustomEnd = "myMission01";            // Custom Ending - Defined in mission's class CfgDebriefing - Can be whatever you want.
            lost = "myMission01";                  // You can send the lobby back to current mission on loss using this. Can also be custom.
            template = "myMission01.worldName";
        };


        class myMission02 : MissionDefault
See {{Link|Category:Community Tools#SQF and debug Tools}}.
        {
            end1 = "myMission03";
            lost = "myMission02";
            template = "myMission02.worldName";
        };


        class myMission03 : MissionDefault
        {
            end1 = "";
            lost = "myMission03";
            template = "myMission03.worldName";
        };
    };


    class Missions                                  // This is essentially a class with empty values. Used to invoke MPCampaignDisplay - See firstBattle above.
[[Category:Scripting Topics]]
    {
        name = "$STR_CAMPAIGN_TITLE";
        cutscene = "";
        firstMission = "";
        end1 = "";
        end2 = "";
        end3 = "";
        end4 = "";
        end5 = "";
        end6 = "";
        lost = "";
    };
};
</syntaxhighlight>

Latest revision as of 04:19, 13 January 2025

Debugging Techniques are ways for a developers to debug (narrow down/determine the root source), or to learn and understand where and why errors or unexpected outcomes are occurring within their code.

«
« Debugging is the process of finding and resolving defects or problems within a computer program that prevent correct operation of computer software or a system. » – Wikipedia (source)


Code basics

Various links about code and how to write it:

IDE and Syntax Highlight

Syntax errors are a frequent occurrence when developing scripts. Syntax highlight will help you find typos in commands and in your scripts.

An Integrated Development Environment (short IDE) is a development environment software that provides among other things code analysis, code completion and syntax highlight to help you write better, more error free code.

See further Debugging Tools page section for download links.

Errors

An error in SQF will purely and simply halt the current script, leading to unpredictable behavior. Error are not to happen as it may reduce performance as well as break a mission/feature. So if you notice an issue, address it as your script will stop.

Finding Errors

Game Errors

Debugging is usually as complicated as writing the actual code itself. You usually get hinted by the game, where some error happened, and what it was.
There are instances where actual error reason might be something completely different than announced (eg. you get the error on a variable being nil somewhere, but the actual error is that you mistyped it where you set it initially).

Make sure you enable Show Script Errors in the Launcher (for Arma 3) or set the corresponding -showScriptErrors startup parameter.

A script error will greet you with this box:

Script error message

It tells you what went wrong, shows a snippet of the code that failed and what file and line it occurred in (unless you used a command combination like eg. compile loadFile "filename";). Knowing this, you now can proceed to find the actual reason why that specific piece of code errored.

This information can also be found in the RPT log.

To solve an issue, you must first to became aware that of it. Aside from your script not having the desired result/effect (if any), an error message can tell you about the problem.

Silent Failing

Not all script errors trigger a clear error message or the helpful black box indicating where the issue is. Some commands can silently fail, meaning they don’t produce any error message—they simply are ignored by the script.

Example of a Silent Fail:

private _group = createGroup [civ, false]; _group createUnit ["B_Soldier_F", [0,0,0], [], 0, "NONE"]; _group addWaypoint [[100,100,100], -1];

This script contains an error, but no error message appears to indicate it. The commands createGroup, createUnit, and addWaypoint all silently fail (they actually return objNull --more on that later). When you run the script, you’ll notice that no unit is created, and no waypoint is assigned. The issue lies in the creation of the group: an invalid side civ was used instead of the correct civilian.

To solve errors of this type, refer to the "Debugging Specific Seconds Of Code".

Scripter Assumption Error

«
« "Computers are good at following instructions, but not at reading your mind. — Donald Knuth" »

Consider a scripter who wants to modify the damage of a unit's current ammo based on the unit's rank, increasing it by 10% for each rank value. Their script looks like this:

Example of Assumption Error:

private _calculateScaledDamage = { params ["_unit"]; private _rank = rankId _unit; private _currentMagazine = currentMagazine _unit; if (_currentMagazine == "") exitWith { 0 }; private _magazineAmmo = getText (configFile >> "CfgMagazines" >> _currentMagazine >> "ammo"); private _baseAmmoDamage = getNumber (configFile >> "CfgAmmo" >> _magazineAmmo >> "hit"); private _damageMultiplier = 1 + (_rank * 10); // return if (_rank == 0) then { _baseAmmoDamage } else { _baseAmmoDamage * _damageMultiplier }; };

They integrate this function into their damage script, and while no errors occur, they notice their units deal far more damage than expected. The issue is that the script is working exactly as written—multiplying the unit's rank by 10. However, this isn't what the scripter intended. They forgot to include the decimal, so the multiplier should have been 0.10 instead of 10.

To solve errors of this type, refer to the "Debugging Specific Seconds Of Code".

RPT files

The RPT is also used by the game's engine log other type of problems (low level engine issues, or content problems for example). Hence it will dump all kinds of debug information, warnings, errors as well as encountered script errors. One can also write information to it by using commands like diag_log.

The RPT file might be disabled using the Arma 3 launcher or by setting the corresponding -noLogs startup parameter (Arma 3 or Arma 2: Operation Arrowhead option)
RPT File location per game (See Crash Files for more information)
Game Location Files
Arma 3 logo black.png1.00 Arma 3 %localappdata%\Arma 3 Arma3_x64_yyyy-mm-dd_hh-mm-ss.rpt
Logo A2.png1.00 Arma 2 %localappdata%\Arma 2 arma2.rpt
Logo A1 black.png1.00 Armed Assault %localappdata%\ArmA arma.rpt
Logo A0.png1.00 Operation Flashpoint OFP root directory flashpoint.rpt
context.bin

Solving Errors

Once the script error is located: Make sure to check first the BiKi page corresponding to the command/function you are using to learn about potential misuse or other important information mentioned on the page!

The most simple thing you can do is to output expected values. This can be done using for example diag_log or systemChat. Output yourself a few variables that relate to your problem (for example: When the error occurs because you land in some if block, output the corresponding variables inside of the if). You continue doing this until you hit the actual problem: When variable A is not set, go to where variable A gets set and check around there, repeat and continue.

The same can be done for non-critical errors like when a method is "just" computing the wrong values.

If you're having great difficulty solving a problem, the good way is to narrow down the issue to the simplest code block possible;

or take the mission part that doesn't work, paste it into a new test mission and go from here.

Doing so will save you dealing with other scripts' potential side effects!

Common errors

Error message Cause Solution
Error Undefined variable in expression: _varName Variable _varName has not been initialised properly in this context.

hint _nonExistentVar;

  • in the case of a spawned code, previous local variables are not accessible and must be passed as arguments in order to access them.
  • a variable may have been undefined (_varName = nil;). Unset it obviously after you are done using it.
Error x elements provided, y is expected A wrong number of arguments in array was provided

_unit setPos [0]; // setPos only takes an array of 2 to 3 elements

Read the wiki about said command.

Use typeName to output a variable type and compare it to the command's wiki page.
Error type x, expected y An ill-typed argument was provided.

hint 5; // must be String or Structured Text, Number provided

Error Zero divisor Pretty self-explanatory, somewhere in the code is a division by zero.

private _number = 0; private _result = 100/_number;

  • Make sure to check that your divisor is different from zero before dividing.

private _result = if (_number != 0) then { 100 / _number } else { 100 };

Also happens when using an invalid select index.

["a", "b", "c"] select 20; // index can only be 0, 1 or 2

  • Ensure your select happens within the array boundaries.
  • Use selectRandom to pick a random item in an array.
Local variable in global space An attempt to private a public variable happened

private "myVar"; // should be "_myVar"

  • Replace public variable usage with private variables.
Error Generic error in expression A sleep/uiSleep/waitUntil command has been used in an unscheduled environment.

player addEventHandler ["Fired", { sleep 1; hint "bang"; }];

  • Do not use such suspending commands in unscheduled environment.
  • For an FSM condition, a common workaround would be:

private _t = time + 5; // code - having to wait for 5 seconds
time > _t // condition

Further code reading is required.
  • Cut your code in smaller pieces and locate the concerned line(s).

Working with Addons

If you are working on an addon, repacking a PBO can be time-intensive. This operation can be replaced simply by creating a basic mission in the "Missions" or "MPmissions" (if your feature is multiplayer-specific) folder of your game installation, and running the mission locally. The easiest way of accomplishing this is by the use of Event Scripts to run your code such as init.sqf. Once you have tested your code this way, you can then sequentially pack your PBO when major changes have been made, rather than for each debug session of a script or piece of code.

Debugging Specific Sections of Code

Although primitive, the combined use of diag_log, systemChat/hint and format can help to debug the content of function arguments. In the case that specific pieces of code do not run, or if specific if conditions don't appear to fire, debugging the variable content with diag_log can be useful. As with all debugging, as long as the developer is methodical and logical in checking each section of code that runs, finding bugs and resolving them can be straightforward.


Debugging Tools

Code Editing

See Community Tools Category.

Debug Console/System

See Community Tools Category.

SQF and debug Tools

See Community Tools Category.