Skip to content

Commit

Permalink
Merge pull request #143 from Sparker95/improve-safe-spawning
Browse files Browse the repository at this point in the history
Civilian vehicle spawning
  • Loading branch information
billw2012 authored Jun 6, 2019
2 parents a92f10a + 079ef23 commit b5ab219
Show file tree
Hide file tree
Showing 14 changed files with 511 additions and 48 deletions.
18 changes: 16 additions & 2 deletions Project_0.Altis/GameMode/GameModeBase.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,6 @@ CLASS("GameModeBase", "")

private _cmdr = CALL_STATIC_METHOD("AICommander", "getCommanderAIOfSide", [_side]);
if(!IS_NULL_OBJECT(_cmdr)) then {
OOP_DEBUG_MSG("founc cmdr %1 for loc %2", [_cmdr ARG _loc]);
CALLM(_cmdr, "registerLocation", [_loc]);

private _gar = T_CALLM("initGarrison", [_loc ARG _side]);
Expand All @@ -158,8 +157,23 @@ CLASS("GameModeBase", "")
};
};

// Send intel to commanders
private _type = GETV(_loc, "type");
private _radius = GETV(_loc, "boundingRadius");

// Create vehicles in civilian area for player to steal
if(_type == LOCATION_TYPE_CITY) then {
private _gar = NEW("Garrison", [CIVILIAN]);
private _maxCars = 3 max (25 min (0.03 * _radius));
for "_i" from 0 to _maxCars do {
private _newUnit = NEW("Unit", [tCIVILIAN ARG T_VEH ARG T_VEH_DEFAULT ARG -1 ARG ""]);
CALLM(_gar, "addUnit", [_newUnit]);
};
CALLM1(_gar, "setLocation", _loc);
CALLM1(_loc, "registerGarrison", _gar);
CALLM0(_gar, "activate");
};

