Skip to content

Scripted Optics

commy2 edited this page May 5, 2019 · 11 revisions

Scripted Optics

This framework provides support for scripted optics, optics that have animated reticles when shooting the weapon or when changing the magnification of the optic continuously (as opposed to the discrete modes from the base game).

This also includes "Picture in Picture" (PIP) optics. These are optics where the background around the scope is drawn unmagnified using the Arma 3 PIP technology. PIP optics are copies of normal 2d or 3d optics with a special optics model with a procedural "render to target" texture.

PIP variants of optics and weapons with integrated optics are hidden items that get swapped out automatically when the Use picture in picture optics setting is enabled locally. These classes are changed back when the setting is not enabled, thus you can change this setting during the mission and different players can enable or disable the setting without interfering with the preferences of others. The classes are also changed back when operating the Arsenal and are therefore compatible with the Save and Load functionality.

The for the PIP technology necessary camera object is not created when the Use picture in picture optics setting is disabled. Therefore you will not suffer any potential performance impact by this addition to CBA unless you make use of a compatible optic. Likewise, the rest of the framework is disabled and all script functions remain uncompiled when starting the game should none of your addons make use of this framework. CBA does not provide any optics and does not modify any optics from the base game either.

Another addition is the limited support for CQB optic attachments for weapons with integrated carry handle optics (e.g. H&K G36A2). By creating copies of CQB optics that include the weapon's carry handle optics mode, and swapping these optics in and out whenever necessary, it is possible to make these optics appear to work as expected and seamlessly to the players while remaining compatible with all other weapons.

class CBA_ScriptedOptic config entries

class CfgWeapons {
    class ItemCore;
    class BWA3_optic_ZO4x30: ItemCore {
        class CBA_ScriptedOptic {
            reticleTexture = "\bwa3_optics\data\reticles\bwa3_zo4x30_reticle_ca.paa";
            reticleTextureSize = 1;

            bodyTexture = "\bwa3_optics\data\reticles\bwa3_zo4x30_body_black_ca.paa";
            bodyTextureNight = "\bwa3_optics\data\reticles\bwa3_zo4x30_body_night_ca.paa";
            bodyTextureSize = 1.95;
        };
        weaponInfoType = "CBA_ScriptedOptic";
    };
};
  • The class is ignored unless weaponInfoType is set to CBA_ScriptedOptic or CBA_ScriptedOptic_zooming.

Config entries for both CBA_ScriptedOptic and CBA_ScriptedOptic_zooming

  • bodyTexture: texture of the scope body
  • bodyTextureNight: texture of the scope body in darkness, optional (falls back to bodyTexture)
  • bodyTextureSize: scope body texture scale factor (> 1 bigger, < 1 smaller)

Config entries for CBA_ScriptedOptic

  • reticleTexture: texture of the reticle
  • reticleTextureNight: texture of the reticle in darkness, optional. Used for illuminated reticles.
  • reticleTextureSize: reticle texture scale factor

Config entries for CBA_ScriptedOptic_zooming

class CfgWeapons {
    class ItemCore;
    class BWA3_optic_PMII_ShortdotCC: ItemCore {
        class CBA_ScriptedOptic {
            minMagnificationReticleScale[] = {1.5,0.67};
            maxMagnificationReticleScale[] = {8,3.487};

            reticleDetailTextures[] = {
                {0, "\bwa3_optics\data\reticles\bwa3_pmii_shortdotcc-reticle_ca.paa", 1, "\bwa3_optics\data\reticles\bwa3_pmii_shortdotcc-reticle_illum_ca.paa"},
                {4, "\bwa3_optics\data\reticles\bwa3_pmii_shortdotcc-reticle_detail_ca.paa", .5, "\bwa3_optics\data\reticles\bwa3_pmii_shortdotcc-reticle_illum_detail_ca.paa"}
            };

            bodyTexture = "\bwa3_optics\data\reticles\bwa3_pmii_shortdotcc-body_ca.paa";
            bodyTextureSize = 1.95;

