Jump to content
rübe

selectBestPlaces (do-it-yourself documentation)

Recommended Posts

So, there's this promising command selectBestPlaces, but once more the documentation is barely useable. The search here didn't yield a single hit either...

But fortunately I've scanned config.cpp some time ago, which enabled me to get some idea what expression in this context could mean, so excuse the little digression:

cost/probability expressions in config.cpp

There is the subclass Ambient in config.cpp containing definitions of all the small animals, and an - tada - expression (named "cost" here) that defines, where and when these animals may apear (all guesswork of course, so don't take this for granted).

The cost for these is a simple function (or expression) returning scalar based on simple arithmetic and some interesting keywords serving as variables of the sampled positions. The higher the output of this function, the more likely is it, that such an animal gets spawned at the sampling position. (plus there are again subclasses of these, where the cost is usually set to 1, while we have another expression for "probability" where `more exclusive?` keywords are beeing used...)

The keywords used in config.cpp are:

  • forest
  • trees
  • meadow
  • hills
  • houses
  • sea
  • night
  • rain
  • windy
  • deadBody

Each of theses variable in an expression will be replaced by the actual sampling value at the sample position of this factor, ranging from 0 to 1, where 0 means not at all and 1 means totally! (which allows for easy arithmetic combination of multiple factors)

Thus we can mix factors from three major different dimensions: the geographic dimension, time and the weather. (plus the deadBody thing, which looks like a "bonus" dimension implemented especially for the flies)

Some short examples:

  • deep forest: "(1 + forest + trees) * (1 - sea) * (1 - houses)"
  • tree, but not in forest: "(1 - forest) * (1 + trees)"
  • hill, no forest: "(1 - forest) * (1 + hills) * (1 - sea)"
  • house on hill: "(1 + houses) * (1 + hills)"
  • ...

This is how leaves get spawned where trees are near, while the game spawns rocks where hills are located (clutter particles). And this is how the HouseFly finds deadBody, DragonFlys the forest and ButterFly the meadow, while the birds disapear in the night or in the rain.

Some examples, taken from config.cpp:

        class BigBirds {
           radius = 300;
           cost = "((1 + forest + trees) - ((2 * rain)) - houses) * (1 - night) * (1 - sea)";

           class Species {

              class Hawk {
                 probability = 0.200000;
                 cost = 1;
              };
           };
        };

        class BigInsects {
           radius = 20;
           cost = "(5 - (2 * houses)) * (1 - night) * (1 - rain) * (1 - sea) * (1 - windy)";

           class Species {

              class DragonFly {
                 probability = "0.6 - (meadow * 0.5) + (forest * 0.4)";
                 cost = 1;
              };

              class ButterFly {
                 probability = "0.4 + (meadow * 0.5) - (forest * 0.4)";
                 cost = 1;
              };
           };
        };

        class SmallInsects {
           radius = 3;
           cost = "(12 - 8 * hills) * (1 - night) * (1 - rain) * (1 - sea) * (1 - windy)";

           class Species {

              class HouseFly {
                 probability = "deadBody + (1 - deadBody) * (0.5 - forest * 0.1 - meadow * 0.2)";
                 cost = 1;
              };

              class HoneyBee {
                 probability = "(1 - deadBody) * (0.5 - forest * 0.1 + meadow * 0.2)";
                 cost = 1;
              };

              class Mosquito {
                 probability = "(1 - deadBody) * (0.2 * forest)";
                 cost = 1;
              };
           };
        };

        class WindClutter {
           radius = 10;
           cost = "((20 - 5 * rain) * (3 * (windy factor [0.2, 0.5]))) * (1 - sea)";

           class Species {

              class FxWindGrass1 {
                 probability = "0.4 - 0.2 * hills - 0.2 * trees";
                 cost = 1;
              };

              class FxWindGrass2 {
                 probability = "0.4 - 0.2 * hills - 0.2 * trees";
                 cost = 1;
              };

              class FxWindRock1 {
                 probability = "0.4 * hills";
                 cost = 1;
              };

              class FxCrWindLeaf1 {
                 probability = "0.2 * trees";
                 cost = 1;
              };

              class FxCrWindLeaf2 {
                 probability = "0.1 * trees + 0.2";
                 cost = 1;
              };

              class FxCrWindLeaf3 {
                 probability = "0.1 * trees";
                 cost = 1;
              };
           };
        };

...you get the idea. And as it turns out, these config entries (cost/probability) will be feed to exactly this selectBestPlaces command.

selectBestPlaces

Thus, I may (with some surety, based on my experiments) complete the documentation for this command:

selectBestPlaces [_position,_radius,_expression,_precision,_sourcesCount] 

Parameters:

  • _position: sample position (2d position seems to be sufficient, so no ASL position needed)
  • _radius: defines the area to find the positions in (scalar in meter)
  • _expression: cost function (string, not code!), calculated for every sampling position returning a positive number. The higher (positive) the value, the better. Zero means that the expression does not apply. The expression can rely on any of the variables in: [forest, trees, meadow, hills, houses, sea, night, rain, windy, deadBody], maybe more.
  • _precision: sample precision/ radius of the sample position (scalar, maybe in meter, but for sure this is no 0 to 1 range). Given a precision of zero, your game will probably halt for some time, most likely for ever for you basically try to divide by zero. I guess we divide the area by this sampling area the precision/radius defines. With a very low value, you may get a lot of "bestPlaces" very near to each other - the precision as min. probably. With a greater value the returned positions are much further apart from each other, though it looks like the min. distance isn't exactly the given precision interpreted as radius... But you get the idea.
  • _sourcesCount: number of best places returned (integer)

Returns:

No, you won't get an array of positions. You will get an empty array in case no best places were found (I guess this is true, when all "costs" where exactly zero) OR an array of "bestPlaces", where "bestPlaces" is an array of:

  • _this select 0: position
  • _this select 1: cost (expression result at this position)

Now be carefull with the sampling radius and precision because selectBestPlaces gets computed in one go and doesn't suffer from any maximum runtime-cap each frame. Thus if you're precision is too small for a too large sampling area, you may freeze the game for several seconds! (too many samples taken, plus all these samples need to be sorted too, to return the "best" ones, of course..)

Oh, after a first unsuccessfull regex search for an official example, I actually do have found just some, all from the animal module (of course, DO!), in Animals_main.fsm:

  _root = configFile >> "CfgVehicles" >> "Goat";
  _favouritezones = getText ( _root >> "favouritezones");

  _randrefpoint =  [(getpos player select 0) +Random(2*_SpawnRadius) -_SpawnRadius,(getpos player select 1) +Random(2*_SpawnRadius) -_SpawnRadius];
  _PosList = selectbestplaces [_randrefpoint,_FindPlaceRadius,_favouritezones,10,5];
  _PosSelect = _PosList select (floor random (count _PosList));
  _Pos =  _PosSelect select 0;    

So there you go. Happy selecting best places. I imagine this is an elegant way to solve similar problems of finding appropriate places randomly.

And in case some developer pops in, I'd like to know if I've mention all of the available sampling-variables? What's really the meaning of precision? Or anything else, that I got wrong... so once we have the facts straight, we may complete the wiki about it.. :rolleyes:

  • Like 7

Share this post


Link to post
Share on other sites

If i understand this right we can use this command to up the count of insects in forest and birds and the like? And spawn flies around dead enemies?

Sorry for being so noobish, but as far as i read(the whole thing!) it, im not quite sure i understood it. But by all means, it was well presented! =)

Share this post


Link to post
Share on other sites
But by all means, it was well presented! =)

