Code Bank - share useful code!

rytan451

Member
Quicksort for sorting by some property of elements of arrays
GML:
function shuffle = function(array) {
  for (var i = array_length(array) - 1; i >= 0; i++) {
    array[@ i] = array[irandom(i)];
  }
}

function array_sort_by(array, map) {
  static _swap = function(array, a, b) {
    var c = array[a];
    array[@ a] = array[b];
    array[@ b] = array[a];
  };
  static _quicksort = function(array, map, start, end) {
    // Base case
    if (start == end) {
      return;
    } else if (start == end - 1) {
      if (map(array[start]) > map(array[end])) {
        _swap(array, start, end);
      }
    }
    // Partition
    var end_of_lesser = start;
    for (var i = end_of_lesser; i < end - 1; i++) {
      if (map(array[i]) < map(array[end])) {
        _swap(array, i, end_of_lesser);
        ++end_of_lesser;
      }
    }
    _swap(array, end, end_of_lesser);
    // Recurse
    _quicksort(start, end_of_lesser - 1);
    _quicksort(end_of_lesser + 1, end);
  };
  shuffle(array);
  _quicksort(array, map, 0, array_length(array) - 1);
}
 

Joe Ellis

Member
Alphabetized "Maps" (maybe could be called alphamaps)
Searching for things by name usually is a linear scan through a list, but with alphamapsā„¢ you can add them to an array structure based on what letter they begin with, and end with, so in most cases it narrows the amount of names down so that each name has it's own slot, with only a few others in the same slot, which reduces search iterations by about 90%
So instead of iterating through a list 50 times before it gets to the name it's searching for, it simply gets 2 grid coordinates, the cell with the ord value of the first character on the x axis and the ord of the last character on the y axis, then the cell will only contain a few things and will only have to iterate 3 or so times.

So, the search function is this:

GML:
///map_search(map, name)

var m = argument0, name = argument1;

var
i = wrap(ord(string_char_at(name, 1)), 0, 26),
j = wrap(ord(string_char_at(name, string_length(name))), 0, 26);
var c = m[i, j], n;
if c != 0
{
n = c[_mapcell.names]
i = 0
repeat n[0]
{
if n[++i] = name
{
n = c[_mapcell.nodes]
return n[i]
}
}
}
return 0
To create a map to begin with:

GML:
///map_create()

var m, i = -1;

repeat 26
{m[++i, 25] = 0}

return m
Then to add things to the map:

GML:
///map_add(map, thing)

var m = argument0, n = argument1;

var name = n[_node.name];
var
i = wrap(ord(string_char_at(name, 1)), 0, 26),
j = wrap(ord(string_char_at(name, string_length(name))), 0, 26);

var c = m[i, j];
if c = 0
{
c[_node.type] = _type.mapcell
c[_mapcell.map] = m
c[_mapcell.i] = i
c[_mapcell.j] = j
c[_mapcell.names] = float(0)
c[_mapcell.nodes] = float(0)
m[@ i, j] = c
}

var l = c[_mapcell.nodes];
l[@ ++l[@ 0]] = n
l = c[_mapcell.names]
l[@ ++l[@ 0]] = n[_node.name]
and the wrap function is this:
GML:
///wrap(val, min, max)

var val = argument0;
var min_val = argument1;
var max_val = argument2;
var delta = abs(min_val - max_val);

if min_val = max_val
{return min_val}

if val < min_val
{
while val < min_val
{val += delta}
}

if val >= max_val
{
while val >= max_val
{val -= delta}
}

return val
I did make a newer version of wrap using frac, but I kind of made it then forgot to update my project with it so it's still this at the moment, it's hardly any slower though cus in this case it's just used to make numbers wrap and become part of the alphabet, so things like "Thing 1" "Thing 2" will be classed as "Thing A" "Thing B" and have different slots in the map.

And "float(0)" is just a quick way I use of making a blank array with only 1 (0) index:

GML:
var a;
a[0] = argument0
return a
But you may aswell just use this:

GML:
///array_init()
var a;
a[0] = 0
return a
I also always use the [0] index of arrays to hold how many things are in the array\list, instead of using array_length, so that's what the "c[0]" is used for. You can of course change this and use array_length and start the values at 0. I just do it this way cus I like the 1st thing in a list to have the index of 1, and the 39th to have the index of 39, instead of 38. It just seems ridiculous to always have to subtract 1. If it was up to me the 0 index wouldn't exist and it would start at 1, but hay ho :D
 
Last edited:

Samuel Venable

Time Killer
run the application.
click a window to capture a screenshot of it.
paste into mspaint.

C++:
#include <thread>
#include <chrono>
#include <iostream>
#include <windows.h>

using std::string;
using std::cout;
using std::endl;

int main() {
  RECT rc;
  std::this_thread::sleep_for (std::chrono::seconds(1));
  while (true) {
    if ((GetKeyState(VK_LBUTTON) & 0x100) != 0) {
      break;
    }
  }
  std::this_thread::sleep_for (std::chrono::seconds(1));
  HWND hwnd = GetForegroundWindow();
  GetWindowRect(hwnd, &rc);
  HDC hdcScreen = GetDC(NULL);
  HDC hdc = CreateCompatibleDC(hdcScreen);
  HBITMAP hbmp = CreateCompatibleBitmap(hdcScreen, rc.right - rc.left, rc.bottom - rc.top);
  SelectObject(hdc, hbmp);
  PrintWindow(hwnd, hdc, 0);
  OpenClipboard(NULL);
  EmptyClipboard();
  SetClipboardData(CF_BITMAP, hbmp);
  CloseClipboard();
  DeleteDC(hdc);
  DeleteObject(hbmp);
  ReleaseDC(NULL, hdcScreen);
  cout << "successfully copied to clipboard, please paste it into 'mspaint'" << endl;
  return 0;
}
Based on this stackoverflow answer: https://stackoverflow.com/a/7292773/4821390
 
Last edited:

EvanSki

Raccoon Jam Host
GML:
// check for numbers
var _string = "123";

for (var i = 0; i < 10; i++)
{
    //show_message(i) //DEBUG
    if (string_pos((i),_string))
    {
        _string = real(string(_string));
    }
    //other wise the string will stay a string
}
Simple loop checking for numbers,
its not fool proof in any means but its a start, if anyone wants to improve this they are more then encouraged to do so, this is the same amount of a duct tape fix as depth = -y
 

ZeDuval

Member
GML:
// check for numbers
var _string = "123";

for (var i = 0; i < 10; i++)
{
    //show_message(i) //DEBUG
    if (string_pos((i),_string))
    {
        _string = real(string(_string));
    }
    //other wise the string will stay a string
}
Simple loop checking for numbers,
its not fool proof in any means but its a start, if anyone wants to improve this they are more then encouraged to do so, this is the same amount of a duct tape fix as depth = -y
GML:
if( string_digits( str ) == str ) str = real( str )
 

Joe Ellis

Member
-EDIT-
Ignore this, I got confused and thought that ZeDuval had written the original loop version and didn't realize the code under it was his solution to it, which is all that's needed.
 
Last edited:

Samuel Venable

Time Killer
I wrote a quick API that can extract values from a string representing screen coordinates in the form of WidthxHeight+X+Y as well as put those values back into that originally formatted string. I might make this into an extension, but it will need a lot of other stuff to go with it otherwise I don't see people actually using it. The code may be edited and tested online here as well - http://cpp.sh/4smsc

C++:
#include <iostream>
#include <string>
#include <cerrno>

typedef std::string rectangle_t;

static const char ldgrs[256] = {
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0,
  0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0,
  0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0
};

std::string string_digits(std::string str) {
  std::string ret;
  for (const char *c = str.c_str(); *c; c++)
    if (ldgrs[(unsigned char)*c]&4) ret += *c;
  return ret;
}

std::size_t string_count(std::string substr, std::string str) {
  std::size_t pos = 0, occ = 0;
  const std::size_t sublen = substr.length();
  while ((pos = str.find(substr, pos)) != std::string::npos)
    occ++, pos += sublen;
  return occ;
}

void print_help(std::string argv0) {
  std::cout << "usage:     " << argv0 << " <flag>           <WxH+X+Y>  "      << std::endl;
  std::cout << "xposition: " << argv0 << " --xpos   (or -x) 640x480+0+0"      << std::endl;
  std::cout << "yposition: " << argv0 << " --ypos   (or -y) 640x480+0+0"      << std::endl;
  std::cout << "width:     " << argv0 << " --width  (or -w) 640x480+0+0"      << std::endl;
  std::cout << "height:    " << argv0 << " --height (or -h) 640x480+0+0"      << std::endl;
  exit(0);
}

void print_error() {
  std::cout << "error parsing or invalid rectangle!" << std::endl;
  exit(1);
}

void print_int(int value) {
  std::cout << value << std::endl;
  exit(0);
}

void print_uint(unsigned value) {
  std::cout << value << std::endl;
  exit(0);
}

void rectangle_getter(rectangle_t rect, int *ret_x, int *ret_y,
  unsigned *ret_w, unsigned *ret_h, bool *ret_e) {
  bool error = false;
  std::size_t wpos = 0;
  std::size_t hpos = rect.find("x") + 1;
  if (hpos == wpos) error = true;
  if (string_count("+", rect) != 2) error = true;
  std::size_t xpos = rect.find_first_of("+") + 1;
  if (xpos == wpos) error = true;
  std::size_t ypos = rect.find_last_of("+") + 1;
  if (ypos == wpos) error = true;
  std::string w = rect.substr(wpos, hpos - wpos - 1);
  if (string_digits(w).empty()) error = true;
  std::string h = rect.substr(hpos, xpos - hpos - 1);
  if (string_digits(h).empty()) error = true;
  std::string x = rect.substr(xpos, ypos - xpos - 1);
  if (string_digits(x).empty()) error = true;
  std::string y = rect.substr(ypos);
  if (string_digits(y).empty()) error = true;
  *ret_x = !error ? (int)strtol(x.c_str(), nullptr, 10) : 0;
  if (errno == ERANGE) error = true;
  *ret_y = !error ? (int)strtol(y.c_str(), nullptr, 10) : 0;
  if (errno == ERANGE) error = true;
  *ret_w = !error ? (unsigned)strtoul(w.c_str(), nullptr, 10) : 0;
  if (errno == ERANGE) error = true;
  *ret_h = !error ? (unsigned)strtoul(h.c_str(), nullptr, 10) : 0;
  if (errno == ERANGE) error = true;
  *ret_e = error;
}

rectangle_t rectangle_setter(int x, int y, unsigned w, unsigned h) {
  return std::to_string(w) + "x" + std::to_string(h) + "+" +
    std::to_string(x) + "+" + std::to_string(y);
}

unsigned rectangle_get_width(rectangle_t rect) {
  bool ret_e;
  int ret_x, ret_y;
  unsigned ret_w, ret_h;
  rectangle_getter(rect, &ret_x, &ret_y, &ret_w, &ret_h, &ret_e);
  if (ret_e) { print_error(); }
  return ret_w;
}

unsigned rectangle_get_height(rectangle_t rect) {
  bool ret_e;
  int ret_x, ret_y;
  unsigned ret_w, ret_h;
  rectangle_getter(rect, &ret_x, &ret_y, &ret_w, &ret_h, &ret_e);
  if (ret_e) { print_error(); }
  return ret_h;
}

int rectangle_get_x(rectangle_t rect) {
  bool ret_e;
  int ret_x, ret_y;
  unsigned ret_w, ret_h;
  rectangle_getter(rect, &ret_x, &ret_y, &ret_w, &ret_h, &ret_e);
  if (ret_e) { print_error(); }
  return ret_x;
}

int rectangle_get_y(rectangle_t rect) {
  bool ret_e;
  int ret_x, ret_y;
  unsigned ret_w, ret_h;
  rectangle_getter(rect, &ret_x, &ret_y, &ret_w, &ret_h, &ret_e);
  if (ret_e) { print_error(); }
  return ret_y;
}

int main (int argc, char **argv) {
  if (argc == 1) { print_help(argv[0]); }
  if (argc > 3)  { print_error(); }
  std::string flag = argv[1];
  rectangle_t rect = argv[1];
  if (argc == 3) { rect = argv[2]; }
  if (argc == 2 && (flag == "--help" || flag == "-h"))
  { print_help(argv[0]); }
  int x = rectangle_get_x(rect);
  int y = rectangle_get_y(rect);
  unsigned w = rectangle_get_width(rect);
  unsigned h = rectangle_get_height(rect);
  if (flag == "--xpos" || flag == "-x")   { print_int(x); }
  if (flag == "--ypos" || flag == "-y")   { print_int(y); }
  if (flag == "--width" || flag == "-w")  { print_uint(w); }
  if (flag == "--height" || flag == "-h") { print_uint(h); }
  rectangle_t r = rectangle_setter(x, y, w, h);
  std::cout << "unparsed: " << r << std::endl;
  std::cout << "parsed x: " << x << std::endl;
  std::cout << "parsed y: " << y << std::endl;
  std::cout << "parsed w: " << w << std::endl;
  std::cout << "parsed h: " << h << std::endl;
  return 0;
}
It can be done in pure GML I'm sure, but this extension would be more for helping people learn C++ than anything else. Using negative numbers for the width or height of the rectangle will result in those values being treated as UINT_MAX, (which equals 4294967295).

So, entering this into the terminal:
Code:
/path/to/rectparser.exe -1x-1+0+0
...will result in the following output:
Code:
unparsed: 4294967295x4294967295+0+0
parsed x: 0
parsed y: 0
parsed w: 4294967295
parsed h: 4294967295
 
Last edited:

Samuel Venable

Time Killer
I wrote another example of how to use my process information extension. This time I demonstrate how to execute notepad.exe (and the equivalent for other platforms) so that the program stays on top of the game while forcing it to close after an alarm event has triggered.

Create Event:
GML:
globalvar process, directory, command, stayontop;
process[0] = 0;

directory_create(game_save_id);
file_copy(working_directory + "readme.txt", game_save_id + "readme.txt");

if (os_type == os_windows) {
  directory = "C:\\Windows\\System32\\";
  command[0] = "notepad.exe \"" + game_save_id + "readme.txt\"";
} else if (os_type == os_macosx) {
  directory = "/Applications/";
  if (file_exists(directory + "TextEdit.app/Contents/MacOS/TextEdit")) { // older than macOS 10.15 Catalina
    command[0] = directory + "TextEdit.app/Contents/MacOS/TextEdit \"" + game_save_id + "readme.txt\"";
  } else { // macOS 10.15 Catalina or later
    directory = "/System" + directory;
    command[0] = directory + "TextEdit.app/Contents/MacOS/TextEdit \"" + game_save_id + "readme.txt\"";
  }
} else {
  if (os_type == os_linux) {
    directory = "/usr/bin/";
  } else if (os_type == os_freebsd) {
    directory = "/usr/local/bin/";
  }
  if (os_type == os_linux || os_type == os_freebsd) {
    if (file_exists(directory + "gedit")) { // GNOME Text Editor
      command[0] = directory + "gedit \"" + game_save_id + "readme.txt\"";
    } else if (file_exists(directory + "pluma")) { // MATE Text Editor
      command[0] = directory + "pluma \"" + game_save_id + "readme.txt\"";
    } else if (file_exists(directory + "kate")) { // KDE Advanced Text Editor
      command[0] = directory + "kate \"" + game_save_id + "readme.txt\"";
    } else if (file_exists(directory + "kwrite")) { // KDE Text Editor
      command[0] = directory + "kwrite \"" + game_save_id + "readme.txt\"";
    } else if (file_exists(directory + "mousepad")) { // XFCE Text Editor
      command[0] = directory + "mousepad \"" + game_save_id + "readme.txt\"";
    } else if (file_exists(directory + "leafpad")) { // LXDE Text Editor
      command[0] = directory + "leafpad \"" + game_save_id + "readme.txt\"";
    } else { // if you have anything else, forget about it, kid!
      show_error("Missing Text Editor or Unsupported Desktop Environment!", true);
    }
  } else {
    show_error("Unsupported Operating System!", true);
  }
}

stayontop[0] = true;
alarm[0] = 10;
Alarm 0 Event:
GML:
process_create(process[0], command[0], stayontop[0]);
alarm[1] = 500;
Alarm 1 Event:
GML:
process_destroy(process[0]);
process_create.gml Script:
GML:
function process_create(process, command, stayontop) {
  process_execute_async(int64(process), string(command));
  if (stayontop) {
    var hwnd = wid_from_top(), pid = pid_from_wid(hwnd);
    while (pid == 0 || hwnd == wid_from_window(window_handle()) ||
      (pid != process_current(int64(process)) &&
      pid_exists(process_current(int64(process))))) {
      pid = pid_from_wid(hwnd);
      hwnd = wid_from_top();
    }
    wid_set_pwid(hwnd, wid_from_window(window_handle()));
  }
}
process_destroy.gml Script:
GML:
function process_destroy(process) {
  if (process_exists(process)) {
    pid_kill(process_current(int64(process)))
    process_clear_out(int64(process));
    process_clear_pid(int64(process));
    wid_to_top(wid_from_window(window_handle()));
  }
}
process_exists.gml Script:
GML:
function process_exists(process) {
  var ppid = ppid_from_pid(process_current(int64(process)));
  if (string_copy(cmd_from_pid(ppid), 0, 8) == "/bin/sh ") { ppid = ppid_from_pid(ppid); }
  return (pid_exists(process_current(int64(process))) && pid_from_self() == ppid);
}
You can get an example project here:
Or if you want to try the code snippets in your own project, you'll need this extension:
p.s. os_freebsd is just a macro for os_unknown :p
 
Last edited:

Bart

WiseBart
The following script allows you to draw a texture group's layout while running the game, which might come in handy for debugging purposes.
It basically shows the same information as the texture preview option in the game options.
You can modify it further by adding specific colors for each resource type, by choosing which resource type to draw, etc.

GML:
/// dbg_draw_texgroup(x, y, texgroup_name)
var _x = argument0;
var _y = argument1;
var texgroup_name = argument2;

var arr_sprites = texturegroup_get_sprites(texgroup_name);
var arr_fonts = texturegroup_get_fonts(texgroup_name);
var arr_tilesets = texturegroup_get_tilesets(texgroup_name);

var len_sprites = array_length_1d(arr_sprites);
var len_fonts = array_length_1d(arr_fonts);
var len_tilesets = array_length_1d(arr_tilesets);

if (len_sprites == 0 && len_fonts == 0 && len_tilesets == 0) {
    // Nothing to draw
    return;
}

// Get texture page dimensions from the first resource we can find on the page
var resource, tex;
if (len_sprites > 0) {
    var resource = arr_sprites[0];
    var tex = sprite_get_texture(resource, 0);
} else if (len_fonts > 0) {
    var resource = arr_fonts[0];
    var tex = font_get_texture(resource);
} else if (len_tilesets > 0) {
    var tls = arr_tilesets[0];
    var tex = tileset_get_texture(arr_tilesets[0]);
}
var w = 1/texture_get_texel_width(tex);
var h = 1/texture_get_texel_height(tex);

// Keep things tidy by storing the current draw color
var c = draw_get_color();

// Now change the color
draw_set_color(c_red);
draw_rectangle(_x, _y, _x + w, _y + h, true);
draw_set_color(c_white);

// Loop through all different resources that take up space on a texture
for(var i = 0;i < len_sprites;i++) {
    var spr = arr_sprites[i];
    var arr_uvs = sprite_get_uvs(spr, 0);
    draw_rectangle(_x + arr_uvs[0]*w, _y + arr_uvs[1]*h, _x + arr_uvs[2]*w, _y + arr_uvs[3]*h, true);
    draw_text(_x + arr_uvs[0]*w, _y + arr_uvs[1]*h, sprite_get_name(spr));
}

for(var i = 0;i < len_fonts;i++) {
    var fnt = arr_fonts[i];
    var arr_uvs = font_get_uvs(fnt);
    draw_rectangle(_x + arr_uvs[0]*w, _y + arr_uvs[1]*h, _x + arr_uvs[2]*w, _y + arr_uvs[3]*h, true);
    draw_text(_x + arr_uvs[0]*w, _y + arr_uvs[1]*h, font_get_name(fnt));
}

for(var i = 0;i < len_tilesets;i++) {
    var tls = arr_tilesets[i];
    var arr_uvs = tileset_get_uvs(tls);
    draw_rectangle(_x + arr_uvs[0]*w, _y + arr_uvs[1]*h, _x + arr_uvs[2]*w, _y + arr_uvs[3]*h, true);
    draw_text(_x + arr_uvs[0]*w, _y + arr_uvs[1]*h, tileset_get_name(tls));
}

// Restore draw state
draw_set_color(c);
Using the script:
GML:
/// Draw event
dbg_draw_texgroup(x, y, "Default");
 
Last edited:

angelwire

Member
This is a dumb little thing I do because I'm scared I'll forget to come back to a "//Todo: do something" comment. I make it a function instead:

GML:
todo("Give the player a pair of pants");

///@function todo(arg)
function todo(_arg)
{
    //Show as a debug message
    show_debug_message("You forgot to: " + string(_arg));
    //Or throw an error
    //throw ("You forgot to: " + string(_arg));
}
 

hippyman

Member
With these four functions you use JSON files to generate structs at runtime.
EDIT: I added a few more functions so that you can save structs to a JSON file.

test.json
JSON:
{
    "object": { "innerNum": 50 },
    "array": [1,2,3],
    "string_var": "Howdy!"
}
GML:
json_struct = json_load_from_file("test.json");

// json_struct.object == { "innerNum": 50 }
// json_struct.object.innerNum == 50
// json_struct.array == [5,10,15]
// json_struct.array[0] == 5
// json_struct.array[1] == 10
// json_struct.array[2] == 15
// json_struct.string_var == "Howdy!"

GML:
function struct_save_to_file(struct,path) {
    var json_string = struct_get_string(struct);
    var file = file_text_open_write(path);
    file_text_write_string(file,json_string);
    file_text_close(file);
}
GML:
/// @func json_load_from_file
function json_load_from_file(path) {
    var json_string = json_file_get_string(path);
    var json_map = json_decode(json_string);
    var json_object = ds_map_to_struct(json_map);
    ds_map_destroy(json_map);
    return json_object;
}
GML:
/// @func json_file_get_string
function json_file_get_string(path) {
    if (!file_exists(path)) show_error(path + " doesn't exist. Check the path.",true);
    var file = file_text_open_read(path);
    var json_string = ""
    while (!file_text_eof(file)) {
        json_string += file_text_readln(file);
    }
    file_text_close(file);
    return json_string;
}
GML:
/// @func ds_map_to_struct
function ds_map_to_struct(map) {
    var struct = {};
    var key = ds_map_find_first(map);
    var count = ds_map_size(map);
    for (var i = 0; i < count; ++i) {
       
        if (ds_map_is_map(map,key)) {
            // Value is a ds_map
            variable_struct_set(struct,key,ds_map_to_struct(map[? key ]));
        } else if (ds_map_is_list(map,key)) {
            // Value is a ds_list
            variable_struct_set(struct,key,ds_list_to_array(map[? key ]));
        } else {
            // Value is a real/string/boolean
            variable_struct_set(struct,key,map[? key ]);
        }
       
        key = ds_map_find_next(map,key);
    }
   
    return struct;
}
GML:
/// @func ds_list_to_array
function ds_list_to_array(list) {
    var count = ds_list_size(list);
    var array = array_create(count);
    for (var i = 0; i < count; ++i) {
        //var val = list[| i ];
       
        if (ds_list_is_map(list,i)) {
            // Value is a ds_map
            array[ i ] = ds_map_to_struct(list[| i ]);
        } else if (ds_list_is_list(list,i)) {
            // Value is a ds_list
            array[ i ] = ds_list_to_array(list[| i ]);
        } else {
            // value is a real/string/boolean
            array[ i ] = list[| i ];
        }
    }
    return array;
}
GML:
function array_get_string(array) {
    var array_str = "[";
    var count = array_length(array);
    for (var i = 0; i < count; ++i) {
        var val = array[i];
        if (is_struct(val)) {
            val = struct_get_string(val);
        } else if (is_array(val)) {
            val = array_get_string(val);
        } else if (is_string(val)) {
            val = "\"" + val + "\"";
        } else {
            val = string(val);
        }
        
        array_str += val;
        if (i < count-1) {
            array_str += ",";
        }
    }
    
    array_str += "]";
    
    return array_str;
}
GML:
function struct_get_string(struct) {
    var struct_str = "{";
    var var_count = variable_struct_names_count(struct);
    var var_names = variable_struct_get_names(struct)
    for (var i = 0; i < var_count; ++i) {
        var var_name = var_names[i];
        var var_val = variable_struct_get(struct,var_name);
        if (is_struct(var_val)) {
            var_val = struct_get_string(var_val);
        } else if (is_array(var_val)) {
            var_val = array_get_string(var_val);
        } else if (is_string(var_val)) {
            var_val = "\"" + var_val + "\"";
        } else {
            var_val = string(var_val);
        }
        
        struct_str += "\"" + var_name + "\"" + ":" + var_val;
        if (i < var_count-1) {
            struct_str += ",";
        }
    }
    
    struct_str += "}";
    
    return struct_str;
}
 
Last edited:

rytan451

Member
A basic linked list implementation in pure GML (2.3). Untested.

All methods are O(1) time complexity, O(1) space complexity.

new LinkedList()
Creates a new linked list.

LinkedList#size
The size (number of elements) of the linked list.

LinkedList#insertFirst(value)
Inserts an element into the start of the list.

LinkedList#insertLast(value)
Inserts an element into the end of the list.

LinkedList#deleteFirst()
Deletes the first element in the list; returns the value of that element.

