diff --git a/addons/ballistics/functions/fnc_statTextStatement_magazineMuzzleVelocity.sqf b/addons/ballistics/functions/fnc_statTextStatement_magazineMuzzleVelocity.sqf index 701ce544f7..a57a52bba7 100644 --- a/addons/ballistics/functions/fnc_statTextStatement_magazineMuzzleVelocity.sqf +++ b/addons/ballistics/functions/fnc_statTextStatement_magazineMuzzleVelocity.sqf @@ -41,7 +41,11 @@ if (_initSpeedCoef > 0) then { }; private _abAdjustText = ""; -if (_magIsForCurrentWeapon && {["ace_advanced_ballistics"] call EFUNC(common,isModLoaded)}) then { +if ( + _magIsForCurrentWeapon && + {missionNamespace getVariable [QEGVAR(advanced_ballistics,enabled), false]} && + {missionNamespace getVariable [QEGVAR(advanced_ballistics,barrelLengthInfluenceEnabled), false]} // this can be on while AB is off or vice-versa +) then { private _configAmmo = (configFile >> "CfgAmmo" >> (getText (_configMagazine >> "ammo"))); private _barrelLength = getNumber (_configWeapon >> "ACE_barrelLength"); private _muzzleVelocityTable = getArray (_configAmmo >> "ACE_muzzleVelocities"); diff --git a/addons/ballistics/functions/fnc_statTextStatement_weaponMuzzleVelocity.sqf b/addons/ballistics/functions/fnc_statTextStatement_weaponMuzzleVelocity.sqf index 16ab9e2a47..e11c7cf5fb 100644 --- a/addons/ballistics/functions/fnc_statTextStatement_weaponMuzzleVelocity.sqf +++ b/addons/ballistics/functions/fnc_statTextStatement_weaponMuzzleVelocity.sqf @@ -37,7 +37,10 @@ if (_magazine isEqualTo "") then { }; private _abAdjustText = ""; - if (["ace_advanced_ballistics"] call EFUNC(common,isModLoaded)) then { + if ( + missionNamespace getVariable [QEGVAR(advanced_ballistics,enabled), false] && + {missionNamespace getVariable [QEGVAR(advanced_ballistics,barrelLengthInfluenceEnabled), false]} // this can be on while AB is off or vice-versa + ) then { private _configAmmo = (configFile >> "CfgAmmo" >> (getText (_configMagazine >> "ammo"))); private _barrelLength = getNumber (_configWeapon >> "ACE_barrelLength"); private _muzzleVelocityTable = getArray (_configAmmo >> "ACE_muzzleVelocities"); diff --git a/addons/common/functions/fnc_disableUserInput.sqf b/addons/common/functions/fnc_disableUserInput.sqf index 4037a168b7..6c58954b13 100644 --- a/addons/common/functions/fnc_disableUserInput.sqf +++ b/addons/common/functions/fnc_disableUserInput.sqf @@ -1,7 +1,8 @@ #include "..\script_component.hpp" #include "\a3\ui_f_curator\ui\defineResinclDesign.inc" +#include "\a3\ui_f\hpp\defineDIKCodes.inc" /* - * Author: commy2 + * Author: commy2, johnb43 * Disables key input. ESC can still be pressed to open the menu. * * Arguments: @@ -27,7 +28,7 @@ if (_state) then { if (!isNull (uiNamespace getVariable [QGVAR(dlgDisableMouse), displayNull])) exitWith {}; if (!isNil QGVAR(disableInputPFH)) exitWith {}; - // end TFAR and ACRE2 radio transmissions + // End TFAR and ACRE2 radio transmissions call FUNC(endRadioTransmission); // Close map @@ -45,38 +46,120 @@ if (_state) then { private _map = _display displayCtrl 101; _map ctrlMapCursor ["", QGVAR(blank)]; + GVAR(keyboardInputMain) = createHashMap; + GVAR(keyboardInputCombo) = createHashMap; + _display displayAddEventHandler ["KeyDown", { + // If input is enabled again, ignore + if (isNil QGVAR(keyboardInputMain)) exitWith {}; + params ["", "_key"]; - if (_key == 1 && {alive player}) then { - createDialog (["RscDisplayInterrupt", "RscDisplayMPInterrupt"] select isMultiplayer); + // Get key info; Stored as [isPressed, pressedCount] + private _keyPressedInfo = GVAR(keyboardInputMain) getOrDefault [_key, [false, 0], true]; + _keyPressedInfo params ["_keyPressed", "_keyPressedCount"]; + // For regular keys: If pressed, set to release and remove one key press + if (!_keyPressed) then { + _keyPressedInfo set [0, true]; + _keyPressedInfo set [1, _keyPressedCount + 1]; + }; + + // For combo keys, register only if pushed or released (no keypress count) + if !(GVAR(keyboardInputCombo) getOrDefault [_key, false]) then { + GVAR(keyboardInputCombo) set [_key, true]; + }; + + // Look if keybinds of various actions have been pressed + private _action = ""; + private _comboDikPressed = false; + private _return = false; + + // This technique has a limitation: It can't process the Escape key properly (KeyUp EH does not fire) + (["TeamSwitch", "CuratorInterface", "ShowMap", "DefaultAction", "Throw", "Chat", "PrevChannel", "NextChannel"] apply { + _action = _x; + + { + _x params ["_mainKeyArray", "_comboKeyArray", "_isDoubleTap"]; + _mainKeyArray params ["_mainDik", "_mainDevice"]; + + // If keybind doesn't contain key combo, it returns empty array; Therefore, return true + _comboDikPressed = if (_comboKeyArray isEqualTo []) then { + true + } else { + _comboKeyArray params ["_comboDik", "_comboDevice"]; + + _comboDevice == "KEYBOARD" && {GVAR(keyboardInputCombo) getOrDefault [_comboDik, false]} + }; + + // Check if the necessary keys were pressed for a keybind + _return = _comboDikPressed && + {_mainDevice == "KEYBOARD"} && + {((GVAR(keyboardInputMain) getOrDefault [_mainDik, [false, 0]]) select 1) > ([0, 1] select _isDoubleTap)}; // check how many times the main key was pressed + + // Keybind was detected + if (_return) exitWith { + TRACE_1("Action triggered: ",_action); + }; + } forEach (actionKeysEx _action); + + _return + }) params ["_teamSwitch", "_curatorInterface", "_showMap", "_defaultAction", "_throw", "_chat", "_prevChannel", "_nextChannel"]; + + // Handle Escape separately because of limitation mentioned above + if (_key == DIK_ESCAPE && {alive player}) then { disableSerialization; + private _isMultiplayer = isMultiplayer; + private _is3DENPreview = is3DENPreview; + + createDialog (["RscDisplayInterrupt", "RscDisplayMPInterrupt"] select _isMultiplayer); + private _dlg = findDisplay 49; for "_index" from 100 to 2000 do { (_dlg displayCtrl _index) ctrlEnable false; }; - private _ctrl = _dlg displayctrl 103; - _ctrl ctrlSetEventHandler ["buttonClick", QUOTE(while {!isNull (uiNamespace getVariable [ARR_2(QUOTE(QGVAR(dlgDisableMouse)),displayNull)])} do {closeDialog 0}; failMission 'LOSER'; [false] call DFUNC(disableUserInput))]; - _ctrl ctrlEnable true; - _ctrl ctrlSetText "ABORT"; - _ctrl ctrlSetTooltip "Abort."; + private _ctrl = _dlg displayCtrl 103; + _ctrl ctrlSetEventHandler ["ButtonClick", toString { + while {!isNull (uiNamespace getVariable [QGVAR(dlgDisableMouse), displayNull])} do { + closeDialog 0 + }; - _ctrl = _dlg displayctrl ([104, 1010] select isMultiplayer); - if (["ace_medical"] call FUNC(isModLoaded)) then { - _ctrl ctrlSetEventHandler ["buttonClick", 'closeDialog 0; [player, "respawn_button"] call EFUNC(medical_status,setDead); [false] call DFUNC(disableUserInput);']; - } else { - _ctrl ctrlSetEventHandler ["buttonClick", QUOTE(closeDialog 0; player setDamage 1; [false] call DFUNC(disableUserInput))]; - }; - _ctrl ctrlEnable ((getMissionConfigValue ["respawnButton", -1]) != 0); // handles 3den attribute or description.ext - _ctrl ctrlSetText localize "$str_3den_multiplayer_attributecategory_respawn_displayname"; - _ctrl ctrlSetTooltip "Respawn."; + failMission "LOSER"; + + [false] call FUNC(disableUserInput); + }]; + _ctrl ctrlEnable true; + _ctrl ctrlSetText localize (["str_disp_int_abort", "STR_3DEN_RscDisplayInterrupt_ButtonAbort_3DEN_text"] select (_is3DENPreview && !_isMultiplayer)); + _ctrl ctrlSetTooltip localize ([ + "STR_TOOLTIP_MAIN_ABORT_CAMPAIGN", + "STR_3DEN_RscDisplayInterrupt_ButtonAbort_3DEN_tooltip", + "STR_TOOLTIP_MAIN_ABORT" + ] select (([_is3DENPreview, _isMultiplayer] call FUNC(toBitmask)) min 2)); + + _ctrl = _dlg displayCtrl ([104, 1010] select _isMultiplayer); + _ctrl ctrlSetEventHandler ["ButtonClick", toString { + closeDialog 0; + + if (["ace_medical"] call FUNC(isModLoaded)) then { + [player, "respawn_button"] call EFUNC(medical_status,setDead); + } else { + player setDamage 1; + }; + + [false] call FUNC(disableUserInput); + }]; + + private _respawnEnabled = (getMissionConfigValue ["respawnButton", -1]) != 0; + + _ctrl ctrlEnable _respawnEnabled; // handles 3den attribute or description.ext + _ctrl ctrlSetText localize "str_disp_int_respawn"; + _ctrl ctrlSetTooltip localize (["str_3den_attributes_respawn_none_tooltip", "str_disp_int_respawn"] select _respawnEnabled); }; - if (_key in actionKeys "TeamSwitch" && {teamSwitchEnabled}) then { + if (_teamSwitch && teamSwitchEnabled) then { (uiNamespace getVariable [QGVAR(dlgDisableMouse), displayNull]) closeDisplay 0; private _acc = accTime; @@ -84,18 +167,20 @@ if (_state) then { setAccTime _acc; }; - if (_key in actionKeys "CuratorInterface" && {getAssignedCuratorLogic player in allCurators}) then { + if (_curatorInterface && {!isNull getAssignedCuratorLogic player}) then { (uiNamespace getVariable [QGVAR(dlgDisableMouse), displayNull]) closeDisplay 0; + openCuratorInterface; }; - if (_key in actionKeys "ShowMap" && {player getVariable ["ACE_canSwitchUnits", false]}) then { + if (_showMap && {player getVariable ["ACE_canSwitchUnits", false]}) then { (uiNamespace getVariable [QGVAR(dlgDisableMouse), displayNull]) closeDisplay 0; + openMap true; }; - if (isServer || {serverCommandAvailable "#kick"}) then { - if (!(_key in (actionKeys "DefaultAction" + actionKeys "Throw")) && {_key in (actionKeys "Chat" + actionKeys "PrevChannel" + actionKeys "NextChannel")}) then { + if (isMultiplayer && {isServer || {serverCommandAvailable "#kick"}}) then { + if (!(_defaultAction || _throw) && {_chat || _prevChannel || _nextChannel}) then { _key = 0; }; }; @@ -103,7 +188,41 @@ if (_state) then { _key > 0 }]; - _display displayAddEventHandler ["KeyUp", {true}]; + _display displayAddEventHandler ["KeyUp", { + // If input is enabled again, ignore + if (isNil QGVAR(keyboardInputMain)) exitWith {}; + + params ["", "_key"]; + + // For combo keys: If pressed, release + if (GVAR(keyboardInputCombo) getOrDefault [_key, false]) then { + GVAR(keyboardInputCombo) deleteAt _key; + }; + + private _keyPressedInfo = GVAR(keyboardInputMain) getOrDefault [_key, [false, 0]]; + + // If pressed, release it + if (_keyPressedInfo select 0) then { + _keyPressedInfo set [0, false]; + }; + + // Cache keystrokes of regular keys for a small amount of time + [{ + // If input is enabled again, ignore + if (isNil QGVAR(keyboardInputMain)) exitWith {}; + + params ["_key"]; + + private _keyPressedInfo = GVAR(keyboardInputMain) getOrDefault [_key, [false, 0]]; + + // Release it + _keyPressedInfo set [1, ((_keyPressedInfo select 1) - 1) max 0]; + + if (_keyPressedInfo isEqualTo [false, 0]) then { + GVAR(keyboardInputMain) deleteAt _key, + }; + }, _key, 0.5] call CBA_fnc_waitAndExecute; + }]; }; GVAR(disableInputPFH) = [{ @@ -133,4 +252,7 @@ if (_state) then { }; (uiNamespace getVariable [QGVAR(dlgDisableMouse), displayNull]) closeDisplay 0; + + GVAR(keyboardInputMain) = nil; + GVAR(keyboardInputCombo) = nil; }; diff --git a/addons/kestrel4500/functions/fnc_collectData.sqf b/addons/kestrel4500/functions/fnc_collectData.sqf index 14fd6047f2..318a5a7123 100644 --- a/addons/kestrel4500/functions/fnc_collectData.sqf +++ b/addons/kestrel4500/functions/fnc_collectData.sqf @@ -15,6 +15,8 @@ * Public: No */ +#define TEMPERATURE_SLOT_INDEX 5 + private _playerDir = getDir ACE_player; private _playerAltitude = (getPosASL ACE_player) select 2; private _temperature = _playerAltitude call EFUNC(weather,calculateTemperatureAtHeight); @@ -41,9 +43,10 @@ if (isNil QGVAR(MIN) || isNil QGVAR(MAX)) then { [0, _playerDir] call FUNC(updateMemory); if (GVAR(MinAvgMaxMode) == 1) then { + private _useAB = missionNamespace getVariable [QEGVAR(advanced_ballistics,enabled), false]; { GVAR(ENTRIES) set [_x, (GVAR(ENTRIES) select _x) + 1]; - } count [2, 3, 4]; + } forEach [2, 3, 4]; // Wind SPD private _windSpeed = call FUNC(measureWindSpeed); @@ -51,7 +54,7 @@ if (GVAR(MinAvgMaxMode) == 1) then { // CROSSWIND private _crosswind = 0; - if (missionNamespace getVariable [QEGVAR(advanced_ballistics,enabled), false]) then { + if (_useAB) then { _crosswind = abs(sin(GVAR(RefHeading) - _playerDir) * _windSpeed); } else { _crosswind = abs(sin(GVAR(RefHeading)) * _windSpeed); @@ -60,7 +63,7 @@ if (GVAR(MinAvgMaxMode) == 1) then { // HEADWIND private _headwind = 0; - if (missionNamespace getVariable [QEGVAR(advanced_ballistics,enabled), false]) then { + if (_useAB) then { _headwind = cos(GVAR(RefHeading) - _playerDir) * _windSpeed; } else { _headwind = cos(GVAR(RefHeading)) * _windSpeed; @@ -74,4 +77,18 @@ if (GVAR(MinAvgMaxMode) == 1) then { GVAR(TOTAL) set [4, (GVAR(TOTAL) select 4) + _headwind]; }; -{ _x call FUNC(updateMemory); true } count [[5, _temperature],[6, _chill],[7, _humidity],[8, _heatIndex],[9, _dewPoint],[10, _wetBulb],[11, _barometricPressure],[12, _altitude],[13, _densityAltitude]]; +private _data = [ + _temperature, + _chill, + _humidity, + _heatIndex, + _dewPoint, + _wetBulb, + _barometricPressure, + _altitude, + _densityAltitude +]; + +{ + [TEMPERATURE_SLOT_INDEX + _forEachIndex, _x] call FUNC(updateMemory); +} forEach _data; diff --git a/addons/main/script_macros.hpp b/addons/main/script_macros.hpp index ed3a826678..495d592e5f 100644 --- a/addons/main/script_macros.hpp +++ b/addons/main/script_macros.hpp @@ -99,7 +99,11 @@ #define TYPE_SCUBA 604 // not implemented #define TYPE_HEADGEAR 605 #define TYPE_FACTOR 607 +#define TYPE_MAP 608 +#define TYPE_COMPASS 609 +#define TYPE_WATCH 610 #define TYPE_RADIO 611 +#define TYPE_GPS 612 #define TYPE_HMD 616 #define TYPE_BINOCULAR 617 #define TYPE_MEDIKIT 619 diff --git a/addons/maptools/CfgVehicles.hpp b/addons/maptools/CfgVehicles.hpp index 8c320de321..10302a0c4f 100644 --- a/addons/maptools/CfgVehicles.hpp +++ b/addons/maptools/CfgVehicles.hpp @@ -1,62 +1,204 @@ +#define EXCEPTIONS exceptions[] = {"isNotDragging", "notOnMap", "isNotInside", "isNotSitting"} + class CfgVehicles { class Man; class CAManBase: Man { class ACE_SelfActions { class ACE_MapGpsShow { displayName = CSTRING(MapGpsShow); - condition = QUOTE((!GVAR(mapGpsShow)) && {call FUNC(canUseMapGPS)}); - statement = QUOTE(GVAR(mapGpsShow) = true;); - exceptions[] = {"isNotDragging", "notOnMap", "isNotInside", "isNotSitting"}; + condition = QUOTE(!GVAR(mapGpsShow) && {call FUNC(canUseMapGPS)}); + statement = QUOTE(GVAR(mapGpsShow) = true); + EXCEPTIONS; showDisabled = 0; }; class ACE_MapGpsHide { displayName = CSTRING(MapGpsHide); - condition = QUOTE((GVAR(mapGpsShow)) && {call FUNC(canUseMapGPS)}); - statement = QUOTE(GVAR(mapGpsShow) = false;); - exceptions[] = {"isNotDragging", "notOnMap", "isNotInside", "isNotSitting"}; + condition = QUOTE(GVAR(mapGpsShow) && {call FUNC(canUseMapGPS)}); + statement = QUOTE(GVAR(mapGpsShow) = false); + EXCEPTIONS; showDisabled = 0; }; class ACE_MapTools { displayName = CSTRING(MapTools_Menu); condition = QUOTE(call FUNC(canUseMapTools)); statement = ""; - exceptions[] = {"isNotDragging", "notOnMap", "isNotInside", "isNotSitting"}; + EXCEPTIONS; showDisabled = 0; + class ACE_MapToolsHide { displayName = CSTRING(MapToolsHide); condition = QUOTE(GVAR(mapTool_Shown) != 0); - statement = QUOTE(GVAR(mapTool_Shown) = 0;); - exceptions[] = {"isNotDragging", "notOnMap", "isNotInside", "isNotSitting"}; + statement = QUOTE(GVAR(mapTool_Shown) = 0); + EXCEPTIONS; showDisabled = 1; }; class ACE_MapToolsShowNormal { displayName = CSTRING(MapToolsShowNormal); condition = QUOTE(GVAR(mapTool_Shown) != 1); - statement = QUOTE(if (GVAR(mapTool_Shown) == 0) then {GVAR(mapTool_moveToMouse) = true}; GVAR(mapTool_Shown) = 1;); - exceptions[] = {"isNotDragging", "notOnMap", "isNotInside", "isNotSitting"}; + statement = QUOTE(if (GVAR(mapTool_Shown) == 0) then {GVAR(mapTool_moveToMouse) = true}; GVAR(mapTool_Shown) = 1); + EXCEPTIONS; showDisabled = 1; }; class ACE_MapToolsShowSmall { displayName = CSTRING(MapToolsShowSmall); condition = QUOTE(GVAR(mapTool_Shown) != 2); - statement = QUOTE(if (GVAR(mapTool_Shown) == 0) then {GVAR(mapTool_moveToMouse) = true}; GVAR(mapTool_Shown) = 2;); - exceptions[] = {"isNotDragging", "notOnMap", "isNotInside", "isNotSitting"}; + statement = QUOTE(if (GVAR(mapTool_Shown) == 0) then {GVAR(mapTool_moveToMouse) = true}; GVAR(mapTool_Shown) = 2); + EXCEPTIONS; showDisabled = 1; }; - class ACE_MapToolsAlignNorth { - displayName = CSTRING(MapToolsAlignNorth); + class ACE_MapToolsAlign { + displayName = CSTRING(AlignTo); condition = QUOTE(GVAR(mapTool_Shown) != 0); - statement = QUOTE(GVAR(mapTool_angle) = 0;); - exceptions[] = {"isNotDragging", "notOnMap", "isNotInside", "isNotSitting"}; + statement = ""; + EXCEPTIONS; + showDisabled = 0; + + class ACE_MapToolsAlignToPlottingBoardRuler { + displayName = CSTRING(ToPlottingBoardRulerLabel); + condition = QUOTE(GVAR(mapTool_Shown) != 0 && GVAR(plottingBoard_Shown) == 2); + statement = QUOTE(GVAR(mapTool_angle) = GVAR(plottingBoard_rulerAngle)); + EXCEPTIONS; + showDisabled = 1; + }; + class ACE_MapToolsAlignToPlottingBoardAcrylic { + displayName = CSTRING(ToPlottingBoardAcrylicLabel); + condition = QUOTE(GVAR(mapTool_Shown) != 0 && GVAR(plottingBoard_Shown) != 0); + statement = QUOTE(GVAR(mapTool_angle) = GVAR(plottingBoard_acrylicAngle)); + EXCEPTIONS; + showDisabled = 1; + }; + class ACE_MapToolsAlignToPlottingBoard { + displayName = CSTRING(ToPlottingBoardLabel); + condition = QUOTE(GVAR(mapTool_Shown) != 0 && GVAR(plottingBoard_Shown) != 0); + statement = QUOTE(GVAR(mapTool_angle) = GVAR(plottingBoard_angle)); + EXCEPTIONS; + showDisabled = 1; + }; + class ACE_MapToolsAlignCompass { + displayName = CSTRING(ToCompassLabel); + condition = QUOTE(GVAR(mapTool_Shown) != 0 && {ACE_player getSlotItemName TYPE_COMPASS != ''}); + statement = QUOTE(GVAR(mapTool_angle) = getDir ACE_player); + EXCEPTIONS; + showDisabled = 1; + }; + class ACE_MapToolsAlignNorth { + displayName = CSTRING(ToNorthLabel); + condition = QUOTE(GVAR(mapTool_Shown) != 0); + statement = QUOTE(GVAR(mapTool_angle) = 0); + EXCEPTIONS; + showDisabled = 1; + }; + }; + }; + class ACE_PlottingBoard { + displayName = CSTRING(ShowPlottingBoard); + condition = QUOTE(GVAR(plottingBoard_Shown) < 1 && {call FUNC(canUsePlottingBoard)}); + statement = QUOTE(GVAR(plottingBoard_Shown) = 1); + EXCEPTIONS; + showDisabled = 0; + }; + class ACE_PlottingBoardHide { + displayName = CSTRING(HidePlottingBoard); + condition = QUOTE(GVAR(plottingBoard_Shown) != 0 && {call FUNC(canUsePlottingBoard)}); + statement = QUOTE(GVAR(plottingBoard_Shown) = 0); + EXCEPTIONS; + showDisabled = 0; + + class ACE_PlottingBoardRulerShow { + displayName = CSTRING(TogglePlottingBoardRuler); + condition = QUOTE(GVAR(plottingBoard_Shown) == 1); + statement = QUOTE(GVAR(plottingBoard_Shown) = 2); + EXCEPTIONS; showDisabled = 1; }; - class ACE_MapToolsAlignCompass { - displayName = CSTRING(MapToolsAlignCompass); - condition = QUOTE(GVAR(mapTool_Shown) != 0 && {getUnitLoadout ACE_player param [ARR_2(9,[])] param [ARR_2(3,'')] != ''}); - statement = QUOTE(GVAR(mapTool_angle) = getDir ACE_player;); - exceptions[] = {"isNotDragging", "notOnMap", "isNotInside", "isNotSitting"}; + class ACE_PlottingBoardRulerHide { + displayName = CSTRING(TogglePlottingBoardRuler); + condition = QUOTE(GVAR(plottingBoard_Shown) == 2); + statement = QUOTE(GVAR(plottingBoard_Shown) = 1); + EXCEPTIONS; showDisabled = 1; }; + class ACE_PlottingBoardWipe { + displayName = CSTRING(WipeBoard); + condition = QUOTE(GVAR(plottingBoard_markers) isNotEqualTo createHashMap); + statement = QUOTE(call FUNC(wipeMarkers)); + EXCEPTIONS; + showDisabled = 1; + }; + class ACE_PlottingBoardAlign { + displayName = CSTRING(AlignTo); + condition = QUOTE(GVAR(plottingBoard_Shown) > 0); + statement = ""; + EXCEPTIONS; + showDisabled = 0; + + class ACE_PlottingBoardAlignBoard { + displayName = CSTRING(PlottingBoardLabel); + condition = QUOTE(true); + statement = ""; + EXCEPTIONS; + showDisabled = 0; + + class ACE_PlottingBoardAlignBoardMaptool { + displayName = CSTRING(Name); + condition = QUOTE(GVAR(mapTool_Shown) > 0 && GVAR(plottingBoard_angle) != GVAR(mapTool_angle)); + statement = QUOTE(GVAR(plottingBoard_angle) = GVAR(mapTool_angle)); + EXCEPTIONS; + showDisabled = 0; + }; + class ACE_PlottingBoardAlignBoardUp { + displayName = CSTRING(ToUpLabel); + condition = QUOTE(GVAR(plottingBoard_angle) != 0); + statement = QUOTE(GVAR(plottingBoard_angle) = 0); + EXCEPTIONS; + showDisabled = 0; + }; + }; + class ACE_PlottingBoardAlignAcrylic { + displayName = CSTRING(PlottingBoardAcrylicLabel); + condition = QUOTE(true); + statement = ""; + EXCEPTIONS; + showDisabled = 0; + + class ACE_PlottingBoardAlignAcrylicMaptool { + displayName = CSTRING(Name); + condition = QUOTE(GVAR(mapTool_Shown) > 0 && GVAR(plottingBoard_acrylicAngle) != GVAR(mapTool_angle)); + statement = QUOTE(GVAR(plottingBoard_acrylicAngle) = GVAR(mapTool_angle)); + EXCEPTIONS; + showDisabled = 0; + }; + class ACE_PlottingBoardAlignAcrylicUp { + displayName = CSTRING(ToUpLabel); + condition = QUOTE(GVAR(plottingBoard_acrylicAngle) != 0); + statement = QUOTE(GVAR(plottingBoard_acrylicAngle) = 0); + EXCEPTIONS; + showDisabled = 0; + }; + }; + class ACE_PlottingBoardAlignRuler { + displayName = CSTRING(PlottingBoardRulerLabel); + condition = QUOTE(GVAR(plottingBoard_Shown) == 2); + statement = ""; + EXCEPTIONS; + showDisabled = 0; + + class ACE_PlottingBoardAlignRulerMaptool { + displayName = CSTRING(Name); + condition = QUOTE(GVAR(mapTool_Shown) > 0 && GVAR(plottingBoard_rulerAngle) != GVAR(mapTool_angle)); + statement = QUOTE(GVAR(plottingBoard_rulerAngle) = GVAR(mapTool_angle)); + EXCEPTIONS; + showDisabled = 0; + }; + class ACE_PlottingBoardAlignRulerUp { + displayName = CSTRING(ToUpLabel); + condition = QUOTE(GVAR(plottingBoard_rulerAngle) != 0); + statement = QUOTE(GVAR(plottingBoard_rulerAngle) = 0); + EXCEPTIONS; + showDisabled = 0; + }; + }; + }; }; }; }; @@ -69,30 +211,35 @@ class CfgVehicles { class Box_NATO_Support_F: NATO_Box_Base { class TransportItems { MACRO_ADDITEM(ACE_MapTools,12); + MACRO_ADDITEM(ACE_PlottingBoard,12); }; }; class Box_East_Support_F: EAST_Box_Base { class TransportItems { MACRO_ADDITEM(ACE_MapTools,12); + MACRO_ADDITEM(ACE_PlottingBoard,12); }; }; class Box_IND_Support_F: IND_Box_Base { class TransportItems { MACRO_ADDITEM(ACE_MapTools,12); + MACRO_ADDITEM(ACE_PlottingBoard,12); }; }; class Box_FIA_Support_F: FIA_Box_Base_F { class TransportItems { MACRO_ADDITEM(ACE_MapTools,12); + MACRO_ADDITEM(ACE_PlottingBoard,12); }; }; class ACE_Box_Misc: Box_NATO_Support_F { class TransportItems { MACRO_ADDITEM(ACE_MapTools,12); + MACRO_ADDITEM(ACE_PlottingBoard,12); }; }; }; diff --git a/addons/maptools/CfgWeapons.hpp b/addons/maptools/CfgWeapons.hpp index 62d969badb..9d2727612b 100644 --- a/addons/maptools/CfgWeapons.hpp +++ b/addons/maptools/CfgWeapons.hpp @@ -14,4 +14,17 @@ class CfgWeapons { mass = 0.2; }; }; + + class ACE_PlottingBoard: ACE_ItemCore { + displayName = CSTRING(PlottingBoard_Name); + author = ECSTRING(common,ACETeam); + descriptionShort = CSTRING(Description); + model = QPATHTOF(data\ace_MapTools.p3d); + picture = QPATHTOF(UI\plottingboard_item.paa); + scope = 2; + ACE_isTool = 1; + class ItemInfo: CBA_MiscItem_ItemInfo { + mass = 0.5; + }; + }; }; diff --git a/addons/maptools/README.md b/addons/maptools/README.md index a11f57a1a5..fe9c836c13 100644 --- a/addons/maptools/README.md +++ b/addons/maptools/README.md @@ -5,3 +5,4 @@ Adds the following map tools: - Roamer - Map drawing - Showing GPS on map +- Plotting Board diff --git a/addons/maptools/UI/plottingboard_item.paa b/addons/maptools/UI/plottingboard_item.paa new file mode 100644 index 0000000000..2d89f35514 Binary files /dev/null and b/addons/maptools/UI/plottingboard_item.paa differ diff --git a/addons/maptools/XEH_PREP.hpp b/addons/maptools/XEH_PREP.hpp index cf193698e5..ac9ed8b91f 100644 --- a/addons/maptools/XEH_PREP.hpp +++ b/addons/maptools/XEH_PREP.hpp @@ -7,3 +7,8 @@ PREP(handleMouseMove); PREP(isInsideMapTool); PREP(openMapGpsUpdate); PREP(updateMapToolMarkers); + +PREP(canUsePlottingBoard); +PREP(isInsidePlottingBoard); +PREP(handlePlottingBoardMarkers); +PREP(wipeMarkers); diff --git a/addons/maptools/XEH_postInitClient.sqf b/addons/maptools/XEH_postInitClient.sqf index eede4db015..a1b8afd08d 100644 --- a/addons/maptools/XEH_postInitClient.sqf +++ b/addons/maptools/XEH_postInitClient.sqf @@ -1,4 +1,4 @@ -// by esteldunedain +// by esteldunedain, LorenLuke #include "script_component.hpp" @@ -9,12 +9,22 @@ GVAR(mapGpsShow) = true; GVAR(mapGpsNextUpdate) = -1; GVAR(mapTool_Shown) = 0; -GVAR(mapTool_pos) = [0,0]; +GVAR(mapTool_pos) = [0, 0]; GVAR(mapTool_angle) = 0; GVAR(mapTool_isDragging) = false; GVAR(mapTool_isRotating) = false; GVAR(mapTool_moveToMouse) = true; // used to display it in center of screen when opened +GVAR(plottingBoard_Shown) = 0; +GVAR(plottingBoard_pos) = [0, 0]; +GVAR(plottingBoard_angle) = 0; +GVAR(plottingBoard_acrylicAngle) = 0; +GVAR(plottingBoard_rulerAngle) = 0; +GVAR(plottingBoard_isDragging) = false; +GVAR(plottingBoard_isRotating) = -1; +GVAR(plottingBoard_moveToMouse) = true; // used to display it in center of screen when opened +GVAR(plottingBoard_markers) = createHashMap; + //Install the event handers for the map tools on the main in-game map [{!isNull findDisplay 12}, { @@ -32,7 +42,13 @@ GVAR(mapTool_moveToMouse) = true; // used to display it in center of screen whe }; }] call CBA_fnc_addPlayerEventHandler; +addMissionEventHandler ["MarkerCreated", { + [_this, false] call FUNC(handlePlottingBoardMarkers); +}]; + +addMissionEventHandler ["MarkerDeleted", { + [[_this select 0, -1, objNull, _this select 1], true] call FUNC(handlePlottingBoardMarkers); +}]; GVAR(freeDrawingData) = []; GVAR(freedrawing) = false; - diff --git a/addons/maptools/data/plottingBoardAcrylic.paa b/addons/maptools/data/plottingBoardAcrylic.paa new file mode 100644 index 0000000000..eaa8bf2e13 Binary files /dev/null and b/addons/maptools/data/plottingBoardAcrylic.paa differ diff --git a/addons/maptools/data/plottingBoardBack.paa b/addons/maptools/data/plottingBoardBack.paa new file mode 100644 index 0000000000..79772e7cb0 Binary files /dev/null and b/addons/maptools/data/plottingBoardBack.paa differ diff --git a/addons/maptools/data/plottingBoardRuler.paa b/addons/maptools/data/plottingBoardRuler.paa new file mode 100644 index 0000000000..83160af799 Binary files /dev/null and b/addons/maptools/data/plottingBoardRuler.paa differ diff --git a/addons/maptools/functions/fnc_canUsePlottingBoard.sqf b/addons/maptools/functions/fnc_canUsePlottingBoard.sqf new file mode 100644 index 0000000000..d3394916f1 --- /dev/null +++ b/addons/maptools/functions/fnc_canUsePlottingBoard.sqf @@ -0,0 +1,22 @@ +#include "..\script_component.hpp" +/* + * Author: LorenLuke + * Returns if the plotting board can be used. + * + * Arguments: + * None + * + * Return Value: + * Plotting board can be used + * + * Example: + * call ace_maptools_fnc_canUsePlottingBoard + * + * Public: No + */ + +visibleMap && +{alive ACE_player} && +{[ACE_player, "ACE_PlottingBoard"] call EFUNC(common,hasItem)} && +{!GVAR(plottingBoard_isDragging)} && +{GVAR(plottingBoard_isRotating) == -1} diff --git a/addons/maptools/functions/fnc_handleMouseButton.sqf b/addons/maptools/functions/fnc_handleMouseButton.sqf index beee6157eb..796a84744a 100644 --- a/addons/maptools/functions/fnc_handleMouseButton.sqf +++ b/addons/maptools/functions/fnc_handleMouseButton.sqf @@ -1,17 +1,17 @@ #include "..\script_component.hpp" /* - * Author: esteldunedain + * Author: esteldunedain, LorenLuke * Handle mouse buttons. * * Arguments: - * 0: 1 if mouse down down, 0 if mouse button up + * 0: 1 if mouse down down, 0 if mouse button up * 1: Parameters of the mouse button event * * Return Value: - * true if event was handled + * True if event was handled * * Example: - * [0, [array]] call ACE_maptools_fnc_handleMouseButton + * [0, []] call ace_maptools_fnc_handleMouseButton * * Public: No */ @@ -24,20 +24,27 @@ TRACE_2("params",_dir,_params); if ((_button == 0) && {GVAR(freedrawing) || _ctrlKey}) exitWith { if (GVAR(freedrawing) && {_dir == 0}) then { GVAR(freedrawing) = false; + if (_shiftKey) exitWith { TRACE_1("using vanilla straight line",_shiftKey); }; + TRACE_2("Ending Line",GVAR(freedrawing),GVAR(freeDrawingData)); + [{ - if (allMapMarkers isEqualTo []) exitWith {}; - private _markerName = allMapMarkers select (count allMapMarkers - 1); + if (GVAR(freeDrawingData) isEqualTo []) exitWith {TRACE_1("never touched roamer",GVAR(freeDrawingData))}; + + private _allMarkers = allMapMarkers; + + if (_allMarkers isEqualTo []) exitWith {}; + + private _markerName = _allMarkers select -1; private _markerPos = getMarkerPos _markerName; - private _distanceCheck = _markerPos distance2d GVAR(drawPosStart); + private _distanceCheck = _markerPos distance2D GVAR(drawPosStart); TRACE_3("Line Drawn",_markerName,_markerPos,_distanceCheck); - if (_distanceCheck > 1) exitWith {WARNING("Wrong Marker!");}; - if ((count GVAR(freeDrawingData)) != 3) exitWith {TRACE_1("never touched roamer",GVAR(freeDrawingData));}; + if (_distanceCheck > 1) exitWith {WARNING("Wrong Marker!")}; GVAR(freeDrawingData) params ["", "_startStraightPos", "_endStraightPos"]; @@ -54,17 +61,20 @@ if ((_button == 0) && {GVAR(freedrawing) || _ctrlKey}) exitWith { TRACE_2("Starting Line",GVAR(freedrawing),GVAR(drawPosStart)); } else { GVAR(freedrawing) = false; - TRACE_1("weird - reseting",GVAR(freedrawing)); + TRACE_1("weird - resetting",GVAR(freedrawing)); }; }; - false + + false // return +}; + +// If it's not a left button event, exit +if (_button != 0) exitWith { + false // return }; private _handled = false; -// If it's not a left button event, exit -if (_button != 0) exitWith {_handled}; - // If releasing if (_dir != 1) then { if (GVAR(mapTool_isDragging) || GVAR(mapTool_isRotating)) then { @@ -72,44 +82,98 @@ if (_dir != 1) then { GVAR(mapTool_isRotating) = false; _handled = true; }; + + if (GVAR(plottingBoard_isDragging) || GVAR(plottingBoard_isRotating) > -1) then { + GVAR(plottingBoard_isDragging) = false; + GVAR(plottingBoard_isRotating) = -1; + _handled = true; + }; } else { // If clicking - if !(call FUNC(canUseMapTools)) exitWith {}; + if (call FUNC(canUseMapTools)) then { + GVAR(mapTool_isDragging) = false; + GVAR(mapTool_isRotating) = false; - // Transform mouse screen position to coordinates - private _pos = _control ctrlMapScreenToWorld [_screenPosX, _screenPosY]; - _pos set [count _pos, 0]; + // If no map tool marker then exit + if (GVAR(mapTool_Shown) != 0) then { + // Transform mouse screen position to coordinates + private _pos = _control ctrlMapScreenToWorld [_screenPosX, _screenPosY]; - GVAR(mapTool_isDragging) = false; - GVAR(mapTool_isRotating) = false; + // Check if clicking the maptool + if (_pos call FUNC(isInsideMapTool)) then { + // Store data for dragging + GVAR(mapTool_startPos) = +GVAR(mapTool_pos); + GVAR(mapTool_startDragPos) = _pos; - // If no map tool marker then exit - if (GVAR(mapTool_Shown) == 0) exitWith {}; + private _rotateKeyPressed = switch (GVAR(rotateModifierKey)) do { + case 1: {_altKey}; + case 2: {_ctrlKey}; + case 3: {_shiftKey}; + default {false}; + }; - // Check if clicking the maptool - if (_pos call FUNC(isInsideMapTool)) exitWith { - // Store data for dragging - GVAR(mapTool_startPos) = + GVAR(mapTool_pos); - GVAR(mapTool_startDragPos) = + _pos; + if (_rotateKeyPressed) then { + // Store data for rotating + GVAR(mapTool_startAngle) = GVAR(mapTool_angle); - private _rotateKeyPressed = switch (GVAR(rotateModifierKey)) do { - case (1): {_altKey}; - case (2): {_ctrlKey}; - case (3): {_shiftKey}; - default {false}; + private _pos = GVAR(mapTool_startDragPos) vectorDiff GVAR(mapTool_startPos); + GVAR(mapTool_startDragAngle) = ((_pos select 0) atan2 (_pos select 1) + 360) % 360; + + // Start rotating + GVAR(mapTool_isRotating) = true; + } else { + // Start dragging + GVAR(mapTool_isDragging) = true; + }; + + _handled = true; + }; }; + }; - if (_rotateKeyPressed) then { - // Store data for rotating - GVAR(mapTool_startAngle) = + GVAR(mapTool_angle); - GVAR(mapTool_startDragAngle) = (180 + ((GVAR(mapTool_startDragPos) select 0) - (GVAR(mapTool_startPos) select 0)) atan2 ((GVAR(mapTool_startDragPos) select 1) - (GVAR(mapTool_startPos) select 1)) mod 360); - // Start rotating - GVAR(mapTool_isRotating) = true; - } else { - // Start dragging - GVAR(mapTool_isDragging) = true; + if (call FUNC(canUsePlottingBoard)) then { + GVAR(plottingBoard_isDragging) = false; + GVAR(plottingBoard_isRotating) = -1; + + if (GVAR(plottingBoard_Shown) != 0) then { + // Transform mouse screen position to coordinates + private _pos = _control ctrlMapScreenToWorld [_screenPosX, _screenPosY]; + private _click = _pos call FUNC(isInsidePlottingBoard); + + if (_click > -1) then { + GVAR(plottingBoard_startPos) = +GVAR(plottingBoard_pos); + GVAR(plottingBoard_startDragPos) = _pos; + + private _rotateKeyPressed = switch (GVAR(rotateModifierKey)) do { + case 1: {_altKey}; + case 2: {_ctrlKey}; + case 3: {_shiftKey}; + default {false}; + }; + + if (_rotateKeyPressed) then { + // Store data for rotating + private _ang = switch (_click) do { + case 1: {GVAR(plottingBoard_acrylicAngle)}; + case 2: {GVAR(plottingBoard_rulerAngle)}; + default {GVAR(plottingBoard_angle)}; + }; + + GVAR(plottingBoard_startAngle) = _ang; + + private _pos = GVAR(plottingBoard_startDragPos) vectorDiff GVAR(plottingBoard_startPos); + GVAR(plottingBoard_startDragAngle) = ((_pos select 0) atan2 (_pos select 1) + 360) % 360; + + // Start rotating + GVAR(plottingBoard_isRotating) = _click; + } else { + // Start dragging + GVAR(plottingBoard_isDragging) = true; + }; + + _handled = true; + }; }; - _handled = true; }; }; diff --git a/addons/maptools/functions/fnc_handleMouseMove.sqf b/addons/maptools/functions/fnc_handleMouseMove.sqf index 5af4f02e30..37c6cf4d21 100644 --- a/addons/maptools/functions/fnc_handleMouseMove.sqf +++ b/addons/maptools/functions/fnc_handleMouseMove.sqf @@ -1,47 +1,75 @@ #include "..\script_component.hpp" /* - * Author: esteldunedain - * Handle mouse movement over the map tool. + * Author: esteldunedain, LorenLuke + * Handle mouse movement over the map tool and plotting board. * * Arguments: - * 0: Map Control + * 0: Map control * 1: Mouse position on screen coordinates * * Return Value: - * true if event was handled + * If the event was handled * * Example: - * [CONTROL, [0, 5, 1]] call ACE_maptools_fnc_handleMouseMove + * [CONTROL, [0, 5]] call ace_maptools_fnc_handleMouseMove * * Public: No */ -params ["_control", "_mousePosX", "_mousePosY"]; -TRACE_3("params",_control,_mousePosX,_mousePosY); +params ["_mapCtrl", "_mousePosX", "_mousePosY"]; +TRACE_3("params",_mapCtrl,_mousePosX,_mousePosY); // If have no map tools, then exit -if (((isNull ACE_player) || {!("ACE_MapTools" in (ACE_player call EFUNC(common,uniqueItems)))})) exitWith { +if (isNull ACE_player || { + private _uniqueItems = ACE_player call EFUNC(common,uniqueItems); + + !(("ACE_MapTools" in _uniqueItems) || {"ACE_PlottingBoard" in _uniqueItems}) +}) exitWith { false }; // If map tools not shown, then exit -if (GVAR(mapTool_Shown) == 0) exitWith {false}; +if (GVAR(mapTool_Shown) == 0 && {GVAR(plottingBoard_Shown) == 0}) exitWith {false}; -private _mousePosition = _control ctrlMapScreenToWorld [_mousePosX, _mousePosY]; +private _mousePosition = _mapCtrl ctrlMapScreenToWorld [_mousePosX, _mousePosY]; -// Translation +// Map tools - translation if (GVAR(mapTool_isDragging)) exitWith { - GVAR(mapTool_pos) set [0, (GVAR(mapTool_startPos) select 0) + (_mousePosition select 0) - (GVAR(mapTool_startDragPos) select 0)]; - GVAR(mapTool_pos) set [1, (GVAR(mapTool_startPos) select 1) + (_mousePosition select 1) - (GVAR(mapTool_startDragPos) select 1)]; + GVAR(mapTool_pos) = GVAR(mapTool_startPos) vectorAdd _mousePosition vectorDiff GVAR(mapTool_startDragPos); true }; -// Rotation +// Map tools - rotation if (GVAR(mapTool_isRotating)) exitWith { // Get new angle - private _angle = (180 + ((_mousePosition select 0) - (GVAR(mapTool_startPos) select 0)) atan2 ((_mousePosition select 1) - (GVAR(mapTool_startPos) select 1)) mod 360); - GVAR(mapTool_angle) = GVAR(mapTool_startAngle) + _angle - GVAR(mapTool_startDragAngle); + private _pos = _mousePosition vectorDiff GVAR(mapTool_startPos); + private _angle = (_pos select 0) atan2 (_pos select 1); + + GVAR(mapTool_angle) = ((GVAR(mapTool_startAngle) + _angle - GVAR(mapTool_startDragAngle)) % 360 + 360) % 360; + + true +}; + +// Plotting board - translation +if (GVAR(plottingBoard_isDragging)) exitWith { + GVAR(plottingBoard_pos) = GVAR(plottingBoard_startPos) vectorAdd _mousePosition vectorDiff GVAR(plottingBoard_startDragPos); + + true +}; + +// Plotting board - rotation +if (GVAR(plottingBoard_isRotating) > -1) exitWith { + // Get new angle + private _pos = _mousePosition vectorDiff GVAR(plottingBoard_startPos); + private _angle = (_pos select 0) atan2 (_pos select 1); + private _returnAngle = ((GVAR(plottingBoard_startAngle) + _angle - GVAR(plottingBoard_startDragAngle)) % 360 + 360) % 360; + + switch (GVAR(plottingBoard_isRotating)) do { + case 0: {GVAR(plottingBoard_angle) = _returnAngle}; + case 1: {GVAR(plottingBoard_acrylicAngle) = _returnAngle}; + case 2: {GVAR(plottingBoard_rulerAngle) = _returnAngle}; + }; true }; diff --git a/addons/maptools/functions/fnc_handlePlottingBoardMarkers.sqf b/addons/maptools/functions/fnc_handlePlottingBoardMarkers.sqf new file mode 100644 index 0000000000..b73bfb7dc4 --- /dev/null +++ b/addons/maptools/functions/fnc_handlePlottingBoardMarkers.sqf @@ -0,0 +1,219 @@ +#include "..\script_component.hpp" +/* + * Author: LorenLuke, johnb43 + * Handle map marker creation. + * If a marker is (partially) on the plotting board, the parts on the plotting board are attached to the plotting board + * and move with the board accordingly. + * + * Arguments: + * 0: Arguments + * - 0: Marker name + * - 1: Chat channel number + * - 2: Marker owner + * - 3: Local origin + * 1: Deleted + * + * Return Value: + * None + * + * Example: + * [CONTROL, [0, 5]] call ace_maptools_fnc_handlePlottingBoardMarkers + * + * Public: No + */ + +params ["_args", "_deleted"]; +_args params ["_marker", "_channelNumber", "_owner", "_local"]; + +if (_deleted) exitWith { + GVAR(plottingBoard_markers) deleteAt _marker; +}; + +// Do not process non-local or already processed markers, don't check if the plotting board isn't shown +if (!_local || {GVAR(plottingBoard_Shown) < 1} || {QUOTE(ADDON) in _marker}) exitWith {}; + +// Check if the channel the marker was made in can be marked on the plotting board +private _continue = true; + +if (isMultiplayer) then { + switch (GVAR(plottingBoardAllowChannelDrawing)) do { + case 0: { + if (_channelNumber != 5) then {_continue = false}; + }; + case 1: { + if !(_channelNumber in [3, 5]) then {_continue = false}; + }; + }; +}; + +if (!_continue) exitWith {}; + +private _boardPos = GVAR(plottingBoard_pos); +private _boardAng = GVAR(plottingBoard_acrylicAngle); + +private _markerPolyline = markerPolyline _marker; +private _count = count _markerPolyline; + +// If the marker is not a polyline marker +if (_count == 0) exitWith { + private _diffPos = (getMarkerPos _marker) vectorDiff _boardPos; + + // If the marker is on the acrylic or ruler of the plotting board, save it + if (vectorMagnitude _diffPos < PLOTTINGBOARD_DRAWRANGE) then { + private _relPos = [[0, 0], _diffPos, _boardAng] call CBA_fnc_vectRotate2D; + + GVAR(plottingBoard_markers) set [_marker, [_relPos, [], _boardAng, +_boardPos, 1]]; + }; +}; + +// If the marker is a polyline marker, but doesn't have enough components (happens when you ctrl-left click on the map), ignore +if (_count <= 4) exitWith {}; + +// Polyine markers (lines) +private _startPos = []; +private _endPos = []; +private _dir = []; +private _diffPos = []; + +private _a = 0; +private _b = 0; +private _c = 0; +private _t1 = nil; +private _t2 = nil; +private _delta = 0; + +private _intersectionValid1 = false; +private _intersectionValid2 = false; +private _intersectPoint1 = []; +private _intersectPoint2 = []; +private _intersectClose = []; +private _intersectFar = []; + +private _polylineIndex = 0; +private _markerArray = [[]]; +private _insideArray = []; + +for "_i" from 0 to _count - 1 - 2 step 2 do { + _startPos = [_markerPolyline select _i, _markerPolyline select (_i + 1)]; + _endPos = [_markerPolyline select (_i + 2), _markerPolyline select (_i + 3)]; + _dir = _endPos vectorDiff _startPos; + _diffPos = _startPos vectorDiff _boardPos; + + // Circle-line intersection: Check for intersections between plotting board and current piece of polyline + // https://stackoverflow.com/a/1084899 + _a = _dir vectorDotProduct _dir; + _b = 2 * (_diffPos vectorDotProduct _dir); + _c = (_diffPos vectorDotProduct _diffPos) - PLOTTINGBOARD_DRAWRANGE^2; + + _delta = _b^2 - 4 * _a * _c; + + // Stretch factors + _t1 = nil; + _t2 = nil; + + if (_delta > 0) then { + _t1 = (-_b + sqrt _delta) / (2 * _a); + _t2 = (-_b - sqrt _delta) / (2 * _a); + + // Don't look for intersection points beyond the start or end points + if (_t1 < 0 || _t1 > 1) then { + _t1 = nil; + }; + + if (_t2 < 0 || _t2 > 1) then { + _t2 = nil; + }; + }; + + // The current point is always part of a polyline + (_markerArray param [_polylineIndex, []]) append _startPos; + _insideArray set [_polylineIndex, vectorMagnitude _diffPos < PLOTTINGBOARD_DRAWRANGE]; // keep track if point is within plotting board + + _intersectionValid1 = !isNil "_t1"; + _intersectionValid2 = !isNil "_t2"; + + // If no valid intersection points, continue + if (!_intersectionValid1 && {!_intersectionValid2}) then { + continue; + }; + + // Extremely rare case if the marker is tangential to the plotting board: Ignore + if (_intersectionValid1 && {_intersectionValid2} && {_t1 == _t2}) then { + continue; + }; + + if (_intersectionValid1) then { + _intersectPoint1 = _startPos vectorAdd (_dir vectorMultiply _t1); + }; + + if (_intersectionValid2) then { + _intersectPoint2 = _startPos vectorAdd (_dir vectorMultiply _t2); + }; + + // When a marker crosses the plotting board entirely (one straight line through the plotting board) + if (_intersectionValid1 && {_intersectionValid2}) then { + // Take the closer point first + _intersectClose = [_intersectPoint1, _intersectPoint2] select (_t1 > _t2); + + // Finish previous polyline with the last point being the intersection + (_markerArray select _polylineIndex) append _intersectClose; + + // Create a new polyline, with the first point being the closest intersection + _polylineIndex = _polylineIndex + 1; + _markerArray set [_polylineIndex, _intersectClose]; + + // Now take the point further away + _intersectFar = [_intersectPoint1, _intersectPoint2] select (_t1 < _t2); + + // Make a polyline between the intersection points + (_markerArray select _polylineIndex) append _intersectClose; + (_markerArray select _polylineIndex) append _intersectFar; + _insideArray set [_polylineIndex, true]; // with 2 intersections, this part of the polyline must be inside + + // Create a new polyline, with the first point being the furthest intersection + _polylineIndex = _polylineIndex + 1; + _markerArray set [_polylineIndex, _intersectFar]; + } else { + // Only 1 intersection (either point 1 or 2, exclusive or) + if (_intersectionValid2) then { + _intersectPoint1 = _intersectPoint2; + }; + + // Finish previous polyline with the last point being the intersection + (_markerArray select _polylineIndex) append _intersectPoint1; + + // Create a new polyline, with the first point being the intersection + _polylineIndex = _polylineIndex + 1; + _markerArray set [_polylineIndex, _intersectPoint1]; + }; +}; + +// If there were no polyline intersections and the marker was not on the plotting board, don't create new markers +if (_insideArray isEqualTo [false]) exitWith {}; + +private _color = getMarkerColor _marker; +private _name = ""; +private _polylineRelative = []; +private _relPos = []; + +{ + _name = format ["%1-%2-%3", _marker, _forEachIndex, QUOTE(ADDON)]; // adding an identifier allow to check if marker was already processed + createMarkerLocal [_name, [0, 0], _channelNumber, _owner]; + _name setMarkerColorLocal _color; + _name setMarkerPolyline _x; // global marker broadcast + + // If the marker was on the plotting board, take it's unrotated position and store it + if (_insideArray select _forEachIndex) then { + _polylineRelative = []; + + for "_i" from 0 to count _x - 1 step 2 do { + _relPos = [[0, 0], [_x select _i, _x select (_i + 1)] vectorDiff _boardPos, _boardAng] call CBA_fnc_vectRotate2D; + _polylineRelative append _relPos; + }; + + GVAR(plottingBoard_markers) set [_name, [[0, 0], +_polylineRelative, _boardAng, +_boardPos, 1]]; + }; +} forEach _markerArray; + +// Delete original marker +deleteMarker _marker; diff --git a/addons/maptools/functions/fnc_isInsidePlottingBoard.sqf b/addons/maptools/functions/fnc_isInsidePlottingBoard.sqf new file mode 100644 index 0000000000..081615d1b7 --- /dev/null +++ b/addons/maptools/functions/fnc_isInsidePlottingBoard.sqf @@ -0,0 +1,54 @@ +#include "..\script_component.hpp" +/* + * Author: LorenLuke + * Return if the position is inside the map marker (to allow dragging) or not. + * + * Arguments: + * 0: x Position (in meters) + * 1: y Position (in meters) + * + * Return Value: + * Where in the plotting board it is + * -1 - Nowhere, 0 - In the Board, 1 - In the Acrylic, 2 - In the Ruler + * + * Example: + * [0, 5] call ace_maptools_fnc_isInsidePlottingBoard + * + * Public: No + */ + +if (GVAR(plottingBoard_Shown) == 0) exitWith {-1}; + +private _relPos = _this vectorDiff GVAR(plottingBoard_pos); +private _dist = vectorMagnitude _relPos; + +private _isRuler = if (GVAR(plottingBoard_Shown) == 2) then { + // If it's within these bounds, it's going to be on the ruler + if (_dist <= PLOTTINGBOARD_RULERCENTER) exitWith {true}; + + private _rulerVector = [sin GVAR(plottingBoard_rulerAngle), cos GVAR(plottingBoard_rulerAngle)]; + private _dirRightVector = [_dirVector select 1, -(_dirVector select 0)]; + private _rulerAng = acos (_rulerVector vectorCos _relPos); + + if (cos _rulerAng > 0 && {tan (_rulerAng) * _dist < PLOTTINGBOARD_RULERHALFWIDTH}) exitWith {true}; + + _dist > PLOTTINGBOARD_RULERINNERCIRCLE && {_dist < PLOTTINGBOARD_RULEROUTERCIRCLE && {abs (_rulerAng * DEGTOMILS) < PLOTTINGBOAR_RULEROUTERHALFANGLE}} +}; + +if (_isRuler) exitWith {2}; + +// If it's within 3000 meters, it's going to be on the acrylic +if (_dist < PLOTTINGBOARD_RULEROUTERCIRCLE) exitWith {1}; + +private _dirVector = [sin GVAR(plottingBoard_angle), cos GVAR(plottingBoard_angle)]; +private _dirRightVector = [_dirVector select 1, -(_dirVector select 0)]; + +// Projection of the relative position over the longitudinal axis of the map tool +private _ang = _dirVector vectorCos _relPos; +private _ang2 = _dirRightVector vectorCos _relPos; + +private _relPosAdjusted = [_ang2 * _dist / PLOTTINGBOARD_DRAWRANGE, _ang * _dist / PLOTTINGBOARD_DRAWRANGE]; + +if ((_relPosAdjusted select 0 > 0) && (_relPosAdjusted select 0 < 1) && (abs (_relPosAdjusted select 1) < 1)) exitWith {0}; + +-1 diff --git a/addons/maptools/functions/fnc_updateMapToolMarkers.sqf b/addons/maptools/functions/fnc_updateMapToolMarkers.sqf index 34642c5c68..fbad42c36e 100644 --- a/addons/maptools/functions/fnc_updateMapToolMarkers.sqf +++ b/addons/maptools/functions/fnc_updateMapToolMarkers.sqf @@ -1,56 +1,136 @@ #include "..\script_component.hpp" /* - * Author: esteldunedain - * Update the map tool markers, position, size, rotation and visibility. + * Author: esteldunedain, LorenLuke + * Update the map tool and plotting board markers. Update their position, size, rotation and visibility. * * Arguments: - * 0: The Map + * 0: Map control * * Return Value: * None * * Example: - * [CONTROL] call ACE_maptools_fnc_updateMapToolMarkers + * [CONTROL] call ace_maptools_fnc_updateMapToolMarkers * * Public: No */ -params ["_theMap"]; +params ["_mapCtrl"]; -if ((GVAR(mapTool_Shown) == 0) || {!("ACE_MapTools" in (ACE_player call EFUNC(common,uniqueItems)))}) exitWith {}; - -// open map tools in center of screen when toggled to be shown -if (GVAR(mapTool_moveToMouse)) then { - private _mousePosition = _theMap ctrlMapScreenToWorld getMousePosition; - GVAR(mapTool_pos) = _mousePosition; - GVAR(mapTool_moveToMouse) = false; // we only need to do this once after opening the map tool +if (GVAR(plottingBoard_Shown) == 0) then { + // Hide all plotting board markers when board is put away + { + if (_y select 4 != 0) then { + _x setMarkerAlpha 0; + _y set [4, 0]; + }; + } forEach GVAR(plottingBoard_markers); }; -private _rotatingTexture = ""; -private _textureWidth = 0; -if (GVAR(mapTool_Shown) == 1) then { - _rotatingTexture = QPATHTOF(data\mapToolRotatingNormal.paa); - _textureWidth = TEXTURE_WIDTH_IN_M; -} else { - _rotatingTexture = QPATHTOF(data\mapToolRotatingSmall.paa); - _textureWidth = TEXTURE_WIDTH_IN_M / 2; +if (((GVAR(mapTool_Shown) == 0) && {GVAR(plottingBoard_Shown) == 0}) || { + private _uniqueItems = ACE_player call EFUNC(common,uniqueItems); + + !(("ACE_MapTools" in _uniqueItems) || {"ACE_PlottingBoard" in _uniqueItems}) +}) exitWith {}; + +if (GVAR(plottingBoard_Shown) > 0) then { + if (GVAR(plottingBoard_moveToMouse)) then { + GVAR(plottingBoard_pos) = _mapCtrl ctrlMapScreenToWorld getMousePosition; + GVAR(plottingBoard_moveToMouse) = false; // we only need to do this once after opening the map tool + }; + + getResolution params ["_resWidth", "_resHeight", "", "", "_aspectRatio"]; + private _scaleX = 32 * PLOTTINGBOARD_TEXTUREWIDTH * CONSTANT_SCALE * (call FUNC(calculateMapScale)); + private _scaleY = _scaleX * ((_resWidth / _resHeight) / _aspectRatio); // handle bad aspect ratios + + _mapCtrl drawIcon [QPATHTOF(data\plottingBoardBack.paa), [1, 1, 1, 1], GVAR(plottingBoard_pos), _scaleX, _scaleY, GVAR(plottingBoard_angle), "", 0]; + _mapCtrl drawIcon [QPATHTOF(data\plottingBoardAcrylic.paa), [1, 1, 1, 1], GVAR(plottingBoard_pos), _scaleX, _scaleY, GVAR(plottingBoard_acrylicAngle), "", 0]; + + // Show ruler + if (GVAR(plottingBoard_Shown) == 2) then { + _mapCtrl drawIcon [QPATHTOF(data\plottingBoardRuler.paa), [1, 1, 1, 1], GVAR(plottingBoard_pos), _scaleX, _scaleY, GVAR(plottingBoard_rulerAngle), "", 0]; + }; + + private _marker = ""; + private _angle = GVAR(plottingBoard_acrylicAngle); + private _boardPos = GVAR(plottingBoard_pos); + private _count = -1; + private _rotatedPolyPos = []; + private _rotatedPos = []; + + { + _marker = _x; + _y params ["_markerPos", "_polyline", "_lastAngle", "_lastBoardPos", "_lastAlpha"]; + + // Show all plotting board markers when the board is shown + if (_lastAlpha != 1) then { + _marker setMarkerAlpha 1; + _y set [4, 1]; + }; + + // If nothing has changed, don't update marker + if (_angle == _lastAngle && {_boardPos isEqualTo _lastBoardPos}) then { + continue; + }; + + _count = count _polyline; + + // Rotate all points of polyline + if (_count >= 4) then { // polylines need at least 2 points (2 components per point) + _rotatedPolyline = []; + + for "_i" from 0 to _count - 1 step 2 do { + _rotatedPolyPos = [[0, 0], [_polyline select _i, _polyline select (_i + 1)], -_angle] call CBA_fnc_vectRotate2D; + _rotatedPolyline append (_rotatedPolyPos vectorAdd _boardPos); + }; + + _marker setMarkerPolyline _rotatedPolyline; + }; + + // Rotate marker position, regardless of marker type + _rotatedPos = [[0, 0], _markerPos, -_angle] call CBA_fnc_vectRotate2D; + + _marker setMarkerPos (_boardPos vectorAdd _rotatedPos); + + _y set [2, _angle]; + _y set [3, +_boardPos]; + } forEach GVAR(plottingBoard_markers); }; -if (GVAR(freedrawing)) then {[_theMap, _textureWidth] call FUNC(drawLinesOnRoamer);}; +if (GVAR(mapTool_Shown) > 0) then { + // Open map tools in center of screen when toggled to be shown + if (GVAR(mapTool_moveToMouse)) then { + GVAR(mapTool_pos) = _mapCtrl ctrlMapScreenToWorld getMousePosition; + GVAR(mapTool_moveToMouse) = false; // we only need to do this once after opening the map tool + }; -// Update scale of both parts -getResolution params ["_resWidth", "_resHeight", "", "", "_aspectRatio"]; -private _scaleX = 32 * _textureWidth * CONSTANT_SCALE * (call FUNC(calculateMapScale)); -private _scaleY = _scaleX; + private _rotatingTexture = ""; + private _textureWidth = 0; -// Position of the fixed part -private _xPos = GVAR(mapTool_pos) select 0; -private _yPos = (GVAR(mapTool_pos) select 1) + _textureWidth * CENTER_OFFSET_Y_PERC; + if (GVAR(mapTool_Shown) == 1) then { + _rotatingTexture = QPATHTOF(data\mapToolRotatingNormal.paa); + _textureWidth = TEXTURE_WIDTH_IN_M; + } else { + _rotatingTexture = QPATHTOF(data\mapToolRotatingSmall.paa); + _textureWidth = TEXTURE_WIDTH_IN_M / 2; + }; -_theMap drawIcon [QPATHTOF(data\mapToolFixed.paa), [1,1,1,1], [_xPos,_yPos], _scaleX, _scaleY, 0, "", 0]; + if (GVAR(freedrawing)) then { + [_mapCtrl, _textureWidth] call FUNC(drawLinesOnRoamer); + }; -// Position and rotation of the rotating part -_xPos = (GVAR(mapTool_pos) select 0) + sin(GVAR(mapTool_angle)) * _textureWidth * CENTER_OFFSET_Y_PERC; -_yPos = (GVAR(mapTool_pos) select 1) + cos(GVAR(mapTool_angle)) * _textureWidth * CENTER_OFFSET_Y_PERC; + // Update scale of both parts + getResolution params ["_resWidth", "_resHeight", "", "", "_aspectRatio"]; + private _scaleX = 32 * _textureWidth * CONSTANT_SCALE * (call FUNC(calculateMapScale)); + private _scaleY = _scaleX * ((_resWidth / _resHeight) / _aspectRatio); // handle bad aspect ratios -_theMap drawIcon [_rotatingTexture, [1,1,1,1], [_xPos,_yPos], _scaleX, _scaleY, GVAR(mapTool_angle), "", 0]; + // Position of the fixed part + private _pos = GVAR(mapTool_pos) vectorAdd [0, _textureWidth * CENTER_OFFSET_Y_PERC]; + + _mapCtrl drawIcon [QPATHTOF(data\mapToolFixed.paa), [1, 1, 1, 1], _pos, _scaleX, _scaleY, 0, "", 0]; + + // Position and rotation of the rotating part + _pos = GVAR(mapTool_pos) vectorAdd ([sin GVAR(mapTool_angle), cos GVAR(mapTool_angle)] vectorMultiply (_textureWidth * CENTER_OFFSET_Y_PERC)); + + _mapCtrl drawIcon [_rotatingTexture, [1, 1, 1, 1], _pos, _scaleX, _scaleY, GVAR(mapTool_angle), "", 0]; +}; diff --git a/addons/maptools/functions/fnc_wipeMarkers.sqf b/addons/maptools/functions/fnc_wipeMarkers.sqf new file mode 100644 index 0000000000..5131e469a3 --- /dev/null +++ b/addons/maptools/functions/fnc_wipeMarkers.sqf @@ -0,0 +1,23 @@ +#include "../script_component.hpp" +/* + * Author: LorenLuke + * Delete all markers on the plotting board. + * + * Arguments: + * None + * + * Return Value: + * None + * + * Example: + * call ace_maptools_fnc_wipeMarkers + * + * Public: No + */ + +{ + deleteMarker _x; +} forEach (keys GVAR(plottingBoard_markers)); + +// Reset list +GVAR(plottingBoard_markers) = createHashMap; diff --git a/addons/maptools/initSettings.inc.sqf b/addons/maptools/initSettings.inc.sqf index cbb8e9c1b8..5eaa778e32 100644 --- a/addons/maptools/initSettings.inc.sqf +++ b/addons/maptools/initSettings.inc.sqf @@ -15,3 +15,11 @@ private _category = format ["ACE %1", localize LSTRING(Name)]; true, 0 ] call CBA_fnc_addSetting; + +[ + QGVAR(plottingBoardAllowChannelDrawing), "LIST", + [LSTRING(allowChannelDrawing_displayName), LSTRING(allowChannelDrawing_description)], + _category, + [[0, 1], [LSTRING(allowDirectCommsOnly), LSTRING(allowDirectGroupComms)], 1], + 0 +] call CBA_fnc_addSetting; diff --git a/addons/maptools/script_component.hpp b/addons/maptools/script_component.hpp index 4710caa7a3..7c1c4757d2 100644 --- a/addons/maptools/script_component.hpp +++ b/addons/maptools/script_component.hpp @@ -16,9 +16,19 @@ #include "\z\ace\addons\main\script_macros.hpp" +#define DEGTOMILS 17.7777778 + #define TEXTURE_WIDTH_IN_M 6205 #define CENTER_OFFSET_Y_PERC 0.1606 #define CONSTANT_SCALE 0.2 #define DIST_BOTTOM_TO_CENTER_PERC -0.33 #define DIST_TOP_TO_CENTER_PERC 0.65 #define DIST_LEFT_TO_CENTER_PERC 0.30 + +#define PLOTTINGBOARD_DRAWRANGE 3000 +#define PLOTTINGBOARD_TEXTUREWIDTH 6000 +#define PLOTTINGBOARD_RULERCENTER 450 +#define PLOTTINGBOARD_RULERHALFWIDTH 100 +#define PLOTTINGBOARD_RULERINNERCIRCLE 2900 +#define PLOTTINGBOARD_RULEROUTERCIRCLE 3000 +#define PLOTTINGBOARD_RULEROUTERHALFANGLE 100 diff --git a/addons/maptools/stringtable.xml b/addons/maptools/stringtable.xml index 7ab1662bb2..c7ac9a423c 100644 --- a/addons/maptools/stringtable.xml +++ b/addons/maptools/stringtable.xml @@ -35,6 +35,12 @@ 地圖工具能夠讓你在地圖上測量距離與角度 Harita Araçları, haritadaki mesafeleri ve açıları ölçmenize olanak tanır. + + Plotting Board + + + The Plotting Board is a map tool designed for use in the directing of short range indirect fires. + Map Tools Herramientas de mapa @@ -252,5 +258,89 @@ Düz çizgiler çizmek için maptools'un kenarına çizin. Not: Silmek için orta noktada fareyle üzerine gelmeniz gerekir. Dibujar sobre el borde de las herramientas de mapa para dibujar líneas rectas. Nota: Debe situarse en el punto intermedio para eliminarla. + + Allow Plotting Board Drawing channels + + + Channels in which plotting board drawing is enabled. + + + Allow Direct Comms Only (Polylines Only) + + + Allow Direct/Group Comms (Polylines and Group Markers) + + + Plotting Board + + + Plotting Board Acrylic + + + Plotting Board Ruler + + + To Plotting Board + + + To Plotting Board Acrylic + + + To Plotting Board Ruler + + + Wipe all markers off Plotting Board + + + Show Plotting Board + + + Hide Plotting Board + + + Toggle Plotting Board Ruler + + + Align + Ausrichten + Alinear + Aligner + Allinea + Alinhar + Állítása + Wyrównaj + Srovnat + Выровнять + + + To North + Nach Norden + Al norte + Sur le nord + Con il Nord + Com o Norte + Északhoz + Do północy + Na sever + На север + + + To Compass + Am Kompass + A la brújula + Sur la boussole + Con la bussola + Com a Bússola + Iránytűhöz + Do kompasu + Ke kompasu + По компасу + + + Up + + + To Maptool + diff --git a/addons/markers/functions/fnc_onButtonClickConfirm.sqf b/addons/markers/functions/fnc_onButtonClickConfirm.sqf index 2822cd69f0..622a306e38 100644 --- a/addons/markers/functions/fnc_onButtonClickConfirm.sqf +++ b/addons/markers/functions/fnc_onButtonClickConfirm.sqf @@ -1,6 +1,6 @@ #include "..\script_component.hpp" /* - * Author: Freddo + * Author: Freddo, Daniël H., johnb43 * When the confirm button is pressed. * * Arguments: @@ -14,40 +14,100 @@ * * Public: No */ + params ["_buttonOk"]; private _display = ctrlParent _buttonOk; -private _description = _display displayctrl IDC_INSERT_MARKER; +private _description = _display displayCtrl IDC_INSERT_MARKER; private _aceTimestamp = _display displayCtrl IDC_ACE_INSERT_MARKER_TIMESTAMP; -// handle timestamp +// Handle timestamp if (cbChecked _aceTimestamp && {ACE_player call FUNC(canTimestamp)}) then { - private _time = daytime; + // Determine marker timestamp based on time settings + private _time = switch (GVAR(timestampTimezone)) do { + case 1: { + systemTime select [3] + }; + case 2: { + systemTimeUTC params ["", "", "", "_hour", "_min", "_sec", "_msec"]; - // add timestamp suffix + private _hourOffset = round GVAR(timestampUTCOffset); + _hour = _hour + _hourOffset; + + // Add or subtract minutes offset based on the negative or positive timezone + if (GVAR(timestampUTCMinutesOffset) != 0) then { + _min = if (_hourOffset < 0) then { _min - GVAR(timestampUTCMinutesOffset) } else { _min + GVAR(timestampUTCMinutesOffset) }; + + // Add/remove extra hours from minutes + _hour = _hour + floor (_min / 60); + _min = (_min % 60 + 60) % 60; // ensure that minutes are between 0 and 59 (included) + }; + + [(_hour % 24 + 24) % 24, _min, _sec, _msec] // ensure that hours are between 0 and 23 (included) + }; + default { + private _daytime = dayTime; + + private _hour = floor _daytime; + private _min = floor ((_daytime - _hour) * 60); + private _sec = floor ((((_daytime - _hour) * 60) - _min) * 60); + private _msec = floor ((((((_daytime - _hour) * 60) - _min) * 60) - _sec) * 1000); + + [_hour, _min, _sec, _msec] + }; + }; + + _time params ["_hour", "_min", "_sec", "_msec"]; + + // Add timestamp suffix private _periodPostfix = ""; + if (GVAR(timestampHourFormat) == 12) then { - if (floor _time == 0) exitWith { - _time = _time + 12; + if (_hour == 0) exitWith { + _hour = _hour + 12; _periodPostfix = " am"; }; - if (floor _time == 12) exitWith { + if (_hour == 12) exitWith { _periodPostfix = " pm"; }; - if (_time < 12) then { + if (_hour < 12) then { _periodPostfix = " am"; } else { - _time = _time - 12; + _hour = _hour - 12; _periodPostfix = " pm"; }; }; + private _format = switch (GVAR(timestampFormat)) do { + case "HH": {"%1"}; + case "HH:MM": {"%1:%2"}; + case "HH:MM:SS": {"%1:%2:%3"}; + case "HH:MM:SS:MM": { // milliseconds are displayed as 0 to 59 + _msec = [_msec * 60 / 1000, 2] call CBA_fnc_formatNumber; + + "%1:%2:%3:%4" + }; + case "HH:MM:SS.mmm": { // milliseconds are displayed as 0 to 999 + _msec = [_msec, 3] call CBA_fnc_formatNumber; + + "%1:%2:%3.%4" + }; + }; + + _time = format [ + _format, + [_hour, 2] call CBA_fnc_formatNumber, + [_min, 2] call CBA_fnc_formatNumber, + [_sec, 2] call CBA_fnc_formatNumber, + _msec + ]; + _description ctrlSetText format [ "%1 [%2%3]", ctrlText _description, - [_time, GVAR(timestampFormat)] call BIS_fnc_timeToString, + _time, _periodPostfix ]; }; diff --git a/addons/markers/initSettings.inc.sqf b/addons/markers/initSettings.inc.sqf index ce8e8e0473..65bf17e8b6 100644 --- a/addons/markers/initSettings.inc.sqf +++ b/addons/markers/initSettings.inc.sqf @@ -32,6 +32,37 @@ private _categoryName = format ["ACE %1", localize ELSTRING(map,Module_DisplayNa true ] call CBA_fnc_addSetting; +[ + QGVAR(timestampTimezone), "LIST", + [LSTRING(TimestampTimezone), LSTRING(TimestampTimezoneDescription)], + [_categoryName, LLSTRING(Module_DisplayName)], + [ + [0, 1, 2], + [LSTRING(TimestampTimezoneIngameTime), LSTRING(TimestampTimezoneSystemTime), LSTRING(TimestampTimezoneUTCTime)], + 0 + ], + true +] call CBA_fnc_addSetting; + +[ + QGVAR(timestampUTCOffset), "SLIDER", + [LSTRING(TimestampUTCOffset), LSTRING(TimestampUTCOffsetDescription)], + [_categoryName, LLSTRING(Module_DisplayName)], + [-12, 14, 0, 0], + true +] call CBA_fnc_addSetting; + +[ + QGVAR(TimestampUTCMinutesOffset), "LIST", + [LSTRING(TimestampUTCMinutesOffset), LSTRING(TimestampUTCMinutesOffsetDescription)], + [_categoryName, LLSTRING(Module_DisplayName)], + [ + [0, 15, 30, 45], + [0, 15, 30, 45], + 0 + ] +] call CBA_fnc_addSetting; + [ QGVAR(timestampHourFormat), "LIST", [LSTRING(TimestampHourFormat), LSTRING(TimestampHourFormatDescription)], @@ -48,7 +79,8 @@ private _formatDescription = [ LLSTRING(TimestampFormatDescription1), LLSTRING(TimestampFormatDescription2), LLSTRING(TimestampFormatDescription3), - LLSTRING(TimestampFormatDescription4) + LLSTRING(TimestampFormatDescription4), + LLSTRING(TimestampFormatDescription5) ] joinString endl; [ @@ -56,8 +88,8 @@ private _formatDescription = [ [LSTRING(timestampFormat), _formatDescription], [_categoryName, LLSTRING(Module_DisplayName)], [ - ["HH", "HH:MM", "HH:MM:SS", "HH:MM:SS:MM"], - ["HH", "HH:MM", "HH:MM:SS", "HH:MM:SS:MM"], + ["HH", "HH:MM", "HH:MM:SS", "HH:MM:SS:MM", "HH:MM:SS.mmm"], + ["HH", "HH:MM", "HH:MM:SS", "HH:MM:SS:MM", "HH:MM:SS.mmm"], 1 ] ] call CBA_fnc_addSetting; diff --git a/addons/markers/stringtable.xml b/addons/markers/stringtable.xml index f15394e3b5..b4a772e171 100644 --- a/addons/markers/stringtable.xml +++ b/addons/markers/stringtable.xml @@ -239,6 +239,105 @@ 시계 필요함 Relógio necessário + + Time Zone + Часовой пояс + Fuseau horaire + 時間帯 + Zona horaria + Strefa czasowa + Zeitzone + 时区 + 시간대 + + + Changes the time zone for the timestamp + Измените часовой пояс для метки времени + Modifiez le fuseau horaire pour l'horodatage + タイムスタンプの時間帯を変更します + Cambie la zona horaria para la marca de tiempo + Zmień strefę czasową dla znaczników czasu + Ändern Sie die Zeitzone für den Zeitstempel + 更改时间戳的时区 + 타임스탬프의 시간대를 변경하십시오 + + + In-game Time + Время в игре + Heure de jeu + ゲーム内時刻 + Hora del juego + Czas gry + Ingame-Zeit + 游戏内时间 + 게임 시간 + + + System Time + Системное время + Heure système + システム時刻 + Hora del sistema + Czas systemowy + Systemzeit + 系统时间 + 시스템 시간 + + + UTC Time + Время UTC + Heure UTC + UTC時刻 + Hora UTC + Czas UTC + UTC-Zeit + UTC时间 + UTC 시간 + + + UTC Offset + Смещение UTC + Décalage UTC + UTCオフセット + Desplazamiento UTC + Przesunięcie UTC + UTC-Verschiebung + UTC偏移量 + UTC 오프셋 + + + Changes the time offset for the UTC timestamp + Измените смещение времени для метки времени UTC + Modifier le décalage horaire pour l'horodatage UTC + UTCタイムスタンプの時差を変更する + Cambiar el desplazamiento horario para la marca de tiempo UTC + Zmień przesunięcie czasu dla sygnatury czasowej UTC + Ändere die Zeitverschiebung für den UTC-Zeitstempel + 更改UTC时间戳的时间偏移量 + UTC 타임 스탬프의 시간 오프셋을 변경하십시오 + + + UTC Minutes Offset + UTC Минутное Смещение + Décalage des minutes UTC + UTC分オフセット + Desplazamiento de minutos UTC + Przesunięcie minut UTC + UTC-Minutenversatz + UTC分钟偏移量 + UTC 분 오프셋 + + + Change the minute offset for the UTC timestamp + Изменить минутное смещение для времени UTC + Modifier le décalage des minutes pour l'horodatage UTC + UTCタイムスタンプの分差を変更する + Cambiar el desplazamiento de minutos para la marca de tiempo UTC + Zmień przesunięcie minut dla sygnatury czasowej UTC + Ändere den Minutenversatz für den UTC-Zeitstempel + 更改UTC时间戳的分钟偏移量 + UTC 타임 스탬프의 분 오프셋을 변경하십시오 + Timestamp Format Формат времени @@ -305,17 +404,16 @@ SS - Segundos - "MM" - Milliseconds - "МС" - Миллисекунда - "MM" - Millisecondes - "MM" - ミリ秒 - "MM" - Milisegundos - "MM" - Milisekundy - "MS" - Milisekunden - "MS" - Millisecondi - "MS"—毫秒 - "MS" - 밀리초 - MM - Milisegundos + "MM" - Milliseconds (from 0 to 59) + "MM" - Millisecondes (de 0 à 59) + "MS" - Milisekunden (von 0 bis 59) + "MS" - Milissegundos (de 0 a 59) + + + "mmm" - Milliseconds (from 0 to 999) + "mmm" - Millisecondes (de 0 à 999) + "mmm" - Milisekunden (von 0 bis 999) + "mmm" - Milissegundos (de 0 a 999) Timestamp Hour Format diff --git a/addons/overheating/ACE_Arsenal_Stats.hpp b/addons/overheating/ACE_Arsenal_Stats.hpp new file mode 100644 index 0000000000..ef1e497c55 --- /dev/null +++ b/addons/overheating/ACE_Arsenal_Stats.hpp @@ -0,0 +1,21 @@ +class EGVAR(arsenal,stats) { + class statBase; + class ACE_allowSwapBarrel: statBase { + scope = 2; + priority = -1; + stats[] = {QGVAR(allowSwapBarrel)}; + displayName = CSTRING(statBarrelType); + showText = 1; + textStatement = QUOTE(call FUNC(statTextStatement_allowSwapBarrel)); + tabs[] = {{0,1}, {}}; + }; + class ACE_boltType: statBase { + scope = 2; + priority = -1.1; + stats[] = {QGVAR(closedBolt)}; + displayName = CSTRING(statBoltType); + showText = 1; + textStatement = QUOTE(call FUNC(statTextStatement_boltType)); + tabs[] = {{0,1}, {}}; + }; +}; diff --git a/addons/overheating/XEH_PREP.hpp b/addons/overheating/XEH_PREP.hpp index 865a049569..91d345914d 100644 --- a/addons/overheating/XEH_PREP.hpp +++ b/addons/overheating/XEH_PREP.hpp @@ -25,6 +25,8 @@ PREP(overheat); PREP(sendSpareBarrelsTemperaturesHint); PREP(setAmmoTemperature); PREP(setWeaponTemperature); +PREP(statTextStatement_boltType); +PREP(statTextStatement_allowSwapBarrel); PREP(swapBarrel); PREP(swapBarrelAssistant); PREP(swapBarrelCallback); diff --git a/addons/overheating/config.cpp b/addons/overheating/config.cpp index f04238fd10..4d8fb34cc3 100644 --- a/addons/overheating/config.cpp +++ b/addons/overheating/config.cpp @@ -26,6 +26,8 @@ class CfgPatches { #include "ACE_Settings.hpp" +#include "ACE_Arsenal_Stats.hpp" + class CfgMovesBasic { class ManActions { GVAR(GestureMountMuzzle) = QGVAR(GestureMountMuzzle); diff --git a/addons/overheating/functions/fnc_statTextStatement_allowSwapBarrel.sqf b/addons/overheating/functions/fnc_statTextStatement_allowSwapBarrel.sqf new file mode 100644 index 0000000000..9713023c17 --- /dev/null +++ b/addons/overheating/functions/fnc_statTextStatement_allowSwapBarrel.sqf @@ -0,0 +1,21 @@ +#include "..\script_component.hpp" +/* + * Author: drofseh + * Barrel Type statement. + * + * Arguments: + * 0: Not used + * 1: Item config path + * + * Return Value: + * Stat Text + * + * Public: No +*/ + +params ["", "_config"]; +TRACE_1("statTextStatement_allowSwapBarrel",_config); + +if ((getNumber (_config >> QGVAR(allowSwapBarrel))) == 1) exitWith {LLSTRING(statBarrelType_removeable)}; + +LLSTRING(statBarrelType_nonRemoveable) diff --git a/addons/overheating/functions/fnc_statTextStatement_boltType.sqf b/addons/overheating/functions/fnc_statTextStatement_boltType.sqf new file mode 100644 index 0000000000..67967496c7 --- /dev/null +++ b/addons/overheating/functions/fnc_statTextStatement_boltType.sqf @@ -0,0 +1,21 @@ +#include "..\script_component.hpp" +/* + * Author: drofseh + * Bolt Type statement. + * + * Arguments: + * 0: Not used + * 1: Item config path + * + * Return Value: + * Stat Text + * + * Public: No +*/ + +params ["", "_config"]; +TRACE_1("statTextStatement_boltType",_config); + +if ((getNumber (_config >> QGVAR(closedBolt))) == 1) exitWith {LLSTRING(statBoltType_closedBolt)}; + +LLSTRING(statBoltType_openBolt) diff --git a/addons/overheating/stringtable.xml b/addons/overheating/stringtable.xml index 2c6249be2f..678a9a746a 100644 --- a/addons/overheating/stringtable.xml +++ b/addons/overheating/stringtable.xml @@ -875,5 +875,23 @@ 备用枪管温度非常热 備用槍管溫度超級熱 + + Bolt Type + + + Open Bolt + + + Closed Bolt + + + Barrel Type + + + Non-Removeable + + + Quick Change + diff --git a/addons/rangecard/functions/fnc_calculateRangeCard.sqf b/addons/rangecard/functions/fnc_calculateRangeCard.sqf index 179437eb58..d2347b8494 100644 --- a/addons/rangecard/functions/fnc_calculateRangeCard.sqf +++ b/addons/rangecard/functions/fnc_calculateRangeCard.sqf @@ -49,7 +49,8 @@ private _bulletSpeed = 0; private _gravity = [0, sin(_scopeBaseAngle) * -GRAVITY, cos(_scopeBaseAngle) * -GRAVITY]; private _deltaT = 1 / _simSteps; private _speedOfSound = 0; -if (missionNamespace getVariable [QEGVAR(advanced_ballistics,enabled), false]) then { +private _isABenabled = missionNamespace getVariable [QEGVAR(advanced_ballistics,enabled), false]; +if (_isABenabled) then { _speedOfSound = _temperature call EFUNC(weather,calculateSpeedOfSound); }; @@ -68,7 +69,7 @@ if (_useABConfig) then { }; private _airFrictionCoef = 1; -if (!_useABConfig && (missionNamespace getVariable [QEGVAR(advanced_ballistics,enabled), false])) then { +if (!_useABConfig && _isABenabled) then { private _airDensity = [_temperature, _barometricPressure, _relativeHumidity] call EFUNC(weather,calculateAirDensity); _airFrictionCoef = _airDensity / 1.22498; }; diff --git a/addons/rangecard/functions/fnc_updateRangeCard.sqf b/addons/rangecard/functions/fnc_updateRangeCard.sqf index d7acd46486..e8fb0f9c6c 100644 --- a/addons/rangecard/functions/fnc_updateRangeCard.sqf +++ b/addons/rangecard/functions/fnc_updateRangeCard.sqf @@ -107,12 +107,17 @@ private _transonicStabilityCoef = _ammoConfig select 4; private _dragModel = _ammoConfig select 5; private _atmosphereModel = _ammoConfig select 8; -private _useABConfig = (missionNamespace getVariable [QEGVAR(advanced_ballistics,enabled), false]); -if (_bc == 0) then { - _useABConfig = false; -}; +private _isABenabled = (missionNamespace getVariable [QEGVAR(advanced_ballistics,enabled), false]) && (_bc != 0); +private _useBarrelLengthInfluence = ( + _isABenabled && + {missionNamespace getVariable [QEGVAR(advanced_ballistics,barrelLengthInfluenceEnabled), false]} +); +private _useAmmoTemperatureInfluence = ( + _isABenabled && + {missionNamespace getVariable [QEGVAR(advanced_ballistics,ammoTemperatureEnabled), false]} +); -if (_barrelLength > 0 && _useABConfig) then { +if (_barrelLength > 0 && _useBarrelLengthInfluence) then { _muzzleVelocity = [_barrelLength, _ammoConfig select 10, _ammoConfig select 11, 0] call EFUNC(advanced_ballistics,calculateBarrelLengthVelocityShift); } else { private _initSpeed = getNumber (configFile >> "CfgMagazines" >> _magazineClass >> "initSpeed"); @@ -128,7 +133,7 @@ if (_barrelLength > 0 && _useABConfig) then { ctrlSetText [770000, format["%1'' - %2 gr (%3)", round((_ammoConfig select 1) * 39.3700787) / 1000, round((_ammoConfig select 3) * 15.4323584), _ammoClass]]; if (_barrelLength > 0) then { - if (_useABConfig && _barrelTwist > 0) then { + if (_useBarrelLengthInfluence && _barrelTwist > 0) then { ctrlSetText [770002, format["Barrel: %1'' 1:%2'' twist", round(2 * _barrelLength * 0.0393700787) / 2, round(_barrelTwist * 0.0393700787)]]; } else { ctrlSetText [770002, format["Barrel: %1''", round(2 * _barrelLength * 0.0393700787) / 2]]; @@ -136,7 +141,7 @@ if (_barrelLength > 0) then { }; lnbAddRow [770100, ["4mps Wind(MRADs)", "1mps LEAD(MRADs)"]]; -if (missionNamespace getVariable [QEGVAR(advanced_ballistics,enabled), false]) then { +if (_isABenabled) then { lnbAddRow [770100, ["Air/Ammo Temp", "Air/Ammo Temp"]]; lnbAddRow [770200, ["-15°C", " -5°C", " 5°C", " 10°C", " 15°C", " 20°C", " 25°C", " 30°C", " 35°C"]]; @@ -145,7 +150,7 @@ if (missionNamespace getVariable [QEGVAR(advanced_ballistics,enabled), false]) t ctrlSetText [77003, format["%1m ZERO", round(_zeroRange)]]; -if (missionNamespace getVariable [QEGVAR(advanced_ballistics,enabled), false]) then { +if (_isABenabled) then { ctrlSetText [770001, format["Drop Tables for B.P.: %1mb; Corrected for MVV at Air/Ammo Temperatures -15-35 °C", round(EGVAR(scopes,zeroReferenceBarometricPressure) * 100) / 100]]; ctrlSetText [77004 , format["B.P.: %1mb", round(EGVAR(scopes,zeroReferenceBarometricPressure) * 100) / 100]]; } else { @@ -153,30 +158,30 @@ if (missionNamespace getVariable [QEGVAR(advanced_ballistics,enabled), false]) t ctrlSetText [77004 , ""]; }; -private _cacheEntry = missionNamespace getVariable format[QGVAR(%1_%2_%3_%4_%5), _zeroRange, _boreHeight, _ammoClass, _weaponClass, missionNamespace getVariable [QEGVAR(advanced_ballistics,enabled), false]]; -if (isNil {_cacheEntry}) then { - private _scopeBaseAngle = if (!_useABConfig) then { +private _cacheEntry = missionNamespace getVariable format [QGVAR(%1_%2_%3_%4_%5_%6_%7), _zeroRange, _boreHeight, _ammoClass, _weaponClass, _isABenabled, _useBarrelLengthInfluence, _useAmmoTemperatureInfluence]; +if (isNil "_cacheEntry") then { + private _scopeBaseAngle = if (!_isABenabled) then { private _zeroAngle = "ace_advanced_ballistics" callExtension format ["calcZero:%1:%2:%3:%4", _zeroRange, _muzzleVelocity, _airFriction, _boreHeight]; (parseNumber _zeroAngle) } else { private _zeroAngle = "ace_advanced_ballistics" callExtension format ["calcZeroAB:%1:%2:%3:%4:%5:%6:%7:%8:%9", _zeroRange, _muzzleVelocity, _boreHeight, EGVAR(scopes,zeroReferenceTemperature), EGVAR(scopes,zeroReferenceBarometricPressure), EGVAR(scopes,zeroReferenceHumidity), _bc, _dragModel, _atmosphereModel]; (parseNumber _zeroAngle) }; - if (missionNamespace getVariable [QEGVAR(advanced_ballistics,enabled), false] && missionNamespace getVariable [QEGVAR(advanced_ballistics,ammoTemperatureEnabled), false]) then { + if (_useAmmoTemperatureInfluence) then { { private _mvShift = [_ammoConfig select 9, _x] call EFUNC(advanced_ballistics,calculateAmmoTemperatureVelocityShift); private _mv = _muzzleVelocity + _mvShift; - [_scopeBaseAngle,_boreHeight,_airFriction,_mv,_x,EGVAR(scopes,zeroReferenceBarometricPressure),EGVAR(scopes,zeroReferenceHumidity),200,4,1,GVAR(rangeCardEndRange),_bc,_dragModel,_atmosphereModel,_transonicStabilityCoef,_forEachIndex,_useABConfig] call FUNC(calculateRangeCard); + [_scopeBaseAngle,_boreHeight,_airFriction,_mv,_x,EGVAR(scopes,zeroReferenceBarometricPressure),EGVAR(scopes,zeroReferenceHumidity),200,4,1,GVAR(rangeCardEndRange),_bc,_dragModel,_atmosphereModel,_transonicStabilityCoef,_forEachIndex,_isABenabled] call FUNC(calculateRangeCard); } forEach [-15, -5, 5, 10, 15, 20, 25, 30, 35]; } else { - [_scopeBaseAngle,_boreHeight,_airFriction,_muzzleVelocity,15,EGVAR(scopes,zeroReferenceBarometricPressure),EGVAR(scopes,zeroReferenceHumidity),200,4,1,GVAR(rangeCardEndRange),_bc,_dragModel,_atmosphereModel,_transonicStabilityCoef,3,_useABConfig] call FUNC(calculateRangeCard); + [_scopeBaseAngle,_boreHeight,_airFriction,_muzzleVelocity,15,EGVAR(scopes,zeroReferenceBarometricPressure),EGVAR(scopes,zeroReferenceHumidity),200,4,1,GVAR(rangeCardEndRange),_bc,_dragModel,_atmosphereModel,_transonicStabilityCoef,3,_isABenabled] call FUNC(calculateRangeCard); }; for "_i" from 0 to 9 do { GVAR(lastValidRow) pushBack count (GVAR(rangeCardDataElevation) select _i); while {count (GVAR(rangeCardDataElevation) select _i) < 50} do { - if (missionNamespace getVariable [QEGVAR(advanced_ballistics,enabled), false]) then { + if (_isABenabled) then { (GVAR(rangeCardDataElevation) select _i) pushBack "###"; (GVAR(rangeCardDataWindage) select _i) pushBack "##"; (GVAR(rangeCardDataLead) select _i) pushBack "##"; @@ -188,7 +193,7 @@ if (isNil {_cacheEntry}) then { }; }; - missionNamespace setVariable [format[QGVAR(%1_%2_%3_%4_%5), _zeroRange, _boreHeight, _ammoClass, _weaponClass, missionNamespace getVariable [QEGVAR(advanced_ballistics,enabled), false]], [GVAR(rangeCardDataElevation), GVAR(rangeCardDataWindage), GVAR(rangeCardDataLead), GVAR(rangeCardDataMVs), GVAR(lastValidRow)]]; + missionNamespace setVariable [format [QGVAR(%1_%2_%3_%4_%5_%6_%7), _zeroRange, _boreHeight, _ammoClass, _weaponClass, _isABenabled, _useBarrelLengthInfluence, _useAmmoTemperatureInfluence], [GVAR(rangeCardDataElevation), GVAR(rangeCardDataWindage), GVAR(rangeCardDataLead), GVAR(rangeCardDataMVs), GVAR(lastValidRow)]]; } else { GVAR(rangeCardDataElevation) = _cacheEntry select 0; GVAR(rangeCardDataWindage) = _cacheEntry select 1; diff --git a/addons/xm157/functions/fnc_ballistics_getData.sqf b/addons/xm157/functions/fnc_ballistics_getData.sqf index 928936c51e..828db8e4ae 100644 --- a/addons/xm157/functions/fnc_ballistics_getData.sqf +++ b/addons/xm157/functions/fnc_ballistics_getData.sqf @@ -24,7 +24,6 @@ private _key = format ["weaponInfoCache-%1-%2-%3",_weaponClass,_magazineClass,_a private _weaponInfo = GVAR(data) getOrDefault [_key, []]; if ((_weaponInfo isEqualTo []) && {_magazineClass != ""}) then { TRACE_3("new weapon/mag",_weaponClass,_magazineClass,_ammoClass); - private _useABConfig = (missionNamespace getVariable [QEGVAR(advanced_ballistics,enabled), false]); private _zeroRange = 100; private _boreHeight = [_unit, 0] call EFUNC(scopes,getBoreHeight); @@ -35,8 +34,14 @@ if ((_weaponInfo isEqualTo []) && {_magazineClass != ""}) then { _weaponConfig params ["_barrelTwist", "_twistDirection", "_barrelLength"]; private _bc = if (_ballisticCoefficients isEqualTo []) then { 0 } else { _ballisticCoefficients # 0 }; + private _useAB = ( + missionNamespace getVariable [QEGVAR(advanced_ballistics,enabled), false] && + {missionNamespace getVariable [QEGVAR(advanced_ballistics,barrelLengthInfluenceEnabled), false]} && + {_bc != 0} + ); + // Get Muzzle Velocity - private _muzzleVelocity = if (_barrelLength > 0 && _useABConfig && {_bc != 0}) then { + private _muzzleVelocity = if (_barrelLength > 0 && _useAB) then { [_barrelLength, _muzzleVelocityTable, _barrelLengthTable, 0] call EFUNC(advanced_ballistics,calculateBarrelLengthVelocityShift) } else { private _initSpeed = getNumber (configFile >> "CfgMagazines" >> _magazineClass >> "initSpeed"); diff --git a/docs/wiki/feature/maptools.md b/docs/wiki/feature/maptools.md index c6f4903780..4145eb1a18 100644 --- a/docs/wiki/feature/maptools.md +++ b/docs/wiki/feature/maptools.md @@ -21,7 +21,10 @@ This adds the possibility to draw accurate lines on the map screen. ### 1.2 Map tools This adds map tools that can be used to measure distances between two points or bearings on the map. -### 1.3 GPS on map +### 1.3 Plotting Board +This adds a plotting board that can be used to aid in the rapid usage and adjustment of short-ranged indirect fires, as well as quick measurements of directions and distances between points, and general land-navigation. + +### 1.4 GPS on map If you are equipped with a vanilla GPS it will be shown on the map. (You don't need the `Map Tools` item in your inventory for this.) ## 2. Usage @@ -39,3 +42,15 @@ If you are equipped with a vanilla GPS it will be shown on the map. (You don't n - Press ALT + LMB to start the line, left click again to end it. - To delete a line press Del around the center of the line. - Note that you can change the color of the lines by clicking on one of the coloured column on top of the screen (While the map is opened). + +### 2.3 Using the plotting board +- To use map tools the `Plotting Board` item is required. +- Open the map M (Arma 3 default key bind `Map`). +- Press the self interaction key Ctrl + ⊞ Win (ACE3 default key bind `Self Interaction Key`). +- Select `Show Plotting Board`. +- Note that you can drag the Plotting Board around with LMB and rotate the different parts of the Plotting Board, each independently from each other, with Ctrl + LMB. + +### 2.4 Drawing lines +- You can draw lines on the plotting board. +- These lines are removed from the map once the plotting board is hidden, but they restored when the plotting board is shown again. +- These lines are moved along with the plotting board when the plotting board is dragged.