Jump to content

Recommended Posts

It's time for the final Bearing development post.


This one is going to be a collection of smaller examples and prototypes that don't need an entire post about them.

 

 

Underwater beacons

 

There are a few repair tasks in Bearing that involve diving underwater and 'tagging' some wave power generators.
Arma 3's water is very murky, which is great, but also makes it a little more difficult that I'd like to find the generators.

 

I hooked up an effect to the Fast Travel Handset which activates a flashing light and particle effect. It's cool seeing the lights in the gloom.

 

Click the image below for a video example

W7lqNBy.png

 


Sun spots

 

An unused effect - staring at the sun too long will harm your eyes for a little bit.

 

This is an interface that creates lots of stacked RscPicture controls referencing "\a3\ui_f\data\igui\cfg\radar\radarbackground_ca.paa" and adjusts their colour over 18 seconds.

 

The position of each sun spot is found by using llw_fnc_getSunAngle (found here) to get the sun angle and azimuth.
A vector built from the sun values is then extended 2000.0 from eyePos and the end position is translated to UI coords.

 

Click the image below for a video example

Cp3zBzC.png

 


Isolines

 

This test came from trying to implement some sort of manual isolines (map contour lines).

 

Click the image below for a video example

hGyklex.png

 

 

This was done by generating a grid of RscText controls and then setting the background colour of them based on their heights against some 'isoline boundaries'.
When the average height (from the 4 corners) of each grid cell overlapped one of the boundary heights, it was coloured black - otherwise white.

 

As you can tell, it's very laggy.

 

But, it did lead to another much cooler prototype.

 

Click the image below for a video example

7Me4DkU.png

 

 

Here the lines are pinched by 'nulls' which act a bit like a black hole.
A little interpolation between the boundary line distances was added, along with alternating colours, so they appear to be moving outwards.

 

Imagine a handset that detects STALKER-like anomalies, and you'll get where I was headed with this.

 

This was effect done by using a blank Map control along with drawTriangle, which is how I pull off most of my favourite things about Bearing.
Such as the next example!

 


drawTriangle

 

If you've played Bearing, you've seen this:

 

Click the image below for a video example

0H8zCmB.png

 

 

drawTriangle is by far my favourite command in SQF.


Here's a config for a blank Map control, which took a little while to figure out (I used to draw a fill rectangle over the standard map before figuring this out):

class MapControlBlank : RscMapControl
{
    showMarkers = 0;
    maxSatelliteAlpha = 0.0;
    colorGrid[] = {0,0,0,0};
    colorGridMap[] = {0,0,0,0};
    //NOTE: 1.90 bug needs this line added
    widthRailWay = 0;
    
    colorBackground[] = { 0.00, 0.00, 0.00, 1.00 };
    colorText[] = { 0.00, 0.00, 0.00, 0.00 };
    colorSea[] = { 0.00, 0.00, 0.00, 0.00 };
    colorForest[] = { 0.00, 0.00, 0.00, 0.00 };
    colorRocks[] = { 0.00, 0.00, 0.00, 0.00 };
    colorCountlines[] = { 0.00, 0.00, 0.00, 0.00 };
    colorMainCountlines[] = { 0.00, 0.00, 0.00, 0.00 };
    colorCountlinesWater[] = { 0.00, 0.00, 0.00, 0.00 };
    colorMainCountlinesWater[] = { 0.00, 0.00, 0.00, 0.00 };
    colorForestBorder[] = { 0.00, 0.00, 0.00, 0.00 };
    colorRocksBorder[] = { 0.00, 0.00, 0.00, 0.00 };
    colorPowerLines[] = { 0.00, 0.00, 0.00, 0.00 };
    colorNames[] = { 0.00, 0.00, 0.00, 0.00 };
    colorInactive[] = { 0.00, 0.00, 0.00, 0.00 };
    colorLevels[] = { 0.00, 0.00, 0.00, 0.00 };
    colorRailWay[] = { 0.00, 0.00, 0.00, 0.00 };
    colorOutside[] = { 0.00, 0.00, 0.00, 0.00 };
    colorTracks[] = { 0.00, 0.00, 0.00, 0.00 };
    colorRoads[] = { 0.00, 0.00, 0.00, 0.00 };
    colorMainRoads[] = { 0.00, 0.00, 0.00, 0.00 };
    colorTracksFill[] = { 0.00, 0.00, 0.00, 0.00 };
    colorRoadsFill[] = { 0.00, 0.00, 0.00, 0.00 };
    colorMainRoadsFill[] = { 0.00, 0.00, 0.00, 0.00 };
    colorTrails[] = { 0.00, 0.00, 0.00, 0.00 };
    colorTrailsFill[] = { 0.00, 0.00, 0.00, 0.00 };
    