Well, obviously not, hehe. :p (I'm sorry, I usually fail at keeping things short and spot on..)

To answer your question: well, sure you could (and this is how the animal module decides where each kind gets spawned exactly), but all this command does is returning positions with the highest value of the given expression. What you do with those positions (e.g. spawn appropriate animals there) is completely up to you, and therefore this command may be valuable for completely other task than spawning animals.

Just remeber this command, next time you need to find one or multiple positions that are guaranteed to be characterised by your expression.

Share this post


Link to post
Share on other sites

this is nice! I've been looking for something to help me spawn tanks in a random area without having them spawn in dumb places like dense forest or on a building. NICE!

Share this post


Link to post
Share on other sites

So I've been playing around with this because I wanted to be able to spawn tanks/armor in random areas, but they would sometimes spawn in dumb places like in the middle of forests. I wanted them only to spawn in meadow/planes so this is how I got it to work. I just made a quick sample mission with a single marker in the editor named "marker" and then put this into init.sqf and it spawns a new USMC guy in a meadow every time:

_pos = getmarkerpos "marker";
_radius = 500;
_exp = "(1 + meadow) * (1 - forest) * (1 - trees)";
_prec = 10;

_bestplace = selectBestPlaces [_pos,_radius,_exp,_prec,1];
_spot = _bestplace select 0;
_spot2 = _spot select 0;


"USMC_Soldier_MG" createUnit [_spot2, group player, "newguy = this", 0.5, "corporal"];
sleep 2;
selectPlayer newguy;

I figured out the hard way that selectBestPlaces was returning a multi-dimensional array, which is why I needed this bit:

_spot = _bestplace select 0;

_spot2 = _spot select 0;

in order to end up with the coordinate value that I needed.

Edited by node_runner

Share this post


Link to post
Share on other sites

Hmmmm could this then be combined with helo landings so that they ONLY landing in proper clearings and NOT on top of trees ?

Share this post


Link to post
Share on other sites

Nice find Ruebe, and well doco'd will be handy for when I am trying to randomly hide ammo caches in forests rather than in the middle of an open field!

As a side note to some of the previous posts, I use isflatEmpty a lot to find suitable placement locations. It checks for things like not on water or visa versa, slope angle isn't to great and that the object will fit between existing objects at the location. Great for placing HQ's(tanks LZ's) that aren't impailed in trees and hanging off cliffs!

http://community.bistudio.com/wiki/isFlatEmpty

Edited by blakeace

Share this post


Link to post
Share on other sites

I'm looking for interesting ways to pick random spots, so this is very much appreciated!

Edited by Wokstation

Share this post


Link to post
Share on other sites

Excellent work ruebe. Thank you for taking the time to document and show this. I can think of interesting ways to make the AI find cover with this command.

-AI could search for a treeline for cover.

-Snipers could search for hills with no trees for overwatch.

-Medics set up MASH behind building areas for better cover while they heal.

Again, great work and contribution to this board.

Share this post


Link to post
Share on other sites

I'd LOVE to see BIS add more types of areas to this command, or better yet, let the community define more types. Some things that would be really useful:

Road

Mountain Peak

Thick Grass

Defilade

Desert

Share this post


Link to post
Share on other sites
Hmmmm could this then be combined with helo landings so that they ONLY landing in proper clearings and NOT on top of trees ?

Absolutely, though you need to be very carefull and experiment a bit, so your restrictions aren't too tight, nor too loose.

I'd LOVE to see BIS add more types of areas to this command, or better yet, let the community define more types. Some things that would be really useful:

Road

Mountain Peak

Thick Grass

Defilade

Desert

I can help you with some of them, though I completely agree that more commands in this direction would be welcome. Sure, some stuff can be solved with the given commands and a clever script. But this usually involves some sort of "sampling" or iterating through the world, thus you'll end up with scripts/functions beeing way too resource hungry. The information we're interested in could be preprocessed once the world is finalized (lookup tables and the like), which could offer much better and more effective query-tools/functions for mission makers...

Roads

This is already pretty "accessible" and straight forward to deal with. Given your sampling position, you may use the command nearRoads to check if there are any near you position. E.g.:

        _roads = _dicePos nearRoads _roadDistance;
        if ((count _roads) > 0) then
        {
           _accepted = false;
        };

where _dicePos is the sampling position and _roadDistance is the radius you're checking. Then you may either reject or accept the current position - e.g. rejecting the position if there is a road there, going on with another random _dicePos until one meets your criteria.

You may play the same trick to assure that a random position keeps a minimum distance to any location with nearestLocations.

Beware though, there is one pitfall: you may not operate with 2d positions! E.g. given a 2d position, nearRoads will only return roads if the sample position is ~at sea level. See, you really need the 3d-position, since these commands respect the distance in all axis including the z-axis and not only x and y as you might have expected. Since this is a common "problem" and there is no easy way to transform a 2d position to the 3d position - on ground level `there`(getPosASL only takes objects and unfortunately doesn't compute with 2d positions), I'd suggest you play the following trick:

// probe object (to setPos and getPosASL)
RUBE_PROBE_OBJ = createVehicle ["Baseball", [0,0,0], [], 0, "NONE"];
//...
RUBE_getPosASL = compile preprocessFileLineNumbers "RUBE\fn\fn_getPosASL.sqf";

where fn_getPosASL.sqf is:

/*
  Author
   rübe

  Description:
   wrapper-function for getPosASL (which only takes objects at this point)

   takes a 2d-position, places a "probe"-object there, retrieves 
   and returns the ASL position

   - the "probe"-object get's created once at initialization of
     the RUBE function library.

  Parameter(s):
   _this: position

  Returns:
   position ASL
*/
/*
----------------------------------------------------------------------------
 PROBE_OBJ hack is finally not needed anymore, yay!
 DEPRECATED(!) since OA 1.55 and the introduction of getTerrainHeightASL
----------------------------------------------------------------------------
*/
/*
private ["_probe"];

RUBE_PROBE_OBJ setPos [(_this select 0), (_this select 1)];
_probe = getPosASL RUBE_PROBE_OBJ;

[
  (_this select 0),
  (_this select 1),
  (_probe select 2)
]
*/

[
  (_this select 0),
  (_this select 1),
  (getTerrainHeightASL _this)
]

Thus, RUBE_getPosASL places our "probe object" (the baseball) at the given 2d position. The engine places this probe object at ground level (config behaviour). And now you can easily query the probe object with getPosASL and return the 3d position.

I create the baseball once and never delete it, leaving it at the last position I sampled to save some resources. So you might get to see this ball ingame.. pretty eastereggy, so I don't care, haha.

If your own functions - relying on such "world-commands" - don't return anything and you have no idea what's going wrong, this is most likely the problem.

Another common problem is this: Find all roads going to and from a given location such as some city. The idea: choose a radius at some reasonable distance (locationsize * multiplier), choose how many samples you wanna take, divide the circle to get the angle of the sample-slice, calculate the needed sample-radius (to cover the circumcircle) and collect all roads you find there with nearRoads. You might double-check and try to trace them to the locationcenter - depends on your intentions.

This for example is a nice way to spawn random road-blocks around or inside a city. Just slightly randomize the starting angle and the sample radius.. but that'd be a pretty specific script.

A very generic script I use a lot is the following, feel free to use it or get inspired to write your own:

  RUBE_randomCirclePositions = compile preprocessFileLineNumbers "RUBE\fn\fn_randomCirclePositions.sqf";

fn_randomCirclePositions.sqf:

/*
 Author: rübe

 Description:
 Generate random positions around a center position within 
 the given limitations (distance, sector and findSafePos).

 The function does not guarantee to return the desired 
 amount of positions and may return an empty array anyway!

   - Beware: You could get positions on islands though... :/

  Parameter(s):
   _this: parameters (array of array [key (string), value (any)])

          - required:

            - "position" (position)

            - "number" (int)
              - number of positions to be returned, though it's 
                not guaranteed that this many will be returned.

          - optional:

            - "range" (array of [minDistance (scalar), maxDistance (scalar)])
              - default: [0, 250]

            - "sector" (array of [minAngle (scalar), maxAngle (scalar)])
              - default: [0, 360]

            - "objDistance" (scalar)
              - minimal distance to other/nearest objects
              - default = 1

            - "posDistance" (scalar)
              - minimal distance between returned positions
              - default = "objDistance" * 2

            - "roadDistance" (scalar)
              - 0: doesn't matter/no check
              - n > 0: minimal distance to any roads 
              - default: 0

            - "locationDistance" (scalar)
              - 0: doesn't matter/no check
              - n > 0: minimal distance to nearest location
              - default: 0

            - "locations" (array of strings)
              - define what is considered a location
              - affects "locationDistance"
              - default: ["NameCityCapital", "NameCity", "NameVillage"]

            - "maxGradient"   (scalar from 0.0 to 1.0)
              - default = 1.0;

            - "gradientRadius" (scalar in m)
              - default = "objDistance"

            - "waterMode" (int from 0 to 2)
               - 0: cannot be in water
               - 1: can either be in water or not
               - 2: must be in water
               - default: 0

            - "onShore" (int from 0 to 1)
               - 0: does not have to be at a shore
               - 1: must be at a shore
               - default: 0


 Example:
     _positions = [
        ["position", _campCenterPosition],
        ["number", 7],
        ["range", [0, 120]],
        ["sector", [0, 360]],
        ["objDistance", (_tentLength * 0.5)],
        ["maxGradient", 0.3]
     ] call RUBE_randomCirclePositions; 

 Returns:
  array-of-positions OR empty-array

*/

private ["_thePositions", "_pos", "_numPos", "_distMin", "_distMax", "_dirMin", "_dirMax", "_objDistance", "_posDistance", "_maxGradient", "_gradientRadius", "_waterMode", "_onShore", "_roadDistance", "_locationDistance", "_entities", "_i", "_maxIter", "_accepted", "_diceDir", "_diceDist", "_dicePos"];

_thePositions = [];

// catch arguments
_pos = [0,0,0];
_numPos = 1;
_distMin = 0;
_distMax = 250;
_dirMin = 0;
_dirMax = 360;
_objDistance = 1;
_posDistance = false;
_maxGradient = 1.0;
_gradientRadius = false;
_waterMode = 0;
_onShore = false;
_roadDistance = 0;
_locationDistance = 0;
_locationDefinition = [
  "NameCityCapital",
  "NameCity",
  "NameVillage"
];

// read parameters
{
  switch (_x select 0) do
  {
     case "position": { _pos = _x select 1; };
     case "number": { _numPos = _x select 1; };
     case "range": { 
        _distMin = (_x select 1) select 0; 
        _distMax = (_x select 1) select 1; 
     };
     case "sector": { 
        _dirMin = (_x select 1) select 0; 
        _dirMax = (_x select 1) select 1; 
     };
     case "objDistance": { _objDistance = _x select 1; };
     case "posDistance": { _posDistance = _x select 1; };
     case "maxGradient": { _maxGradient = _x select 1; };
     case "gradientRadius": { _gradientRadius = _x select 1; };
     case "waterMode": { _waterMode = _x select 1; };
     case "onShore": { 
        if ((typeName (_x select 1)) == "BOOL") then
        {
           _onShore = _x select 1; 
        } else {
           if ((_x select 1) == 1) then
           {
              _onShore = true;
           }; 
        };
     };
     case "roadDistance": { _roadDistance = _x select 1; }; 
     case "locationDistance": { _locationDistance = _x select 1; };
     case "locations": { _locationDefinition = _x select 1; };
  };
} forEach _this;

// set adaptive auto default values
if ((typeName _posDistance) == "BOOL") then
{
  _posDistance = _objDistance * 2;
};

if ((typeName _gradientRadius) == "BOOL") then
{
  _gradientRadius = _objDistance;
};




// we may need more iterations if we need a minimum distance between the positions
_i = 0;
_maxIter = 1000;
if (_posDistance > 0) then
{
  // roughly approximated..
  _maxIter = _maxIter + (_posDistance * _numPos);
};

// search n positions
while {(count _thePositions) < _numPos} do
{
  _diceDir = _dirMin + (random (_dirMax - _dirMin));
  _diceDist = _distMin + (random (_distMax - _distMin));
  _dicePos = [_pos, _diceDist, _diceDir] call BIS_fnc_relPos;

  // check if the position is safe
  if ((count (_dicePos isFlatEmpty[_objDistance, 0, _maxGradient, _gradientRadius, _waterMode, _onShore, objNull])) > 0) then
  {
     // check if dicePos is not to near to an already accepted position
     _accepted = true;
     if (_posDistance > 0) then
     {
        {
           if ((_dicePos distance _x) < _posDistance) then
           {
              _accepted = false;
           };
        } forEach _thePositions;
     };

     if (_roadDistance > 0) then
     {
        _entities = _dicePos nearRoads _roadDistance;
        if ((count _entities) > 0) then
        {
           _accepted = false;
        };
     };

     if (_locationDistance > 0) then
     {
        _entities = nearestLocations [_dicePos, _locationDefinition, _locationDistance];
        if ((count _entities) > 0) then
        {
           _accepted = false;
        };
     };      


     // all sub-tests passed?
     if (_accepted) then
     {
        _thePositions = _thePositions + [_dicePos];
     };
  };

  // we do not wanna search forever...
  if (_i > _maxIter) exitWith {};
  _i = _i + 1;
}; 


// return
_thePositions

It's like my swissknife for random positions. Note, that the arguments are passed as an array of arrays (key, value). This allows to skip arguments, you don't have to worry about the order and once written, you don't have to look up what the 7th argument exactly was ment for again...

Also note, that this function doesn't guarantee to return any position at all. Thus the basic idea of useage is to repeatedly call this function in a loop, while weakening the restrictions every iteration (e.g. increasing max. radius).

Terrain, topography

I feel it's somehow wrong to try to do much in this respect, given the tools we have now. But I didn't care anyway and came up with the following:

First, have a look: http://non.sense.ch/shared/terrain-sampling/

The idea is pretty straight forward: let's shoot "rays" away from the sampling position at a given radius and the desired sample-density. Then keep following the trend at a given sampling distance and stop once the trend has changed. Thus, the sampels should "halt" where an important change of the relief occurs, which is typically the top of a hill/mountain or the bottom of a valley. (so basically, we are pushing our baseball around the sample position, playing marble madness - kind of, and thus, this might take some time to compute. But it isn't too bad, certainly not if you're doing this a few times at the start of a mission)

To be able to judge the sampling position itself, which is kind a self-referential, we simply shoot the rays to us instead of away from us. Thus we can answer question's like: are we on a hilltop? Is there an elevation to the north? Is this a valley? ...

In a try to classify a position in one word, I came up with the following "scale": peak, topSlope, middleSlope, bottomSlope, valley, plain.

A peak is completely surrounded by lower grounds, a topSlope has few higher grounds, middleSlope has equally both...

To do this, there is RUBE_analyzePeriphery, which heavily relies on RUBE_samplePeriphery and also some auxilliary functions are needed:

RUBE_average = compile preprocessFileLineNumbers "RUBE\fn\fn_average.sqf";
RUBE_arrayMin = compile preprocessFileLineNumbers "RUBE\fn\fn_arrayMin.sqf";
RUBE_arrayMax = compile preprocessFileLineNumbers "RUBE\fn\fn_arrayMax.sqf";
RUBE_arrayMap = compile preprocessFileLineNumbers "RUBE\fn\fn_arrayMap.sqf";
RUBE_getPosASL = compile preprocessFileLineNumbers "RUBE\fn\fn_getPosASL.sqf";
RUBE_getALD = compile preprocessFileLineNumbers "RUBE\fn\fn_getALD.sqf";

RUBE_samplePeriphery = compile preprocessFileLineNumbers "RUBE\fn\fn_samplePeriphery.sqf";
RUBE_analyzePeriphery = compile preprocessFileLineNumbers "RUBE\fn\fn_analyzePeriphery.sqf";

RUBE_samplePeriphery:

/*
  Author:
   rübe

  Descriptions:
   samples the terrain around a given position to get an
   idea of the landform/physical features. 

   Illustration:


                      ^ 
                      x  
              /       |       \
               x      |      x
                \     |     /
                 \    x    /
                  x   |   x
                   \  |  / 
                    \ | /           
      <-x------x------O------x------x->
                    / |\
                   /  | \
                  x   |  x
                 /    x   \
                /     |    \
               x      |     x
              \       |      /
                      x
                      \   

   we're sampling N positions on a line cast from the given position
   into all directions using a uniform sector/step. This gives us the 
   opportunity to characterize the terrain around the given position, 
   for the following five outcomes can be distinguished (only three 
   if we sample only one position per direction-ray):

    0) flat:           2) ascent:       1) hill: 

       O----x----x           x                x
                            /                / \
                           x                /   \
                          /                /     x
                         O                O       


                      -2) descent      -1) pit

                         O               O       
                          \               \     x
                           x               \   /
                            \               \ /
                             x               x


    - if the last sample on a ray is the highest/lowest, we label
      it as ascent or descent respectively. Otherwise the most extrem
      point (to O) is either a hill or a pit.

    - if refine is activated (highly recommended) any points that are
      not considered flat will search for a more extreme position:
      ascent and hills for a higher, descent and pits for a lower resp.
      (thus some rays may end up with more or less the same position!)

    - thus if something is considered an ascent or a hill (descent or pit
      respectively) depends on the sample rays spacing (N samples taken 
      on a ray). So it's up to you if you can manage to do something with
      this distinction or just test for greater or smaller than zero...

      Position refinement illustration with a 90er sector (4 rays):

                         (again most extreme, but all taken samples
                           from here are less extreme than the current
                           origin, thus we stop the refine search)
                        .  /
                        | / 
                    .--(x)--.
                 .      \
                 \      |
                 |      |
           .<--- P --->(x)--->.
                 |      |\
                 |      . \
                 /      (most extreme out of the samples)
                 .

     - since multiple positions can point to the same position (e.g. a hill),
       we merge those, but height it in means of a sector (how bread/wide the
       hill is).

  Parameter(s):
   _this: parameters (array of array [key (string), value (any)])

          - required:

            - "position" (position)

          - optional:

            - "refine" (int 0 - 2)
              0: no sample refinement
              1: refine peak and valleys
              2: refine peaks only
              - default = 1

            - "gradientThreshold" (scalar)
              max. gradient below terrain is considered flat
              - default = 2.0

            - "sampleSpacing" (array of scalar)
              sample positions in m taken on each ray
              - default = [75, 250, 500]

            - "sampleRange" ([start (scalar), end (scalar)])
              - default = [0, 360]  

            - "sampleSector" (scalar)
              360 / N = number of sample-rays
              - default = 10 degree (36 sample-rays à count(sampleSpacing) samples)

            - "refinementSector" (scalar)
              360 / N = number of refinement sample-rays, where
              the most extreme is followed further (peak/valley)
              - default = 60 degree (6 sample-rays à one sample)

            - "refinementDistance" (scalar)
              length of refinement sample-rays in m. (or how exact peaks/
              valleys can be pinpointed)
              - default = 10

  Returns:
   array of [type, gradient, direction, position1, ¦ position0 (if refined)]
*/

private ["_position", "_height", "_sampleSpacing", "_sampleRange", "_sampleSector", "_gradientThreshold", "_refinementMode", "_refinementDistance"];

_position = [0,0,0];
_height = 0;

_sampleSpacing = [75, 250, 500];
_sampleRange = [0, 360];
_sampleSector = 10;

// any gradient (abs) below this threshold is
// considered "flat"
_gradientThreshold = 2.0;

_refinementMode = 1;
_refinementSector = 60; 
_refinementDistance = 10;

// read parameters
{
  switch (_x select 0) do
  {
     case "position": { _position = _x select 1; };
     case "refine": { _refinementMode = _x select 1; };

     case "gradientThreshold": { _gradientThreshold = _x select 1; };
     case "sampleRange": { _sampleRange = _x select 1; };
     case "sampleSpacing": { _sampleSpacing = _x select 1; };
     case "sampleSector": { _sampleSector = _x select 1; };
     case "refinementSector": { _refinementSector = _x select 1; };
     case "refinementDistance": { _refinementDistance = _x select 1; };
  };
} forEach _this;

_height = (_position call RUBE_getPosASL) select 2;


private ["_probe", "_refine", "_characterize"];

// pos, dir => posASL
_probe = {
  (([(_this select 0), _refinementDistance, (_this select 1)] call BIS_fnc_relPos) call RUBE_getPosASL)
};

// [pos, type(up/down)]
_refine = {
  private ["_pos", "_probes", "_p", "_i"];

  _pos = _this select 0;

  _probes = [];
  _p = 0;

  for "_i" from 0 to (360 - _refinementSector) step _refinementSector do
  {
     _probes set [_p, ([_pos, _i] call _probe)];
     _p = _p + 1;
  };

  if ((_this select 1) > 0) then
  {
     _p = [_probes, { (_this select 2) }, 0, -1, true] call RUBE_arrayMax;
     if (((_probes select _p) select 2) > (_pos select 2)) then
     {
        _pos = [(_probes select _p), (_this select 1)] call _refine;
     };
  } else {
     _p = [_probes, { (_this select 2) }, 0, -1, true] call RUBE_arrayMin;
     if (((_probes select _p) select 2) < (_pos select 2)) then
     {
        _pos = [(_probes select _p), (_this select 1)] call _refine;
     };
  };

  _pos
};



// [dir, probes] -> [typeInt, gradient, direction, position]
_characterize = {
  private ["_direction", "_probes", "_n", "_a", "_type"];
  _direction = _this select 0;
  _probes = _this select 1;
  _type = 0;
  _n = count _probes;

  if (_n == 1) exitWith
  {
     _type = 2;
     // gradient
     _a = atan ((((_probes select (_n - 1)) select 2) - _height) / (_sampleSpacing select (_n - 1)));

     switch (true) do
     {
        case ((abs _a) < _gradientThreshold): 
        { 
           _type = 0; 
        };
        case (_a < 0): 
        { 
           _type = -2; 
        };
     };

     [_type, _a, _direction, (_probes select 0)]
  };

  private ["_min", "_max", "_minH", "_maxH", "_index", "_pos", "_pos0", "_a", "_distance"];

  // extremes (indices! not values)
  _min = [_probes, { (_this select 2) }, 0, (_n - 1), true] call RUBE_arrayMin;
  _max = [_probes, { (_this select 2) }, 0, (_n - 1), true] call RUBE_arrayMax;

  // abs. height difference
  _minH = abs (((_probes select _min) select 2) - _height);
  _maxH = abs (((_probes select _max) select 2) - _height);

  _index = 0;

  if (_maxH >= _minH) then
  {
     _index = _max;
     _type = 2;
     if (_index != (_n - 1)) then
     {
        _type = 1;
     };
  } else {
     _index = _min;
     _type = -2;
     if (_index != (_n - 1)) then
     {
        _type = -1;
     };
  };

  // gradient
  _pos0 = [];
  _pos = _probes select _index;
  _a = atan (((_pos select 2) - _height) / (_sampleSpacing select _index));

  // considered flat?
  if ((abs _a) < _gradientThreshold) then
  {
     _type = 0;
  } else {
     if ((_refinementMode == 1) || ((_refinementMode == 2) && (_type > 0))) then
     {
        _pos0 = +_pos;
        _pos = [_pos, _type] call _refine;

        _distance = [_pos, _position] call RUBE_getALD;
        _a = atan (((_pos select 2) - _height) / _distance);
     };
  };

  if ((count _pos0) == 0) exitWith
  {
     [_type, _a, _direction, _pos]
  };

  [_type, _a, _direction, _pos, _pos0]
};


// scan relief
private ["_periphery", "_n", "_s", "_probes"];

_periphery = [];
_n = count _sampleSpacing;
_s = 0;

for "_i" from (_sampleRange select 0) to ((_sampleRange select 1) - _sampleSector) step _sampleSector do
{
  _probes = [];
  for "_j" from 0 to (_n - 1) do 
  {
     _probes set [_j, (([_position, (_sampleSpacing select _j), _i] call BIS_fnc_relPos) call RUBE_getPosASL)];
  };

  _periphery set [_s, ([_i, _probes] call _characterize)];
  _s = _s + 1;
};

// filter/merge relief data

// return
_periphery

RUBE_analysePeriphery:

/*
  Author:
   rübe

  Descriptions:
   retrieves information about the landform/physical features 
   inside the periphery of a given position, evaluating data 
   from RUBE_samplePeriphery.

   In case we're on a peak, most samples will refine to/follow
   valleys and we're missing all peaks lower than we are. Thus
   we kick in a backscan in this situation, gathering some more
   samples to make those peaks visible. If we are the peak, then 
   you may think of this like an act of self-awareness...

   btw. I'd suggest to force backscan anyway for better results.

   Illustration:

    1) forward scan:              2) backward scan
        (rays going outwards)         (rays coming to us)

                                          .
               \                          |
               |                          | 
               |                          |
               |                         \
        <------O------>                   O 
               |                        \  /\
               |                       /     \  
               |                      /       \ 
               /                     /         \
                                    .           .


  Anyway... we gather these points and make a very simple cluster
  "analysis" (single-scan based on distance threshold) to merge
  samples that "mean" the same spot.

  Based on the forward scan only (rays around and away from us), 
  we calculate the average gradient for each compass direction and
  we can classify/label the landform either as:

   - peak 
   - topSlope
   - middleSlope
   - bottomSlope
   - valley
   - plain

  Be aware though that these label cant be interpreted too literally.
  Eg. a peak may be a tiny hill or a really big mountain we're
  upon. Slopes just means, that there are higher and lower places
  around us; more higher places equals bottomSlop, more lower ones
  topSlop (assuming that we're pretty much on top already).
  Just keep in mind that these labels are of strategic value and may
  not equal the impressions/images you may have in mind. (e.g. you may
  need to check the ASL of returned peaks to get a better picture)

   - We return the distinctive positions separated as arrays of:
     - peaks (higher than we are)
     - peaks (lower than we are, can only be found with backward scans)
     - plains (equal height to our position)
     - valleys 

   - as long as position refinement is on for peaks and valleys, 
     these positions really are distinctive.

   - the positions are not further sorted (or filtered), for there 
     are too many usefull options (distance, height, ...). But feel 
     free to do so yourself. (e.g. filter on distance and/or rel. dir
     and then sort per height-ASL; or find the nearest peak, that has
     a good spot for an aa-site, ...)


  Have a look at the description in RUBE_samplePeriphery for more
  detailed information about the sampling process and the position-
  refinement method.


  Parameter(s):
   _this: parameters (array of array [key (string), value (any)])

          - required:

            - "position" (position)

          - optional:

            - "backScanMode" (int 0 - 2)
              0: never
              1: auto. (only for position on top of a peak)
              2: always
              - default = 1


          - optional (samplePeriphery settings):  

            - "gradientThreshold" (scalar)
              max. gradient below terrain is considered flat
              - default = 2.0

            - "sampleSpacing" (array of scalar)
              sample positions in m taken on each ray
              - default = [75, 250, 500]

            - "sampleRange" ([start (scalar), end (scalar)])
              - default = [0, 360]  

            - "sampleSector" (scalar)
              360 / N = number of sample-rays
              - default = 10 degree (36 sample-rays à count(sampleSpacing) samples)

            - "refine" (int 0 - 2)
              0: no sample refinement
              1: refine peak and valleys
              2: refine peaks only (faster if you aren't interessted in
                 valleys anyway)
              - default = 1

            - "refinementSector" (scalar)
              360 / N = number of refinement sample-rays, where
              the most extreme is followed further (peak/valley)
              - default = 60 degree (6 sample-rays à one sample)

            - "refinementDistance" (scalar)
              length of refinement sample-rays in m. (or how exact peaks/
              valleys can be pinpointed)
              - default = 10

            - "debug" (boolean)

  Returns:
   [_landform, _periphery, _mountains, _plains, _hilltops, _valleys] where:

    - 0: landform (string) 
      in ["peak", "topSlope", "middleSlope", "bottomSlope", "valley", "plain"]

    - 1: periphery (avg. gradient per compass direction) (array of scalar, N=7) 
      0=N, 1=NE, 2=E, 3=SE, 4=S, 5=SW, 6=W, 7=NW

    - 2: mountains (array) peaks higher than we are
    - 3: plains (array) considered flat to us (~same height)
    - 4: hilltops (array) peaks lower than we are
    - 5: valleys (array) valleys, where the terrain starts to raise again
*/

private ["_debug", "_position", "_height", "_backScanMode", "_landformThreshold", "_clusterThreshold"];

_debug = false;

_position = [0,0,0];
_height = 0;

_backScanMode = 1;
_landformThreshold = 0.7; // % needed to dominate/label the landform
_clusterThreshold = 50; // max. distance in m to be in the same cluster

// samplePerihpery variables
_sampleSpacing = [150, 300, 600];
_sampleRange = [0, 360];
_sampleSector = 10;
_gradientThreshold = 2.0;
_refinementMode = 1;
_refinementSector = 60; 
_refinementDistance = 10;

// read parameters
{
  switch (_x select 0) do
  {
     case "position": { _position = _x select 1; };
     case "backScanMode": { _backScanMode = _x select 1; };
     case "debug": { _debug = _x select 1; };

     // samplePeriphery variables
     case "gradientThreshold": { _gradientThreshold = _x select 1; };
     case "sampleRange": { _sampleRange = _x select 1; };
     case "sampleSpacing": { _sampleSpacing = _x select 1; };
     case "sampleSector": { _sampleSector = _x select 1; };

     case "refine": { _refinementMode = _x select 1; };
     case "refinementSector": { _refinementSector = _x select 1; };
     case "refinementDistance": { _refinementDistance = _x select 1; };
  };
} forEach _this;


_height = (_position call RUBE_getPosASL) select 2;

private ["_mountains", "_hilltops", "_plains", "_valleys", "_samples", "_n", "_f", "_i", "_landform", "_lfPlains", "_lfMountains", "_lfValleys", "_peripehery", "_triangleBackScan", "_squareBackScan", "_clusterInsert"];

_mountains = 0; // peaks higher than we are
_hilltops = 0;  // peaks lower than we are (only with backscanning)
_plains = 0;
_valleys = 0;

_samples = [
  ["position", _position],
  ["refine", _refinementMode],
  ["sampleSpacing", _sampleSpacing],
  ["sampleRange", _sampleRange],
  ["sampleSector", _sampleSector],
  ["gradientThreshold", _gradientThreshold],
  ["refinementSector", _refinementSector],
  ["refinementDistance", _refinementDistance]
] call RUBE_samplePeriphery;

_n = count _samples;
_f = 1;
if (_n > 0) then
{
  _f = 1 / _n;
};

for "_i" from 0 to (_n - 1) do
{
  switch (true) do
  {
     case (((_samples select _i) select 0) > 0):
     {
        _mountains = _mountains + 1;
     };
     case (((_samples select _i) select 0) < 0):
     {
        _valleys = _valleys + 1;
     };
     default
     {
        _plains = _plains + 1;
     };
  };
};

// landform (relative to sample center)
/*
  [
     <string> (peak, topSlope, middleSlope, bottomSlope, plain, valley)
     flat,             0 == rough; 1 == flat/plains 
     hilltops/upper,   0 == no hills, 1 == 100% hills
     valleys/lower     0 == no valleys, 1 == 100% valleys
  ]
*/
// [label, plainsValue, mountainsValue, valleysValue]
_landform = "";

_lfPlains = _plains * _f;
_lfMountains = _mountains * _f;
_lfValleys = _valleys * _f;

// landform label
switch (true) do
{
  case (_lfPlains > _landformThreshold): { _landform = "plain"; };
  case (_lfMountains > _landformThreshold): { _landform = "valley"; };
  case (_lfValleys > _landformThreshold): { _landform = "peak"; };
  default 
  { 
     if ( (abs (_lfMountains - _lfValleys)) > 0.2) then
     {
        if (_lfValleys > _lfMountains) then
        {
           _landform = "topSlope";
        } else {
           _landform = "bottomSlope";
        };
     } else {
        _landform = "middleSlope";
     };
  };
};


// periphery (from the pov of the sample center)
// we calculate the average gradient of a 1/8 compass direction,
// we take all samples inside a 90 degree angle (thus, overlapping
// by 45 degree)
// 0=N, 1=NE, 2=E, ... NW=7
_periphery = [[],[],[],[],[],[],[],[]];
{
  if (((_x select 2) >= 315) || ((_x select 2) <= 45)) then
  {
     (_periphery select 0) set [(count (_periphery select 0)), (_x select 1)];
  };
  if (((_x select 2) >= 0) && ((_x select 2) <= 90)) then
  {
     (_periphery select 1) set [(count (_periphery select 1)), (_x select 1)];
  };
  if (((_x select 2) >= 45) && ((_x select 2) <= 135)) then
  {
     (_periphery select 2) set [(count (_periphery select 2)), (_x select 1)];
  };
  if (((_x select 2) >= 90) && ((_x select 2) <= 180)) then
  {
     (_periphery select 3) set [(count (_periphery select 3)), (_x select 1)];
  };
  if (((_x select 2) >= 135) && ((_x select 2) <= 225)) then
  {
     (_periphery select 4) set [(count (_periphery select 4)), (_x select 1)];
  };
  if (((_x select 2) >= 180) && ((_x select 2) <= 270)) then
  {
     (_periphery select 5) set [(count (_periphery select 5)), (_x select 1)];
  };
  if (((_x select 2) >= 225) && ((_x select 2) <= 315)) then
  {
     (_periphery select 6) set [(count (_periphery select 6)), (_x select 1)];
  };
  if (((_x select 2) >= 270) || ((_x select 2) <= 0)) then
  {
     (_periphery select 7) set [(count (_periphery select 7)), (_x select 1)];
  };
} forEach _samples;

// calculate avg. gradient
[
  _periphery, 
  {
     if ((count _this) == 0) exitWith
     {
        0
     };
     ([_this] call RUBE_average)
  }
] call RUBE_arrayMap;



// we need to take another set of samples if we're on
// a hilltop, to get a better picture (backscanMode auto)
if ((_backScanMode == 1) && (_landform == "peak")) then
{
  _backScanMode = 2;
};


// equilateral triangle backscan
// 3 periphery scans à 180 degree (+ (540 degree / _sampleSector) rays)
_triangleBackScan = {
  private ["_c", "_a", "_a2", "_ha", "_hb"];

  _c = (_sampleSpacing select ((count _sampleSpacing) - 1)) * 3.3;
  _a = (cos 30) * _c;
  _a2 = _a * 0.5;
  _ha = (tan 30) * _a2;
  _hb = (_a2 * (sqrt 3)) - _ha;

  [
     [
        [
           (_position select 0),
           ((_position select 1) + _hb)
        ],
        [90, 270]
     ],
     [
        [
           ((_position select 0) - _a2),
           ((_position select 1) - _ha)
        ],
        [-30, 150]
     ],
     [
        [
           ((_position select 0) + _a2),
           ((_position select 1) - _ha)
        ],
        [-150, 30]
     ]
  ]
};

// square backscan 
// 4 periphery scans à 90 degree (+ (360 degree / _sampleSector) rays)
_squareBackScan = {
  private ["_c"];

  _c = (_sampleSpacing select ((count _sampleSpacing) - 1)) * 1.2;

  [
     [
        [
           ((_position select 0) - _c),
           ((_position select 1) + _c)
        ],
        [90, 180]
     ],
     [
        [
           ((_position select 0) + _c),
           ((_position select 1) + _c)
        ],
        [180, 270]
     ],
     [
        [
           ((_position select 0) + _c),
           ((_position select 1) - _c)
        ],
        [270, 360]
     ],
     [
        [
           ((_position select 0) - _c),
           ((_position select 1) - _c)
        ],
        [0, 90]
     ]
  ]
};

if (_backScanMode == 2) then
{  
  {

     {
        // plains from backscans are meaningless
        if ((_x select 0) != 0) then
        {
           _samples set [(count _samples), _x];
        };
     } forEach ([
        ["position", (_x select 0)],
        ["refine", _refinementMode],
        ["sampleSpacing", _sampleSpacing],
        ["sampleRange", (_x select 1)],
        ["sampleSector", _sampleSector],
        ["gradientThreshold", _gradientThreshold],
        ["refinementSector", _refinementSector],
        ["refinementDistance", _refinementDistance]
     ] call RUBE_samplePeriphery);

  } forEach ([] call _triangleBackScan);
  // _triangleBackScan / _squareBackScan 
};


// find clusters (single scan)
// [position, value]
_clusterInsert = {
  private ["_index", "_n", "_i"];
  _index = -1;
  _n = count (_this select 1);

  for "_i" from 0 to (_n - 1) do
  {
     if ((((_this select 0) select 3) distance 
          (((_this select 1) select _i) select 0)) < _clusterThreshold) exitWith
     {
        _index = _i;
        switch (true) do
        {
           case (((_this select 0) select 0) > 0):
           {
              if ( (((_this select 0) select 3) select 2) >
                   ((((_this select 1) select _i) select 0) select 2)) then
              {
                 ((_this select 1) select _i) set [0, ((_this select 0) select 3)];
              };
           };
           case (((_this select 0) select 0) < 0):
           {
              if ( (((_this select 0) select 3) select 2) <
                   ((((_this select 1) select _i) select 0) select 2)) then
              {
                 ((_this select 1) select _i) set [0, ((_this select 0) select 3)];
              };
           };
        };

     };
  };

  if (_index < 0) then
  {
     (_this select 1) set [_n, [((_this select 0) select 3), "unknown"]];
  };
};

_mountains = []; // peaks higher than we are
_hilltops = [];  // peaks lower than we are (only with backscanning)
_plains = [];
_valleys = [];

{
  switch (true) do
  {
     case ((_x select 0) > 0): 
     {
        if (((_x select 3) select 2) > _height) then
        {
           [_x, _mountains] call _clusterInsert;
        } else {
           [_x, _hilltops] call _clusterInsert;
        };
     };
     case ((_x select 0) < 0): 
     {
        [_x, _valleys] call _clusterInsert;
     };
     default 
     {
        [_x, _plains] call _clusterInsert;
     };
  };

  if (_debug) then
  {
     [
        ["position", (_x select 3)],
        ["type", "mil_dot"],
        ["color", "ColorWhite"],
        ["size", 0.3]
     ] call RUBE_mapDrawMarker;
     if ((count _x) > 4) then
     {
        [
           ["start", (_x select 3)],
           ["end", (_x select 4)],
           ["color", "ColorWhite"],
           ["size", 3]
        ] call RUBE_mapDrawLine;
        [
           ["position", (_x select 4)],
           ["type", "mil_dot"],
           ["color", "ColorWhite"],
           ["size", 0.6]
        ] call RUBE_mapDrawMarker;
     };
  };

} forEach _samples;


// return
[_landform, _periphery, _mountains, _plains, _hilltops, _valleys]

You should get the idea without posting the auxilliary functions here too. Though you may get the current state of my RUBE function library (including these) over here: http://non.sense.ch/shared/rube-wip/

I hope this is usefull to some of you - or at least a strong hint at BIS that we really could need some help here.

on random (use specific buildings!)

Start with a random world position. Settle for an area of operation, that is a reasonably sized radius. Abuse nearestObjects together with a typed building-library until you're funny. Something along these lines:

_buildingPool = [
 // strategic enterable
  "Land_A_BuildingWIP",
  "Land_A_Castle_Bastion",
  "Land_A_Castle_Bergfrit",
  "Land_A_Castle_Donjon",
  "Land_A_Castle_Gate",
  "Land_A_Castle_Wall2_30",
  "Land_A_Office01",
  "Land_A_Office02",
  "Land_HouseV2_02_interier",
  "Land_HouseV2_04_interier",
  "Land_stodola_open",
  "Land_stodola_old_open",
  "Land_Wall_Gate_Kolchoz",
  "Land_Farm_Cowshed_a",
  "Land_Farm_Cowshed_b",
  "Land_Farm_Cowshed_c",
  "Land_Barn_Metal",
  "Land_Barn_W_01",
  "Land_Barn_W_02",
  "Land_A_GeneralStore_01",
  "Land_A_GeneralStore_01a",
  "Land_HouseV_1I4",

 // strategic targets
  "Land_A_FuelStation_Shed",
  "Land_A_FuelStation_Build",
  "Land_repair_center",
  "Land_A_Hospital",
  "Land_Church_03",
  "Land_Rail_House_01",

 // heavy/strat. industry
  "Land_Ind_SawMill", "Land_Ind_SawMillPen",
  "Land_Ind_Quarry",
  "Land_Ind_Pec_01",
  "Land_Ind_Pec_02",
  "Land_Ind_Pec_03a",
  "Land_Ind_Pec_03b",
  "Land_Ind_Mlyn_01", "Land_Ind_Mlyn_02", "Land_Ind_Mlyn_03",
  "Land_Ind_Expedice_1", "Land_Ind_Expedice_2", "Land_Ind_Expedice_3",
  "Land_Ind_Vysypka",
  "Land_Shed_Ind02",
  "Land_Ind_Stack_Big",
  "Land_a_stationhouse",
  "Land_Tovarna2",
  "Land_Ind_Workshop01_01",
  "Land_Ind_Workshop01_02",
  "Land_Ind_Workshop01_04",
  "Land_Hangar_2",
  "Land_Hlidac_budka",

 // military
  "Land_Barrack2",
  "Land_Mil_Barracks",
  "Land_Mil_Barracks_L",
  "Land_Mil_Barracks_i",
  "Land_Mil_Guardhouse",
  "Land_Mil_ControlTower",
  "Land_Mil_House",
  "Land_Hlidac_budka",
  "Land_SS_hangar",

 // watchtowers
  "Land_Ind_IlluminantTower",
  "Land_Misc_deerstand",
  "Land_vez",
  "Land_komin",

 // radio
  "Land_A_TVTower_Base",
  "Land_Vysilac_FM",
  "Land_telek1",

 // power
  "Land_Ind_Pec_03a",
  "Land_trafostanica_velka",
  "Land_sloup_vn",

 // bridges
  "Land_rails_bridge_40",

 // port, docks
  "land_nav_pier_M_fuel",
  "land_nav_pier_m_end",
  "land_nav_pier_m_1",
  "Land_nav_pier_m_2",
  "land_nav_pier_C_R30",
  "land_nav_pier_C_R10",
  "land_nav_pier_C_R",
  "land_nav_pier_c_big",
  "land_nav_pier_c_90",
  "land_nav_pier_c_270",
  "land_nav_pier_c_t15",
  "land_nav_pier_c_t20",
  "land_nav_pier_c2",
  "land_nav_pier_c2_end",
  "Land_NAV_Lighthouse",
  "Land_NAV_Lighthouse2",
  "Land_Nav_Boathouse", "Land_Nav_Boathouse_PierL", "Land_Nav_Boathouse_PierR", "Land_Nav_Boathouse_PierT",
  "land_nav_pier_F_23",
  "land_nav_pier_c",

 // statues/symbols of power
  "Land_A_statue01",
  "Land_A_statue02"
];

// ...

// search strategic buildigs/objects in aoo
_aooBuildings = nearestObjects [(_aooPosition call RUBE_getPosASL), _buildingPool, _aooRadius];
{
  _icon = "mil_dot";
  _color = "ColorBlack";
  _c = typeOf _x;
  switch (true) do
  {
     // power(-lines)
     case (_c in ["Land_Ind_Pec_03a", "Land_trafostanica_velka", "Land_sloup_vn"]): 
     {
        _color = "ColorYellow";
        _icon = "mil_dot";
     };
     // radio / antennas
     case (_c in ["Land_A_TVTower_Base", "Land_Vysilac_FM", "Land_telek1"]): 
     {
        _color = "ColorRed";
        _icon = "mil_objective";
     };
     // castle
     case (_c in ["Land_A_Castle_Bastion", "Land_A_Castle_Bergfrit", "Land_A_Castle_Donjon", "Land_A_Castle_Gate", "Land_A_Castle_Wall2_30"]): 
     {
        _color = "ColorBrown";
        _icon = "mil_marker";
     };
     // port / docks
     case (_c in ["land_nav_pier_M_fuel", "land_nav_pier_m_end", "land_nav_pier_m_1", "Land_nav_pier_m_2", "land_nav_pier_c_big", "land_nav_pier_c2_end", "land_nav_pier_C_R30", "land_nav_pier_C_R10", "land_nav_pier_C_R", "land_nav_pier_c2", "land_nav_pier_c_90", "land_nav_pier_c_270", "land_nav_pier_c_t15", "land_nav_pier_c_t20", "Land_NAV_Lighthouse", "Land_NAV_Lighthouse2", "Land_Nav_Boathouse", "Land_Nav_Boathouse_PierL", "Land_Nav_Boathouse_PierR", "Land_Nav_Boathouse_PierT", "land_nav_pier_F_23", "land_nav_pier_c"]):
     {
        _color = "ColorPink";
        _icon = "Boat";
     };
  };
  [
     ["position", (position _x)],
     ["size", 0.618],
     ["type", _icon],
     ["color", _color],
     ["text", format["%1", (typeOf _x)]]
  ] call RUBE_mapDrawMarker;
} forEach _aooBuildings;

see: http://non.sense.ch/shared/rube-world/

What you then have is a list of all interesting buildings in the current AOO. Then you may either build your main random mission around some of these, or use them for side missions. Pick a random building from this pool and check if you have a certain mission type for this building (another pool - this time of functions).

E.g. you may have an antenna in your AOO. Then you have a pool of "mission-setup functions" for each building type. E.g. a destroy antenna mission, a secure/use/propaganda mission. You may simply set up some troops there, securing the antenna - anything randomly picked.

You may iterate over all found buildings in your AOO and pick every nth object to have something there, use them to setup patrols, hideouts...

Sure, you need to adapt and classify the building-library for each world that uses another set of buildings. And yeah, writing missions around these buildings is a timeconsuming task. But, you might start with generic missions, that work with all buildings of a certain type, mixing in more specific ones as you go.

The basic idea is, to populate the world in your AOO in a coherent way. Found a Kolchoz in your AOO? Spawn a farmer, later send some insurgents or russians this way to take over his farm. Or hide some troops there, that will join you if you find them.

Industrial buildings are very fruitful too. Or port/dock pieces. Powerstations and -lines, fuelstations, military infrastructure, hospitals, castles, bridges, radiotowers, watchtowers. Use the interiors, the roofs of certain buildings...

Starting with the available buildings and building a random mission around them offers great possibilities, for again, the key is to respect the environment, to interpret the world in a meanigful way so the random actions do not occur to be "random" or arbitrary anymore.

tweak on,

rübe

Edited by ruebe
new command getTerrainHeightASL finally available, yay!
  • Like 3

Share this post


Link to post
Share on other sites

ruebe

thanx for sharing very interesting info

Edited by nikita320106

Share this post


Link to post
Share on other sites

Some pretty damn nice info and scripts in this thread. Deserves a friendly bump.

Share this post


Link to post
Share on other sites

Just something I just whipped up, trying to use selectBestPlaces as a means to give us some of the information the missing developer command diag_toggle (notice diag_toggle missing) gives. The basic idea is to show player the actual ambient values when trying to make ambient sound mods, which I guess is it's main function. Using Gaia's debug, you can also edit on the fly a custom simple expression assigned as string to global variable named expr. It should make life a lot easier trying to figure out volumes and blends and complex simple expressions (lol). If expr produces a result greater than zero, an arrow points to the location (only 5 meter search radius so that it is close to player).

Ok, the actual script, just execVM it as any other script:

createCenter sideLogic;
_gchat = createGroup sideLogic;
_chatlogic = _gchat createUnit ["Logic",[0,0,0],[],0,"NONE"];
_obj = "Sign_arrow_down_large_EP1" createVehicle [0,0,0];
expr = "forest factor [0.8,0.2]";
//expr can be written in Gaia's debug console. While running, try pasting the following:
//expr = "forest interpolate [0.8,0.2,0.1,0.4]"
//for more on simple expressions, see http://community.bistudio.com/wiki/Simple_Expression
//unfortunately this method only works for ambient parameters, not vehicle parameters. I wish...
_round = {
private "_ret";
_ret = (round (_this * 1000))/1000;
_ret
};
waitUntil {player == player};
while {true} do {
_forest = selectBestPlaces [getPos vehicle player,5,"(forest)",1,1] select 0 select 1;
_trees = selectBestPlaces [getPos vehicle player,5,"(trees)",1,1] select 0 select 1;
_meadow = selectBestPlaces [getPos vehicle player,5,"(meadow)",1,1] select 0 select 1;
_hills = selectBestPlaces [getPos vehicle player,5,"(hills)",1,1] select 0 select 1;
_houses = selectBestPlaces [getPos vehicle player,5,"(houses)",1,1] select 0 select 1;
_sea = selectBestPlaces [getPos vehicle player,5,"(sea)",1,1] select 0 select 1;
_night = selectBestPlaces [getPos vehicle player,5,"(night)",1,1] select 0 select 1;
_rain = selectBestPlaces [getPos vehicle player,5,"(rain)",1,1] select 0 select 1;
_windy = selectBestPlaces [getPos vehicle player,5,"(windy)",1,1] select 0 select 1;
_deadBody = selectBestPlaces [getPos vehicle player,5,"(deadBody)",1,1] select 0 select 1;
_expr = selectBestPlaces [getPos vehicle player,5,expr,1,1] select 0;
_chatlogic globalChat format ["forest: %1", _forest call _round];
_chatlogic globalChat format ["trees: %1", _trees call _round];
_chatlogic globalChat format ["meadow: %1", _meadow call _round];
_chatlogic globalChat format ["hills: %1", _hills call _round];
_chatlogic globalChat format ["houses: %1%2", _houses call _round];
_chatlogic globalChat format ["sea: %1", _sea call _round];
_chatlogic globalChat format ["night: %1", _night call _round];
_chatlogic globalChat format ["rain: %1", _rain call _round];
_chatlogic globalChat format ["windy: %1", _windy call _round];
_chatlogic globalChat format ["deadBody: %1", _deadBody call _round];
hintSilent format ["Custom Expression:\n%1\n\nYields:\n%2\n\nAt 2D Position:\n%3", expr, _expr select 1 call _round, _expr select 0];
if (_expr select 1 > 0) then {
	_obj setPos [_expr select 0 select 0, _expr select 0 select 1, 0];
} else {
	_obj setPos [0,0,0];
};
sleep 0.5;
};

Edited by CarlGustaffa

Share this post


Link to post
Share on other sites

This is very interesting however very confusing for someone with limited scripting knowlege! So i have a question?

I have an Enemy Camp(3 tents/3 trucks and a fire place)! Is it possible to beable to use this Function to write a simple script that will search for a "Random" and "Flat" place to place my Camp with in a Marker zone? My goal is to have a few randomly spawning camps in the Mountains inwhich will Spawn Enemy troops. And even more, it would be nice to have it so the Enemy Camps keep spawning Enemy Troops till the Enemy Camp is destroyed, maby work by having the script look for all 3 tents to be destroyed, and once destroyed, no enemy troops can be Randomly Spawned???

Edited by soldier2390

Share this post


Link to post
Share on other sites

Have someone found a way how to find a treeline?

Share this post


Link to post
Share on other sites
Have someone found a way how to find a treeline?

Sure. Check my library which has implemented (see /RUBE/lib/world.sqf):

  • RUBE_WORLD_isTree
  • RUBE_WORLD_isBush
  • RUBE_WORLD_isPlant
  • RUBE_WORLD_isStone
  • ...

...which you may use to filter a list of objects like in here or something:

// [number, pos, start, max] => objects
_findObjects = {
  private ["_n", "_pos", "_start", "_max", "_delta", "_objects"];
  _n = _this select 0;
  _pos = _this select 1;
  _radius = _this select 2;
  _max = _this select 3;

  _delta = (_max - _radius) * 0.2; // 5 steps
  _objects = [];

  while {(_radius <= _max)} do
  {
     _objects = [
        (nearestObjects [_pos, [], _radius]),
        {
           // object type checks
           if (_selectTree && (_this call RUBE_WORLD_isTree)) exitWith { true };
           if (_selectBush && (_this call RUBE_WORLD_isBush)) exitWith { true };
           if (_selectStone && (_this call RUBE_WORLD_isStone)) exitWith { true };
           false
        }
     ] call RUBE_arrayFilter;

     _objects = [_objects, (_spotRadius * 2.5)] call RUBE_distanceFilter;

     if ((count _objects) >= _n) exitWith {};
     _radius = _radius + _delta;
  };

  _objects
};

...it shouldn't take too many modifications to search for tree-lines, though you have to come up with a workable definition of what is a tree-line and what isn't. Is a forest a tree-line too? :p

Btw. the isTree, isPlant, ... -functions work only with stock-objects found in A2 and OA. So custom worlds won't work, unless you extend world.sqf with the needed classes.

Share this post


Link to post
Share on other sites

Thanks, ruebe. Those could be handy :) I meant the edge of forest, so I could spawn hidden machine guns and cannons to guard open areas. I will work on it, but could be a challenging to find the right way without taking too much performance away. Maybe a good way would be to search a forest first then seek nearest open area by inceasing the search radius with little steps until its found. Then count the angle from forest to the found open area with BIS_fnc_dirTo and go to that direction with open area check to mesure it´s size to prevent static guns spawning near to small forest roads and such.

btw. I have been playing much with "selectBestPlaces" lately. Very great command. I hope its expanded in ArmA3 with new types, including ground´s slope and deep forest (that would never select a position near the edge of forest).

Edited by SaOk

Share this post


Link to post
Share on other sites
I meant the edge of forest, so I could spawn hidden machine guns and cannons to guard open areas.

Ah, very well then. Some ideas:

  • A cheap way could be to make good use of the command surfaceType. Given you have the position to be guarded, first you look if there is a forest nearby by iterating over the direction at some distance. And do some iterations, while increasing that probing-distance. If you have a forest, start probing the surfaceType from the forest into the direction of the open area at some interval and wait until surfaceType isn't of type forest anymore. Then you could further precise your search, by looking for all objects at that position and counting trees or something.
  • You could elaborate on that sampling-iteration from the forest to the open area, by not looking at the surfaceType, but by counting trees in a small radius around the sampling positions. Once that number drops below a certain threshold (or even to zero), you now you've reached the open area. Aslong as your sampling radius - in which you catch and filter all objects to look if they're trees or not - is small, this shouldn't take too much processing power. And if you don't plan to do that while the mission is already running, then it's actually no problem at all. Put on the loadingScreen and that's calculated in virtually no time.
  • check out fn_maxFlatEmptyArea.sqf (which tries to maximize isFlatEmpty at a given position) from my library and try it at such an open position (prior to spawning anything else or this won't work). This might give you some idea about the borders of such an area. Maybe with multiple probes with displaced positions. And remember that isFlatEmpty has a parameter which allows it's sample-position to be slightly adapted to a better position if possible - which is why this might actually work. :)
  • Divide and conquer :cool:

Tweak on!

:D

Share this post


Link to post
Share on other sites

Thanks, I am definitely going to check that surfaceType, sounds very handy command, and the rest are good ideas too. I am creating a long dynamic mission with much stuff created on the fly. It will be mixture of dynamic and fixed stuff.

Share this post


Link to post
Share on other sites
Thus we can mix factors from three major different dimensions: the geographic dimension, time and the weather. (plus the deadBody thing, which looks like a "bonus" dimension implemented especially for the flies)

Or it would be useful in a zombie mission (zombies wandering toward dead bodies) Maybe there's a potential game mechanic in there somewhere (disposing of corpses so it doesn't attract the (un)dead). Sry, just thought of it so I thought I'd post before it gets forgotten :)

Share this post


Link to post
Share on other sites

Two new expressions supported since a3: waterDepth & camDepth

and selectbestplaces also support operators like randomgen, factor and interpolate

e.g. "(4 * (WaterDepth interpolate [1,30,0,1]))" etc

Share this post


Link to post
Share on other sites

this post is so crazy useful. But i have no idea how the new command like interpolate would work here.

Share this post


Link to post
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now

×