Raining6 43 Posted February 28, 2023 On 2/27/2023 at 4:03 PM, lukio said: I don't get the "The grass is not short, it completely blocks the line of sight" statement - it doesn't block line of sight? Like there is tall grass (we call it elephant grass - on Altis) that blocks line of sight and provides concealment, the short grass does not. ...interesting for those that want to use the grass as concealment - like in that old CoD mission where you crawl around. On second thought, my "blocked by grass first person view" argument is not perfect. It is a fact of optics that if player's view of AI's eyes is blocked by grass, the AI's view of players eyes would also be blocked by grass. However, It is not a fact that player's FEET would be blocked by grass from the AI's line of sight (LOS) in that same case. So, was it justified to approximate the situation focusing purely on the LOS from players head to the AI's head? And at what distance to the AI does our approximation break down? Calculation: Spoiler h_AI = 1.82 #AI soldier height h_g = 0.6 #grass height max_d = 200 #some max distance def tan_angle_find (opp,adj): tan = opp/adj alpha = math.degrees(math.atan(tan)) return alpha def tan_length_find (alpha, opp): ang_rad = alpha * math.pi/180 d1 = opp/math.tan(ang_rad) return d1 for dist in range(2, max_d, 10): #start, stop, step ang = tan_angle_find (h_AI, dist) safe_d = tan_length_find(ang,h_g) print (f'At {dist:.0f}m distance the angle is {ang:.2f}deg, the obscured by grass dist is {safe_d:.1f}m') Result: At 2m distance the angle is 42.30deg, the obscured by grass dist is 0.7m At 6m distance the angle is 16.87deg, the obscured by grass dist is 2.0m At 10m distance the angle is 10.31deg, the obscured by grass dist is 3.3m At 30m distance the angle is 3.47deg, the obscured by grass dist is 9.9m At 50m distance the angle is 2.08deg, the obscured by grass dist is 16.5m At 70m distance the angle is 1.49deg, the obscured by grass dist is 23.1m At 90m distance the angle is 1.16deg, the obscured by grass dist is 29.7m At 110m distance the angle is 0.95deg, the obscured by grass dist is 36.3m At 130m distance the angle is 0.80deg, the obscured by grass dist is 42.9m At 150m distance the angle is 0.70deg, the obscured by grass dist is 49.5m At 170m distance the angle is 0.61deg, the obscured by grass dist is 56.0m At 190m distance the angle is 0.55deg, the obscured by grass dist is 62.6m So, the "player's head LOS to the AI's head" approximation in our "hide in grass" scripts is realistic when the player is over 6 meters away from the AI. And yes, the 0.6m tall "short" grass hides many lengths of a human body at a typical engagement distance in Arma. Call of Duty doesn't need to enter the argument 🙂 Share this post Link to post Share on other sites
Raining6 43 Posted March 12, 2023 My final (for now at least) "invisible wall spawning" solution to this. The advantage of the invis wall approach is that it does not affect the AI algorithms. PierreMGI's disableFSM solution is faster than mine, shorter and way cooler overall, but it does affect the AI - for instance, VCom AI become more passive and no longer aggressively flank and hunt the player. With my walls, the AI is completely unaffected and all AI mods remain functioning, just without having X-ray vision through the grass 🙂 A vid of a showcase grassy hill battle that is completely impossible/unrealistic with vanilla AI (they simply continuously shoot you in the face right through the grass), but is really quite cool now - I'm happy at least 😎 I recommend slow movement and the use of Arma 3's (always cool, but seldom used) LCtrl+W/LCtrl+S gradual stance changes to carefully peek around the grass. Abrupt fast movements get you spotted. The VCom AI doesnt just stand there, waiting to be picked off, they move and change angles, and the grass hide'n'seek-pikaboo makes the fights much more varied still. EDIT: see my post below for the improved version. The script code: Spoiler handled_enemies = []; dtangos =[]; //downed tangos [] spawn { while {true} do { _enemies = player call BIS_fnc_enemyTargets; //hint STR handled_enemies; //systemChat STR _enemies; dtangos = handled_enemies - _enemies; handled_enemies = handled_enemies - dtangos; //remove dead dudes from the calculation list { if (!(_x in handled_enemies) ) then { handled_enemies append [_x]; _x spawn { if ( !alive _this) then {terminate _thisScript}; // terminate a dead dude's thread while{alive _this } do { //build a list of sect coords sects _a = eyePos player; _b = eyePos _this; _dist = player distance _this; _wH = -0.7; //invis wall height, make less negative to increase height _losH = 0.3; //LOS height threshold AGL, higher = easier to make walls, but less likely to hit hills apexes. Lower = reverse. _n = 30; //into how many sections to split LOS _dcf = 30; _c_set =[]; _createWall = objNull; _sol_found = false; //try to find solution closer to hill's apex, if cannot- go for less ambitious sol _sect_fn = { params ["_a","_b","_n","_dcf"]; _x_sect = _a#0+_dcf*(_b#0-_a#0)/_n; _y_sect = _a#1+_dcf*(_b#1-_a#1)/_n; _z_sect = _a#2+_dcf*(_b#2-_a#2)/_n; [_x_sect, _y_sect, _z_sect]; }; for [{ _i = 0 }, { _i < _n-1 }, { _i = _i + 1 }] do { _c_set = [_a, _b, _n, _i+1] call _sect_fn; //i+1 as dcf+1 to ignore Bill eyepos ;_n-1 to ignore player eyepos //_sectsASL set [_i, _c_set]; //_sectsAGL set [_i, ASLToAGL _c_set]; _thASL = getTerrainHeightASL [_c_set#0, _c_set#1]; _thresh = _c_set#2 - _thASL; //threshold set at the actual grass height level if ( (abs _thresh) < _losH && _dist>10 && "grass" in toLowerANSI (STR surfaceType (_c_set))) then //try to pick the lowest LOS above ground, e.g. closer to hill apex { //systemChat "Short grass!"; _vec2e = _this getDir player; _tgtAGL = _c_set; _tgtAGL set [2, _wH]; _createWall = createVehicle["GalleryLabel_01_F", _tgtAGL, [], 0, "CAN_COLLIDE"]; _createWall setDir _vec2e; _createWall setVectorUp surfaceNormal position _createWall; player disableCollisionWith _createWall; _createWall setObjectScale 12; _createWall setObjectTexture [0,""]; //comment out to make the walls visible _sol_found = true; break; }; }; if (_sol_found==false) then //if the above fails, create a wall at a higher point { for [{ _i = 0 }, { _i < _n-1 }, { _i = _i + 1 }] do { _c_set = [_a, _b, _n, _i+1] call _sect_fn; //i+1 as dcf+1 to ignore Bill eyepos ;_n-1 to ignore player eyepos //_sectsASL set [_i, _c_set]; //_sectsAGL set [_i, ASLToAGL _c_set]; _thASL = getTerrainHeightASL [_c_set#0, _c_set#1]; _thresh = _c_set#2 - _thASL; //threshold set at the actual grass height level if ( (abs _thresh) < (_losH+0.2) && _dist>10 && "grass" in toLowerANSI (STR surfaceType (_c_set))) then { //systemChat "Long grass!"; _vec2e = _this getDir player; _tgtAGL = _c_set; _tgtAGL set [2, _wH]; _createWall = createVehicle["GalleryLabel_01_F", _tgtAGL, [], 0, "CAN_COLLIDE"]; _createWall setDir _vec2e; _createWall setVectorUp surfaceNormal position _createWall; player disableCollisionWith _createWall; _createWall setObjectScale 12; _createWall setObjectTexture [0,""]; //comment out to make the walls visible _sol_found = true; break; }; }; }; sleep 1; if (!isNull _createWall) then { deleteVehicle _createWall; _createWall = objNull;} }; }; }; } forEach _enemies; sleep 2; }; }; Many thanks to @pierremgi , @johnnyboy and @EO for your help, I may not have done it without you! 4 Share this post Link to post Share on other sites
johnnyboy 3797 Posted March 12, 2023 Great work dude. Way to tough it out! Edit: @Raining6 I didn't see any shadow...how was that solved? 1 Share this post Link to post Share on other sites
Raining6 43 Posted March 12, 2023 @johnnyboy Ah, the shadow! i remembered to thank EO for suggesting the solution, but forgot to include the actual little mod file with it 😄 To remove the invisible walls' shadows, place this mod into steamapps/common/Arma3/!Workshop/ and activate as "Local mod" https://app.box.com/s/8celqpuft0qljj4m2oudzbmejt0sd1eb 1 Share this post Link to post Share on other sites
Raining6 43 Posted March 14, 2023 Slight further revision of the above "final" version (no such thing as "final" in nature :)) . Previously, the list of enemies was built from units that were already in combat mode. The walls were spawned only after they entered combat mode. This was problematic as they saw the player through the grass without any walls, opened fire and only then lost direct sight because of the newly spawned wall. By which time the player was sometimes injured or dead. In the current revision, I select enemies before they enter combat and it is thus easier (as well as more realistic) to dictate the start of the fight by using grass cover even before the start of the firefight. Code: Spoiler handled_enemies = []; dtangos =[]; //downed tangos [] spawn { while {true} do { _allEnemies = allUnits select {side _x getFriend playerSide <0.6}; _enemies = []; //enemies near player {if ((player distance _x) < 300) then {_enemies append [_x]} } forEach _allEnemies; hint STR handled_enemies; systemChat STR _enemies; dtangos = handled_enemies - _enemies; handled_enemies = handled_enemies - dtangos; //remove dead dudes from the calculation list { if (!(_x in handled_enemies) ) then { handled_enemies append [_x]; _x spawn { if ( !alive _this) then {terminate _thisScript}; // terminate a dead dude's thread while{alive _this } do { //build a list of sect coords sects _a = eyePos player; _b = eyePos _this; _dist = player distance _this; _wH = -0.7; //invis wall height, make less negative to increase height _losH = 0.3; //LOS height threshold AGL, higher = easier to make walls, but less likely to hit hills apexes. Lower = reverse. _n = 30; //into how many sections to split LOS _dcf = 30; _c_set =[]; _createWall = objNull; _sol_found = false; //try to find solution closer to hill's apex, if cannot- go for less ambitious sol _sect_fn = { params ["_a","_b","_n","_dcf"]; _x_sect = _a#0+_dcf*(_b#0-_a#0)/_n; _y_sect = _a#1+_dcf*(_b#1-_a#1)/_n; _z_sect = _a#2+_dcf*(_b#2-_a#2)/_n; [_x_sect, _y_sect, _z_sect]; }; for [{ _i = 0 }, { _i < _n-1 }, { _i = _i + 1 }] do { _c_set = [_a, _b, _n, _i+1] call _sect_fn; //i+1 as dcf+1 to ignore Bill eyepos ;_n-1 to ignore player eyepos //_sectsASL set [_i, _c_set]; //_sectsAGL set [_i, ASLToAGL _c_set]; _thASL = getTerrainHeightASL [_c_set#0, _c_set#1]; _thresh = _c_set#2 - _thASL; //threshold set at the actual grass height level if ( (abs _thresh) < _losH && _dist>10 && "grass" in toLowerANSI (STR surfaceType (_c_set))) then //try to pick the lowest LOS above ground, e.g. closer to hill apex { //systemChat "Short grass!"; _vec2e = _this getDir player; _tgtAGL = _c_set; _tgtAGL set [2, _wH]; _createWall = createVehicle["GalleryLabel_01_F", _tgtAGL, [], 0, "CAN_COLLIDE"]; _createWall setDir _vec2e; _createWall setVectorUp surfaceNormal position _createWall; player disableCollisionWith _createWall; _createWall setObjectScale 12; _createWall setObjectTexture [0,""]; //comment out to make the walls visible _sol_found = true; break; }; }; if (_sol_found==false) then //if the above fails, create a wall at a higher point { for [{ _i = 0 }, { _i < _n-1 }, { _i = _i + 1 }] do { _c_set = [_a, _b, _n, _i+1] call _sect_fn; //i+1 as dcf+1 to ignore Bill eyepos ;_n-1 to ignore player eyepos //_sectsASL set [_i, _c_set]; //_sectsAGL set [_i, ASLToAGL _c_set]; _thASL = getTerrainHeightASL [_c_set#0, _c_set#1]; _thresh = _c_set#2 - _thASL; //threshold set at the actual grass height level if ( (abs _thresh) < (_losH+0.2) && _dist>10 && "grass" in toLowerANSI (STR surfaceType (_c_set))) then { //systemChat "Long grass!"; _vec2e = _this getDir player; _tgtAGL = _c_set; _tgtAGL set [2, _wH]; _createWall = createVehicle["GalleryLabel_01_F", _tgtAGL, [], 0, "CAN_COLLIDE"]; _createWall setDir _vec2e; _createWall setVectorUp surfaceNormal position _createWall; player disableCollisionWith _createWall; _createWall setObjectScale 12; _createWall setObjectTexture [0,""]; //comment out to make the walls visible _sol_found = true; break; }; }; }; sleep 1; if (!isNull _createWall) then { deleteVehicle _createWall; _createWall = objNull;} }; }; }; } forEach _enemies; sleep 2; }; }; Share this post Link to post Share on other sites
johnnyboy 3797 Posted March 14, 2023 Good work dude. Now that you got the arma scripting bug, what are you gonna tackle next? 🤪 btw, your sketch skills are right up there with mine! Haha. 1 Share this post Link to post Share on other sites
Raining6 43 Posted March 14, 2023 14 hours ago, johnnyboy said: btw, your sketch skills are right up there with mine! Haha. Function over form any day. Ok, I lie, but you know what I mean 😄 14 hours ago, johnnyboy said: Now that you got the arma scripting bug, what are you gonna tackle next? 🤪 No idea! My second biggest gripe - no replays on helicopter flying in Arma is kinda sorted by A3RR mod and my single line hack... Not sure what next. Build ChatGPT? It's not really Arma though haha Sorting bugs with this mod will last me a while it looks like. I found another big one - player disableCollisionWith _createWall; that I heavily relied on in my mod, it only works with one object at a time. So one invisible wall I can ghost through without noticing, but all the others that are being spawned at the same time are quite literally invisible walls 😣 Here is a quick hacky solution, this is now my "Final" version 🙂 Code: Spoiler [] spawn{while {true} do {_pspeed = speed player; if ( _pspeed >14) then {_list = position player nearObjects ["GalleryLabel_01_F", 7]; {deleteVehicle _x} forEach _list}; if ( _pspeed >10) then {_list = position player nearObjects ["GalleryLabel_01_F", 5]; {deleteVehicle _x} forEach _list}; if ( _pspeed >5) then {_list = position player nearObjects ["GalleryLabel_01_F", 2]; {deleteVehicle _x} forEach _list}; sleep 0.2; } }; handled_enemies = []; dtangos =[]; //downed tangos [] spawn { while {true} do { _allEnemies = allUnits select {side _x getFriend playerSide <0.6}; _enemies = []; //enemies near player {if ((player distance _x) < 300) then {_enemies append [_x]} } forEach _allEnemies; //hint STR handled_enemies; //systemChat STR _enemies; dtangos = handled_enemies - _enemies; handled_enemies = handled_enemies - dtangos; //remove dead dudes from the calculation list { if (!(_x in handled_enemies) ) then { handled_enemies append [_x]; _x spawn { if ( !alive _this) then {terminate _thisScript}; // terminate a dead dude's thread while{alive _this } do { //build a list of sect coords sects _a = eyePos player; _b = eyePos _this; _dist = player distance _this; _wH = -0.7; //invis wall height, make less negative to increase height _losH = 0.3; //LOS height threshold AGL, higher = easier to make walls, but less likely to hit hills apexes. Lower = reverse. _n = 30; //into how many sections to split LOS _dcf = 30; _c_set =[]; _createWall = objNull; _sol_found = false; //try to find solution closer to hill's apex, if cannot- go for less ambitious sol _sect_fn = { params ["_a","_b","_n","_dcf"]; _x_sect = _a#0+_dcf*(_b#0-_a#0)/_n; _y_sect = _a#1+_dcf*(_b#1-_a#1)/_n; _z_sect = _a#2+_dcf*(_b#2-_a#2)/_n; [_x_sect, _y_sect, _z_sect]; }; for [{ _i = 0 }, { _i < _n-1 }, { _i = _i + 1 }] do { _c_set = [_a, _b, _n, _i+1] call _sect_fn; //i+1 as dcf+1 to ignore Bill eyepos ;_n-1 to ignore player eyepos //_sectsASL set [_i, _c_set]; //_sectsAGL set [_i, ASLToAGL _c_set]; _thASL = getTerrainHeightASL [_c_set#0, _c_set#1]; _thresh = _c_set#2 - _thASL; //threshold set at the actual grass height level if ( (abs _thresh) < _losH && _dist>10 && "grass" in toLowerANSI (STR surfaceType (_c_set))) then //try to pick the lowest LOS above ground, e.g. closer to hill apex { //systemChat "Short grass!"; _vec2e = _this getDir player; _tgtAGL = _c_set; _tgtAGL set [2, _wH]; _createWall = createVehicle["GalleryLabel_01_F", _tgtAGL, [], 0, "CAN_COLLIDE"]; player disableCollisionWith _createWall; _createWall setDir _vec2e; _createWall setVectorUp surfaceNormal position _createWall; _createWall setObjectScale 12; _createWall setObjectTexture [0,""]; //comment out to make the walls visible _sol_found = true; break; }; }; if (_sol_found==false) then //if the above fails, create a wall at a higher point { for [{ _i = 0 }, { _i < _n-1 }, { _i = _i + 1 }] do { _c_set = [_a, _b, _n, _i+1] call _sect_fn; //i+1 as dcf+1 to ignore Bill eyepos ;_n-1 to ignore player eyepos //_sectsASL set [_i, _c_set]; //_sectsAGL set [_i, ASLToAGL _c_set]; _thASL = getTerrainHeightASL [_c_set#0, _c_set#1]; _thresh = _c_set#2 - _thASL; //threshold set at the actual grass height level if ( (abs _thresh) < (_losH+0.2) && _dist>10 && "grass" in toLowerANSI (STR surfaceType (_c_set))) then { //systemChat "Long grass!"; _vec2e = _this getDir player; _tgtAGL = _c_set; _tgtAGL set [2, _wH]; _createWall = createVehicle["GalleryLabel_01_F", _tgtAGL, [], 0, "CAN_COLLIDE"]; player disableCollisionWith _createWall; _createWall setDir _vec2e; _createWall setVectorUp surfaceNormal position _createWall; _createWall setObjectScale 12; _createWall setObjectTexture [0,""]; //comment out to make the walls visible _sol_found = true; break; }; }; }; sleep 1; if (!isNull _createWall) then { deleteVehicle _createWall; _createWall = objNull;} }; }; }; } forEach _enemies; sleep 2; }; }; PS The mod is now on Steam Workshop: https://steamcommunity.com/sharedfiles/filedetails/?id=2946868556 , local thread is here: https://forums.bohemia.net/forums/topic/241678-acstg-ai-cannot-see-through-grass/ 3 Share this post Link to post Share on other sites