Code Bank - share useful code!

Discussion in 'Advanced Programming Discussion' started by Samuel Venable, Mar 10, 2019.

  1. Samuel Venable

    Samuel Venable Time Killer

    Joined:
    Sep 13, 2016
    Posts:
    1,084
    Similar to the VBForums code bank, we could seriously use one. Just a place to share and talk about useful code snippets, whether raw GML or stuff useful to build upon for native extensions. I'm only putting this in the advanced forum because native extension code is permitted to be shared here. Not all code snippets must be written by advanced users! Most notably, GML can be shared here--and if it's useful--doesn't matter if it was easy to write. Please give explanations of what your code does and how to use it. This'll be a good resource.

    Just post it!

    Have questions about a code snippet? Do not clog or distract the goodness of this topic. PM the post's author, and hope they get back to you. :)

    ------------------------

    I'll start.

    For example, here's a snippet I wrote for a much larger project recently, but is useful for much more than being shared as an extension on its own; I'm using it quite a lot for a Linux re-write of my Dialog Module extension:

    main.cpp
    Code:
    #include <cstdio>  // needed for popen(), getline(), pclose()
    #include <cstdlib> // needed for free()
    #include <string>  // needed for std::string, string
    using std::string; // needed for string
    
    #define EXPORTED_FUNCTION extern "C" __attribute__((visibility("default")))
    
    EXPORTED_FUNCTION char *shellscript_evaluate(char *command) {
      char *buffer = NULL;
      size_t buffer_size = 0;
    
      string str_command = command;
      string str_buffer = "";
    
      FILE *file = popen(str_command.c_str(), "r");
      while (getline(&buffer, &buffer_size, file) != -1)
        str_buffer += buffer;
    
      free(buffer);
      pclose(file);
    
      if (str_buffer.back() == '\n')
        str_buffer.pop_back();
    
      static string str_result;
      str_result = str_buffer;
    
      return (char *)str_result.c_str();
    }
    Again, please give explanations of what your code does and how to use it. Allow me to start by explaining the code above. It evaluates shell scripts, while similar to my "famous" Execute Shell extension, not only can it execute from the shell, it can also read any output from it (not including errors, but that could easily be added with a small tweak). Compile it with the G++ GNU C++ Compiler, using the following in any Unix terminal:

    Code:
    cd /Path/To/EvaluateShell/
    g++ -c -std=c++11 main.cpp -fPIC -m64
    g++ main.o -o EvaluateShell.x64/EvaluateShell.so -shared -fPIC -m64
    Replace "/Path/To/EvaluateShell/" with the path to a folder containing the C++ code file and make sure that file is named "main.cpp". You will need an output folder in that same directory named "EvaluateShell.x64". Notice the x64, this will be for 64-bit builds and requires building on a 64-bit Unix OS (whether macOS, Linux, BSD, Solaris, or whatever). With small modifications it will also work on Windows, but you will need to add UTF-8 support manually because Windows is a pain in the butt about string and language encodings such as Cyrillic.

    If you want 32-bit support of this extension sample, the same rules apply. Install G++ on your OS as needed, cd to the correct path, and run the commands one line at a time on your terminal application. Create the same folder, but this time have it named "EvaluateShell.x86". Depending on your OS, you will need the proper file extension for the shared library. Linux as you can see is *.so, macOS is *.dylib, Windows is *.dll and so forth:

    Code:
    cd /Path/To/EvaluateShell/
    g++ -c -std=c++11 main.cpp -fPIC -m32
    g++ main.o -o EvaluateShell.x86/EvaluateShell.dylib -shared -fPIC -m32
    Notice in the above terminal commands snippet, this time I used the dylib extenson - "EvaluateShell.dylib" - this is good for compiling on macOS right out of the box as soon as you have G++ via the HomeBrew package manager. On Linux you can compile the code in the snippet previous to the one directly above and you can use the extension via GameMaker Studio 1.4, as the Linux runner is 32-bit in all versions of Studio prior to 2.x. Mac is 64-bit in both runners as of 1.49999 to Studio 2.x onward.

    Should you compile for Studio 2 on Linux, you will need to use the -m64 flag instead of -m32, via modifying the terminal snippet, depending on which one of the two you copy/paste. Create an extension in Studio from the Extensions node of the resource tree in the IDE. Get the library file imported and create the function. Make sure the internal and external names of the function are "shellscript_evaluate". The return value must be a string. Add an arguments and make it a string. The help line should be:

    Code:
    shellscript_evaluate(command)
    Now you are ready to test it. command is the terminal command or shell script contents you wish to execute and evaluate.

    ------------------------

    Have fun and I hope this topic gets a lot of love.

    Edit:

    I discovered Windows does not have the getline() POSIX function, so to port this properly, I'll need to write my own. I'll share it here when I have a thoroughly tested and working version of that.
     
    Last edited: Mar 12, 2019
    Bart, TheSnidr and immortalx like this.
  2. Tthecreator

    Tthecreator Your Creator!

    Joined:
    Jun 20, 2016
    Posts:
    722
    The first script I always import into a new project is this:
    Code:
    ///sdbm(var1,var2)
    /*
    Stands for "show debug message" and writes a log line showing any values given to it.(Will be shown within game maker's compiler log)
    Multiple arguments of any type can be passes to this script and every argument will be seperated by a "#" symbol.
    */
    var str=""
    for(var i=0;i<argument_count;i++){
    str=str+string(argument[i])+"#"
    }
    show_debug_message(str)
    
    It will draw any inside your compile form. The nice thing is that multiple values can be given. These values will be seperated by a #.
    An example would be:
    sdbm("updated position",id,x,y).
    The output could be:
    update position#5#100#234#
     
    Bentley, cgPixel, immortalx and 3 others like this.
  3. Tsa05

    Tsa05 Member

    Joined:
    Jun 21, 2016
    Posts:
    539
    Each of the scripts below were devised while working on a dialog/VN engine, and now I routinely go back to them in other projects.


    array_delete(array, pos)
    Deletes a position from an array. Srsly, how is this not a built-in thing?
    Code:
    ///    @description     array_delete(array, pos)
    ///    @arg    {array}    array    The array to delete from
    ///    @arg    {real}    pos        The position to delete
    
    var a = argument0;
    var i = argument1;
    
    var L = array_length_1d(a);
    var r=array_create(L-1);
    array_copy(r, 0, a, 0, i);
    array_copy(r, i, a, i+1, L-i+1);
    
    return r;
    

    array_find(array, value)
    Finds the index of value in array, or returns -1, because srsly, how is this not a built-in thing?
    Also, note the hilarity of the length of this script, while reading the GMS 2.2 continuity thread. See how this script tediously tests its way through amusingly weird problems with equivalency.
    Code:
    ///    @description    array_find(array, value)
    ///    @args    {array}    array    The array to search
    ///    @args    {any}    value    The value to search for
    /*
    *    Searches a given array for the value indicated.
    *    Returns -1 or the first index located.
    */
    var a = argument[0];
    var v = argument[1];
    var retval = -1;
    if(!is_array(a)){
        return retval;
    }
    var type = typeof(v)
    for(var z=0;z<array_length_1d(a);z+=1){
        var av = a[z];
        var avtype = typeof(av);
        if(avtype==type){
            if(av==v){
                retval = z;
                return retval;
            }
        }
    }
    return retval;
    
    

    array_insert_1d(array, index, value)
    Inserts a value into an array because srsly, how is this not a built-in thing? Sensing a theme here?
    Code:
    ///    @description    Returns the modified array
    ///    @arg    {real}    array    The arrey to insert into
    ///    @arg    {real}    index    Where to insert
    ///    @arg            value    What to add to the array
    ///    @returns    {real}    Array with the newly inserted value
    
    var a = argument[0];
    var p = argument[1];
    var d = argument[2];
    
    if(!is_array(a)){
        a[0] = d;
    }else{
        var L = array_length_1d(a);
        var r = array_create(1);
        array_copy(r, 0, a, 0, p);
        r[p] = d;
        array_copy(r, p+1, a, p, L-p+1);
    }
    return r;
    

    array_is_ok(array, index)
    Simply performs that oft-needed check of whether an index is greater than or equal to zero and also within the length bounds of the array.
    Code:
    ///    @desc    Returns true if a valid index within a valid array is found
    ///    @arg    {real}    array
    ///    @arg    {real}    index
    var ary = argument[0];
    var ind = argument[1];
    
    return (ind>=0 && is_array(ary) && ind<array_length_1d(ary));
     

    array_to_string(array, token)
    Turns an array into a string, adding token after each entry
    Code:
    /// @description array_to_string(array, token)
    /// @arg {array}    array    The array to concatenate
    /// @arg {string}    token    Character to tokenize with
    var a = argument[0];
    var t = argument[1];
    var r = "";
    if(!is_array(a)){ return a; }
    for (var z=0; z<array_length_1d(a); z+=1){
        r+=string(a[z])+t;
    }
    return r;

    string_to_array(string, token)
    Break up a string into array parts using a token separator
    Code:
    /// @desc string_to_array(string, token)
    /// @arg {string} string The string to tokenize
    /// @arg {string} token The character to use as a token
    /*
    *   Turn a tokenized string into an array of values
    */
    
    var s = argument0;
    var t = argument1;
    var a = 0;
    
    var n = string_count(t,s);
    if(string_char_at(s,string_length(s))!=t){
        n+=1;
    }
    
    for(var z = 0;z<n;z+=1){
        var p = string_pos(t,s);
        if(!p) p=string_length(s)+1;
        a[z] = string_copy(s,1,p-1);
        s = string_delete(s,1,p-1+string_length(t));
    }
    return a;

    debug_array(array)
    Print the array to the debugger. Sanity restored.
    Code:
    /// debug_array(array)
    var array = argument[0];
    if(!is_array(array)){
        show_debug_message("Not an array! "+string(array));
    }
    var height = array_height_2d(array);
    if(height>0){ //2D
        for(var row=0; row<height; row+=1){
            var txt = "| ";
            for(var col=0; col<array_length_2d(array, row); col+=1){
                txt += string(array[row, col])+" | ";
            }
            show_debug_message(txt);
        }
    }else{
        var txt = "| ";
        for(var col=0; col<array_length_1d(array); col+=1){
            txt += string(array[col])+" | ";
        }
        show_debug_message(txt);
    }

    color_sub(colorName)
    Accepts a string name of GameMaker's built-in colors, returns the value of that color. Useful for cases where you might be loading color info from an external file. Extra useful in cases where GMS adds made-up precision to numbers when writing data to files ;)
    Code:
    ///    @arg    {string}    colorName        The name of the GMS color to use
    ///    @returns    {real}    colorVal        Game Maker encoded color or -1
    
    var c = string_lower(argument[0]);
    
    switch (c){
        case "aqua":
            return c_aqua;
        break;
        case "black":
            return c_black
        break;
        case "blue":
            return c_blue;
        break;
        case "dkgray":
            return c_dkgray;
        break;
        case "fuchsia":
            return c_fuchsia;
        break;
        case "gray":
            return c_gray;
        break;
        case "green":
            return c_green;
        break;
        case "lime":
            return c_lime;
        break;
        case "ltgray":
            return c_ltgray;
        break;
        case "maroon":
            return c_maroon;
        break;
        case "navy":
            return c_navy;
        break;
        case "olive":
            return c_olive
        break;
        case "orange":
            return c_orange
        break;
        case "purple":
            return c_purple
        break;
        case "red":
            return c_red;
        break;
        case "silver":
            return c_silver;
        break;
        case "teal":
            return c_teal;
        break;
        case "white":
            return c_white;
        break;
        case "yellow":
            return c_yellow;
        break;
    }
    return -1;

    debug_console(val1, val2, ... )
    An old debug_message replacement script. Could use some minor updating, but you use it in place of the regular debug message command and it automatically adds the TIME, OBJECT, EVENT, and all passed arguments cast to strings, separated by spaces.
    Code:
    /// debug_console(val1,val2,...)
    var o=object_get_name(object_index)
    var e="";
    var s="";
    var i=0;
    repeat (argument_count) {
        s+=" "+string(argument[i++]);
    }
    switch event_type{
    case ev_create: e="Create";break;
    case ev_destroy: e="Destroy";break;
    case ev_alarm: e="Alarm";break;
    case ev_step: e="Step";break;
    case ev_keyboard: e="Keyboard";break;
    case ev_mouse: e="Mouse";break;
    case ev_collision: e="Collision";break;
    case ev_other:
        switch event_number{
        case 63:
            e="Async";
        break;
        default:
            e="Other";
        }
    break;
    case ev_draw: e="Draw";break;
    case ev_keyrelease: e="KeyRelease";break;
    default: e="Room";o="noone";}
    show_debug_message(date_time_string(date_current_datetime())+" | "+o+" | "+e+" |"+s);

    draw_color_picker(x, y, w, h, ds_list);
    You make a ds list containing color info in the form of [R,G,B] arrays. This script draws a series of color picker boxes with those colors, fitting within the width and height desired. The script also returns the index of the color that you're currently hovering over, so that you can store it to an instance variable and use it on clobal mouse click, etc. Returns -1 if you're not hovering over a color.
    Code:
    /// @description        draw_color_picker(x,y,w,h, ds_list)
    /// @param    {real}    x            Starting x-coordinate
    /// @param    {real}    y            Starting y-coordinate
    /// @param    {real}    w            Width of picker
    /// @param    {real}    h            Height of picker
    /// @param    {real}    perRow        How many per row to draw
    /// @param    {real}    colorsList    A ds_list containing [r,g,b] data
    /*
    *    Draw a series of bordered boxes using a defined border color and
    *    a ds_list of Game Maker color values
    *
    *    Returns:
    *        Real: An array of RGB color values
    */
    var hilightColor = c_yellow;
    var borderColor = c_black;
    
    var px = argument[0];
    var py = argument[1];
    var pw = argument[2];
    var ph = argument[3];
    var pr = argument[4];
    var pc = argument[5];
    
    if(!ds_exists(pc,ds_type_list)) return -1;
    var pn = ds_list_size(pc);
    var preColor = draw_get_color();
    // How small is a side, to make it all fit...
    var p = 3;
    var s = pw/pr-p;
    var retVal = -1;
    var dx = px; var dy = py;
    for(var z = 0; z< pn; z+=1){
        draw_set_color(borderColor);
        if(point_in_rectangle(mouse_x,mouse_y, dx, dy, dx+s, dy+s)){
            retVal = pc[|z];
            draw_set_color(hilightColor);
        }
        draw_rectangle(dx, dy, dx+s, dy+s, 0);
        var c = pc[|z];
        if(is_array(c) && array_length_1d(c)>=3){
            draw_set_color(make_color_rgb(c[0], c[1], c[2]));
        }
        draw_rectangle(dx+1, dy+1, dx+s-1, dy+s-1, 0);
        dx+=s+p;
        if(dx>=px+pw){
            dx = px; dy+=s+p;
        }
    }
    draw_set_color(preColor);
    return retVal;

    draw_dashed_line(x1, y1, x2, y2, width, spacing/size, dashType)
    Draws a dashed (0) or dotted (1) line between the given points.
    Code:
    ///    @arg    x1            x1
    ///    @arg    y1            y1
    ///    @arg    x2            x2
    ///    @arg    y2            y2
    ///    @arg    width        Width of line
    ///    @arg    dashSize    Spacing/size of dash
    ///    @arg    dashType    Dashed or dotted
    var x1 = argument0;
    var y1 = argument1;
    var x2 = argument2;
    var y2 = argument3;
    var w  = argument4;
    var size = argument5;
    var type = argument6;
    
    var len = point_distance(x1,y1,x2,y2) div size;
    var dir = point_direction(x1,y1,x2,y2);
    var a = lengthdir_x(size,dir);
    var b = lengthdir_y(size,dir);
    for(var i=0; i<len; i++){
        if !(i & 1){
            if(type==0){
                draw_line_width(x1+a*i, y1+b*i, x1+a*(i+1), y1+b*(i+1), w);
            }else if(type==1){
                draw_circle(x1+a*i, y1+b*i, w, 0);
            }
        }
    }

    DS CHECKING: Since there's some funny ways that things can go wrong, especially with loading external JSON, I found myself typing out the same darned silly long checking statements over and over. For example, attempting to load a list and then using the ds_exists function will crash GameMaker if the result was "undefined."
    So there's scripts now. There's never a guarantee that a datastructure is actually what you think it is, but these help...

    ds_is_list(listID)
    Is the value stored in this variable actually a ds_list?
    Code:
    ///@description    ds_is_list(listID)
    ///@param    listID    Check whether this is a list
    var l = argument[0];
    
    return !(is_undefined(l) || is_string(l) || !ds_exists(l,ds_type_list));
    

    ds_is_map(mapID)
    Code:
    ///@description    ds_is_map(mapID)
    ///@param    mapID    Check whether this is a map
    /*
    *    Returns whether the input is a map
    */
    var m = argument[0];
    
    return !(is_undefined(m) || is_string(m) || !ds_exists(m,ds_type_map));
    

    DS_List_adds: The ds_ functions and accessors are often decent, but in cases where you're dealing with JSON, you're going to find yourself repeating certain extra functions a stupid number of times. GMS has ds_map_add_map() and ds_map_add_list() for adding data structures to maps. But is missing these others:

    Warning:
    These functions are magical and also poison. By marking a list item as a data structure, GameMaker will automatically clean up (destroy) child structures when the parent structure is destroyed. This is a blessing for memory management, and a curse if you needed those lists elsewhere ;D

    ds_list_add_list(id, list)
    Add a list to a list, marking it as a list.
    Code:
    ///    @arg    {real}        id        The list to add to
    ///    @arg    {real}        list    The list to add
    /*
    *    Put a list into a list properly; JSON compliant
    *    If the containing list is destroyed,
    *    this list destroys with it.
    *
    */
    var a = argument[0];
    var b = argument[1];
    
    ds_list_add(a, b);
    ds_list_mark_as_list(a, ds_list_size(a)-1);
    

    ds_list_add_map(id, map)
    Add a map to a list, marking it as a map
    Code:
    ///    @arg    {real}        id        The list to add to
    ///    @arg    {real}        map        The map to add
    /*
    *    Put a map into a list properly; JSON compliant
    *    If the containing list is destroyed,
    *    this map destroys with it.
    */
    
    var a = argument[0];
    var b = argument[1];
    
    ds_list_add(a, b);
    ds_list_mark_as_map(a, ds_list_size(a)-1);
    

    ds_list_lookup(list, index, default)
    Especially when dealing with external data, you'll need to look up a value, then examine it for validity and possibly substitute a default. All in one script, here.
    Code:
    /// @description ds_map_lookup(list, index, default);
    ///    @arg    {ds_list}        list        The Map to search
    ///    @arg    {real}            index        The key to look up
    ///    @arg                    default        The value to return if not found
    var L = argument[0];
    var k = argument[1];
    var d = argument[2];
    if(!is_undefined(L) && ds_exists(real(L), ds_type_list) && k>=0 && k<ds_list_size(L)){
        d = L[|k];
    }
    return d;

    ds_map_lookup(map, key, default)
    Especially when dealing with external data, you'll need to look up a value, then examine it for validity and possibly substitute a default. All in one script, here.
    Code:
    /// @description ds_map_lookup(map, key, default);
    ///    @arg    {ds_map}        map        The Map to search
    ///    @arg    {real,string}    key        The key to look up
    ///    @arg    {real,string}    default The value to return if not found
    var m = argument[0];
    var k = argument[1];
    var d = argument[2];
    if(!is_undefined(m) && ds_exists(real(m), ds_type_map) && ds_map_exists(m,k)){
        d = ds_map_find_value(m,k);
    }
    return d;

    is_in(value, test1, test2, test3, ...) OR
    is_in(value, array)
    Described in function, and I submitted it to the gmlscripts.com forums, too.
    Basically, if I want to test whether x, y, or z is equal to a value, that requires a longish IF statement.
    If I'm ok with potentially sacrificing some short-circuit probabilities, I can use this:
    if is_in(value, x, y, x) OR
    if is_in(value, [x, y, z])
    (Which can be especially useful if the array is populated elsewhere)
    Code:
    ///    @description        Returns whether a value is in the supplied set of arguments
    ///    @function            is_in( value, test0, [test1], [...] )
    ///    @param                value    The value to search for
    ///    @param                test0    Value to compare to
    ///    @param                [test1]    Add parameters as needed to extend the set
    ///    @returns    {bool}    True when value was found in set
    /*
    *    Suppose we want to test whether x, y, or z is equal to value.
    *    GameMaker:
    *        if(x==value || y==value || z==value)
    *    Python:
    *        if value in {x, y, z}
    *    This script shortens what you'd have to type in GameMaker like so:
    *        if is_in(value, x, y, z)
    */
    // Created for SLIDGE engine
    // Submitted to GMLscripts.com/license
    
    var test = argument[0];
    var argz = 0;
    for(var z=1; z<argument_count; z+=1){
        argz = argument[z];
        if(is_array(argz)){
            for(var i=0; i<array_length_1d(argz); i+=1){
                if(test == argz[i]){
                    return true;
                }
            }
        }else{
            if(test == argument[z]){
                return true;
            }
        }
    }
    return false;
    

    I think those are the chief ones I keep reusing...
     
    Last edited: Mar 14, 2019
    Bentley, NeZvers, cgPixel and 8 others like this.
  4. YellowAfterlife

    YellowAfterlife ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ Forum Staff Moderator

    Joined:
    Apr 21, 2016
    Posts:
    2,211
    This can be done with two array_copy calls and an array_create call, no need to loop over array while reallocating it length-1 times.
    Similar goes for array_insert_1d.

    You could use typeof() instead of manual checking (which has you miss a handful of types), but also you could just check if value is a string and check for strings in that case (and anything but strings if it's not).

    As of past few years, debug_get_callstack offers better position information (I also wrote a post about this)

    ds-related functions won't reliably save you from trouble (e.g. if your JSON contains a number instead of an array, it might hit a valid DS id), but do what you may
     
    CombatCalamity and NeZvers like this.
  5. kraifpatrik

    kraifpatrik Member

    Joined:
    Jun 23, 2016
    Posts:
    83
    A funny little macro that ensures that the code after it in a script / object (not instance!) event is executed only once.
    Code:
    #macro PRAGMA_ONCE \
        do \
        { \
            if (!variable_global_exists("__pragmaOnce")) \
            { \
                global.__pragmaOnce = ds_map_create(); \
            } \
            var _cs = debug_get_callstack(); \
            var _cr = _cs[0]; \
            if (ds_map_exists(global.__pragmaOnce, _cr)) \
            { \
                exit; \
            } \
            global.__pragmaOnce[? _cr] = 1; \
        } \
        until (1)
    

    EDIT: Oh god dammit, I accidentaly deleted the previous snippets... :( I will add them again later
     
    Last edited: Mar 25, 2019
    ZeDuval likes this.
  6. Tsa05

    Tsa05 Member

    Joined:
    Jun 21, 2016
    Posts:
    539
    @YellowAfterlife Great suggestions; I'd assumed that GameMaker's copying was essentially equivalent, but they appear to be a bajillion times speedier. I've updated array_delete, array_insert_1d, and array_find in the OP accordingly, so that it looks like you're crazy ;D
     
  7. TheSnidr

    TheSnidr Heavy metal viking dentist GMC Elder

    Joined:
    Jun 21, 2016
    Posts:
    450
    Simple script for getting a 3D vector from a 2D window coordinate, works for both perspective and orthographic projections:
    Code:
    /// @description convert_2d_to_3d(camera, x, y)
    /// @param camera
    /// @param x
    /// @param y
    /*
    Transforms a 2D coordinate (in window space) to a 3D vector.
    Returns an array of the following format:
    [dx, dy, dz, ox, oy, oz]
    where [dx, dy, dz] is the direction vector and [ox, oy, oz] is the origin of the ray.
    
    Works for both orthographic and perspective projections.
    
    Script created by TheSnidr
    */
    var camera = argument0;
    var _x = argument1;
    var _y = argument2;
    
    var V = camera_get_view_mat(camera);
    var P = camera_get_proj_mat(camera);
    
    var mx = 2 * (_x / window_get_width() - .5) / P[0];
    var my = 2 * (_y / window_get_height() - .5) / P[5];
    var camX = - (V[12] * V[0] + V[13] * V[1] + V[14] * V[2]);
    var camY = - (V[12] * V[4] + V[13] * V[5] + V[14] * V[6]);
    var camZ = - (V[12] * V[8] + V[13] * V[9] + V[14] * V[10]);
    
    if (P[15] == 0) 
    {    //This is a perspective projection
        return [V[2]  + mx * V[0] + my * V[1], 
                V[6]  + mx * V[4] + my * V[5], 
                V[10] + mx * V[8] + my * V[9], 
                camX, 
                camY, 
                camZ];
    }
    else 
    {    //This is an ortho projection
        return [V[2], 
                V[6], 
                V[10], 
                camX + mx * V[0] + my * V[1], 
                camY + mx * V[4] + my * V[5], 
                camZ + mx * V[8] + my * V[9]];
    }
     
    Cpaz, RujiK and Samuel Venable like this.
  8. Pfap

    Pfap Member

    Joined:
    Apr 30, 2017
    Posts:
    475
    Here's a script for finding the greatest common divisor of two numbers.

    Code:
    // gcd(a,b)
    // constraint: a>0 and b>0
    var a,b;
    a = argument0;
    b = argument1;
    //It will run until the return. while true == true{
    while true {
        a = a % b;
        if a = 0{ return b };
        b = b % a;
        if b = 0{ return a };
    }
    
    I discovered the usefulness of this script while working out a way to display point meters. It could be useful for health bars or other in game bars; basically, anytime you want to display a fraction of a whole.

    Lets say you have a level progress bar that takes 1000 points to fill and you want it to be displayed in the center of your room and for this example, the bar already has 500 points. The problem I ran into is that my room_width is 432 and I wanted 16 pixels of padding on either side.
    So, we know that the full bar is 1000 points, which leaves us with 1 unkown that can be though of as a fraction algebra problem.
    500/1000 and x/(room_width-32)

    We need to reduce before we can divide and multiply to find x, which is what the above script will do for you.


    Here is some code depicting a progress bar 1/2 full.
    Code:
    bar_progress = 500;
    bar_length = 1000;
    
    
    //reduce the current amount of bar_progress/bar_length
    var divisor = gcd(bar_progress,bar_length);
    var reduced_progress = bar_progress/divisor;
    var reduced_bar = bar_length/divisor;
    
    //now get it into the amounts I can use to draw
    full_length = (room_width-32);//32 for padding
    var multiplier = full_length/reduced_bar;
    draw_progress = multiplier*reduced_progress;
    


    Now draw progress will be the length of half of (room_width-32) or 200/400. Which is what I wanted a 200 pixel length bar to draw on top of a 400 pixel bar of a different color, thus depicting progress of 50%.
    It is really easy to use any values to build dynamic meters and bars to display info and can of course be easily used with sprite_width for any custom bars or even set values and draw_line for simple enemy health bars that will not pollute the game screen. For instance, if you have enemy sprites that are 32 pixels wide and you want to cram a 1000 point health bar above them; you would just replace the full_length variable with a value you set, in this case 32.

    I guess it could almost be thought of as a unit converter for points to pixels.





    Commonly known as the Euclidean algorithm and the original script I discovered here:
    https://forum.yoyogames.com/index.php?threads/greatest-common-factor-script.25245/
     
    Last edited: Mar 20, 2019
    Mr Giff and NeZvers like this.
  9. Samuel Venable

    Samuel Venable Time Killer

    Joined:
    Sep 13, 2016
    Posts:
    1,084
    This is a continuation from the OP, where I demonstrated how to execute and evaluate shell scripts with a GameMaker extension. After you have compiled main.cpp into a DYLIB for macOS, and done everything else instructed in the OP, you are ready for trying out the stuff outlined in this post.

    If you would like to skip this tutorial completely, you may download the finished product here.

    (Note: the app in the ZIP included file still needs to be code-signed).


    First, you will need to download DialogModule for Mac Scripting. Download and extract the ZIP archive from that link. Under the extracted folder named "DialogModule" there is the entire source code for the DialogModule Mac Executable. Don't be so focussed on that folder - it's just there for people with experience in C++ and Objective-C who want to modify or expand the software I'm providing free in that download. MIT License.

    Where you should first place your focus, however, is on the extracted folder, "dlgmod64" where you will find a mac app that can be used for scripting, named "dlgmod64.app". This file will execute the shell script commands you feed it at runtime, and this example will open a series of dialog boxes that normally are not available in GameMaker Studio, such as an NSAlert.

    Create a new project in GameMaker, import the extension you previously had made, and paste this code into the create event of an empty, first room:

    For GameMaker Studio 1.4 users:
    Code:
    zip_unzip(working_directory + "dlgmod64.zip", game_save_id + "dlgmod64/");
    shellscript_evaluate('chmod 777 "' + game_save_id + 'dlgmod64/dlgmod64.app/Contents/MacOS/dlgmod64"');
    var prefix = '"' + game_save_id + 'dlgmod64/dlgmod64.app/Contents/MacOS/dlgmod64" ', str;
    
    str = shellscript_evaluate(prefix + '--show-message "Hello World" "DialogModule"');
    shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');
    
    str = shellscript_evaluate(prefix + '--show-message-cancelable "Hello World" "DialogModule"');
    shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');
    
    str = shellscript_evaluate(prefix + '--show-question "Yes or no?" "DialogModule"');
    shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');
    
    str = shellscript_evaluate(prefix + '--show-question-cancelable "Yes, no, or cancel?" "DialogModule"');
    shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');
    
    str = shellscript_evaluate(prefix + '--show-attempt "Hello World!" "Error - DialogModule"');
    shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');
    
    str = shellscript_evaluate(prefix + '--show-error "Hello World!" 0 "Error - DialogModule"');
    if (str == "") { game_end(); exit; } shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');
    
    str = shellscript_evaluate(prefix + '--get-string "Enter a string:" "Hello World!" "DialogModule"');
    if (str != "") shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');
    
    str = shellscript_evaluate(prefix + '--get-password "Enter a string password:" "Hello World!" "DialogModule"');
    if (str != "") shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');
    
    str = shellscript_evaluate(prefix + '--get-integer "Enter an integer:" 0 "DialogModule"');
    shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');
    
    str = shellscript_evaluate(prefix + '--get-passcode "Enter an integer passcode:" 0 "DialogModule"');
    shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');
    
    var filter = "Sprite Images (*.png *.gif *.jpg *.jpeg)|*.png;*.gif;*.jpg;*.jpeg|Background Images (*.png)|*.png|All Files (*.*)|*.*";
    
    str = shellscript_evaluate(prefix + '--get-open-filename "' + filter + '" "Select a File"');
    if (str != "") shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');
    
    str = shellscript_evaluate(prefix + '--get-open-filename-ext "' + filter + '" "Select a File" "" "Open Ext"');
    if (str != "") shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');
    
    str = shellscript_evaluate(prefix + '--get-open-filenames "' + filter + '" "Select Files"');
    if (str != "") shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');
    
    str = shellscript_evaluate(prefix + '--get-open-filenames-ext "' + filter + '" "Select Files" "" "Open Ext"');
    if (str != "") shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');
    
    str = shellscript_evaluate(prefix + '--get-save-filename "' + filter + '" "Untitled.png"');
    if (str != "") shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');
    
    str = shellscript_evaluate(prefix + '--get-save-filename-ext "' + filter + '" "Untitled.png" "" "Save As Ext"');
    if (str != "") shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');
    
    str = shellscript_evaluate(prefix + '--get-directory ""');
    if (str != "") shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');
    
    str = shellscript_evaluate(prefix + '--get-directory-alt "Select Directory Alt" ""');
    if (str != "") shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');
    
    str = shellscript_evaluate(prefix + '--get-color ' + string(c_red));
    shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');
    
    str = shellscript_evaluate(prefix + '--get-color-ext ' + string(c_red) + ' "Colors Ext"');
    shellscript_evaluate(prefix + '--show-message "' + str + '" "DialogModule"');
    
    game_end();
    For GameMaker Studio 2.x users:
    Code:
    zip_unzip(working_directory + "dlgmod64.zip", game_save_id + "dlgmod64/");
    shellscript_evaluate(@'chmod 777 "' + game_save_id + @'dlgmod64/dlgmod64.app/Contents/MacOS/dlgmod64"');
    var prefix = @'"' + game_save_id + @'dlgmod64/dlgmod64.app/Contents/MacOS/dlgmod64" ', str;
    
    str = shellscript_evaluate(prefix + @'--show-message "Hello World" "DialogModule"');
    shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');
    
    str = shellscript_evaluate(prefix + @'--show-message-cancelable "Hello World" "DialogModule"');
    shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');
    
    str = shellscript_evaluate(prefix + @'--show-question "Yes or no?" "DialogModule"');
    shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');
    
    str = shellscript_evaluate(prefix + @'--show-question-cancelable "Yes, no, or cancel?" "DialogModule"');
    shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');
    
    str = shellscript_evaluate(prefix + @'--show-attempt "Hello World!" "Error - DialogModule"');
    shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');
    
    str = shellscript_evaluate(prefix + @'--show-error "Hello World!" 0 "Error - DialogModule"');
    if (str == "") { game_end(); exit; } shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');
    
    str = shellscript_evaluate(prefix + @'--get-string "Enter a string:" "Hello World!" "DialogModule"');
    if (str != "") shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');
    
    str = shellscript_evaluate(prefix + @'--get-password "Enter a string password:" "Hello World!" "DialogModule"');
    if (str != "") shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');
    
    str = shellscript_evaluate(prefix + @'--get-integer "Enter an integer:" 0 "DialogModule"');
    shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');
    
    str = shellscript_evaluate(prefix + @'--get-passcode "Enter an integer passcode:" 0 "DialogModule"');
    shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');
    
    var filter = "Sprite Images (*.png *.gif *.jpg *.jpeg)|*.png;*.gif;*.jpg;*.jpeg|Background Images (*.png)|*.png|All Files (*.*)|*.*";
    
    str = shellscript_evaluate(prefix + @'--get-open-filename "' + filter + @'" "Select a File"');
    if (str != "") shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');
    
    str = shellscript_evaluate(prefix + @'--get-open-filename-ext "' + filter + @'" "Select a File" "" "Open Ext"');
    if (str != "") shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');
    
    str = shellscript_evaluate(prefix + @'--get-open-filenames "' + filter + @'" "Select Files"');
    if (str != "") shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');
    
    str = shellscript_evaluate(prefix + @'--get-open-filenames-ext "' + filter + @'" "Select Files" "" "Open Ext"');
    if (str != "") shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');
    
    str = shellscript_evaluate(prefix + @'--get-save-filename "' + filter + @'" "Untitled.png"');
    if (str != "") shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');
    
    str = shellscript_evaluate(prefix + @'--get-save-filename-ext "' + filter + @'" "Untitled.png" "" "Save As Ext"');
    if (str != "") shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');
    
    str = shellscript_evaluate(prefix + @'--get-directory ""');
    if (str != "") shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');
    
    str = shellscript_evaluate(prefix + @'--get-directory-alt "Select Directory Alt" ""');
    if (str != "") shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');
    
    str = shellscript_evaluate(prefix + @'--get-color ' + string(c_red));
    shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');
    
    str = shellscript_evaluate(prefix + @'--get-color-ext ' + string(c_red) + @' "Colors Ext"');
    shellscript_evaluate(prefix + @'--show-message "' + str + @'" "DialogModule"');
    
    game_end();
    Then you will need to archive into a zip file the "dlgmod64.app", (make sure it's in the root directory of the zip), and import that zip as an included file, naming it "dlgmod64.zip". Now you are ready to test run your game!

    You may also build and distribute the app as it is, but before you do so, you will need to code-sign the "dlgmod64.app" mac app yourself. There are many tutorials online that will show you how to run mac apps that are not code-signed that were downloaded from the internet. Google is your friend for that step.

    To reiterate, once you have enabled the app to run, and then code-signed it, you may then use it in your GameMaker-created mac apps.

    Should you change the GML to do something else, of which, you may actually need for a final game, you can follow all these steps with that extension and/or "dlgmod64.zip" file, as needed, edited to your liking, then you may code-sign your app for distribution.
     
    Last edited: Apr 23, 2019 at 2:35 PM
  10. Tsa05

    Tsa05 Member

    Joined:
    Jun 21, 2016
    Posts:
    539
    Here's a Windows Batch script that is *technically* utterly useless, but since that's not the point of this topic, I'm forced to reveal that I find it useful to convince myself that my life is going somewhere:

    countLines.bat (Counts lines of gml in a project folder and offers you comfort)
    You place it in the project directory of a game (where the .yyp file is located) and run it from a command prompt.
    Code:
    @echo off
    
    :CountLines
    setlocal
    set /a totalNumLines = 0
    for /r %1 %%F in (*.gml) do (
      for /f %%N in ('find /v /c "" ^<"%%F"') do set /a totalNumLines+=%%N
    )
    
    echo Total number of code lines = %totalNumLines%
    echo Making progress!
     
    NeZvers and Appsurd like this.
  11. Samuel Venable

    Samuel Venable Time Killer

    Joined:
    Sep 13, 2016
    Posts:
    1,084
    @The Sorcerer get over here right quick and share your knowledge with us
     
    The Sorcerer likes this.
  12. Samuel Venable

    Samuel Venable Time Killer

    Joined:
    Sep 13, 2016
    Posts:
    1,084
    I have updated the tutorial in post #9 to be much simpler by removing the need for AppleScript and instead just executing the shellscripts from the extension directly, allowing it all to be done in GML.
     

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice