Skip to content

gml-extended-1.6.0

Latest

Choose a tag to compare

@DAndrewBox DAndrewBox released this 06 Sep 09:06
· 2 commits to main since this release
8aaf53d

GML Extended - The Audit Update!

Time for the yearly major release of this library.

This release is the result of a full audit of every .gml script in the library. On top of 33 new functions that I have been commonly using on other projects, and a new optional library dedicated only to Data Structures.

This release fixes a long list of silent bugs (several of which meant a function had never worked as documented) and rebuilds the compatibility system, which turned out to be doing the exact opposite of its job on older runtimes.


Breaking changes

Warning

Read this section before updating. Most of these are bug fixes, but they change behaviour you may have worked around.

  • int() now truncates towards zero instead of flooring. int(-10.5) returns -10, not -11. This matches the documented "removes all decimals". Use the uilt-in function floor() if you relied on the old behaviour.
  • uuid_v4() now returns a real RFC 4122 UUID (36 chars, dashed, with version and variant nibbles) instead of a raw 32 char MD5 string.
  • unix_timestamp() and unix_to_datetime() were rebuilt on integer calendar math. A date now produces the same timestamp on every machine regardless of timezone or daylight saving. now() reads the clock in the timezone GameMaker is set to. You will need to call date_set_timezone(timezone_utc) at game start if you need it to match a server.
  • struct_merge() no longer modifies its arguments. It returns a deep copy that shares no struct or array reference with either source.
  • instance_create() and shader_set_ext() no longer delete the params struct you pass in. The struct stays usable after the call.
  • instance_get_if() now returns the instances that match the callback (it returned the ones that did not match).
  • instance_get_all() now returns every instance (it silently truncated the result).
  • random_range_linear() now returns a value inside the given range.
  • rng() and choice_weighted() now use a uniform roll. rng(0.5) fires 50% of the time, not 25%.
  • Surface.exists() now returns false for a freed surface (it always returned true).
  • Removed the internal __gml_ext_comp_set_gamemaker_version() and __gml_ext_comp_is_gamemaker_major_version(), replaced by the version core described below.
  • Removed the Project-2.3.7 folder from the repository. It had not been updated since February 2025 (Before the compatibility update), and the main compatibility layer now genuinely supports GameMaker 2.3.

Major changes

Optional > Data Structures

A new library for saving and loading data structures as compressed base64 strings, ready to drop straight into a JSON save file.

  • ds_grid_write_buffer / ds_grid_read_buffer: Serialize a numeric grid. Cells are stored as 64-bit floats so every value survives the round trip, and reading resizes the destination grid automatically.
  • ds_list_write_buffer / ds_list_read_buffer: Serialize a list, keeping every value type including nested lists and maps.
  • ds_map_write_buffer / ds_map_read_buffer: Serialize a map, keeping key types and nested data structures.
  • struct_write_buffer / struct_read_buffer: Serialize a struct or an array, nested values included. (included this one here instead inside the "structs" lib since it has dependency on the ds_map* functions)_

Compatibility system rewrite

Important

The old system did not work on every case. Because GameMaker 2.3 reported a major version of 2.3, every check like GM_VERSION_IS_2022 && ... evaluated to false, so on a real 2.3 runtime every fallback was skipped and the library called functions that did not exist. Turning on GML_EXT_FORCE_COMPATIBILITY_MODE did the same thing, so it disabled the fallbacks instead of forcing them.

Every GameMaker version number is now in one table, and each fallback asks for a feature by name instead of comparing versions itself.

  • New macros: GM_HAS, GM_VERSION_AT_LEAST, GM_VERSION_IS_2_3, GM_VERSION_IS_2025, GM_VERSION_IS_2026.
  • Version parsing now splits on . instead of reading fixed character offsets, so it no longer breaks on unexpected version strings, and GM_CURRENT_VERSION is parsed once and cached instead of on every reference.
  • Asking for a feature that is not in the table logs an error and returns false, so a typo surfaces immediately.

Core > Strings

  • string_truncate: Cuts a string to a maximum length and adds a suffix, useful for UI labels.
  • string_remove_duplicate_chars: Collapses every run of a character into a single one, so double spaces become one space and .. becomes ..
  • string_to_snake: Converts a string to snake_case, splitting on spaces, dashes, underscores and capital letters.
  • string_to_camel: Converts a string to camelCase.
  • string_slugify: Returns a lowercase URL friendly version of a string, keeping any special characters you allow.

Core > Arrays

  • array_chunk: Splits an array into an array of smaller arrays.
  • array_flatten: Unwraps nested arrays, up to an optional depth.
  • array_group_by: Groups the elements into a struct using a callback for the key.
  • array_sum, array_mean, array_median: Statistics over the numeric elements of an array.
  • array_sample: Takes N random elements, without repeats by default.
  • array_swap: Swaps two elements in place.

Core > Math

  • approach: Moves a value towards a target by a fixed step without overshooting it.
  • lerp_angle: Interpolates between two angles the short way around the circle.
  • normalize: Converts a value from one range to another. With the default arguments it normalizes between 0 and 1.
  • snap: Rounds a value to the closest point of a grid. The grid starts at 0 unless an offset is given, so snap(x, 16, 8) snaps to 8, 24, 40 and so on.