    //Watertower = {};

    sizeEx = 0;
    sizeExLabel = 0;
    sizeExGrid = 0;
    sizeExUnits = 0;
    sizeExNames = 0;
    sizeExInfo = 0;
    sizeExLevel = 0;
    showCountourInterval = 0;
    
    drawObjects = false;

    class Legend
    {
        x=0;y=0;w=0;h=0;
        font="RobotoCondensed";
        sizeEx=1;
        colorBackground[]={0,0,0,0};
        color[]={0,0,0,0};
    };
};

 

Keep in mind that you'll have to convert everything to map coords, respecting the current zoom level, so it's not very straight-forward.
You need to get the top-left and bottom-right UI positions in world space and use that:

//Get some worldspace positions from the map to size everything to
private _layoutMap = ctrlPosition _ctrlMap;
private _mapTL = _ctrlMap ctrlMapScreenToWorld [_layoutMap # 0, _layoutMap # 1];
private _mapBR = _ctrlMap ctrlMapScreenToWorld [(_layoutMap # 0) + (_layoutMap # 2), (_layoutMap # 1) + (_layoutMap # 3)];

 

Having a drawTriangleUI, which takes UI coord values and doesn't require a Map control, would be amazing for fancy UI animations.

 


Binocular shine

 

This was added to that other players could be more easily spotted from a distance, and introduce a little position awareness. Bearing doesn't have any PVP-like elements, but if it did (and some were planned), I wanted something to help make things more interesting.
The prototype adds a sun glint when other players were using binoculars, which also correctly used the sun's position (again using llw_fnc_getSunAngle).
I specifically didn't want a sniper-scope-like flash that happens no matter where the sun is.

 

I used the NPC head direction (via eyeDirection) in the test example below, so the glint would respect the minor angle changes that come from the NPC idle animation.

This subtle variation in angle is what makes the glint flash that bit more authentic.

 

Click the image below for a video example

cGmIvPA.png

 

 

Unfortunately, it doesn't work perfectly because Arma 3 applies a vertical offset to units, which changes depending on what surface is being stood on. This allows for units to 'sink' into grass when prone, etc.
That vertical offset information isn't available to scripts, and the offset doesn't apply to commands like eyePos so there's nothing that can be done to correctly sync the position of the unit model and the flash.

 

Click the image below for a video example

GWGAwA3.png

 

 

At a distance it's fine because the glint is quite large, but using binoculars quickly reveals the problem.
This is why the feature didn't make it into Bearing.

 


Filament walker

 

This was the first test I made using drawLine3D, before I started on any of the Pylon designs and visuals. I called them 'filaments' at the time, due to the thin line effect.

 

Click the image below for a video example

TQXZIKX.png

 

 

Made over a day or two, figuring out how to do IK limbs in Unity and then porting the code over to SQF, it walks along the terrain and properly aligns to the height of the ground it's standing on.
I'm still a big fan of how the lines break up and disintegrate at the end.

 

The plan was to have them appear all across Altis, once every 9 hours, during the brief period when time 'resets' in Bearing (which might have become a time-loop backstory later on, but I left it unexplained).

 


Rock spin

 

While developing the effects system, I thought I'd try out creating some objects to move around with them.

 

Click the image below for a video example

nnDuoeJ.png

 

 

The rocks have collision and you can briefly stand on top of them, if you try hard enough.

 

 

Signal strength

 

This map feature already exists in Bearing, but is locked behind the last available NPC in the alpha content.
The subsequent tasks, if more content was added, involved the NPC repairing a signal monitor handset for you, which revealed the impact that Pylons and markers were having on radio signal absorption.

 

Click the image below for a video example

nk2f6iO.png

 

 

It helps to give a way to track Job progression across the player's lifetime, with the map becoming clearer as more Jobs are completed and showing the player that they are having an impact on the world.

 

The two examples below show earlier tests.
The first is made from a bunch of different shapes overlaid on top of each other - you can also see how laggy it gets when regenerating every so often. The second is built from banded areas of signal, like the example above.
Both also animate slightly, something which is disabled in the final version because it was distracting (although cool).

 

Click the images below for video examples

0Xzp5n8.png     ybPDGmk.png

 


Chat scripts

 

Bearing's chat system was inspired by Inkle's Ink scripting language but eventually became its own awkward and sprawling thing.

 

Each conversation begins with a single starting chat script, which then forks out to others based on the available responses.

These responses are often made available by testing player data, such as tasks completed, and can trigger code both on the server and the client to support their content.
Most of the unlocks a player receives happens through these chat scripts.

 

The example below shows one such chat script, which is owned by the first NPC that the player talks to.
When this chat script is selected, the top script block runs on the server, and the next runs on the client.
The block of text below that, which is full of various tags that the preprocessor will convert to proper NPC names and bold/colour tags, is shown on the client.
The final array lists all of the available responses that the player can choose.

 

RuXivwD.png

 

 

The next example shows how chat responses can be hidden based on the result of any script that the developer (me) cares to write.

 

CI7vPzq.png

 

 

There is extra functionality to the system, added in the first development pass, that I ultimately didn't find a use for - such as code that runs when the player chooses or doesn't choose a response.

 

All chat scripts were written by hand - had Bearing's development had continued I would have written a visual tool to help put these scripts together more easily.
This tool would have also included an in-tool preview system so all tags and text can be inspected without having to launch the client and check in-game. That sort of thing is a significant time-saver.

 


Task scripts

 

Tasks are the 'quests' of Bearing, and these scripts are what do the heavy lifting behind the scenes.
They attach themselves to 'event listeners' which pass along event information as it happens. If the event matches what it's looking for, then the task script will process the event.

 

The available task event listeners in Bearing, and the data they transfer, are:

#define TESTVAR_LISTENERTYPE_NPCFIRSTMET        0      //[_player, _npcID]
#define TESTVAR_LISTENERTYPE_NPCCHAT            1      //[_player, _npcID, _chatID]
#define TESTVAR_LISTENERTYPE_JOBCOMPLETE        2      //[_player, _jobID, _jobTotalScore]
#define TESTVAR_LISTENERTYPE_JOBFIRSTCOMPLETE   3      //[_player, _jobID, _jobTotalScore]
#define TESTVAR_LISTENERTYPE_TRIGGERENTER       4      //[_trigger, _triggerList]
#define TESTVAR_LISTENERTYPE_TRIGGEREXIT        5      //[_trigger, _triggerList]
#define TESTVAR_LISTENERTYPE_TRIANGULATEADD     6      //[_player, _triangulateMark]
#define TESTVAR_LISTENERTYPE_FTPUNLOCKED        7      //[_player, _ftpID]
#define TESTVAR_LISTENERTYPE_POWERCHANGED       8      //[_player]
#define TESTVAR_LISTENERTYPE_FTPTRAVELLED       9      //[player, _distance, _wasPickup]
#define TESTVAR_LISTENERTYPE_FTPHANDSETUSED    10      //[player]

 

There are various types of task:

#define TESTVAR_TASKTYPE_NPCSMET                0      //Has met specific NPCs, e.g. NPCREF_FACTORIES_WEST, attaches to listener TESTVAR_LISTENERTYPE_NPCFIRSTMET
#define TESTVAR_TASKTYPE_NPCCHAT                1      //Has started a specific chat script with a specific NPC, e.g. NPCREF_BASECAMP_DOCK,CHATREF_basecamp_dock_needmap2, attaches to listener TESTVAR_LISTENERTYPE_NPCCHAT
#define TESTVAR_TASKTYPE_JOBSCOMPLETE           2      //Has completed a specific set of Jobs, e.g. JOBREF_BASECAMP_DOCK_J1,JOBREF_BASECAMP_DOCK_J3, attaches to listeners TESTVAR_LISTENERTYPE_JOBFIRSTCOMPLETE
#define TESTVAR_TASKTYPE_INTERACT               3      //Has interacted with a world object, e.g. UniformCrate_1, doesn't attach to a listener
#define TESTVAR_TASKTYPE_TRIGGERENTER           4      //Has entered a trigger volume, e.g. 4228.113,10794.56,192.551,5,5,0,false,2.0 (manually defined bounds), attaches to listener TESTVAR_LISTENERTYPE_TRIGGERENTER
#define TESTVAR_TASKTYPE_TRIGGEREXIT            5      //Has exited a trigger volume, attaches to listener TESTVAR_LISTENERTYPE_TRIGGEREXIT
#define TESTVAR_TASKTYPE_TRIANGULATEADD         6      //Has created a new Survey mark, params are tested within the script, attaches to listener TESTVAR_LISTENERTYPE_TRIANGULATEADD
#define TESTVAR_TASKTYPE_FTPSUNLOCKED           7      //Has unlocked specific Fast Travel locations, e.g. FTPREF_BASETRAIL, attaches to listener TESTVAR_LISTENERTYPE_FTPUNLOCKED
#define TESTVAR_TASKTYPE_JOBSCORE               8      //Has reached a specific Job score, e.g. 100, attaches to listeners TESTVAR_LISTENERTYPE_JOBCOMPLETE
#define TESTVAR_TASKTYPE_NPCSCORE               9      //Has met a specific *number* of NPCs, e.g. 20, attaches to listener TESTVAR_LISTENERTYPE_NPCFIRSTMET
#define TESTVAR_TASKTYPE_MANUAL                10      //Doesn't hook into any listener, the data is passed raw to the task script for it to parse, e.g. TASKREF_POWER_HELIOSTATREPAIR_ZAROS,SolarGlow_Zaros_Near,SolarGlow_Zaros_Far
#define TESTVAR_TASKTYPE_POWERED               11      //Has powered a specific power network node, e.g. PWB1833, attaches to listener TESTVAR_LISTENERTYPE_POWERCHANGED

 

Custom scripts exists for:

  • TESTVAR_LISTENERTYPE_POWERCHANGED
    • to update the map's power data visuals when something changes
  • TESTVAR_LISTENERTYPE_FTPTRAVELLED
    • to check when the total distance traveled is far enough to unlock a specific outfit piece
  • TESTVAR_LISTENERTYPE_FTPHANDSETUSED
    • to tell various things about a signal being sent, setting off FTP alarms or alerting nearby Pylons or Guards

 

Like most games, a lot of the Bearing's data exists in spreadsheets.
Here are the task definitions for the entirety of Bearing's tutorial:

 

zcFtcpE.png

 

 

Each task has a list of scripts it can call when certain things happen to it, such as:

#define TESTVAR_TASKSCRIPTTYPE_INITSERVER          0       //Run on every task when the server boots, does many things such as creating objects or linking to existing ones
#define TESTVAR_TASKSCRIPTTYPE_INITINACTIVE        1       //Run on the client for each task that hasn't been started yet, can set up things like smoke effects on broken objects
#define TESTVAR_TASKSCRIPTTYPE_INITACTIVE          2       //Run on currently active but incomplete task
#define TESTVAR_TASKSCRIPTTYPE_INITCOMPLETE        3       //Run on all completed tasks, used to clear up various things (like effects, hide objects)
#define TESTVAR_TASKSCRIPTTYPE_STEP                4       //Many tasks require multiple things to happen, this script is called each time one of them happens - a listener event will typically call this script
                                                           //(also used to send update notifications and change progress bars, using TESTVAR_TASKSCRIPTTYPE_PROGRESS)
#define TESTVAR_TASKSCRIPTTYPE_PROGRESS            5       //Specifically used to gather and arrange task progress data, can be called at any time
#define TESTVAR_TASKSCRIPTTYPE_COMPLETE            6       //Run when the player completes a task
#define TESTVAR_TASKSCRIPTTYPE_COMPLETECHECK       7       //If a task changes in the future, and the player has already completed it, this script exists to validate that the player has still completed it

 

Here is an example of the task script handling for commonJobScore, which listens for job score changes.

 

The scripts are broken up by headers, to avoid having to wrap individual scripts in {} and declare empty scripts.
The headers are split into proper scripts and put into arrays during the project build, and the script names validated against the task spreadsheet data.

//////////////////////////////////////////////////////////////////////////////
//SCRIPTREF_commonJobScore_initServer
params ["_task", "_taskData"];
_taskData params ["_jobScore"];
//Register with the listener
(GAME_LISTENERS # TESTVAR_LISTENERTYPE_JOBCOMPLETE) pushBack [
    //Does this step script need to be called? Calls the next script if true
    {
        (_this # 0) params ["_player", "_inJobID", "_inJobLastTotalScore", "_inJobTotalScore"];
        (_this # 1) params ["_taskID", "_reqJobScore"];
        (
            //Pass if the score has increased so we get progress updates
            _inJobTotalScore > _inJobLastTotalScore &&
            {_player in (GAME_TASKSACTIVEPLAYERS # _taskID)}
        );
    },
    //The main listener script call
    {
        (_this # 0) params ["_player", "_inJobID", "_inJobLastTotalScore", "_inJobTotalScore"];
        (_this # 1) params ["_taskID", "_reqJobScore"];
    { [_player, _taskID, [_inJobLastTotalScore, _inJobTotalScore, _reqJobScore]] call (GAME_TASKSCRIPTS # _x); } forEach (((GAME_TASKS # _taskID) # TESTVAR_TASKDEF_SCRIPTS) # TESTVAR_TASKSCRIPTTYPE_STEP);
    },
    //Some data to pass around
    [_task # TESTVAR_TASKDEF_TASKID, _jobScore]
];
//Task-specific data is not added to the task by default, do it now
_task pushBack _jobScore;


//////////////////////////////////////////////////////////////////////////////
//SCRIPTREF_commonJobScore_progress
params ["_player", "_taskID"];
private _targetJobScore = (GAME_TASKS # _taskID) # TESTVAR_TASKDEF_TASKTYPEDATA;
private _playerUID = _player getVariable "TESTVAR_PLAYERVAR_UID";
private _playerAccountID = _player getVariable "TESTVAR_PLAYERVAR_ACCOUNTID";
//Get all the current NPC states
private _result = ["PlayerGetJobScore", [_playerAccountID]] call GiragastBearing_fnc_CallExtension;
if (!(_result # 0)) exitWith {
    [_player, format ["Call to PlayerGetJobScore, as part of task %1 'progress' script, failed for player account ID ""%2"", kicking player (uid %3)\n\tReason:\n\t%4", _taskID, _playerAccountID, _playerUID, _result # 1]] call GiragastBearing_fnc_ErrorKick;
};
private _jobScore = _result # 1;
//No map hints
private _mapHints = [];
//Return the progress text
[format ["%1 of %2 Completed", _jobScore, _targetJobScore], _mapHints];


//////////////////////////////////////////////////////////////////////////////
//SCRIPTREF_commonJobScore_step
params ["_player", "_taskID", "_stepParams"];
_stepParams params ["_jobLastTotalScore", "_jobTotalScore", "_jobTargetScore"];
//If the score is met then deal with that
if (_jobTotalScore >= _jobTargetScore) then {
    { [_player, _taskID] call (GAME_TASKSCRIPTS # _x); } forEach (((GAME_TASKS # _taskID) # TESTVAR_TASKDEF_SCRIPTS) # TESTVAR_TASKSCRIPTTYPE_COMPLETE);
} else {
    //Not finished yet, notify about the task progress
    [_player, _taskID, _jobLastTotalScore, _jobTotalScore, _jobTargetScore] call GiragastBearing_fnc_NotifyTaskProgress;
};

//////////////////////////////////////////////////////////////////////////////
//SCRIPTREF_commonJobScore_completeCheck
params ["_player", "_taskID"];
private _targetJobScore = (GAME_TASKS # _taskID) # TESTVAR_TASKDEF_TASKTYPEDATA;
private _playerUID = _player getVariable "TESTVAR_PLAYERVAR_UID";
private _playerAccountID = _player getVariable "TESTVAR_PLAYERVAR_ACCOUNTID";
//Get all the current NPC states
private _result = ["PlayerGetJobScore", [_playerAccountID]] call GiragastBearing_fnc_CallExtension;
if (!(_result # 0)) exitWith {
    [_player, format ["Call to PlayerGetJobScore, as part of task %1 'completeCheck' script, failed for player account ID ""%2"", kicking player (uid %3)\n\tReason:\n\t%4", _taskID, _playerAccountID, _playerUID, _result # 1]] call GiragastBearing_fnc_ErrorKick;
};
private _jobScore = _result # 1;
//Return whether the score has been met
_jobScore >= _targetJobScore;

 

It looks complex, and it is, but it's powerful and flexible enough to do almost every dynamic thing in the mod.
That might just be the programmer in me talking, though. Who knows how a content scripter might react to it.

 

 

Last words

I had fun putting Bearing together over the past 4 years, although it would have been cool to get some more feedback so I could iterate on the designs some more.

 

To date, two or three people have reached the end of the alpha content, and about 20 more played for over an hour and made some good progress.

The vast majority, which is expected, play for a few minutes, then leave. (Strangely, since I moved house and have a worse internet connection, it's almost always exactly 5 minutes - maybe there's a bug I don't know about)

 

There's a lot of fun stuff you can do with SQF, and hopefully Arma 4 will have same flexibility in whatever form the new language takes.

 

Cheers.

  • Like 2
  • Thanks 1

Share this post


Link to post
Share on other sites

Cool thread with a lot of interesting and creative solutions!
Yeah it's hard to reach out to interested audience withing arma. Forum is mostly modders, not players.

  • Like 1

Share this post


Link to post
Share on other sites
21 hours ago, im_an_engineer said:

To date, two or three people have reached the end of the alpha content, and about 20 more played for over an hour and made some good progress.

The vast majority, which is expected, play for a few minutes, then leave. (Strangely, since I moved house and have a worse internet connection, it's almost always exactly 5 minutes - maybe there's a bug I don't know about)

 

For me that's the case - two times I tried to play Bearing (I think in May and June/July), I got CTD during the first scan in tutorial, with an error message related to memory problems IIRC. Time permitting, I'll try again and write down the error - for whatever reason, I couldn't get a screenshot of it (PrntScrn doesn't work).

  • Like 1

Share this post


Link to post
Share on other sites
31 minutes ago, krzychuzokecia said:

PrntScrn doesn't work

Try Win + Print Screen, that should automatically make a high quality PNG in "Pictures\Screenshots".

Share this post


Link to post
Share on other sites

Dude. Nothing much more to say other than that is some masterfully, insane work you've done there. Chapeau!

  • Like 1

Share this post


Link to post
Share on other sites
On 9/19/2021 at 8:58 AM, Malkain said:

Cool thread with a lot of interesting and creative solutions!
Yeah it's hard to reach out to interested audience withing arma. Forum is mostly modders, not players.

 

I had a peek at reddit, but it was pretty clear at no self-promotion, so I dropped a few screenshots in Discord and posted here. Seemed like the best option at the time!

 

On 9/19/2021 at 6:16 PM, krzychuzokecia said:

 

For me that's the case - two times I tried to play Bearing (I think in May and June/July), I got CTD during the first scan in tutorial, with an error message related to memory problems IIRC. Time permitting, I'll try again and write down the error - for whatever reason, I couldn't get a screenshot of it (PrntScrn doesn't work).

 

How much RAM have you got? Do you know if you're playing the 64bit client?

Share this post


Link to post
Share on other sites
On 9/22/2021 at 2:37 AM, im_an_engineer said:

How much RAM have you got? Do you know if you're playing the 64bit client?

8GB RAM, running 64bit executable on (obviously) 64bit CPU.

 

Edit: obviously that's not much for today's standards, but I'm still able to play Arma with assortment of popular mods (RHS etc.) without crashes. I understand however that your mod is a completely different thing, and may not play nicely with my PC. Which sucks, because I'm really liking the idea, and I'm really impressed by how much thought was put into it!

Share this post


Link to post
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now

×