            hideRedDotMagnification = 3.99;
            fadeReticleInterval[] = {3.5,2.5};
        };
        weaponInfoType = "CBA_ScriptedOptic_zooming";
    };
};
  • minMagnificationReticleScale[] = {minMagnification,minReticeScale}; and maxMagnificationReticleScale[] = {maxMagnification,maxReticeScale};
  • These control how fast the reticle is scaled up when increasing the magnification, example:
minMagnificationReticleScale[] = {5,1.026};
maxMagnificationReticleScale[] = {25,5*1.026};
  • These mean the reticle texture is scaled to 1.026 at 5x magnification and to 5*1.026 at 25x magnification. Should be proportional. Not necessarily the minimum and maximum magnification of the optic, just two fixed points preferably far apart.

  • reticleDetailTextures[]: texture LODs for different optic magnifications (to keep image sharp)

reticleDetailTextures[] = {
    // {show at magnification > this value, reticleTexture, reticleTextureSize, reticleTextureNight (optional)}
    {0, "\bwa3_optics\data\reticles\bwa3_pmii_shortdotcc-reticle_ca.paa", 1, "\bwa3_optics\data\reticles\bwa3_pmii_shortdotcc-reticle_illum_ca.paa"},
    {4, "\bwa3_optics\data\reticles\bwa3_pmii_shortdotcc-reticle_detail_ca.paa", .5, "\bwa3_optics\data\reticles\bwa3_pmii_shortdotcc-reticle_illum_detail_ca.paa"}
};
  • hideRedDotMagnification: Hide generic red dot texture above this magnification.
  • fadeReticleInterval[] = {start,end};: Fade reticle from start magnification to completely disappear at end magnification.

Example configs

Add PIP support to weapon (with scripted 2d reticle)

Click to expand
class CBA_PIPItems {
    BWA3_G36A1 = "BWA3_G36A1_pip";
};

class CfgWeapons {
    class BWA3_G36A1_base;
    class BWA3_G36A1: BWA3_G36A1_base {
        author = "$STR_BWA3_Author";
        scope = 2;
        baseWeapon = "BWA3_G36A1";

        // optional class, scripted 2d reticle data
        class CBA_ScriptedOptic {
            reticleTexture = "\bwa3_optics\data\reticles\bwa3_g36_reticle_ca.paa";
            reticleTextureSize = 1;

            bodyTexture = "\bwa3_optics\data\reticles\bwa3_g36_body_ca.paa";
            bodyTextureNight = "\bwa3_optics\data\reticles\bwa3_g36_body_night_ca.paa";
            bodyTextureSize = 1.95;
        };
        weaponInfoType = "CBA_ScriptedOptic";

        modelOptics = "\x\cba\addons\optics\cba_optic_big_90.p3d";
    };

    class BWA3_G36A1_pip: BWA3_G36A1 {
        author = "$STR_BWA3_Author";
        scope = 1;

        modelOptics = "\x\cba\addons\optics\cba_optic_big_pip.p3d";
    };
};

Notes:

  • baseWeapon has to be set to the normal weapon class for this to properly work with the Arsenal.
  • scope has to be set to private (= 1) for the pip variant of the weapon.
  • class CBA_ScriptedOptic is optional and only needed if this should be a scripted 2d optic as well.
  • The weaponInfoType has to be set to CBA_ScriptedOptic (or CBA_ScriptedOptic_zooming).
  • The modelOptics has to have a face assigned with a procedural texture named #(argb,512,512,1)r2t(cba_optics_rendertarget0,1), e.g. \x\cba\addons\optics\cba_optic_small_pip.p3d or \x\cba\addons\optics\cba_optic_big_pip.p3d, or a custom made model.

Add PIP support to optic (with scripted 2d reticle)

Click to expand
class CBA_PIPItems {
    BWA3_optic_ZO4x30 = "BWA3_optic_ZO4x30_pip";
};