LinkedList#deleteLast()
Deletes the last element in the list; returns the value of that element.

LinkedList#clear()
Clears the list; returns the number of elements destroyed.

LinkedList#empty()
Returns true if the list is empty, and false otherwise.

LinkedList#getViewFirst()
Returns a view (type LLView) of the first element. This allows looping through the list.

LinkedList#getViewLast()
Returns a view (type LLView) of the last element. This allows looping through the list.

LLView#current
Returns the value of the viewed element. Setting does not affect the linked list.

LLView#previous()
Moves the view to the previous element. Returns true if said element exists, and false otherwise.

LLView#insertBefore(value)
Inserts an element before the current (viewed) element.

LLView#insertAfter(value)
Inserts an element after the current (viewed) element.

LLView#destroyBefore()
Destroys the element before the current (viewed) element; returns the value of the destroyed element.

LLView#destroyAfter()
Destroys the element after the current (viewed) element; returns the value of the destroyed element.

LLView#set(value)
Sets the value of the current (viewed) element.

GML:
/// @desc Linked List implementation
/// @func LinkedList()
function LinkedList() constructor {
  first = undefined;
  last = undefined;
  size = 0;
  _size = 0;
  static t_element = function(value, next, previous, ll) {
    v = value;
    n = next;
    p = previous;
    par = ll;
    static insertBefore = function(value) {
      var e = new t_element(value, self, p, par);
      if (!is_undefined(p)) p.n = e;
      else par.first = e;
      p = e;
      par._size += 1;
      par.size = _size;
    }
    static insertAfter = function(value) {
      var e = new t_element(value, n, self, par);
      if (is_undefined(n)) par.last = e;
      else n.p = e;
      n = e;
      par._size += 1;
      par.size = _size;
    }
    static destroy = function() {
      if (is_undefined(p)) par.first = n;
      else p.n = n;
      if (is_undefined(n)) par.last = p;
      else n.p = p;
      par._size -= 1;
      par.size = _size;
    }
  }
  static LLView = function(viewing) {
    _c = viewing;
    current = is_undefined(_c) ? undefined : _c.value;
    static previous = function() {
      if (is_undefined(_c) || is_undefined(_c.p)) {
        return false;
      } else {
        _c = _c.p;
        current = is_undefined(_c) ? undefined : _c.value;
        return true;
      }
    }
    static next = function() {
      if (is_undefined(_c) || is_undefined(_c.n)) {
        return false;
      } else {
        _c = _c.n;
        current = is_undefined(_c) ? undefined : _c.value;
        return true;
      }
    }
    static insertBefore = function(value) {
      if (!is_undefined(_c)) {
        _c.insertBefore(value);
      }
    }
    static insertAfter = function(value) {
      if (!is_undefined(_c)) {
        _c.insertAfter(value);
      }
    }
    static destroyBefore = function(value) {
      if (!is_undefined(_c) && !is_undefined(_c.p)) {
        var v = _c.p.value;
        _c.p.destroy();
        return v;
      }
      return undefined;
    }
    static destroyAfter = function(value) {
      if (!is_undefined(_c) && !is_undefined(_c.n)) {
        var v = _c.n.value;
        _c.n.destroy();
        return v;
      }
      return undefined;
    }
    static set = function(value) {
      if (!is_undefined(_c)) {
        _c.value = value;
      }
    }
    static destroy = function() {};
  }
  
  static empty = function() {
    return _size == 0;
  }
  static clear = function() {
    var s = _size;
    first = undefined;
    last = undefined;
    _size = 0;
    size = _size;
    return s;
  }
  static insertFirst = function(value) {
    var e = new t_element(value, first, undefined, self);
    if (!is_undefined(first)) {
      first.p = e;
    }
    if (is_undefined(last)) {
      last = e;
    }
    first = e;
    _size += 1;
    size = _size;
  }
  static insertLast = function(value) {
    var e = new t_element(value, undefined, last, self);
    if (!is_undefined(last)) {
      last.n = e;
    }
    if (is_undefined(first)) {
      first = e;
    }
    last = e;
    _size += 1;
    size = _size;
  }
  static deleteFirst = function() {
    if (is_undefined(first)) {
      return undefined;
    }
    var v = first.value;
    first = first.n;
    if (is_undefined(first)) {
      last = first;
    } else {
      first.p = undefined;
    }
    _size -= 1;
    size = _size;
    return v;
  }
  static deleteLast = function() {
    if (is_undefined(last)) {
      return undefined;
    }
    var v = last.value;
    last = last.p;
    if (is_undefined(last)) {
      first = last;
    } else {
      last.n = undefined;
    }
    _size -= 1;
    size = _size;
    return v;
  }
  static getViewFirst = function() {
    return new LLView(first);
  }
  static getViewLast = function() {
    return new LLView(last);
  }
  
  static destroy = function() {}
}
 

Joe Ellis

Member
Here are two functions for HSL color handling (hue, sat, lum)
I've always preferred color pickers that use lum instead of val, cus I find it awkward to create pastel colors with hue sat val. You have to adjust the sat and val separately and lum makes it a lot simpler. I generally just prefer the way it works cus it seems more logical and I'm used to it from using paint since I was little.

I got the original code from https://www.easyrgb.com/en/, which is a great resource for anything color related.

I can't actually explain what the maths is doing cus I don't understand it 100%, more like 65% lol
but it is explained on https://www.easyrgb.com/en/

