diff --git a/_build_dependencies.sh b/_build_dependencies.sh
index 0a478428b69b..552b2f4d155c 100644
--- a/_build_dependencies.sh
+++ b/_build_dependencies.sh
@@ -14,4 +14,4 @@ export STABLE_BYOND_MINOR=1633
# Python version for mapmerge and other tools
export PYTHON_VERSION=3.11.6
# RUSTG version
-export RUSTG_VERSION=v3.0.0-P
+export RUSTG_VERSION=v3.4.0-P
diff --git a/code/__DEFINES/_versions.dm b/code/__DEFINES/_versions.dm
index 5086d8f8ae42..74eb2e89dacb 100644
--- a/code/__DEFINES/_versions.dm
+++ b/code/__DEFINES/_versions.dm
@@ -1,2 +1,2 @@
/// Version of RUST-G that this codebase wants
-#define RUST_G_VERSION "3.0.0-P"
+#define RUST_G_VERSION "3.4.0-P"
diff --git a/code/__DEFINES/rust_g.dm b/code/__DEFINES/rust_g.dm
index 8943239ae6cc..109fcad020b0 100644
--- a/code/__DEFINES/rust_g.dm
+++ b/code/__DEFINES/rust_g.dm
@@ -144,7 +144,7 @@
// File Operations //
#define rustg_file_read(fname) RUSTG_CALL(RUST_G, "file_read")(fname)
-#define rustg_file_exists(fname) RUSTG_CALL(RUST_G, "file_exists")(fname)
+#define rustg_file_exists(fname) (RUSTG_CALL(RUST_G, "file_exists")(fname) == "true")
#define rustg_file_write(text, fname) RUSTG_CALL(RUST_G, "file_write")(text, fname)
#define rustg_file_append(text, fname) RUSTG_CALL(RUST_G, "file_append")(text, fname)
#define rustg_file_get_line_count(fname) text2num(RUSTG_CALL(RUST_G, "file_get_line_count")(fname))
@@ -157,8 +157,23 @@
// Git Operations //
+/// Returns the git hash of the given revision, ex. "HEAD".
#define rustg_git_revparse(rev) RUSTG_CALL(RUST_G, "rg_git_revparse")(rev)
-#define rustg_git_commit_date(rev) RUSTG_CALL(RUST_G, "rg_git_commit_date")(rev)
+
+/**
+ * Returns the date of the given revision using the provided format.
+ * Defaults to returning %F which is YYYY-MM-DD.
+ */
+/proc/rustg_git_commit_date(rev, format = "%F")
+ return RUSTG_CALL(RUST_G, "rg_git_commit_date")(rev, format)
+
+/**
+ * Returns the formatted datetime string of HEAD using the provided format.
+ * Defaults to returning %F which is YYYY-MM-DD.
+ * This is different to rustg_git_commit_date because it only needs the logs directory.
+ */
+/proc/rustg_git_commit_date_head(format = "%F")
+ return RUSTG_CALL(RUST_G, "rg_git_commit_date_head")(format)
// Hashing Functions //
@@ -174,6 +189,11 @@
#define RUSTG_HASH_XXH64 "xxh64"
#define RUSTG_HASH_BASE64 "base64"
+/// Encode a given string into base64
+#define rustg_encode_base64(str) rustg_hash_string(RUSTG_HASH_BASE64, str)
+/// Decode a given base64 string
+#define rustg_decode_base64(str) RUSTG_CALL(RUST_G, "decode_base64")(str)
+
#ifdef RUSTG_OVERRIDE_BUILTINS
#define md5(thing) (isfile(thing) ? rustg_hash_file(RUSTG_HASH_MD5, "[thing]") : rustg_hash_string(RUSTG_HASH_MD5, thing))
#endif
@@ -192,6 +212,68 @@
/proc/rustg_create_async_http_client() return RUSTG_CALL(RUST_G, "start_http_client")()
/proc/rustg_close_async_http_client() return RUSTG_CALL(RUST_G, "shutdown_http_client")()
+// Iconforge Operations //
+
+/// Generates a spritesheet at: [file_path][spritesheet_name]_[size_id].png
+/// The resulting spritesheet arranges icons in a random order, with the position being denoted in the "sprites" return value.
+/// All icons have the same y coordinate, and their x coordinate is equal to `icon_width * position`.
+///
+/// hash_icons is a boolean (0 or 1), and determines if the generator will spend time creating hashes for the output field dmi_hashes.
+/// These hashes can be heplful for 'smart' caching (see rustg_iconforge_cache_valid), but require extra computation.
+///
+/// Spritesheet will contain all sprites listed within "sprites".
+/// "sprites" format:
+/// list(
+/// "sprite_name" = list(// <--- this list is a [SPRITE_OBJECT]
+/// icon_file = 'icons/path_to/an_icon.dmi',
+/// icon_state = "some_icon_state",
+/// dir = SOUTH,
+/// frame = 1,
+/// transform = list([TRANSFORM_OBJECT], ...)
+/// ),
+/// ...,
+/// )
+/// TRANSFORM_OBJECT format:
+/// list("type" = RUSTG_ICONFORGE_BLEND_COLOR, "color" = "#ff0000", "blend_mode" = ICON_MULTIPLY)
+/// list("type" = RUSTG_ICONFORGE_BLEND_ICON, "icon" = [SPRITE_OBJECT], "blend_mode" = ICON_OVERLAY)
+/// list("type" = RUSTG_ICONFORGE_SCALE, "width" = 32, "height" = 32)
+/// list("type" = RUSTG_ICONFORGE_CROP, "x1" = 1, "y1" = 1, "x2" = 32, "y2" = 32) // (BYOND icons index from 1,1 to the upper bound, inclusive)
+///
+/// Returns a SpritesheetResult as JSON, containing fields:
+/// list(
+/// "sizes" = list("32x32", "64x64", ...),
+/// "sprites" = list("sprite_name" = list("size_id" = "32x32", "position" = 0), ...),
+/// "dmi_hashes" = list("icons/path_to/an_icon.dmi" = "d6325c5b4304fb03", ...),
+/// "sprites_hash" = "a2015e5ff403fb5c", // This is the xxh64 hash of the INPUT field "sprites".
+/// "error" = "[A string, empty if there were no errors.]"
+/// )
+/// In the case of an unrecoverable panic from within Rust, this function ONLY returns a string containing the error.
+#define rustg_iconforge_generate(file_path, spritesheet_name, sprites, hash_icons) RUSTG_CALL(RUST_G, "iconforge_generate")(file_path, spritesheet_name, sprites, "[hash_icons]")
+/// Returns a job_id for use with rustg_iconforge_check()
+#define rustg_iconforge_generate_async(file_path, spritesheet_name, sprites, hash_icons) RUSTG_CALL(RUST_G, "iconforge_generate_async")(file_path, spritesheet_name, sprites, "[hash_icons]")
+/// Returns the status of an async job_id, or its result if it is completed. See RUSTG_JOB DEFINEs.
+#define rustg_iconforge_check(job_id) RUSTG_CALL(RUST_G, "iconforge_check")("[job_id]")
+/// Clears all cached DMIs and images, freeing up memory.
+/// This should be used after spritesheets are done being generated.
+#define rustg_iconforge_cleanup RUSTG_CALL(RUST_G, "iconforge_cleanup")
+/// Takes in a set of hashes, generate inputs, and DMI filepaths, and compares them to determine cache validity.
+/// input_hash: xxh64 hash of "sprites" from the cache.
+/// dmi_hashes: xxh64 hashes of the DMIs in a spritesheet, given by `rustg_iconforge_generate` with `hash_icons` enabled. From the cache.
+/// sprites: The new input that will be passed to rustg_iconforge_generate().
+/// Returns a CacheResult with the following structure: list(
+/// "result": "1" (if cache is valid) or "0" (if cache is invalid)
+/// "fail_reason": "" (emtpy string if valid, otherwise a string containing the invalidation reason or an error with ERROR: prefixed.)
+/// )
+/// In the case of an unrecoverable panic from within Rust, this function ONLY returns a string containing the error.
+#define rustg_iconforge_cache_valid(input_hash, dmi_hashes, sprites) RUSTG_CALL(RUST_G, "iconforge_cache_valid")(input_hash, dmi_hashes, sprites)
+/// Returns a job_id for use with rustg_iconforge_check()
+#define rustg_iconforge_cache_valid_async(input_hash, dmi_hashes, sprites) RUSTG_CALL(RUST_G, "iconforge_cache_valid_async")(input_hash, dmi_hashes, sprites)
+
+#define RUSTG_ICONFORGE_BLEND_COLOR "BlendColor"
+#define RUSTG_ICONFORGE_BLEND_ICON "BlendIcon"
+#define RUSTG_ICONFORGE_CROP "Crop"
+#define RUSTG_ICONFORGE_SCALE "Scale"
+
// Jobs Defines //
#define RUSTG_JOB_NO_RESULTS_YET "NO RESULTS YET"
@@ -211,6 +293,20 @@
#define rustg_noise_get_at_coordinates(seed, x, y) RUSTG_CALL(RUST_G, "noise_get_at_coordinates")(seed, x, y)
+/**
+ * Generates a 2D poisson disk distribution ('blue noise'), which is relatively uniform.
+ *
+ * params:
+ * `seed`: str
+ * `width`: int, width of the noisemap (see world.maxx)
+ * `length`: int, height of the noisemap (see world.maxy)
+ * `radius`: int, distance between points on the noisemap
+ *
+ * returns:
+ * a width*length length string of 1s and 0s representing a 2D poisson sample collapsed into a 1D string
+ */
+#define rustg_noise_poisson_map(seed, width, length, radius) RUSTG_CALL(RUST_G, "noise_poisson_map")(seed, width, length, radius)
+
// AStar Operations //
/**
@@ -236,15 +332,15 @@
*/
#define rustg_add_node_astar(json) RUSTG_CALL(RUST_G, "add_node_astar")(json)
-/**²
+/**
* Remove every link to the node with unique_id. Replace that node by null
*/
-#define rustg_remove_node_astart(unique_id) RUSTG_CALL(RUST_G, "remove_node_astar")(unique_id)
+#define rustg_remove_node_astar(unique_id) RUSTG_CALL(RUST_G, "remove_node_astar")("[unique_id]")
/**
* Compute the shortest path between start_node and goal_node using A*. Heuristic used is simple geometric distance
*/
-#define rustg_generate_path_astar(start_node_id, goal_node_id) RUSTG_CALL(RUST_G, "generate_path_astar")(start_node_id, goal_node_id)
+#define rustg_generate_path_astar(start_node_id, goal_node_id) RUSTG_CALL(RUST_G, "generate_path_astar")("[start_node_id]", "[goal_node_id]")
// Redis PubSub Operations //
@@ -256,6 +352,8 @@
/proc/rustg_redis_get_messages() return RUSTG_CALL(RUST_G, "redis_get_messages")()
#define rustg_redis_publish(channel, message) RUSTG_CALL(RUST_G, "redis_publish")(channel, message)
+// Redis Core Operations //
+
/**
* Connects to a given redis server.
*
@@ -295,6 +393,18 @@
*/
#define rustg_redis_lpop(key, count) RUSTG_CALL(RUST_G, "redis_lpop")(key, count)
+// Sanitisation Operations //
+
+/*
+ * Takes in a string and json_encode()"d lists to produce a sanitized string.
+ * This function operates on whitelists, there is currently no way to blacklist.
+ * Args:
+ * * text: the string to sanitize.
+ * * attribute_whitelist_json: a json_encode()'d list of HTML attributes to allow in the final string.
+ * * tag_whitelist_json: a json_encode()'d list of HTML tags to allow in the final string.
+ */
+#define rustg_sanitize_html(text, attribute_whitelist_json, tag_whitelist_json) RUSTG_CALL(RUST_G, "sanitize_html")(text, attribute_whitelist_json, tag_whitelist_json)
+
// SQL Operations //
#define rustg_sql_connect_pool(options) RUSTG_CALL(RUST_G, "sql_connect_pool")(options)
diff --git a/code/datums/revision.dm b/code/datums/revision.dm
index 10d5ee1d09d6..5b843266268b 100644
--- a/code/datums/revision.dm
+++ b/code/datums/revision.dm
@@ -100,6 +100,11 @@ GLOBAL_PROTECT(revision_info) // Dont mess with this
else
msg += "Server Commit: Unable to determine"
+ msg += "RUST-G Build: [rustg_get_version()]"
+
+ if(world.TgsAvailable())
+ msg += "TGS Version: [world.TgsVersion()] (API: [world.TgsApiVersion()])"
+
if(world.TgsAvailable() && length(GLOB.revision_info.testmerges))
msg += "Active Testmerges:"
msg += GLOB.revision_info.get_testmerge_chatmessage(FALSE)
diff --git a/rust_g.dll b/rust_g.dll
index c77db3ec9c65..9104d762be15 100644
Binary files a/rust_g.dll and b/rust_g.dll differ
diff --git a/tools/ci/install_rustg.sh b/tools/ci/install_rustg.sh
index c7e1cfc606c5..f1b77ef5baad 100755
--- a/tools/ci/install_rustg.sh
+++ b/tools/ci/install_rustg.sh
@@ -4,6 +4,6 @@ set -euo pipefail
source _build_dependencies.sh
mkdir -p ~/.byond/bin
-wget -O ~/.byond/bin/librust_g.so "https://github.com/ParadiseSS13/rust-g/releases/download/$RUSTG_VERSION/librust_g-ci.so"
+wget -O ~/.byond/bin/librust_g.so "https://github.com/ParadiseSS13/rust-g/releases/download/$RUSTG_VERSION/librust_g.so"
chmod +x ~/.byond/bin/librust_g.so
ldd ~/.byond/bin/librust_g.so