diff --git a/SQF/dayz_code/actions/modular_build.sqf b/SQF/dayz_code/actions/modular_build.sqf index bb9ec8438..5ece107eb 100644 --- a/SQF/dayz_code/actions/modular_build.sqf +++ b/SQF/dayz_code/actions/modular_build.sqf @@ -1,4 +1,4 @@ -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // DayZ Base Building // @@ -6,12 +6,11 @@ // Updated by: Victor the Cleaner // Date: August 2021 // -// - Players may no longer build objects outside the plot radius. -// This is calculated using the object's pivot point, not its geometry nor its bounding box. -// - Players may move an object any distance or height within the plot boundary/sphere. -// Therefore, DZE_buildMaxMoveDistance and DZE_buildMaxHeightDistance are now obsolete. +// - Players may not build objects outside the plot boundary. +// - Distance from plot pole is calculated using the model center, not its ATL/ASL value. +// - Players may move an object any distance or height within the plot boundary. // -// - Players may move a small distance outside the plot radius, provided the object remains inside. Enabled with DZE_PlotOzone. Default: 10 meters. +// - Players may move a small distance outside the plot boundary, provided the object remains inside. Enabled with DZE_PlotOzone. Default: 10 meters. // - A line of helpers now appears through the plot's vertical axis to aid line-of-sight to the pole. Enabled with DZE_AxialHelper. // This may be useful for nearby plots that are grouped together, or where their radii overlap. // @@ -20,48 +19,37 @@ // Crouch-walking or slow-walking will not cancel the build. // Pressing ESC will also cancel without opening the exit menu. // -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// - A collision check is performed only if the held object is collidable, i.e. it has no ghost preview. +// Now the player will let go of the object, preventing them from being knocked across the map. +// +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// if (dayz_actionInProgress) exitWith {localize "str_epoch_player_40" call dayz_rollingMessages;}; // Building already in progress. dayz_actionInProgress = true; /////////////////////////////////////////////////////////////////////////////////////////////////// // -// _misc_checks function +// Miscellaneous Checks // /////////////////////////////////////////////////////////////////////////////////////////////////// + local _misc_checks = { - local _onLadder = (getNumber (configFile >> "CfgMovesMaleSdr" >> "States" >> (animationState player) >> "onLadder")) == 1; + local _inCombat = (player getVariable["combattimeout",0] >= diag_tickTime); local _inVehicle = ((vehicle player) != player); - local _ctime = (player getVariable["combattimeout",0] >= diag_tickTime); + local _onLadder = (getNumber (configFile >> "CfgMovesMaleSdr" >> "States" >> (animationState player) >> "onLadder")) == 1; local _reason = ""; call { + if (_inCombat) exitWith {_reason = "str_epoch_player_43"}; // You cannot build while in combat. + if (_inVehicle) exitWith {_reason = "str_epoch_player_42"}; // You cannot build while in a vehicle. if (_onLadder) exitWith {_reason = "str_player_21"}; // You cannot do this while you are on a ladder. if (dayz_isSwimming) exitWith {_reason = "str_player_26"}; // You cannot do this while you are in the water. - if (_inVehicle) exitWith {_reason = "str_epoch_player_42"}; // You cannot build while in a vehicle. - if (_ctime) exitWith {_reason = "str_epoch_player_43"}; // You cannot build while in combat. }; _reason; }; -/////////////////////////////////////////////////////////////////////////////////////////////////// -// -// _cancel_build function -// -/////////////////////////////////////////////////////////////////////////////////////////////////// -_cancel_build = { - - _isOk = false; - _cancel = true; - detach _object; - deleteVehicle _object; - detach _objectHelper; - deleteVehicle _objectHelper; -}; - /////////////////////////////////////////////////////////////////////////////////////////////////// local _reason = call _misc_checks; @@ -71,130 +59,836 @@ if (_reason != "") exitWith { localize _reason call dayz_rollingMessages; }; -/////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Initialise -// -/////////////////////////////////////////////////////////////////////////////////////////////////// -DZE_buildItem = _this; // buildable classname, e.g. "full_cinder_wall_kit" - -DZE_5 = false; // space bar - build -DZE_4 = false; // Q Key - rotate left -DZE_6 = false; // E Key - rotate right -DZE_F = false; // F Key - hold/release - -DZE_Q = false; // PgUp -DZE_Z = false; // PgDn -DZE_Q_alt = false; // Alt + PgUp -DZE_Z_alt = false; // Alt + PgDn -DZE_Q_ctrl = false; // Ctrl + PgUp -DZE_Z_ctrl = false; // Ctrl + PgDn - -DZE_cancelBuilding = false; // ESC Key -DZE_updateVec = false; // vector changed via build_vectors.sqf -DZE_memDir = 0; // object rotation (Q/E keys) -DZE_memForBack = 0; // pitch forward/back -DZE_memLeftRight = 0; // bank left/right - call gear_ui_init; closeDialog 1; +DZE_buildItem = _this; // CfgMagazines class, e.g. "full_cinder_wall_kit" local _playerPos = [player] call FNC_GetPos; -local _buildCheck = [_playerPos, DZE_buildItem, true] call dze_buildChecks; // input: [player position, kit classname, toolcheck] // return value: [_canBuild, _isPole, _nearestPole]; -local _canBuild = (_buildCheck select 0); // bool - -/////////////////////////////////////////////////////////////////////////////////////////////////// +local _buildCheck = [_playerPos, DZE_buildItem, true] call dze_buildChecks; // input: [player position, class, toolcheck] // return value: [_canBuild, _isPole, _nearestPole]; +local _canBuild = _buildCheck select 0; // bool if (_canBuild) then { - player playActionNow "Crouch"; // prevent instant cancel from moving too fast + /////////////////////////////////////////////////////////////////////////////////////////// + // + // Initialise + // + /////////////////////////////////////////////////////////////////////////////////////////// - local _isPole = (_buildCheck select 1); // bool - local _nearestPole = (_buildCheck select 2); // object or objNull + player playActionNow "PlayerCrouch"; // prevent instant cancel from moving too fast + + local _isPole = _buildCheck select 1; // bool + local _nearestPole = _buildCheck select 2; // object or objNull local _findNearestPole = []; - local _inRadius = (!_isPole && !(isNull _nearestPole)); // bool // is the player attempting to place an object within a plot boundary? - local _radius = DZE_PlotPole select 0; // max distance from plot pole an object may be built - local _minDistance = DZE_PlotPole select 1; // minimum distance between plot poles - local _diameter = _radius * 2; // plot diameter - local _ozone = _radius + DZE_PlotOzone; // zone outside plot radius where a player may stand before placing an object, but the object must remain within the plot radius - local _isBHL = (DZE_BuildHeightLimit > 0); // is build height limit enabled - local _BHL = DZE_BuildHeightLimit; // short name - local _sphere = "Sign_sphere100cm_EP1"; // helper classname + local _inRadius = (!_isPole && !(isNull _nearestPole)); // is the player attempting to place an object within a plot boundary? + local _radius = DZE_PlotPole select 0; // max distance from plot pole an object may be built + local _minDistance = DZE_PlotPole select 1; // minimum distance between plot poles + local _diameter = _radius * 2; // plot diameter + local _ozone = _radius + DZE_PlotOzone; // zone outside plot radius where a player may stand before placing an object, but the object must remain within the plot radius + local _isBHL = DZE_BuildHeightLimit > 0; // is build height limit enabled + local _BHL = DZE_BuildHeightLimit; // short name + local _sphere = "Sign_sphere100cm_EP1"; // axial helper class name local _polePos = []; - local _pArray = []; + local _pArray = []; // used to store axial helper objects local _isAdmin = dayz_playerUID in DZE_PlotManagementAdmins; - local _dir = 0; - local _vector = []; + + local _dir = 0; // object direction + local _vector = []; // object vector local _object = objNull; - local _objectPos = []; - local _zPos = 0; local _objectHelper = objNull; local _objectHelperPos = []; - local _walk = "amovpercmwlk"; // animation state substrings - local _run = "amovpercmrun"; - local _sprint = "meva"; + local _noColor = [0,"#(argb,8,8,3)color(0,0,0,0,ca)"]; // transparent helper color + + local _boundingCenter = []; // model center offset + local _modelOffset = 0; // model center Z offset + local _modelBase = objNull; // Attached to logical base of model + local _modelBasePos = []; + local _modelCenter = objNull; // Attached to logical center of model + local _modelCenterPos = []; + local _modelCenterPrevPos = []; + useModelCenter = 0; // use visual center of model instead of base when determining transforms and ground collision + modelSelect = objNull; + + local _tooLow = false; // warn player object cannot go below ground + local _isOnWater = false; // object placed in or above water + local _heightASL = 0; // test if object is below max sea level + + local _minHeight = 0; // used for nounderground + local _startPos = []; + local _playerASL = 0; + local _startHeight = 0; // raise object up on creation if necessary + + local _vectoringEnabled = false; + local _snappingEnabled = false; + local _snapList = []; // helper panel array of valid snapping points + local _snapTabMax = 0; // hotkey index + local _snapSelMax = 0; // snapping point index + local _points = []; // snapping point array + + local _distFromPlot = "-"; + if (!isNull _nearestPole) then {_distFromPlot = "0";}; + + local _degreeCount = count DZE_vectorDegrees; // index count of degree array + local _refreshDist = 0; // init snap auto-refresh distance + local _OFF = localize "STR_EPOCH_ACTION_SNAP_OFF"; + DZR_snapRefresh = false; // notify snap functions an auto-refresh is in progress + skipUpdates = false; // skip over multiple snapActionState updates from single keypress + distanceFromPlot = 0; // realtime updates on snap building panel + DZE_snapRadius = 0; + DZE_SnapTabIdx = 0; // tab hotkey array index + DZE_SnapSelIdx = -2; // array of object snapping points + DZE_nowBuilding = false; // notify snap build so it can clean up helpers + snapGizmosNearby = []; + + local _walk = "amovpercmwlk"; // animation state substrings + local _run = "amovpercmrun"; + local _sprint = "meva"; // evasive manoeuvre + local _collisionCheck = false; + local _hitSfx = [[0,1,3,5],4] call fn_shuffleArray; // used in collision check + local _hitIdx = 0; + local _hitCount = 4; + local _scream = "z_scream_"; // male Sfx class + local _scrSfx = [[0,1,2,3],4] call fn_shuffleArray; // male scream index + local _scrIdx = 0; + local _scrCount = 4; + local _isWoman = getText(configFile >> "cfgVehicles" >> (typeOf player) >> "TextSingular") == "Woman"; + if (_isWoman) then { + _scream = _scream + "w_"; // female Sfx class + _scrSfx = [[1,3,4],3] call fn_shuffleArray; // female scream index + _scrCount = 3; + }; - helperDetach = false; local _isOk = true; local _cancel = false; + DZE_Q = false; // PgUp + DZE_Z = false; // PgDn + DZE_Q_alt = false; // Alt-PgUp + DZE_Z_alt = false; // Alt-PgDn + DZE_Q_ctrl = false; // Ctrl-PgUp + DZE_Z_ctrl = false; // Ctrl-PgDn + DZE_5 = false; // space bar - build + DZE_4 = false; // Q Key - rotate left + DZE_6 = false; // E Key - rotate right + DZE_F = false; // F Key - hold/release + DZE_P = false; // P Key - show/hide plot pole + DZE_T = false; // T Key - terrain align + DZE_L = false; // L Key - local mode + DZE_H = false; // H Key - hide/unhide panel + DZE_LEFT = false; // Left Arrow Key - Bank Left + DZE_RIGHT = false; // Right Arrow Key - Bank Right + DZE_UP = false; // Up Arrow Key - Pitch Forward + DZE_DOWN = false; // Down Arrow Key - Pitch Back + DZE_MINUS = false; // Minus Key - Decrease Degrees + DZE_PLUS = false; // Plus Key (=+) - Increase Degrees + DZE_BACK = false; // Backspace Key - reset vectors + DZE_TAB = false; // Tab Key - Next Snap + DZE_TAB_SHIFT = false; // Shift-Tab - Prev Snap + DZE_cancelBuilding = false; // ESC Key + /////////////////////////////////////////////////////////////////////////////////////////// // - // create vertical helpers at the plot center to which the object will be assigned + // Axial Helper // /////////////////////////////////////////////////////////////////////////////////////////// - if (_inRadius && DZE_AxialHelper) then { + local _axial_helper = { // Create vertical helpers at the plot center to which the object will be assigned + + if !(DZE_AxialHelper && _inRadius) exitWith {}; local _density = 4; // minimum distance between helpers local _segments = floor (_diameter / _density); // total helpers = _segments + 1 local _segments = _segments - (_segments % 2); // get even number of segments local _spacing = -(_diameter / _segments); // actual distance between helpers local _zenith = _radius; - local _polePos = [_nearestPole] call FNC_GetPos; + local _color = [DZE_plotGreen, DZE_plotGreen]; + if (_isBHL && DZE_HeightLimitColor) then { + _color set [1, DZE_plotRed]; // red helpers above building height limit + }; if (!isNil "PP_Marks") then { _zenith = _zenith + _spacing; // subtract upper and lower positions }; - for "_i" from _zenith to -_zenith step _spacing do { // start at the zenith - local _offset = [0,0,_i]; // decrement towards nadir - local _pos = _nearestPole modelToWorld _offset; // translate to world - local _pZ = (_pos select 2); // get Z height - - if (_pZ < 0) exitWith {}; // too low - - local _texture = DZE_plotGreen; // green - if (_isBHL && (_pZ > _BHL)) then { // too high - _texture = DZE_plotRed; // red - }; + for "_i" from _zenith to -_zenith step _spacing do { // decrement from zenith to nadir local _helper = _sphere createVehicleLocal [0,0,0]; // create helper - _helper setObjectTexture _texture; // add colour + _helper setObjectTexture _noColor; - _pos set [2,(_polePos select 2) + _i]; // align to radial height + _helper attachTo [_nearestPole, [0,0,_i]]; // pre-position + uiSleep 0.001; + local _height = ([_helper] call FNC_GetPos) select 2; // test ATL/ASL + + if (_height < 0) exitWith {deleteVehicle _helper}; // prevent placing below ground/sea + + local _texture = _color select (_height > _BHL); // red if too high + _helper setObjectTexture _texture; // or green - if (surfaceIsWater _pos) then { // adjust for land and sea - _helper setPosASL _pos; - } else { - _helper setPosATL _pos; - }; _pArray = _pArray + [_helper]; // record helper }; }; + + /////////////////////////////////////////////////////////////////////////////////////////// + // + // Setup Object + // + /////////////////////////////////////////////////////////////////////////////////////////// + + local _setup_object = { + + DZE_updateVec = false; // trigger update on true + DZE_memDir = 0; // object rotation (Q/E keys) + DZE_memForBack = 0; // pitch forward/back + DZE_memLeftRight = 0; // bank left/right + + _tooLow = false; + + _dir = getDir player; + _objectHelper setDir _dir; + + modelSelect = [_modelBase, _modelCenter] select useModelCenter; // substitute base with center if required + _visualBase = [0,0,[_modelOffset,0] select useModelCenter]; // update visual position + _object attachTo [_objectHelper, _visualBase]; // align to helper + + _minHeight = _offset select 2; // min Z height allowed + _startPos = player modelToWorld _offset; // AGL = ATL/ASLW (variable height over ocean waves) + _playerASL = (getPosASL player) select 2; // player ASL height + + if (surfaceIsWater _startPos) then { // if object in/over water + _startPos set [2, _playerASL + _minHeight]; // match player height + min height + }; + _startHeight = _startPos select 2; // current start height + + if ((_isAllowedUnderGround == 0) && (_startHeight < _minHeight)) then { // if too low + _startPos set [2, _minHeight]; // raise up + }; + + if (surfaceIsWater _startPos) then { // adjust for land and sea + _objectHelper setPosASL _startPos; + } else { + _objectHelper setPosATL _startPos; + }; + + _objectHelper attachTo [player]; + helperDetach = false; + + uiSleep 0.05; + + _modelCenterPrevPos = getPosASL _modelCenter; // used in snap auto-refresh check + }; + + /////////////////////////////////////////////////////////////////////////////////////////// + // + // Player is Stopped + // + /////////////////////////////////////////////////////////////////////////////////////////// + + _isStopped = { + local _stopped = ["mstp", animationState player] call fnc_inString; + + _stopped; + }; + + /////////////////////////////////////////////////////////////////////////////////////////// + // + // Move Check + // + /////////////////////////////////////////////////////////////////////////////////////////// + + _moveCheck = { + // if the object is attached and the player is still moving in some way + local _isMoving = (!helperDetach && (speed player != 0 || {!(call _isStopped)})); + + _isMoving; + }; + + /////////////////////////////////////////////////////////////////////////////////////////// + // + // Change Height + // + /////////////////////////////////////////////////////////////////////////////////////////// + + local _change_height = { + if (call _moveCheck) exitWith {}; + + local _distance = _this; // height vector + local _level = ""; // ground/sea level message + local _notify = false; // notify player that object is too low + + local _zHeightOld = 0; + local _zHeightNew = 0; + local _terrainOld = true; + local _terrainNew = true; + + local _helperPos = getPosASL _objectHelper; // helper ASL + local _helperPosZ = _helperPos select 2; // helper ASL height + local _modelOldASL = getPosASL modelSelect; // old pos + local _modelOldASLZ = _modelOldASL select 2; // old pos height + + local _vector1 = []; // safe vector + local _vector2 = []; // terrain intersect + + if (DZE_LOCAL_MODE) then { + _vector1 = vectorUp _object; // local vectorUp + } else { + _vector1 = [0,0,1]; // world vectorUp + }; + {_vector1 set [_forEachIndex, _x * _distance];} forEach _vector1; // vector distance + + local _modelNewASL = [_modelOldASL, _vector1] call BIS_fnc_vectorAdd; // new pos + local _modelNewASLZ = _modelNewASL select 2; // new pos height + local _modelNewATL = ASLToATL _modelNewASL; // new ATL + local _modelNewATLZ = _modelNewATL select 2; // new ATL height + + if (surfaceIsWater _modelNewASL) then { + _terrainNew = false; + _zHeightNew = _modelNewASLZ; // sea + } else { + _zHeightNew = _modelNewATLZ; // terrain + }; + + /////////////////////////////////////////////////////////////////////////////////////////////////////// + // + // Object is below ground/sea + // + /////////////////////////////////////////////////////////////////////////////////////////////////////// + + if ((_isAllowedUnderGround == 0) && {_zHeightNew < _minHeight}) then { // if new height too low, raise up along vector + + if (!_tooLow && {_distance != 0}) then { + _tooLow = true; + _notify = true; // notify once only while building + }; + + local _modelOldATL = ASLToATL _modelOldASL; // old ATL + local _modelOldATLZ = _modelOldATL select 2; // old ATL height + + if (surfaceIsWater _modelOldASL) then { + _terrainOld = false; + _zHeightOld = _modelOldASLZ; // sea + } else { + _zHeightOld = _modelOldATLZ; // terrain + }; + + if (_zHeightOld < _minHeight) exitWith { // if both points are too low + _helperPos set [2, _helperPosZ + _minHeight - _zHeightOld]; // set to ground/sea level + }; + + /////////////////////////////////////////////////////////////////////////////////////////////// + // + // Simulate terrainIntersectAtASL from Arma 3 + // + /////////////////////////////////////////////////////////////////////////////////////////////// + + _modelNewASL = +_modelOldASL; // start pos + _modelNewASLZ = _modelNewASL select 2; // current height + local _vector2 = +_vector1; // vector distance + + local _water = !_terrainOld && !_terrainNew; // water only + local _terrain = _terrainOld && _terrainNew; // land only + local _coast = (_terrainOld || _terrainNew) && !(_terrainOld && _terrainNew);// coastline (water and land) + local _heightZ = _minHeight + 0.001; // prevent asymptote curve + + local _count = 0; // loop counter + + if (_terrain) then {_modelNewASLZ = (ASLToATL _modelNewASL) select 2;}; // ignore loop if already touching ground/sea level + + while {_modelNewASLZ > _heightZ} do { // while object is above ground/sea level + + {_vector2 set [_forEachIndex, _x * 0.5]} forEach _vector2; // get half the previous vector distance + + local _newPos = [_modelNewASL, _vector2] call BIS_fnc_vectorAdd; // test start position + half previous distance + local _newPosZ = _newPos select 2; // ASL height + + call { + if (_water) exitWith {}; // transform is entirely over water + + local _ATLZ = (ASLToATL _newPos) select 2; // prepare for terrain check + + if (_terrain) exitWith { // transform is entirely over land + _newPosZ = _ATLZ; // ATL + }; + if (_coast) exitWith { // transform crosses threshold between land and water + if !(surfaceIsWater _newPos) then { + _newPosZ = _ATLZ; // ATL + }; + }; + }; + if (_newPosZ > _minHeight) then { // test new height + _modelNewASL = +_newPos; // move closer + _modelNewASLZ = _newPosZ; // update height + _helperPos = [_helperPos, _vector2] call BIS_fnc_vectorAdd; // vector aggregate + }; + _count = _count + 1; + if (_count > 15) exitWith {}; // prevent endless looping (usually resolves within 10 loops) + }; + + /////////////////////////////////////////////////////////////////////////////////////////////////////// + + } else { + _helperPos = [_helperPos, _vector1] call BIS_fnc_vectorAdd; // safe vector + }; + + /////////////////////////////////////////////////////////////////////////////////////////////////////// + + if (!helperDetach) then {detach _objectHelper;}; + + _objectHelper setPosASL _helperPos; + uiSleep 0.04; + + if (!helperDetach) then {_objectHelper attachTo [player];}; // re-attach helper + + _modelNewASL = getPosASL modelSelect; + _modelNewASLZ = _modelNewASL select 2; // get current ASL height + + if (surfaceIsWater _modelNewASL) then { + _level = localize "STR_EPOCH_PLAYER_NO_UGROUND_03"; // object cannot be placed below sea level + } else { + _level = localize "STR_EPOCH_PLAYER_NO_UGROUND_02"; // object cannot be placed below ground + }; + + local _floodArea = (_distance == 0 && {_modelNewASLZ < DZE_maxSeaLevel}); // No need to check surfaceIsWater. The surface area will change significantly near coastlines. + + if (DZE_buildOnWater && _floodArea) then { + localize "STR_EPOCH_BEWARE_RISING_TIDE" call dayz_rollingMessages; // Warn only when player tries to place the object so this message does not coincide with the next one. + }; + + if (_notify) then { + format[localize "STR_EPOCH_PLAYER_NO_UGROUND_01", _text, _level] call dayz_rollingMessages; + }; + + [true] call _update; // update with collision check + }; + + /////////////////////////////////////////////////////////////////////////////////////////// + // + // Degree Change + // + /////////////////////////////////////////////////////////////////////////////////////////// + + local _degrees = { + local _index = DZE_vectorDegrees find DZE_curDegree; // get current degree index + _index = (_index + _this + _degreeCount) % _degreeCount; // get adjacent value + DZE_curDegree = DZE_vectorDegrees select _index; // update degrees + + if (_vectoringEnabled) then { + degreeActionState = localize "STR_EPOCH_VECTORS_CLOSE"; + [1,1] call fnc_degreeActionCleanup; // keep action menu in sync + }; + }; + + /////////////////////////////////////////////////////////////////////////////////////////// + // + // Rotate Object + // + /////////////////////////////////////////////////////////////////////////////////////////// + + local _rotate = { + if (call _moveCheck) exitWith {}; + + DZE_memDir = DZE_memDir + _this; + [true] call _update; + }; + + /////////////////////////////////////////////////////////////////////////////////////////// + // + // Bank Object + // + /////////////////////////////////////////////////////////////////////////////////////////// + + local _bank = { + if (!_vectoringEnabled || (call _moveCheck)) exitWith {}; + + DZE_memLeftRight = DZE_memLeftRight + _this; + DZE_updateVec = true; + }; + + /////////////////////////////////////////////////////////////////////////////////////////// + // + // Pitch Object + // + /////////////////////////////////////////////////////////////////////////////////////////// + + local _pitch = { + if (!_vectoringEnabled || (call _moveCheck)) exitWith {}; + + DZE_memForBack = DZE_memForBack + _this; + DZE_updateVec = true; + }; + + /////////////////////////////////////////////////////////////////////////////////////////// + // + // Vector Object + // + /////////////////////////////////////////////////////////////////////////////////////////// + + local _vector = { + vectorActionState = localize "STR_EPOCH_VECTORS_CLOSE"; + [1,1] call fnc_vectorActionCleanup; + + [true] call _update; + DZE_updateVec = false; + }; + + /////////////////////////////////////////////////////////////////////////////////////////// + // + // Hold / Release Object + // + /////////////////////////////////////////////////////////////////////////////////////////// + + local _hold_release = { + if (!r_drag_sqf && !r_player_unconscious) then { + + if (helperDetach) then { + + call _attach; + } else { + call _detach; + }; + }; + }; + + /////////////////////////////////////////////////////////////////////////////////////////// + // + // Attach Object + // + /////////////////////////////////////////////////////////////////////////////////////////// + + local _attach = { + detach _objectHelper; + _objectHelper attachTo [player]; + DZE_memDir = DZE_memDir - (getDir player); + + [false] call _update; // update, without collision check + + helperDetach = false; + }; /////////////////////////////////////////////////////////////////////////////////////////// // - // get config data + // Detach Object + // + /////////////////////////////////////////////////////////////////////////////////////////// + + local _detach = { + detach _objectHelper; // release object + + DZE_memDir = getDir _objectHelper; // get current Z rotation + + if (DZE_memLeftRight == 0) then { // if object is not banked (left/right) + + local _absMem = abs DZE_memForBack; + + if ((_absMem >= 90) && (_absMem < 270)) then { // but is pitched upside down (forward/back) + DZE_memDir = DZE_memDir + 180; // prevent flipping around X axis + }; + } else { // if object is banked (left/right) + if (DZE_memForBack != 0) then { // and object is pitched (forward/back) + + local _dX = (sin DZE_memForBack) * (sin DZE_memLeftRight); // Pre-calculate Z rotation + local _dY = cos DZE_memForBack; + local _delta = _dX atan2 _dY; // convert to degrees on world axis + + DZE_memDir = DZE_memDir - _delta; // prevent Z rotation bug on sloping terrain + }; + }; + if (!_collisionCheck) then { + [false] call _update; // update rotations, without collision check + }; + + _objectHelper setVelocity [0,0,0]; + + helperDetach = true; + }; + + /////////////////////////////////////////////////////////////////////////////////////////// + // + // Update Pitch/Bank/Yaw + // + /////////////////////////////////////////////////////////////////////////////////////////// + + local _update = { + DZE_memForBack = DZE_memForBack % 360; // clamp rotation angles + DZE_memLeftRight = DZE_memLeftRight % 360; + DZE_memDir = DZE_memDir % 360; + + [_objectHelper, [DZE_memForBack, DZE_memLeftRight, DZE_memDir]] call fnc_SetPitchBankYaw; + + if ((_this select 0) && !helperDetach) then { + call _collision_check; + }; + }; + + /////////////////////////////////////////////////////////////////////////////////////////// + // + // Collision Check + // + /////////////////////////////////////////////////////////////////////////////////////////// + // + // Small movements can occur from turning on the spot, or the beginning phase of a + // new animation state. Therefore the _tolerance variable is the distance in meters + // the player can move before a collision check is considered valid. + // + /////////////////////////////////////////////////////////////////////////////////////////// + + local _collision_check = { + + local _prevPos = _playerPos; // previous position + uiSleep 0.05; // allow time for player to be knocked back + _playerPos = getPosASL player; // current position + + local _tolerance = 0.01; // movement threshold + local _distance = _prevPos distance _playerPos; // distance moved + local _hasMoved = _distance > _tolerance; // collision check valid + + if (_hasMoved && (call _isStopped)) then { // if player is hit + _collisionCheck = true; // disable updates + call _detach; // let go of object + _collisionCheck = false; // enable updates + + _objectHelper setPosASL _objectHelperPos; // reposition object + + player say ["z_hit_" + str(_hitSfx select _hitIdx), 10]; // hit sound (local only) + uiSleep 0.2; // wait + player say [_scream + str(_scrSfx select _scrIdx), 70]; // scream sound (local only) + + [player, 70, true, _playerPos] spawn player_alertZombies; // check if zombies hear the scream + _hitIdx = (_hitIdx + 1) % _hitCount; // cue next hit + _scrIdx = (_scrIdx + 1) % _scrCount; // cue next scream + + format[localize "STR_EPOCH_PLAYER_COLLISION_01", _text] call dayz_rollingMessages; + }; + + }; + + /////////////////////////////////////////////////////////////////////////////////////////// + // + // Plot boundary + // + /////////////////////////////////////////////////////////////////////////////////////////// + + local _plot_pole = { + if (_inRadius) then { + [_nearestPole] call PlotPreview; + }; + }; + + /////////////////////////////////////////////////////////////////////////////////////////// + // + // Terrain Align + // + /////////////////////////////////////////////////////////////////////////////////////////// + + local _terrain = { + if (call _moveCheck) exitWith {}; + + skipUpdates = true; // prevent temporary values updating on the snap panel + + detach _objectHelper; + detach _object; + + local _pos = getPosATL modelSelect; + _objectHelper setPosATL _pos; // the helper must align with the model base or center + _object attachTo [_objectHelper]; + + _pos = [_objectHelper] call FNC_GetPos; // ATL/ASL + local _vector = surfaceNormal _pos; // get terrain vector + _pos set [2, _minHeight]; // set default height to touch the ground/water + + if (surfaceIsWater _pos) then { // place on ground or at sea level + _objectHelper setPosASL _pos; + _objectHelperPos = _pos; + } else { + _objectHelper setPosATL _pos; + _objectHelperPos = ATLToASL _pos; + }; + + if (_vectoringEnabled) then { + _objectHelper setVectorUp _vector; // align + }; + + local _pb = _objectHelper call BIS_fnc_getPitchBank; // not fully accurate according to the wiki, but for this purpose it will do + DZE_memForBack = _pb select 0; // pitch + DZE_memLeftRight= _pb select 1; // bank + DZE_memDir = getDir _objectHelper; // rotation + + if (DZE_memForBack != 0) then { + + local _dX = (sin DZE_memForBack) * (sin DZE_memLeftRight); // Pre-calculate Z rotation + local _dY = cos DZE_memForBack; + local _delta = _dX atan2 _dY; // convert to degrees on world axis + + DZE_memDir = DZE_memDir - _delta; // prevent Z rotation bug on sloping terrain + }; + + if (!helperDetach) then { + call _attach; // reattach if object was attached prior + }; + [true] call _update; // update with collision check + + call _resetMenu; // snap and vector settings must yield to the terrain vector + + skipUpdates = false; + }; + + /////////////////////////////////////////////////////////////////////////////////////////// + // + // Snap Next / Prev + // + /////////////////////////////////////////////////////////////////////////////////////////// + + local _snap = { + if (_snappingEnabled) then { + + DZE_SnapTabIdx = DZE_SnapTabIdx + _this; + DZE_SnapSelIdx = DZE_SnapSelIdx + _this; + + call { + if (DZE_SnapTabIdx < 0) exitWith { // selection was Shift-Tabbed left and looped around + DZE_SnapTabIdx = _snapTabMax; + DZE_SnapSelIdx = _snapSelMax; + }; + if (DZE_SnapTabIdx > _snapTabMax) exitWith { // selection was Tabbed right and looped around + DZE_SnapTabIdx = 0; + DZE_SnapSelIdx = -2; + }; + }; + + local _snapParams = []; + + local _ON = [localize "STR_EPOCH_ACTION_SNAP_ON", _object, _classname, _objectHelper]; // Click ON to Turn OFF + local _OFF = [localize "STR_EPOCH_ACTION_SNAP_OFF", _object, _classname, _objectHelper]; // From OFF to ON/Auto + local _MANUAL = [localize "STR_EPOCH_ACTION_SNAP_POINT_MANUAL", _object, _classname, _objectHelper]; // From Manual to ON/Auto + local _AUTO = ["Auto", _object, _classname, _objectHelper]; // From Auto to Manual (nothing selected) + local _SELECT = ["Selected", _object, _classname, _objectHelper, DZE_SnapSelIdx]; // Select snapping point + + // + // keep action menu in sync with hotkeys + // + call { + if (DZE_SnapTabIdx == 0) exitWith { + _snapParams = [_ON]; // ON to OFF + }; + if (DZE_SnapTabIdx == 1) exitWith { + if (_this == 1) then { + _snapParams = [_OFF]; // From OFF to ON/Auto + } else { + _snapParams = [_MANUAL]; // Shift-Tab left from Manual to ON/Auto + }; + }; + if (DZE_SnapTabIdx > 1) exitWith { + if (_this == -1) then { + if (DZE_SnapTabIdx == _snapTabMax) then { + _snapParams = [_OFF, _AUTO]; // Shift-Tab left and loop back to select the last snapping point + }; + } else { + if (DZE_SnapSelIdx == 0) then { + _snapParams = [_AUTO]; // Tab right from Auto to first snapping point + }; + }; + _snapParams = _snapParams + [_SELECT]; // Tab/Shift-Tab through snapping points + }; + }; + + skipUpdates = true; // prevent display of multiple snapping states on helper panel until complete + + { + ["", "", "", _x] spawn snap_build; + uiSleep 0.04; + } count _snapParams; + + skipUpdates = false; // re-enable snapping state display + }; + }; + + /////////////////////////////////////////////////////////////////////////////////////////// + // + // Snap Auto-Refresh + // + /////////////////////////////////////////////////////////////////////////////////////////// + + local _refresh = { + if !(_snappingEnabled && DZE_snapAutoRefresh && {snapActionState != _OFF}) exitWith {}; + + _modelCenterPos = getPosASL _modelCenter; + local _objectMove = _modelCenterPrevPos distance _modelCenterPos; + + if (_objectMove > _refreshDist) then { + _modelCenterPrevPos = _modelCenterPos; + + DZR_snapRefresh = true; // suspend fnc_snapDistanceCheck + + [_object] call fnc_initSnapPointsNearby; // create new snap point radius + + DZR_snapRefresh = false; // resume fnc_snapDistanceCheck + }; + }; + + /////////////////////////////////////////////////////////////////////////////////////////// + // + // Reset functions + // + /////////////////////////////////////////////////////////////////////////////////////////// + + local _resetMenu = { + if (_snappingEnabled) then { + snapActionState = localize "STR_EPOCH_ACTION_SNAP_OFF"; // close Snap menu + [1,0,0, _object, _classname, _objectHelper, _points] call fnc_snapActionCleanup; + call fnc_initSnapPointsCleanup; + }; + if (_vectoringEnabled) then { + vectorActionState = localize "STR_EPOCH_VECTORS_OPEN"; // close Vectors menu + [1,0] call fnc_vectorActionCleanup; + + degreeActionState = localize "STR_EPOCH_VECTORS_OPEN"; // close Degrees menu + [1,0] call fnc_degreeActionCleanup; + }; + }; + + local _reset = { + detach _objectHelper; + detach _object; + + call _setup_object; // reset object + call _resetMenu; + DZE_LOCAL_MODE = false; + DZE_HIDE_PANEL = false; + }; + + /////////////////////////////////////////////////////////////////////////////////////////// + // + // Cancel Build + // + /////////////////////////////////////////////////////////////////////////////////////////// + + _cancel_build = { + + DZE_cancelBuilding = true; // snapping point cleanup + + _isOk = false; + _cancel = true; + + deleteVehicle _modelBase; + deleteVehicle _modelCenter; + deleteVehicle _object; + deleteVehicle _objectHelper; + }; + + /////////////////////////////////////////////////////////////////////////////////////////// + // + // Get config data // /////////////////////////////////////////////////////////////////////////////////////////// local _classname = getText (configFile >> "CfgMagazines" >> DZE_buildItem >> "ItemActions" >> "Build" >> "create"); // e.g. "CinderWall_DZ" - local _classnametmp = _classname; - + local _classnameBuild = _classname; local _text = getText (configFile >> "CfgVehicles" >> _classname >> "displayName"); // e.g. "Cinder Wall Full" local _ghost = getText (configFile >> "CfgVehicles" >> _classname >> "ghostpreview"); // e.g. "CinderWall_Preview_DZ" local _lockable = getNumber (configFile >> "CfgVehicles" >> _classname >> "lockable"); // defaults to 0 - local _offset = getArray (configFile >> "CfgVehicles" >> _classname >> "offset"); + + local _offset = getArray (configFile >> "CfgVehicles" >> _classname >> "offset"); + if (count _offset == 0) then { + _offset = [0, abs (((boundingBox _object) select 0) select 1), 0]; + }; local _isAllowedUnderGround = 1; @@ -202,184 +896,159 @@ if (_canBuild) then { _isAllowedUnderGround = getNumber(configFile >> "CfgVehicles" >> _classname >> "nounderground"); }; + local _requireplot = 1; + + if (isNumber (configFile >> "CfgVehicles" >> _classname >> "requireplot")) then { + _requireplot = getNumber(configFile >> "CfgVehicles" >> _classname >> "requireplot"); + }; + + useModelCenter = 0; // global + + if (isNumber (configFile >> "CfgVehicles" >> _classname >> "useModelCenter")) then { + useModelCenter = getNumber(configFile >> "CfgVehicles" >> _classname >> "useModelCenter"); + }; + if (_ghost != "") then { _classname = _ghost; }; /////////////////////////////////////////////////////////////////////////////////////////// // - // create ghost object and helper during pre-build + // Create ghost preview and object helpers + // + /////////////////////////////////////////////////////////////////////////////////////////// + // + // Some models have a non-zero boundingCenter, i.e. values other than [0,0,0], so this + // needs to be offset from their ATL/ASL position. They use only the Z value returned + // from "boundingCenter _object" for this purpose. Furthermore, these objects require + // a second offset position value saved to the database to correctly handle vectoring + // and rotation data. + // + // To improve accuracy, we can attach a dummy object to the base of the model, and to + // the model center, and use these position values when performing calculations. + // + // This setup works for any model type. // /////////////////////////////////////////////////////////////////////////////////////////// _object = _classname createVehicle [0,0,0]; - if (count _offset == 0) then { - _offset = [0, abs (((boundingBox _object) select 0) select 1), 0]; - }; + _boundingCenter = boundingCenter _object; // model center offset + _modelOffset = _boundingCenter select 2; // Z offset - _objectHelper = "Sign_sphere10cm_EP1" createVehicle [0,0,0]; - _objectHelper setobjecttexture [0,"#(argb,8,8,3)color(0,1,0,1,ca)"]; // solid helper sphere + _modelBase = "Sign_sphere10cm_EP1" createVehicleLocal [0,0,0]; + _modelBase setObjectTexture _noColor; + _modelBase attachTo [_object, [0,0,-_modelOffset]]; // getPosATL/ASL position - local _tempPos = player modelToWorld _offset; + _modelCenter = "Sign_sphere25cm_EP1" createVehicleLocal [0,0,0]; + _modelCenter setObjectTexture _noColor; + _modelCenter attachTo [_object, [0,0,0]]; // model center - _dir = getDir player; - _objectHelper setDir _dir; - _object setDir _dir; + _objectHelper = "Sign_sphere10cm_EP1" createVehicle [0,0,0]; // main helper during building + _objectHelper setObjectTexture _noColor; - if ((_isAllowedUnderGround == 0) && ((_tempPos select 2) < (_offset select 2))) then { - - _tempPos set [2, _offset select 2]; // raise up to ground/sea level - }; - - if (surfaceIsWater _tempPos) then { - _objectHelper setPosASL _tempPos; - _object setPosASL _tempPos; - } else { - _objectHelper setPosATL _tempPos; - _object setPosATL _tempPos; - }; - - _objectHelper attachTo [player]; - _object attachTo [_objectHelper]; + /////////////////////////////////////////////////////////////////////////////////////////// + // + // Initialize Snapping and Vectoring + // + /////////////////////////////////////////////////////////////////////////////////////////// if (isClass (configFile >> "SnapBuilding" >> _classname)) then { - ["","","",["Init", _object, _classname, _objectHelper]] spawn snap_build; + _points = getArray(configFile >> "SnapBuilding" >> _classname >> "points"); // get all snapping points + + _snapList = [localize "STR_EPOCH_ACTION_SNAP_OFF", localize "STR_EPOCH_ACTION_SNAP_POINT_AUTO"]; // initialize + {_snapList = _snapList + [_x select 3];} count _points; // append + + _snappingEnabled = true; + snapActionState = ""; + snapActionStateSelect = ""; + _snapTabMax = (count _snapList) - 1; + _snapSelMax = (count _points) - 1; + + ["", "", "", ["Init", _object, _classname, _objectHelper]] spawn snap_build; + + local _box = boundingBox _object; + local _b0 = _box select 0; // lower diagonal + local _b1 = _box select 1; // upper diagonal + local _bx = abs (_b0 select 0) + abs (_b1 select 0); + local _by = abs (_b0 select 1) + abs (_b1 select 1); + local _bz = abs (_b0 select 2) + abs (_b1 select 2); + local _diag = sqrt (_bx^2 + _by^2 + _bz^2); // get diagonal of boundingBox + + DZE_snapRadius = _diag * 0.5 + 9; // 9 is half the largest bounding box diagonal (rounded up) of the largest snappable objects in the game; currently the Land_WarfareBarrier10xTall_DZ and the MetalContainer2D_DZ. + _refreshDist = DZE_snapRadius * 0.5; // distance object moves before the snap auto-refresh triggers }; + if !(DZE_buildItem in DZE_noRotate) then { + _vectoringEnabled = true; ["","","",["Init", "Init", 0]] spawn build_vectors; }; - /////////////////////////////////////////////////////////////////////////////////////////// - // - // _change_height function - // - /////////////////////////////////////////////////////////////////////////////////////////// - - local _change_height = { - local _delta = _this; - local _helperPos = [_objectHelper] call FNC_GetPos; - - if (!helperDetach) then { - detach _objectHelper; - }; - - _helperPos set [2,((_helperPos select 2) + _delta)]; - - if (_isAllowedUnderGround == 0 && (_helperPos select 2) < (_offset select 2)) then { - _helperPos set [2, _offset select 2]; - }; - - if (surfaceIsWater _helperPos) then { - _objectHelper setPosASL _helperPos; - } else { - _objectHelper setPosATL _helperPos; - }; - if (!helperDetach) then { - _objectHelper attachTo [player]; - }; - - [_objectHelper, [DZE_memForBack, DZE_memLeftRight, DZE_memDir]] call fnc_SetPitchBankYaw; - - _helperPos = _objectHelper modelToWorld [0,0,0]; - local _objectPos = _object modelToWorld [0,0,0]; - }; - - /////////////////////////////////////////////////////////////////////////////////////////// - // - // _rotate function - // - /////////////////////////////////////////////////////////////////////////////////////////// - - local _rotate = { - local _degrees = [45, DZE_curDegree] select DZE_dirWithDegrees; - - if (_this == -1) then {_degrees = -_degrees}; // Q Key - DZE_memDir = DZE_memDir + _degrees; - - [_objectHelper, [DZE_memForBack, DZE_memLeftRight, DZE_memDir]] call fnc_SetPitchBankYaw; - }; - - /////////////////////////////////////////////////////////////////////////////////////////// - // - // _attach function - // - /////////////////////////////////////////////////////////////////////////////////////////// - - local _attach = { - _objectHelper attachTo [player]; - - DZE_memDir = DZE_memDir - (getDir player); - [_objectHelper, [DZE_memForBack, DZE_memLeftRight, DZE_memDir]] call fnc_SetPitchBankYaw; - - helperDetach = false; - }; - - /////////////////////////////////////////////////////////////////////////////////////////// - // - // _detach function - // - /////////////////////////////////////////////////////////////////////////////////////////// - - local _detach = { - _objectHelperPos = getPosATL _objectHelper; - detach _objectHelper; - - DZE_memDir = getDir _objectHelper; - [_objectHelper, [DZE_memForBack, DZE_memLeftRight, DZE_memDir]] call fnc_SetPitchBankYaw; - - _objectHelper setPosATL _objectHelperPos; - _objectHelper setVelocity [0,0,0]; - - helperDetach = true; - }; - /////////////////////////////////////////////////////////////////////////////////////////// // // Main Loop // /////////////////////////////////////////////////////////////////////////////////////////// + call _axial_helper; + call _setup_object; + [_distFromPlot, _radius, _snappingEnabled, _vectoringEnabled, _snapList, _object] spawn dze_snap_building; + while {_isOk} do { - // raise/lower object - call { - if (DZE_Q) exitWith {DZE_Q = false; 0.10 call _change_height;}; // +10cm - if (DZE_Z) exitWith {DZE_Z = false; -0.10 call _change_height;}; // -10cm - if (DZE_Q_alt) exitWith {DZE_Q_alt = false; 1.00 call _change_height;}; // +1m - if (DZE_Z_alt) exitWith {DZE_Z_alt = false; -1.00 call _change_height;}; // -1m - if (DZE_Q_ctrl) exitWith {DZE_Q_ctrl = false; 0.01 call _change_height;}; // +1cm - if (DZE_Z_ctrl) exitWith {DZE_Z_ctrl = false; -0.01 call _change_height;}; // -1cm - }; + _playerPos = getPosASL player; // current position used in collision check + _objectHelperPos = getPosASL _objectHelper; // used to reposition object after a collision + + // scan for key press - // rotate object call { - if (DZE_4) exitWith {DZE_4 = false; -1 call _rotate;}; // Q Key - if (DZE_6) exitWith {DZE_6 = false; 1 call _rotate;}; // E Key + // adjust height of object + if (DZE_Q) exitWith {DZE_Q = false; 0.10 call _change_height;}; // +10cm + if (DZE_Z) exitWith {DZE_Z = false; -0.10 call _change_height;}; // -10cm + if (DZE_Q_alt) exitWith {DZE_Q_alt = false; 1.00 call _change_height;}; // +1m + if (DZE_Z_alt) exitWith {DZE_Z_alt = false; -1.00 call _change_height;}; // -1m + if (DZE_Q_ctrl) exitWith {DZE_Q_ctrl = false; 0.01 call _change_height;}; // +1cm + if (DZE_Z_ctrl) exitWith {DZE_Z_ctrl = false; -0.01 call _change_height;}; // -1cm + + // rotate object + if (DZE_4) exitWith {DZE_4 = false; -DZE_curDegree call _rotate;}; // Q Key / CCW + if (DZE_6) exitWith {DZE_6 = false; DZE_curDegree call _rotate;}; // E Key / CW + + // vector object using arrow keys + if (DZE_LEFT) exitWith {DZE_LEFT = false; -DZE_curDegree call _bank;}; // Left Arrow Key + if (DZE_RIGHT) exitWith {DZE_RIGHT = false; DZE_curDegree call _bank;}; // Right Arrow Key + if (DZE_UP) exitWith {DZE_UP = false; -DZE_curDegree call _pitch;}; // Up Arrow Key + if (DZE_DOWN) exitWith {DZE_DOWN = false; DZE_curDegree call _pitch;}; // Down Arrow Key + + // adjust degrees + if (DZE_MINUS) exitWith {DZE_MINUS = false; -1 call _degrees;}; // Minus Key + if (DZE_PLUS) exitWith {DZE_PLUS = false; 1 call _degrees;}; // Plus Key (=+) + + // snapping points + if (DZE_TAB) exitWith {DZE_TAB = false; 1 call _snap;}; // Tab Key + if (DZE_TAB_SHIFT) exitWith {DZE_TAB_SHIFT = false; -1 call _snap;}; // Shift-Tab + + // hold or release object + if (DZE_F) exitWith {DZE_F = false; call _hold_release;}; // F Key + + // terrain align + if (DZE_T) exitWith {DZE_T = false; call _terrain;}; // T Key + + // show plot boundary + if (DZE_P) exitWith {DZE_P = false; call _plot_pole;}; // P Key + + // local mode + if (DZE_L) exitWith {DZE_L = false; DZE_LOCAL_MODE = !DZE_LOCAL_MODE;}; // L Key + + // hide panel + if (DZE_H) exitWith {DZE_H = false; DZE_HIDE_PANEL = !DZE_HIDE_PANEL;}; // H Key + + // reset object + if (DZE_BACK) exitWith {DZE_BACK = false; call _reset;}; // Backspace Key }; // vector object - if (DZE_updateVec) then { - [_objectHelper, [DZE_memForBack, DZE_memLeftRight, DZE_memDir]] call fnc_SetPitchBankYaw; - DZE_updateVec = false; - }; - - local _canDo = (!r_drag_sqf && !r_player_unconscious); - - // hold or release object - if (DZE_F and _canDo) then { // F Key - - if (helperDetach) then { - - call _attach; - } else { - call _detach; - }; - - DZE_F = false; - }; - - uiSleep 0.1; // default: 0.5 + if (DZE_updateVec) then {call _vector;}; // Swimming, in vehicle, on ladder, in combat _reason = call _misc_checks; @@ -388,16 +1057,19 @@ if (_canBuild) then { _reason = localize _reason; }; + // auto-refresh snap radius + call _refresh; + /////////////////////////////////////////////////////////////////////////////////// // - // player has plot pole + // Player has plot pole // /////////////////////////////////////////////////////////////////////////////////// - _objectPos = [_object] call FNC_GetPos; // object position + _modelCenterPos = [_modelCenter] call FNC_GetPos; if (_isPole) then { - _findNearestPole = _objectPos nearEntities ["Plastic_Pole_EP1_DZ", _minDistance]; // check for nearby plots within range of current pole + _findNearestPole = _modelCenterPos nearEntities ["Plastic_Pole_EP1_DZ", _minDistance]; // check for nearby plots within range of current pole _findNearestPole = _findNearestPole - [_object]; // exclude current pole }; if (count _findNearestPole > 0) exitWith { // pole is too close to another plot @@ -407,18 +1079,18 @@ if (_canBuild) then { /////////////////////////////////////////////////////////////////////////////////// // - // object was initially within plot radius + // Object was initially within plot radius // /////////////////////////////////////////////////////////////////////////////////// - _playerPos = [player] call FNC_GetPos; // player's current position - if (_inRadius) then { - _polePos = [_nearestPole] call FNC_GetPos; + distanceFromPlot = _nearestPole distance _object; // distance is calculated from model center, not getPosATL/ASL - call { - if ((_polePos distance _playerPos) > _ozone) exitWith {_reason = localize "STR_EPOCH_BUILD_MOVE_TOO_FAR"}; // You moved too far! - if ((_polePos distance _objectPos) > _radius) exitWith {_reason = localize "STR_EPOCH_BUILD_OBJ_MOVE_TOO_FAR"}; // Object moved too far! + if (_requireplot != 0) then { + call { + if ((_nearestPole distance player) > _ozone) exitWith {_reason = localize "STR_EPOCH_BUILD_MOVE_TOO_FAR"}; // You moved too far! + if (distanceFromPlot > _radius) exitWith {_reason = localize "STR_EPOCH_BUILD_OBJ_MOVE_TOO_FAR"}; // Object moved too far! + }; }; }; if (_reason != "") exitWith { @@ -427,7 +1099,7 @@ if (_canBuild) then { /////////////////////////////////////////////////////////////////////////////////// // - // other checks + // Other checks // /////////////////////////////////////////////////////////////////////////////////// @@ -438,7 +1110,7 @@ if (_canBuild) then { local _isSprinting = ([_sprint, _anim] call fnc_inString); local _isFastWalking = (_isWalking && (_speed >= 10)); // fast walking on steep incline local _tooFast = (!helperDetach && (_isRunning || _isSprinting || _isFastWalking)); // fast movement on level ground - local _tooHigh = (_isBHL && ((_objectPos select 2) > _BHL)); + local _tooHigh = (_isBHL && {(_modelCenterPos select 2) > _BHL}); call { if (_tooFast) exitWith {_reason = localize "STR_EPOCH_BUILD_MOVE_TOO_FAST"}; // You moved too fast! @@ -452,42 +1124,55 @@ if (_canBuild) then { /////////////////////////////////////////////////////////////////////////////////// // - // space bar - place object + // Space Bar - Place Object // /////////////////////////////////////////////////////////////////////////////////// - if (DZE_5) exitWith { + if (DZE_5 && !(call _moveCheck)) exitWith { + DZE_nowBuilding = true; _isOk = false; - _objectPos = [_object] call FNC_GetPos; - _zPos = (_objectPos select 2); + + 0 call _change_height; // raise up to ground/sea level if necessary + uiSleep 0.01; + + _modelBasePos = [_modelBase] call FNC_GetPos; + _modelCenterPos = [_modelCenter] call FNC_GetPos; + _isOnWater = surfaceIsWater _modelBasePos; + _heightASL = (getPosASL _modelBase) select 2; + detach _object; - _dir = getDir _object; - _vector = [(vectorDir _object),(vectorUp _object)]; - deleteVehicle _object; detach _objectHelper; + + _dir = getDir _object; + _vector = [vectorDir _object, vectorUp _object]; + + deleteVehicle _modelBase; + deleteVehicle _modelCenter; + deleteVehicle _object; deleteVehicle _objectHelper; }; + + uiSleep 0.02; }; /////////////////////////////////////////////////////////////////////////////////////////// + helperDetach = false; // set false to terminate fnc_snapDistanceCheck + // Delete Helper Array {deleteVehicle _x;} count _pArray; _pArray = []; /////////////////////////////////////////////////////////////////////////////////////////// // - // Double check that object/pole height does not violate distance requirements + // Check that the auto-adjusted height does not violate distance requirements. // /////////////////////////////////////////////////////////////////////////////////////////// - if (!_cancel && (_isAllowedUnderGround == 0) && (_zPos < (_offset select 2))) then { // object/pole was improperly placed below ground - - _objectPos set [2, _offset select 2]; // raise up to ground/sea level - + if (!_cancel) then { if (_isPole) then { // check for nearby plots within range local _findNearestPole = []; - _findNearestPole = _objectPos nearEntities ["Plastic_Pole_EP1_DZ", _minDistance]; + _findNearestPole = _modelCenterPos nearEntities ["Plastic_Pole_EP1_DZ", _minDistance]; _findNearestPole = _findNearestPole - [_object]; if (count _findNearestPole > 0) then { @@ -495,7 +1180,7 @@ if (_canBuild) then { _reason = format[localize "str_epoch_player_44", _minDistance]; // pole is too close to another plot }; } else { - if (_inRadius && ((_polePos distance _objectPos) > _radius)) then { + if (_inRadius && {_requireplot != 0 && {(_nearestPole distance _modelCenterPos) > _radius}}) then { _cancel = true; _reason = localize "STR_EPOCH_BUILD_OBJ_MOVE_TOO_FAR"; // object has moved outside radius }; @@ -508,7 +1193,7 @@ if (_canBuild) then { // /////////////////////////////////////////////////////////////////////////////////////////// - if (!_cancel && !DZE_BuildOnRoads && (isOnRoad _objectPos)) then { + if (!_cancel && !DZE_BuildOnRoads && (isOnRoad _modelCenterPos)) then { _cancel = true; _reason = localize "STR_EPOCH_BUILD_FAIL_ROAD"; }; @@ -521,7 +1206,7 @@ if (_canBuild) then { if (!_cancel) then { - _findNearestPole = _objectPos nearEntities ["Plastic_Pole_EP1_DZ", _minDistance]; // check for nearby plots within range of current pole + _findNearestPole = _modelCenterPos nearEntities ["Plastic_Pole_EP1_DZ", _minDistance]; // check for nearby plots within range of current pole _findNearestPole = _findNearestPole - [_object]; // exclude current pole if (count _findNearestPole > 0) then { // is near plot @@ -545,14 +1230,25 @@ if (_canBuild) then { /////////////////////////////////////////////////////////////////////////////////////////// // - // You cannot build. There are too many objects within "DZE_maintainRange" m. + // You are not allowed to build over sea water. + // + /////////////////////////////////////////////////////////////////////////////////////////// + + if (!_cancel && !DZE_buildOnWater && (_isOnWater || {_heightASL < DZE_maxSeaLevel})) then { + _cancel = true; + _reason = localize "STR_EPOCH_BUILD_FAIL_WATER"; + }; + + /////////////////////////////////////////////////////////////////////////////////////////// + // + // You cannot build. There are too many objects within the maintain range. // /////////////////////////////////////////////////////////////////////////////////////////// if (!_cancel) then { _buildables = DZE_maintainClasses + DZE_LockableStorage + ["DZ_storage_base"]; - if (_isPole && ((count (nearestObjects [_objectPos, _buildables, DZE_maintainRange])) >= DZE_BuildingLimit)) then { // object's position + if (_isPole && ((count (nearestObjects [_modelCenterPos, _buildables, DZE_maintainRange])) >= DZE_BuildingLimit)) then { _cancel = true; _reason = format[localize "str_epoch_player_41", floor DZE_maintainRange]; // You cannot build. There are too many objects within %1m. }; @@ -560,7 +1256,43 @@ if (_canBuild) then { /////////////////////////////////////////////////////////////////////////////////////////// // - // You can't build a "displayName" within "_distance" meters of a safe zone. + // You cannot build within X meters of a restricted zone. + // + /////////////////////////////////////////////////////////////////////////////////////////// + + if (!_cancel) then { + { + local _dis = (_x select 2); // minimum distance + local _chk = _modelCenterPos distance (_x select 1); // current distance + + if (_chk <= _dis) then { // object is within restricted zone + _cancel = true; + _reason = format[localize "STR_EPOCH_PLAYER_RES_ZONE", _dis, (_x select 0), floor _chk]; + }; + } count DZE_RestrictedBuildingZones; + }; + + /////////////////////////////////////////////////////////////////////////////////////////// + // + // You cannot build within X meters of a blacklisted building. + // + /////////////////////////////////////////////////////////////////////////////////////////// + + if (!_cancel) then { + { + local _dis = (_x select 2); // minimum distance + local _chk = count (nearestObjects [_modelCenterPos, [(_x select 1)], _dis]); // blacklisted buildings + + if (_chk > 0) exitWith { // object is too close + _cancel = true; + _reason = format[localize "STR_EPOCH_PLAYER_RES_BUILDING", _dis, (_x select 0)]; + }; + } count DZE_BlacklistedBuildings; + }; + + /////////////////////////////////////////////////////////////////////////////////////////// + // + // You can't build an object within X meters of a safe zone. // /////////////////////////////////////////////////////////////////////////////////////////// @@ -583,7 +1315,7 @@ if (_canBuild) then { } count DZE_SafeZoneNoBuildItems; if (_checkOK && !_isAdmin) then { - _canBuild = !([_objectPos, _distance] call DZE_SafeZonePosCheck); // object's position + _canBuild = !([_modelCenterPos, _distance] call DZE_SafeZonePosCheck); }; if (!_canBuild) then { _cancel = true; @@ -593,13 +1325,13 @@ if (_canBuild) then { /////////////////////////////////////////////////////////////////////////////////////////// // - // You can't build a "displayName" within "DZE_NoBuildNearDistance" meters of a "class". + // You can't build an object within X meters of a specified building. // /////////////////////////////////////////////////////////////////////////////////////////// if (!_cancel) then { if ((count DZE_NoBuildNear > 0) && !_isAdmin) then { - _near = (nearestObjects [_objectPos, DZE_NoBuildNear, DZE_NoBuildNearDistance]); // object's position + _near = (nearestObjects [_modelCenterPos, DZE_NoBuildNear, DZE_NoBuildNearDistance]); if ((count _near) > 0) then { _cancel = true; @@ -612,22 +1344,31 @@ if (_canBuild) then { if (!_cancel) then { - _classname = _classnametmp; - local _tmpbuilt = _classname createVehicle [0,0,0]; - //_tmpbuilt setdir _dir; // setdir is incompatible with setVectorDirAndUp and should not be used together on the same object https://community.bistudio.com/wiki/setVectorDirAndUp - _tmpbuilt setVariable["memDir", _dir, true]; - local _position = _objectPos; + _classname = _classnameBuild; - _tmpbuilt setVectorDirAndUp _vector; + local _builtObject = _classname createVehicle [0,0,0]; + + //_builtObject setDir _dir; // setDir is incompatible with setVectorDirAndUp and should not be used together on the same object https://community.bistudio.com/wiki/setVectorDirAndUp + _builtObject setVariable["memDir", _dir, true]; + _builtObject setVectorDirAndUp _vector; + + local _position = _modelBasePos; // ATL/ASL if (surfaceIsWater _position) then { - _tmpbuilt setPosASL _position; - _position = ASLtoATL _position; - } else { - _tmpbuilt setPosATL _position; + _position = ASLToATL _position; // position must be ATL + }; + _builtObject setPosATL _position; + + /////////////////////////////////////////////////////////////////////////////////// + + _position = _modelCenterPos; // ATL/ASL. Update db position in case model center is non-zero + _position set [2, (_position select 2) - _modelOffset]; // adjust world Z-height + + if (surfaceIsWater _position) then { + _position = ASLToATL _position; // ensure position passed to db is ATL }; - //format[localize "str_epoch_player_138", _text] call dayz_rollingMessages; // Placing %1, move to cancel. + /////////////////////////////////////////////////////////////////////////////////// local _limit = 3; @@ -677,7 +1418,7 @@ if (_canBuild) then { call player_forceSave; [format[localize "str_build_01", _text], 1] call dayz_rollingMessages; - _tmpbuilt setVariable ["OEMPos", _position, true]; // store original location as a variable + _builtObject setVariable ["OEMPos", _position, true]; // store original location as a variable if (_lockable > 1) then { //if item has code lock on it @@ -793,20 +1534,22 @@ if (_canBuild) then { }; }; - _tmpbuilt setVariable ["CharacterID", _combination, true]; // set combination as a character ID + _builtObject setVariable ["CharacterID", _combination, true]; // set combination as a character ID // call publish precompiled function with given args and send public variable to server to save item to database - _tmpbuilt setVariable ["ownerPUID", dayz_playerUID, true]; + _builtObject setVariable ["ownerPUID", dayz_playerUID, true]; - PVDZ_obj_Publish = [_combination, _tmpbuilt, [_dir, _position, dayz_playerUID, _vector], [], player, dayz_authKey]; + PVDZ_obj_Publish = [_combination, _builtObject, [_dir, _position, dayz_playerUID, _vector], [], player, dayz_authKey]; if (_lockable == 3) then { - _friendsArr = [[dayz_playerUID, toArray (name player)]]; - _tmpbuilt setVariable ["doorfriends", _friendsArr, true]; - PVDZ_obj_Publish = [_combination, _tmpbuilt, [_dir, _position, dayz_playerUID, _vector], _friendsArr, player, dayz_authKey]; + _friendsArr = [[dayz_playerUID, toArray (name player)]]; + _builtObject setVariable ["doorfriends", _friendsArr, true]; + + PVDZ_obj_Publish = [_combination, _builtObject, [_dir, _position, dayz_playerUID, _vector], _friendsArr, player, dayz_authKey]; }; + publicVariableServer "PVDZ_obj_Publish"; [format[localize "str_epoch_player_140", _combinationDisplay, _text], 1] call dayz_rollingMessages; // display new combination @@ -814,42 +1557,42 @@ if (_canBuild) then { } else { // if not lockable item - _tmpbuilt setVariable ["CharacterID", dayz_characterID, true]; + _builtObject setVariable ["CharacterID", dayz_characterID, true]; // fireplace - if (_tmpbuilt isKindOf "Land_Fire_DZ") then { // if campfire, then spawn, but do not publish to database + if (_builtObject isKindOf "Land_Fire_DZ") then { // if campfire, then spawn, but do not publish to database - [_tmpbuilt, true] call dayz_inflame; - _tmpbuilt spawn player_fireMonitor; + [_builtObject, true] call dayz_inflame; + _builtObject spawn player_fireMonitor; } else { - _tmpbuilt setVariable ["ownerPUID", dayz_playerUID, true]; + _builtObject setVariable ["ownerPUID", dayz_playerUID, true]; if (_isPole) then { _friendsArr = [[dayz_playerUID, toArray (name player)]]; - _tmpbuilt setVariable ["plotfriends", _friendsArr, true]; + _builtObject setVariable ["plotfriends", _friendsArr, true]; - PVDZ_obj_Publish = [dayz_characterID, _tmpbuilt, [_dir, _position, dayz_playerUID, _vector], _friendsArr, player, dayz_authKey]; + PVDZ_obj_Publish = [dayz_characterID, _builtObject, [_dir, _position, dayz_playerUID, _vector], _friendsArr, player, dayz_authKey]; } else { - PVDZ_obj_Publish = [dayz_characterID, _tmpbuilt, [_dir, _position, dayz_playerUID, _vector], [], player, dayz_authKey]; + PVDZ_obj_Publish = [dayz_characterID, _builtObject, [_dir, _position, dayz_playerUID, _vector], [], player, dayz_authKey]; }; publicVariableServer "PVDZ_obj_Publish"; }; }; if (DZE_GodModeBase && {!(_classname in DZE_GodModeBaseExclude)}) then { - _tmpbuilt addEventHandler ["HandleDamage", {false}]; + _builtObject addEventHandler ["HandleDamage", {false}]; }; } else { // if magazine was not removed, cancel publish - deleteVehicle _tmpbuilt; + deleteVehicle _builtObject; localize "str_epoch_player_46" call dayz_rollingMessages; // Canceled building. }; } else { // if player was interrupted cancel publish - deleteVehicle _tmpbuilt; + deleteVehicle _builtObject; localize "str_epoch_player_46" call dayz_rollingMessages; // Canceled building. }; } else { // cancel build if passed _cancel arg was true or building on roads/trader city format[localize "str_epoch_player_47", _text, _reason] call dayz_rollingMessages; // Canceled construction of %1, %2. }; }; -DZE_buildItem = nil; -dayz_actionInProgress = false; +DZE_buildItem = nil; +dayz_actionInProgress = false;