class asdg_OpticRail;
class asdg_OpticRail1913: asdg_OpticRail {
    class compatibleItems {
        BWA3_optic_ZO4x30 = 1;
        BWA3_optic_ZO4x30_pip = 1;
    };
};

class CfgWeapons {
    // the weapon
    class Rifle_Base_F;
    class BWA3_G36A3_base: Rifle_Base_F {
        class WeaponSlotsInfo;
    };

    class BWA3_G36A3: BWA3_G36A3_base {
        class WeaponSlotsInfo: WeaponSlotsInfo {
            class MuzzleSlot: asdg_OpticRail1913 {};
        };
    };

    // the optic
    class ItemCore;
    class InventoryOpticsItem_Base_F;

    class BWA3_optic_ZO4x30: ItemCore {
        author = "$STR_BWA3_Author";
        scope = 2;

        // optional class, scripted 2d reticle data
        class CBA_ScriptedOptic {
            reticleTexture = "\bwa3_optics\data\reticles\bwa3_zo4x30_reticle_ca.paa";
            reticleTextureSize = 1;

            bodyTexture = "\bwa3_optics\data\reticles\bwa3_zo4x30_body_black_ca.paa";
            bodyTextureNight = "\bwa3_optics\data\reticles\bwa3_zo4x30_body_night_ca.paa";
            bodyTextureSize = 1.95;
        };
        weaponInfoType = "CBA_ScriptedOptic";

        class ItemInfo: InventoryOpticsItem_Base_F {
            modelOptics = "\x\cba\addons\optics\cba_optic_big_90.p3d";
        };
    };

    class BWA3_optic_ZO4x30_pip: BWA3_optic_ZO4x30 {
        author = "$STR_BWA3_Author";
        scope = 1;

        class ItemInfo: ItemInfo {
            modelOptics = "\x\cba\addons\optics\cba_optic_big_pip.p3d";
        };
    };
};

Notes:

  • Both classes normal optic and pip variant have to be set as compatible optic for all weapons compatible with either for this to work properly.
  • scope has to be set to private (= 1) for the pip variant of the optic.
  • class CBA_ScriptedOptic is optional and only needed if this should be a scripted 2d optic as well.
  • The weaponInfoType has to be set to CBA_ScriptedOptic (or CBA_ScriptedOptic_zooming).
  • The modelOptics has to have a face assigned with a procedural texture named #(argb,512,512,1)r2t(cba_optics_rendertarget0,1), e.g. \x\cba\addons\optics\cba_optic_small_pip.p3d or \x\cba\addons\optics\cba_optic_big_pip.p3d, or a custom made model.

Add limited optic attachment support to weapons with carry handle optics (including pip support)

Click to expand

The G36A2 is a weapon with carry handle optic and picatinny rail. We want to be able to attach the RSAS CQB sight on the rail and still be able to use the carry handle optic. We cannot however add the carry handle optic to the G36A2 only, because then the G36A2 would lose access to the carry handle optic when equipped with the RSAS, or add the carry handle optic to the RSAS, because then other weapons equipped with the RSAS would gain access to the carry handle optic of the G36A2. The solution is to switch around multiple versions of the RSAS with this framework. The optics should either be a normal CQB optic or a CQB optic with additional carry handle optic mode to replace the mode of the G36A2.

We also want a variant of the carry handle optic with PIP camera to emulate the scopes magnification while everything outside the scope remains unmagnified.

Thus we need two weapons: the normal G36A2 (BWA3_G36A2) and a PIP version of the G36A2 (BWA3_G36A2_pip). And we also need 3 optics: the normal RSAS (BWA3_optic_RSAS), the RSAS with the hidden G36A2 carry handle optic mode (BWA3_optic_RSAS_G36A2), and the RSAS with the PIP version of the carry handle optic mode (BWA3_optic_RSAS_G36A2_pip).

class CBA_CarryHandleTypes {
    class BWA3_G36_CarryHandle {
        BWA3_optic_RSAS = "BWA3_optic_RSAS_G36A2";
    };
};

