Jump to content

dwringer

Member
  • Content Count

    331
  • Joined

  • Last visited

  • Medals

Posts posted by dwringer


  1. 4 hours ago, zagor64bz said:

    It works..kinda. a group spawn on my position and move to random marker, but what I really want is the "hvt" to move...(with the spawned group is also ok..even better)

    You haven't given us all of your code.  If _group represents the group of hvt then the code would function as you desire.  Since you are spawning a group and _group refers to that, of course the waypoints are added to _group.  What you probably want is to run that script with _group assigned to "group hvt" and it will add the waypoints you want.  Sorry I can't be more explicit without seeing exactly the code you are running and exactly how you are calling it.


  2. An update on the EDEN GUI errors I've been having with the dev branch:

     

    With "Small" Interface size and 4k resolution, none of the pull-down menus are functional.  They still can have a set value, but the expanding menu never appears so it is impossible to select different options.  Pressing alphabet keys will set some of the values inconsistently based on their first letter (?).

     

    With the interface set to "Very Small" or "Normal" the pull-down menus function properly with no problems.

     

    I have tried this in 32- and 64-bit, with and without mods.


  3. When you have that array of houses, you need to iterate through it to build an average position as a new array.  Each position is a 3-element array, so your new array will also be 3-elements: consider each element as its own unique 1-dimensional position (x, y, and z). 
     

    private ["_meanPosition", "_length", "_component"];
    _meanPosition = [];
    _length = count _findhouses;
    for "_i" from 0 to 2 do {
      _component = 0;
      for "_j" from 0 to (_length - 1) do {
        _component = _component + (((position (_findhouses select _j)) select _i) / _length);
      };
      _meanPosition pushBack _component;
    };

    This is the same as adding all the x's and dividing by the total, then adding all the y's and dividing by the total, and finally adding all the z's and dividing the total, to yield three position averages (The average 3D position).  In this algorithm division is done at each step instead of at the end to avoid accumulating really large sums that could cause problems.

    Here is a function I have been using, which you can paste in init.sqf or somewhere like that:
     

    fnc_vector_mean = {
    	/* Find the mean vector of a list of vectors */
    	_vectors = _this;
    	private ["_alen", "_component", "_mean"];
    	_mean = [];
    	_alen = count _vectors;
    	for "_i" from 0 to 2 do {
    		_component = 0;
    		for "_j" from 0 to (_alen - 1) do {
    			_component = _component +
    				(((_vectors select _j) select _i) / _alen);
    		};
    		_mean pushBack _component;
    	};
    	_mean
    };

    That is only for 3D vectors, so the name is not very apt, but it would not be complicated to extend it (but you don't need to in this case).

    • Like 2

  4. Heh, though I am flattered, that is currently somewhat outside of my intended scope with this project.  Since all users of this system have to do is drop the source folders into their mission and add a couple of lines to the init.sqf, it should offer almost as smooth an experience as if it were integrated with an addon.  Since much of the system's power derives from creating one's own classes, users are expected to dig into their mission folder somewhat anyway, although typically this need not go much farther than simple drag'n'drop.

     

    I've been working with this system for the last several days now and can report there will be at least one significant addition to the next release (it is already available on GitHub from the master branch's root): A version of fnc_tell that uses spawn instead of call to execute methods.  This returns a handle to the new execution thread and lets much more complicated methods run asynchronously. The new function is called fnc_tells and otherwise works the same way as fnc_tell.

     



     

    This function was created to aid in a new module with which I've been experimenting as a test of the class system's robustness. That new system, composed primarily of classes, can be found on GitHub in the "opti/" folder and provides a basic framework for running multi-objective optimization algorithms.  Don't expect high-performance computation here, as not all the systems are well-optimized (or ever going to be extremely fast, honestly). You can, however, expect usable results.  More detailed documentation on this is to come later but it could definitely be worth taking a look at.  A quick and dirty example is provided that relies on the mkcivs module (also on GitHub) and optimizes positions around the player's group location for partial LOS and nearby civilians in the global array "civArray".

     

    That example is illustrated here:


     

    [An example mission and more will be coming soon]

     

     

    EDIT: Ugh... starting to see how ArmA can do "interesting" things with its objects.  Fixed the Optimizer example - _this inside the spawn was not behaving the same way as the object it was supposed to represent by the end.  Not sure why because it seemed to work for the first loop of calls and then return an old version for the last call.  Using the actual object name instead fixed the problem in this case.  I regret nothing!

    • Like 1

  5. Thank you for the advice, and it is an honor to be recognized by such an illustrious forum member.  You make the discord sound just so inviting, heh, but I might stick my head in there as I am sure you're right that there is much to gain. 

     

    I'm not too confident in any of this stuff yet as I'm the only one to my knowledge to be testing it and I never went past trivialities until after I released v1.0 of this class system.  I've been trying to break it, though, for the past few days, and surprisingly it has proven robust and a lot faster than I had expected considering the number of function calls going on at times.

     

    I share some cynicism about OO too, and there is something to be said for avoiding it, but it is still the right tool for certain jobs and exactly the tool I was wanting in this case.  I had checked out a couple of other OO packages in SQF, but didn't see anything as high-level and dynamic (or, some might say, hackish and horrifically unsafe).... This is pretty much an abomination of an homage to Python, mixed with Lisp, slopped together like Christmas dinner leftovers.  But as far as I can tell, it works.

     

    Thanks again; I still am not committing to anything more than a low profile and a disclaimer that this might not satisfy every use case for now.  But, as time permits, I am working on several systems using this class module, and will be sure to post more information as it is available.

     

    I also put everything under the MIT license, and it may be painfully obvious that I did not follow the typical community naming convention of prefixing my initials to any function or variable names.  That is to emphasize how this is meant to be low-level, experimental, and open to all to make of it what you will.  I must leave it at this for now until I get some non-trivial applications going and test things more thoroughly.

    • Like 1

  6. A Tutorial Walkthrough - The Marker Class:

    In order to demonstrate the power of this Class system, and hopefully make it seem more approachable, I will walk through creation of a Marker class that adds the much-desired (by me, at least) functionality to instantiate a marker once and then have unlimited freedom to manipulate it, hide it, or show it again.  Without using a Marker class, it takes several calls and the managing of complicated data structures to accomplish what is done here in just a couple of short lines.

     

    The Marker class will inherit from the ObjectRoot class (classdef\ObjectRoot.hpp) that is provided with the class system files.  ObjectRoot gives us the ability to send the messages "_getf" <value> and "_setf" <key> <value> [sent with the fnc_tell function] for conveniently managing local instance variables.

     

    This walkthrough guides us through the creation of classdef\Marker.hpp, which can be located at https://github.com/dwringer/a3system/blob/master/classdef/Marker.hpp

     

    Initialization:

    Following the BIS Community Wiki documentation for createMarker, we find that the following minimum information is required to make a new marker:

    • name
    • position
    • shape
    • type (if ICON)
    • size (if RECTANGLE, ELLIPSE)
     

    We wish to simplify marker creation as much as possible, so here we will no longer take "marker name" from the user as a creation parameter.  Instead, we will keep a global variable called MarkerIndex and increment it for a unique value we can append to the name of each new marker.



    MarkerIndex = 0;


     

    Our initialization method for the new Marker class will take the remaining necessary parameters, and we will allow nil to be used in place of "type" or "size" depending on which kind of marker is being made (area or icon).



    DEFCLASS("Marker") ["_self", "_position", "_shape", "_type", "_size"] DO {
    /* Initialize marker parameters with the minimum required info */
    SUPER("ObjectRoot", _self);
    [_self, "_setf", "name",
    format ["MarkerInstance%1", MarkerIndex]] call fnc_tell;
    MarkerIndex = MarkerIndex + 1;
    [_self, "_setf", "position", _position] call fnc_tell;
    [_self, "_setf", "shape", _shape] call fnc_tell;
    if (not isNil "_type") then {
    [_self, "_setf", "type", _type] call fnc_tell;
    };
    if (not isNil "_size") then {
            [_self, "_setf", "size", _size] call fnc_tell;
    };
            _self
    } ENDCLASS;


     

    With this, we can create new Marker instances at will with a call like:



    //mkr = ["Marker", <position>, <shape>, <type>, <size>] call fnc_new;
    mkr = ["Marker", position player, "ELLIPSE", nil, [10, 10]] call fnc_new;


     

    Methods:

    This marker will still not be visible - all we have done is configure the new instance.  We still need a "show" method to reveal the marker on the map.  This could be tacked onto the end of our initializer as well, but it needs to exist as a

    separate method.



    DEFMETHOD("Marker", "show") ["_self"] DO {
    /* Create the marker using the loaded configuration */
    private ["_marker", "_name", "_position", "_shape", "_type", "_size",
                     "_brush", "_color", "_direction", "_text", "_alpha"];
    _name = [_self, "_getf", "name"] call fnc_tell;
    _position = [_self, "_getf", "position"] call fnc_tell;
    _marker = createMarker [_name, _position];
    _shape = [_self, "_getf", "shape"] call fnc_tell;
    _type = [_self, "_getf", "type"] call fnc_tell;
    _size = [_self, "_getf", "size"] call fnc_tell;
    _brush = [_self, "_getf", "brush"] call fnc_tell;
    _color = [_self, "_getf", "color"] call fnc_tell;
    _direction = [_self, "_getf", "direction"] call fnc_tell;
    _text = [_self, "_getf", "text"] call fnc_tell;
    _alpha = [_self, "_getf", "alpha"] call fnc_tell;
    _marker setMarkerShape _shape;
    if (not isNil "_type") then {
    _marker setMarkerType _type;
    };
    if (not isNil "_size") then {
    _marker setMarkerSize _size;
    };
    if (not isNil "_brush") then {
    _marker setMarkerBrush _brush;
    };
    if (not isNil "_color") then {
    _marker setMarkerColor _color;
    };
    if (not isNil "_direction") then {
    _marker setMarkerDir _direction;
    };
    if (not isNil "_text") then {
    _marker setMarkerText _text;
    };
    if (not isNil "_alpha") then {
    _marker setMarkerAlpha _alpha;
    };
    [_self, "_setf", "marker", _marker] call fnc_tell;
    } ENDMETHOD;


     

    You can see our show method reads a lot more variables than what we configured in the initializer.  This lets us set more than the minimum required for a marker by setting the appropriate instance variables (with "_setf" calls).  More on that in a moment.

     

    A call to



    [mkr, "show"] call fnc_tell;


    will now reveal the marker instance on the map.  In order to hide the marker again, we will use another method: "hide".



    DEFMETHOD("Marker", "hide") ["_self"] DO {
    /* Remove marker from the map */
    deleteMarker ([_self, "_getf", "name"] call fnc_tell);
    [_self, "_setf", "marker", nil] call fnc_tell;
    } ENDMETHOD;


     

    You may note that reconfiguring the marker instance variables using "_setf" will not have any effect on the existing marker on the map.  For that reason, we simply create a "redraw" method to call "hide" and "show" in succession whenever the marker is already visible.



    DEFMETHOD("Marker", "redraw") ["_self"] DO {
    /* If shown, hide and then recreate the marker */
    private ["_marker"];
    _marker = [_self, "_getf", "marker"] call fnc_tell;
    if (not isNil "_marker") then {
    [_self, "hide"] call fnc_tell;
    [_self, "show"] call fnc_tell;
    };
    } ENDMETHOD;


     

    Finally, we implement trivial methods to set each configuration variable and then call redraw.



    DEFMETHOD("Marker", "set_alpha") ["_self", "_alpha"] DO {
    /* Configure alpha (opacity) */
    [_self, "_setf", "alpha", _alpha] call fnc_tell;
    [_self, "redraw"] call fnc_tell;
    } ENDMETHOD;
     
     
    DEFMETHOD("Marker", "set_brush") ["_self", "_brush"] DO {
    /* Configure brush (area fill type) */
    [_self, "_setf", "brush", _brush] call fnc_tell;
    [_self, "redraw"] call fnc_tell;
    } ENDMETHOD;
     
     
    DEFMETHOD("Marker", "set_color") ["_self", "_color"] DO {
    /* Configure color (array or Cfg value) */
    [_self, "_setf", "color", _color] call fnc_tell;
    [_self, "redraw"] call fnc_tell;
    } ENDMETHOD;
     
     
    DEFMETHOD("Marker", "set_direction") ["_self", "_direction"] DO {
    /* Set marker orientation (in degrees) */
    [_self, "_setf", "direction", _direction] call fnc_tell;
    [_self, "redraw"] call fnc_tell;
    } ENDMETHOD;
     
     
    DEFMETHOD("Marker", "set_position") ["_self", "_position"] DO {
    /* Set marker position */
    [_self, "_setf", "position", _position] call fnc_tell;
    [_self, "redraw"] call fnc_tell;
    } ENDMETHOD;
     
     
    DEFMETHOD("Marker", "set_shape") ["_self", "_shape"] DO {
    /* Set marker shape (i.e. "RECTANGLE", "ELLIPSE", "ICON") */
    [_self, "_setf", "shape", _shape] call fnc_tell;
    [_self, "redraw"] call fnc_tell;
    } ENDMETHOD;
     
     
    DEFMETHOD("Marker", "set_size") ["_self", "_size"] DO {
    /* Set size [X x Y] */
    [_self, "_setf", "size", _size] call fnc_tell;
    [_self, "redraw"] call fnc_tell;
    } ENDMETHOD;
     
     
    DEFMETHOD("Marker", "set_text") ["_self", "_text"] DO {
    /* Configure marker text */
    [_self, "_setf", "text", _text] call fnc_tell;
    [_self, "redraw"] call fnc_tell;
    } ENDMETHOD;
     
     
    DEFMETHOD("Marker", "set_type") ["_self", "_type"] DO {
    /* Configure marker type (i.e. which icon) */
    [_self, "_setf", "type", _type] call fnc_tell;
    [_self, "redraw"] call fnc_tell;
    } ENDMETHOD;


     

    Using:

    Combining all of the above together into classdef\Marker.hpp and adding the line #include <classdef\Marker.hpp> to our init.sqf (after including include\classes.hpp) gives us access to the new object interface, and we can use it as one might expect:



    // Create and show a new marker:
    mkr = ["Marker", position player, "RECTANGLE", nil, [5, 5]] call fnc_new;
    [mkr, "show"] call fnc_tell;
     
    // Reorient the marker:
    [mkr, "set_direction", 45] call fnc_tell;
     
    // Change marker color:
    [mkr, "set_color", "ColorGreen"] call fnc_tell;
     
    // Change marker size:
    [mkr, "set_size", [10, 10]] call fnc_tell;
     
    // Hide marker again:
    [mkr, "hide"] call fnc_tell;


     

    This is just one simple example of how to use object orientation with the SQF Classes module to facilitate simpler, more powerful options in the editor and during scenario design.  Markers were a prime candidate for this technique because of the overhead involved every time a marker is to be created, altered, or destroyed.  That overhead can be wrapped up into the class and its methods, and then ignored even as the class can be reused in different projects or even extended via inheritance and subclassing.

     

    I hope this may interest you in trying to create your own classes and sharing them with the community, or at least implementing them in your own scenarios to make things easier on yourself!

     

    Edited to fix a bug that shows up in certain cases when using nil as a parameter.


  7. In that case they will engage, but continue making their way along the waypoints.  You would need to explicitly give them a seek and destroy waypoint to activate when they detected enemies, and then switch them back to their normal waypoints once the enemies are gone.  There are a number of ways to do this, but none are that simple.  I would say a solution using "Skip Waypoint" triggers (I think that's what they're called... used to be "SWITCH") is probably the simplest.

    You may have some luck also if you set them to "Open Fire" instead of the default "Open Fire, Keep Formation"  (these used to be called "Open Fire, Engage at Will", and "Open Fire", [reversed] which makes things confusing to say the least).  You may need to do some testing to determine under what circumstances the units will abandon waypoints to go after enemies.  That may suffice instead of using any seek-and-destroy waypoint switching.


  8. I'm not sure, but I tried using it the other day in response to a question I saw on another forum and found it not to work, also.  It seemed like it might have possibly done something, but as soon as the units detected enemies they ignored it.  Might have been a coincidence, and it never worked at all, but I did not investigate further.


  9. Not true, when calling a function the handle will return the return value from the function, when used by a spawn it can be used in combination with scriptDone _handle or isNil _handle to determine if the spawned function has finished.

    You see all kind of examples in tutorials using _nul =, _nil =, or even 0 =.

    No idea where that came from, call and spawn always return their respective values.

     

    Cheers

    What? The editor won't even allow you to write "this spawn {}" in a trigger activation or init field.  You HAVE to assign it to something, '_nil' is a convention because the results are not used in this case.

    _nil = this spawn {};
    

    is fine, but

    this spawn {};
    

    won't even let you close the dialog window.

     

    EDIT: specifically, the error message is:

     

     

    Init: Type Script, expected Nothing

     

    What I meant was, the game will not allow you to return a variable from whatever you insert into Init.  It expects "Nothing" [which is precisely what an assignment (to _nil or anything else) returns]

     

    EDIT 2:

    I can see how in some contexts it may be desirable to wait for the script to finish (or check its value) during init.  In that case, you're right, assignment to _nil is the wrong pattern.  It is perfectly fine in most cases, though, and certainly better than "nil =" or "0 =" which are plainly invalid.

     

    EDIT 3:

    Perhaps what we should start doing is "_dontCare =" or "_handle ="... I did that for a while, but honestly, "_nil =" is just a lot shorter, clearer than "_dc =", and works just as well for something that's never going to be seen again.


  10. In that case you can just try:

    _nil = [
    
    [GKB_aa1, GKB_aa2, GKB_aa3],
    [[0, 4500], [7000, 8000], [1000, 500]],
    20 + random 100,
    0.1,
    {alive GKB_radar}] spawn GKB_fnc_randomAAFire;

    If you add "_nil =" at the beginning it won't be trying to return anything to the game where it isn't supposed to.

     

    (of course, you will need to replace the parameters with things that make sense in your case... I presume you don't have any of the units GKB_aa1.. or GKB_radar unless you named them that to work with this specifically)


  11. You may be right... since this is a function it must be compiled before you can say "spawn GKB_fnc_randomAAFire" as in the example. <EDIT: try it with no ".sqf" here>

     

    Assuming that example syntax is correct, try adding this to the file "init.sqf" in your mission directory:

    GKB_fnc_randomAAFire = compile preprocessfile "path\to\the\script.sqf";
    

    Where, of course, you insert the actual relative path from your mission directory.  I.e if it isn't in a subfolder, just write "scriptname.sqf" (whatever the filename is).

     

    EDIT: I'm not sure that example syntax IS correct; try dropping the ".sqf" when you actually spawn it.

    EDIT 2: backslashes


  12. SQF Classes for ArmA 3
    Edited - release updated with a couple of bugfixes - if you downloaded prior to 6pm GMT on 29 October then please re-download to get the corrected files.
     
     
    oFOUVjO.jpg
     
     
    "If . . . [the] fact [that brutes abstract not] be made the distinguishing property of that sort of animal, I fear a great many of those that pass for men must be reckoned into their number." -- George Berkeley
     

    Many times when scripting in SQF it may occur that implementing object-oriented techniques can simplify or enhance certain abstractions.  The technique of using getVariable/setVariable on a unit or gamelogic to store function code or references specific to that unit's situation is a common pattern found in many missions and modules.  Here, that idea is taken one step farther: instead of storing functions on game objects, we store an array of class names (representing a chain of inheritance or a mixture of interfaces).  Meanwhile, a master Classes array holds a subarray for each defined class, containing a function definition for each class method.  With this arrangement and just a handful of primitive functions, an unlimited variety of classes and methods can be declared on-the-fly while a mission is running.

     

    This module relies on the alist/, lambda/, and vectools/ modules (included).  These include support functions that have not been particularly optimized, and indeed several of them are suboptimal solutions.  This class framework expects certain of these functions to be available, but they can be replaced with any suitable alternative that implements the same interface.  In fact, I recommend any serious user of this system consider these functions as a "starter kit" which may be upgraded as convenient.
     
    Because I have only scripted in SQF for my own enjoyment in the Editor, I am not familiar with techniques or best practices for robust server-ready code.  It is quite possible that there are fatal security flaws in this approach for online code.  I would be happy to make additions and updates to help secure the system for deployment "in the wild", but totally lack the experience to assess the requirements.  Specific suggestions are invited and very welcome.

     
     
    This is a selection of modules drawn from a larger repository I keep at http://www.github.com/dwringer/a3system/.  There, more recent versions of these modules can often be found.  I also placed other modules I created over the years, which are of variable quality and may or may not be worth a look.
     

     
    Class layout
    By themselves, classes have a name, an initializer method which is declared alongisde the class, and any number of additional methods.  The initializer and other methods can all take multiple parameters, although currently variable-parameter-counts are unfortunately not supported.  Each instance stores its own inheritance chain under the local variable "class_names".
     
    A new class can be created at any time with a call like so:
    ["<your-class's-name>", <init_function>] call fnc_class;

    "_self" (or something equivalent) is a required first parameter for all functions used as methods.  Additional parameters can also be added to the parameter list for the initializer and ordinary methods.  Replace <init_function> with an actual function name that accepts the desired number of parameters, or create one with fnc_lambda (below).

     

     
    Lambda
    To prevent the tediousness of creating a new SQF function or otherwise writing out a function of several parameters every time a method is needed, this module can take advantage of the scripts in the included lambda/ module.  For a simple example, a compiled function to compute the volume of a cube accepting three variables can be made in one line:
     _fnc = [["_l", "_w", "_h"], {_l * _w * _h}] call fnc_lambda;
     
    A default initializer for a new class can be made like this:
     _fnc = [["_self"], {_self}] call fnc_lambda;
     
    That is a trivial example which merely returns the instance after creation, but the technique can be generalized.
     
    We can define a class along with its default initialization-method by combining fnc_lambda and fnc_class like this:
    ["MyClass", 
     [["_self"], {_self}] call fnc_lambda] call fnc_class;

     


     
    Macros
    We can also take the above class creation using fnc_lambda and fnc_class and rearrange line breaks to make it look more like a declaration in a more traditional language:
      ["MyClass", [["_self"], {
            _self
      }] call fnc_lambda] call fnc_class;

    But that is also unwieldy.  Instead, we can create macros to standardize this declaration and clean it up slightly:

      DEFCLASS("MyClass") ["_self"] DO {
          _self
      } ENDCLASS;
    Perhaps not ideal, but usable.
     
    Method declarations work the same way, except the function takes classname and method name as the first two parameters, and is named fnc_method instead of fnc_class.  This is wrapped up in the DEFMETHOD macros:
      DEFMETHOD("MyClass", "a_method") ["_self", "_param1"] DO {
    
        .. do some stuff ..
    
      } ENDMETHOD;
     

     
    Example overview
    This will demonstrate how to create abstract instances and send messages (method calls) to them. The included example is a mission with four men (crew_1..crew_4) and two vehicles (car_1, car_2).  You may easily duplicate it in a different map if you don't have Tanoa available.  There is a single Radio Alpha trigger, upon whose activation the following scripts are executed: 
      CG = ["CrewUnitGroup"] call fnc_new;
      [CG, "assign", crew_1, "driver"] call fnc_tell;
      [CG, "assign", crew_2, "gunner"] call fnc_tell;
      [CG, "assign", crew_3, "driver"] call fnc_tell;
      [CG, "assign", crew_4, "gunner"] call fnc_tell;
      [CG, "assign", car_1, "vehicle"] call fnc_tell;
      [CG, "assign", car_2, "vehicle"] call fnc_tell;
      [CG, "board_instant"] call fnc_tell;
    

    Here, fnc_new is how we create a new instance.  It accepts the class name and any init parameters appended to it (here there are none).  

     

    We could also put a Game Logic in the editor instead of using fnc_new, and in its init use the following:  

      _nil = this spawn {
          waitUntil {not isNil "ClassesInitialized"};
          [_this, "CrewUnitGroup"] call fnc_instance;
          .. rest of code ..
      };

    The method calls are actually made with fnc_tell, which takes as parameters the class instance, method name, and subsequently each method parameter.  Thus, all methods to all classes are defined and called using a standardized syntax and set of functions which have been precompiled.

     
    Since fnc_instance makes an instance out of an existing object, we can also use it for creating class inheritance.  Consider the following:
      DEFCLASS("MySubClass") ["_self"] DO {
          [_self, "SuperClass"] call fnc_instance;
          .. rest of code ..
          _self
      } ENDCLASS;

    Now, MySubClass inherits all the methods from SuperClass.  Each game object keeps an ordered list of its class assignments, so if a method is not found on MySubClass it will be looked up on SuperClass.  I have provided the macros SUPER and SUPER_ARGS in include\classes.hpp to facilitate subclassing from within classdef header files.

     
    If you want to create a subclass method that implements a superclass method under a different name, that can be done by looking up the class alist in the Classes global alist and using the function contained there as a new method.  In classdef files, this has a macro called ALIAS(subclass, subclass_method, superclass, superclass_method).
     

     
    Another example

    If you don't have Apex and can't load the example mission, or want to see how this can be injected into a mission very simply, drop the system folders into your mission with the required #include's in your init.sqf (see example code or the docs on GitHub), then place a group of four units in the editor.  Place an empty Hunter HMG next to them and name it "car".  Now, in the group leader's init field, paste the following code:

    _nil = this spawn {
        waitUntil {not isNil "ClassesInitialized"};
        CG = ["CrewUnitGroup"] call fnc_new;
        [[["_u", "_role"], {
             [CG, "assign", _u, _role] call fnc_tell;
         }] call fnc_lambda,
         (units group _this) + [car],
         ["driver", "gunner", "cargo", "cargo", "vehicle"]
        ] call fnc_map;
        [CG, "board_instant"] call fnc_tell;
    };

    As soon as the class system is loaded, this instantiates an abstract CrewUnitGroup instance, then maps an anonymous function (created with fnc_lambda) across two arrays: the units to be assigned, and the roles to which they will be assigned.  The CrewUnitGroup instance is then given the "board_instant" method name (With no parameters - the crew assignments were already set up by the map!) and the mission starts with the vehicle fully crewed.  Go back to the editor and change the vehicle for a Hunter GMG, but keep the same name ("car").  Just like that, your crew uses the new vehicle. Hopefully this provides some ideas to anyone reading this.

     


     
    Without further ado, the link to the repository containing the above example mission folder and all required modules is:
     
     

     
    Good luck, and if anyone creates some useful classes or a cool class tree, I'd love to hear about it.  Even if this just serves as an example of what can be done to (ab)use SQF, I am satisfied.
     

    P.S. it is almost certain you will find typos, errors and omissions in this post, the documents, and/or the code.  I would be enormously grateful if these errors could be (kindly?) brought to my attention, and I will make every effort to correct them quickly.

     

    "No point in mentioning these bats, I thought. Poor bastard will see them soon enough." -- Raoul Duke
     

    Edited - release updated with a couple of bugfixes - if you downloaded prior to 6pm GMT on 29 October then please redownload to get the corrected files.  There were changes made to fnc_tell and fnc_filter related to nil values being passed as parameters.

    Edited - added another example section
     

    • Like 2

  13. Interesting.

     

    ...however, I can't help but wonder why you don't make use of the functions library there:

    https://community.bistudio.com/wiki/Functions_Library_(Arma_3)

    I'm afraid I don't fully understand the reference, although looking through the functions I have found a couple that would have made string manipulation slightly simpler and probably more efficient.  I must caution that this system is not designed for speed, but simply for power of abstraction.  The chaining of functions can get very deep and is bound to cause problems if abused or used heavily. 

     

    I noted that there is a way to register functions in the game's function library, which I have not done as it is all new to me (I have not been active in the community for a while).  I welcome any specific suggestions on how to handle improvements and optimizations like that, but I am out-of-touch when it comes to standard practices with sqf.  This is also designed and tested only for single-player; if anyone with multiplayer coding experience has advice or concerns I'd be grateful.  I'm not entirely sure how the scripts would interact in such an environment.

     

    Finally, I have added to the class hierarchy including a unit group abstraction that offers a center_pos method (giving the mean position of all units in the group instance).  I will be continuing to work on fleshing out classes but the things in the repository are bound to fluctuate wildly in quality as I continue updating and moving things around.

     

    [Edited for clarity]

     

    If you are wondering why fnc_sorted would be more useful than the BI sort functions, it is because you can use fnc_lambda inline to create a custom comparison function and pass it , and an array of any supporting variables, to the sort and get any ordering you can define.

     

    In any case, I have just finished a couple of methods on a CrewUnitGroup class.  This class keeps unit arrays for various roles (driver, gunner, cargo), and vehicles themselves.  There is a simple "assign" method (remove has not been properly overloaded yet so that functionality is not present) which takes a unit and role and adds it to the corresponding array.  The other method is "board_instant", which loops through vehicles dynamically assigning drivers and gunners, then filling in cargo as space allows.  Hopefully this is a better example of what is possible, and maybe even conceivably useful.

     

    [Edited again for update]


  14. We need a proper implementation from Bohemia imo.

    I have always wondered why they didn't add these features to SQF, and presumed it is because if they are used excessively it can be grossly inefficient.  I remember hearing that they were looking into Java or something, but that somehow doesn't sound very appealing.  

     

    I haven't implemented any complex systems with these classes yet, but the functional tools are already proving their worth in my missions and experiments.  The class system will surely prove invaluable (and has already been helpful) in organizing mission components, inheritance trees among abstract concepts, etc.

     

    Anyway, just wanted to let everyone know I have massively upgraded the documentations (trying to find errors - especially in the dependency listings - is something I don't find to be a very rewarding use of time).  They should be usable, which is all that I want from them, but if you have trouble getting a module to work from the docs just try adding the whole repo to your mission first.  That way you can make sure to have any unlisted dependencies.

     

    The class system has been tested and proven effective with the ObjectRoot and Dictionary classes, which show off the abstraction and inheritance capabilities avaialble.  Some other test/example classes of low quality are also included (these are in the classdef/ folder).


  15. I am probably going to work some parts of this into their own independent modules (they can already be used that way but perhaps the codebase isn't too approachable). But, I wanted to announce a couple of things here:

    If anyone checked out this code after my first post you may have found some show stopping bugs. I have been working extensively on this and everything should be working as intended as of now.

    Most importantly: dynamic classes!
    I've seen a few other class implementations in sqf out there, but none of them were much like this - although I'm sure it is out there somewhere :).  I urge you all to check out the idea and share what you think. Here's an example class definition file to hack into objects' native dictionary capabilities:
    https://github.com/dwringer/a3system/blob/master/classdef/Dictionary.hpp


  16. Hello,
    If anyone would like to see a wide assortment of scripts including some helpful functional programming tools and array manipulation utilities, I have assembled several modules with extremely basic documentation at https://github.com/dwringer/a3system

    You are pretty much on your own with these, especially some of the less documented stuff, but the stuff in "lambda" and "vectools" is pretty well tested so far and helpful in a lot of situations.

    I would be interested to hear if anyone makes any use of some of this.

    Best of luck, and let me know if there are any usage questions or problems identified.

     

    EDIT 2: There is now a class system included that is superficially similar to Python classes.  These can be defined in header files using syntax macros, or created on-the-fly using the right functions.  So far there is an ObjectRoot and Dictionary class, but these are more examples than anything.

    EDIT:

    Fixed a couple of functions - in particular, fnc_sorted was destroying the original array passed to it. There was also a problem with using negative indexing in fnc_subseq which should be fixed now.


  17. I Don't understand the question. Are you referring to the fact that the places where you can realistically build these are very limited. To look any good, you need 11m or less or water depth, yet it needs to be some distance from the shore. The location I used is in the straits between the mainland and Ammolofi bay in the NW of the island.

    Although that does answer a question I had, I think he was referring to the fact that your video is private, and thus we are not able to view it at all :)


  18. I meant in A3, to be honest I've never seen them using those "special" stances, it's rare enough if the lean for me.

    By the way I tested, if we change their animation to one of those stances they succesfully turn around and shoot, I wish they used them without scripting though.

    Sorry, yeah; that I can definitely agree with.. but I can also guarantee they do use the stances now and then. I have a half-assed script that positions units in "advantageous" overwatch positions and from there, occasionally I have seen them use the animations. But, these are locations that were tailored to their AI already, so the results don't replicate well on random positions they tend to end up in :(


  19. Thanks. It was cool to see that they used cover for real and didn't go prone like always.

    The AI has been able to effectively use and shoot from cover since Arma 2, it doesn't require any special scripting; just for their behavior to be set to "Combat" (which happens automatically even when they're in "Aware" when they come under effective fire)


  20. Sounds to me like "ff1grp" is not defined. Check your .rpt file and see if you are getting any specific error messages.

    I would find the leader of your ff1grp, if its in the editor, and make sure his init says "ff1grp = group this", and then (as Na_Palm suggests) put a waituntil{time>0} or something into the above script to make sure the group has initialized before this script gets called.

    If this is running inside a trigger you can just make time>0 part of its condition.

    Forgive me if time>0 isn't the best way (or 100% correct way) to do this ... hopefully this might help

×