Core > Structs

  • struct_filter, struct_map, struct_reduce: The array iteration functions, for structs.
  • struct_get_path: Reads a nested value from a dot separated path such as "player.stats.hp", with a default when any step is missing. Numeric steps walk into arrays, so "items.0.name" works too. Safe to use on parsed JSON.
  • struct_flatten: Turns nested keys into paths, so {a: {b: 1}} becomes {"a.b": 1}.

Core > Types

  • trunc: Removes the decimals of a value without rounding. GameMaker has no built-in for this, and floor gets negatives wrong.

Core > Snippets

  • trace_once: Prints a message the first time only, so a Step event does not spam the log.
  • get_size and contains now work on data structures when you pass the matching ds_type_* constant, which is what the documentation had been promising since v1.5.0. (I promise I thought I already implemented it! Sorry!)

Optional > Instances & Events

  • is_inside_room: Returns whether an instance is inside the room boundaries, with an option to require the whole bounding box. Safe to call on a destroyed instance. instance_in_room is now an alias for it.

Bug Fixes

Core > Arrays

  • sort_elem_size_desc compared an element against itself, and both size comparators returned a boolean where array_sort needs a signed number, so sorting by size never worked.

Core > Strings

  • string_title lost the capital letter on any word that followed two or more spaces.

Core > Math

  • rng and choice_weighted used a skewed random roll, so the odds never matched the value you passed.
  • choice_weighted no longer reads past the end when both arrays have a different length.
  • range with a step of 0 or less looped forever and hung the game.
  • wrap divided by zero when min and max were equal.
  • random_range_linear returned the square root of the range, landing far outside it, and returned NaN for negative ranges.
  • percentage raised a divide by zero error when the total was 0.
  • uuid_v4 crashed when the platform did not report a UDID, and could collapse its entropy to zero.
  • dec2hex produced garbage for negative values, which are now clamped to 0, and lost a dead code branch.

Core > Structs

  • struct_merge wrote straight into the first struct, treated a stored undefined as a missing key, and shared array and struct references with the second struct.
  • struct_merge now merges arrays index by index, and merges the structs found on the same index, so an array of structs keeps the keys of both sides.
  • struct_equal compared nested structs and arrays by reference, so two identical structs were reported as different.

Core > Types

  • is_type compared a string against an array when given a list of types with no match.

Core > Snippets

  • contains raised a type error when looking for a number inside a string.

Optional > Time

  • %DD and %D printed the month instead of the day.
  • The weekday tables started on Monday while GameMaker counts from Sunday, so every weekday name was off by one and Sundays read past the start of the array.
  • Format tokens were sorted shortest first, so %YYYY could be partly replaced by %YY.
  • %YY called string_copy on a number, and several tokens passed numbers to string_replace_all.
  • unix_timestamp was anchored on an epoch built in the local timezone and then floored to UTC midnight, shifting every result by the timezone offset, losing a second to floating point truncation, and drifting by an hour across a daylight saving change. It is now built on integer calendar math and is exact.
  • Fixed the "Thrusday" typo and a stray } in the time_performance log line.

Optional > Instances & Events

  • instance_get_all used the digit count of the instance total as its loop bound, so 12 instances returned 2.
  • instance_get_if returned the instances that did not match the callback. (my bad sorry :P)

Optional > Layers

  • layer_get_background and layer_get_tileset checked for failure with !id, which never catches the -1 that layer_get_id returns.

Optional > Draw / GUI

  • draw_create_profile and draw_set_profile guarded on a global that nothing ever wrote to.
  • mouse_is_moving returned true when the mouse had not moved on older runtimes, and the fallback variables it needs were not always created.

Optional > Ease

  • anim_easeInElastic, anim_easeOutElastic and anim_easeInOutElastic did not match the easings.net curves they are documented to follow. anim_easeInElastic(1) returned 0 instead of 1.
  • anim_easeInBounce and anim_easeInOutBounce applied the factor twice.

Experimental

  • Surface.exists() compared a boolean against -1, so it always returned true.
  • shader_set_ext used the outer loop counter to index a struct when setting a texture uniform.
  • Logger.warn() and Logger.error() wrote the file handle into the log line as if it were part of the message.
  • http_request_send leaked a ds_map on every call.

Compatibility

  • __gml_ext_comp_array_contains incremented its step variable instead of its index, looping forever, and computed the wrong end position.
  • The string_split fallback dropped an empty trailing chunk, so it returned [] where the built-in returns [""], and it never matched a delimiter longer than one character.

Minor changes

No minor changes in this update.

Documentation

  • Added pages for every new function, plus a new Optional > Data Structures page.
  • Compatibility.md now documents the feature table, the GM_HAS pattern and how to add a new fallback.
  • Fixed the random_linear_range / random_range_linear name mismatch in Core/Math.md.
  • Corrected the contains return type, which was documented as returning an index and -1 when it returns a boolean.
  • Corrected instance_create_unique, documented as returning -1 when it returns the instance that already exists.
  • Documented that int truncates towards zero and that dec2hex clamps negative values.

Repository

  • Removed the unmaintained Project-2.3.7 folder.

If you have any feature request, issue, comment or suggestion, please let me know in the issues tab!


Full Changelog: v1.5.3...v1.6.0