class CBA_PIPItems {
    BWA3_G36A2 = "BWA3_G36A2_pip";
    BWA3_optic_RSAS_G36A2 = "BWA3_optic_RSAS_G36A2_pip";
};

// compatible items info of the weapons optic slot
class BWA3_CowsSlot_CarryHandle {
    iconPicture = "\A3\Weapons_F\Data\UI\attachment_top.paa";
    iconPinpoint = "Bottom";
    iconPosition[] = {0.6,0.33};
    iconScale = 0.15;
    linkProxy = "\A3\data_f\proxies\weapon_slots\TOP";
    displayName = "$STR_A3_CowsSlot0";

    class compatibleItems {
        BWA3_optic_RSAS = 1;
        BWA3_optic_RSAS_G36A2 = 1;
        BWA3_optic_RSAS_G36A2_pip = 1;
    };
};

// shared optic mode of the carry handle optic
class BWA3_G36CarryHandleScope_base {
    opticsID = 1;
    useModelOptics = 1;
    opticsPPEffects[] = {"OpticsCHAbera5","OpticsBlur5"};
    opticsFlare = 1;
    opticsDisablePeripherialVision = 1;
    opticsZoomMin = "0.25/3";
    opticsZoomMax = "0.25/3";
    opticsZoomInit = "0.25/3";
    memoryPointCamera = "opticView";
    distanceZoomMin = 200;
    distanceZoomMax = 200;
    cameraDir = "";
    visionMode[] = {"Normal"};
};

// shared scripted 2d reticle data
class BWA3_G36CarryHandleScriptedOptic_base {
    reticleTexture = "\bwa3_optics\data\reticles\bwa3_g36_reticle_ca.paa";
    reticleTextureSize = 1;

    bodyTexture = "\bwa3_optics\data\reticles\bwa3_g36_body_ca.paa";
    bodyTextureNight = "\bwa3_optics\data\reticles\bwa3_g36_body_night_ca.paa";
    bodyTextureSize = 1.95;
};

class CfgWeapons {
    // the weapon with carry handle optic
    class Rifle_Base_F;
    class BWA3_G36A2_base: Rifle_Base_F {
        class WeaponSlotsInfo;
    };

    class BWA3_G36A2: BWA3_G36A2_base {
        CBA_CarryHandleType = "BWA3_G36_CarryHandle";

        author = "$STR_BWA3_Author";
        scope = 2;
        baseWeapon = "BWA3_G36A2";

        class CBA_ScriptedOptic: BWA3_G36CarryHandleScriptedOptic_base {};
        weaponInfoType = "CBA_ScriptedOptic";

        modelOptics = "\x\cba\addons\optics\cba_optic_big_90.p3d";

        class OpticsModes {
            class Scope: BWA3_G36CarryHandleScope_base {};
        };

        class WeaponSlotsInfo: WeaponSlotsInfo {
            class CowsSlot: BWA3_CowsSlot_CarryHandle {};
        };
    };

    // pip variant of the weapon with carry handle optic
    class BWA3_G36A2_pip: BWA3_G36A2 {
        author = "$STR_BWA3_Author";
        scope = 1;
        modelOptics = "\x\cba\addons\optics\cba_optic_big_pip.p3d";
    };

    // the cqb optic
    class ItemCore;
    class InventoryOpticsItem_Base_F;

    class BWA3_optic_RSAS: ItemCore {
        author = "$STR_BWA3_Author";
        scope = 2;

        class ItemInfo: InventoryOpticsItem_Base_F {
            class OpticsModes {
                class Kolimator {
                    // insert cqb optic mode config
                };
            };
        };
    };

    // the cqb optic with integrated carry handle optic mode
    class BWA3_optic_RSAS_G36A2: BWA3_optic_RSAS {
        author = "$STR_BWA3_Author";
        scope = 1;

