Files
DayZ-Epoch/SQF/dayz_code/actions/modular_build.sqf
A Man 43006760f8 Re-build DayZ_SafeObjects to handle all objects
This is a complete re-build of DayZ_SafeObjects. It was necessary to remove all the duplicated entries. DayZ_SafeObjects gets defined by multiple arrays now. This makes it much easier to update. Also every building with gear gets maintained now. Every single buildable epoch building is included within the maintain array and DayZ_SafeObjects.
2022-03-20 17:53:35 +01:00

1599 lines
60 KiB
Plaintext

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// DayZ Base Building
//
// Author: vbawol@veteranbastards.com
// Updated by: Victor the Cleaner
// Date: August 2021
//
// - 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 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.
//
// - Players may now cancel the build by fast movement, i.e. running, or fast walking on steep terrain.
// This only applies when the player is holding the object. They may press F to release it, then run without cancelling.
// 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;
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// Miscellaneous Checks
//
///////////////////////////////////////////////////////////////////////////////////////////////////
local _misc_checks = {
local _inCombat = (player getVariable["combattimeout",0] >= diag_tickTime);
local _inVehicle = ((vehicle player) != player);
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.
};
_reason;
};
///////////////////////////////////////////////////////////////////////////////////////////////////
local _reason = call _misc_checks;
if (_reason != "") exitWith {
dayz_actionInProgress = false;
localize _reason call dayz_rollingMessages;
};
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, class, toolcheck] // return value: [_canBuild, _isPole, _nearestPole];
local _canBuild = _buildCheck select 0; // bool
if (_canBuild) then {
///////////////////////////////////////////////////////////////////////////////////////////
//
// Initialise
//
///////////////////////////////////////////////////////////////////////////////////////////
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)); // 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 = []; // used to store axial helper objects
local _isAdmin = dayz_playerUID in DZE_PlotManagementAdmins;
local _dir = 0; // object direction
local _vector = []; // object vector
local _object = objNull;
local _objectHelper = objNull;
local _objectHelperPos = [];
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;
};
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
///////////////////////////////////////////////////////////////////////////////////////////
//
// Axial Helper
//
///////////////////////////////////////////////////////////////////////////////////////////
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 _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 { // decrement from zenith to nadir
local _helper = _sphere createVehicleLocal [0,0,0]; // create helper
_helper setObjectTexture _noColor;
_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
_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;
};
///////////////////////////////////////////////////////////////////////////////////////////
//
// 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 _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");
if (count _offset == 0) then {
_offset = [0, abs (((boundingBox _object) select 0) select 1), 0];
};
local _isAllowedUnderGround = 1;
if (isNumber (configFile >> "CfgVehicles" >> _classname >> "nounderground")) 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 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];
_boundingCenter = boundingCenter _object; // model center offset
_modelOffset = _boundingCenter select 2; // Z offset
_modelBase = "Sign_sphere10cm_EP1" createVehicleLocal [0,0,0];
_modelBase setObjectTexture _noColor;
_modelBase attachTo [_object, [0,0,-_modelOffset]]; // getPosATL/ASL position
_modelCenter = "Sign_sphere25cm_EP1" createVehicleLocal [0,0,0];
_modelCenter setObjectTexture _noColor;
_modelCenter attachTo [_object, [0,0,0]]; // model center
_objectHelper = "Sign_sphere10cm_EP1" createVehicle [0,0,0]; // main helper during building
_objectHelper setObjectTexture _noColor;
///////////////////////////////////////////////////////////////////////////////////////////
//
// Initialize Snapping and Vectoring
//
///////////////////////////////////////////////////////////////////////////////////////////
if (isClass (configFile >> "SnapBuilding" >> _classname)) then {
_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;
};
///////////////////////////////////////////////////////////////////////////////////////////
//
// Main Loop
//
///////////////////////////////////////////////////////////////////////////////////////////
call _axial_helper;
call _setup_object;
[_distFromPlot, _radius, _snappingEnabled, _vectoringEnabled, _snapList, _object] spawn dze_snap_building;
while {_isOk} do {
_playerPos = getPosASL player; // current position used in collision check
_objectHelperPos = getPosASL _objectHelper; // used to reposition object after a collision
// scan for key press
call {
// 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 {call _vector;};
// Swimming, in vehicle, on ladder, in combat
_reason = call _misc_checks;
if (_reason != "") exitWith {
call _cancel_build;
_reason = localize _reason;
};
// auto-refresh snap radius
call _refresh;
///////////////////////////////////////////////////////////////////////////////////
//
// Player has plot pole
//
///////////////////////////////////////////////////////////////////////////////////
_modelCenterPos = [_modelCenter] call FNC_GetPos;
if (_isPole) then {
_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
call _cancel_build;
_reason = format[localize "str_epoch_player_44", _minDistance];
};
///////////////////////////////////////////////////////////////////////////////////
//
// Object was initially within plot radius
//
///////////////////////////////////////////////////////////////////////////////////
if (_inRadius) then {
distanceFromPlot = _nearestPole distance _object; // distance is calculated from model center, not getPosATL/ASL
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 {
call _cancel_build;
};
///////////////////////////////////////////////////////////////////////////////////
//
// Other checks
//
///////////////////////////////////////////////////////////////////////////////////
local _anim = animationState player;
local _speed = floor(abs(speed player));
local _isWalking = ([_walk, _anim] call fnc_inString);
local _isRunning = ([_run, _anim] call fnc_inString);
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 && {(_modelCenterPos select 2) > _BHL});
call {
if (_tooFast) exitWith {_reason = localize "STR_EPOCH_BUILD_MOVE_TOO_FAST"}; // You moved too fast!
if (_tooHigh) exitWith {_reason = format[localize "STR_EPOCH_PLAYER_168", _BHL]}; // object moved above height limit
if (!canbuild) exitWith {_reason = format[localize "STR_EPOCH_PLAYER_136", localize "STR_EPOCH_TRADER"]}; // trader nearby
if (DZE_cancelBuilding) exitWith {_reason = localize "STR_EPOCH_PLAYER_46"}; // ESC Key
};
if (_reason != "") exitWith {
call _cancel_build;
};
///////////////////////////////////////////////////////////////////////////////////
//
// Space Bar - Place Object
//
///////////////////////////////////////////////////////////////////////////////////
if (DZE_5 && !(call _moveCheck)) exitWith {
DZE_nowBuilding = true;
_isOk = false;
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;
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 = [];
///////////////////////////////////////////////////////////////////////////////////////////
//
// Check that the auto-adjusted height does not violate distance requirements.
//
///////////////////////////////////////////////////////////////////////////////////////////
if (!_cancel) then {
if (_isPole) then { // check for nearby plots within range
local _findNearestPole = [];
_findNearestPole = _modelCenterPos nearEntities ["Plastic_Pole_EP1_DZ", _minDistance];
_findNearestPole = _findNearestPole - [_object];
if (count _findNearestPole > 0) then {
_cancel = true;
_reason = format[localize "str_epoch_player_44", _minDistance]; // pole is too close to another plot
};
} else {
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
};
};
};
///////////////////////////////////////////////////////////////////////////////////////////
//
// You cannot build on a road.
//
///////////////////////////////////////////////////////////////////////////////////////////
if (!_cancel && !DZE_BuildOnRoads && (isOnRoad _modelCenterPos)) then {
_cancel = true;
_reason = localize "STR_EPOCH_BUILD_FAIL_ROAD";
};
///////////////////////////////////////////////////////////////////////////////////////////
//
// You do not have access to build on this plot.
//
///////////////////////////////////////////////////////////////////////////////////////////
if (!_cancel) then {
_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
_nearestPole = _findNearestPole select 0; // get first entry
_ownerID = _nearestPole getVariable["CharacterID","0"];
if (dayz_characterID != _ownerID) then { // not the owner
_buildcheck = [player, _nearestPole] call FNC_check_access;
_isowner = _buildcheck select 0;
_isfriendly = ((_buildcheck select 1) || (_buildcheck select 3));
if (!_isowner && !_isfriendly) then {
_cancel = true;
};
if (_cancel) then {
_reason = localize "STR_EPOCH_PLAYER_134"; // You do not have access to build on this plot.
};
};
};
};
///////////////////////////////////////////////////////////////////////////////////////////
//
// 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 [_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.
};
};
///////////////////////////////////////////////////////////////////////////////////////////
//
// 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.
//
///////////////////////////////////////////////////////////////////////////////////////////
if (!_cancel) then {
local _checkOK = false;
local _distance = DZE_SafeZoneNoBuildDistance;
{
if (typeName _x == "ARRAY") then {
if (_x select 0 == _classname) then {
_checkOK = true;
_distance = _x select 1;
};
} else {
if (_x == _classname) then {
_checkOK = true;
};
};
if (_checkOK) exitWith {};
} count DZE_SafeZoneNoBuildItems;
if (_checkOK && !_isAdmin) then {
_canBuild = !([_modelCenterPos, _distance] call DZE_SafeZonePosCheck);
};
if (!_canBuild) then {
_cancel = true;
_reason = format [localize "STR_EPOCH_PLAYER_166", _text, _distance]; // You can't build a %1 within %2 meters of a safe zone.
};
};
///////////////////////////////////////////////////////////////////////////////////////////
//
// 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 [_modelCenterPos, DZE_NoBuildNear, DZE_NoBuildNearDistance]);
if ((count _near) > 0) then {
_cancel = true;
_reason = format [localize "STR_EPOCH_PLAYER_167", _text, DZE_NoBuildNearDistance, typeOf (_near select 0)]; // You can't build a %1 within %2 meters of a %3.
};
};
};
///////////////////////////////////////////////////////////////////////////////////////////
if (!_cancel) then {
_classname = _classnameBuild;
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 {
_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
};
///////////////////////////////////////////////////////////////////////////////////
local _limit = 3;
if (DZE_StaticConstructionCount > 0) then {
_limit = DZE_StaticConstructionCount;
} else {
if (isNumber (configFile >> "CfgVehicles" >> _classname >> "constructioncount")) then {
_limit = getNumber(configFile >> "CfgVehicles" >> _classname >> "constructioncount");
};
};
_isOk = true;
local _counter = 0;
local _proceed = false;
while {_isOk} do {
format[localize "str_epoch_player_139", _text, (_counter + 1), _limit] call dayz_rollingMessages; // Constructing %1 stage %2 of %3, move to cancel.
[player, (getPosATL player), 40, "repair"] spawn fnc_alertZombies;
local _finished = ["Medic", 1, {player getVariable["combattimeout", 0] >= diag_tickTime or DZE_cancelBuilding}] call fn_loopAction;
if (!_finished) exitWith {
_isOk = false;
_proceed = false;
};
if (_finished) then {
_counter = _counter + 1;
};
if (_counter == _limit) exitWith {
_isOk = false;
_proceed = true;
};
};
if (_proceed) then {
local _num_removed = ([player, DZE_buildItem] call BIS_fnc_invRemove); // remove item's magazine from inventory
if (_num_removed == 1) then {
local _friendsArr = [];
["Working", 0, [20,10,5,0]] call dayz_NutritionSystem;
call player_forceSave;
[format[localize "str_build_01", _text], 1] call dayz_rollingMessages;
_builtObject setVariable ["OEMPos", _position, true]; // store original location as a variable
if (_lockable > 1) then { //if item has code lock on it
local _combination = "";
local _combinationDisplay = ""; //define new display
local _combination_1_Display = "";
local _combination_1 = 0;
local _combination_2 = 0;
local _combination_3 = 0;
local _combination_4 = 0;
dayz_combination = "";
dayz_selectedVault = objNull;
call { // generate random combinations depending on item type
///////////////////////////////////////////////////
//
// 2 Lockbox
//
///////////////////////////////////////////////////
if (_lockable == 2) exitWith {
createDialog "KeyPadUI";
waitUntil {!dialog};
_combinationDisplay = dayz_combination call fnc_lockCode;
if (keypadCancel || {typeName _combinationDisplay == "SCALAR"}) then {
_combination_1 = (floor(random 3)) + 100; // 100=red / 101=green / 102=blue
_combination_2 = floor(random 10);
_combination_3 = floor(random 10);
_combination = format["%1%2%3",_combination_1,_combination_2,_combination_3];
dayz_combination = _combination;
call {
if (_combination_1 == 100) exitWith {_combination_1_Display = localize "STR_TEAM_RED"};
if (_combination_1 == 101) exitWith {_combination_1_Display = localize "STR_TEAM_GREEN"};
if (_combination_1 == 102) exitWith {_combination_1_Display = localize "STR_TEAM_BLUE"};
};
_combinationDisplay = format["%1%2%3", _combination_1_Display, _combination_2, _combination_3];
} else {
_combination = dayz_combination;
};
};
///////////////////////////////////////////////////
//
// 3 Combo Lock
//
///////////////////////////////////////////////////
if (_lockable == 3) exitWith {
DZE_topCombo = 0;
DZE_midCombo = 0;
DZE_botCombo = 0;
DZE_Lock_Door = "";
dayz_selectedDoor = objNull;
dayz_actionInProgress = false;
createDialog "ComboLockUI";
waitUntil {!dialog};
dayz_actionInProgress = true;
if (keypadCancel || {parseNumber DZE_Lock_Door == 0}) then {
_combination_1 = floor(random 10);
_combination_2 = floor(random 10);
_combination_3 = floor(random 10);
_combination = format["%1%2%3", _combination_1, _combination_2, _combination_3];
DZE_Lock_Door = _combination;
} else {
_combination = DZE_Lock_Door;
};
if (_classname in DZE_LockedGates) then {
GateMethod = DZE_Lock_Door;
};
_combinationDisplay = _combination;
};
///////////////////////////////////////////////////
//
// 4 Safe
//
///////////////////////////////////////////////////
if (_lockable == 4) exitWith {
createDialog "SafeKeyPad";
waitUntil {!dialog};
if (keypadCancel || {(parseNumber dayz_combination) > 9999} || {count (toArray (dayz_combination)) < 4}) then {
_combination_1 = floor(random 10);
_combination_2 = floor(random 10);
_combination_3 = floor(random 10);
_combination_4 = floor(random 10);
_combination = format["%1%2%3%4", _combination_1, _combination_2, _combination_3, _combination_4];
dayz_combination = _combination;
} else {
_combination = dayz_combination;
};
_combinationDisplay = _combination;
};
};
_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
_builtObject setVariable ["ownerPUID", dayz_playerUID, true];
PVDZ_obj_Publish = [_combination, _builtObject, [_dir, _position, dayz_playerUID, _vector], [], player, dayz_authKey];
if (_lockable == 3) then {
_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
systemChat format[localize "str_epoch_player_140", _combinationDisplay, _text]; // You have setup your %2. The combination is %1
} else { // if not lockable item
_builtObject setVariable ["CharacterID", dayz_characterID, true];
// fireplace
if (_builtObject isKindOf "Land_Fire_DZ") then { // if campfire, then spawn, but do not publish to database
[_builtObject, true] call dayz_inflame;
_builtObject spawn player_fireMonitor;
} else {
_builtObject setVariable ["ownerPUID", dayz_playerUID, true];
if (_isPole) then {
_friendsArr = [[dayz_playerUID, toArray (name player)]];
_builtObject setVariable ["plotfriends", _friendsArr, true];
PVDZ_obj_Publish = [dayz_characterID, _builtObject, [_dir, _position, dayz_playerUID, _vector], _friendsArr, player, dayz_authKey];
} else {
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 {
_builtObject addEventHandler ["HandleDamage", {false}];
};
} else { // if magazine was not removed, cancel publish
deleteVehicle _builtObject;
localize "str_epoch_player_46" call dayz_rollingMessages; // Canceled building.
};
} else { // if player was interrupted cancel publish
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;