// Send intel to commanders
{
private _sideCommander = GETV(_x, "side");
if (_sideCommander != WEST) then { // Enemies are smart
Expand Down
7 changes: 3 additions & 4 deletions Project_0.Altis/Garrison/Garrison.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -333,9 +333,10 @@ CLASS("Garrison", "MessageReceiverEx");

// Check spawn state if active
if (T_GETV("active")) then {
T_CALLM("updateSpawnState", []);
T_CALLM("updateSpawnState", []);

// If we are empty except for vehicles then we must abandon them
if(T_CALLM("isOnlyEmptyVehicles", [])) then {
if(T_GETV("side") != CIVILIAN and {T_CALLM("isOnlyEmptyVehicles", [])}) then {
OOP_INFO_MSG("This garrison only has vehicles left, abandoning them", []);
// Move the units to the abandoned vehicle garrison
CALLM(gGarrisonAbandonedVehicles, "addGarrison", [_thisObject]);
Expand Down Expand Up @@ -1771,7 +1772,6 @@ CLASS("Garrison", "MessageReceiverEx");
if(IS_GARRISON_DESTROYED(_thisObject)) exitWith {
WARN_GARRISON_DESTROYED;
__MUTEX_UNLOCK;
+T_EFF_null
};

// Call handleUnitKilled of the group of this unit
Expand Down Expand Up @@ -1823,7 +1823,6 @@ CLASS("Garrison", "MessageReceiverEx");
if(IS_GARRISON_DESTROYED(_thisObject)) exitWith {
WARN_GARRISON_DESTROYED;
__MUTEX_UNLOCK;
+T_EFF_null
};

// Get garrison of the unit that entered the vehicle
Expand Down
6 changes: 5 additions & 1 deletion Project_0.Altis/Garrison/updateSpawnState.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ pr _thisPos = if (_loc == "") then {
pr _speedMax = 200;

// Get distances to all garrisons of other sides
pr _garrisonDist = CALL_STATIC_METHOD("Garrison", "getAllActive", [[] ARG [_side]]) apply {CALLM(_x, "getPos", []) distance _thisPos};
pr _garrisonDist = if(_side != CIVILIAN) then {
CALL_STATIC_METHOD("Garrison", "getAllActive", [[] ARG [_side ARG CIVILIAN]]) apply {CALLM(_x, "getPos", []) distance _thisPos}
} else {
[]
};
pr _dstMin = if (count _garrisonDist > 0) then {selectMin _garrisonDist} else {_dstSpawnMax};
// Double check unit distances as well
if(_dstMin >= _dstSpawnMax) then {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
params["_vehicle","_fuelCargoCapacity",["_fuelCargo",0]];

//check if it already has a action
if !isnil(_vehicle getVariable "refuelAction_id")exitWith{diag_log ("JN_fuel already init for object: "+str _vehicle)};
if (!isnil{ _vehicle getVariable "refuelAction_id" }) exitWith{diag_log ("JN_fuel already init for object: "+str _vehicle)};

pr _id = _vehicle addaction [
"place holder",
Expand Down
66 changes: 35 additions & 31 deletions Project_0.Altis/Location/Location.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,8 @@ CLASS("Location", "MessageReceiverEx")
Returns: Array, [_pos, _dir]
*/
#define ROAD_DIR_LIMIT 15

STATIC_METHOD("findSafePosOnRoad") {
params ["_thisClass", ["_startPos", [], [[]]], ["_className", "", [""]] ];

Expand All @@ -530,34 +532,32 @@ CLASS("Location", "MessageReceiverEx")
private _rct = roadsConnectedTo _road;
// TODO: we can preprocess spawn locations better than this probably.
// Need a connected road (this is guaranteed probably?)
if (count _rct > 0 and
// Avoid spawning on a person or another car
{count (nearestObjects [getPos _road, ["LandVehicle", "Man"], 10]) == 0} and
// Avoid spawning too close to a junction
{{ count (roadsConnectedTo _x) > 2} count ((getPos _road) nearRoads 10) == 0}) then { // We better don't use terminal road pieces

// Check position if it's safe
private _dir = _road getDir (_rct select 0);
// Get Z component of ATL height from two nearest road pieces
// private _z0 = (getPosASL (_rct select 0)) select 2;
// private _z1 = (getPosASL (_rct select 1)) select 2;
// private _posRoad = getPosASL _road;
//_posRoad set [2, 0.5*(_z0 + _z1)];
//_posRoad = ASLToATL _posRoad;
private _posRoad = getPos _road;

private _foundSafePos = [];
for "_offs" from 2 to 8 step 2 do {
// Find offset position away from center of road
private _spawnPos = [_posRoad, _offs, _dir + 90] call BIS_Fnc_relPos;
if(!CALLSM3("Location", "isPosSafe", _spawnPos, _dir, _className)) exitWith {};
_foundSafePos = _spawnPos;
};

//diag_log format ["--- road: %1, pos atl: %2", _road, getPosATL _road];
if (!(_foundSafePos isEqualTo [])) then {
_return = [_foundSafePos, _dir];
_found = true;
// Avoid spawning too close to a junction
if(count _rct > 0) then {
private _dir = _road getDir _rct#0;
if ({
private _rctOther = roadsConnectedTo _x;
if(count _rctOther == 0) exitWith { false };
private _dirOther = _x getDir _rctOther#0;
private _relDir = _dir - _dirOther;
if(_relDir < 0) then { _relDir = _relDir + 360 };
(_relDir > ROAD_DIR_LIMIT and _relDir < 180-ROAD_DIR_LIMIT) or (_relDir > 180+ROAD_DIR_LIMIT and _relDir < 360-ROAD_DIR_LIMIT)
} count ((getPos _road) nearRoads 25) == 0) then {
// Check position if it's safe

private _width = [_road, 1, 8] call misc_fnc_getRoadWidth;
// Move to the edge
private _pos = [getPos _road, _width - 3, _dir + (selectRandom [90, 270]) ] call BIS_Fnc_relPos;
// Move up and down the street a bit
_pos = [_pos, _width * 0.5, _dir + (selectRandom [0, 180]) ] call BIS_Fnc_relPos;
// Perturb the direction a bit
private _dirPert = _dir + random [-20, 0, 20] + (selectRandom [0, 180]);
// Perturb the position a bit
private _posPert = _pos vectorAdd [random [-1, 0, 1], random [-1, 0, 1], 0];
if(CALLSM3("Location", "isPosEvenSafer", _posPert, _dirPert, _className)) then {
_return = [_posPert, _dirPert];
_found = true;
};
};
};
_i = _i + 1;
Expand Down Expand Up @@ -585,12 +585,12 @@ CLASS("Location", "MessageReceiverEx")
Returns: [_pos, _dir]
*/
STATIC_METHOD("findSafeSpawnPos") {
params ["_thisObject", ["_className", "", [""]], ["_startPos", [], [[]]]];
params ["_thisClass", ["_className", "", [""]], ["_startPos", [], [[]]]];

private _found = false;
private _searchRadius = 50;
pr _posAndDir = [];
while {!_found} do {
pr _posAndDir = [_startPos, 0];
while {!_found and _searchRadius < 2000} do {
for "_i" from 0 to 16 do {
pr _pos = _startPos vectorAdd [-_searchRadius + random(2*_searchRadius), -_searchRadius + random(2*_searchRadius), 0];
if (CALLSM3("Location", "isPosSafe", _pos, 0, _className) && ! (surfaceIsWater _pos)) exitWith {
Expand Down Expand Up @@ -701,6 +701,10 @@ CLASS("Location", "MessageReceiverEx")
// Checks if given position is safe to spawn a vehicle here
STATIC_METHOD_FILE("isPosSafe", "Location\isPosSafe.sqf");

// Checks if given position is even safer to spawn a vehicle here (conservative, doesn't allow spawning
// in buildings etc.)
STATIC_METHOD_FILE("isPosEvenSafer", "Location\isPosEvenSafer.sqf");

// Returns the nearest location to given position and distance to it
STATIC_METHOD_FILE("getNearestLocation", "Location\getNearestLocation.sqf");

Expand Down
8 changes: 6 additions & 2 deletions Project_0.Altis/Location/getSpawnPos.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,13 @@ if(_found) then {//If the spawn position has been found
_return = [ ( _locPos vectorAdd [-_r + (random (2*_r)), -_r + (random (2*_r)), 0] ), 0];
OOP_WARNING_MSG("[Location::getSpawnPos] Warning: spawn position not found for unit: %1. Returning default position.", [_catID ARG _subcatID ARG _groupType]);
} else {
// Try to find a safe position on a road for this vehicle
// Try to find a random safe position on a road for this vehicle
private _locPos = GET_VAR(_thisObject, "pos");
_return = CALLSM2("Location", "findSafePosOnRoad", _locPos, _className);
private _locRadius = GET_VAR(_thisObject, "boundingRadius");
private _testPos = [_locPos, _locRadius min random [0, 0, _locRadius*5], random 360] call BIS_fnc_relPos;
// DUMP_CALLSTACK;
// [[[_locPos, _locRadius]],[]] call BIS_fnc_randomPos;
_return = CALLSM2("Location", "findSafePosOnRoad", _testPos, _className);
};
};

Expand Down
66 changes: 66 additions & 0 deletions Project_0.Altis/Location/isPosEvenSafer.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Class: Location
/*
Method: (static)isPosEvenSafer
Returns true if the place is guaranteed safe for spawning specific vehicle.
It is very conservative and as such will disallow spawning in buildings or too close
to existing objects.
Parameters: _pos, _dir, _className
_pos - position ATL
_dir - direction
_className - vehicle class name
Returns: Bool
Author: Sparker 29.07.2018
*/

//#define DEBUG

pr0_fn_getGlobalRectAndSize = {
params [["_pos", [], [[]]], ["_dir", 0, [0]], ["_bbox", [], [[]]] ];

private _bx = _bbox select 1 select 0; //width
private _by = _bbox select 1 select 1; //length
private _bz = _bbox select 1 select 2; //height

private _c = cos _dir;
private _s = sin _dir;

private _posASL = ATLTOASL _pos;
private _pos_0 = _posASL vectorAdd [_bx*_c + _by*_s, -_bx*_s + _by*_c, 0];
private _pos_1 = _posASL vectorAdd [_bx*_c - _by*_s, -_bx*_s - _by*_c, 0];
private _pos_2 = _posASL vectorAdd [-_bx*_c - _by*_s, _bx*_s - _by*_c, 0];
private _pos_3 = _posASL vectorAdd [-_bx*_c + _by*_s, _bx*_s + _by*_c, 0];
private _rect = [_pos_0, _pos_1, _pos_2, _pos_3];
[_rect, [_bx, _by, _bz]]
};

params [ ["_thisClass", "", [""]], ["_pos", [], [[]]], ["_dir", 0, [0]], ["_className", "", [""]] ];

// Bail if Z is below surface, as it happens with positions of bridges
if (_pos#2 < -0.3) exitWith {
false
};

([
_pos, _dir,
[_className] call misc_fnc_boundingBoxReal
] call pr0_fn_getGlobalRectAndSize) params ["_rect3D", "_size"];

// TODO: expand the class set here?
private _o = nearestObjects [_pos, [], 15, true] - (_pos nearRoads 15);

_o findIf {
#ifndef _SQF_VM
private _bbox = 0 boundingBoxReal _x;
#else
private _bbox = [[0,0,0],[1,1,1],1];
#endif
([getPos _x, getDir _x, _bbox] call pr0_fn_getGlobalRectAndSize) params ["_oRect3D", "_oSize"];
if(_oSize#2 > 0.3) then {
[_rect3D, _oRect3D] call misc_fnc_polygonCollision
} else {
false
}
} == -1
45 changes: 45 additions & 0 deletions Project_0.Altis/Misc/Math/fn_polygonCollision.sqf
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// See https://gist.github.com/nyorain/dc5af42c6e83f7ac6d831a2cfd5fbece
private _fn_separatedPolys = {
params ["_a", "_b"];
private _result = false;
for "_i" from 0 to (count _a - 1) do
{
// calculate the normal vector of the current edge
// this is the axis will we check in this loop
private _current = _a select _i;
private _next = _a select ((_i + 1) % count _a);
private _edge = [_next#0 - _current#0, _next#1 - _current#1];
private _axis = [-(_edge#1), _edge#0];

// loop over all vertices of both polygons and project them
// onto the axis. We are only interested in max/min projections
private _aMaxProj = -1000000;
private _aMinProj = 1000000;
private _bMaxProj = -1000000;
private _bMinProj = 1000000;

{
private _proj = _axis#0 * _x#0 + _axis#1 * _x#1;
_aMinProj = _aMinProj min _proj;
_aMaxProj = _aMaxProj max _proj;
} forEach _a;

{
private _proj = _axis#0 * _x#0 + _axis#1 * _x#1;
_bMinProj = _bMinProj min _proj;
_bMaxProj = _bMaxProj max _proj;
} forEach _b;

// now check if the intervals the both polygons projected on the
// axis overlap. If they don't, we have found an axis of separation and
// the given polygons cannot overlap
if(_aMaxProj < _bMinProj or _aMinProj > _bMaxProj) exitWith {
_result = true;
};
};
_result
};

params ["_a", "_b"];

!(([_a, _b] call _fn_separatedPolys) or {([_b, _a] call _fn_separatedPolys)})
4 changes: 3 additions & 1 deletion Project_0.Altis/Misc/initFunctions.sqf
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ misc_fnc_mapDrawLineLocal = compile preprocessFileLineNumbers "Misc\fn_mapDrawLi
misc_fnc_dateToNumber = compile preprocessFileLineNumbers "Misc\fn_dateToNumber.sqf";

misc_fnc_getRoadDirection = compile preprocessFileLineNumbers "Misc\fn_getRoadDirection.sqf";
misc_fnc_getRoadWidth = compile preprocessFileLineNumbers "Misc\fn_getRoadWidth.sqf";
misc_fnc_getRoadWidth = compile preprocessFileLineNumbers "Misc\fn_getRoadWidth.sqf";

misc_fnc_polygonCollision = compile preprocessFileLineNumbers "Misc\Math\fn_polygonCollision.sqf";
Loading

0 comments on commit b5ab219

Please sign in to comment.