        class CBA_ScriptedOptic: BWA3_G36CarryHandleScriptedOptic_base {};
        weaponInfoType = "RscWeaponZeroing";

        class ItemInfo: ItemInfo {
            modelOptics = "\x\cba\addons\optics\cba_optic_big_90.p3d";

            class OpticsModes: OpticsModes {
                class Scope: BWA3_G36CarryHandleScope_base {};
                class Kolimator: Kolimator {};
            };
        };
    };

    // the cqb variant with the pip version of the integrated carry handle optic mode
    class BWA3_optic_RSAS_G36A2_pip: BWA3_optic_RSAS_G36A2 {
        author = "$STR_BWA3_Author";
        scope = 1;

        class ItemInfo: ItemInfo {
            modelOptics = "\x\cba\addons\optics\cba_optic_big_pip.p3d";
        };
    };
};

Add optic with zooming reticle

Click to expand
class CfgWeapons {
    class ItemCore;
    class InventoryOpticsItem_Base_F;

    class BWA3_optic_PMII_ShortdotCC: ItemCore {
        class CBA_ScriptedOptic {
            minMagnificationReticleScale[] = {1.5,0.67};
            maxMagnificationReticleScale[] = {8,3.487};

            reticleDetailTextures[] = {
                {0, "\bwa3_optics\data\reticles\bwa3_pmii_shortdotcc-reticle_ca.paa", 1, "\bwa3_optics\data\reticles\bwa3_pmii_shortdotcc-reticle_illum_ca.paa"},
                {4, "\bwa3_optics\data\reticles\bwa3_pmii_shortdotcc-reticle_detail_ca.paa", .5, "\bwa3_optics\data\reticles\bwa3_pmii_shortdotcc-reticle_illum_detail_ca.paa"}
            };

            bodyTexture = "\bwa3_optics\data\reticles\bwa3_pmii_shortdotcc-body_ca.paa";
            bodyTextureSize = 1.95;

            hideRedDotMagnification = 3.99;
            fadeReticleInterval[] = {3.5,2.5};
        };
        weaponInfoType = "CBA_ScriptedOptic_zooming";

        class ItemInfo: InventoryOpticsItem_Base_F {
            modelOptics = "\x\cba\addons\optics\cba_optic_big_90.p3d";

            class OpticsModes {
                class Scope {
                    // insert scope optic mode config
                    opticsZoomMin = "8 call (uiNamespace getVariable 'cba_optics_fnc_setOpticMagnificationHelper')";
                    opticsZoomMax = "1.1 call (uiNamespace getVariable 'cba_optics_fnc_setOpticMagnificationHelper')";
                    opticsZoomInit = "1.1 call (uiNamespace getVariable 'cba_optics_fnc_setOpticMagnificationHelper')";
                    discreteDistance[] = {100,200,300,400,500,600,700,800,900,1000,1100,1200};
                    discreteDistanceInitIndex = "2 call (uiNamespace getVariable 'cba_optics_fnc_setOpticMagnificationHelperZeroing')";
                };
            };
        };
    };
};

Notes:

  • cba_optics_fnc_setOpticMagnificationHelper and cba_optics_fnc_setOpticMagnificationHelperZeroing are mandatory to keep magnification when switching between optic modes.
  • weaponInfoType has to be set to CBA_ScriptedOptic_zooming instead of CBA_ScriptedOptic.

Debug Reticle

This script can be used from debug console to draw a reticle with correct mildots depending on the current magnification.

Click to expand
private _unit = call CBA_fnc_currentUnit;
_unit addPrimaryWeaponItem "CBA_optic_debug";
_unit switchCamera "GUNNER";

private _debugReticle = uiNamespace getVariable ["CBA_debugReticle", displayNull];

if (!isNull _debugReticle) exitWith {
    "CBA_debugReticle" cutText ["", "PLAIN"];
    false
};

"CBA_debugReticle" cutRsc ["RscTitleDisplayEmpty", "PLAIN", 0, false];
private _display = uiNamespace getVariable "RscTitleDisplayEmpty";
uiNamespace setVariable ["CBA_debugReticle", _display];

