diff --git a/addons/trenches/XEH_PREP.hpp b/addons/trenches/XEH_PREP.hpp
index 05eb403fd8..340cfe482d 100644
--- a/addons/trenches/XEH_PREP.hpp
+++ b/addons/trenches/XEH_PREP.hpp
@@ -1,4 +1,5 @@
+PREP(blockTrench_place);
PREP(camouflageTrench);
PREP(canCamouflageTrench);
PREP(canContinueDiggingTrench);
diff --git a/addons/trenches/XEH_postInit.sqf b/addons/trenches/XEH_postInit.sqf
index 1fbfa24116..ea8ff7e24f 100644
--- a/addons/trenches/XEH_postInit.sqf
+++ b/addons/trenches/XEH_postInit.sqf
@@ -3,6 +3,17 @@
if (isServer) then {
// Cancel dig on hard disconnection. Function is identical to killed
addMissionEventHandler ["HandleDisconnect", {_this call FUNC(handleKilled)}];
+
+ // Wrapper for blockTrench_place, on failure send hint back to source
+ [QGVAR(layTrenchline), {
+ params [["_source", objNull, [objNull]], ["_args", [], [[]]]];
+ private _return = _args call FUNC(blockTrench_place);
+ TRACE_3("layTrenchline EH",_source,_args,_return);
+ _return params ["_success", "_reason", ["_info", ""]];
+ if ((!_success) && {!isNull _source}) then {
+ [QEGVAR(common,displayTextStructured), [["%1:
%2
%3", "str_mis_state_failed", _reason, _info], 3], [_source]] call CBA_fnc_targetEvent;
+ };
+ }] call CBA_fnc_addEventHandler;
};
if (!hasInterface) exitWith {};
diff --git a/addons/trenches/functions/fnc_blockTrench_place.sqf b/addons/trenches/functions/fnc_blockTrench_place.sqf
new file mode 100644
index 0000000000..f2aa062fe8
--- /dev/null
+++ b/addons/trenches/functions/fnc_blockTrench_place.sqf
@@ -0,0 +1,164 @@
+#include "..\script_component.hpp"
+/*
+ * Author: PabstMirror
+ * Dig trenchline
+ *
+ * Arguments:
+ * 0: Position
+ * 1: Position
+ * 2: Force - ignoring saftey checks (optional: false)
+ * 3: Cut Grass (optional: false)
+ *
+ * Return Value:
+ *
+ * 0: Success
+ * 1: Failure reason
+ * 2: Extra info
+ *
+ * Example:
+ * [a, b] call ace_trenches_fnc_blockTrench_place
+ *
+ * Public: No
+ */
+
+if (!isServer) exitWith { ERROR("function must be called on server"); [false, "server-only"]; };
+
+params [["_start2d", [], [[]]], ["_end2d", [], [[]]], ["_force", false, [false]], ["_cutGrass", false, [false]]];
+TRACE_3("blockTrench_place",_start2d,_end2d,_force);
+
+scopeName "main";
+
+// get maths
+getTerrainInfo params ["", "", "_cellsize"];
+if ((_cellsize < 1) || {_cellsize > 10}) exitWith { [false, "world cellsize"] breakOut "main" }; // malden is 12.5
+
+// for Land_Trench_01_forest_F
+private _modelX = 2.1;
+private _modelZ = 1.1;
+private _modelSize = 3.75;
+
+private _landAdjust = -1.7; // how deep we dig into the terrain
+private _trenchDepth = -1; // how deep the floor is
+private _trenchWidth = 1; // offset for each side from center
+private _blockAdjust = -0.45; // get block to sit flush
+private _blockScale = _cellsize / _modelSize; // scale up block to fit cellsize
+
+private _xOffset = _trenchWidth + _blockScale * _modelX;
+private _zOffset = _blockAdjust - (_blockScale - 1) * _modelZ;
+private _testRadius = 1 * _blockScale * _modelSize;
+
+// convert to terrain grid
+_start2d = (_start2d select [0,2]) apply {_cellsize * round (_x / _cellsize)};
+_end2d = (_end2d select [0,2]) apply {_cellsize * round (_x / _cellsize)};
+_start2d params ["_ax", "_ay"];
+_end2d params ["_bx", "_by"];
+{ // make sure points aren't outside terrain
+ if (_x < _cellsize || {_x > (worldSize - _cellsize)}) exitWith { [false, "outside map boundry"] breakOut "main" };
+} forEach [_ax, _ay, _bx, _by];
+TRACE_3("adjusted",_cellsize,_start2d,_end2d);
+
+// get direction and start/end
+private _east = (abs (_ax - _bx)) >= (abs (_ay - _by));
+private _origin2D = [];
+private _length = 0;
+if (_east) then {
+ _origin2D = if (_ax < _bx) then { _start2d } else { _end2d };
+ _length = (abs (_ax - _bx)) / _cellsize;
+} else {
+ _origin2D = if (_ay < _by) then { _start2d } else { _end2d };
+ _length = (abs (_ay - _by)) / _cellsize;
+};
+TRACE_3("",_east,_origin2D,_length);
+if (_length < 2) exitWith { [false, "too short"] breakOut "main" };
+
+
+// Test and get block data
+private _blockData = [];
+for "_i" from 0 to _length do { // intentionally inclusive
+ private _posCenter = _origin2D;
+ private _posLeft = _origin2D;
+ private _posRight = _origin2D;
+ private _direction = [];
+ if (_east) then {
+ _posCenter = _posCenter vectorAdd [(_i + 0.5) * _cellsize, 0];
+ _posLeft = _posCenter vectorAdd [0, _xOffset];
+ _posRight = _posCenter vectorAdd [0, -_xOffset];
+ _direction = [0,-1,0];
+ } else {
+ _posCenter = _posCenter vectorAdd [0, (_i + 0.5) * _cellsize];
+ _posLeft = _posCenter vectorAdd [_xOffset, 0];
+ _posRight = _posCenter vectorAdd [-_xOffset, 0];
+ _direction = [-1,0,0];
+ };
+
+ { // Test if each point is valid
+ private _pos2d = _x;
+ // check water
+ if ((!_force) && {(getTerrainHeightASL _pos2D) < 0}) then { [false, "water"] breakOut "main" };
+ // check canDig (surface type)
+ if ((!_force) && {!([_pos2d] call EFUNC(common,canDig))}) then { [false, "canDig surface"] breakOut "main" };
+ // check canDig (surface type)
+ if ((!_force) && {isOnRoad _pos2D}) then { [false, "road"] breakOut "main" };
+ // check terrain objects
+ private _terrainObjects = nearestTerrainObjects [_pos2d, [], _testRadius, false, true];
+ // todo: want to avoid touching trees and large rocks but could allow some small shrubs to be overlapped
+ if (_terrainObjects isNotEqualTo []) then {
+ if (_force) then {
+ WARNING_1("overlapping terrainObjects %1",_terrainObjects);
+ } else {
+ [false, "terrain object", _terrainObjects] breakOut "main";
+ };
+ };
+ // check mission objects
+ private _missionObjects = nearestObjects [_origin2D, ["All"], _testRadius, true];
+ _missionObjects = _missionObjects select { !(_x isKindOf "Logic") };
+ if (_missionObjects isNotEqualTo []) then {
+ _missionObjects = _missionObjects apply {typeOf _x};
+ if (_force) then {
+ WARNING_1("blocking missionObjects %1",_missionObjects);
+ } else {
+ [false, "mission object", _missionObjects] breakOut "main";
+ };
+ };
+ } forEach [_posCenter, _posLeft, _posRight];
+
+ _posCenter set [2, (getTerrainHeightASL _posCenter) + _zOffset + _trenchDepth];
+ _posLeft set [2, (getTerrainHeightASL _posLeft) + _zOffset];
+ _posRight set [2, (getTerrainHeightASL _posRight) + _zOffset];
+
+ if (_cutGrass && {_i != 0} && {_i != _length}) then {
+ _blockData pushBack ["Land_ClutterCutter_medium_F", _blockScale, _posCenter, [0,1,0], surfaceNormal _posCenter];
+ };
+ // todo: there also is a snow textured block or do it right and make our own re-texturable model
+ _blockData pushBack ["Land_Trench_01_forest_F", _blockScale, _posCenter, _direction, surfaceNormal _posCenter];
+ _blockData pushBack ["Land_Trench_01_forest_F", _blockScale, _posLeft, _direction, surfaceNormal _posLeft];
+ _blockData pushBack ["Land_Trench_01_forest_F", _blockScale, _posRight, _direction vectorMultiply -1, surfaceNormal _posRight];
+};
+
+
+// Adjust terrain heights
+private _terrainData = [];
+for "_i" from 1 to (_length - 1) do { // skip first and last
+ private _posCenter = _origin2D;
+ if (_east) then {
+ _posCenter = _posCenter vectorAdd [_i * _cellsize, 0];
+ } else {
+ _posCenter = _posCenter vectorAdd [0, _i * _cellsize];
+ };
+
+ _posCenter set [2, (getTerrainHeight _posCenter) + _landAdjust];
+ _terrainData pushBack _posCenter
+};
+TRACE_1("setTerrainHeight",count _terrainData);
+setTerrainHeight [_terrainData, true];
+
+
+// Place blocks
+{
+ _x params ["_xClass", "_xScale", "_xPosASL", "_xDir", "_xUp"];
+ private _block = createSimpleObject [_xClass, _xPosASL];
+ _block setVectorDirAndUp [_xDir, _xUp];
+ if (_xScale != 1) then { _block setObjectScale _xScale; };
+} forEach _blockData;
+
+[true, "", _length]
diff --git a/addons/zeus/CfgVehicles.hpp b/addons/zeus/CfgVehicles.hpp
index 737f2acc09..4ae07fdedc 100644
--- a/addons/zeus/CfgVehicles.hpp
+++ b/addons/zeus/CfgVehicles.hpp
@@ -179,6 +179,11 @@ class CfgVehicles {
function = QFUNC(moduleHeal);
icon = QPATHTOF(ui\Icon_Module_Zeus_Heal_ca.paa);
};
+ class GVAR(moduleLayTrench): GVAR(moduleBase) {
+ category = QGVAR(Utility);
+ displayName = CSTRING(ModuleLayTrenchline_DisplayName);
+ function = QFUNC(moduleLayTrench);
+ };
class GVAR(moduleLoadIntoCargo): GVAR(moduleBase) {
curatorCanAttach = 1;
category = QGVAR(Utility);
diff --git a/addons/zeus/XEH_PREP.hpp b/addons/zeus/XEH_PREP.hpp
index c92c98b211..46cbbeb446 100644
--- a/addons/zeus/XEH_PREP.hpp
+++ b/addons/zeus/XEH_PREP.hpp
@@ -21,6 +21,7 @@ PREP(moduleGarrison);
PREP(moduleGlobalSetSkill);
PREP(moduleGroupSide);
PREP(moduleHeal);
+PREP(moduleLayTrench);
PREP(moduleLoadIntoCargo);
PREP(moduleRemoveArsenal);
PREP(moduleRemoveAceArsenal);
diff --git a/addons/zeus/config.cpp b/addons/zeus/config.cpp
index 3856828230..8b752771b7 100644
--- a/addons/zeus/config.cpp
+++ b/addons/zeus/config.cpp
@@ -92,6 +92,11 @@ class CfgPatches {
QGVAR(moduleBurn)
};
};
+ class GVAR(trenches): ADDON {
+ units[] = {
+ QGVAR(moduleLayTrench)
+ };
+ };
};
class ACE_Curator {
@@ -104,6 +109,7 @@ class ACE_Curator {
GVAR(pylons) = "ace_pylons";
GVAR(arsenal) = "ace_arsenal";
GVAR(fire) = "ace_fire";
+ GVAR(trenches) = "ace_trenches";
};
#include "CfgFactionClasses.hpp"
diff --git a/addons/zeus/functions/fnc_moduleLayTrench.sqf b/addons/zeus/functions/fnc_moduleLayTrench.sqf
new file mode 100644
index 0000000000..ae9d60f2f2
--- /dev/null
+++ b/addons/zeus/functions/fnc_moduleLayTrench.sqf
@@ -0,0 +1,50 @@
+#include "..\script_component.hpp"
+/*
+ * PabstMirror
+ * Dig trenchline
+ *
+ * Arguments:
+ * 0: Module logic