Update fnc_isInsideBuilding

Made by @Victor-the-Cleaner

- If the player is inside a building but near a large open doorway or full height windows, or out on a balcony, they may be considered outside.
- The UI visual stealth icon will update accordingly, so the player will know if they need to step back from open doors or windows to regain stealth.
- dayz_inside global variable will now only affect player temperature, stealth vs zombies, and blizzard effects.
- The new dayz_insideBuilding global variable stores the building name the player is currently inside of, or null if player is outside. This may be used for modding purposes.
This commit is contained in:
A Man
2022-04-02 12:22:56 +02:00
parent 5a37ed6fec
commit e116caa815
5 changed files with 161 additions and 61 deletions

View File

@@ -1,77 +1,175 @@
/*
Created exclusively for ArmA2:OA - DayZMod.
Please request permission to use/alter/distribute from project leader (R4Z0R49) AND the author (facoptere@gmail.com)
*/
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// fn_isInsideBuilding.sqf
//
// Author: Victor the Cleaner
// Date: April 2022
//
// [_unit] call fnc_isInsideBuilding;
//
// Called continuously from player_checkStealth.
// Used for temperature, stealth vs zombies, and blizzard effects.
//
///////////////////////////////////////////////////////////////////////////////////////////////////
local _unit = _this select 0; // player
local _inside = false;
local _scan = 3; // horizontal radius around player (in meters)
local _zenith = 50; // scan height above and below player
local _posASL = aimPos _unit; // center of mass (ASL)
local _posLowASL = getPosASL _unit; // foot of player (ASL)
_posLowASL set [2, (_posLowASL select 2) + 0.3]; // shin level, below most windows
// check if arg#0 is inside or on the roof of a building
// second argument is optional:
// - arg#1 is an object: check whether arg#0 is inside (bounding box of) arg#1
// - missing arg#1: check whether arg#0 is inside (bounding box of) the nearest enterable building
// - arg#1 is a boolean: check also whether arg#0 is inside (bounding box of) some non-enterable buildings around. Can be used to check if a player or an installed item is on a building roof.
// - arg#0 is posATL, arg#1 should be a building
local _posX = _posASL select 0;
local _posY = _posASL select 1;
local _posZ = _posASL select 2;
local _posLowZ = _posLowASL select 2;
private ["_check","_unit","_inside","_building","_type","_option"];
local _insideBox = objNull; // object the player is inside of
local _type = ""; // class name
local _roofAbove = false; // is there geometry above
local _intersect = false; // for raycast
local _hit = []; // array to record hits and misses
local _idx = 0; // initialize
local _truth = []; // used with the _hit array
_check = {
private ["_building", "_pos", "_inside", "_offset", "_relPos", "_boundingBox", "_min", "_max", "_myX", "_myY", "_myZ"];
local _cowsheds = ["Land_Farm_Cowshed_a","Land_Farm_Cowshed_b","Land_Farm_Cowshed_c"];
local _isCowshed = false; // special case objects
_building = _this select 0;
_inside = false;
if (isNull _building) exitWith {_inside};
_pos = _this select 1;
_offset = 1; // shrink building boundingbox by this length.
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// Functions
//
///////////////////////////////////////////////////////////////////////////////////////////////////
local _checkBox = {
local _object = _this select 0; // object
_type = typeOf _object; // class name
_relPos = _building worldToModel _pos;
_boundingBox = boundingBox _building;
if (_type isKindOf "House" || {_type isKindOf "Church" || {_type in DZE_insideExceptions}}) then {
_min = _boundingBox select 0;
_max = _boundingBox select 1;
_myX = _relPos select 0;
_myY = _relPos select 1;
_myZ = _relPos select 2;
local _pos = _object worldToModel (ASLToATL _posASL);
local _max = (boundingBox _object) select 1;
_insideBox = _object;
if ((_myX > (_min select 0)+_offset) and {(_myX < (_max select 0)-_offset)}) then {
if ((_myY > (_min select 1)+_offset) and {(_myY < (_max select 1)-_offset)}) then {
if ((_myZ > (_min select 2)) and {(_myZ < (_max select 2))}) then {
_inside = true;
};
for "_i" from 0 to 2 do {
if (abs (_pos select _i) > (_max select _i)) exitWith {_insideBox = objNull;};
};
};
//diag_log(format["fnc_isInsideBuilding: building:%1 typeOf:%2 bbox:%3 relpos:%4 result:%5", _building, typeOf(_building), _boundingBox, _relPos, _inside ]);
};
local _scanUp = {
local _pos = [_posX, _posY, _posZ + _zenith];
local _arr = lineIntersectsWith [_posASL, _pos, _unit, objNull, true]; // sorted (nearest last)
_inside
for "_i" from (count _arr - 1) to 0 step -1 do { // count backwards
[_arr select _i] call _checkBox; // validate object
if (!isNull _insideBox) exitWith {_roofAbove = true;}; // player is within bounds of a candidate object
};
};
local _scanDown = {
local _pos = [_posX, _posY, _posZ - _zenith];
local _arr = lineIntersectsWith [_posASL, _pos, _unit, objNull, true]; // sorted (nearest last)
for "_i" from (count _arr - 1) to 0 step -1 do { // count backwards
[_arr select _i] call _checkBox; // validate object
if (!isNull _insideBox) exitWith {}; // player is within bounds of a candidate object
};
};
local _scanNear = {
local _north = [_posX, _posY + _scan, _posZ];
local _east = [_posX + _scan, _posY, _posZ];
local _south = [_posX, _posY - _scan, _posZ];
local _west = [_posX - _scan, _posY, _posZ];
local _cp = [_north, _east, _south, _west, _north]; // compass points
scopeName "near";
for "_i" from 0 to 3 do {
local _arr = lineIntersectsWith [_cp select _i, _cp select (_i + 1)]; // unsorted
{
[_x] call _checkBox; // validate object
if (!isNull _insideBox) then {breakTo "near";}; // player is within bounds of a candidate object
} count _arr;
};
};
_unit = _this select 0;
_inside = false;
// [object] call fnc_isInsideBuilding;
// This option is called continuously from player_checkStealth.
if (count _this == 1) exitWith {
//_building = nearestObject [_unit, "Building"];
_building = nearestBuilding _unit; // Not sure if this command is faster.
_inside = [_building,(getPosATL _unit)] call _check;
_inside
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// Cowsheds are arranged from 3 separate classes. Therefore, we need to allow adjacent
// cowshed class names as substitutes for the object we're scanning.
//
///////////////////////////////////////////////////////////////////////////////////////////////////
local _cowshedCheck = {
local _array = _this select 0;
if (_isCowshed) then {
{
if (typeOf _x in _cowsheds) exitWith { // is object of similar type?
_intersect = true; // override radial scan
_truth set [0,49]; // force hit detection
};
} forEach _array;
};
};
local _checkWalls = {
// known problem buildings
if (_type == (_cowsheds select 2) && _idx in [3]) exitWith {_hit set [_idx, 49];}; // simulate wall at East sector
if (_type == (_cowsheds select 1) && _idx in [3,11]) exitWith {_hit set [_idx, 49];}; // simulate walls at East and West sectors
if (_type == (_cowsheds select 0) && _idx in [11]) exitWith {_hit set [_idx, 49];}; // simulate wall at West sector
};
_option = _this select 1;
// [object,building] call fnc_isInsideBuilding;
if (typeName _option == "OBJECT") then {
// optional argument is a specific building
_inside = [_option,(getPosATL _unit)] call _check;
} else {
// [object,boolean] call fnc_isInsideBuilding; This is used in fn_niceSpot.
{
_building = _x;
_type = typeOf _building;
if (!(_type in DayZ_SafeObjects) // not installable objects
&& {!(_type isKindOf "ReammoBox")} // not lootpiles (weaponholders and ammoboxes)
&& {((sizeOf typeOf _unit) + (sizeOf _type)) > (_unit distance _building)} // objects might colliding
&& {[_building, _unit] call _check}) exitWith { // perform the check. exitWith works only in non-nested "if"
_inside = true;
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// Initial scan to determine if player is above, below, or near a building
//
///////////////////////////////////////////////////////////////////////////////////////////////////
call _scanUp;
if (isNull _insideBox) then { // no detectable roof
call _scanDown;
if (isNull _insideBox) then { // no detectable floor
call _scanNear;
};
};
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// If player is inside a bounding box, perform radial scan and determine the outcome
//
///////////////////////////////////////////////////////////////////////////////////////////////////
if (!isNull _insideBox) then { // bounding box detected
local _dir = getDir _insideBox; // direction of object on map
local _rad = sizeOf (typeOf _insideBox); // scan radius
local _seg = 16; // radial scan density (must be evenly divisible by 4)
local _arc = 360 / _seg; // radial scan delta
local _miss = "00000"; // must be (_seg / 4 + 1) characters in length -- the number of consecutive misses before player is determined to be outside
_isCowshed = _type in _cowsheds; // special case for known problem buildings
for "_n" from _arc to 360 step _arc do { // perform radial scan
local _angle = (_dir + _n) % 360; // normalize from 0 to 360
local _a = (sin _angle) * _rad; // X offset
local _b = (cos _angle) * _rad; // Y offset
local _v = [_posX + _a, _posY + _b, _posZ]; // radial vector
_truth = [48,49]; // [miss,hit]
local _arr = lineIntersectsWith [_posASL, _v, _unit]; // raycast
_intersect = (_insideBox in _arr); // did an intersect occur?
[_arr] call _cowshedCheck; // check known problem buildings
if (!_intersect && _roofAbove) then { // if no hit at chest level, check lower. This eliminates most normal windows.
_v = [_posX + _a, _posY + _b, _posLowZ]; // radial vector Low
_arr = lineIntersectsWith [_posLowASL, _v, _unit]; // raycast
[_arr] call _cowshedCheck; // re-check known problem buildings
};
} forEach (nearestObjects [_unit, ["Building"], 50]);
};
//diag_log ("fnc_isInsideBuilding Check: " + str(_inside)+ " last building:"+str(_building));
_hit set [_idx, _truth select (_insideBox in _arr)]; // record hit or miss
call _checkWalls; // simulate walls for known problem buildings, and override scan
_idx = _idx + 1;
};
for "_i" from 0 to 3 do {
_hit set [_seg + _i, _hit select _i]; // loop (_seg / 4) times to allow wrap-around search
};
if (!_roofAbove) then {_miss = "0000";}; // if player is on a roof or in an open area, reduce the consecutive miss criteria by one arc
if !([_miss, toString _hit] call fnc_inString) then { // if there are no sufficient consecutive misses, then player is deemed to be inside
_inside = true;
};
};
dayz_insideBuilding = [objNull, _insideBox] select _inside;
_inside

View File

@@ -767,6 +767,7 @@ if (!isDedicated) then {
DZ_KeyDown_EH = compile preprocessFileLineNumbers "\z\addons\dayz_code\compile\keyboard.sqf";
dayz_EjectPlayer = compile preprocessFileLineNumbers "\z\addons\dayz_code\compile\dze_ejectPlayer.sqf";
fnc_isInsideBuilding = compile preprocessFileLineNumbers "\z\addons\dayz_code\compile\fn_isInsideBuilding.sqf"; //_isInside = [_unit,_building] call fnc_isInsideBuilding;
};
//Both
@@ -789,7 +790,6 @@ fnc_veh_handleKilled = compile preprocessFileLineNumbers "\z\addons\dayz_code\co
fnc_veh_handleRepair = compile preprocessFileLineNumbers "\z\addons\dayz_code\compile\veh_handleRepair.sqf"; //process the hit as a NORMAL damage (useful for persistent vehicles)
fnc_veh_ResetEH = compile preprocessFileLineNumbers "\z\addons\dayz_code\init\veh_ResetEH.sqf"; //Initialize vehicle
fnc_inString = compile preprocessFileLineNumbers "\z\addons\dayz_code\compile\fn_inString.sqf";
fnc_isInsideBuilding = compile preprocessFileLineNumbers "\z\addons\dayz_code\compile\fn_isInsideBuilding.sqf"; //_isInside = [_unit,_building] call fnc_isInsideBuilding;
dayz_zombieSpeak = compile preprocessFileLineNumbers "\z\addons\dayz_code\compile\object_speak.sqf"; //Used to generate random speech for a unit
vehicle_getHitpoints = compile preprocessFileLineNumbers "\z\addons\dayz_code\compile\vehicle_getHitpoints.sqf";
local_gutObject = compile preprocessFileLineNumbers "\z\addons\dayz_code\compile\local_gutObject.sqf"; //Generated on the server (or local to unit) when gutting an object

View File

@@ -491,4 +491,6 @@ if (!isDedicated) then {
["RightFoot","LeftFoot"],
["neck","pilot"]
];
dayz_insideBuilding = objNull; // building name the player is currently inside of, or objNull if player is outside
DZE_insideExceptions = ["Garage_Green_DZ","Garage_White_DZ","Garage_Brown_DZ","Garage_Grey_DZ","Wooden_shed_DZ","Wooden_shed2_DZ","WoodShack_DZ","WoodShack2_DZ","StorageShed_DZ","StorageShed2_DZ","Concrete_Bunker_DZ","Concrete_Bunker_Locked_DZ","SandNestLarge_DZ"]; // list of base-building objects that allow checking if player is inside (fnc_isInsideBuilding)
};