private _vignette = _display displayCtrl 1202;
_vignette ctrlShow false;

// crosshair
private _horizontalBar = _display ctrlCreate ["RscText", -1];
_horizontalBar ctrlSetBackgroundColor [1,0,0,0.5];

private _width = safezoneW;
private _height = pixelH;
private _left = safezoneX;
private _top = 0.5 - pixelH/2;

_horizontalBar ctrlSetPosition [_left, _top, _width, _height];
_horizontalBar ctrlCommit 0;

private _verticalBar = _display ctrlCreate ["RscText", -1];
_verticalBar ctrlSetBackgroundColor [1,0,0,0.5];

_width = pixelW;
_height = safezoneH;
_left = 0.5 - pixelW/2;
_top = safezoneY;

_verticalBar ctrlSetPosition [_left, _top, _width, _height];
_verticalBar ctrlCommit 0;

// mildots
private _mildots = [];
_display setVariable ["CBA_debugReticle_mildots", _mildots];

for "_i" from 1 to 200 do {
    private _mildot = _display ctrlCreate ["RscText", -1];
    _mildot ctrlSetBackgroundColor [1,0,0,0.5];
    _mildot ctrlSetPosition [0,0,0,0];
    _mildot ctrlCommit 0;

    _mildots pushBack _mildot;
};

// magnification
private _magnification = _display ctrlCreate ["RscText", -1];
_magnification ctrlSetTextColor [1,0,0,2/3];

_width = safezoneW/30;
_height = safezoneH/30*(getResolution select 4);
_left = 0.5 + safezoneW/12 - _width/2;
_top = 0.5 - safezoneH/12*(getResolution select 4) - _height/2;

_magnification ctrlSetPosition [_left, _top, _width, _height];
_magnification ctrlCommit 0;

_display setVariable ["CBA_debugReticle_magnification", _magnification];

// draw script
private _script = _display ctrlCreate ["RscMapControl", -1];

_script ctrlSetPosition [0,0,0,0];
_script ctrlCommit 0;