So this is the function for creating a color using hsl: (It's basically the same as make_color_hsv, but with lum instead)

GML:
function make_color_hsl(hue, sat, lum) {

//Same as make_color_hsv only it uses lum instead of val (Like the ms paint color picker)
//and returns a standard gm color value (u32 integer)
//Note: the input ranges are: hue(0 - 1530), sat(0 - 255), lum(0 - 510)
//This means you're able to use all 1530 possible hues instead of just 255 like make_color_hsv

    //Convert input values to 0-1 range
    hue /= 1530
    sat /= 255
    lum /= 510

    var
    r = hue + 0.333,
    g = hue,
    b = hue - 0.333,
    v1, v2, _range;
  
    if lum < 0.5
    {v1 = lum * (1.0 + sat)}
    else
    {v1 = lum + sat - lum * sat}

    v2 = 2 * lum - v1

    _range = v1 - v2

    //Wrap rgb values to 0-1 range
    if r > 1
    || r < 0
    {r -= sign(r)}
  
    if g > 1
    || g < 0
    {g -= sign(g)}
  
    if b > 1
    || b < 0
    {b -= sign(b)}

    //Red
    if 6 * r < 1
    {r = v2 + _range * 6 * r}
    else
    {
    if 2 * r < 1
    {r = v1}
    else
    {
    if 3 * r < 2
    {r = v2 + _range * (0.666 - r) * 6}
    else
    {r = v2}
    }
    }

    //Green
    if 6 * g < 1
    {g = v2 + _range * 6 * g}
    else
    {
    if 2 * g < 1
    {g = v1}
    else
    {
    if 3 * g < 2
    {g = v2 + _range * (0.666 - g) * 6}
    else
    {g = v2}
    }
    }

    //Blue
    if 6 * b < 1
    {b = v2 + _range * 6 * b}
    else
    {
    if 2 * b < 1
    {b = v1}
    else
    {
    if 3 * b < 2
    {b = v2 + _range * (0.666 - b) * 6}
    else
    {b = v2}
    }
    }

    return make_color_rgb(r * 255, g * 255, b * 255)
}

And this is the function for getting the hue, sat and lum values of a color (A standard gm color as the input argument)

GML:
function color_get_hsl(color) {

//Gets the hue, sat and lum of a given color (standard gm color value)
//and returns them in a vec3 array
//Note: the output ranges are: hue(0 - 1530), sat(0 - 255), lum(0 - 510)

    var
    r = colour_get_red(color) / 255,
    g = colour_get_green(color) / 255,
    b = colour_get_blue(color) / 255,
    _min, _max, _range, hue, sat, lum;

    _min = min(r, g, b)
    _max = max(r, g, b)
    _range = _max - _min

    lum = (_max + _min) / 2

    if _range = 0
    {
    hue = 0
    sat = 0
    }
    else
    {

    if lum < 0.5
    {sat = _range / (_max + _min)}
    else
    {sat = _range / (2 - _max - _min)}

    if r = _max
    {
    if g > b
    {hue = (g - b) / _range}
    else
    {hue = 6 - ((b - g) / _range)}
    }
    else
    {
    if g = _max
    {hue = 2 + (b - r) / _range}
    else
    {hue = 4 + (r - g) / _range}
    }
    if h < 0
    {++hue}
    if h > 6
    {--hue}
    }

    return [round(hue * 1530), round(sat * 255), round(lum * 510)]
}

So now y'all guys can make hsl color pickers! šŸ¤— No credit required lol
 

Samuel Venable

Time Killer
Remember from GameMaker 8.1 splash_show_video(fname, loop)?
GML:
// Initialize defaults...
function splash_initialize() {
  globalvar splash_get_main, splash_get_caption, splash_get_fullscreen,
  splash_get_border, splash_get_volume;

  splash_get_main = true;
  splash_get_caption = "";
  splash_get_fullscreen = false;
  splash_get_border = true;
  splash_get_volume = 100;
}

// splash_set_main(main) Indicated whether the splash screen must be
// shown in the main game window (true, default) or in a separate window
// (false).

function splash_set_main(main) {
  splash_get_main = main;
}

// splash_set_caption(cap) Sets the caption for the splash window. This
// only has effect when is a separate splash window is used. Default the
// empty string is used.

function splash_set_caption(cap) {
  splash_get_caption = cap;
}

// splash_set_fullscreen(full) Indicates whether to use a full screen
// window or not. This only has effect when is a separate splash window is
// used. Default a normal window is used.

function splash_set_fullscreen(full) {
  splash_get_fullscreen = full;
}

// splash_set_border(border) Indicates whether the window should have a
// border. This only has effect when is a separate normal splash window is
// used. Default a border is used.

function splash_set_border(border) {
  splash_get_border = border;
}

// splash_set_volume(vol) Indicates the percent of volume the splash
// screen is to have if video or audio. Default the value is 100.

function splash_set_volume(vol) {
  splash_get_volume = vol;
}

// splash_show_video(fname,loop) Shows a video splash screen. fname is the
// name of the video file. loop indicates whether to loop the video.

function splash_show_video(fname, loop) {
  globalvar video;
  var wid, wstr, hstr, xstr, ystr, size, pstn, geom;

  if (splash_get_main) {
    if (os_type == os_macosx) {
      wid = window_get_contentview(string(int64(window_handle())));
    } else { wid = string(int64(window_handle())); }
  } else { wid = "-1"; }

  if (splash_get_fullscreen) splash_get_fullscreen = "yes";
  else splash_get_fullscreen = "no";

  if (splash_get_border) splash_get_border = "yes";
  else splash_get_border = "no";

  if (splash_get_main) splash_get_main = "no";
  else splash_get_main = "yes";

  if (loop) loop = "yes";
  else loop = "no";

  wstr = string(window_get_width());
  hstr = string(window_get_height());
  xstr = string(window_get_x());
  ystr = string(window_get_y());
  size = wstr + "x" + hstr;
  pstn = xstr + "+" + ystr;
  geom = size + "+" + pstn;

  video = video_add(fname);
  video_set_option_string(video, "volume", string(splash_get_volume));
  video_set_option_string(video, "input-default-bindings", "no");
  video_set_option_string(video, "title", splash_get_caption);
  video_set_option_string(video, "fs", splash_get_fullscreen);
  video_set_option_string(video, "border", splash_get_border);
  video_set_option_string(video, "keepaspect-window", "no");
  video_set_option_string(video, "taskbar-progress", "no");
  video_set_option_string(video, "ontop", splash_get_main);
  video_set_option_string(video, "geometry", geom);
  video_set_option_string(video, "config", "no");
  video_set_option_string(video, "loop", loop);
  video_set_option_string(video, "osc", "no");
  video_set_option_string(video, "wid", wid);

  video_play(video);
  var canpause = true;
  if (os_type == os_windows) {
    while (video_is_playing(video)) {
      /* handle video pause and stop actions here */
      /* use keyboard_check_direct for pause/stop */
      /* you can use window_has_focus in if check */
      /* keyboard_check_direct is broken on linux */
      /* use async video playback for linux input */
      // example:
      if (window_has_focus()) {
        if (keyboard_check_direct(vk_escape)) {
          video_stop(video);
        } else if (keyboard_check_direct(vk_space)) {
          if (canpause) {
            video_pause(video);
            canpause = false;
          }
        } else {
          canpause = true;
        }
      }
    }
    video_delete(video);
  }
}

// Process Pause/Stop input on non-Windows
function splash_update() {
  if (os_type != os_windows) {
    if (video_exists(video)) {
      if (keyboard_check_pressed(vk_escape)) {
        video_stop(video);
        video_delete(video);
      } else if (keyboard_check_pressed(vk_space)) {
        video_pause(video);
      } else if (!video_is_playing(video)) {
        video_delete(video);
      }
    }
  }
}
Needs this extension.

Example...

Create Event:
GML:
if (os_type == os_macosx) {
  /* macOS-specific instructions for building & running games */   

  // Backspace the signing identity of your mac app to skip code
  // signing; doing so will allow you to move the necessary libs
  // to the correct place in your mac bundle. After building the
  // game, you will need to move ALL *.dylib files in the *.app,
  // EXCEPT "libmpv.1.dylib" and "libvidplayer.dylib" from *.app
  // YouAppBundle.app/Contents/Resources to Contents/Frameworks.
  // After you have moved these files to your frameworks folder,
  // you may then codesign your app manually, and open your app.

  // install libmpv on Mac OS X
  // look at macOS Game Options
  // disable filesystem sandbox

  var src, dst;
  dst = "/usr/local/opt/mpv";
  directory_create(dst);
  dst = "/usr/local/opt/mpv/lib";
  directory_create(dst);
  src = working_directory;
  file_copy(src + "/libmpv.1.dylib",
    dst + "/libmpv.1.dylib");
}

// requirement: any video file supported by ffmpeg & mpv media player
// see online manual for option strings https://mpv.io/manual/stable/
// video_set_option_string() wraps the mpv_set_option_string function

// Ubuntu packages install needed:
// sudo apt-get install libmpv-dev

// Initialize defaults...
splash_initialize();

// splash_set_main(main) Indicated whether the splash screen must be
// shown in the main game window (true, default) or in a separate window
// (false).

splash_set_main(true);

// splash_set_caption(cap) Sets the caption for the splash window. This
// only has effect when is a separate splash window is used. Default the
// empty string is used.

splash_set_caption(window_get_caption());

// splash_set_fullscreen(full) Indicates whether to use a full screen
// window or not. This only has effect when is a separate splash window is
// used. Default a normal window is used.

splash_set_fullscreen(window_get_fullscreen());

// splash_set_border(border) Indicates whether the window should have a
// border. This only has effect when is a separate normal splash window is
// used. Default a border is used.

splash_set_border(true);

// splash_set_volume(vol) Indicates the percent of volume the splash
// screen is to have if video or audio. Default the value is 100.

splash_set_volume(100);

// splash_show_video(fname,loop) Shows a video splash screen. fname is the
// name of the video file. loop indicates whether to loop the video.

splash_show_video(working_directory + "example.webm", false);
Step Event:
GML:
// Process Pause/Stop input on non-Windows
splash_update();

if (!video_exists(video)) {
  // End the game when video playback is ogre...
  // ...it's never ogre...
  // ...shrek is love...
  // ...shrek is life...
  game_end();
}
 
Last edited:

rytan451

Member
An (untested) implementation of the Union-Find Disjoint Set data structure (GC-safe). This data structure works well for Kruskal's Algorithm (for finding minimum/maximum spanning trees), as well as incremental graph connectivity.

GML:
/// @func UnionFindDisjointSet
/// @desc A Union-Find Disjoint Set implementation
function UnionFindDisjointSet(size) constructor {
  var _set, _rank;
  _rank = array_create(size, 0);
  _set = array_create(size);
  for (var i = 0; i < size; i++) {
    _set[i] = i;
  }

  static find = function(element) {
    if (_set[element] != element && _set[_set[element]] != _set[element]) {
      _set[element] = find(_set[element])
    }
    return _set[element];
  }

  static union = function(e1, e2) {
    e1 = find(e1);
    e2 = find(e2);
    switch (sign(_rank[e1] - _rank[e2])) {
      case 0:
        _rank[e1] += 1;
      case 1:
        _set[e2] = e1;
        break;
      case -1:
        _set[e1] = e2;
    }
  }
}
This implementation uses both path compression and union by rank, so their asymptotic time complexity is O(a(n)), where a(n) is the inverse Ackermann function.

(For some reason, I keep implementing data structures and algorithms in GML, but I never end up using them in any games. Or working on any games in any form of consistency)
 

FoxyOfJungle

Kazan Games
Simple string code generator:

GML:
/// @func generate_code(chars, size)
/// @arg chars
/// @arg size

function generate_code(chars, size)
{
    var _len = string_length(chars);

    var _strfinal = "";
    for (var i = 0; i < size; i+=1)
    {
        _strchars[i] = string_char_at(chars, irandom(_len));
        _strfinal += _strchars[i];
    }

    return string(_strfinal);
}
You define the character combination, the size and the function will generate a code.


Usage:

GML:
var code = generate_code("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 12);

Generated examples:

Code:
MGQEx7B5PX5s
laV38d2kCQlWjbR
B18BuAhIKcQAeMl9RMGqDbg0J1azpM
 

Yal

šŸ§ *penguin noises*
GMC Elder
Simple string code generator:

GML:
/// @func generate_code(chars, size)
/// @arg chars
/// @arg size

function generate_code(chars, size)
{
    var _len = string_length(chars);

    var _strfinal = "";
    for (var i = 0; i < size; i+=1)
    {
        _strchars[i] = string_char_at(chars, irandom(_len));
        _strfinal += _strchars[i];
    }

    return string(_strfinal);
}
You define the character combination, the size and the function will generate a code.


Usage:

GML:
var code = generate_code("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 12);

Generated examples:

Code:
MGQEx7B5PX5s
laV38d2kCQlWjbR
B18BuAhIKcQAeMl9RMGqDbg0J1azpM
Improvement suggestion... ideally you should have some sort of checksum as well, so it's possible to tell a valid code from an invalid code without having to store a list of all the codes you've generated (and checking the entire list every time!)
 

FoxyOfJungle

Kazan Games
Advanced print function that allow multiple arguments:

GML:
function print() {
    if (argument_count > 0) {
        var _log = "";
        for (var i = 0; i < argument_count; i++;) {
            _log += string(argument[i]);
            if (argument_count > 1) _log += " | ";
        }
        show_debug_message(_log);
    }
}

Examples:

GML:
print();
print("AAA");
print("AAA", "BBB");
print("AAA", "BBB", "CCC");
print(4524, "ASD", true, false);

GMS 2 Console output:

Code:
AAA
AAA | BBB |
AAA | BBB | CCC |
4524 | ASD | 1 | 0 |
Note: As the first function was empty, so it wasn't even printed on the console.

I needed this function when I had to quickly display multiple variables with the " | " separator on the same line on the console.
 
Last edited:

FoxyOfJungle

Kazan Games
Get the top instance of an object:

GML:
function instance_top_position(mx, my, object)
{
    /// @func instance_top_position(mx, my, object)
    /// @arg mx
    /// @arg my
    /// @arg object

    var _top_instance = noone;

    var _list = ds_list_create();
    var _num = collision_point_list(mx, my, object, false, true, _list, false);
    if (_num > 0)
    {
        for (var i = 0; i < _num; ++i;)
        {
            _top_instance = _list[| ds_list_size(_list)-1]
        }
    }

    ds_list_destroy(_list);

    return _top_instance;
}

Get the top instance on a specific layer:

GML:
function layer_instance_top_position(mx, my, layer_id)
{
    /// @func layer_instance_top_position(mx, my, layer_id)
    /// @arg mx
    /// @arg my
    /// @arg layer_id
 
    var _top_instance = noone;
 
    // create list
    var _list = ds_list_create();
 
    // add layer objects to the list
    var _array = layer_get_all_elements(layer_id);

    var i = array_length(_array)-1;
    repeat (array_length(_array))
    {
        if (layer_get_element_type(_array[i]) == layerelementtype_instance)
        {
            var _inst = layer_instance_get_instance(_array[i]);
             
            if position_meeting(mx, my, _inst) ds_list_add(_list, _inst);
        }
        i -= 1;
    }
 
    // get top instance
    var _num = ds_list_size(_list);
    if (_num > 0)
    {
        for (var i = 0; i < _num; ++i;)
        {
            _top_instance = _list[| ds_list_size(_list)-1]
        }
    }
 
    // destroy list
    ds_list_destroy(_list);
 
    return _top_instance;
}

It would be nice if these were built-in functions of GMS 2.


Utiki - UI Editor for Game Maker
 
Last edited:

EvanSki

Raccoon Jam Host
GML:
function draw_set_default(){
//Sets all Draw options to there default states.

draw_set_alpha(1);
draw_set_circle_precision(24);
draw_set_color(c_black);
draw_set_font(-1);
draw_set_halign(fa_left);
draw_set_lighting(false);
draw_set_valign(fa_top);

}
call at the end of a draw event and never worry about text not being drawn correctly
 

Joe Ellis

Member
I've just gone through the utility functions that I use quite often:
A bit of a mixture, all are quite useful I think

GML:
function string_to_array(str) {

//Creates an array from a string
//The string uses commas to separate each value

    var
    commas = string_count(",", str),
    a, i = -1, comma;
    a[commas] = 0
    repeat commas
    {
    comma = string_pos(",", str)
    a[++i] = real(string_copy(str, 1, comma - 1))
    str = string_delete(str, 1, comma)
    }
    a[++i] = real(str)

    return a
}

function string_format_var(str) {
  
//Formats a string so it's safe to use as a variable name
//ei. with underscores instead of spaces and all lower case
  
    return string_replace_all(string_lower(str), " ", "_")
}

function line_intersect_bbox(lx1, ly1, lz1, lx2, ly2, lz2, bbox_x1, bbox_y1, bbox_z1, bbox_x2, bbox_y2, bbox_z2) {

    //Detects whether a 3d line\ray intersects a 3d bounding box
    //and returns the intersect position if there is one.
    //Useful for bullet collisions
    //Based on the SLAB method (Look it up!)
  
    var
    tmin = -10000000,
    tmax = 10000000,
    nx, ny, nz, d, v1, v2;

    nx = lx2 - lx1
    ny = ly2 - ly1
    nz = lz2 - lz1
    d = 1 / point_distance_3d(0, 0, 0, nx, ny, nz)
    nx *= d
    ny *= d
    nz *= d

    if nx != 0.0
    {
    v1 = (bbox_x1 - lx1) / nx
    v2 = (bbox_x2 - lx1) / nx
    tmin = max(tmin, min(v1, v2))
    tmax = min(tmax, max(v1, v2))
    }

    if ny != 0.0
    {
    v1 = (bbox_y1 - ly1) / ny
    v2 = (bbox_y2 - ly1) / ny
    tmin = max(tmin, min(v1, v2))
    tmax = min(tmax, max(v1, v2))
    }

    if nz != 0.0
    {
    v1 = (bbox_z1 - lz1) / nz
    v2 = (bbox_z2 - lz1) / nz
    tmin = max(tmin, min(v1, v2))
    tmax = min(tmax, max(v1, v2))
    }

    if tmax >= tmin
    {return [lx1 + (nx * tmin), ly1 + (ny * tmin), lz1 + (nz * tmin)]}

    return 0
}

function clamp_vec3(_x, _y, _z, _max) {

//Clamps the length of a vector whilst maintaining the direction

    var d = point_distance_3d(0, 0, 0, _x, _y, _z);

    if d > _max
    {
    d = _max / d
    return [_x * d, _y * d, _z * d]
    }

    return [_x, _y, _z]
}

function pos3d_get_screen_pos(_x, _y, _z) {

//Gets the position on the screen of a 3d coordinate, given the current view & projection matrices
//Requires global.matrix_world_view_projection to be pre calculated before hand,
//Ideally just after the view & projection matrices have been set:
//global.matrix_world_view_projection =
//matrix_multiply(matrix_get(matrix_world), matrix_multiply(matrix_get(matrix_view), matrix_get(matrix_projection)))

    var pos = matrix_transform_vertex(global.matrix_world_view_projection, _x, _y, _z);
  
    _z = pos[2] + 1

    //If the depth is "behind" the view focal point(xyz_from), set to -10000 so it's off the screen
    if _z < 0
    {return [-10000, -10000, _z]}

    //Convert the position from clip space to pixel space:
    pos[0] = (1 + (pos[0] / _z)) * 0.5 * screen_width
    pos[1] = (1 - (pos[1] / _z)) * 0.5 * screen_height
    return pos
}

function decel_val(val, amt) {

//Moves a value (negative or positive) towards zero.
//Useful for deceleration of speed variables
//Has a quicker\sharper effect than lerping or multiplying speed by 0.9 etc.
//Similar effect to older games

    if val = 0
    {return 0}

    var s = sign(val);

    val -= amt * s

    if s != sign(val)
    {return 0}

    return val
}

function yaw_pitch_length(yaw, pitch, length) {

//Creates a 3d vector(3 component array)

    var l = lengthdir_x(length, pitch);

    return [lengthdir_x(l, yaw), lengthdir_y(l, yaw), lengthdir_y(length, pitch)]
}

function vec3_to_yaw_pitch(vx, vy, vz) {

//Gets the yaw, pitch and length of a vector and returns a vec3 array

    return [
    point_direction(0, 0, vx, vy),
    point_direction(0, 0, point_distance(0, 0, vx, vy), vz),
    point_distance_3d(0, 0, 0, vx, vy, vz)]
}
 

Samuel Venable

Time Killer
Load a cylindrical panorama named "panorama.png" and view it in first person 360 degree scrolling perspective (requires lodepng):

C++:
/*

MIT License

Copyright Ā© 2021 Samuel Venable

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

*/

#include <cmath>
#include <cstring>
#include <climits>

#include <string>

#include "lodepng.h"

#include <sys/types.h>
#include <unistd.h>

#if defined(__APPLE__) && defined(__MACH__)
#include <GLUT/glut.h>
#include <libproc.h>
#else
#include <GL/glut.h>
#endif

void CreateCylinder() {
  const double PI = 3.141592653589793;
  double i, resolution  = 0.3141592653589793;
  double height = 3.5;
  double radius = 0.5;
  glPushMatrix();
  glRotatef(90, 0, 90, 0);
  glTranslatef(0, -1.75, 0);
  glBegin(GL_TRIANGLE_FAN);
  glTexCoord2f(0.5, 0.5);
  glVertex3f(0, height, 0);
  for (i = 2 * PI; i >= 0; i -= resolution) {
    glTexCoord2f(0.5f * cos(i) + 0.5f, 0.5f * sin(i) + 0.5f);
    glVertex3f(radius * cos(i), height, radius * sin(i));
  }
  glTexCoord2f(0.5, 0.5);
  glVertex3f(radius, height, 0);
  glEnd();
  glBegin(GL_TRIANGLE_FAN);
  glTexCoord2f(0.5, 0.5);
  glVertex3f(0, 0, 0);
  for (i = 0; i <= 2 * PI; i += resolution) {
    glTexCoord2f(0.5f * cos(i) + 0.5f, 0.5f * sin(i) + 0.5f);
    glVertex3f(radius * cos(i), 0, radius * sin(i));
  }
  glEnd();
  glBegin(GL_QUAD_STRIP);
  for (i = 0; i <= 2 * PI; i += resolution) {
    const float tc = (i / (float)(2 * PI));
    glTexCoord2f(tc, 0.0);
    glVertex3f(radius * cos(i), 0, radius * sin(i));
    glTexCoord2f(tc, 1.0);
    glVertex3f(radius * cos(i), height, radius * sin(i));
  }
  glTexCoord2f(0.0, 0.0);
  glVertex3f(radius, 0, 0);
  glTexCoord2f(0.0, 1.0);
  glVertex3f(radius, height, 0);
  glEnd();
  glPopMatrix();
}

std::string exeParentDirectory() {
  std::string fname;
  #if defined(__APPLE__) && defined(__MACH__)
  char exe[PROC_PIDPATHINFO_MAXSIZE];
  if (proc_pidpath(getpid(), exe, sizeof(exe)) > 0) {
    fname = exe;
  }
  #elif defined(__linux__) && !defined(__ANDROID__)
  char exe[PATH_MAX];
  std::string symLink = "/proc/self/exe";
  if (realpath(symLink.c_str(), exe)) {
    fname = exe;
  }
  #elif defined(__FreeBSD__)
  int mib[4]; size_t s;
  mib[0] = CTL_KERN;
  mib[1] = KERN_PROC;
  mib[2] = KERN_PROC_PATHNAME;
  mib[3] = -1;
  if (sysctl(mib, 4, nullptr, &s, nullptr, 0) == 0) {
    std::string str; str.resize(s, '\0');
    char *exe = str.data();
    if (sysctl(mib, 4, exe, &s, nullptr, 0) == 0) {
      fname = exe;
    }
  }
  #endif
  size_t fpos = fname.find_last_of("/");
  return fname.substr(0, fpos + 1);
}

GLuint tex;
void CreateTexture(const char *fname) {
  unsigned char *data = nullptr;
  unsigned pngwidth, pngheight;
  unsigned error = lodepng_decode32_file(&data, &pngwidth, &pngheight, fname);
  if (error) return;
  const int size = pngwidth * pngheight * 4;
  unsigned char *buffer = new unsigned char[size]();
  for (unsigned i = 0; i < pngheight; i++) {
    for (unsigned j = 0; j < pngwidth; j++) {
      unsigned oldPos = (pngheight - i - 1) * (pngwidth * 4) + 4 * j;
      unsigned newPos = i * (pngwidth * 4) + 4 * j;
      buffer[newPos + 0] = data[oldPos + 0];
      buffer[newPos + 1] = data[oldPos + 1];
      buffer[newPos + 2] = data[oldPos + 2];
      buffer[newPos + 3] = data[oldPos + 3];
    }
  }
  glGenTextures(1, &tex);
  glBindTexture(GL_TEXTURE_2D, tex);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexImage2D(GL_TEXTURE_2D, 0,GL_RGBA, pngwidth, pngheight, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
  delete[] buffer;
  delete[] data;
}

float angle = 0;
void timer(int value) {
  angle += 0.1;
  glutPostRedisplay();
  glutTimerFunc(16, timer, 0);
}

void display() {
  glClearColor(0, 0, 0, 1);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(90.0f, 16/9, 0.1f, 100.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glTranslatef(0, 0, 0);
  glEnable(GL_CULL_FACE);
  glCullFace(GL_BACK);
  glFrontFace(GL_CW);
  glEnable(GL_DEPTH_TEST);
  glLoadIdentity();
  glRotatef(angle, 0, 1, 0);
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, tex);
  CreateCylinder();
  glutSwapBuffers();
}

int window = 0;
void keyboard(unsigned char key, int x, int y) {
  switch (key) {
    case 27:
      glutDestroyWindow(window);
      exit(0);
      break;
  }
  glutPostRedisplay();
}

int main(int argc, char **argv) {
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DOUBLE);
  window = glutCreateWindow("");
  glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
  glClearDepth(1.0f);
  glEnable(GL_DEPTH_TEST);
  glDepthFunc(GL_LEQUAL);
  glShadeModel(GL_SMOOTH);
  glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
  CreateTexture("panorama.png");
  glutDisplayFunc(display);
  glutTimerFunc(0, timer, 0);
  glutSetCursor(GLUT_CURSOR_NONE);
  glutKeyboardFunc(keyboard);
  glutFullScreen();
  glutMainLoop();
  return 0;
}

Check out panoview on GitHub, which the above code is based on.

360 degree cylindrical panorama viewer - can be used graphically or from the command line




 
Last edited:

FoxyOfJungle

Kazan Games
Compact tween function that uses the animation curves of GMS 2.3:

GML:
function tween(curve, position, channels_array, channel_index) {
    var _cv_ch_lenght = array_length(channels_array);
    var _cv_ch = 0;
    var _cv = animcurve_get(curve);
    var _cv_value = 0;
    if (channel_index < _cv_ch_lenght) {
        _cv_ch = animcurve_get_channel(_cv, channels_array[@ channel_index]);
        _cv_value = animcurve_channel_evaluate(_cv_ch, position);
    }
    return _cv_value;
}

Example of use:



GML:
// One
curvePosition = approach(curvePosition, 1, 0.02);
var _curveValue = tween(ac_tween_elastic, curvePosition, ["tweenIn"], 0);

// Two
curvePosition = approach(curvePosition, 1, 0.02);
var _curveValue = tween(ac_tween_elastic, curvePosition, ["tweenIn", "tweenOut"], 1);

Result:

 
Last edited:

EvanSki

Raccoon Jam Host
Apparently made this and forgot about it
Its an edit of an existing script

This one draws the grid with color to easily show information
such as a mp_grid to ds_grid



GML:
function ds_grid_draw(grid,sx,sy) {
//
//  Draws the data of a given grid at a screen location.
//
//      grid        grid data structure, id
//      x,y         screen position, real
//
// GMLscripts.com/license
    var w = ds_grid_width(grid);
    var h = ds_grid_height(grid);

    var M = ds_grid_get_max(grid,0,0,w-1,h-1);
    var m = ds_grid_get_min(grid,0,0,w-1,h-1);
    if (M == m) var f = 0 else var f = 1/(M-m);

    for (var i=0; i<w; i++)
    {
        for (var j=0; j<h; j++)
        {

            var value = (ds_grid_get(grid,i,j));
            if (value == -1)
            {
                value = 1;
                draw_text_color(sx+i*20,sy+j*20,string(value),c_red,c_red,c_red,c_red,1);
            }else{
                value = 0;
                draw_text_color(sx+i*20,sy+j*20,string(value),c_lime,c_lime,c_lime,c_lime,1);
            }
         
        }
    }

    return 0;
}
 

Samuel Venable

Time Killer

Quick and easy borderless windows for Linux and Mac:

borderless.cpp (linux)
C++:
#include <X11/Xlib.h>
#define EXPORTED_FUNCTION extern "C" __attribute__((visibility("default")))
EXPORTED_FUNCTION double borderless(void *handle) {
  typedef struct {
    unsigned long flags;
    unsigned long functions;
    unsigned long decorations;
    long inputMode;
    unsigned long status;
  } Hints;
  Display *d = XOpenDisplay(nullptr);
  Window w = (Window)handle;
  Hints hints;
  Atom property = XInternAtom(d, "_MOTIF_WM_HINTS", false);
  hints.flags = 2;
  hints.decorations = 0;
  XChangeProperty(d, w, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
  XCloseDisplay(d);
  return 0;
}
borderless.mm (mac)
Objective-C:
#include <Cocoa/Cocoa.h>
#define EXPORTED_FUNCTION extern "C" __attribute__((visibility("default")))
@interface NSWindow(subclass)
-(BOOL)canBecomeKeyWindow;
-(BOOL)canBecomeMainWindow;
@end
@implementation NSWindow(subclass)
-(BOOL)canBecomeKeyWindow{return YES;}
-(BOOL)canBecomeMainWindow{return YES;}
@end
EXPORTED_FUNCTION double borderless(void *handle) {
  NSWindow *w = (NSWindow *)handle;
  [w setStyleMask:NSWindowStyleMaskBorderless];
  return 0;
}
build64.sh
Code:
#!/bin/sh
cd "${0%/*}"
if [ $(uname) = "Darwin" ]; then
  clang++ borderless.mm -o libborderless.dylib -std=c++17 -shared -ObjC++ -framework Cocoa -m64
elif [ $(uname) = "Linux" ]; then
  g++ borderless.cpp -o libborderless.so -std=c++17 -shared -static-libgcc -static-libstdc++ -lX11 -m64
fi
...and with borderless mode available under Windows Game Options, all 3 desktops are now supported.





 
Last edited:

EvanSki

Raccoon Jam Host
idk when you'd need it, but heres a script to convert a string to unicode

GML:
function string_unicode(input_string){

    //make input uppercase so we are in 65-90 range
    input_string = string_upper(input_string);
    
    //get length
    input_string_length = string_length(input_string);
    
    //empty unicode string
    unicode_string = " ";
    
    //convert input word to numbers
    for (var i = 1; i < (input_string_length+1); ++i){
        var _n = ord(string_char_at(input_string,i));
        //adds the two numbers on unicode letter to the end of the string using a formula to get the pos it needs
        unicode_string = string_insert(string(_n),unicode_theme_word,string_length(unicode_string)+2);
    }
    
    return(unicode_string);
}
EXAMPLE:
Code:
string_apple = "apple";

unicode_apple = string_unicode(string_apple);

//unicode_apple = 6580807669
 

FoxyOfJungle

Kazan Games
Simple function to draw a sprite with aspect ratio that's fit an area:

GML:
function draw_sprite_aspect(sprite, subimage, x, y, xscale, yscale, rot, col, alpha, areaw, areah) {
    var xsc, ysc;
    if (areaw != 0) {
        xsc = areaw / sprite_get_width(sprite) * xscale;
        ysc = xsc;
    } else {
        ysc = areah / sprite_get_height(sprite) * yscale;
        xsc = ysc;
    }
    draw_sprite_ext(sprite, subimage, x, y, xsc, ysc, rot, col, alpha);
}

Original sprite size: 720 x 320
GUI size: 720 x 1280


Usage one:

GML:
var scale = device_mouse_x_to_gui() / display_get_gui_width();
draw_sprite_aspect(spr_studio_logo, 0, display_get_gui_width()/2, display_get_gui_height()/2, scale, scale, 0, c_white, 1, display_get_gui_width(), 0);



Usage two:

GML:
var scale = device_mouse_y_to_gui() / display_get_gui_height();
draw_sprite_aspect(spr_studio_logo, 0, display_get_gui_width()/2, display_get_gui_height()/2, scale, scale, 0, c_white, 1, 0, display_get_gui_height());
 
Last edited:

Samuel Venable

Time Killer
If you want your GameMaker game to open a file when it is drag-and-dropped onto the game's exe in your file explorer, you may use parameter_count() and parameter_string(). But if you drag two files one at a time onto the exe, you'll notice multiple instances of the game will open. What if you want the files to be opened by the same game instance, instead? Well thanks to my process information extension, this is now possible. For a basic demonstration, here I wrote an example which opens and plays an ogg audio file in your game's included files when you drag it onto your game executable in the file explorer, allowing for more than one audio file being played from the same instance, when dragging one audio file at a time. (make sure you turn off the splash screen so a second window won't open and close briefly).

Create Event:
GML:
exeutable_path = path_from_pid(pid_from_self());
i = 0; for (; i <= 32000; i++) {
  audio[i] = -1;
}

i = 0;
if (parameter_count() == 2) {
  if (filename_ext(parameter_string(1)) == ".ogg") {
    audio[i] = audio_create_stream(parameter_string(1));
    audio_play_sound(audio[i], 0, false);
  }
}
Step Event:
GML:
if (audio[i] != -1) {
  if (!audio_is_playing(audio[i])) {
    audio_destroy_stream(audio[i]);
  }
}

if (exeutable_path != "") {
  var duplicate_instances = pids_from_spec(exeutable_path, spec_both);
  if (duplicate_instances != "") {
    var pos = string_pos(@'|', duplicate_instances);
    if (pos != 0) {
      var dup1 = string_copy(duplicate_instances, 0, pos - 1);
      var dup2 = string_copy(duplicate_instances, pos + 1,
      string_length(duplicate_instances));
      var pid = (int64(dup1) != pid_from_self()) ? dup1 : dup2;
      if (pid_exists(int64(pid))) {
        var cmd = cmd_from_pid(int64(pid));
        pos = 0; var str = cmd;
        var j = 0; for (; j < 3; j++) {
          pos = string_pos(@'"', str); pos++;
          str = string_copy(str, pos, string_length(str));
        }
        var arg = string_copy(str, 0, string_pos(@'"', str) - 1);
        if (filename_ext(arg) == ".ogg") {
          i++; audio[i] = audio_create_stream(arg);
          audio_play_sound(audio[i], 0, false);
        }
        pid_kill(int64(pid));
      }
    }
  }
}
Download the example here:

And for those of you who want a demo executable:

If you need additional explanation on how this code works, please private message me or add me on discord, and I can also help adapt the code to your specific needs. :)
 
Last edited:

rytan451

Member
Functions that return ISO 8601-formatted datetime strings.
GML:
function datetime_current_string(){
	var tz = date_get_timezone();
	date_set_timezone(timezone_utc);
	var out = string_format(current_year, 4, 0) + string_format(current_month, 2, 0) + string_format(current_day, 2, 0) + "T" + string(current_hour) + ":" + string(current_minute) + ":" + string(current_second) + "Z";
	date_set_timezone(tz);
	return out;
}

function datetime_current_string_filesafe() {
	var tz = date_get_timezone();
	date_set_timezone(timezone_utc);
	var out = string_format(current_year, 4, 0) + "-" + string_format(current_month, 2, 0) + "-" + string_format(current_day, 2, 0) + "T" + string(current_hour) + string(current_minute) + string(current_second) + "Z";
	date_set_timezone(tz);
	return out;
}
Function that reads a single UTF-8 character from a buffer, and returns its value as an integer.
GML:
function buffer_read_utf8char(buffer) {
	var output = buffer_read(buffer, buffer_u8);
	if (output & 0x80 == 0)
		return output
	if (output & 0xF0 == 0xE0) {
		output = (output << 16) | buffer_read(buffer, buffer_u16);
		return output;
	} else {
		output = (output << 8) | buffer_read(buffer, buffer_u8);
		if (output & 0xE000 == 0xC000)
			return output;
		output = (output << 16) | buffer_read(buffer, buffer_u16);
		return output;
	}
}
 
Last edited:

Samuel Venable

Time Killer
Sets min/max window size on linux (YoYoGames had this built in but it has been broken for years now)...

Pass window_handle() to the first argument for each function. Use a min/max value <= zero to remove min/max size.

minmax.cpp
C++:
#include <map>
#include <cstdint>
#include <climits>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#define EXPORTED_FUNCTION extern "C" __attribute__((visibility("default")))

std::map<Window, int> minw;
std::map<Window, int> minh;
std::map<Window, int> maxw;
std::map<Window, int> maxh;

EXPORTED_FUNCTION double window_set_min_size(void *window, double min_width, double min_height) {
  Display *d = XOpenDisplay(nullptr);
  Window w = (Window)(std::intptr_t)window;
  XSizeHints *sh = XAllocSizeHints();
  sh->flags = PMinSize | PMaxSize;
  if (min_width <= 0) min_width = 1;
  if (min_height <= 0) min_height = 1;
  sh->min_width = min_width;
  sh->min_height = min_height;
  minw[w] = min_width;
  minh[w] = min_height;
  if (maxw.find(w) != maxw.end())
  sh->max_width = maxw[w];
  if (maxh.find(w) != maxh.end())
  sh->max_height = maxh[w];
  XSetWMNormalHints(d, w, sh);
  XFree(sh); XCloseDisplay(d);
  return 0;
}

EXPORTED_FUNCTION double window_set_max_size(void *window, double max_width, double max_height) {
  Display *d = XOpenDisplay(nullptr);
  Window w = (Window)(std::intptr_t)window;
  XSizeHints *sh = XAllocSizeHints();
  sh->flags = PMinSize | PMaxSize;
  if (max_width <= 0) max_width = INT_MAX;
  if (max_height <= 0) max_height = INT_MAX;
  sh->max_width = max_width;
  sh->max_height = max_height;
  maxw[w] = max_width;
  maxh[w] = max_height;
  if (minw.find(w) != minw.end())
  sh->min_width = minw[w];
  if (minh.find(w) != minh.end())
  sh->min_height = minh[w];
  XSetWMNormalHints(d, w, sh);
  XFree(sh); XCloseDisplay(d);
  return 0;
}
Compile with:
Code:
sudo apt-get install g++ libx11-dev
g++ minmax.cpp -o libminmax.so -std=c++17 -shared -static-libgcc -static-libstdc++ -lX11 -fPIC
Works for both Ubuntu and Raspberry Pi exports. You will have to build it for the Pi yourself.

Here's an Ubuntu build of the extension: minmax.yyz
 

Bart

WiseBart
The following is a very simple way to figure out, through code, which object variables are defined for an instance.
The trick is to get the list of object variable names as the very first thing in the Create event, before any other variables are defined:
GML:
/// The Create event
// There should be nothing here!
names = variable_instance_get_names(id);
// names now contains the names of all object variables

event_inherited(); // Define this here, AFTER looking up the list of names
The above also works for object variables that are inherited from a parent object.
 

8BitWarrior

Member
Since string_lower() and string_upper() are VERY slow, I've made alternative scripts which, on typical use cases, run much faster:
  • string_lower_fast()
  • string_upper_fast()

GML:
function string_lower_fast(str, offset, use_cache)
{    /// @desc "efficient" alternative to string_lower()
    /// @func string_lower_fast(string, [offset=0], [use_cache=true])
 
    static cache = ds_map_create(); // Create static cache for fast string lookup
    var og_string; // Used to preserve original string
 
    // Default [use_cache] to true if not defined
    if (is_undefined(use_cache)) { use_cache = true; }
 
    if (use_cache)
    {    // Check cache to return early
        if (ds_map_exists(cache, str))
        {
            return cache[? str];
        }
     
        og_string = str;  // Store original string
    }
 
    // Default [offset] to 0 if not defined
    if (is_undefined(offset)) { offset = 0; }
 
    // Convert string to lowercase
    repeat(string_length(str)-offset)
    {
        var byte = string_byte_at(str, ++offset);
     
        if (byte <= 90 && byte >= 65)
        {
            str = string_set_byte_at(str, offset, byte+32);
        }
    }

    // Cache string for quick future lookup
    if (use_cache)
    {
        cache[? og_string] = str;
    }
 
    return str; // Return lowercase string
}

function string_upper_fast(str, offset, use_cache)
{    /// @desc "efficient" alternative to string_upper()
    /// @func string_upper_fast(string, [offset=0], [use_cache=true])
 
    static cache = ds_map_create(); // Create static cache for fast string lookup
    var og_string; // Used to preserve original string
 
    // Default [use_cache] to true if not defined
    if (is_undefined(use_cache)) { use_cache = true; }
 
    if (use_cache)
    {    // Check cache to return early
        if (ds_map_exists(cache, str))
        {
            return cache[? str];
        }
     
        og_string = str;  // Store original string
    }
 
    // Default [offset] to 0 if not defined
    if (is_undefined(offset)) { offset = 0; }
 
    // Convert string to uppercase
    repeat(string_length(str)-offset)
    {
        var byte = string_byte_at(str, ++offset);
     
        if (byte >= 97 && byte <= 122)
        {
            str = string_set_byte_at(str, offset, byte-32);
        }
    }

    // Cache string for quick future lookup
    if (use_cache)
    {
        cache[? og_string] = str;
    }
 
    return str; // Return uppercase string
}
 
Last edited:

HalRiyami

Member
A bunch of primitive based shape drawing functions. The precision (for arc, circle, and capsule) is hardcoded at 15 degrees by you can change that to whatever you want.

GML:
///    @func                draw_line_advanced()
///    @param    {real}        x1            The x coordinate of the start of the line.
///    @param    {real}        y1            The y coordinate of the start of the line.
///    @param    {real}        x2            The x coordinate of the end of the line.
///    @param    {real}        y2            The y coordinate of the end of the line.
///    @param    {integer}    width        The width in pixels of the line.
///    @param    {integer}    color        The color of the line.
function draw_line_advanced(_x1, _y1, _x2, _y2, _width, _color) {
    // Zero width case
    if (_width <= 0) {
        draw_primitive_begin(pr_linestrip);
        draw_vertex_color(_x1, _y1, _color, 1);
        draw_vertex_color(_x2, _y2, _color, 1);
        draw_primitive_end();
        exit;
    }
    // Default case
    var _nX    = _y2 -_y1;
    var _nY    = _x1 -_x2;
    var _d    = sqrt(_nX*_nX +_nY*_nY);
    _nX        = _nX*_width/_d/2;
    _nY        = _nY*_width/_d/2;

    draw_primitive_begin(pr_trianglestrip);
    draw_vertex_color(_x1 +_nX, _y1 +_nY, _color, 1);
    draw_vertex_color(_x1 -_nX, _y1 -_nY, _color, 1);
    draw_vertex_color(_x2 +_nX, _y2 +_nY, _color, 1);
    draw_vertex_color(_x2 -_nX, _y2 -_nY, _color, 1);
    draw_primitive_end();
}

///    @func                draw_arc_advanced()
///    @param    {real}        x            The x coordinate of the center of the arc.
///    @param    {real}        y            The y coordinate of the center of the arc.
///    @param    {real}        r            The arc's radius (length from its center to its edge).
///    @param    {real}        angle        The starting angle in degrees.
///    @param    {real}        span        The arc's span in degrees.
///    @param    {integer}    width        The width in pixels of the arc.
///    @param    {integer}    color        The color of the arc.
function draw_arc_advanced(_x, _y, _r, _angle, _span, _width, _color) {
    var _increment    = 15*sign(_span);
    var _traversal    = 0;

    // Zero width case
    if (_width <= 0) {
        draw_primitive_begin(pr_linestrip);
        // Draw vertices up to the last one
        while (abs(_traversal) < abs(_span)) {
            var _dcos    = dcos(_angle +_traversal);
            var _dsin    = dsin(_angle +_traversal);
            draw_vertex_color(_x +_r*_dcos, _y -_r*_dsin, _color, 1);
            _traversal += _increment;
        }
        // Draw last vertex
        var _dcos    = dcos(_angle +_span);
        var _dsin    = dsin(_angle +_span);
        draw_vertex_color(_x +_r*_dcos, _y -_r*_dsin, _color, 1);
        draw_primitive_end();
        exit;
    }
    // Default case
    var _ro    = _r +_width/2;
    var _ri    = _r -_width/2;
    draw_primitive_begin(pr_trianglestrip);
    // Draw vertices up to the last one
    while (abs(_traversal) < abs(_span)) {
        var _dcos    = dcos(_angle +_traversal);
        var _dsin    = dsin(_angle +_traversal);
        draw_vertex_color(_x +_ro*_dcos, _y -_ro*_dsin, _color, 1);
        draw_vertex_color(_x +_ri*_dcos, _y -_ri*_dsin, _color, 1);
        _traversal += _increment;
    }
    // Draw last vertex
    var _dcos    = dcos(_angle +_span);
    var _dsin    = dsin(_angle +_span);
    draw_vertex_color(_x +_ro*_dcos, _y -_ro*_dsin, _color, 1);
    draw_vertex_color(_x +_ri*_dcos, _y -_ri*_dsin, _color, 1);
    draw_primitive_end();
}

///    @func                draw_rectangle_advanced()
///    @param    {real}        x1            The x coordinate of the left of the rectangle.
///    @param    {real}        y1            The y coordinate of the top of the rectangle.
///    @param    {real}        x2            The x coordinate of the right of the rectangle.
///    @param    {real}        y2            The y coordinate of the bottom of the rectangle.
///    @param    {integer}    color        The color of the rectangle.
///    @param    {boolean}    outline        Whether the rectangle is drawn filled (false) or as an outline (true).
///    @param    {integer}    width        The width of the outline in pixels. Ignored of filled.
function draw_rectangle_advanced(_x1, _y1, _x2, _y2, _color, _outline, _width) {
    // Filled case
    if (!_outline) {
        draw_primitive_begin(pr_trianglestrip);
        draw_vertex_color(_x1, _y1, _color, 1);
        draw_vertex_color(_x2, _y1, _color, 1);
        draw_vertex_color(_x1, _y2, _color, 1);
        draw_vertex_color(_x2, _y2, _color, 1);
        draw_primitive_end();
        exit;
    }
    // Outline with 0 width
    if (_width <= 0) {
        draw_primitive_begin(pr_linestrip);
        draw_vertex_color(_x1, _y1, _color, 1);
        draw_vertex_color(_x2, _y1, _color, 1);
        draw_vertex_color(_x2, _y2, _color, 1);
        draw_vertex_color(_x1, _y2, _color, 1);
        draw_vertex_color(_x1, _y1, _color, 1);
        draw_primitive_end();
        exit;
    }
    // Outline with width
    _width /= 2;
    draw_primitive_begin(pr_trianglestrip);
    // Top left corner
    draw_vertex_color(_x1 -_width, _y1 -_width, _color, 1);
    draw_vertex_color(_x1 +_width, _y1 +_width, _color, 1);
    // Top right corner
    draw_vertex_color(_x2 +_width, _y1 -_width, _color, 1);
    draw_vertex_color(_x2 -_width, _y1 +_width, _color, 1);
    // Bottom right corner
    draw_vertex_color(_x2 +_width, _y2 +_width, _color, 1);
    draw_vertex_color(_x2 -_width, _y2 -_width, _color, 1);
    // Bottom left corner
    draw_vertex_color(_x1 -_width, _y2 +_width, _color, 1);
    draw_vertex_color(_x1 +_width, _y2 -_width, _color, 1);
    // Top left corner
    draw_vertex_color(_x1 -_width, _y1 -_width, _color, 1);
    draw_vertex_color(_x1 +_width, _y1 +_width, _color, 1);
    draw_primitive_end();
}

///    @func                draw_circle_advanced()
///    @param    {real}        x            The x coordinate of the center of the circle.
///    @param    {real}        y            The y coordinate of the center of the circle.
///    @param    {real}        r            The radius (distance from center to edge) of the circle in pixels.
///    @param    {integer}    color        The color of the circle.
///    @param    {boolean}    outline        Whether the circle is an outline (true) or not (false).
///    @param    {integer}    width        The width of the outline in pixels. Ignored of filled.
function draw_circle_advanced(_x, _y, _r, _color, _outline, _width) {
    // Filled case
    if (!_outline) {
        draw_primitive_begin(pr_trianglefan);
        draw_vertex_color(_x, _y, _color, 1);
        var _a = 360;
        repeat (360/15 +1) {draw_vertex_color(_x +_r*dcos(_a), _y +_r*dsin(_a), _color, 1);    _a -= 15;}
        draw_primitive_end();
        exit;
    }

    // Outline with 0 width
    if (_width <= 0) {
        draw_primitive_begin(pr_linestrip);
        var _a = 360;
        repeat (360/15 +1) {draw_vertex_color(_x +_r*dcos(_a), _y +_r*dsin(_a), _color, 1);    _a -= 15;}
        draw_primitive_end();
        exit;
    }

    // Outline with width
    var _ro    = _r +_width/2;
    var _ri    = _r -_width/2;
    draw_primitive_begin(pr_trianglestrip);
    var _a = 360;
    repeat (360/15 +1) {
        var _cos    = dcos(_a);
        var _sin    = dsin(_a);
        draw_vertex_color(_x +_ro*_cos, _y +_ro*_sin, _color, 1);
        draw_vertex_color(_x +_ri*_cos, _y +_ri*_sin, _color, 1);
        _a -= 15;
    }
    draw_primitive_end();
}

///    @func                draw_capsule_advanced()
///    @param    {real}        x1            The x coordinate of the center of the start of the capsule.
///    @param    {real}        y1            The y coordinate of the center of the start of the capsule.
///    @param    {real}        x2            The x coordinate of the center of the end of the capsule.
///    @param    {real}        y2            The y coordinate of the center of the end of the capsule.
///    @param    {real}        r            The radius (distance from center to edge) of the capsule in pixels.
///    @param    {integer}    color        The color of the capsule.
///    @param    {boolean}    outline        Whether the capsule is drawn filled (false) or as an outline (true).
///    @param    {integer}    width        The width of the outline (in pixels). Ignored of filled.
function draw_capsule_advanced(_x1, _y1, _x2, _y2, _r, _color, _outline, _width) {
    // Normal vector
    var _d    = sqrt((_y2 -_y1)*(_y2 -_y1) +(_x1 -_x2)*(_x1 -_x2));
    var _vx    = (_y2 -_y1)/_d*_r;
    var _vy    = (_x1 -_x2)/_d*_r;
    var _a    = darctan2(_vy, _vx);

    // Filled case
    if (!_outline) {
        draw_primitive_begin(pr_trianglefan);
        draw_vertex_color((_x1 +_x2)/2, (_y1 +_y2)/2, _color, 1);
        repeat (180/15 +1) {draw_vertex_color(_x1 +_r*dcos(_a), _y1 +_r*dsin(_a), _color, 1);    _a -= 15;}
        _a    += 15;
        repeat (180/15 +1) {draw_vertex_color(_x2 +_r*dcos(_a), _y2 +_r*dsin(_a), _color, 1);    _a -= 15;}
        draw_vertex_color(_x1 +_vx, _y1 +_vy, _color, 1);
        draw_primitive_end();
        exit;
    }
    // Outline with 0 width
    if (_width <= 0) {
        draw_primitive_begin(pr_linestrip);
        repeat (180/15 +1) {draw_vertex_color(_x1 +_r*dcos(_a), _y1 +_r*dsin(_a), _color, 1);    _a -= 15;}
        _a    += 15;
        repeat (180/15 +1) {draw_vertex_color(_x2 +_r*dcos(_a), _y2 +_r*dsin(_a), _color, 1);    _a -= 15;}
        draw_vertex_color(_x1 +_vx, _y1 +_vy, _color, 1);
        draw_primitive_end();
        exit;
    }
    // Outline with width
    var _ro    = _r +_width/2;
    var _ri    = _r -_width/2;
    draw_primitive_begin(pr_trianglestrip);
    repeat (180/15 +1) {
        var _cos    = dcos(_a);
        var _sin    = dsin(_a);
        draw_vertex_color(_x1 +_ro*_cos, _y1 +_ro*_sin, _color, 1);
        draw_vertex_color(_x1 +_ri*_cos, _y1 +_ri*_sin, _color, 1);
        _a -= 15;
    }
    _a    += 15;
    repeat (180/15 +1) {
        var _cos    = dcos(_a);
        var _sin    = dsin(_a);
        draw_vertex_color(_x2 +_ro*_cos, _y2 +_ro*_sin, _color, 1);
        draw_vertex_color(_x2 +_ri*_cos, _y2 +_ri*_sin, _color, 1);
        _a -= 15;
    }
    draw_vertex_color(_x1 +_ro*_cos, _y1 +_ro*_sin, _color, 1);
    draw_vertex_color(_x1 +_ri*_cos, _y1 +_ri*_sin, _color, 1);
    draw_primitive_end();
}

///    @func                draw_polygon_advanced()
///    @param    {array}        polygon        A closed convex polygon array structure [x1, y1, x2, y2, x3, y3,...].
///    @param    {integer}    color        The color of the polygon.
///    @param    {boolean}    outline        Whether the polygon is drawn filled (false) or as an outline (true).
///    @param    {integer}    width        The width of the outline (in pixels). Ignored of filled.
function draw_polygon_advanced(_polygon, _color, _outline, _width) {
    var _vertexCount    = array_length(_polygon)/2;
    var _i = 0;
    // Filled case
    if (!_outline) {
        draw_primitive_begin(pr_trianglefan);
        repeat (_vertexCount) {draw_vertex_color(_polygon[_i*2], _polygon[_i*2 +1], _color, 1);    ++_i;}
        draw_primitive_end();
        exit;
    }
    // Outline with 0 width
    if (_width <= 0) {
        draw_primitive_begin(pr_linestrip);
        repeat (_vertexCount) {draw_vertex_color(_polygon[_i*2], _polygon[_i*2 +1], _color, 1);    ++_i;}
        draw_vertex_color(_polygon[0], _polygon[1], _color, 1);
        draw_primitive_end();
        exit;
    }
    
    // Outline with width
    var _m    = array_create(_vertexCount);    // Slopes
    var _bi    = array_create(_vertexCount);    // Inner edge intercepts
    var _bo    = array_create(_vertexCount);    // Outer edge intercepts
    // Loop to find the slope and intercepts of the inner and outer edges
    repeat (_vertexCount) {
        var _j    = (_i +1) % _vertexCount;
    
        var _vx    = _polygon[_j*2] -_polygon[_i*2];
        var _vy    = _polygon[_j*2 +1] -_polygon[_i*2 +1];
        var _d    = sqrt(_vx*_vx +_vy*_vy);
        var _nx    =  _vy*_width/2/_d;
        var _ny    = -_vx*_width/2/_d;
    
        _m[_i]    = _vy/_vx;
        _bo[_i]    = (_polygon[_i*2 +1] +_ny) -(_polygon[_i*2] +_nx)*_m[_i];
        _bi[_i]    = (_polygon[_i*2 +1] -_ny) -(_polygon[_i*2] -_nx)*_m[_i];
        
        ++_i;
    }
    // Draw loop
    var _i    = 0;
    draw_primitive_begin(pr_trianglestrip);
    repeat (_vertexCount +1) {
        var _j    = (_i +1) % _vertexCount;
        
        if (abs(_m[_i]) == infinity) {
            var _ox = _polygon[_i*2] +_nx;
            var _oy    = _m[_j]*_ox +_bo[_j];
            var _ix = _polygon[_i*2] -_nx;
            var _iy    = _m[_j]*_ix +_bi[_j];
        }
        else if (abs(_m[_j]) == infinity) {
            var _ox = _polygon[_j*2] +_nx;
            var _oy    = _m[_i]*_ox +_bo[_i];
            var _ix = _polygon[_j*2] -_nx;
            var _iy    = _m[_i]*_ix +_bi[_i];
        }
        else {
            var _ox    = (_bo[_j] -_bo[_i])/(_m[_i] -_m[_j]);
            var _oy    = _m[_i]*_ox +_bo[_i];
            var _ix    = (_bi[_j] -_bi[_i])/(_m[_i] -_m[_j]);
            var _iy    = _m[_i]*_ix +_bi[_i];
        }
        draw_vertex_color(_ox, _oy, _color, 1);
        draw_vertex_color(_ix, _iy, _color, 1);
        
        _i    = (_i +1) % _vertexCount;
    }
    draw_primitive_end();
}
 

Joe Ellis

Member
Here's a simple function I worked out recently, basically a smooth sine wave lerp.
It produces the same (or very similar) result as glsl's smoothstep function, and is done using lengthdir_y and captures the circular curve it produces.

Tutorial:

GML:
function ratio_to_sine(r) // "r" should be a value between 1 and 0
{

//Convert input into an angle (the range is 180 degrees)
var angle = r * 180;

//Add 90 degrees onto the angle to make it start at 90 and end at 270
angle += 90;

//If you picture it in your head, this means it starts at fully at the top, and ends fully at the bottom.
//This is the range of values we need. Basically, with a length of 1, it'll return between -1 (top) to 1 (bottom)

var value = lengthdir_y(1, angle);

//Then to convert this output range into 0 - 1 range, we just need to add 1, which will make -1 = 0, and 1 = 2.

value += 1;

//Then to reduce the range from 0 - 2, to 0 - 1, we just need to divide it by 2.

value *= 0.5;

//Then we have the value. The input value has been transformed (bended) into a sine wave!

return value;

}
Here are the functions in concise form:

GML:
function sine_lerp(v1, v2, r)
{return lerp(v1, v2, (lengthdir_y(1, 90 + (r * 180)) + 1) * 0.5)}

function ratio_to_sine(r)
{return (lengthdir_y(1, 90 + (r * 180)) + 1) * 0.5}
I'm using this function with my soft brush falloff, it has a really nice effect!

Oh, I almost forgot! Visual results comparison: (Approximately)

 
Last edited:

Samuel Venable

Time Killer
Ok... so GM's built-in means to auto check for dll architectures has been broken. In the meantime if you want to support both 32-bit and 64-bit dll's on Windows from one codebase you may do this:

GML:
if (environment_get_variable("PROCESSOR_ARCHITECTURE") == "AMD64") { /* external_* 64-bit dll */ }
else if (environment_get_variable("PROCESSOR_ARCHITECTURE") == "x86") { /* external_* 32-bit dll */ }
 

Samuel Venable

Time Killer
In this post I will be discussing a cross-platform solution for desktops allowing you to convert a native global window datatype to and from a usable string that can be passed to and used in GameMaker, (yes, due to being a regular string and not a pointer, this will work in GameMaker Studio 1.x/2.x, as well as old versions of GM, including GameMaker 8.1 and older, and potentially GameMaker 7.5 for Mac).

As for outside the world of GameMaker, this also supports FreeBSD, or any other platform you can think of running an X Window Server, platforms besides Windows, MacOS (Cocoa), Linux, and FreeBSD will need to compile with the -DXPROCESS_XQUARTZ_IMPL definition flag, I used the name XQuartz because it can also be used to compile an X11 version of the MacOS/Darwin target with the XQuartz app, or alternative X Window Servers. Which potentially means this could work on all the other *BSD's and Solaris derivatives.

Why anyone would want to do this on platforms GM doesn't support is beyond me, but I do admit I like the portability X11 offers. Well, actually there is another practical reason to do this, and that's being able to pass pointers in the form of a string to a CLI and print them to stdout, but I doubt anyone here has much if any use for writing command line interfaces; one of mine is linked to, toward the bottom of this post.

First off, we will be defining our Native Global Window Datatype, which will be defined differently for each platform GM supports. On Windows, this is an HWND (typedef of void *), on MacOS it is a CGWindowID (typedef of unsigned int), and on Linux is a Window (typedef of unsigned long). Since the Windows datatype is a pointer and MacOS/Linux are numbers of varying precision, it is important we define them individually as such and don't typedef them to std::uintptr_t or similar because not only will that require an extra cast it will also return wrong values in crucial circumstances, such as allocating memory you might do sizeof(WINDOW ...) whose values will differ from the original type.

Also note, for consistency, a CGWindowID is a global window datatype on MacOS, just like the other platforms. GameMaker only supports passing an NSWindow * as the window_handle() on MacOS, which happens to be a local window datatype. On MacOS you cannot do much with a Global window handle other than retrive various properties from it that are read-only. This is why it might be more appealing to have access to an NSWindow * for the current app instead, which is necessary for setting advanced properties and having more control.

Note: an NSWindow * is ONLY valid for the current app! You may not access an NSWindow * from external apps running on your machine.

All the code in this post is pure C++, however, if you wish to get the NSWindow * from a CGWindowID you may use in Objective-C++ windowWithWindowNumber. In order to do the reverse and get a CGWindowID from an NSWindow *, you may use windowNumber in Objective-C++. The actual code I am referring to when I speak of defining the global window datatype may be seen in the snippet below:

C++:
#if defined(_WIN32)
#include <windows.h>
typedef HWND WINDOW;
#else
#if (defined(__APPLE__) && defined(__MACH__)) && !defined(XPROCESS_XQUARTZ_IMPL)
#include <CoreGraphics/CoreGraphics.h>
#include <CoreFoundation/CoreFoundation.h>
typedef CGWindowID WINDOW;
#elif (defined(__linux__) && !defined(__ANDROID__)) || defined(__FreeBSD__) || defined(XPROCESS_XQUARTZ_IMPL)
#include <X11/Xlib.h>
typedef Window WINDOW;
#endif
#endif
This will make all platforms represent their global window datatype as the WINDOW typedef.

Now let's define a datatype for the "Window ID" which in this context is just a string that can be passed to GM:

C++:
typedef char *WINDOWID;
A char * is just a string which holds either the HWND pointer address, (for example, a nullptr is "0x0"), on Windows or on other platforms it simply wraps the number as-is in quotes to form the string, without loss in precision. The WINDOWID typedef can be used as either an argument or return type for your *.DLL/*.DYLIB/*.SO native extension libraries. Now for the functions...

Below we have the functions which will handle this string (window id) <-> native global window datatype conversion. Again, the WINDOWID is nothing more than a string that may be used in GameMaker, however the WINDOW is a pointer on Windows so that as well as an NSWIndow * on MacOS extracted from a CGWindowID will both not be returnable values for your native library extension functions. GameMaker native extension functions can only return strings (char *) or reals (double).

While you can technically pass any type of pointer you wish as a DLL argument to an extension, this only applies to function arguments, not return values. On Linux, (and if on MacOS you actually want a CGWindowID passed to GM, not an NSWindow *), these values may be returned as doubles without lositng precision but because of, (at the very least), Windows, (if not MacOS as well), this is not 100% cross-platform, so it is advised to convert these window datatypes to and from a valid string before using them return value in GameMaker.

It also a good idea to use the WINDOWID string type for augments in GM functions as well and convert those to the native types for use on the C++ side, because we need our functions we define in GM's extension editor to have the same datatype for both function return values and function arguments. Enough talk, more code. Below is the C++ helper functions to handle the conversion:

C++:
WINDOWID WindowIdFromNativeWindow(WINDOW window) {
  static std::string wid;
  #if defined(_WIN32)
  const void *address = static_cast<const void *>(window);
  std::stringstream ss; ss << address;
  wid = ss.str();
  #else
  wid = std::to_string((unsigned long)window);
  #endif
  return (WINDOWID)wid.c_str();
}

WINDOW NativeWindowFromWindowId(WINDOWID winId) {
  #if defined(_WIN32)
  void *address; sscanf(winId, "%p", &address);
  WINDOW window = (WINDOW)address;
  #else
  WINDOW window = (WINDOW)strtoul(winId, nullptr, 10);
  #endif
  return window;
}
Note these functions will not be passed to GM but are for convenience so that the native global window datatypes may be converted on the C++ side beforehand and called by your exported functions as needed before calling your exported functions from GameMaker. Private message me if you have any questions. I use this code in my xproc command line application, (link is to the permissive source code and free downloads).
 
Last edited:

EvanSki

Raccoon Jam Host
Extreamly simple function to check if a number is in between two values
GML:
function in_range(number,min,max){
    if (number >= min) && (number <= max){
        return(true);   
    }else{
        return(false);   
    }
}
 

Juju

Member
GML:
/// Writes a SHA1 string to a buffer as individual bytes, from left to right in the SHA1 string
///
/// @return Nothing (undefined)
/// @param buffer      Buffer to write to
/// @param sha1String  SHA1 string to write

function buffer_write_sha1(_buffer, _sha1_string)
{
    var _i = 1;
    repeat(5)
    {
        //Did you know real() worked on hex?
        var _value = real("0x" + string_copy(_sha1_string, _i, 8));
        
        //Reverse bytes in the 32-bit word
        //We do this so that the bytes in the buffer correspond exactly to the bytes in the SHA1 string
        _value = (((_value & 0xFF) << 24) | ((_value & 0xFF00) << 8) | ((_value >> 8) & 0xFF00) | (_value >> 24));
        
        buffer_write(_buffer, buffer_u32, _value);
        
        _i += 8;
    }
}




/// Reads a SHA1 hash from a buffer and converts it into a string
///
/// @return SHA1 string extracted from the buffer
/// @param buffer   Buffer to read from
/// @param [lower]  Optional. Whether to force the SHA1 string into lowercase for easier comparison with other SHA1 strings. Defaults to <true>

function buffer_read_sha1()
{
    var _buffer = argument[0];
    var _lower  = ((argument_count > 1) && (argument[1] != undefined))? argument[1] : true;
    
    var _sha1_string = "";
    
    repeat(5)
    {
        var _value = buffer_read(_buffer, buffer_u32);
        
        //Reverse bytes in the 32-bit word we pulled out
        _value = (((_value & 0xFF) << 24) | ((_value & 0xFF00) << 8) | ((_value >> 8) & 0xFF00) | (_value >> 24));
        
        //Checky hack with pointers to output a hex string
        _sha1_string += string(ptr(_value));
    }
    
    //If necessary, force lowercase abcdef
    if (_lower) _sha1_string = string_lower(_sha1_string);
    
    return _sha1_string;
}
 

Samuel Venable

Time Killer
C++:
HWND window_handle() {
  HWND hWnd = GetTopWindow(GetDesktopWindow());
  DWORD pid = 0; GetWindowThreadProcessId(hWnd, &pid);
  if (IsWindowVisible(hWnd) && GetCurrentProcessId() == pid) {
    return hWnd;
  }
  while (hWnd = GetWindow(hWnd, GW_HWNDNEXT)) {
    DWORD pid = 0; GetWindowThreadProcessId(hWnd, &pid);
    if (IsWindowVisible(hWnd) && GetCurrentProcessId() == pid) {
      return hWnd;
    }
  }
  // return desktop if game window isn't visible yet
  return GetDesktopWindow();
}
With this C++ function you may get the window handle of your game without needing to call window_handle() on the GML side. It will work as long as you call it when the game window is actually visible, which when a game first starts it will not be visible for the first few steps so be sure to call in an alarm at the very least. If you would otherwise use the create event it would get the window handle to the desktop. For the same reasons it will get the desktop window when running in headless mode. Bewares! Windows-only solution.
 

FrostyCat

Member
Cross-platform solution for adding sprites from data URIs:
GML:
///@func sprite_add_datauri(datauri, imgnumb, removeback, smooth, xorig, yorig)
///@arg datauri
///@arg imgnumb
///@arg removeback
///@arg smooth
///@arg xorig
///@arg yorig
///@desc Add sprite from Data URI
function sprite_add_datauri(datauri, imgnumb, removeback, smooth, xorig, yorig) {
    var result;
    if (os_browser != browser_not_a_browser) {
        result = sprite_add(datauri, imgnumb, removeback, smooth, xorig, yorig);
    } else {
        var posFirstSlash = string_pos("/", datauri);
        var posFirstSemicolon = string_pos(";", datauri);
        var extension = string_copy(datauri, posFirstSlash+1, posFirstSemicolon-posFirstSlash-1);
        var bufferBase64 = string_delete(datauri, 1, string_pos("base64,", datauri)+6);
        var buffer = buffer_base64_decode(bufferBase64);
        var filepath = working_directory + "temp" + sha1_string_unicode(string(date_current_datetime())) + "." + extension;
        buffer_save(buffer, filepath);
        buffer_delete(buffer);
        result = sprite_add(filepath, imgnumb, removeback, smooth, xorig, yorig);
        file_delete(filepath);
    }
    return result;
}
Updated (2021-08-09): Fixed ignored parameters in non-browser case.
 
Last edited:

EvanSki

Raccoon Jam Host
converts negatives to positives and positives to negatives
depending on which you use

GML:
function Positive(value){
    //returns a input number but as positive
    return abs(value);
}

function Negative(value){
    //returns a input number but as negative
    var get_negative = sign(value);
    
    if (get_negative != -1){
        return value * -1;
    }else{
        return value;   
    }
}
 

slojanko

Member
Cool little function to get bounding vertices of an instance:

GML:
function GetBoundingVertices(){
    var array = array_create(4, 0);
    var matrix = matrix_build(x, y, 0, 0, 0, image_angle, 1, 1, 1);
  
    // sprite_offset already account for scale
    var transformed = matrix_transform_vertex(matrix, -sprite_xoffset, -sprite_yoffset, 0);
    array[0] = new Vec2(transformed[0], transformed[1]);
    transformed = matrix_transform_vertex(matrix, sprite_width-sprite_xoffset, -sprite_yoffset, 0);
    array[1] = new Vec2(transformed[0], transformed[1]);
    transformed = matrix_transform_vertex(matrix, -sprite_xoffset, sprite_height-sprite_yoffset, 0);
    array[2] = new Vec2(transformed[0], transformed[1]);
    transformed = matrix_transform_vertex(matrix, sprite_width-sprite_xoffset, sprite_height-sprite_yoffset, 0);
    array[3] = new Vec2(transformed[0], transformed[1]);
  
    return array;
}
Vec2 only contains 2 variables
GML:
function Vec2(x_, y_) constructor {
    x = x_;
    y = y_;
}
 
Last edited:

gnysek

Member
I recently found out that [@ ] isn't only to directly modify array passed to script, but can also modify arrays returned from other scripts and assigned to different (temporary) variable:

GML:
globalvar BACKPACK, CHEST;
BACKPACK = array_create(10, -1); // 10 slots, -1 means no item
CHEST = array_create(50, -1); // 50 slots, -1 means no item

/// get reference of either backpack (true) or chest (false)
function get_storage(backpack = true) {
    return backpack ? BACKPACK : CHEST; // instead of if-else, there can be switch() here for more options :)
}

/// @param itemid
/// @param insert to backpack (true) or chest (false)
/// @returns true if item can be inserted and false if not
function put_item_into_storage(itemid, backpack = true) {
    var storage = get_storage(backpack);
    for(var i = 0; i < array_length(storage); i++) {
        if (storage[i] == -1) {
            storage[@ i] = itemid; /* MAGIC ! without using [@ ], storage would became a new array (copy of returned data). with [@ ] we can modify existing global array! */
            return true;
        }
    }
  
    return false;
}
 
Top