Files
DayZ-Epoch/SQF/dayz_code/actions/modular_build.sqf
2022-05-02 15:22:36 +02:00

1631 lines
61 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
//
///////////////////////////////////////////////////////////////////////////////////////////
local _wasStanding = ["perc", animationState player] call fnc_inString;
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 _staticOffset = 0;
local _staticOffsetSet = false;
local _isStaticWeapon = false;
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 = {
if (!_staticOffsetSet) then {
_object setVectorUp [0,0,1];
_staticOffset = ((getPosASL _object) select 2) - ((getPosASL _modelBase) select 2);
_staticOffsetSet = true;
};
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 || _isStaticWeapon) 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");
};
if (_classname in DZE_requirePlotOverride) then {_requireplot = 1;};
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
};
_isStaticWeapon = ((_object isKindof "StaticWeapon") || {_classname in DZE_StaticWeapons});
if (!(DZE_buildItem in DZE_noRotate) && !_isStaticWeapon) then {
_vectoringEnabled = true;
["","","",["Init", "Init", 0]] spawn build_vectors;
};
///////////////////////////////////////////////////////////////////////////////////////////
//
// Main Loop
//
///////////////////////////////////////////////////////////////////////////////////////////
call _axial_helper;
call _setup_object;
[_distFromPlot, _radius, _snappingEnabled, _vectoringEnabled, _isStaticWeapon, _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
_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
local _vectorUp = _vector select 1;
local _isWater = surfaceIsWater _position;
if (_isStaticWeapon) then { // handle static weapons
local _positionASL = _position;
if (!_isWater) then {_positionASL = ATLToASL _position;}; // must be ASL
for "_i" from 0 to 2 do {
_positionASL set [_i, (_positionASL select _i) + ((_vectorUp select _i) * _staticOffset)]; // add static weapon vectorUp offset to ASL position (world coordinates)
};
if (!_isWater) then { // convert back to
_position = ASLToATL _positionASL; // ATL
} else {
_position = _positionASL; // or ASL
};
};
if (_isWater) then {
_position = ASLToATL _position; // position must be ATL
};
_builtObject setPosATL _position;
///////////////////////////////////////////////////////////////////////////////////
if (!_isStaticWeapon) then {
_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 {
if (_wasStanding) then {player playActionNow "PlayerStand";}; // once the action has completed, return player to a standing pose if they were standing before the action
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;
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
local _charID = "0";
_builtObject setVariable ["CharacterID", _charID];
// 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 = [_charID, _builtObject, [_dir, _position, dayz_playerUID, _vector], _friendsArr, player, dayz_authKey];
} else {
PVDZ_obj_Publish = [_charID, _builtObject, [_dir, _position, dayz_playerUID, _vector], [], player, dayz_authKey];
};
publicVariableServer "PVDZ_obj_Publish";
};
if (_isStaticWeapon) then {
[_builtObject,DZE_clearStaticAmmo,false] call fn_vehicleAddons;
};
};
if (DZE_GodModeBase && {!(_classname in DZE_GodModeBaseExclude)}) then {
_builtObject addEventHandler ["HandleDamage", {0}];
};
} 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;