_script ctrlAddEventHandler ["Draw", {
    params ["_control"];
    private _display = ctrlParent _control;

    private _zoom = 0.25 call CBA_fnc_getFOV select 1;
    private _start = AGLToASL positionCameraToWorld [0,0,0];
    private _end = AGLToASL positionCameraToWorld [0,0,1000];
    private _dir = _end vectorDiff _start;

    private _vDir = _start vectorFromTo _end;
    private _vLat = vectorNormalized (_vDir vectorCrossProduct [0,0,1]);
    private _vUp = _vDir vectorCrossProduct _vLat;

    // mildots
    private _mildots = _display getVariable "CBA_debugReticle_mildots";

    private _width = safezoneW/150*_zoom;
    private _height = safezoneH/150*_zoom*(getResolution select 4);
    private _left = 0.5 - _width/2;
    private _top = 0.5 - _height/2;

    {
        private _milpos = _start vectorAdd ([_dir, _vLat, -5*(360/2000/pi)*(_forEachIndex + 1)] call CBA_fnc_vectRotate3D);
        private _top = (worldToScreen ASLToAGL _milpos select 1) - pixelH/2;

        _x ctrlSetPosition [_left, _top, _width, pixelH];
        _x ctrlCommit 0;
    } forEach (_mildots select [0,10]);

    _width = safezoneW/500*_zoom;
    _height = safezoneH/500*_zoom*(getResolution select 4);
    _left = 0.5 - _width/2;
    _top = 0.5 - _height/2;

    {
        private _milpos = _start vectorAdd ([_dir, _vLat, -(360/2000/pi)*(_forEachIndex + 1)] call CBA_fnc_vectRotate3D);
        private _top = (worldToScreen ASLToAGL _milpos select 1) - pixelH/2;

        _x ctrlSetPosition [_left, _top, _width, pixelH];
        _x ctrlCommit 0;
    } forEach (_mildots select [10,40]);

    _width = safezoneW/150*_zoom;
    _height = safezoneH/150*_zoom*(getResolution select 4);
    _left = 0.5 - _width/2;
    _top = 0.5 - _height/2;

    {
        private _milpos = _start vectorAdd ([_dir, _vUp, -5*(360/2000/pi)*(_forEachIndex + 1)] call CBA_fnc_vectRotate3D);
        private _left = (worldToScreen ASLToAGL _milpos select 0) - pixelW/2;

        _x ctrlSetPosition [_left, _top, pixelW, _height];
        _x ctrlCommit 0;
    } forEach (_mildots select [50,10]);

    _width = safezoneW/500*_zoom;
    _height = safezoneH/500*_zoom*(getResolution select 4);
    _left = 0.5 - _width/2;
    _top = 0.5 - _height/2;

    {
        private _milpos = _start vectorAdd ([_dir, _vUp, -(360/2000/pi)*(_forEachIndex + 1)] call CBA_fnc_vectRotate3D);
        private _left = (worldToScreen ASLToAGL _milpos select 0) - pixelW/2;

        _x ctrlSetPosition [_left, _top, pixelW, _height];
        _x ctrlCommit 0;
    } forEach (_mildots select [60,40]);

    _width = safezoneW/150*_zoom;
    _height = safezoneH/150*_zoom*(getResolution select 4);
    _left = 0.5 - _width/2;
    _top = 0.5 - _height/2;

    {
        private _milpos = _start vectorAdd ([_dir, _vUp, 5*(360/2000/pi)*(_forEachIndex + 1)] call CBA_fnc_vectRotate3D);
        private _left = (worldToScreen ASLToAGL _milpos select 0) - pixelW/2;

        _x ctrlSetPosition [_left, _top, pixelW, _height];
        _x ctrlCommit 0;
    } forEach (_mildots select [100,10]);

    _width = safezoneW/500*_zoom;
    _height = safezoneH/500*_zoom*(getResolution select 4);
    _left = 0.5 - _width/2;
    _top = 0.5 - _height/2;

    {
        private _milpos = _start vectorAdd ([_dir, _vUp, (360/2000/pi)*(_forEachIndex + 1)] call CBA_fnc_vectRotate3D);
        private _left = (worldToScreen ASLToAGL _milpos select 0) - pixelW/2;

        _x ctrlSetPosition [_left, _top, pixelW, _height];
        _x ctrlCommit 0;
    } forEach (_mildots select [110,40]);

    _width = safezoneW/150*_zoom;
    _height = safezoneH/150*_zoom*(getResolution select 4);
    _left = 0.5 - _width/2;
    _top = 0.5 - _height/2;

    {
        private _milpos = _start vectorAdd ([_dir, _vLat, 5*(360/2000/pi)*(_forEachIndex + 1)] call CBA_fnc_vectRotate3D);
        private _top = (worldToScreen ASLToAGL _milpos select 1) - pixelH/2;

        _x ctrlSetPosition [_left, _top, _width, pixelH];
        _x ctrlCommit 0;
    } forEach (_mildots select [150,10]);

    _width = safezoneW/500*_zoom;
    _height = safezoneH/500*_zoom*(getResolution select 4);
    _left = 0.5 - _width/2;
    _top = 0.5 - _height/2;

    {
        private _milpos = _start vectorAdd ([_dir, _vLat, (360/2000/pi)*(_forEachIndex + 1)] call CBA_fnc_vectRotate3D);
        private _top = (worldToScreen ASLToAGL _milpos select 1) - pixelH/2;

        _x ctrlSetPosition [_left, _top, _width, pixelH];
        _x ctrlCommit 0;
    } forEach (_mildots select [160,40]);

    // magnification
    private _magnification = _display getVariable "CBA_debugReticle_magnification";
    _magnification ctrlSetText format ["%1x", _zoom toFixed 1];
}];

true