diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 17b23901e3..cc4860ce31 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,7 +63,7 @@ jobs: if: ( !contains(github.event.head_commit.message, '[ci skip]') ) name: Integration Tests # needs: ['run_linters', 'dreamchecker'] - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Ensure +x on CI directory @@ -78,6 +78,7 @@ jobs: run: | sudo dpkg --add-architecture i386 sudo apt update || true + sudo apt install gcc-multilib sudo apt install zlib1g-dev:i386 libssl-dev:i386 ldd librust_g.so - name: Unit Tests diff --git a/_build_dependencies.sh b/_build_dependencies.sh index c0873b3860..f96e0c27c7 100644 --- a/_build_dependencies.sh +++ b/_build_dependencies.sh @@ -2,6 +2,9 @@ # This file has all the information on what versions of libraries are thrown into the code +#rust_g git tag +export RUST_G_VERSION=3.1.0 + # byond version export BYOND_MAJOR=514 export BYOND_MINOR=1589 diff --git a/code/__defines/database.dm b/code/__defines/database.dm new file mode 100644 index 0000000000..3d20b3b9a3 --- /dev/null +++ b/code/__defines/database.dm @@ -0,0 +1,6 @@ +/// When a query has been queued up for execution/is being executed +#define DB_QUERY_STARTED 0 +/// When a query is finished executing +#define DB_QUERY_FINISHED 1 +/// When there was a problem with the execution of a query. +#define DB_QUERY_BROKEN 2 diff --git a/code/__defines/rust_g.dm b/code/__defines/rust_g.dm index 378ee27d1b..5404cebed9 100644 --- a/code/__defines/rust_g.dm +++ b/code/__defines/rust_g.dm @@ -38,61 +38,105 @@ #define RUST_G (__rust_g || __detect_rust_g()) #endif -// CHOMPedit Start - Rust http // Handle 515 call() -> call_ext() changes #if DM_VERSION >= 515 #define RUSTG_CALL call_ext #else #define RUSTG_CALL call #endif -// CHOMPedit End - Rust http -#define RUSTG_JOB_NO_RESULTS_YET "NO RESULTS YET" -#define RUSTG_JOB_NO_SUCH_JOB "NO SUCH JOB" -#define RUSTG_JOB_ERROR "JOB PANICKED" +/// Gets the version of rust_g +/proc/rustg_get_version() return RUSTG_CALL(RUST_G, "get_version")() -#define rustg_dmi_strip_metadata(fname) LIBCALL(RUST_G, "dmi_strip_metadata")(fname) -#define rustg_dmi_create_png(path, width, height, data) LIBCALL(RUST_G, "dmi_create_png")(path, width, height, data) -#define rustg_noise_get_at_coordinates(seed, x, y) LIBCALL(RUST_G, "noise_get_at_coordinates")(seed, x, y) +/** + * Sets up the Aho-Corasick automaton with its default options. + * + * The search patterns list and the replacements must be of the same length when replace is run, but an empty replacements list is allowed if replacements are supplied with the replace call + * Arguments: + * * key - The key for the automaton, to be used with subsequent rustg_acreplace/rustg_acreplace_with_replacements calls + * * patterns - A non-associative list of strings to search for + * * replacements - Default replacements for this automaton, used with rustg_acreplace + */ +#define rustg_setup_acreplace(key, patterns, replacements) RUSTG_CALL(RUST_G, "setup_acreplace")(key, json_encode(patterns), json_encode(replacements)) -#define rustg_file_read(fname) LIBCALL(RUST_G, "file_read")(fname) -#define rustg_file_exists(fname) LIBCALL(RUST_G, "file_exists")(fname) -#define rustg_file_write(text, fname) LIBCALL(RUST_G, "file_write")(text, fname) -#define rustg_file_append(text, fname) LIBCALL(RUST_G, "file_append")(text, fname) +/** + * Sets up the Aho-Corasick automaton using supplied options. + * + * The search patterns list and the replacements must be of the same length when replace is run, but an empty replacements list is allowed if replacements are supplied with the replace call + * Arguments: + * * key - The key for the automaton, to be used with subsequent rustg_acreplace/rustg_acreplace_with_replacements calls + * * options - An associative list like list("anchored" = 0, "ascii_case_insensitive" = 0, "match_kind" = "Standard"). The values shown on the example are the defaults, and default values may be omitted. See the identically named methods at https://docs.rs/aho-corasick/latest/aho_corasick/struct.AhoCorasickBuilder.html to see what the options do. + * * patterns - A non-associative list of strings to search for + * * replacements - Default replacements for this automaton, used with rustg_acreplace + */ +#define rustg_setup_acreplace_with_options(key, options, patterns, replacements) RUSTG_CALL(RUST_G, "setup_acreplace")(key, json_encode(options), json_encode(patterns), json_encode(replacements)) + +/** + * Run the specified replacement engine with the provided haystack text to replace, returning replaced text. + * + * Arguments: + * * key - The key for the automaton + * * text - Text to run replacements on + */ +#define rustg_acreplace(key, text) RUSTG_CALL(RUST_G, "acreplace")(key, text) + +/** + * Run the specified replacement engine with the provided haystack text to replace, returning replaced text. + * + * Arguments: + * * key - The key for the automaton + * * text - Text to run replacements on + * * replacements - Replacements for this call. Must be the same length as the set-up patterns + */ +#define rustg_acreplace_with_replacements(key, text, replacements) RUSTG_CALL(RUST_G, "acreplace_with_replacements")(key, text, json_encode(replacements)) + +/** + * This proc generates a cellular automata noise grid which can be used in procedural generation methods. + * + * Returns a single string that goes row by row, with values of 1 representing an alive cell, and a value of 0 representing a dead cell. + * + * Arguments: + * * percentage: The chance of a turf starting closed + * * smoothing_iterations: The amount of iterations the cellular automata simulates before returning the results + * * birth_limit: If the number of neighboring cells is higher than this amount, a cell is born + * * death_limit: If the number of neighboring cells is lower than this amount, a cell dies + * * width: The width of the grid. + * * height: The height of the grid. + */ +#define rustg_cnoise_generate(percentage, smoothing_iterations, birth_limit, death_limit, width, height) \ + RUSTG_CALL(RUST_G, "cnoise_generate")(percentage, smoothing_iterations, birth_limit, death_limit, width, height) + +#define rustg_dmi_strip_metadata(fname) RUSTG_CALL(RUST_G, "dmi_strip_metadata")(fname) +#define rustg_dmi_create_png(path, width, height, data) RUSTG_CALL(RUST_G, "dmi_create_png")(path, width, height, data) +#define rustg_dmi_resize_png(path, width, height, resizetype) RUSTG_CALL(RUST_G, "dmi_resize_png")(path, width, height, resizetype) +/** + * input: must be a path, not an /icon; you have to do your own handling if it is one, as icon objects can't be directly passed to rustg. + * + * output: json_encode'd list. json_decode to get a flat list with icon states in the order they're in inside the .dmi + */ +#define rustg_dmi_icon_states(fname) RUSTG_CALL(RUST_G, "dmi_icon_states")(fname) + +#define rustg_file_read(fname) RUSTG_CALL(RUST_G, "file_read")(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)) +#define rustg_file_seek_line(fname, line) RUSTG_CALL(RUST_G, "file_seek_line")(fname, "[line]") #ifdef RUSTG_OVERRIDE_BUILTINS -#define file2text(fname) rustg_file_read("[fname]") -#define text2file(text, fname) rustg_file_append(text, "[fname]") + #define file2text(fname) rustg_file_read("[fname]") + #define text2file(text, fname) rustg_file_append(text, "[fname]") #endif -#define rustg_git_revparse(rev) LIBCALL(RUST_G, "rg_git_revparse")(rev) -#define rustg_git_commit_date(rev) LIBCALL(RUST_G, "rg_git_commit_date")(rev) +/// 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_hash_string(algorithm, text) LIBCALL(RUST_G, "hash_string")(algorithm, text) -#define rustg_hash_file(algorithm, fname) LIBCALL(RUST_G, "hash_file")(algorithm, fname) - -#define RUSTG_HASH_MD5 "md5" -#define RUSTG_HASH_SHA1 "sha1" -#define RUSTG_HASH_SHA256 "sha256" -#define RUSTG_HASH_SHA512 "sha512" - -#ifdef RUSTG_OVERRIDE_BUILTINS -#define md5(thing) (isfile(thing) ? rustg_hash_file(RUSTG_HASH_MD5, "[thing]") : rustg_hash_string(RUSTG_HASH_MD5, thing)) -#endif - -#define rustg_json_is_valid(text) (LIBCALL(RUST_G, "json_is_valid")(text) == "true") - -#define rustg_log_write(fname, text, format) LIBCALL(RUST_G, "log_write")(fname, text, format) -/proc/rustg_log_close_all() return LIBCALL(RUST_G, "log_close_all")() - -#define rustg_url_encode(text) LIBCALL(RUST_G, "url_encode")(text) -#define rustg_url_decode(text) LIBCALL(RUST_G, "url_decode")(text) - -#ifdef RUSTG_OVERRIDE_BUILTINS -#define url_encode(text) rustg_url_encode(text) -#define url_decode(text) rustg_url_decode(text) -#endif +/** + * Returns the date of the given revision in the format YYYY-MM-DD. + * Returns null if the revision is invalid. + */ +#define rustg_git_commit_date(rev) RUSTG_CALL(RUST_G, "rg_git_commit_date")(rev) #define RUSTG_HTTP_METHOD_GET "get" #define RUSTG_HTTP_METHOD_PUT "put" @@ -100,13 +144,59 @@ #define RUSTG_HTTP_METHOD_PATCH "patch" #define RUSTG_HTTP_METHOD_HEAD "head" #define RUSTG_HTTP_METHOD_POST "post" -#define rustg_http_request_blocking(method, url, body, headers, options) RUSTG_CALL(RUST_G, "http_request_blocking")(method, url, body, headers, options) // CHOMPedit - Rust HTTP Requests -#define rustg_http_request_async(method, url, body, headers, options) RUSTG_CALL(RUST_G, "http_request_async")(method, url, body, headers, options) // CHOMPedit - Rust HTTP Requests -#define rustg_http_check_request(req_id) LIBCALL(RUST_G, "http_check_request")(req_id) +#define rustg_http_request_blocking(method, url, body, headers, options) RUSTG_CALL(RUST_G, "http_request_blocking")(method, url, body, headers, options) +#define rustg_http_request_async(method, url, body, headers, options) RUSTG_CALL(RUST_G, "http_request_async")(method, url, body, headers, options) +#define rustg_http_check_request(req_id) RUSTG_CALL(RUST_G, "http_check_request")(req_id) + +#define RUSTG_JOB_NO_RESULTS_YET "NO RESULTS YET" +#define RUSTG_JOB_NO_SUCH_JOB "NO SUCH JOB" +#define RUSTG_JOB_ERROR "JOB PANICKED" + +#define rustg_json_is_valid(text) (RUSTG_CALL(RUST_G, "json_is_valid")(text) == "true") + +#define rustg_log_write(fname, text, format) RUSTG_CALL(RUST_G, "log_write")(fname, text, format) +/proc/rustg_log_close_all() return RUSTG_CALL(RUST_G, "log_close_all")() + +#define rustg_noise_get_at_coordinates(seed, x, y) RUSTG_CALL(RUST_G, "noise_get_at_coordinates")(seed, x, y) + +#define rustg_sql_connect_pool(options) RUSTG_CALL(RUST_G, "sql_connect_pool")(options) +#define rustg_sql_query_async(handle, query, params) RUSTG_CALL(RUST_G, "sql_query_async")(handle, query, params) +#define rustg_sql_query_blocking(handle, query, params) RUSTG_CALL(RUST_G, "sql_query_blocking")(handle, query, params) +#define rustg_sql_connected(handle) RUSTG_CALL(RUST_G, "sql_connected")(handle) +#define rustg_sql_disconnect_pool(handle) RUSTG_CALL(RUST_G, "sql_disconnect_pool")(handle) +#define rustg_sql_check_query(job_id) RUSTG_CALL(RUST_G, "sql_check_query")("[job_id]") + +#define rustg_time_microseconds(id) text2num(RUSTG_CALL(RUST_G, "time_microseconds")(id)) +#define rustg_time_milliseconds(id) text2num(RUSTG_CALL(RUST_G, "time_milliseconds")(id)) +#define rustg_time_reset(id) RUSTG_CALL(RUST_G, "time_reset")(id) + +/// Returns the timestamp as a string +/proc/rustg_unix_timestamp() + return RUSTG_CALL(RUST_G, "unix_timestamp")() + +#define rustg_raw_read_toml_file(path) json_decode(RUSTG_CALL(RUST_G, "toml_file_to_json")(path) || "null") + +/proc/rustg_read_toml_file(path) + var/list/output = rustg_raw_read_toml_file(path) + if (output["success"]) + return json_decode(output["content"]) + else + CRASH(output["content"]) + +#define rustg_raw_toml_encode(value) json_decode(RUSTG_CALL(RUST_G, "toml_encode")(json_encode(value))) + +/proc/rustg_toml_encode(value) + var/list/output = rustg_raw_toml_encode(value) + if (output["success"]) + return output["content"] + else + CRASH(output["content"]) + +#define rustg_url_encode(text) RUSTG_CALL(RUST_G, "url_encode")("[text]") +#define rustg_url_decode(text) RUSTG_CALL(RUST_G, "url_decode")(text) + +#ifdef RUSTG_OVERRIDE_BUILTINS + #define url_encode(text) rustg_url_encode(text) + #define url_decode(text) rustg_url_decode(text) +#endif -#define rustg_sql_connect_pool(options) LIBCALL(RUST_G, "sql_connect_pool")(options) -#define rustg_sql_query_async(handle, query, params) LIBCALL(RUST_G, "sql_query_async")(handle, query, params) -#define rustg_sql_query_blocking(handle, query, params) LIBCALL(RUST_G, "sql_query_blocking")(handle, query, params) -#define rustg_sql_connected(handle) LIBCALL(RUST_G, "sql_connected")(handle) -#define rustg_sql_disconnect_pool(handle) LIBCALL(RUST_G, "sql_disconnect_pool")(handle) -#define rustg_sql_check_query(job_id) LIBCALL(RUST_G, "sql_check_query")("[job_id]") diff --git a/code/__defines/tgs.dm b/code/__defines/tgs.dm index 3565238195..ad12bcdb27 100644 --- a/code/__defines/tgs.dm +++ b/code/__defines/tgs.dm @@ -1,6 +1,6 @@ // tgstation-server DMAPI -#define TGS_DMAPI_VERSION "6.0.3" +#define TGS_DMAPI_VERSION "7.0.1" // All functions and datums outside this document are subject to change with any version and should not be relied on. @@ -12,8 +12,8 @@ // Comment this out once you've filled in the below. #error TGS API unconfigured -// Uncomment this if you wish to allow the game to interact with TGS 3. -// This will raise the minimum required security level of your game to TGS_SECURITY_TRUSTED due to it utilizing call()() +// Uncomment this if you wish to allow the game to interact with TGS 3.. +// This will raise the minimum required security level of your game to TGS_SECURITY_TRUSTED due to it utilizing call()(). //#define TGS_V3_API // Required interfaces (fill in with your codebase equivalent): @@ -52,44 +52,46 @@ // EVENT CODES -/// Before a reboot mode change, extras parameters are the current and new reboot mode enums +/// Before a reboot mode change, extras parameters are the current and new reboot mode enums. #define TGS_EVENT_REBOOT_MODE_CHANGE -1 -/// Before a port change is about to happen, extra parameters is new port +/// Before a port change is about to happen, extra parameters is new port. #define TGS_EVENT_PORT_SWAP -2 -/// Before the instance is renamed, extra parameter is the new name +/// Before the instance is renamed, extra parameter is the new name. #define TGS_EVENT_INSTANCE_RENAMED -3 -/// After the watchdog reattaches to DD, extra parameter is the new [/datum/tgs_version] of the server +/// After the watchdog reattaches to DD, extra parameter is the new [/datum/tgs_version] of the server. #define TGS_EVENT_WATCHDOG_REATTACH -4 +/// When the watchdog sends a health check to DD. No parameters. +#define TGS_EVENT_HEALTH_CHECK -5 -/// When the repository is reset to its origin reference. Parameters: Reference name, Commit SHA +/// When the repository is reset to its origin reference. Parameters: Reference name, Commit SHA. #define TGS_EVENT_REPO_RESET_ORIGIN 0 -/// When the repository performs a checkout. Parameters: Checkout git object +/// When the repository performs a checkout. Parameters: Checkout git object. #define TGS_EVENT_REPO_CHECKOUT 1 -/// When the repository performs a fetch operation. No parameters +/// When the repository performs a fetch operation. No parameters. #define TGS_EVENT_REPO_FETCH 2 -/// When the repository test merges. Parameters: PR Number, PR Sha, (Nullable) Comment made by TGS user +/// When the repository test merges. Parameters: PR Number, PR Sha, (Nullable) Comment made by TGS user. #define TGS_EVENT_REPO_MERGE_PULL_REQUEST 3 -/// Before the repository makes a sychronize operation. Parameters: Absolute repostiory path +/// Before the repository makes a sychronize operation. Parameters: Absolute repostiory path. #define TGS_EVENT_REPO_PRE_SYNCHRONIZE 4 -/// Before a BYOND install operation begins. Parameters: [/datum/tgs_version] of the installing BYOND -#define TGS_EVENT_BYOND_INSTALL_START 5 -/// When a BYOND install operation fails. Parameters: Error message -#define TGS_EVENT_BYOND_INSTALL_FAIL 6 -/// When the active BYOND version changes. Parameters: (Nullable) [/datum/tgs_version] of the current BYOND, [/datum/tgs_version] of the new BYOND -#define TGS_EVENT_BYOND_ACTIVE_VERSION_CHANGE 7 -/// When the compiler starts running. Parameters: Game directory path, origin commit SHA +/// Before a engine install operation begins. Parameters: Version string of the installing engine. +#define TGS_EVENT_ENGINE_INSTALL_START 5 +/// When a engine install operation fails. Parameters: Error message +#define TGS_EVENT_ENGINE_INSTALL_FAIL 6 +/// When the active engine version changes. Parameters: (Nullable) Version string of the current engine, version string of the new engine. +#define TGS_EVENT_ENGINE_ACTIVE_VERSION_CHANGE 7 +/// When the compiler starts running. Parameters: Game directory path, origin commit SHA. #define TGS_EVENT_COMPILE_START 8 -/// When a compile is cancelled. No parameters +/// When a compile is cancelled. No parameters. #define TGS_EVENT_COMPILE_CANCELLED 9 -/// When a compile fails. Parameters: Game directory path, [TRUE]/[FALSE] based on if the cause for failure was DMAPI validation +/// When a compile fails. Parameters: Game directory path, [TRUE]/[FALSE] based on if the cause for failure was DMAPI validation. #define TGS_EVENT_COMPILE_FAILURE 10 -/// When a compile operation completes. Note, this event fires before the new .dmb is loaded into the watchdog. Consider using the [TGS_EVENT_DEPLOYMENT_COMPLETE] instead. Parameters: Game directory path +/// When a compile operation completes. Note, this event fires before the new .dmb is loaded into the watchdog. Consider using the [TGS_EVENT_DEPLOYMENT_COMPLETE] instead. Parameters: Game directory path. #define TGS_EVENT_COMPILE_COMPLETE 11 -/// When an automatic update for the current instance begins. No parameters +/// When an automatic update for the current instance begins. No parameters. #define TGS_EVENT_INSTANCE_AUTO_UPDATE_START 12 -/// When the repository encounters a merge conflict: Parameters: Base SHA, target SHA, base reference, target reference +/// When the repository encounters a merge conflict: Parameters: Base SHA, target SHA, base reference, target reference. #define TGS_EVENT_REPO_MERGE_CONFLICT 13 -/// When a deployment completes. No Parameters +/// When a deployment completes. No Parameters. #define TGS_EVENT_DEPLOYMENT_COMPLETE 14 /// Before the watchdog shuts down. Not sent for graceful shutdowns. No parameters. #define TGS_EVENT_WATCHDOG_SHUTDOWN 15 @@ -102,6 +104,14 @@ // #define TGS_EVENT_WORLD_REBOOT 20 /// Watchdog event when TgsInitializationComplete() is called. No parameters. #define TGS_EVENT_WORLD_PRIME 21 +// DMAPI also doesnt implement this +// #define TGS_EVENT_DREAM_DAEMON_LAUNCH 22 +/// After a single submodule update is performed. Parameters: Updated submodule name. +#define TGS_EVENT_REPO_SUBMODULE_UPDATE 23 +/// After CodeModifications are applied, before DreamMaker is run. Parameters: Game directory path, origin commit sha, version string of the used engine. +#define TGS_EVENT_PRE_DREAM_MAKER 24 +/// Whenever a deployment folder is deleted from disk. Parameters: Game directory path. +#define TGS_EVENT_DEPLOYMENT_CLEANUP 25 // OTHER ENUMS @@ -112,6 +122,7 @@ /// The watchdog will restart on reboot. #define TGS_REBOOT_MODE_RESTART 2 +// Note that security levels are currently meaningless in OpenDream /// DreamDaemon Trusted security level. #define TGS_SECURITY_TRUSTED 0 /// DreamDaemon Safe security level. @@ -119,6 +130,18 @@ /// DreamDaemon Ultrasafe security level. #define TGS_SECURITY_ULTRASAFE 2 +/// DreamDaemon public visibility level. +#define TGS_VISIBILITY_PUBLIC 0 +/// DreamDaemon private visibility level. +#define TGS_VISIBILITY_PRIVATE 1 +/// DreamDaemon invisible visibility level. +#define TGS_VISIBILITY_INVISIBLE 2 + +/// The Build Your Own Net Dream engine. +#define TGS_ENGINE_TYPE_BYOND 0 +/// The OpenDream engine. +#define TGS_ENGINE_TYPE_OPENDREAM 1 + //REQUIRED HOOKS /** @@ -127,7 +150,7 @@ * * event_handler - Optional user defined [/datum/tgs_event_handler]. * * minimum_required_security_level: The minimum required security level to run the game in which the DMAPI is integrated. Can be one of [TGS_SECURITY_ULTRASAFE], [TGS_SECURITY_SAFE], or [TGS_SECURITY_TRUSTED]. */ -/world/proc/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE) +/world/proc/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_TRUSTED) return /** @@ -145,7 +168,7 @@ #define VGS_TOPIC var/vgs_topic_return = VgsTopic(args[1]); if(vgs_topic_return) return vgs_topic_return // VOREStation Edit - VGS /** - * Call this as late as possible in [world/proc/Reboot]. + * Call this as late as possible in [world/proc/Reboot] (BEFORE ..()). */ /world/proc/TgsReboot() return @@ -157,28 +180,28 @@ /datum/tgs_revision_information /// Full SHA of the commit. var/commit - /// ISO 8601 timestamp of when the commit was created + /// ISO 8601 timestamp of when the commit was created. var/timestamp /// Full sha of last known remote commit. This may be null if the TGS repository is not currently tracking a remote branch. var/origin_commit /// Represents a version. /datum/tgs_version - /// The suite/major version number + /// The suite/major version number. var/suite - // This group of variables can be null to represent a wild card - /// The minor version number. null for wildcards + // This group of variables can be null to represent a wild card. + /// The minor version number. null for wildcards. var/minor - /// The patch version number. null for wildcards + /// The patch version number. null for wildcards. var/patch - /// Legacy version number. Generally null + /// Legacy version number. Generally null. var/deprecated_patch - /// Unparsed string value + /// Unparsed string value. var/raw_parameter - /// String value minus prefix + /// String value minus prefix. var/deprefixed_parameter /** @@ -224,40 +247,49 @@ var/is_admin_channel /// [TRUE]/[FALSE] if the channel is a private message channel for a [/datum/tgs_chat_user]. var/is_private_channel - /// Tag string associated with the channel in TGS + /// Tag string associated with the channel in TGS. var/custom_tag + /// [TRUE]/[FALSE] if the channel supports embeds. + var/embeds_supported // Represents a chat user /datum/tgs_chat_user /// TGS internal user ID. var/id - // The user's display name. + /// The user's display name. var/friendly_name - // The string to use to ping this user in a message. + /// The string to use to ping this user in a message. var/mention - /// The [/datum/tgs_chat_channel] the user was from + /// The [/datum/tgs_chat_channel] the user was from. var/datum/tgs_chat_channel/channel +/// User definable handler for TGS events. +/datum/tgs_event_handler + /// If the handler receieves [TGS_EVENT_HEALTH_CHECK] events. + var/receive_health_checks = FALSE + /** * User definable callback for handling TGS events. * - * event_code - One of the TGS_EVENT_ defines. Extra parameters will be documented in each + * event_code - One of the TGS_EVENT_ defines. Extra parameters will be documented in each. */ /datum/tgs_event_handler/proc/HandleEvent(event_code, ...) set waitfor = FALSE return -/// User definable chat command +/// User definable chat command. /datum/tgs_chat_command - /// The string to trigger this command on a chat bot. e.g `@bot name ...` or `!tgs name ...` + /// The string to trigger this command on a chat bot. e.g `@bot name ...` or `!tgs name ...`. var/name = "" - /// The help text displayed for this command + /// The help text displayed for this command. var/help_text = "" - /// If this command should be available to game administrators only + /// If this command should be available to game administrators only. var/admin_only = FALSE + /// A subtype of [/datum/tgs_chat_command] that is ignored when enumerating available commands. Use this to create shared base /datums for commands. + var/ignore_type /** - * Process command activation. Should return a string to respond to the issuer with. + * Process command activation. Should return a [/datum/tgs_message_content] to respond to the issuer with. * * sender - The [/datum/tgs_chat_user] who issued the command. * params - The trimmed string following the command `/datum/tgs_chat_command/var/name]. @@ -265,6 +297,107 @@ /datum/tgs_chat_command/proc/Run(datum/tgs_chat_user/sender, params) CRASH("[type] has no implementation for Run()") +/// User definable chat message. +/datum/tgs_message_content + /// The tring content of the message. Must be provided in New(). + var/text + + /// The [/datum/tgs_chat_embed] to embed in the message. Not supported on all chat providers. + var/datum/tgs_chat_embed/structure/embed + +/datum/tgs_message_content/New(text) + if(!istext(text)) + TGS_ERROR_LOG("[/datum/tgs_message_content] created with no text!") + text = null + + src.text = text + +/// User definable chat embed. Currently mirrors Discord chat embeds. See https://discord.com/developers/docs/resources/channel#embed-object-embed-structure for details. +/datum/tgs_chat_embed/structure + var/title + var/description + var/url + + /// Timestamp must be encoded as: time2text(world.timeofday, "YYYY-MM-DD hh:mm:ss"). Use the active timezone. + var/timestamp + + /// Colour must be #AARRGGBB or #RRGGBB hex string. + var/colour + + /// See https://discord.com/developers/docs/resources/channel#embed-object-embed-image-structure for details. + var/datum/tgs_chat_embed/media/image + + /// See https://discord.com/developers/docs/resources/channel#embed-object-embed-thumbnail-structure for details. + var/datum/tgs_chat_embed/media/thumbnail + + /// See https://discord.com/developers/docs/resources/channel#embed-object-embed-image-structure for details. + var/datum/tgs_chat_embed/media/video + + var/datum/tgs_chat_embed/footer/footer + var/datum/tgs_chat_embed/provider/provider + var/datum/tgs_chat_embed/provider/author/author + + var/list/datum/tgs_chat_embed/field/fields + +/// Common datum for similar discord embed medias. +/datum/tgs_chat_embed/media + /// Must be set in New(). + var/url + var/width + var/height + var/proxy_url + +/datum/tgs_chat_embed/media/New(url) + if(!istext(url)) + CRASH("[/datum/tgs_chat_embed/media] created with no url!") + + src.url = url + +/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-footer-structure for details. +/datum/tgs_chat_embed/footer + /// Must be set in New(). + var/text + var/icon_url + var/proxy_icon_url + +/datum/tgs_chat_embed/footer/New(text) + if(!istext(text)) + CRASH("[/datum/tgs_chat_embed/footer] created with no text!") + + src.text = text + +/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-provider-structure for details. +/datum/tgs_chat_embed/provider + var/name + var/url + +/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-author-structure for details. Must have name set in New(). +/datum/tgs_chat_embed/provider/author + var/icon_url + var/proxy_icon_url + +/datum/tgs_chat_embed/provider/author/New(name) + if(!istext(name)) + CRASH("[/datum/tgs_chat_embed/provider/author] created with no name!") + + src.name = name + +/// See https://discord.com/developers/docs/resources/channel#embed-object-embed-field-structure for details. Must have name and value set in New(). +/datum/tgs_chat_embed/field + var/name + var/value + var/is_inline + +/datum/tgs_chat_embed/field/New(name, value) + if(!istext(name)) + CRASH("[/datum/tgs_chat_embed/field] created with no name!") + + if(!istext(value)) + CRASH("[/datum/tgs_chat_embed/field] created with no value!") + + src.name = name + src.value = value + // API FUNCTIONS /// Returns the maximum supported [/datum/tgs_version] of the DMAPI. @@ -284,75 +417,81 @@ // No function below this succeeds if it TgsAvailable() returns FALSE or if TgsNew() has yet to be called. /** - * Forces a hard reboot of DreamDaemon by ending the process. + * Forces a hard reboot of DreamDaemon by ending the process. This function may sleep! * * Unlike del(world) clients will try to reconnect. - * If TGS has not requested a [TGS_REBOOT_MODE_SHUTDOWN] DreamDaemon will be launched again + * If TGS has not requested a [TGS_REBOOT_MODE_SHUTDOWN] DreamDaemon will be launched again. */ /world/proc/TgsEndProcess() return /** - * Send a message to connected chats. + * Send a message to connected chats. This function may sleep! * - * message - The string to send. + * message - The [/datum/tgs_message_content] to send. * admin_only: If [TRUE], message will be sent to admin connected chats. Vice-versa applies. */ -/world/proc/TgsTargetedChatBroadcast(message, admin_only = FALSE) +/world/proc/TgsTargetedChatBroadcast(datum/tgs_message_content/message, admin_only = FALSE) return /** - * Send a private message to a specific user. + * Send a private message to a specific user. This function may sleep! * - * message - The string to send. + * message - The [/datum/tgs_message_content] to send. * user: The [/datum/tgs_chat_user] to PM. */ -/world/proc/TgsChatPrivateMessage(message, datum/tgs_chat_user/user) +/world/proc/TgsChatPrivateMessage(datum/tgs_message_content/message, datum/tgs_chat_user/user) return -// The following functions will sleep if a call to TgsNew() is sleeping - /** - * Send a message to connected chats that are flagged as game-related in TGS. + * Send a message to connected chats that are flagged as game-related in TGS. This function may sleep! * - * message - The string to send. + * message - The [/datum/tgs_message_content] to send. * channels - Optional list of [/datum/tgs_chat_channel]s to restrict the message to. */ -/world/proc/TgsChatBroadcast(message, list/channels = null) +/world/proc/TgsChatBroadcast(datum/tgs_message_content/message, list/channels = null) return -/// Returns the current [/datum/tgs_version] of TGS if it is running the server, null otherwise. +/// Returns the current [/datum/tgs_version] of TGS if it is running the server, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping! /world/proc/TgsVersion() return -/// Returns the current [/datum/tgs_version] of the DMAPI being used if it was activated, null otherwise. +/// Returns the running engine type +/world/proc/TgsEngine() + return + +/// Returns the current [/datum/tgs_version] of the DMAPI being used if it was activated, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping! /world/proc/TgsApiVersion() return -/// Returns the name of the TGS instance running the game if TGS is present, null otherwise. +/// Returns the name of the TGS instance running the game if TGS is present, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping! /world/proc/TgsInstanceName() return -/// Return the current [/datum/tgs_revision_information] of the running server if TGS is present, null otherwise. +/// Return the current [/datum/tgs_revision_information] of the running server if TGS is present, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping! /world/proc/TgsRevision() return -/// Returns the current BYOND security level as a TGS_SECURITY_ define if TGS is present, null otherwise. +/// Returns the current BYOND security level as a TGS_SECURITY_ define if TGS is present, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping! /world/proc/TgsSecurityLevel() return -/// Returns a list of active [/datum/tgs_revision_information/test_merge]s if TGS is present, null otherwise. +/// Returns the current BYOND visibility level as a TGS_VISIBILITY_ define if TGS is present, null otherwise. Requires TGS to be using interop API version 5 or higher otherwise the string "___unimplemented" wil be returned. This function may sleep if the call to [/world/proc/TgsNew] is sleeping! +/world/proc/TgsVisibility() + return + +/// Returns a list of active [/datum/tgs_revision_information/test_merge]s if TGS is present, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping! /world/proc/TgsTestMerges() return -/// Returns a list of connected [/datum/tgs_chat_channel]s if TGS is present, null otherwise. +/// Returns a list of connected [/datum/tgs_chat_channel]s if TGS is present, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping! /world/proc/TgsChatChannelInfo() return /* The MIT License -Copyright (c) 2017 Jordan Brown +Copyright (c) 2017-2023 Jordan Brown Permission is hereby granted, free of charge, to any person obtaining a copy of this software and diff --git a/code/_helpers/logging.dm b/code/_helpers/logging.dm index 76a1e03eeb..34d74853bc 100644 --- a/code/_helpers/logging.dm +++ b/code/_helpers/logging.dm @@ -79,7 +79,7 @@ establish_db_connection() if(!SSdbcore.IsConnected()) return null - var/DBQuery/query_insert = SSdbcore.NewQuery("INSERT INTO erro_dialog (mid, time, ckey, mob, type, message) VALUES (null, NOW(), :sender_ckey, :sender_mob, :message_type, :message_content)", \ + var/datum/db_query/query_insert = SSdbcore.NewQuery("INSERT INTO erro_dialog (mid, time, ckey, mob, type, message) VALUES (null, NOW(), :sender_ckey, :sender_mob, :message_type, :message_content)", \ list("sender_ckey" = speaker.ckey, "sender_mob" = speaker.real_name, "message_type" = "say", "message_content" = text)) if(!query_insert.Execute()) log_debug("Error during logging: "+query_insert.ErrorMsg()) @@ -96,7 +96,7 @@ establish_db_connection() if(!SSdbcore.IsConnected()) return null - var/DBQuery/query_insert = SSdbcore.NewQuery("INSERT INTO erro_dialog (mid, time, ckey, mob, type, message) VALUES (null, NOW(), :sender_ckey, :sender_mob, :message_type, :message_content)", \ + var/datum/db_query/query_insert = SSdbcore.NewQuery("INSERT INTO erro_dialog (mid, time, ckey, mob, type, message) VALUES (null, NOW(), :sender_ckey, :sender_mob, :message_type, :message_content)", \ list("sender_ckey" = user.ckey, "sender_mob" = user.mob.real_name, "message_type" = "ooc", "message_content" = text)) if(!query_insert.Execute()) log_debug("Error during logging: "+query_insert.ErrorMsg()) @@ -112,7 +112,7 @@ establish_db_connection() if(!SSdbcore.IsConnected()) return null - var/DBQuery/query_insert = SSdbcore.NewQuery("INSERT INTO erro_dialog (mid, time, ckey, mob, type, message) VALUES (null, NOW(), :sender_ckey, :sender_mob, :message_type, :message_content)", \ + var/datum/db_query/query_insert = SSdbcore.NewQuery("INSERT INTO erro_dialog (mid, time, ckey, mob, type, message) VALUES (null, NOW(), :sender_ckey, :sender_mob, :message_type, :message_content)", \ list("sender_ckey" = user.ckey, "sender_mob" = user.mob.real_name, "message_type" = "aooc", "message_content" = text)) if(!query_insert.Execute()) log_debug("Error during logging: "+query_insert.ErrorMsg()) @@ -128,7 +128,7 @@ establish_db_connection() if(!SSdbcore.IsConnected()) return null - var/DBQuery/query_insert = SSdbcore.NewQuery("INSERT INTO erro_dialog (mid, time, ckey, mob, type, message) VALUES (null, NOW(), :sender_ckey, :sender_mob, :message_type, :message_content)", \ + var/datum/db_query/query_insert = SSdbcore.NewQuery("INSERT INTO erro_dialog (mid, time, ckey, mob, type, message) VALUES (null, NOW(), :sender_ckey, :sender_mob, :message_type, :message_content)", \ list("sender_ckey" = user.ckey, "sender_mob" = user.mob.real_name, "message_type" = "looc", "message_content" = text)) if(!query_insert.Execute()) log_debug("Error during logging: "+query_insert.ErrorMsg()) @@ -148,7 +148,7 @@ establish_db_connection() if(!SSdbcore.IsConnected()) return null - var/DBQuery/query_insert = SSdbcore.NewQuery("INSERT INTO erro_dialog (mid, time, ckey, mob, type, message) VALUES (null, NOW(), :sender_ckey, :sender_mob, :message_type, :message_content)", \ + var/datum/db_query/query_insert = SSdbcore.NewQuery("INSERT INTO erro_dialog (mid, time, ckey, mob, type, message) VALUES (null, NOW(), :sender_ckey, :sender_mob, :message_type, :message_content)", \ list("sender_ckey" = speaker.ckey, "sender_mob" = speaker.real_name, "message_type" = "whisper", "message_content" = text)) if(!query_insert.Execute()) log_debug("Error during logging: "+query_insert.ErrorMsg()) @@ -167,7 +167,7 @@ establish_db_connection() if(!SSdbcore.IsConnected()) return null - var/DBQuery/query_insert = SSdbcore.NewQuery("INSERT INTO erro_dialog (mid, time, ckey, mob, type, message) VALUES (null, NOW(), :sender_ckey, :sender_mob, :message_type, :message_content)", \ + var/datum/db_query/query_insert = SSdbcore.NewQuery("INSERT INTO erro_dialog (mid, time, ckey, mob, type, message) VALUES (null, NOW(), :sender_ckey, :sender_mob, :message_type, :message_content)", \ list("sender_ckey" = speaker.ckey, "sender_mob" = speaker.real_name, "message_type" = "emote", "message_content" = text)) if(!query_insert.Execute()) log_debug("Error during logging: "+query_insert.ErrorMsg()) @@ -201,7 +201,7 @@ establish_db_connection() if(!SSdbcore.IsConnected()) return null - var/DBQuery/query_insert = SSdbcore.NewQuery("INSERT INTO erro_dialog (mid, time, ckey, mob, type, message) VALUES (null, NOW(), :sender_ckey, :sender_mob, :message_type, :message_content)", \ + var/datum/db_query/query_insert = SSdbcore.NewQuery("INSERT INTO erro_dialog (mid, time, ckey, mob, type, message) VALUES (null, NOW(), :sender_ckey, :sender_mob, :message_type, :message_content)", \ list("sender_ckey" = speaker.ckey, "sender_mob" = speaker.real_name, "message_type" = "deadsay", "message_content" = text)) if(!query_insert.Execute()) log_debug("Error during logging: "+query_insert.ErrorMsg()) @@ -222,7 +222,7 @@ establish_db_connection() if(!SSdbcore.IsConnected()) return null - var/DBQuery/query_insert = SSdbcore.NewQuery("INSERT INTO erro_dialog (mid, time, ckey, mob, type, message) VALUES (null, NOW(), :sender_ckey, :sender_mob, :message_type, :message_content)", \ + var/datum/db_query/query_insert = SSdbcore.NewQuery("INSERT INTO erro_dialog (mid, time, ckey, mob, type, message) VALUES (null, NOW(), :sender_ckey, :sender_mob, :message_type, :message_content)", \ list("sender_ckey" = speaker.ckey, "sender_mob" = speaker.real_name, "message_type" = "deademote", "message_content" = text)) if(!query_insert.Execute()) log_debug("Error during logging: "+query_insert.ErrorMsg()) @@ -244,7 +244,7 @@ establish_db_connection() if(!SSdbcore.IsConnected()) return null - var/DBQuery/query_insert = SSdbcore.NewQuery("INSERT INTO erro_dialog (mid, time, ckey, mob, type, message) VALUES (null, NOW(), :sender_ckey, :sender_mob, :message_type, :message_content)", \ + var/datum/db_query/query_insert = SSdbcore.NewQuery("INSERT INTO erro_dialog (mid, time, ckey, mob, type, message) VALUES (null, NOW(), :sender_ckey, :sender_mob, :message_type, :message_content)", \ list("sender_ckey" = speaker.ckey, "sender_mob" = speaker.real_name, "message_type" = "pda", "message_content" = text)) if(!query_insert.Execute()) log_debug("Error during logging: "+query_insert.ErrorMsg()) diff --git a/code/_helpers/logging_vr.dm b/code/_helpers/logging_vr.dm index 7771b49482..4b984f5171 100644 --- a/code/_helpers/logging_vr.dm +++ b/code/_helpers/logging_vr.dm @@ -7,7 +7,7 @@ establish_db_connection() if(!SSdbcore.IsConnected()) return null - var/DBQuery/query_insert = SSdbcore.NewQuery("INSERT INTO erro_dialog (mid, time, ckey, mob, type, message) VALUES (null, NOW(), :sender_ckey, :sender_mob, :message_type, :message_content)", \ + var/datum/db_query/query_insert = SSdbcore.NewQuery("INSERT INTO erro_dialog (mid, time, ckey, mob, type, message) VALUES (null, NOW(), :sender_ckey, :sender_mob, :message_type, :message_content)", \ list("sender_ckey" = speaker.ckey, "sender_mob" = speaker.real_name, "message_type" = "nsay", "message_content" = text)) if(!query_insert.Execute()) log_debug("Error during logging: "+query_insert.ErrorMsg()) @@ -25,7 +25,7 @@ establish_db_connection() if(!SSdbcore.IsConnected()) return null - var/DBQuery/query_insert = SSdbcore.NewQuery("INSERT INTO erro_dialog (mid, time, ckey, mob, type, message) VALUES (null, NOW(), :sender_ckey, :sender_mob, :message_type, :message_content)", \ + var/datum/db_query/query_insert = SSdbcore.NewQuery("INSERT INTO erro_dialog (mid, time, ckey, mob, type, message) VALUES (null, NOW(), :sender_ckey, :sender_mob, :message_type, :message_content)", \ list("sender_ckey" = speaker.ckey, "sender_mob" = speaker.real_name, "message_type" = "nme", "message_content" = text)) if(!query_insert.Execute()) log_debug("Error during logging: "+query_insert.ErrorMsg()) @@ -43,7 +43,7 @@ establish_db_connection() if(!SSdbcore.IsConnected()) return null - var/DBQuery/query_insert = SSdbcore.NewQuery("INSERT INTO erro_dialog (mid, time, ckey, mob, type, message) VALUES (null, NOW(), :sender_ckey, :sender_mob, :message_type, :message_content)", \ + var/datum/db_query/query_insert = SSdbcore.NewQuery("INSERT INTO erro_dialog (mid, time, ckey, mob, type, message) VALUES (null, NOW(), :sender_ckey, :sender_mob, :message_type, :message_content)", \ list("sender_ckey" = speaker.ckey, "sender_mob" = speaker.real_name, "message_type" = "subtle", "message_content" = text)) if(!query_insert.Execute()) log_debug("Error during logging: "+query_insert.ErrorMsg()) diff --git a/code/_helpers/mobs.dm b/code/_helpers/mobs.dm index a0f7189634..42359c6e7a 100644 --- a/code/_helpers/mobs.dm +++ b/code/_helpers/mobs.dm @@ -123,17 +123,19 @@ Proc for attack log creation, because really why not if(ismob(user)) //CHOMPEdit Begin if(SSdbcore.Connect()) user.attack_log += text("\[[time_stamp()]\] [span_red("Attacked [target_str]: [what_done]")]") - var/DBQuery/query_insert = SSdbcore.NewQuery("INSERT INTO erro_attacklog (id, time, ckey, mob, message) VALUES (null, NOW(), :t_ckey, :t_mob, :t_content)", list("t_ckey" = user.ckey, "t_mob" = user.real_name, "t_content" = "Attacked [target_str]: [what_done]")) - query_insert.Execute(async=use_async) - qdel(query_insert) + var/datum/db_query/query_insert = SSdbcore.NewQuery("INSERT INTO erro_attacklog (id, time, ckey, mob, message) VALUES (null, NOW(), :t_ckey, :t_mob, :t_content)", list("t_ckey" = user.ckey, "t_mob" = user.real_name, "t_content" = "Attacked [target_str]: [what_done]")) + spawn() //Change this to a spawn so it doesn't hold us up + query_insert.Execute(async=use_async) + qdel(query_insert) //if(SSdbcore.Connect()) // rustg_sql_query_async(SSdbcore.connection, "INSERT INTO erro_attacklog (id, time, ckey, mob, message) VALUES (null, NOW(), :t_ckey, :t_mob, :t_content)", json_encode(list("t_ckey" = user.ckey, "t_mob" = user.real_name, "t_content" = "Attacked [target_str]: [what_done]"))) if(ismob(target)) if(SSdbcore.Connect()) target.attack_log += text("\[[time_stamp()]\] [span_orange("Attacked by [user_str]: [what_done]")]") - var/DBQuery/query_insert = SSdbcore.NewQuery("INSERT INTO erro_attacklog (id, time, ckey, mob, message) VALUES (null, NOW(), :t_ckey, :t_mob, :t_content)", list("t_ckey" = target.ckey, "t_mob" = target.real_name, "t_content" = "Attacked by [user_str]: [what_done]")) - query_insert.Execute(async=use_async) - qdel(query_insert) + var/datum/db_query/query_insert = SSdbcore.NewQuery("INSERT INTO erro_attacklog (id, time, ckey, mob, message) VALUES (null, NOW(), :t_ckey, :t_mob, :t_content)", list("t_ckey" = target.ckey, "t_mob" = target.real_name, "t_content" = "Attacked by [user_str]: [what_done]")) + spawn() //Change this to a spawn so it doesn't hold us up + query_insert.Execute(async=use_async) + qdel(query_insert) //if(SSdbcore.Connect()) // rustg_sql_query_async(SSdbcore.connection, "INSERT INTO erro_attacklog (id, time, ckey, mob, message) VALUES (null, NOW(), :t_ckey, :t_mob, :t_content)", json_encode(list("t_ckey" = target.ckey, "t_mob" = target.real_name, "t_content" = "Attacked by [user_str]: [what_done]"))) //CHOMPEdit End diff --git a/code/controllers/subsystems/dbcore.dm b/code/controllers/subsystems/dbcore.dm index c649211a4d..77b1187043 100644 --- a/code/controllers/subsystems/dbcore.dm +++ b/code/controllers/subsystems/dbcore.dm @@ -1,8 +1,9 @@ SUBSYSTEM_DEF(dbcore) name = "Database" - flags = SS_BACKGROUND - wait = 1 MINUTES + flags = SS_TICKER + wait = 10 // Not seconds because we're running on SS_TICKER init_order = INIT_ORDER_DBCORE + var/failed_connection_timeout = 0 var/schema_mismatch = 0 @@ -11,38 +12,183 @@ SUBSYSTEM_DEF(dbcore) var/failed_connections = 0 var/last_error - var/list/active_queries = list() + + var/max_concurrent_queries = 25 + + /// Number of all queries, reset to 0 when logged in SStime_track. Used by SStime_track + var/all_queries_num = 0 + /// Number of active queries, reset to 0 when logged in SStime_track. Used by SStime_track + var/queries_active_num = 0 + /// Number of standby queries, reset to 0 when logged in SStime_track. Used by SStime_track + var/queries_standby_num = 0 + + /// All the current queries that exist. + var/list/all_queries = list() + /// Queries being checked for timeouts. + var/list/processing_queries + + /// Queries currently being handled by database driver + var/list/datum/db_query/queries_active = list() + /// Queries pending execution, mapped to complete arguments + var/list/datum/db_query/queries_standby = list() + + /// We are in the process of shutting down and should not allow more DB connections + var/shutting_down = FALSE + var/connection // Arbitrary handle returned from rust_g. + //var/db_daemon_started = FALSE + /datum/controller/subsystem/dbcore/Initialize() return ..() -/datum/controller/subsystem/dbcore/fire() - for(var/I in active_queries) - var/DBQuery/Q = I - if(world.time - Q.last_activity_time > (5 MINUTES)) - message_admins("Found undeleted query, please check the server logs and notify coders.") - log_debug("Undeleted query: \"[Q.sql]\" LA: [Q.last_activity] LAT: [Q.last_activity_time]") - qdel(Q) +/datum/controller/subsystem/dbcore/stat_entry(msg) + msg = "P:[length(all_queries)]|Active:[length(queries_active)]|Standby:[length(queries_standby)]" + return ..() + +/// Resets the tracking numbers on the subsystem. Used by SStime_track. +/datum/controller/subsystem/dbcore/proc/reset_tracking() + all_queries_num = 0 + queries_active_num = 0 + queries_standby_num = 0 + +/datum/controller/subsystem/dbcore/fire(resumed = FALSE) + if(!IsConnected()) + return + + if(!resumed) + if(!length(queries_active) && !length(queries_standby) && !length(all_queries)) + processing_queries = null + return + processing_queries = all_queries.Copy() + + // First handle the already running queries + for (var/datum/db_query/query in queries_active) + if(!process_query(query)) + queries_active -= query + + // Now lets pull in standby queries if we have room. + if (length(queries_standby) > 0 && length(queries_active) < max_concurrent_queries) + var/list/queries_to_activate = queries_standby.Copy(1, min(length(queries_standby), max_concurrent_queries) + 1) + + for (var/datum/db_query/query in queries_to_activate) + queries_standby.Remove(query) + create_active_query(query) + + // And finally, let check queries for undeleted queries, check ticking if there is a lot of work to do. + while(length(processing_queries)) + var/datum/db_query/query = popleft(processing_queries) + if(world.time - query.last_activity_time > (5 MINUTES)) + stack_trace("Found undeleted query, check the sql.log for the undeleted query and add a delete call to the query datum.") + log_debug("Undeleted query: \"[query.sql]\" LA: [query.last_activity] LAT: [query.last_activity_time]") + qdel(query) if(MC_TICK_CHECK) return + +/// Helper proc for handling activating queued queries +/datum/controller/subsystem/dbcore/proc/create_active_query(datum/db_query/query) + PRIVATE_PROC(TRUE) + SHOULD_NOT_SLEEP(TRUE) + if(IsAdminAdvancedProcCall()) + return FALSE + run_query(query) + queries_active_num++ + queries_active += query + return query + +/datum/controller/subsystem/dbcore/proc/process_query(datum/db_query/query) + PRIVATE_PROC(TRUE) + SHOULD_NOT_SLEEP(TRUE) + if(IsAdminAdvancedProcCall()) + return FALSE + if(QDELETED(query)) + return FALSE + if(query.process((TICKS2DS(wait)) / 10)) + queries_active -= query + return FALSE + return TRUE + +/datum/controller/subsystem/dbcore/proc/run_query_sync(datum/db_query/query) + if(IsAdminAdvancedProcCall()) + return + run_query(query) + UNTIL(query.process()) + return query + +/datum/controller/subsystem/dbcore/proc/run_query(datum/db_query/query) + if(IsAdminAdvancedProcCall()) + return + query.job_id = rustg_sql_query_async(connection, query.sql, json_encode(query.arguments)) + +/datum/controller/subsystem/dbcore/proc/queue_query(datum/db_query/query) + if(IsAdminAdvancedProcCall()) + return + + if (!length(queries_standby) && length(queries_active) < max_concurrent_queries) + create_active_query(query) + return + + queries_standby_num++ + queries_standby |= query + /datum/controller/subsystem/dbcore/Recover() connection = SSdbcore.connection /datum/controller/subsystem/dbcore/Shutdown() + shutting_down = TRUE + log_debug("Clearing DB queries standby:[length(queries_standby)] active: [length(queries_active)] all: [length(all_queries)]") //This is as close as we can get to the true round end before Disconnect() without changing where it's called, defeating the reason this is a subsystem + if(SSdbcore.Connect()) + //Execute all waiting queries + for(var/datum/db_query/query in queries_standby) + run_query_sync(query) + queries_standby -= query + for(var/datum/db_query/query in queries_active) + //Finish any remaining active qeries + UNTIL(query.process()) + queries_active -= query + + /*var/datum/db_query/query_round_shutdown = SSdbcore.NewQuery( + "UPDATE [format_table_name("round")] SET shutdown_datetime = Now(), end_state = :end_state WHERE id = :round_id", + list("end_state" = SSticker.end_state, "round_id" = GLOB.round_id), + TRUE + )*/ + //query_round_shutdown.Execute(FALSE) + //qdel(query_round_shutdown) + + log_debug("Done clearing DB queries standby:[length(queries_standby)] active: [length(queries_active)] all: [length(all_queries)]") if(IsConnected()) Disconnect() + //stop_db_daemon() //nu /datum/controller/subsystem/dbcore/can_vv_get(var_name) - return var_name != NAMEOF(src, connection) && var_name != NAMEOF(src, active_queries) && ..() + if(var_name == NAMEOF(src, connection)) + return FALSE + if(var_name == NAMEOF(src, all_queries)) + return FALSE + if(var_name == NAMEOF(src, queries_active)) + return FALSE + if(var_name == NAMEOF(src, queries_standby)) + return FALSE + if(var_name == NAMEOF(src, processing_queries)) + return FALSE + + return ..() /datum/controller/subsystem/dbcore/vv_edit_var(var_name, var_value) if(var_name == NAMEOF(src, connection)) return FALSE + if(var_name == NAMEOF(src, all_queries)) + return FALSE + if(var_name == NAMEOF(src, queries_active)) + return FALSE + if(var_name == NAMEOF(src, queries_standby)) + return FALSE + if(var_name == NAMEOF(src, processing_queries)) + return FALSE return ..() /datum/controller/subsystem/dbcore/proc/Connect() @@ -52,13 +198,15 @@ SUBSYSTEM_DEF(dbcore) if(failed_connection_timeout <= world.time) //it's been more than 5 seconds since we failed to connect, reset the counter failed_connections = 0 - if(failed_connections > 5) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to connect for 5 seconds. + if(failed_connections > 5) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to connect for 5 seconds. failed_connection_timeout = world.time + 50 return FALSE if(!config.sql_enabled) return FALSE + //start_db_daemon() + var/user = sqlfdbklogin var/pass = sqlfdbkpass var/db = sqlfdbkdb @@ -83,7 +231,7 @@ SUBSYSTEM_DEF(dbcore) else connection = null last_error = result["data"] - log_world("Connect() failed | [last_error]") + log_debug("Connect() failed | [last_error]") ++failed_connections /datum/controller/subsystem/dbcore/proc/CheckSchemaVersion() @@ -98,19 +246,19 @@ SUBSYSTEM_DEF(dbcore) /datum/controller/subsystem/dbcore/proc/InitializeRound() if(!Connect()) return - var/DBQuery/query_round_initialize = SSdbcore.NewQuery( + var/datum/db_query/query_round_initialize = SSdbcore.NewQuery( //"INSERT INTO [format_table_name("round")] (initialize_datetime, server_ip, server_port) VALUES (Now(), INET_ATON(:internet_address), :port)", "INSERT INTO round (initialize_datetime, server_ip, server_port) VALUES (Now(), INET_ATON(:internet_address), :port)", list("internet_address" = world.internet_address || "0", "port" = "[world.port]") ) query_round_initialize.Execute(async = FALSE) - GLOB.round_id = "[query_round_initialize.last_insert_id]" + //GLOB.round_id = "[query_round_initialize.last_insert_id]" qdel(query_round_initialize) /datum/controller/subsystem/dbcore/proc/SetRoundStart() if(!Connect()) return - var/DBQuery/query_round_start = SSdbcore.NewQuery( + var/datum/db_query/query_round_start = SSdbcore.NewQuery( //"UPDATE [format_table_name("round")] SET start_datetime = Now() WHERE id = :round_id", "UPDATE round SET start_datetime = Now() WHERE id = :round_id", list("round_id" = GLOB.round_id) @@ -121,7 +269,7 @@ SUBSYSTEM_DEF(dbcore) /datum/controller/subsystem/dbcore/proc/SetRoundEnd() if(!Connect()) return - var/DBQuery/query_round_end = SSdbcore.NewQuery( + var/datum/db_query/query_round_end = SSdbcore.NewQuery( //"UPDATE [format_table_name("round")] SET end_datetime = Now(), game_mode_result = :game_mode_result, station_name = :station_name WHERE id = :round_id", "UPDATE round SET end_datetime = Now(), game_mode_result = :game_mode_result, station_name = :station_name WHERE id = :round_id", //list("game_mode_result" = SSticker.mode_result, "station_name" = station_name(), "round_id" = GLOB.round_id) @@ -151,28 +299,45 @@ SUBSYSTEM_DEF(dbcore) /datum/controller/subsystem/dbcore/proc/ReportError(error) last_error = error -/datum/controller/subsystem/dbcore/proc/NewQuery(sql_query, arguments) +/datum/controller/subsystem/dbcore/proc/NewQuery(sql_query, arguments, allow_during_shutdown=FALSE) + //If the subsystem is shutting down, disallow new queries + if(!allow_during_shutdown && shutting_down) + CRASH("Attempting to create a new db query during the world shutdown") + if(IsAdminAdvancedProcCall()) + log_admin("ERROR: Advanced admin proc call led to sql query: [sql_query]. Query has been blocked") message_admins("ERROR: Advanced admin proc call led to sql query. Query has been blocked") return FALSE - return new /DBQuery(connection, sql_query, arguments) + return new /datum/db_query(connection, sql_query, arguments) -/datum/controller/subsystem/dbcore/proc/QuerySelect(list/querys, warn = FALSE, qdel = FALSE) - if (!islist(querys)) - if (!istype(querys, /DBQuery)) - CRASH("Invalid query passed to QuerySelect: [querys]") - querys = list(querys) +/** QuerySelect + Run a list of query datums in parallel, blocking until they all complete. + * queries - List of queries or single query datum to run. + * warn - Controls rather warn_execute() or Execute() is called. + * qdel - If you don't care about the result or checking for errors, you can have the queries be deleted afterwards. + This can be combined with invoke_async as a way of running queries async without having to care about waiting for them to finish so they can be deleted. +*/ +/datum/controller/subsystem/dbcore/proc/QuerySelect(list/queries, warn = FALSE, qdel = FALSE) + if (!islist(queries)) + if (!istype(queries, /datum/db_query)) + CRASH("Invalid query passed to QuerySelect: [queries]") + queries = list(queries) + else + queries = queries.Copy() //we don't want to hide bugs in the parent caller by removing invalid values from this list. + + for (var/datum/db_query/query as anything in queries) + if (!istype(query)) + queries -= query + stack_trace("Invalid query passed to QuerySelect: `[query]` [REF(query)]") + continue - for (var/thing in querys) - var/DBQuery/query = thing if (warn) - INVOKE_ASYNC(query, /DBQuery.proc/warn_execute) + INVOKE_ASYNC(query, TYPE_PROC_REF(/datum/db_query, warn_execute)) else - INVOKE_ASYNC(query, /DBQuery.proc/Execute) + INVOKE_ASYNC(query, TYPE_PROC_REF(/datum/db_query, Execute)) - for (var/thing in querys) - var/DBQuery/query = thing - UNTIL(!query.in_progress) + for (var/datum/db_query/query as anything in queries) + query.sync() if (qdel) qdel(query) @@ -186,11 +351,8 @@ The duplicate_key arg can be true to automatically generate this part of the que or set to a string that is appended to the end of the query Ignore_errors instructes mysql to continue inserting rows if some of them have errors. the erroneous row(s) aren't inserted and there isn't really any way to know why or why errored -Delayed insert mode was removed in mysql 7 and only works with MyISAM type tables, - It was included because it is still supported in mariadb. - It does not work with duplicate_key and the mysql server ignores it in those cases */ -/datum/controller/subsystem/dbcore/proc/MassInsert(table, list/rows, duplicate_key = FALSE, ignore_errors = FALSE, delayed = FALSE, warn = FALSE, async = TRUE, special_columns = null) +/datum/controller/subsystem/dbcore/proc/MassInsert(table, list/rows, duplicate_key = FALSE, ignore_errors = FALSE, warn = FALSE, async = TRUE, special_columns = null) if (!table || !rows || !istype(rows)) return @@ -207,8 +369,6 @@ Delayed insert mode was removed in mysql 7 and only works with MyISAM type table // Prepare SQL query full of placeholders var/list/query_parts = list("INSERT") - if (delayed) - query_parts += " DELAYED" if (ignore_errors) query_parts += " IGNORE" query_parts += " INTO " @@ -243,21 +403,70 @@ Delayed insert mode was removed in mysql 7 and only works with MyISAM type table else if (duplicate_key != FALSE) query_parts += duplicate_key - var/DBQuery/Query = NewQuery(query_parts.Join(), arguments) + var/datum/db_query/Query = NewQuery(query_parts.Join(), arguments) if (warn) . = Query.warn_execute(async) else . = Query.Execute(async) qdel(Query) -/DBQuery +/* +/datum/controller/subsystem/dbcore/proc/start_db_daemon() + set waitfor = FALSE + + if (db_daemon_started) + return + + db_daemon_started = TRUE + + var/daemon = CONFIG_GET(string/db_daemon) + if (!daemon) + return + + ASSERT(fexists(daemon)) + + var/list/result = world.shelleo("echo \"Starting ezdb daemon, do not close this window\" && [daemon]") + var/result_code = result[1] + if (!result_code || result_code == 1) + return + + stack_trace("Failed to start DB daemon: [result_code]\n[result[3]]") + +/datum/controller/subsystem/dbcore/proc/stop_db_daemon() + set waitfor = FALSE + + if (!db_daemon_started) + return + + db_daemon_started = FALSE + + var/daemon = CONFIG_GET(string/db_daemon) + if (!daemon) + return + + switch (world.system_type) + if (MS_WINDOWS) + var/list/result = world.shelleo("Get-Process | ? { $_.Path -eq '[daemon]' } | Stop-Process") + ASSERT(result[1]) + if (UNIX) + var/list/result = world.shelleo("kill $(pgrep -f '[daemon]')") + ASSERT(result[1]) +*/ + +/datum/db_query // Inputs var/connection var/sql var/arguments + var/datum/callback/success_callback + var/datum/callback/fail_callback + // Status information - var/in_progress + /// Current status of the query. + var/status + /// Job ID of the query passed by rustg. + var/job_id var/last_error var/last_activity var/last_activity_time @@ -270,8 +479,9 @@ Delayed insert mode was removed in mysql 7 and only works with MyISAM type table var/list/item //list of data values populated by NextRow() -/DBQuery/New(connection, sql, arguments) - SSdbcore.active_queries[src] = TRUE +/datum/db_query/New(connection, sql, arguments) + SSdbcore.all_queries += src + SSdbcore.all_queries_num++ Activity("Created") item = list() @@ -279,27 +489,29 @@ Delayed insert mode was removed in mysql 7 and only works with MyISAM type table src.sql = sql src.arguments = arguments -/DBQuery/Destroy() +/datum/db_query/Destroy() Close() - SSdbcore.active_queries -= src + SSdbcore.all_queries -= src + SSdbcore.queries_standby -= src + SSdbcore.queries_active -= src return ..() -/DBQuery/CanProcCall(proc_name) +/datum/db_query/CanProcCall(proc_name) //fuck off kevinz return FALSE -/DBQuery/proc/Activity(activity) +/datum/db_query/proc/Activity(activity) last_activity = activity last_activity_time = world.time -/DBQuery/proc/warn_execute(async = TRUE) +/datum/db_query/proc/warn_execute(async = TRUE) . = Execute(async) if(!.) - to_chat(usr, "A SQL error occurred during this operation, check the server logs.") + to_chat(usr, span_danger("A SQL error occurred during this operation, check the server logs.")) -/DBQuery/proc/Execute(async = TRUE, log_error = TRUE) +/datum/db_query/proc/Execute(async = TRUE, log_error = TRUE) Activity("Execute") - if(in_progress) + if(status == DB_QUERY_STARTED) CRASH("Attempted to start a new query while waiting on the old one") if(!SSdbcore.IsConnected()) @@ -310,7 +522,19 @@ Delayed insert mode was removed in mysql 7 and only works with MyISAM type table if(!async) start_time = REALTIMEOFDAY Close() - . = run_query(async) + status = DB_QUERY_STARTED + if(async) + /*if(!MC_RUNNING(SSdbcore.init_stage)) + SSdbcore.run_query_sync(src) + else + SSdbcore.queue_query(src)*/ + SSdbcore.run_query_sync(src) + sync() + else + var/job_result_str = rustg_sql_query_blocking(connection, sql, json_encode(arguments)) + store_data(json_decode(job_result_str)) + + . = (status != DB_QUERY_BROKEN) var/timed_out = !. && findtext(last_error, "Operation timed out") if(!. && log_error) log_debug("[last_error] | Query used: [sql] | Arguments: [json_encode(arguments)]") @@ -321,42 +545,45 @@ Delayed insert mode was removed in mysql 7 and only works with MyISAM type table log_debug("Query used: [sql]") slow_query_check() -/DBQuery/proc/run_query(async) - var/job_result_str +/// Sleeps until execution of the query has finished. +/datum/db_query/proc/sync() + while(status < DB_QUERY_FINISHED) + stoplag() - if (async) - var/job_id = rustg_sql_query_async(connection, sql, json_encode(arguments)) - in_progress = TRUE - UNTIL((job_result_str = rustg_sql_check_query(job_id)) != RUSTG_JOB_NO_RESULTS_YET) - in_progress = FALSE +/datum/db_query/process(seconds_per_tick) + if(status >= DB_QUERY_FINISHED) + return TRUE // we are done processing after all - if (job_result_str == RUSTG_JOB_ERROR) - last_error = job_result_str - return FALSE - else - job_result_str = rustg_sql_query_blocking(connection, sql, json_encode(arguments)) + status = DB_QUERY_STARTED + var/job_result = rustg_sql_check_query(job_id) + if(job_result == RUSTG_JOB_NO_RESULTS_YET) + return FALSE //no results yet - var/result = json_decode(job_result_str) - switch (result["status"]) - if ("ok") + store_data(json_decode(job_result)) + return TRUE + +/datum/db_query/proc/store_data(result) + switch(result["status"]) + if("ok") rows = result["rows"] affected = result["affected"] last_insert_id = result["last_insert_id"] - return TRUE - if ("err") + status = DB_QUERY_FINISHED + return + if("err") last_error = result["data"] - return FALSE - if ("offline") - last_error = "offline" - return FALSE + status = DB_QUERY_BROKEN + return + if("offline") + last_error = "CONNECTION OFFLINE" + status = DB_QUERY_BROKEN + return -/DBQuery/proc/RowCount() - return rows.len -/DBQuery/proc/slow_query_check() - message_admins("HEY! A database query timed out.") +/datum/db_query/proc/slow_query_check() + message_admins("HEY! A database query timed out. Did the server just hang? \[YES\]|\[NO\]") -/DBQuery/proc/NextRow(async = TRUE) +/datum/db_query/proc/NextRow(async = TRUE) Activity("NextRow") if (rows && next_row_to_take <= rows.len) @@ -366,9 +593,9 @@ Delayed insert mode was removed in mysql 7 and only works with MyISAM type table else return FALSE -/DBQuery/proc/ErrorMsg() +/datum/db_query/proc/ErrorMsg() return last_error -/DBQuery/proc/Close() +/datum/db_query/proc/Close() rows = null item = null diff --git a/code/controllers/subsystems/persist_vr.dm b/code/controllers/subsystems/persist_vr.dm index 0bde0599cb..5e3d2d1ab1 100644 --- a/code/controllers/subsystems/persist_vr.dm +++ b/code/controllers/subsystems/persist_vr.dm @@ -86,7 +86,7 @@ SUBSYSTEM_DEF(persist) var/sql_bal = text2num("[C.department_hours[department_earning]]") var/sql_total = text2num("[C.play_hours[department_earning]]") var/list/sqlargs = list("t_ckey" = sql_ckey, "t_department" = sql_dpt) //CHOMPEdit TGSQL - var/DBQuery/query = SSdbcore.NewQuery("INSERT INTO vr_player_hours (ckey, department, hours, total_hours) VALUES (:t_ckey, :t_department, [sql_bal], [sql_total]) ON DUPLICATE KEY UPDATE hours = VALUES(hours), total_hours = VALUES(total_hours)", sqlargs) //CHOMPEdit TGSQL + var/datum/db_query/query = SSdbcore.NewQuery("INSERT INTO vr_player_hours (ckey, department, hours, total_hours) VALUES (:t_ckey, :t_department, [sql_bal], [sql_total]) ON DUPLICATE KEY UPDATE hours = VALUES(hours), total_hours = VALUES(total_hours)", sqlargs) //CHOMPEdit TGSQL if(!query.Execute()) //CHOMPEdit log_admin(query.ErrorMsg()) //CHOMPEdit qdel(query) //CHOMPEdit TGSQL @@ -106,4 +106,4 @@ SUBSYSTEM_DEF(persist) // They have a custom title, aren't crew, or someone deleted their record, so we need a fallback method. // Let's check the mind. if(M.mind && M.mind.assigned_role) - . = job_master.GetJob(M.mind.assigned_role) \ No newline at end of file + . = job_master.GetJob(M.mind.assigned_role) diff --git a/code/defines/procs/dbcore.dm b/code/defines/procs/dbcore.dm index e0f8ab94af..51c9934c17 100644 --- a/code/defines/procs/dbcore.dm +++ b/code/defines/procs/dbcore.dm @@ -77,10 +77,10 @@ var/DB_PORT = 3306 // This is the port your MySQL server is running on (3306 is if(IsConnected()) Disconnect() //return Connect("[dbi?"[dbi]":"dbi:mysql:[database_name]:[DB_SERVER]:[DB_PORT]"]",user,password) return Connect("[dbi?"[dbi]":"dbi:mysql:[database_name]:[sqladdress]:[sqlport]"]",user,password) -/DBConnection/proc/NewQuery(sql_query,cursor_handler=src.default_cursor) return new/DBQuery(sql_query,src,cursor_handler) +/DBConnection/proc/NewQuery(sql_query,cursor_handler=src.default_cursor) return new/datum/db_query(sql_query,src,cursor_handler) -/DBQuery/New(sql_query,DBConnection/connection_handler,cursor_handler) +/datum/db_query/New(sql_query,DBConnection/connection_handler,cursor_handler) if(sql_query) src.sql = sql_query if(connection_handler) src.db_connection = connection_handler if(cursor_handler) src.default_cursor = cursor_handler @@ -88,7 +88,7 @@ var/DB_PORT = 3306 // This is the port your MySQL server is running on (3306 is return ..() -/DBQuery +/datum/db_query var/sql // The sql query being executed. var/default_cursor var/list/columns //list of DB Columns populated by Columns() @@ -98,26 +98,26 @@ var/DB_PORT = 3306 // This is the port your MySQL server is running on (3306 is var/DBConnection/db_connection var/_db_query -/DBQuery/proc/Connect(DBConnection/connection_handler) src.db_connection = connection_handler +/datum/db_query/proc/Connect(DBConnection/connection_handler) src.db_connection = connection_handler -/DBQuery/proc/Execute(sql_query=src.sql,cursor_handler=default_cursor) +/datum/db_query/proc/Execute(sql_query=src.sql,cursor_handler=default_cursor) Close() return _dm_db_execute(_db_query,sql_query,db_connection._db_con,cursor_handler,null) -/DBQuery/proc/NextRow() return _dm_db_next_row(_db_query,item,conversions) +/datum/db_query/proc/NextRow() return _dm_db_next_row(_db_query,item,conversions) -/DBQuery/proc/RowsAffected() return _dm_db_rows_affected(_db_query) +/datum/db_query/proc/RowsAffected() return _dm_db_rows_affected(_db_query) -/DBQuery/proc/RowCount() return _dm_db_row_count(_db_query) +/datum/db_query/proc/RowCount() return _dm_db_row_count(_db_query) -/DBQuery/proc/ErrorMsg() return _dm_db_error_msg(_db_query) +/datum/db_query/proc/ErrorMsg() return _dm_db_error_msg(_db_query) -/DBQuery/proc/Columns() +/datum/db_query/proc/Columns() if(!columns) columns = _dm_db_columns(_db_query,/DBColumn) return columns -/DBQuery/proc/GetRowData() +/datum/db_query/proc/GetRowData() var/list/columns = Columns() var/list/results if(columns.len) @@ -128,16 +128,16 @@ var/DB_PORT = 3306 // This is the port your MySQL server is running on (3306 is results[C] = src.item[(cur_col.position+1)] return results -/DBQuery/proc/Close() +/datum/db_query/proc/Close() item.len = 0 columns = null conversions = null return _dm_db_close(_db_query) -/DBQuery/proc/Quote(str) +/datum/db_query/proc/Quote(str) return db_connection.Quote(str) -/DBQuery/proc/SetConversion(column,conversion) +/datum/db_query/proc/SetConversion(column,conversion) if(istext(column)) column = columns.Find(column) if(!conversions) conversions = new/list(column) else if(conversions.len < column) conversions.len = column diff --git a/code/defines/procs/statistics.dm b/code/defines/procs/statistics.dm index bc33fb2e5a..816e23df56 100644 --- a/code/defines/procs/statistics.dm +++ b/code/defines/procs/statistics.dm @@ -11,7 +11,7 @@ log_game("SQL ERROR during population polling. Failed to connect.") else var/sqltime = time2text(world.realtime, "YYYY-MM-DD hh:mm:ss") - var/DBQuery/query = SSdbcore.NewQuery("INSERT INTO `population` (`playercount`, `admincount`, `time`) VALUES ([playercount], [admincount], '[sqltime]')") //CHOMPEdit TGSQL + var/datum/db_query/query = SSdbcore.NewQuery("INSERT INTO `population` (`playercount`, `admincount`, `time`) VALUES ([playercount], [admincount], '[sqltime]')") //CHOMPEdit TGSQL if(!query.Execute()) var/err = query.ErrorMsg() log_game("SQL ERROR during population polling. Error : \[[err]\]\n") @@ -54,7 +54,7 @@ if(!SSdbcore.IsConnected()) //CHOMPEdit TGSQL log_game("SQL ERROR during death reporting. Failed to connect.") else - var/DBQuery/query = SSdbcore.NewQuery("INSERT INTO death (name, byondkey, job, special, pod, tod, laname, lakey, gender, bruteloss, fireloss, brainloss, oxyloss, coord) VALUES (:t_name, :t_byondkey, :t_job, :t_special, :t_pod, '[sqltime]', :t_laname, :t_lakey, '[H.gender]', [H.getBruteLoss()], [H.getFireLoss()], [H.brainloss], [H.getOxyLoss()], '[coord]')", list("t_name" = sqlname,"t_byondkey" = sqlkey, "t_job" = sqljob, "t_special" = sqlspecial, "t_pod" = sqlpod, "t_laname" = laname, "t_lakey" = lakey)) //CHOMPEdit TGSQL + var/datum/db_query/query = SSdbcore.NewQuery("INSERT INTO death (name, byondkey, job, special, pod, tod, laname, lakey, gender, bruteloss, fireloss, brainloss, oxyloss, coord) VALUES (:t_name, :t_byondkey, :t_job, :t_special, :t_pod, '[sqltime]', :t_laname, :t_lakey, '[H.gender]', [H.getBruteLoss()], [H.getFireLoss()], [H.brainloss], [H.getOxyLoss()], '[coord]')", list("t_name" = sqlname,"t_byondkey" = sqlkey, "t_job" = sqljob, "t_special" = sqlspecial, "t_pod" = sqlpod, "t_laname" = laname, "t_lakey" = lakey)) //CHOMPEdit TGSQL if(!query.Execute()) var/err = query.ErrorMsg() log_game("SQL ERROR during death reporting. Error : \[[err]\]\n") @@ -89,7 +89,7 @@ if(!SSdbcore.IsConnected()) //CHOMPEdit TGSQL log_game("SQL ERROR during death reporting. Failed to connect.") else - var/DBQuery/query = SSdbcore.NewQuery("INSERT INTO death (name, byondkey, job, special, pod, tod, laname, lakey, gender, bruteloss, fireloss, brainloss, oxyloss, coord) VALUES (:t_name, :t_byondkey, :t_job, :t_special, :t_pod, '[sqltime]', :t_laname, :t_lakey, '[H.gender]', [H.getBruteLoss()], [H.getFireLoss()], [H.brainloss], [H.getOxyLoss()], '[coord]')", list("t_name" = sqlname,"t_byondkey" = sqlkey, "t_job" = sqljob, "t_special" = sqlspecial, "t_pod" = sqlpod, "t_laname" = laname, "t_lakey" = lakey)) //CHOMPEdit TGSQL + var/datum/db_query/query = SSdbcore.NewQuery("INSERT INTO death (name, byondkey, job, special, pod, tod, laname, lakey, gender, bruteloss, fireloss, brainloss, oxyloss, coord) VALUES (:t_name, :t_byondkey, :t_job, :t_special, :t_pod, '[sqltime]', :t_laname, :t_lakey, '[H.gender]', [H.getBruteLoss()], [H.getFireLoss()], [H.brainloss], [H.getOxyLoss()], '[coord]')", list("t_name" = sqlname,"t_byondkey" = sqlkey, "t_job" = sqljob, "t_special" = sqlspecial, "t_pod" = sqlpod, "t_laname" = laname, "t_lakey" = lakey)) //CHOMPEdit TGSQL if(!query.Execute()) var/err = query.ErrorMsg() log_game("SQL ERROR during death reporting. Error : \[[err]\]\n") @@ -122,7 +122,7 @@ log_game("SQL ERROR during feedback reporting. Failed to connect.") else - var/DBQuery/max_query = SSdbcore.NewQuery("SELECT MAX(roundid) AS max_round_id FROM erro_feedback") //CHOMPEdit TGSQL + var/datum/db_query/max_query = SSdbcore.NewQuery("SELECT MAX(roundid) AS max_round_id FROM erro_feedback") //CHOMPEdit TGSQL max_query.Execute() var/newroundid @@ -142,7 +142,7 @@ var/variable = item.get_variable() var/value = item.get_value() - var/DBQuery/query = SSdbcore.NewQuery("INSERT INTO erro_feedback (id, roundid, time, variable, value) VALUES (null, [newroundid], Now(), '[variable]', '[value]')") //CHOMPEdit TGSQL + var/datum/db_query/query = SSdbcore.NewQuery("INSERT INTO erro_feedback (id, roundid, time, variable, value) VALUES (null, [newroundid], Now(), '[variable]', '[value]')") //CHOMPEdit TGSQL if(!query.Execute()) var/err = query.ErrorMsg() log_game("SQL ERROR during death reporting. Error : \[[err]\]\n") diff --git a/code/game/machinery/records_scanner.dm b/code/game/machinery/records_scanner.dm index b80548e876..f829237740 100644 --- a/code/game/machinery/records_scanner.dm +++ b/code/game/machinery/records_scanner.dm @@ -51,7 +51,7 @@ var/age = user.age var/gender = user.gender /* no dbstuff yet - var/DBQuery/cquery = dbcon.NewQuery("SELECT * from jobban WHERE ckey='[user.ckey]'") + var/datum/db_query/cquery = dbcon.NewQuery("SELECT * from jobban WHERE ckey='[user.ckey]'") if(!cquery.Execute()) return else while(cquery.NextRow()) diff --git a/code/game/magic/archived_book.dm b/code/game/magic/archived_book.dm index b0595ec589..5ef639fd67 100644 --- a/code/game/magic/archived_book.dm +++ b/code/game/magic/archived_book.dm @@ -57,7 +57,7 @@ var/global/datum/book_manager/book_mgr = new() if(!SSdbcore.IsConnected()) //CHOMP Edit dat += "ERROR: Unable to contact External Archive. Please contact your system administrator for assistance." else - var/DBQuery/query = dbcon.NewQuery("DELETE FROM library WHERE id=[isbn]") + var/datum/db_query/query = dbcon.NewQuery("DELETE FROM library WHERE id=[isbn]") if(!query.Execute()) to_chat(usr,query.ErrorMsg()) dbcon.Disconnect() diff --git a/code/game/world.dm b/code/game/world.dm index dd3b60958a..148a8eae3d 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -553,7 +553,7 @@ var/world_topic_spam_protect_time = world.timeofday log_misc("Failed to connect to database in load_mentors().") return - var/DBQuery/query = SSdbcore.NewQuery("SELECT ckey, mentor FROM erro_mentor") //CHOMPEdit TGSQL + var/datum/db_query/query = SSdbcore.NewQuery("SELECT ckey, mentor FROM erro_mentor") //CHOMPEdit TGSQL query.Execute() while(query.NextRow()) var/ckey = query.item[1] @@ -632,7 +632,7 @@ var/failed_old_db_connections = 0 to_world_log("SQL connection disabled in config.") else if(establish_db_connection())//CHOMPEdit Begin to_world_log("Feedback database connection established.") - var/DBQuery/query_truncate = SSdbcore.NewQuery("TRUNCATE erro_dialog") + var/datum/db_query/query_truncate = SSdbcore.NewQuery("TRUNCATE erro_dialog") var/num_tries = 0 while(!query_truncate.Execute() && num_tries<5) num_tries++ @@ -640,7 +640,7 @@ var/failed_old_db_connections = 0 if(num_tries==5) log_admin("ERROR TRYING TO CLEAR erro_dialog") qdel(query_truncate) - var/DBQuery/query_truncate2 = SSdbcore.NewQuery("TRUNCATE erro_attacklog") + var/datum/db_query/query_truncate2 = SSdbcore.NewQuery("TRUNCATE erro_attacklog") num_tries = 0 while(!query_truncate2.Execute() && num_tries<5) num_tries++ @@ -671,7 +671,7 @@ var/failed_old_db_connections = 0 if ( . ) failed_db_connections = 0 //If this connection succeeded, reset the failed connections counter. //CHOMPEdit Begin - var/DBQuery/query_truncate = dbcon.NewQuery("TRUNCATE erro_dialog") + var/datum/db_query/query_truncate = dbcon.NewQuery("TRUNCATE erro_dialog") var/num_tries = 0 while(!query_truncate.Execute() && num_tries<5) num_tries++ diff --git a/code/modules/DMAPI/yawnDMAPI.dm b/code/modules/DMAPI/yawnDMAPI.dm index 8f2c5b7605..d8048799b5 100644 --- a/code/modules/DMAPI/yawnDMAPI.dm +++ b/code/modules/DMAPI/yawnDMAPI.dm @@ -85,17 +85,17 @@ GLOBAL_LIST_EMPTY(pending_discord_registrations) /datum/server_tools_command/register/Run(datum/tgs_chat_user/sender, params) // Try to find if that ID is registered to someone already var/sql_discord = sql_sanitize_text(sender.id) - var/DBQuery/query = dbcon.NewQuery("SELECT discord_id FROM erro_player WHERE discord_id = '[sql_discord]'") + var/datum/db_query/query = dbcon.NewQuery("SELECT discord_id FROM erro_player WHERE discord_id = '[sql_discord]'") query.Execute() if(query.NextRow()) return "[sender.friendly_name], your Discord ID is already registered to a Byond username. Please contact an administrator if you changed your Byond username or Discord ID." - + var/key_to_find = "[ckey(params)]" // They didn't provide anything worth looking up. if(!length(key_to_find)) return "[sender.friendly_name], you need to provide your Byond username at the end of the command. It can be in 'key' format (with spaces and characters) or 'ckey' format (without spaces or special characters)." - + // Try to find their client. var/client/user for(var/client/C in GLOB.clients) @@ -106,7 +106,7 @@ GLOBAL_LIST_EMPTY(pending_discord_registrations) // Couldn't find them logged in. if(!user) return "[sender.friendly_name], I couldn't find a logged-in user with the username of '[key_to_find]', which is what you provided after conversion to Byond's ckey format. Please connect to the game server and try again." - + var/sql_ckey = sql_sanitize_text(key_to_find) query = dbcon.NewQuery("SELECT discord_id FROM erro_player WHERE ckey = '[sql_ckey]'") query.Execute() @@ -114,11 +114,11 @@ GLOBAL_LIST_EMPTY(pending_discord_registrations) // We somehow found their client, BUT they don't exist in the database if(!query.NextRow()) return "[sender.friendly_name], the server's database is either not responding or there's no evidence you've ever logged in. Please contact an administrator." - + // We found them in the database, AND they already have a discord ID assigned if(query.item[1]) return "[sender.friendly_name], it appears you've already registered your chat and game IDs. If you've changed game or chat usernames, please contact an administrator for help." - + // Okay. We found them, they're in the DB, and they have no discord ID set. var/message = "A request has been sent from Discord to validate your Byond username, by '[sender.friendly_name]' in '[sender.channel.friendly_name]'\
If you did not send this request, do not click the link below, and do notify an administrator in-game or on Discord ASAP.\ @@ -128,6 +128,6 @@ GLOBAL_LIST_EMPTY(pending_discord_registrations) // To stifle href hacking GLOB.pending_discord_registrations.len++ GLOB.pending_discord_registrations[GLOB.pending_discord_registrations.len] = list("ckey" = key_to_find, "id" = sender.id, "time" = world.realtime) - + return "[sender.friendly_name], I've sent you a message in-game. Please verify your username there to complete your registration within 10 minutes." -*/ \ No newline at end of file +*/ diff --git a/code/modules/admin/DB ban/functions.dm b/code/modules/admin/DB ban/functions.dm index 01c7480170..4269b27b85 100644 --- a/code/modules/admin/DB ban/functions.dm +++ b/code/modules/admin/DB ban/functions.dm @@ -44,7 +44,7 @@ computerid = bancid ip = banip - var/DBQuery/query = SSdbcore.NewQuery("SELECT id FROM erro_player WHERE ckey = :t_ckey", list("t_ckey",ckey)) //CHOMPEdit TGSQL + var/datum/db_query/query = SSdbcore.NewQuery("SELECT id FROM erro_player WHERE ckey = :t_ckey", list("t_ckey",ckey)) //CHOMPEdit TGSQL query.Execute() var/validckey = 0 if(query.NextRow()) @@ -83,7 +83,7 @@ var/list/sqlargs = list("t_bantype" = bantype_str, "t_reason" = reason, "t_job" = job, "t_ckey" = ckey, "t_a_ckey" = a_ckey, "t_who" = who, "t_adminwho" = adminwho) //CHOMPEdit TGSQL var/sql = "INSERT INTO erro_ban (`id`,`bantime`,`serverip`,`bantype`,`reason`,`job`,`duration`,`rounds`,`expiration_time`,`ckey`,`computerid`,`ip`,`a_ckey`,`a_computerid`,`a_ip`,`who`,`adminwho`,`edits`,`unbanned`,`unbanned_datetime`,`unbanned_ckey`,`unbanned_computerid`,`unbanned_ip`) VALUES (null, Now(), '[serverip]', :t_bantype, :t_reason, :t_job, [(duration)?"[duration]":"0"], [(rounds)?"[rounds]":"0"], Now() + INTERVAL [(duration>0) ? duration : 0] MINUTE, :t_ckey, '[computerid]', '[ip]', :t_a_ckey, '[a_computerid]', '[a_ip]', :t_who, :t_adminwho, '', null, null, null, null, null)" //CHOMPEdit TGSQL - var/DBQuery/query_insert = SSdbcore.NewQuery(sql,sqlargs) //CHOMPEdit TGSQL + var/datum/db_query/query_insert = SSdbcore.NewQuery(sql,sqlargs) //CHOMPEdit TGSQL query_insert.Execute() to_chat(usr, "[span_blue("Ban saved to database.")]") message_admins("[key_name_admin(usr)] has added a [bantype_str] for [ckey] [(job)?"([job])":""] [(duration > 0)?"([duration] minutes)":""] with the reason: \"[reason]\" to the ban database.",1) @@ -132,7 +132,7 @@ var/ban_id var/ban_number = 0 //failsafe - var/DBQuery/query = SSdbcore.NewQuery(sql, list("t_ckey" = ckey)) //CHOMPEdit TGSQL + var/datum/db_query/query = SSdbcore.NewQuery(sql, list("t_ckey" = ckey)) //CHOMPEdit TGSQL query.Execute() while(query.NextRow()) ban_id = query.item[1] @@ -162,7 +162,7 @@ to_chat(usr, "Cancelled") return - var/DBQuery/query = SSdbcore.NewQuery("SELECT ckey, duration, reason FROM erro_ban WHERE id = [banid]") //CHOMPEdit TGSQL + var/datum/db_query/query = SSdbcore.NewQuery("SELECT ckey, duration, reason FROM erro_ban WHERE id = [banid]") //CHOMPEdit TGSQL query.Execute() var/eckey = usr.ckey //Editing admin ckey @@ -190,7 +190,7 @@ to_chat(usr, "Cancelled") return var/list/sqlargs = list("t_reason" = value, "t_edits" = "- [eckey] changed ban reason from \\\"[reason]\\\" to \\\"[value]\\\"
") //CHOMPEdit TGSQL - var/DBQuery/update_query = SSdbcore.NewQuery("UPDATE erro_ban SET reason = '[value]', edits = CONCAT(edits,:t_edits) WHERE id = [banid]", sqlargs) //CHOMPEdit TGSQL + var/datum/db_query/update_query = SSdbcore.NewQuery("UPDATE erro_ban SET reason = '[value]', edits = CONCAT(edits,:t_edits) WHERE id = [banid]", sqlargs) //CHOMPEdit TGSQL update_query.Execute() message_admins("[key_name_admin(usr)] has edited a ban for [pckey]'s reason from [reason] to [value]",1) qdel(update_query) //CHOMPEdit TGSQL @@ -201,7 +201,7 @@ to_chat(usr, "Cancelled") return var/list/sqlargs = list("t_edits" = "- [eckey] changed ban duration from [duration] to [value]
") //CHOMPEdit TGSQL - var/DBQuery/update_query = SSdbcore.NewQuery("UPDATE erro_ban SET duration = [value], edits = CONCAT(edits,:t_edits), expiration_time = DATE_ADD(bantime, INTERVAL [value] MINUTE) WHERE id = [banid]",sqlargs) //CHOMPEdit TGSQL + var/datum/db_query/update_query = SSdbcore.NewQuery("UPDATE erro_ban SET duration = [value], edits = CONCAT(edits,:t_edits), expiration_time = DATE_ADD(bantime, INTERVAL [value] MINUTE) WHERE id = [banid]",sqlargs) //CHOMPEdit TGSQL message_admins("[key_name_admin(usr)] has edited a ban for [pckey]'s duration from [duration] to [value]",1) update_query.Execute() qdel(update_query) //CHOMPEdit TGSQL @@ -225,7 +225,7 @@ var/ban_number = 0 //failsafe var/pckey - var/DBQuery/query = SSdbcore.NewQuery(sql) //CHOMPEdit TGSQL + var/datum/db_query/query = SSdbcore.NewQuery(sql) //CHOMPEdit TGSQL query.Execute() while(query.NextRow()) pckey = query.item[1] @@ -249,7 +249,7 @@ var/sql_update = "UPDATE erro_ban SET unbanned = 1, unbanned_datetime = Now(), unbanned_ckey = :t_ckey, unbanned_computerid = '[unban_computerid]', unbanned_ip = '[unban_ip]' WHERE id = [id]" //CHOMPEdit TGSQL message_admins("[key_name_admin(usr)] has lifted [pckey]'s ban.",1) - var/DBQuery/query_update = SSdbcore.NewQuery(sql_update,sqlargs) //CHOMPEdit TGSQL + var/datum/db_query/query_update = SSdbcore.NewQuery(sql_update,sqlargs) //CHOMPEdit TGSQL query_update.Execute() qdel(query_update) //CHOMPEdit TGSQL @@ -407,7 +407,7 @@ else bantypesearch += "'PERMABAN' " - var/DBQuery/select_query = SSdbcore.NewQuery("SELECT id, bantime, bantype, reason, job, duration, expiration_time, ckey, a_ckey, unbanned, unbanned_ckey, unbanned_datetime, edits, ip, computerid FROM erro_ban WHERE 1 [playersearch] [adminsearch] [ipsearch] [cidsearch] [bantypesearch] ORDER BY bantime DESC LIMIT 100", sqlargs) //CHOMPEdit TGSQL + var/datum/db_query/select_query = SSdbcore.NewQuery("SELECT id, bantime, bantype, reason, job, duration, expiration_time, ckey, a_ckey, unbanned, unbanned_ckey, unbanned_datetime, edits, ip, computerid FROM erro_ban WHERE 1 [playersearch] [adminsearch] [ipsearch] [cidsearch] [bantypesearch] ORDER BY bantime DESC LIMIT 100", sqlargs) //CHOMPEdit TGSQL select_query.Execute() var/now = time2text(world.realtime, "YYYY-MM-DD hh:mm:ss") // MUST BE the same format as SQL gives us the dates in, and MUST be least to most specific (i.e. year, month, day not day, month, year) diff --git a/code/modules/admin/IsBanned.dm b/code/modules/admin/IsBanned.dm index 9325152a23..002352d68e 100644 --- a/code/modules/admin/IsBanned.dm +++ b/code/modules/admin/IsBanned.dm @@ -56,7 +56,7 @@ log_misc("Key [ckeytext] cid not checked. Non-Numeric: [computer_id]") failedcid = 1 - var/DBQuery/query = SSdbcore.NewQuery("SELECT ckey, ip, computerid, a_ckey, reason, expiration_time, duration, bantime, bantype FROM erro_ban WHERE (ckey = :t_ckey [ipquery] [cidquery]) AND (bantype = 'PERMABAN' OR (bantype = 'TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned)", list("t_ckey" = ckeytext)) //CHOMPEdit TGSQL + var/datum/db_query/query = SSdbcore.NewQuery("SELECT ckey, ip, computerid, a_ckey, reason, expiration_time, duration, bantime, bantype FROM erro_ban WHERE (ckey = :t_ckey [ipquery] [cidquery]) AND (bantype = 'PERMABAN' OR (bantype = 'TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned)", list("t_ckey" = ckeytext)) //CHOMPEdit TGSQL query.Execute() diff --git a/code/modules/admin/admin_ranks.dm b/code/modules/admin/admin_ranks.dm index 3f40e5ebe5..235b546950 100644 --- a/code/modules/admin/admin_ranks.dm +++ b/code/modules/admin/admin_ranks.dm @@ -69,7 +69,7 @@ var/list/admin_ranks = list() //list of all ranks with associated rights C.holder = null GLOB.admins.Cut() load_admin_ranks() //CHOMP Edit: moved this from "f(config.admin_legacy_system)" and put it here instead, literally just moved it 3 lines. - + if(config.admin_legacy_system) //Clear profile access for(var/A in world.GetConfig("admin")) @@ -119,7 +119,7 @@ var/list/admin_ranks = list() //list of all ranks with associated rights load_admins() return - var/DBQuery/query = SSdbcore.NewQuery("SELECT ckey, rank, level, flags FROM erro_admin") //CHOMPEdit TGSQL + var/datum/db_query/query = SSdbcore.NewQuery("SELECT ckey, rank, level, flags FROM erro_admin") //CHOMPEdit TGSQL query.Execute() while(query.NextRow()) var/ckey = query.item[1] diff --git a/code/modules/admin/admin_tools.dm b/code/modules/admin/admin_tools.dm index 2d9ae2cdd4..4d4ecc4019 100644 --- a/code/modules/admin/admin_tools.dm +++ b/code/modules/admin/admin_tools.dm @@ -12,7 +12,7 @@ //CHOMPEdit Begin /*for(var/d in M.dialogue_log) dat += "[d]
"*/ - var/DBQuery/query = SSdbcore.NewQuery("SELECT id,time,ckey,mob,message from erro_attacklog WHERE ckey = :t_ckey", list("t_ckey" = M.ckey)) + var/datum/db_query/query = SSdbcore.NewQuery("SELECT id,time,ckey,mob,message from erro_attacklog WHERE ckey = :t_ckey", list("t_ckey" = M.ckey)) if(!query.Execute()) dat += "Database query error" else @@ -49,11 +49,11 @@ dat += "Current Antag?: [(M.mind.special_role)?"Yes":"No"]
" dat += "
Note: This is arranged from earliest to latest.

" - + //CHOMPEdit Begin /*for(var/d in M.dialogue_log) dat += "[d]
"*/ - var/DBQuery/query = SSdbcore.NewQuery("SELECT mid,time,ckey,mob,type,message from erro_dialog WHERE ckey = :t_ckey", list("t_ckey" = M.ckey)) + var/datum/db_query/query = SSdbcore.NewQuery("SELECT mid,time,ckey,mob,type,message from erro_dialog WHERE ckey = :t_ckey", list("t_ckey" = M.ckey)) if(!query.Execute()) dat += "Database query error" else @@ -76,4 +76,4 @@ onclose(usr, "admin_dialogue_log") - feedback_add_details("admin_verb","PDL") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! \ No newline at end of file + feedback_add_details("admin_verb","PDL") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! diff --git a/code/modules/admin/admin_verbs_vr.dm b/code/modules/admin/admin_verbs_vr.dm index 0cd74ca1b5..f6db65d671 100644 --- a/code/modules/admin/admin_verbs_vr.dm +++ b/code/modules/admin/admin_verbs_vr.dm @@ -103,7 +103,7 @@ dat += {"(Order book by SS13BN)

TITLE Now()") //CHOMPEdit TGSQL + var/datum/db_query/query1 = SSdbcore.NewQuery("SELECT ckey, job FROM erro_ban WHERE bantype = 'JOB_TEMPBAN' AND isnull(unbanned) AND expiration_time > Now()") //CHOMPEdit TGSQL query1.Execute() while(query1.NextRow()) diff --git a/code/modules/admin/permissionverbs/permissionedit.dm b/code/modules/admin/permissionverbs/permissionedit.dm index 1cb0480395..a1c67f2af7 100644 --- a/code/modules/admin/permissionverbs/permissionedit.dm +++ b/code/modules/admin/permissionverbs/permissionedit.dm @@ -71,7 +71,7 @@ if(!istext(adm_ckey) || !istext(new_rank)) return - var/DBQuery/select_query = SSdbcore.NewQuery("SELECT id FROM erro_admin WHERE ckey = '[adm_ckey]'") //CHOMPEdit TGSQL + var/datum/db_query/select_query = SSdbcore.NewQuery("SELECT id FROM erro_admin WHERE ckey = '[adm_ckey]'") //CHOMPEdit TGSQL select_query.Execute() var/new_admin = 1 @@ -81,19 +81,19 @@ admin_id = text2num(select_query.item[1]) qdel(select_query) //CHOMPEdit TGSQL if(new_admin) - var/DBQuery/insert_query = SSdbcore.NewQuery("INSERT INTO `erro_admin` (`id`, `ckey`, `rank`, `level`, `flags`) VALUES (null, '[adm_ckey]', '[new_rank]', -1, 0)") //CHOMPEdit TGSQL + var/datum/db_query/insert_query = SSdbcore.NewQuery("INSERT INTO `erro_admin` (`id`, `ckey`, `rank`, `level`, `flags`) VALUES (null, '[adm_ckey]', '[new_rank]', -1, 0)") //CHOMPEdit TGSQL insert_query.Execute() qdel(insert_query) //CHOMPEdit TGSQL - var/DBQuery/log_query = SSdbcore.NewQuery("INSERT INTO `test`.`erro_admin_log` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Added new admin [adm_ckey] to rank [new_rank]');") //CHOMPEdit TGSQL + var/datum/db_query/log_query = SSdbcore.NewQuery("INSERT INTO `test`.`erro_admin_log` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Added new admin [adm_ckey] to rank [new_rank]');") //CHOMPEdit TGSQL log_query.Execute() qdel(log_query) //CHOMPEdit TGSQL to_chat(usr, "[span_blue("New admin added.")]") else if(!isnull(admin_id) && isnum(admin_id)) - var/DBQuery/insert_query = SSdbcore.NewQuery("UPDATE `erro_admin` SET rank = '[new_rank]' WHERE id = [admin_id]") //CHOMPEdit TGSQL + var/datum/db_query/insert_query = SSdbcore.NewQuery("UPDATE `erro_admin` SET rank = '[new_rank]' WHERE id = [admin_id]") //CHOMPEdit TGSQL insert_query.Execute() qdel(insert_query) //CHOMPEdit TGSQL - var/DBQuery/log_query = SSdbcore.NewQuery("INSERT INTO `test`.`erro_admin_log` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Edited the rank of [adm_ckey] to [new_rank]');") //CHOMPEdit TGSQL + var/datum/db_query/log_query = SSdbcore.NewQuery("INSERT INTO `test`.`erro_admin_log` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Edited the rank of [adm_ckey] to [new_rank]');") //CHOMPEdit TGSQL log_query.Execute() qdel(log_query) //CHOMPEdit TGSQL to_chat(usr, "[span_blue("Admin rank changed.")]") @@ -127,7 +127,7 @@ if(!istext(adm_ckey) || !isnum(new_permission)) return - var/DBQuery/select_query = SSdbcore.NewQuery("SELECT id, flags FROM erro_admin WHERE ckey = '[adm_ckey]'") //CHOMPEdit TGSQL + var/datum/db_query/select_query = SSdbcore.NewQuery("SELECT id, flags FROM erro_admin WHERE ckey = '[adm_ckey]'") //CHOMPEdit TGSQL select_query.Execute() var/admin_id @@ -140,18 +140,18 @@ return if(admin_rights & new_permission) //This admin already has this permission, so we are removing it. - var/DBQuery/insert_query = SSdbcore.NewQuery("UPDATE `erro_admin` SET flags = [admin_rights & ~new_permission] WHERE id = [admin_id]") //CHOMPEdit TGSQL + var/datum/db_query/insert_query = SSdbcore.NewQuery("UPDATE `erro_admin` SET flags = [admin_rights & ~new_permission] WHERE id = [admin_id]") //CHOMPEdit TGSQL insert_query.Execute() qdel(insert_query) //CHOMPEdit TGSQL - var/DBQuery/log_query = SSdbcore.NewQuery("INSERT INTO `test`.`erro_admin_log` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Removed permission [rights2text(new_permission)] (flag = [new_permission]) to admin [adm_ckey]');") //CHOMPEdit TGSQL + var/datum/db_query/log_query = SSdbcore.NewQuery("INSERT INTO `test`.`erro_admin_log` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Removed permission [rights2text(new_permission)] (flag = [new_permission]) to admin [adm_ckey]');") //CHOMPEdit TGSQL log_query.Execute() qdel(log_query) //CHOMPEdit TGSQL to_chat(usr, "[span_blue("Permission removed.")]") else //This admin doesn't have this permission, so we are adding it. - var/DBQuery/insert_query = SSdbcore.NewQuery("UPDATE `erro_admin` SET flags = '[admin_rights | new_permission]' WHERE id = [admin_id]") //CHOMPEdit TGSQL + var/datum/db_query/insert_query = SSdbcore.NewQuery("UPDATE `erro_admin` SET flags = '[admin_rights | new_permission]' WHERE id = [admin_id]") //CHOMPEdit TGSQL insert_query.Execute() qdel(insert_query) //CHOMPEdit TGSQL - var/DBQuery/log_query = SSdbcore.NewQuery("INSERT INTO `test`.`erro_admin_log` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Added permission [rights2text(new_permission)] (flag = [new_permission]) to admin [adm_ckey]')") //CHOMPEdit TGSQL + var/datum/db_query/log_query = SSdbcore.NewQuery("INSERT INTO `test`.`erro_admin_log` (`id` ,`datetime` ,`adminckey` ,`adminip` ,`log` ) VALUES (NULL , NOW( ) , '[usr.ckey]', '[usr.client.address]', 'Added permission [rights2text(new_permission)] (flag = [new_permission]) to admin [adm_ckey]')") //CHOMPEdit TGSQL log_query.Execute() qdel(log_query) //CHOMPEdit TGSQL to_chat(usr, "[span_blue("Permission added.")]") diff --git a/code/modules/admin/verbs/check_customitem_activity.dm b/code/modules/admin/verbs/check_customitem_activity.dm index 521a49301d..ce068fdea6 100644 --- a/code/modules/admin/verbs/check_customitem_activity.dm +++ b/code/modules/admin/verbs/check_customitem_activity.dm @@ -55,7 +55,7 @@ var/inactive_keys = "None
" //run a query to get all ckeys inactive for over 2 months var/list/inactive_ckeys = list() if(ckeys_with_customitems.len) - var/DBQuery/query_inactive = SSdbcore.NewQuery("SELECT ckey, lastseen FROM erro_player WHERE datediff(Now(), lastseen) > 60") //CHOMPEdit TGSQL + var/datum/db_query/query_inactive = SSdbcore.NewQuery("SELECT ckey, lastseen FROM erro_player WHERE datediff(Now(), lastseen) > 60") //CHOMPEdit TGSQL query_inactive.Execute() while(query_inactive.NextRow()) var/cur_ckey = query_inactive.item[1] @@ -68,9 +68,9 @@ var/inactive_keys = "None
" //if there are ckeys left over, check whether they have a database entry at all if(ckeys_with_customitems.len) for(var/cur_ckey in ckeys_with_customitems) - var/DBQuery/query_inactive = SSdbcore.NewQuery("SELECT ckey FROM erro_player WHERE ckey = :t_ckey", list("t_ckey" = cur_ckey)) //CHOMPEdit TGSQL + var/datum/db_query/query_inactive = SSdbcore.NewQuery("SELECT ckey FROM erro_player WHERE ckey = :t_ckey", list("t_ckey" = cur_ckey)) //CHOMPEdit TGSQL query_inactive.Execute() - if(!query_inactive.RowCount()) + if(!length(query_inactive.rows)) //CHOMPEdit TGSQL inactive_ckeys += cur_ckey qdel(query_inactive) //CHOMPEdit TGSQL if(inactive_ckeys.len) diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index 81e2e61ef6..3005933258 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -101,7 +101,7 @@ var/sql_discord = sql_sanitize_text(their_id) var/sql_ckey = sql_sanitize_text(ckey) - var/DBQuery/query = SSdbcore.NewQuery("UPDATE erro_player SET discord_id = :t_discord_id WHERE ckey = :t_ckey", list("t_discord_id" = sql_discord, "t_ckey" = sql_ckey)) //CHOMPEdit TGSQL + var/datum/db_query/query = SSdbcore.NewQuery("UPDATE erro_player SET discord_id = :t_discord_id WHERE ckey = :t_ckey", list("t_discord_id" = sql_discord, "t_ckey" = sql_ckey)) //CHOMPEdit TGSQL if(query.Execute()) to_chat(src, "Registration complete! Thank you for taking the time to register your Discord ID.") log_and_message_admins("[ckey] has registered their Discord ID. Their Discord snowflake ID is: [their_id]") //YW EDIT @@ -299,7 +299,7 @@ var/sql_ckey = sql_sanitize_text(ckey(key)) - var/DBQuery/query = SSdbcore.NewQuery("SELECT datediff(Now(),firstseen) as age FROM erro_player WHERE ckey = :t_ckey", list("t_ckey" = sql_ckey)) //CHOMPEdit TGSQL + var/datum/db_query/query = SSdbcore.NewQuery("SELECT datediff(Now(),firstseen) as age FROM erro_player WHERE ckey = :t_ckey", list("t_ckey" = sql_ckey)) //CHOMPEdit TGSQL query.Execute() //CHOMPEdit Begin if(query.NextRow()) @@ -323,7 +323,7 @@ var/sql_ckey = sql_sanitize_text(src.ckey) - var/DBQuery/query = SSdbcore.NewQuery("SELECT id, datediff(Now(),firstseen) as age FROM erro_player WHERE ckey = :t_ckey", list("t_ckey" = sql_ckey)) //CHOMPEdit TGSQL + var/datum/db_query/query = SSdbcore.NewQuery("SELECT id, datediff(Now(),firstseen) as age FROM erro_player WHERE ckey = :t_ckey", list("t_ckey" = sql_ckey)) //CHOMPEdit TGSQL query.Execute() var/sql_id = 0 player_age = 0 // New players won't have an entry so knowing we have a connection we set this to zero to be updated if their is a record. @@ -334,19 +334,19 @@ qdel(query) //CHOMPEdit TGSQL account_join_date = sanitizeSQL(findJoinDate()) if(account_join_date && SSdbcore.IsConnected()) //CHOMPEdit TGSQL - var/DBQuery/query_datediff = SSdbcore.NewQuery("SELECT DATEDIFF(Now(),'[account_join_date]')") //CHOMPEdit TGSQL + var/datum/db_query/query_datediff = SSdbcore.NewQuery("SELECT DATEDIFF(Now(),'[account_join_date]')") //CHOMPEdit TGSQL if(query_datediff.Execute() && query_datediff.NextRow()) account_age = text2num(query_datediff.item[1]) qdel(query_datediff) //CHOMPEdit TGSQL - var/DBQuery/query_ip = SSdbcore.NewQuery("SELECT ckey FROM erro_player WHERE ip = '[address]'") //CHOMPEdit TGSQL + var/datum/db_query/query_ip = SSdbcore.NewQuery("SELECT ckey FROM erro_player WHERE ip = '[address]'") //CHOMPEdit TGSQL query_ip.Execute() related_accounts_ip = "" while(query_ip.NextRow()) related_accounts_ip += "[query_ip.item[1]], " break qdel(query_ip) //CHOMPEdit TGSQL - var/DBQuery/query_cid = SSdbcore.NewQuery("SELECT ckey FROM erro_player WHERE computerid = '[computer_id]'") //CHOMPEdit TGSQL + var/datum/db_query/query_cid = SSdbcore.NewQuery("SELECT ckey FROM erro_player WHERE computerid = '[computer_id]'") //CHOMPEdit TGSQL query_cid.Execute() related_accounts_cid = "" while(query_cid.NextRow()) @@ -401,7 +401,7 @@ log_admin("Couldn't perform IP check on [key] with [address]") // VOREStation Edit Start - Department Hours - var/DBQuery/query_hours = SSdbcore.NewQuery("SELECT department, hours, total_hours FROM vr_player_hours WHERE ckey = :t_ckey", list("t_ckey" = sql_ckey)) //CHOMPEdit TGSQL + var/datum/db_query/query_hours = SSdbcore.NewQuery("SELECT department, hours, total_hours FROM vr_player_hours WHERE ckey = :t_ckey", list("t_ckey" = sql_ckey)) //CHOMPEdit TGSQL if(query_hours.Execute()) while(query_hours.NextRow()) department_hours[query_hours.item[1]] = text2num(query_hours.item[2]) @@ -414,18 +414,18 @@ qdel(query_hours) //CHOMPEdit TGSQL if(sql_id) //Player already identified previously, we need to just update the 'lastseen', 'ip' and 'computer_id' variables - var/DBQuery/query_update = SSdbcore.NewQuery("UPDATE erro_player SET lastseen = Now(), ip = '[sql_ip]', computerid = '[sql_computerid]', lastadminrank = '[sql_admin_rank]' WHERE id = [sql_id]") //CHOMPEdit TGSQL + var/datum/db_query/query_update = SSdbcore.NewQuery("UPDATE erro_player SET lastseen = Now(), ip = '[sql_ip]', computerid = '[sql_computerid]', lastadminrank = '[sql_admin_rank]' WHERE id = [sql_id]") //CHOMPEdit TGSQL query_update.Execute() qdel(query_update) //CHOMPEdit TGSQL else //New player!! Need to insert all the stuff - var/DBQuery/query_insert = SSdbcore.NewQuery("INSERT INTO erro_player (id, ckey, firstseen, lastseen, ip, computerid, lastadminrank) VALUES (null, :t_ckey, Now(), Now(), '[sql_ip]', '[sql_computerid]', '[sql_admin_rank]')", list("t_ckey" = sql_ckey)) //CHOMPEdit TGSQL + var/datum/db_query/query_insert = SSdbcore.NewQuery("INSERT INTO erro_player (id, ckey, firstseen, lastseen, ip, computerid, lastadminrank) VALUES (null, :t_ckey, Now(), Now(), '[sql_ip]', '[sql_computerid]', '[sql_admin_rank]')", list("t_ckey" = sql_ckey)) //CHOMPEdit TGSQL query_insert.Execute() qdel(query_insert) //CHOMPEdit TGSQL //Logging player access var/serverip = "[world.internet_address]:[world.port]" - var/DBQuery/query_accesslog = SSdbcore.NewQuery("INSERT INTO `erro_connection_log`(`id`,`datetime`,`serverip`,`ckey`,`ip`,`computerid`) VALUES(null,Now(),'[serverip]',:t_ckey,'[sql_ip]','[sql_computerid]');", list("t_ckey" = sql_ckey)) //CHOMPEdit TGSQL + var/datum/db_query/query_accesslog = SSdbcore.NewQuery("INSERT INTO `erro_connection_log`(`id`,`datetime`,`serverip`,`ckey`,`ip`,`computerid`) VALUES(null,Now(),'[serverip]',:t_ckey,'[sql_ip]','[sql_computerid]');", list("t_ckey" = sql_ckey)) //CHOMPEdit TGSQL query_accesslog.Execute() qdel(query_accesslog) //CHOMPEdit TGSQL diff --git a/code/modules/library/lib_machines.dm b/code/modules/library/lib_machines.dm index 5e24efbcf2..f7ce9d5877 100644 --- a/code/modules/library/lib_machines.dm +++ b/code/modules/library/lib_machines.dm @@ -53,7 +53,7 @@ dat += {""} - var/DBQuery/query = SSdbcore.NewQuery(SQLquery, SQLargs) //CHOMPEdit TGSQL + var/datum/db_query/query = SSdbcore.NewQuery(SQLquery, SQLargs) //CHOMPEdit TGSQL query.Execute() while(query.NextRow()) @@ -291,7 +291,7 @@ dat += {"(Order book by SS13BN)

AUTHORTITLECATEGORYSS13BN
TITLE(Order book by SS13BN)

TITLEThank you for your vote!") qdel(query_insert) diff --git a/code/modules/mob/new_player/poll.dm b/code/modules/mob/new_player/poll.dm index 7e8bc4cf2c..1729031b8a 100644 --- a/code/modules/mob/new_player/poll.dm +++ b/code/modules/mob/new_player/poll.dm @@ -5,7 +5,7 @@ return var/voted = 0 - var/DBQuery/query = SSdbcore.NewQuery("SELECT * FROM erro_privacy WHERE ckey=:t_ckey", list("t_ckey" = src.ckey)) //CHOMPEdit TGSQL + var/datum/db_query/query = SSdbcore.NewQuery("SELECT * FROM erro_privacy WHERE ckey=:t_ckey", list("t_ckey" = src.ckey)) //CHOMPEdit TGSQL query.Execute() while(query.NextRow()) voted = 1 @@ -53,7 +53,7 @@ if(src.client && src.client.holder) isadmin = 1 - var/DBQuery/select_query = SSdbcore.NewQuery("SELECT id, question FROM erro_poll_question WHERE [(isadmin ? "" : "adminonly = false AND")] Now() BETWEEN starttime AND endtime") //CHOMPEdit TGSQL + var/datum/db_query/select_query = SSdbcore.NewQuery("SELECT id, question FROM erro_poll_question WHERE [(isadmin ? "" : "adminonly = false AND")] Now() BETWEEN starttime AND endtime") //CHOMPEdit TGSQL select_query.Execute() var/output = "
Player polls" @@ -84,7 +84,7 @@ establish_db_connection() if(SSdbcore.IsConnected()) //CHOMPEdit TGSQL - var/DBQuery/select_query = SSdbcore.NewQuery("SELECT starttime, endtime, question, polltype, multiplechoiceoptions FROM erro_poll_question WHERE id = [pollid]") //CHOMPEdit TGSQL + var/datum/db_query/select_query = SSdbcore.NewQuery("SELECT starttime, endtime, question, polltype, multiplechoiceoptions FROM erro_poll_question WHERE id = [pollid]") //CHOMPEdit TGSQL select_query.Execute() var/pollstarttime = "" @@ -109,7 +109,7 @@ switch(polltype) //Polls that have enumerated options if("OPTION") - var/DBQuery/voted_query = SSdbcore.NewQuery("SELECT optionid FROM erro_poll_vote WHERE pollid = [pollid] AND ckey = :t_ckey", list("t_ckey" = usr.ckey)) //CHOMPEdit TGSQL + var/datum/db_query/voted_query = SSdbcore.NewQuery("SELECT optionid FROM erro_poll_vote WHERE pollid = [pollid] AND ckey = :t_ckey", list("t_ckey" = usr.ckey)) //CHOMPEdit TGSQL voted_query.Execute() var/voted = 0 @@ -121,7 +121,7 @@ qdel(voted_query) //CHOMPEdit TGSQL var/list/datum/polloption/options = list() - var/DBQuery/options_query = SSdbcore.NewQuery("SELECT id, text FROM erro_poll_option WHERE pollid = [pollid]") //CHOMPEdit TGSQL + var/datum/db_query/options_query = SSdbcore.NewQuery("SELECT id, text FROM erro_poll_option WHERE pollid = [pollid]") //CHOMPEdit TGSQL options_query.Execute() while(options_query.NextRow()) var/datum/polloption/PO = new() @@ -162,7 +162,7 @@ //Polls with a text input if("TEXT") - var/DBQuery/voted_query = SSdbcore.NewQuery("SELECT replytext FROM erro_poll_textreply WHERE pollid = [pollid] AND ckey = :t_ckey", list("t_ckey" = usr.ckey)) //CHOMPEdit TGSQL + var/datum/db_query/voted_query = SSdbcore.NewQuery("SELECT replytext FROM erro_poll_textreply WHERE pollid = [pollid] AND ckey = :t_ckey", list("t_ckey" = usr.ckey)) //CHOMPEdit TGSQL voted_query.Execute() var/voted = 0 @@ -204,7 +204,7 @@ //Polls with a text input if("NUMVAL") - var/DBQuery/voted_query = SSdbcore.NewQuery("SELECT o.text, v.rating FROM erro_poll_option o, erro_poll_vote v WHERE o.pollid = [pollid] AND v.ckey = :t_ckey AND o.id = v.optionid", list("t_ckey" = usr.ckey)) //CHOMPEdit TGSQL + var/datum/db_query/voted_query = SSdbcore.NewQuery("SELECT o.text, v.rating FROM erro_poll_option o, erro_poll_vote v WHERE o.pollid = [pollid] AND v.ckey = :t_ckey AND o.id = v.optionid", list("t_ckey" = usr.ckey)) //CHOMPEdit TGSQL voted_query.Execute() var/output = "
Player poll" @@ -230,7 +230,7 @@ var/minid = 999999 var/maxid = 0 - var/DBQuery/option_query = SSdbcore.NewQuery("SELECT id, text, minval, maxval, descmin, descmid, descmax FROM erro_poll_option WHERE pollid = [pollid]") //CHOMPEdit TGSQL + var/datum/db_query/option_query = SSdbcore.NewQuery("SELECT id, text, minval, maxval, descmin, descmid, descmax FROM erro_poll_option WHERE pollid = [pollid]") //CHOMPEdit TGSQL option_query.Execute() while(option_query.NextRow()) var/optionid = text2num(option_query.item[1]) @@ -273,7 +273,7 @@ src << browse(output,"window=playerpoll;size=500x500") if("MULTICHOICE") - var/DBQuery/voted_query = SSdbcore.NewQuery("SELECT optionid FROM erro_poll_vote WHERE pollid = [pollid] AND ckey = :t_ckey", list("t_ckey" = usr.ckey)) //CHOMPEdit TGSQL + var/datum/db_query/voted_query = SSdbcore.NewQuery("SELECT optionid FROM erro_poll_vote WHERE pollid = [pollid] AND ckey = :t_ckey", list("t_ckey" = usr.ckey)) //CHOMPEdit TGSQL voted_query.Execute() var/list/votedfor = list() @@ -286,7 +286,7 @@ var/maxoptionid = 0 var/minoptionid = 0 - var/DBQuery/options_query = SSdbcore.NewQuery("SELECT id, text FROM erro_poll_option WHERE pollid = [pollid]") //CHOMPEdit TGSQL + var/datum/db_query/options_query = SSdbcore.NewQuery("SELECT id, text FROM erro_poll_option WHERE pollid = [pollid]") //CHOMPEdit TGSQL options_query.Execute() while(options_query.NextRow()) var/datum/polloption/PO = new() @@ -345,7 +345,7 @@ establish_db_connection() if(SSdbcore.IsConnected()) //CHOMPEdit TGSQL - var/DBQuery/select_query = SSdbcore.NewQuery("SELECT starttime, endtime, question, polltype, multiplechoiceoptions FROM erro_poll_question WHERE id = [pollid] AND Now() BETWEEN starttime AND endtime") //CHOMPEdit TGSQL + var/datum/db_query/select_query = SSdbcore.NewQuery("SELECT starttime, endtime, question, polltype, multiplechoiceoptions FROM erro_poll_question WHERE id = [pollid] AND Now() BETWEEN starttime AND endtime") //CHOMPEdit TGSQL select_query.Execute() var/validpoll = 0 @@ -363,7 +363,7 @@ to_chat(usr, span_red("Poll is not valid.")) return - var/DBQuery/select_query2 = SSdbcore.NewQuery("SELECT id FROM erro_poll_option WHERE id = [optionid] AND pollid = [pollid]") //CHOMPEdit TGSQL + var/datum/db_query/select_query2 = SSdbcore.NewQuery("SELECT id FROM erro_poll_option WHERE id = [optionid] AND pollid = [pollid]") //CHOMPEdit TGSQL select_query2.Execute() var/validoption = 0 @@ -378,7 +378,7 @@ var/alreadyvoted = 0 - var/DBQuery/voted_query = SSdbcore.NewQuery("SELECT id FROM erro_poll_vote WHERE pollid = [pollid] AND ckey = :t_ckey", list("t_ckey" = usr.ckey)) //CHOMPEdit TGSQL + var/datum/db_query/voted_query = SSdbcore.NewQuery("SELECT id FROM erro_poll_vote WHERE pollid = [pollid] AND ckey = :t_ckey", list("t_ckey" = usr.ckey)) //CHOMPEdit TGSQL voted_query.Execute() while(voted_query.NextRow()) @@ -399,7 +399,7 @@ adminrank = usr.client.holder.rank - var/DBQuery/insert_query = SSdbcore.NewQuery("INSERT INTO erro_poll_vote (id ,datetime ,pollid ,optionid ,ckey ,ip ,adminrank) VALUES (null, Now(), [pollid], [optionid], :t_ckey, '[usr.client.address]', '[adminrank]')", list("t_ckey" = usr.ckey)) //CHOMPEdit TGSQL + var/datum/db_query/insert_query = SSdbcore.NewQuery("INSERT INTO erro_poll_vote (id ,datetime ,pollid ,optionid ,ckey ,ip ,adminrank) VALUES (null, Now(), [pollid], [optionid], :t_ckey, '[usr.client.address]', '[adminrank]')", list("t_ckey" = usr.ckey)) //CHOMPEdit TGSQL insert_query.Execute() to_chat(usr, span_blue("Vote successful.")) @@ -416,7 +416,7 @@ establish_db_connection() if(SSdbcore.IsConnected()) //CHOMPEdit TGSQL - var/DBQuery/select_query = SSdbcore.NewQuery("SELECT starttime, endtime, question, polltype FROM erro_poll_question WHERE id = [pollid] AND Now() BETWEEN starttime AND endtime") //CHOMPEdit TGSQL + var/datum/db_query/select_query = SSdbcore.NewQuery("SELECT starttime, endtime, question, polltype FROM erro_poll_question WHERE id = [pollid] AND Now() BETWEEN starttime AND endtime") //CHOMPEdit TGSQL select_query.Execute() var/validpoll = 0 @@ -433,7 +433,7 @@ var/alreadyvoted = 0 - var/DBQuery/voted_query = SSdbcore.NewQuery("SELECT id FROM erro_poll_textreply WHERE pollid = [pollid] AND ckey = :t_ckey", list("t_ckey" = usr.ckey)) //CHOMPEdit TGSQL + var/datum/db_query/voted_query = SSdbcore.NewQuery("SELECT id FROM erro_poll_textreply WHERE pollid = [pollid] AND ckey = :t_ckey", list("t_ckey" = usr.ckey)) //CHOMPEdit TGSQL voted_query.Execute() while(voted_query.NextRow()) @@ -458,7 +458,7 @@ to_chat(usr, "The text you entered was blank, contained illegal characters or was too long. Please correct the text and submit again.") return - var/DBQuery/insert_query = SSdbcore.NewQuery("INSERT INTO erro_poll_textreply (id ,datetime ,pollid ,ckey ,ip ,replytext ,adminrank) VALUES (null, Now(), [pollid], :t_ckey, '[usr.client.address]', :t_reply, '[adminrank]')", list("t_ckey" = usr.ckey, "t_reply" = replytext)) //CHOMPEdit TGSQL + var/datum/db_query/insert_query = SSdbcore.NewQuery("INSERT INTO erro_poll_textreply (id ,datetime ,pollid ,ckey ,ip ,replytext ,adminrank) VALUES (null, Now(), [pollid], :t_ckey, '[usr.client.address]', :t_reply, '[adminrank]')", list("t_ckey" = usr.ckey, "t_reply" = replytext)) //CHOMPEdit TGSQL insert_query.Execute() to_chat(usr, span_blue("Feedback logging successful.")) @@ -475,7 +475,7 @@ establish_db_connection() if(SSdbcore.IsConnected()) //CHOMPEdit TGSQL - var/DBQuery/select_query = SSdbcore.NewQuery("SELECT starttime, endtime, question, polltype FROM erro_poll_question WHERE id = [pollid] AND Now() BETWEEN starttime AND endtime") //CHOMPEdit TGSQL + var/datum/db_query/select_query = SSdbcore.NewQuery("SELECT starttime, endtime, question, polltype FROM erro_poll_question WHERE id = [pollid] AND Now() BETWEEN starttime AND endtime") //CHOMPEdit TGSQL select_query.Execute() var/validpoll = 0 @@ -490,7 +490,7 @@ to_chat(usr, span_red("Poll is not valid.")) return - var/DBQuery/select_query2 = SSdbcore.NewQuery("SELECT id FROM erro_poll_option WHERE id = [optionid] AND pollid = [pollid]") //CHOMPEdit TGSQL + var/datum/db_query/select_query2 = SSdbcore.NewQuery("SELECT id FROM erro_poll_option WHERE id = [optionid] AND pollid = [pollid]") //CHOMPEdit TGSQL select_query2.Execute() var/validoption = 0 @@ -505,7 +505,7 @@ var/alreadyvoted = 0 - var/DBQuery/voted_query = SSdbcore.NewQuery("SELECT id FROM erro_poll_vote WHERE optionid = [optionid] AND ckey = :t_ckey", list("t_ckey" = usr.ckey)) //CHOMPEdit TGSQL + var/datum/db_query/voted_query = SSdbcore.NewQuery("SELECT id FROM erro_poll_vote WHERE optionid = [optionid] AND ckey = :t_ckey", list("t_ckey" = usr.ckey)) //CHOMPEdit TGSQL voted_query.Execute() while(voted_query.NextRow()) @@ -521,7 +521,7 @@ adminrank = usr.client.holder.rank - var/DBQuery/insert_query = SSdbcore.NewQuery("INSERT INTO erro_poll_vote (id ,datetime ,pollid ,optionid ,ckey ,ip ,adminrank, rating) VALUES (null, Now(), [pollid], [optionid], '[usr.ckey]', '[usr.client.address]', '[adminrank]', :t_rating)", list("t_ckey" = usr.ckey, "t_rating" = rating)) //CHOMPEdit TGSQL + var/datum/db_query/insert_query = SSdbcore.NewQuery("INSERT INTO erro_poll_vote (id ,datetime ,pollid ,optionid ,ckey ,ip ,adminrank, rating) VALUES (null, Now(), [pollid], [optionid], '[usr.ckey]', '[usr.client.address]', '[adminrank]', :t_rating)", list("t_ckey" = usr.ckey, "t_rating" = rating)) //CHOMPEdit TGSQL insert_query.Execute() to_chat(usr, span_blue("Vote successful.")) diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 257b1b6352..606d4ac28f 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -706,7 +706,7 @@ //admin logs if(!no_attack_log) if(istype(firer, /mob) && istype(target_mob)) - add_attack_logs(firer,target_mob,"Shot with \a [src.type] projectile",use_async=FALSE) //CHOMPEdit + add_attack_logs(firer,target_mob,"Shot with \a [src.type] projectile") //CHOMPEdit //sometimes bullet_act() will want the projectile to continue flying if (result == PROJECTILE_CONTINUE) diff --git a/code/modules/research/message_server.dm b/code/modules/research/message_server.dm index c224319fb9..da2e38b00c 100644 --- a/code/modules/research/message_server.dm +++ b/code/modules/research/message_server.dm @@ -374,7 +374,7 @@ var/obj/machinery/blackbox_recorder/blackbox if(!SSdbcore.IsConnected()) return //CHOMPEdit TGSQL var/round_id - var/DBQuery/query = SSdbcore.NewQuery("SELECT MAX(round_id) AS round_id FROM erro_feedback") //CHOMPEdit TGSQL + var/datum/db_query/query = SSdbcore.NewQuery("SELECT MAX(round_id) AS round_id FROM erro_feedback") //CHOMPEdit TGSQL query.Execute() while(query.NextRow()) round_id = query.item[1] @@ -386,7 +386,7 @@ var/obj/machinery/blackbox_recorder/blackbox for(var/datum/feedback_variable/FV in feedback) var/list/sqlargs = list("t_roundid" = round_id, "t_variable" = "[FV.get_variable()]", "t_value" = "[FV.get_value()]", "t_details" = "[FV.get_details()]") //CHOMPEdit TGSQL var/sql = "INSERT INTO erro_feedback VALUES (null, Now(), :t_roundid, :t_variable, :t_value, :t_details)" //CHOMPEdit TGSQL - var/DBQuery/query_insert = SSdbcore.NewQuery(sql, sqlargs) //CHOMPEdit TGSQL + var/datum/db_query/query_insert = SSdbcore.NewQuery(sql, sqlargs) //CHOMPEdit TGSQL query_insert.Execute() qdel(query_insert) //CHOMPEdit TGSQL diff --git a/code/modules/tgs/LICENSE b/code/modules/tgs/LICENSE index 221f9e1deb..2bedf9a63a 100644 --- a/code/modules/tgs/LICENSE +++ b/code/modules/tgs/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2017 Jordan Brown +Copyright (c) 2017-2023 Jordan Brown Permission is hereby granted, free of charge, to any person obtaining a copy of this software and diff --git a/code/modules/tgs/README.md b/code/modules/tgs/README.md index 445cee41f5..6319028d81 100644 --- a/code/modules/tgs/README.md +++ b/code/modules/tgs/README.md @@ -7,7 +7,7 @@ This folder should be placed on it's own inside a codebase that wishes to use th - The other versioned folders contain code for the different DMAPI versions. - [v3210](./v3210) contains the final TGS3 API. - [v4](./v4) is the legacy DMAPI 4 (Used in TGS 4.0.X versions). - - [v5](./v5) is the current DMAPI version used by TGS4 >=4.1. + - [v5](./v5) is the current DMAPI version used by TGS >=4.1. - [LICENSE](./LICENSE) is the MIT license for the DMAPI. APIs communicate with TGS in two ways. All versions implement TGS -> DM communication using /world/Topic. DM -> TGS communication, called the bridge method, is different for each version. diff --git a/code/modules/tgs/core/README.md b/code/modules/tgs/core/README.md index aa3c7a9c9d..b82d8f49e2 100644 --- a/code/modules/tgs/core/README.md +++ b/code/modules/tgs/core/README.md @@ -5,4 +5,5 @@ This folder contains all DMAPI code not directly involved in an API. - [_definitions.dm](./definitions.dm) contains defines needed across DMAPI internals. - [core.dm](./core.dm) contains the implementations of the `/world/proc/TgsXXX()` procs. Many map directly to the `/datum/tgs_api` functions. It also contains the /datum selection and setup code. - [datum.dm](./datum.dm) contains the `/datum/tgs_api` declarations that all APIs must implement. -- [tgs_version.dm](./tgs_version.dm) contains the `/datum/tgs_version` definition \ No newline at end of file +- [tgs_version.dm](./tgs_version.dm) contains the `/datum/tgs_version` definition +- diff --git a/code/modules/tgs/core/_definitions.dm b/code/modules/tgs/core/_definitions.dm index ebf6d17c2a..fd98034eb7 100644 --- a/code/modules/tgs/core/_definitions.dm +++ b/code/modules/tgs/core/_definitions.dm @@ -1,2 +1,10 @@ +#if DM_VERSION < 510 +#error The TGS DMAPI does not support BYOND versions < 510! +#endif + #define TGS_UNIMPLEMENTED "___unimplemented" #define TGS_VERSION_PARAMETER "server_service_version" + +#ifndef TGS_DEBUG_LOG +#define TGS_DEBUG_LOG(message) +#endif diff --git a/code/modules/tgs/core/core.dm b/code/modules/tgs/core/core.dm index 41a0473394..8be96f2740 100644 --- a/code/modules/tgs/core/core.dm +++ b/code/modules/tgs/core/core.dm @@ -42,11 +42,11 @@ var/datum/tgs_version/max_api_version = TgsMaximumApiVersion(); if(version.suite != null && version.minor != null && version.patch != null && version.deprecated_patch != null && version.deprefixed_parameter > max_api_version.deprefixed_parameter) - TGS_ERROR_LOG("Detected unknown API version! Defaulting to latest. Update the DMAPI to fix this problem.") + TGS_ERROR_LOG("Detected unknown Interop API version! Defaulting to latest. Update the DMAPI to fix this problem.") api_datum = /datum/tgs_api/latest if(!api_datum) - TGS_ERROR_LOG("Found unsupported API version: [raw_parameter]. If this is a valid version please report this, backporting is done on demand.") + TGS_ERROR_LOG("Found unsupported Interop API version: [raw_parameter]. If this is a valid version please report this, backporting is done on demand.") return TGS_INFO_LOG("Activating API for version [version.deprefixed_parameter]") @@ -107,6 +107,13 @@ if(api) return api.ApiVersion() +/world/TgsEngine() +#ifdef OPENDREAM + return TGS_ENGINE_TYPE_OPENDREAM +#else + return TGS_ENGINE_TYPE_BYOND +#endif + /world/TgsInstanceName() var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) if(api) @@ -153,4 +160,9 @@ /world/TgsSecurityLevel() var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) if(api) - api.SecurityLevel() + return api.SecurityLevel() + +/world/TgsVisibility() + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + return api.Visibility() diff --git a/code/modules/tgs/core/datum.dm b/code/modules/tgs/core/datum.dm index 4d37ed662d..07ce3b6845 100644 --- a/code/modules/tgs/core/datum.dm +++ b/code/modules/tgs/core/datum.dm @@ -4,11 +4,22 @@ TGS_DEFINE_AND_SET_GLOBAL(tgs, null) var/datum/tgs_version/version var/datum/tgs_event_handler/event_handler + var/list/warned_deprecated_command_runs + /datum/tgs_api/New(datum/tgs_event_handler/event_handler, datum/tgs_version/version) . = ..() src.event_handler = event_handler src.version = version +/datum/tgs_api/proc/TerminateWorld() + while(TRUE) + TGS_DEBUG_LOG("About to terminate world. Tick: [world.time], sleep_offline: [world.sleep_offline]") + world.sleep_offline = FALSE // https://www.byond.com/forum/post/2894866 + del(world) + world.sleep_offline = FALSE // just in case, this is BYOND after all... + sleep(1) + TGS_DEBUG_LOG("BYOND DIDN'T TERMINATE THE WORLD!!! TICK IS: [world.time], sleep_offline: [world.sleep_offline]") + /datum/tgs_api/latest parent_type = /datum/tgs_api/v5 @@ -55,3 +66,6 @@ TGS_PROTECT_DATUM(/datum/tgs_api) /datum/tgs_api/proc/SecurityLevel() return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/Visibility() + return TGS_UNIMPLEMENTED diff --git a/code/modules/tgs/includes.dm b/code/modules/tgs/includes.dm index d884c67477..bc0eff6448 100644 --- a/code/modules/tgs/includes.dm +++ b/code/modules/tgs/includes.dm @@ -13,8 +13,12 @@ #include "v5\_defines.dm" #include "v5\api.dm" -#include "v5\api_vgs.dm" // VOREStation Edit - Include here so it has access to v5 defines +#include "v5\api_vgs.dm" //VOREStation Edit - Vgs +#include "v5\bridge.dm" +#include "v5\chunking.dm" #include "v5\commands.dm" -#include "v5\chat_commands.dm" -#include "v5\chat_commands_zz_ch.dm" +#include "v5\chat_commands.dm" //CHOMPEdit +#include "v5\chat_commands_zz_ch.dm" //CHOMPEdit +#include "v5\serializers.dm" +#include "v5\topic.dm" #include "v5\undefs.dm" diff --git a/code/modules/tgs/v3210/api.dm b/code/modules/tgs/v3210/api.dm index c0d2497db7..666201a322 100644 --- a/code/modules/tgs/v3210/api.dm +++ b/code/modules/tgs/v3210/api.dm @@ -28,6 +28,8 @@ #define SERVICE_RETURN_SUCCESS "SUCCESS" +#define TGS_FILE2LIST(filename) (splittext(trim_left(trim_right(file2text(filename))), "\n")) + /datum/tgs_api/v3210 var/reboot_mode = REBOOT_MODE_NORMAL var/comms_key @@ -53,9 +55,6 @@ return copytext(text, 1, i + 1) return "" -/datum/tgs_api/v3210/proc/file2list(filename) - return splittext(trim_left(trim_right(file2text(filename))), "\n") - /datum/tgs_api/v3210/OnWorldNew(minimum_required_security_level) . = FALSE @@ -64,13 +63,21 @@ if(!instance_name) instance_name = "TG Station Server" //maybe just upgraded - var/list/logs = file2list(".git/logs/HEAD") + var/list/logs = TGS_FILE2LIST(".git/logs/HEAD") if(logs.len) - logs = splittext(logs[logs.len - 1], " ") - commit = logs[2] - logs = file2list(".git/logs/refs/remotes/origin/master") + logs = splittext(logs[logs.len], " ") + if (logs.len >= 2) + commit = logs[2] + else + TGS_ERROR_LOG("Error parsing commit logs") + + logs = TGS_FILE2LIST(".git/logs/refs/remotes/origin/master") if(logs.len) - originmastercommit = splittext(logs[logs.len - 1], " ")[2] + logs = splittext(logs[logs.len], " ") + if (logs.len >= 2) + originmastercommit = logs[2] + else + TGS_ERROR_LOG("Error parsing origin commmit logs") if(world.system_type != MS_WINDOWS) TGS_ERROR_LOG("This API version is only supported on Windows. Not running on Windows. Aborting initialization!") @@ -92,7 +99,11 @@ if(skip_compat_check && !fexists(SERVICE_INTERFACE_DLL)) TGS_ERROR_LOG("Service parameter present but no interface DLL detected. This is symptomatic of running a service less than version 3.1! Please upgrade.") return - LIBCALL(SERVICE_INTERFACE_DLL, SERVICE_INTERFACE_FUNCTION)(instance_name, command) //trust no retval + #if DM_VERSION >= 515 + call_ext(SERVICE_INTERFACE_DLL, SERVICE_INTERFACE_FUNCTION)(instance_name, command) //trust no retval + #else + call(SERVICE_INTERFACE_DLL, SERVICE_INTERFACE_FUNCTION)(instance_name, command) //trust no retval + #endif return TRUE /datum/tgs_api/v3210/OnTopic(T) @@ -168,7 +179,7 @@ /datum/tgs_api/v3210/Revision() if(!warned_revison) var/datum/tgs_version/api_version = ApiVersion() - TGS_ERROR_LOG("Use of TgsRevision on [api_version.deprefixed_parameter] origin_commit only points to master!") + TGS_WARNING_LOG("Use of TgsRevision on [api_version.deprefixed_parameter] origin_commit only points to master!") warned_revison = TRUE var/datum/tgs_revision_information/ri = new ri.commit = commit @@ -182,16 +193,19 @@ /datum/tgs_api/v3210/ChatChannelInfo() return list() // :omegalul: -/datum/tgs_api/v3210/ChatBroadcast(message, list/channels) +/datum/tgs_api/v3210/ChatBroadcast(datum/tgs_message_content/message, list/channels) if(channels) return TGS_UNIMPLEMENTED + message = UpgradeDeprecatedChatMessage(message) ChatTargetedBroadcast(message, TRUE) ChatTargetedBroadcast(message, FALSE) -/datum/tgs_api/v3210/ChatTargetedBroadcast(message, admin_only) - ExportService("[admin_only ? SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE : SERVICE_REQUEST_IRC_BROADCAST] [message]") +/datum/tgs_api/v3210/ChatTargetedBroadcast(datum/tgs_message_content/message, admin_only) + message = UpgradeDeprecatedChatMessage(message) + ExportService("[admin_only ? SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE : SERVICE_REQUEST_IRC_BROADCAST] [message.text]") /datum/tgs_api/v3210/ChatPrivateMessage(message, datum/tgs_chat_user/user) + UpgradeDeprecatedChatMessage(message) return TGS_UNIMPLEMENTED /datum/tgs_api/v3210/SecurityLevel() @@ -226,3 +240,5 @@ #undef SERVICE_REQUEST_API_VERSION #undef SERVICE_RETURN_SUCCESS + +#undef TGS_FILE2LIST diff --git a/code/modules/tgs/v3210/commands.dm b/code/modules/tgs/v3210/commands.dm index 25b171bb52..e65c816320 100644 --- a/code/modules/tgs/v3210/commands.dm +++ b/code/modules/tgs/v3210/commands.dm @@ -8,11 +8,14 @@ var/list/command_name_types = list() var/list/warned_command_names = warnings_only ? list() : null var/warned_about_the_dangers_of_robutussin = !warnings_only - for(var/I in subtypesof(/datum/tgs_chat_command)) + for(var/I in typesof(/datum/tgs_chat_command) - /datum/tgs_chat_command) if(!warned_about_the_dangers_of_robutussin) - TGS_ERROR_LOG("Custom chat commands in [ApiVersion()] lacks the /datum/tgs_chat_user/sender.channel field!") + TGS_WARNING_LOG("Custom chat commands in [ApiVersion()] lacks the /datum/tgs_chat_user/sender.channel field!") warned_about_the_dangers_of_robutussin = TRUE var/datum/tgs_chat_command/stc = I + if(stc.ignore_type == I) + continue + var/command_name = initial(stc.name) if(!command_name || findtext(command_name, " ") || findtext(command_name, "'") || findtext(command_name, "\"")) if(warnings_only && !warned_command_names[command_name]) @@ -32,7 +35,8 @@ /datum/tgs_api/v3210/proc/HandleServiceCustomCommand(command, sender, params) if(!cached_custom_tgs_chat_commands) cached_custom_tgs_chat_commands = list() - for(var/datum/tgs_chat_command/stc as anything in subtypesof(/datum/tgs_chat_command)) + for(var/I in typesof(/datum/tgs_chat_command) - /datum/tgs_chat_command) + var/datum/tgs_chat_command/stc = I cached_custom_tgs_chat_commands[lowertext(initial(stc.name))] = stc var/command_type = cached_custom_tgs_chat_commands[command] @@ -43,9 +47,12 @@ user.friendly_name = sender // Discord hack, fix the mention if it's only numbers (fuck you IRC trolls) - var/regex/discord_id_regex = regex(@"^[0-9]+$") + var/regex/discord_id_regex = regex("^\[0-9\]+$") if(findtext(sender, discord_id_regex)) sender = "<@[sender]>" user.mention = sender - return stc.Run(user, params) || TRUE + var/datum/tgs_message_content/result = stc.Run(user, params) + result = UpgradeDeprecatedCommandResponse(result, command) + + return result ? result.text : TRUE diff --git a/code/modules/tgs/v4/api.dm b/code/modules/tgs/v4/api.dm index 5e338aaa56..945e2e4117 100644 --- a/code/modules/tgs/v4/api.dm +++ b/code/modules/tgs/v4/api.dm @@ -73,7 +73,7 @@ if(cached_json["apiValidateOnly"]) TGS_INFO_LOG("Validating API and exiting...") Export(TGS4_COMM_VALIDATE, list(TGS4_PARAMETER_DATA = "[minimum_required_security_level]")) - del(world) + TerminateWorld() security_level = cached_json["securityLevel"] chat_channels_json_path = cached_json["chatChannelsJson"] @@ -188,7 +188,7 @@ requesting_new_port = TRUE if(!world.OpenPort(0)) //open any port TGS_ERROR_LOG("Unable to open random port to retrieve new port![TGS4_PORT_CRITFAIL_MESSAGE]") - del(world) + TerminateWorld() //request a new port export_lock = FALSE @@ -196,16 +196,16 @@ if(!new_port_json) TGS_ERROR_LOG("No new port response from server![TGS4_PORT_CRITFAIL_MESSAGE]") - del(world) + TerminateWorld() var/new_port = new_port_json[TGS4_PARAMETER_DATA] if(!isnum(new_port) || new_port <= 0) TGS_ERROR_LOG("Malformed new port json ([json_encode(new_port_json)])![TGS4_PORT_CRITFAIL_MESSAGE]") - del(world) + TerminateWorld() if(new_port != world.port && !world.OpenPort(new_port)) TGS_ERROR_LOG("Unable to open port [new_port]![TGS4_PORT_CRITFAIL_MESSAGE]") - del(world) + TerminateWorld() requesting_new_port = FALSE while(export_lock) @@ -256,31 +256,46 @@ /datum/tgs_api/v4/Revision() return cached_revision -/datum/tgs_api/v4/ChatBroadcast(message, list/channels) +/datum/tgs_api/v4/ChatBroadcast(datum/tgs_message_content/message, list/channels) var/list/ids if(length(channels)) ids = list() - for(var/datum/tgs_chat_channel/channel as anything in channels) + for(var/I in channels) + var/datum/tgs_chat_channel/channel = I ids += channel.id - message = list("message" = message, "channelIds" = ids) + + message = UpgradeDeprecatedChatMessage(message) + + if (!length(channels)) + return + + message = list("message" = message.text, "channelIds" = ids) if(intercepted_message_queue) intercepted_message_queue += list(message) else Export(TGS4_COMM_CHAT, message) -/datum/tgs_api/v4/ChatTargetedBroadcast(message, admin_only) +/datum/tgs_api/v4/ChatTargetedBroadcast(datum/tgs_message_content/message, admin_only) var/list/channels = list() - for(var/datum/tgs_chat_channel/channel as anything in ChatChannelInfo()) + for(var/I in ChatChannelInfo()) + var/datum/tgs_chat_channel/channel = I if (!channel.is_private_channel && ((channel.is_admin_channel && admin_only) || (!channel.is_admin_channel && !admin_only))) channels += channel.id - message = list("message" = message, "channelIds" = channels) + + message = UpgradeDeprecatedChatMessage(message) + + if (!length(channels)) + return + + message = list("message" = message.text, "channelIds" = channels) if(intercepted_message_queue) intercepted_message_queue += list(message) else Export(TGS4_COMM_CHAT, message) -/datum/tgs_api/v4/ChatPrivateMessage(message, datum/tgs_chat_user/user) - message = list("message" = message, "channelIds" = list(user.channel.id)) +/datum/tgs_api/v4/ChatPrivateMessage(datum/tgs_message_content/message, datum/tgs_chat_user/user) + message = UpgradeDeprecatedChatMessage(message) + message = list("message" = message.text, "channelIds" = list(user.channel.id)) if(intercepted_message_queue) intercepted_message_queue += list(message) else diff --git a/code/modules/tgs/v4/commands.dm b/code/modules/tgs/v4/commands.dm index 770b4864b1..25dd6740e3 100644 --- a/code/modules/tgs/v4/commands.dm +++ b/code/modules/tgs/v4/commands.dm @@ -1,8 +1,11 @@ /datum/tgs_api/v4/proc/ListCustomCommands() var/results = list() custom_commands = list() - for(var/I in subtypesof(/datum/tgs_chat_command)) + for(var/I in typesof(/datum/tgs_chat_command) - /datum/tgs_chat_command) var/datum/tgs_chat_command/stc = new I + if(stc.ignore_type == I) + continue + var/command_name = stc.name if(!command_name || findtext(command_name, " ") || findtext(command_name, "'") || findtext(command_name, "\"")) TGS_ERROR_LOG("Custom command [command_name] ([I]) can't be used as it is empty or contains illegal characters!") @@ -34,8 +37,8 @@ var/datum/tgs_chat_command/sc = custom_commands[command] if(sc) - var/result = sc.Run(u, params) - if(result == null) - result = "" - return result + var/datum/tgs_message_content/result = sc.Run(u, params) + result = UpgradeDeprecatedCommandResponse(result, command) + + return result ? result.text : TRUE return "Unknown command: [command]!" diff --git a/code/modules/tgs/v5/README.md b/code/modules/tgs/v5/README.md index 5b48d57a1f..a8a0c748e7 100644 --- a/code/modules/tgs/v5/README.md +++ b/code/modules/tgs/v5/README.md @@ -2,7 +2,12 @@ This DMAPI implements bridge requests using HTTP GET requests to TGS. It has no security restrictions. +- [__interop_version.dm](./__interop_version.dm) contains the version of the API used between the DMAPI and TGS. - [_defines.dm](./_defines.dm) contains constant definitions. - [api.dm](./api.dm) contains the bulk of the API code. +- [bridge.dm](./bridge.dm) contains functions related to making bridge requests. +- [chunking.dm](./chunking.dm) contains common function for splitting large raw data sets into chunks BYOND can natively process. - [commands.dm](./commands.dm) contains functions relating to `/datum/tgs_chat_command`s. +- [serializers.dm](./serializers.dm) contains function to help convert interop `/datum`s into a JSON encodable `list()` format. +- [topic.dm](./topic.dm) contains functions related to processing topic requests. - [undefs.dm](./undefs.dm) Undoes the work of `_defines.dm`. diff --git a/code/modules/tgs/v5/__interop_version.dm b/code/modules/tgs/v5/__interop_version.dm new file mode 100644 index 0000000000..616263098f --- /dev/null +++ b/code/modules/tgs/v5/__interop_version.dm @@ -0,0 +1 @@ +"5.8.0" diff --git a/code/modules/tgs/v5/_defines.dm b/code/modules/tgs/v5/_defines.dm index 87c20e1a6b..b36d634eac 100644 --- a/code/modules/tgs/v5/_defines.dm +++ b/code/modules/tgs/v5/_defines.dm @@ -4,16 +4,29 @@ #define DMAPI5_BRIDGE_DATA "data" #define DMAPI5_TOPIC_DATA "tgs_data" -#define DMAPI5_BRIDGE_COMMAND_PORT_UPDATE 0 +#define DMAPI5_BRIDGE_REQUEST_LIMIT 8198 +#define DMAPI5_TOPIC_REQUEST_LIMIT 65528 +#define DMAPI5_TOPIC_RESPONSE_LIMIT 65529 + #define DMAPI5_BRIDGE_COMMAND_STARTUP 1 #define DMAPI5_BRIDGE_COMMAND_PRIME 2 #define DMAPI5_BRIDGE_COMMAND_REBOOT 3 #define DMAPI5_BRIDGE_COMMAND_KILL 4 #define DMAPI5_BRIDGE_COMMAND_CHAT_SEND 5 -#define DMAPI5_BRIDGE_COMMAND_ADD_MEMBER_ROLE 6 // VOREStation Edit +#define DMAPI5_BRIDGE_COMMAND_CHUNK 6 +#define DMAPI5_BRIDGE_COMMAND_ADD_MEMBER_ROLE 7 // VOREStation Edit #define DMAPI5_PARAMETER_ACCESS_IDENTIFIER "accessIdentifier" #define DMAPI5_PARAMETER_CUSTOM_COMMANDS "customCommands" +#define DMAPI5_PARAMETER_TOPIC_PORT "topicPort" + +#define DMAPI5_CHUNK "chunk" +#define DMAPI5_CHUNK_PAYLOAD "payload" +#define DMAPI5_CHUNK_TOTAL "totalChunks" +#define DMAPI5_CHUNK_SEQUENCE_ID "sequenceId" +#define DMAPI5_CHUNK_PAYLOAD_ID "payloadId" + +#define DMAPI5_MISSING_CHUNKS "missingChunks" #define DMAPI5_RESPONSE_ERROR_MESSAGE "errorMessage" @@ -38,6 +51,7 @@ #define DMAPI5_RUNTIME_INFORMATION_REVISION "revision" #define DMAPI5_RUNTIME_INFORMATION_TEST_MERGES "testMerges" #define DMAPI5_RUNTIME_INFORMATION_SECURITY_LEVEL "securityLevel" +#define DMAPI5_RUNTIME_INFORMATION_VISIBILITY "visibility" #define DMAPI5_CHAT_UPDATE_CHANNELS "channels" @@ -65,9 +79,12 @@ #define DMAPI5_TOPIC_COMMAND_INSTANCE_RENAMED 4 #define DMAPI5_TOPIC_COMMAND_CHAT_CHANNELS_UPDATE 5 #define DMAPI5_TOPIC_COMMAND_SERVER_PORT_UPDATE 6 -#define DMAPI5_TOPIC_COMMAND_HEARTBEAT 7 +#define DMAPI5_TOPIC_COMMAND_HEALTHCHECK 7 #define DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH 8 -#define DMAPI5_TOPIC_COMMAND_GET_CHAT_COMMANDS 9 // VOREStation Edit +#define DMAPI5_TOPIC_COMMAND_SEND_CHUNK 9 +#define DMAPI5_TOPIC_COMMAND_RECEIVE_CHUNK 10 +#define DMAPI5_TOPIC_COMMAND_RECEIVE_BROADCAST 11 +#define DMAPI5_TOPIC_COMMAND_GET_CHAT_COMMANDS 12 #define DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE "commandType" #define DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND "chatCommand" @@ -77,7 +94,9 @@ #define DMAPI5_TOPIC_PARAMETER_NEW_INSTANCE_NAME "newInstanceName" #define DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE "chatUpdate" #define DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION "newServerVersion" +#define DMAPI5_TOPIC_PARAMETER_BROADCAST_MESSAGE "broadcastMessage" +#define DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE "commandResponse" #define DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE_MESSAGE "commandResponseMessage" #define DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES "chatResponses" @@ -96,6 +115,7 @@ #define DMAPI5_CHAT_CHANNEL_IS_ADMIN_CHANNEL "isAdminChannel" #define DMAPI5_CHAT_CHANNEL_IS_PRIVATE_CHANNEL "isPrivateChannel" #define DMAPI5_CHAT_CHANNEL_TAG "tag" +#define DMAPI5_CHAT_CHANNEL_EMBEDS_SUPPORTED "embedsSupported" #define DMAPI5_CUSTOM_CHAT_COMMAND_NAME "name" #define DMAPI5_CUSTOM_CHAT_COMMAND_HELP_TEXT "helpText" diff --git a/code/modules/tgs/v5/api.dm b/code/modules/tgs/v5/api.dm index 7383385a76..25d49b3e3b 100644 --- a/code/modules/tgs/v5/api.dm +++ b/code/modules/tgs/v5/api.dm @@ -4,6 +4,7 @@ var/instance_name var/security_level + var/visibility var/reboot_mode = TGS_REBOOT_MODE_NORMAL @@ -16,19 +17,32 @@ var/list/chat_channels var/initialized = FALSE + var/initial_bridge_request_received = FALSE + var/datum/tgs_version/interop_version + + var/chunked_requests = 0 + var/list/chunked_topics = list() + + var/detached = FALSE + +/datum/tgs_api/v5/New() + . = ..() + interop_version = version + TGS_DEBUG_LOG("V5 API created: [json_encode(args)]") /datum/tgs_api/v5/ApiVersion() return new /datum/tgs_version( - #include "interop_version.dm" + #include "__interop_version.dm" ) /datum/tgs_api/v5/OnWorldNew(minimum_required_security_level) + TGS_DEBUG_LOG("OnWorldNew()") server_port = world.params[DMAPI5_PARAM_SERVER_PORT] access_identifier = world.params[DMAPI5_PARAM_ACCESS_IDENTIFIER] var/datum/tgs_version/api_version = ApiVersion() - version = null - var/list/bridge_response = Bridge(DMAPI5_BRIDGE_COMMAND_STARTUP, list(DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL = minimum_required_security_level, DMAPI5_BRIDGE_PARAMETER_VERSION = api_version.raw_parameter, DMAPI5_PARAMETER_CUSTOM_COMMANDS = ListCustomCommands())) + version = null // we want this to be the TGS version, not the interop version + var/list/bridge_response = Bridge(DMAPI5_BRIDGE_COMMAND_STARTUP, list(DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL = minimum_required_security_level, DMAPI5_BRIDGE_PARAMETER_VERSION = api_version.raw_parameter, DMAPI5_PARAMETER_CUSTOM_COMMANDS = ListCustomCommands(), DMAPI5_PARAMETER_TOPIC_PORT = GetTopicPort())) if(!istype(bridge_response)) TGS_ERROR_LOG("Failed initial bridge request!") return FALSE @@ -40,10 +54,12 @@ if(runtime_information[DMAPI5_RUNTIME_INFORMATION_API_VALIDATE_ONLY]) TGS_INFO_LOG("DMAPI validation, exiting...") - del(world) + TerminateWorld() - version = new /datum/tgs_version(runtime_information[DMAPI5_RUNTIME_INFORMATION_SERVER_VERSION]) + initial_bridge_request_received = TRUE + version = new /datum/tgs_version(runtime_information[DMAPI5_RUNTIME_INFORMATION_SERVER_VERSION]) // reassigning this because it can change if TGS updates security_level = runtime_information[DMAPI5_RUNTIME_INFORMATION_SECURITY_LEVEL] + visibility = runtime_information[DMAPI5_RUNTIME_INFORMATION_VISIBILITY] instance_name = runtime_information[DMAPI5_RUNTIME_INFORMATION_INSTANCE_NAME] var/list/revisionData = runtime_information[DMAPI5_RUNTIME_INFORMATION_REVISION] @@ -90,191 +106,50 @@ initialized = TRUE return TRUE +/datum/tgs_api/v5/proc/GetTopicPort() +#if defined(OPENDREAM) && defined(OPENDREAM_TOPIC_PORT_EXISTS) + return "[world.opendream_topic_port]" +#else + return null +#endif + /datum/tgs_api/v5/proc/RequireInitialBridgeResponse() - while(!version) + TGS_DEBUG_LOG("RequireInitialBridgeResponse()") + var/logged = FALSE + while(!initial_bridge_request_received) + if(!logged) + TGS_DEBUG_LOG("RequireInitialBridgeResponse: Starting sleep") + logged = TRUE + sleep(1) + TGS_DEBUG_LOG("RequireInitialBridgeResponse: Passed") + /datum/tgs_api/v5/OnInitializationComplete() Bridge(DMAPI5_BRIDGE_COMMAND_PRIME) -/datum/tgs_api/v5/proc/TopicResponse(error_message = null) - var/list/response = list() - response[DMAPI5_RESPONSE_ERROR_MESSAGE] = error_message - - return json_encode(response) - /datum/tgs_api/v5/OnTopic(T) + TGS_DEBUG_LOG("OnTopic()") + RequireInitialBridgeResponse() + TGS_DEBUG_LOG("OnTopic passed bridge request gate") var/list/params = params2list(T) var/json = params[DMAPI5_TOPIC_DATA] if(!json) + TGS_DEBUG_LOG("No \"[DMAPI5_TOPIC_DATA]\" entry found, ignoring...") return FALSE // continue to /world/Topic - var/list/topic_parameters = json_decode(json) - if(!topic_parameters) - return TopicResponse("Invalid topic parameters json!"); - if(!initialized) - TGS_WARNING_LOG("Missed topic due to not being initialized: [T]") + TGS_WARNING_LOG("Missed topic due to not being initialized: [json]") return TRUE // too early to handle, but it's still our responsibility - var/their_sCK = topic_parameters[DMAPI5_PARAMETER_ACCESS_IDENTIFIER] - if(their_sCK != access_identifier) - return TopicResponse("Failed to decode [DMAPI5_PARAMETER_ACCESS_IDENTIFIER] from: [json]!"); - - var/command = topic_parameters[DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE] - if(!isnum(command)) - return TopicResponse("Failed to decode [DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE] from: [json]!") - - switch(command) - if(DMAPI5_TOPIC_COMMAND_CHAT_COMMAND) - var/result = HandleCustomCommand(topic_parameters[DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND]) - if(!result) - result = TopicResponse("Error running chat command!") - return result - // VOREStation Edit Start - GetChatCommands command - if(DMAPI5_TOPIC_COMMAND_GET_CHAT_COMMANDS) - var/topic_response = list(DMAPI5_PARAMETER_CUSTOM_COMMANDS = ListCustomCommands()) - return json_encode(topic_response) - // VOREStation Edit - End - if(DMAPI5_TOPIC_COMMAND_EVENT_NOTIFICATION) - intercepted_message_queue = list() - var/list/event_notification = topic_parameters[DMAPI5_TOPIC_PARAMETER_EVENT_NOTIFICATION] - if(!istype(event_notification)) - return TopicResponse("Invalid [DMAPI5_TOPIC_PARAMETER_EVENT_NOTIFICATION]!") - - var/event_type = event_notification[DMAPI5_EVENT_NOTIFICATION_TYPE] - if(!isnum(event_type)) - return TopicResponse("Invalid or missing [DMAPI5_EVENT_NOTIFICATION_TYPE]!") - - var/list/event_parameters = event_notification[DMAPI5_EVENT_NOTIFICATION_PARAMETERS] - if(event_parameters && !istype(event_parameters)) - return TopicResponse("Invalid or missing [DMAPI5_EVENT_NOTIFICATION_PARAMETERS]!") - - var/list/event_call = list(event_type) - if(event_parameters) - event_call += event_parameters - - if(event_handler != null) - event_handler.HandleEvent(arglist(event_call)) - - var/list/response = list() - response[DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES] = intercepted_message_queue - intercepted_message_queue = null - return json_encode(response) - if(DMAPI5_TOPIC_COMMAND_CHANGE_PORT) - var/new_port = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_PORT] - if (!isnum(new_port) || !(new_port > 0)) - return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_PORT]]") - - if(event_handler != null) - event_handler.HandleEvent(TGS_EVENT_PORT_SWAP, new_port) - - //the topic still completes, miraculously - //I honestly didn't believe byond could do it without exploding - if(!world.OpenPort(new_port)) - return TopicResponse("Port change failed!") - - return TopicResponse() - if(DMAPI5_TOPIC_COMMAND_CHANGE_REBOOT_STATE) - var/new_reboot_mode = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_REBOOT_STATE] - if(!isnum(new_reboot_mode)) - return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_REBOOT_STATE]!") - - if(event_handler != null) - event_handler.HandleEvent(TGS_EVENT_REBOOT_MODE_CHANGE, reboot_mode, new_reboot_mode) - - reboot_mode = new_reboot_mode - return TopicResponse() - if(DMAPI5_TOPIC_COMMAND_INSTANCE_RENAMED) - var/new_instance_name = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_INSTANCE_NAME] - if(!istext(new_instance_name)) - return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_INSTANCE_NAME]!") - - if(event_handler != null) - event_handler.HandleEvent(TGS_EVENT_INSTANCE_RENAMED, new_instance_name) - - instance_name = new_instance_name - return TopicResponse() - if(DMAPI5_TOPIC_COMMAND_CHAT_CHANNELS_UPDATE) - var/list/chat_update_json = topic_parameters[DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE] - if(!istype(chat_update_json)) - return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE]!") - - DecodeChannels(chat_update_json) - return TopicResponse() - if(DMAPI5_TOPIC_COMMAND_SERVER_PORT_UPDATE) - var/new_port = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_PORT] - if (!isnum(new_port) || !(new_port > 0)) - return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_PORT]]") - - server_port = new_port - return TopicResponse() - if(DMAPI5_TOPIC_COMMAND_HEARTBEAT) - return TopicResponse() - if(DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH) - var/new_port = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_PORT] - var/error_message = null - if (new_port != null) - if (!isnum(new_port) || !(new_port > 0)) - error_message = "Invalid [DMAPI5_TOPIC_PARAMETER_NEW_PORT]]" - else - server_port = new_port - - var/new_version_string = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION] - if (!istext(new_version_string)) - if(error_message != null) - error_message += ", " - error_message += "Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION]]" - else - var/datum/tgs_version/new_version = new(new_version_string) - if (event_handler) - event_handler.HandleEvent(TGS_EVENT_WATCHDOG_REATTACH, new_version) - - version = new_version - - return json_encode(list(DMAPI5_RESPONSE_ERROR_MESSAGE = error_message, DMAPI5_PARAMETER_CUSTOM_COMMANDS = ListCustomCommands())) - - return TopicResponse("Unknown command: [command]") - -/datum/tgs_api/v5/proc/Bridge(command, list/data) - if(!data) - data = list() - - data[DMAPI5_BRIDGE_PARAMETER_COMMAND_TYPE] = command - data[DMAPI5_PARAMETER_ACCESS_IDENTIFIER] = access_identifier - - var/json = json_encode(data) - var/encoded_json = url_encode(json) - - // This is an infinite sleep until we get a response - var/export_response = world.Export("http://127.0.0.1:[server_port]/Bridge?[DMAPI5_BRIDGE_DATA]=[encoded_json]") - if(!export_response) - TGS_ERROR_LOG("Failed export request: [json]") - return - - var/response_json = file2text(export_response["CONTENT"]) - if(!response_json) - TGS_ERROR_LOG("Failed export request, missing content!") - return - - var/list/bridge_response = json_decode(response_json) - if(!bridge_response) - TGS_ERROR_LOG("Failed export request, bad json: [response_json]") - return - - var/error = bridge_response[DMAPI5_RESPONSE_ERROR_MESSAGE] - if(error) - TGS_ERROR_LOG("Failed export request, bad request: [error]") - return - - return bridge_response + return ProcessTopicJson(json, TRUE) /datum/tgs_api/v5/OnReboot() var/list/result = Bridge(DMAPI5_BRIDGE_COMMAND_REBOOT) if(!result) return - //okay so the standard TGS4 proceedure is: right before rebooting change the port to whatever was sent to us in the above json's data parameter + //okay so the standard TGS proceedure is: right before rebooting change the port to whatever was sent to us in the above json's data parameter var/port = result[DMAPI5_BRIDGE_RESPONSE_NEW_PORT] if(!isnum(port)) @@ -302,43 +177,70 @@ RequireInitialBridgeResponse() return revision -/datum/tgs_api/v5/ChatBroadcast(message, list/channels) +// Common proc b/c it's used by the V3/V4 APIs +/datum/tgs_api/proc/UpgradeDeprecatedChatMessage(datum/tgs_message_content/message) + if(!istext(message)) + return message + + TGS_WARNING_LOG("Received legacy string when a [/datum/tgs_message_content] was expected. Please audit all calls to TgsChatBroadcast, TgsChatTargetedBroadcast, and TgsChatPrivateMessage to ensure they use the new /datum.") + return new /datum/tgs_message_content(message) + +/datum/tgs_api/v5/ChatBroadcast(datum/tgs_message_content/message2, list/channels) if(!length(channels)) channels = ChatChannelInfo() var/list/ids = list() - for(var/datum/tgs_chat_channel/channel as anything in channels) + for(var/I in channels) + var/datum/tgs_chat_channel/channel = I ids += channel.id - message = list(DMAPI5_CHAT_MESSAGE_TEXT = message, DMAPI5_CHAT_MESSAGE_CHANNEL_IDS = ids) - if(intercepted_message_queue) - intercepted_message_queue += list(message) - else - Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = message)) + message2 = UpgradeDeprecatedChatMessage(message2) -/datum/tgs_api/v5/ChatTargetedBroadcast(message, admin_only) + if (!length(channels)) + return + + var/list/data = message2._interop_serialize() + data[DMAPI5_CHAT_MESSAGE_CHANNEL_IDS] = ids + if(intercepted_message_queue) + intercepted_message_queue += list(data) + else + Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = data)) + +/datum/tgs_api/v5/ChatTargetedBroadcast(datum/tgs_message_content/message2, admin_only) var/list/channels = list() - for(var/datum/tgs_chat_channel/channel as anything in ChatChannelInfo()) + for(var/I in ChatChannelInfo()) + var/datum/tgs_chat_channel/channel = I if (!channel.is_private_channel && ((channel.is_admin_channel && admin_only) || (!channel.is_admin_channel && !admin_only))) channels += channel.id - message = list(DMAPI5_CHAT_MESSAGE_TEXT = message, DMAPI5_CHAT_MESSAGE_CHANNEL_IDS = channels) - if(intercepted_message_queue) - intercepted_message_queue += list(message) - else - Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = message)) -/datum/tgs_api/v5/ChatPrivateMessage(message, datum/tgs_chat_user/user) - message = list(DMAPI5_CHAT_MESSAGE_TEXT = message, DMAPI5_CHAT_MESSAGE_CHANNEL_IDS = list(user.channel.id)) + message2 = UpgradeDeprecatedChatMessage(message2) + + if (!length(channels)) + return + + var/list/data = message2._interop_serialize() + data[DMAPI5_CHAT_MESSAGE_CHANNEL_IDS] = channels if(intercepted_message_queue) - intercepted_message_queue += list(message) + intercepted_message_queue += list(data) else - Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = message)) + Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = data)) + +/datum/tgs_api/v5/ChatPrivateMessage(datum/tgs_message_content/message2, datum/tgs_chat_user/user) + message2 = UpgradeDeprecatedChatMessage(message2) + var/list/data = message2._interop_serialize() + data[DMAPI5_CHAT_MESSAGE_CHANNEL_IDS] = list(user.channel.id) + if(intercepted_message_queue) + intercepted_message_queue += list(data) + else + Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = data)) /datum/tgs_api/v5/ChatChannelInfo() RequireInitialBridgeResponse() + WaitForReattach(TRUE) return chat_channels.Copy() /datum/tgs_api/v5/proc/DecodeChannels(chat_update_json) + TGS_DEBUG_LOG("DecodeChannels()") var/list/chat_channels_json = chat_update_json[DMAPI5_CHAT_UPDATE_CHANNELS] if(istype(chat_channels_json)) chat_channels.Cut() @@ -357,8 +259,13 @@ channel.is_admin_channel = channel_json[DMAPI5_CHAT_CHANNEL_IS_ADMIN_CHANNEL] channel.is_private_channel = channel_json[DMAPI5_CHAT_CHANNEL_IS_PRIVATE_CHANNEL] channel.custom_tag = channel_json[DMAPI5_CHAT_CHANNEL_TAG] + channel.embeds_supported = channel_json[DMAPI5_CHAT_CHANNEL_EMBEDS_SUPPORTED] return channel /datum/tgs_api/v5/SecurityLevel() RequireInitialBridgeResponse() return security_level + +/datum/tgs_api/v5/Visibility() + RequireInitialBridgeResponse() + return visibility diff --git a/code/modules/tgs/v5/bridge.dm b/code/modules/tgs/v5/bridge.dm new file mode 100644 index 0000000000..a0ab359876 --- /dev/null +++ b/code/modules/tgs/v5/bridge.dm @@ -0,0 +1,106 @@ +/datum/tgs_api/v5/proc/Bridge(command, list/data) + if(!data) + data = list() + + var/single_bridge_request = CreateBridgeRequest(command, data) + if(length(single_bridge_request) <= DMAPI5_BRIDGE_REQUEST_LIMIT) + return PerformBridgeRequest(single_bridge_request) + + // chunking required + var/payload_id = ++chunked_requests + + var/raw_data = CreateBridgeData(command, data, FALSE) + + var/list/chunk_requests = GenerateChunks(raw_data, TRUE) + + var/list/response + for(var/bridge_request in chunk_requests) + response = PerformBridgeRequest(bridge_request) + if(!response) + // Abort + return + + var/list/missing_sequence_ids = response[DMAPI5_MISSING_CHUNKS] + if(length(missing_sequence_ids)) + do + TGS_WARNING_LOG("Server is still missing some chunks of bridge P[payload_id]! Sending missing chunks...") + if(!istype(missing_sequence_ids)) + TGS_ERROR_LOG("Did not receive a list() for [DMAPI5_MISSING_CHUNKS]!") + return + + for(var/missing_sequence_id in missing_sequence_ids) + if(!isnum(missing_sequence_id)) + TGS_ERROR_LOG("Did not receive a num in [DMAPI5_MISSING_CHUNKS]!") + return + + var/missing_chunk_request = chunk_requests[missing_sequence_id + 1] + response = PerformBridgeRequest(missing_chunk_request) + if(!response) + // Abort + return + + missing_sequence_ids = response[DMAPI5_MISSING_CHUNKS] + while(length(missing_sequence_ids)) + + return response + +/datum/tgs_api/v5/proc/CreateBridgeRequest(command, list/data) + var/json = CreateBridgeData(command, data, TRUE) + var/encoded_json = url_encode(json) + + var/api_prefix = interop_version.minor >= 8 ? "api/" : "" + + var/url = "http://127.0.0.1:[server_port]/[api_prefix]Bridge?[DMAPI5_BRIDGE_DATA]=[encoded_json]" + return url + +/datum/tgs_api/v5/proc/CreateBridgeData(command, list/data, needs_auth) + data[DMAPI5_BRIDGE_PARAMETER_COMMAND_TYPE] = command + if(needs_auth) + data[DMAPI5_PARAMETER_ACCESS_IDENTIFIER] = access_identifier + + var/json = json_encode(data) + return json + +/datum/tgs_api/v5/proc/WaitForReattach(require_channels = FALSE) + if(detached) + // Wait up to one minute + for(var/i in 1 to 600) + sleep(1) + if(!detached && (!require_channels || length(chat_channels))) + break + + // dad went out for milk and cigarettes 20 years ago... + // yes, this affects all other waiters, intentional + if(i == 600) + detached = FALSE + +/datum/tgs_api/v5/proc/PerformBridgeRequest(bridge_request) + WaitForReattach(FALSE) + + // This is an infinite sleep until we get a response + var/export_response = world.Export(bridge_request) + if(!export_response) + TGS_ERROR_LOG("Failed bridge request: [bridge_request]") + return + + var/content = export_response["CONTENT"] + if(!content) + TGS_ERROR_LOG("Failed bridge request, missing content!") + return + + var/response_json = file2text(content) + if(!response_json) + TGS_ERROR_LOG("Failed bridge request, failed to load content!") + return + + var/list/bridge_response = json_decode(response_json) + if(!bridge_response) + TGS_ERROR_LOG("Failed bridge request, bad json: [response_json]") + return + + var/error = bridge_response[DMAPI5_RESPONSE_ERROR_MESSAGE] + if(error) + TGS_ERROR_LOG("Failed bridge request, bad request: [error]") + return + + return bridge_response diff --git a/code/modules/tgs/v5/chat_commands.dm b/code/modules/tgs/v5/chat_commands.dm index 3deb15f52c..74096d886f 100644 --- a/code/modules/tgs/v5/chat_commands.dm +++ b/code/modules/tgs/v5/chat_commands.dm @@ -92,7 +92,7 @@ GLOBAL_LIST_EMPTY(pending_discord_registrations) /datum/tgs_chat_command/register/Run(datum/tgs_chat_user/sender, params) // Try to find if that ID is registered to someone already //var/sql_discord = sql_sanitize_text(sender.id) //CHOMPEdit TGSQL - var/DBQuery/query = SSdbcore.NewQuery("SELECT discord_id FROM erro_player WHERE discord_id = :t_discord", list("t_discord"=sender.id)) //CHOMPEdit TGSQL + var/datum/db_query/query = SSdbcore.NewQuery("SELECT discord_id FROM erro_player WHERE discord_id = :t_discord", list("t_discord"=sender.id)) //CHOMPEdit TGSQL query.Execute() if(query.NextRow()) qdel(query) //CHOMPEdit TGSQL @@ -116,7 +116,7 @@ GLOBAL_LIST_EMPTY(pending_discord_registrations) return "[sender.friendly_name], I couldn't find a logged-in user with the username of '[key_to_find]', which is what you provided after conversion to Byond's ckey format. Please connect to the game server and try again." //var/sql_ckey = sql_sanitize_text(key_to_find) //CHOMPEdit TGSQL - var/DBQuery/query2 = SSdbcore.NewQuery("SELECT discord_id FROM erro_player WHERE ckey = :t_ckey",list("t_ckey" = key_to_find)) //CHOMPEdit TGSQL + var/datum/db_query/query2 = SSdbcore.NewQuery("SELECT discord_id FROM erro_player WHERE ckey = :t_ckey",list("t_ckey" = key_to_find)) //CHOMPEdit TGSQL query2.Execute() //CHOMPEdit TGSQL // We somehow found their client, BUT they don't exist in the database diff --git a/code/modules/tgs/v5/chat_commands_zz_ch.dm b/code/modules/tgs/v5/chat_commands_zz_ch.dm index 2e90c63e75..3da1ba3eb9 100644 --- a/code/modules/tgs/v5/chat_commands_zz_ch.dm +++ b/code/modules/tgs/v5/chat_commands_zz_ch.dm @@ -66,7 +66,7 @@ if(!length(key_to_find)) return "[sender.friendly_name], you need to provide a Byond username at the end of the command. It can be in 'key' format (with spaces and characters) or 'ckey' format (without spaces or special characters)." - var/DBQuery/query = SSdbcore.NewQuery("SELECT discord_id FROM erro_player WHERE ckey = :t_ckey",list("t_ckey" = key_to_find)) + var/datum/db_query/query = SSdbcore.NewQuery("SELECT discord_id FROM erro_player WHERE ckey = :t_ckey",list("t_ckey" = key_to_find)) query.Execute() if(!query.NextRow()) @@ -89,7 +89,7 @@ if(!params) return "[sender.friendly_name], you need to provide a Discord ID at the end of the command. To obtain someone's Discord ID, you need to enable developer mode on discord, and then right click on their name and click Copy ID." - var/DBQuery/query = SSdbcore.NewQuery("SELECT ckey FROM erro_player WHERE discord_id = :t_discord", list("t_discord"=params)) + var/datum/db_query/query = SSdbcore.NewQuery("SELECT ckey FROM erro_player WHERE discord_id = :t_discord", list("t_discord"=params)) query.Execute() if(!query.NextRow()) @@ -113,6 +113,6 @@ name = "vore" help_text = "vore" admin_only = FALSE - + /datum/tgs_chat_command/vore/Run(datum/tgs_chat_user/sender, params) return "vore" diff --git a/code/modules/tgs/v5/chunking.dm b/code/modules/tgs/v5/chunking.dm new file mode 100644 index 0000000000..cd5944d34f --- /dev/null +++ b/code/modules/tgs/v5/chunking.dm @@ -0,0 +1,43 @@ +/datum/tgs_api/v5/proc/GenerateChunks(payload, bridge) + var/limit = bridge ? DMAPI5_BRIDGE_REQUEST_LIMIT : DMAPI5_TOPIC_RESPONSE_LIMIT + + var/payload_id = ++chunked_requests + var/data_length = length(payload) + + var/chunk_count + var/list/chunk_requests + for(chunk_count = 2; !chunk_requests; ++chunk_count) + var/max_chunk_size = -round(-(data_length / chunk_count)) + if(max_chunk_size > limit) + continue + + chunk_requests = list() + for(var/i in 1 to chunk_count) + var/start_index = 1 + ((i - 1) * max_chunk_size) + if (start_index > data_length) + break + + var/end_index = min(1 + (i * max_chunk_size), data_length + 1) + + var/chunk_payload = copytext(payload, start_index, end_index) + + // sequence IDs in interop chunking are always zero indexed + var/list/chunk = list(DMAPI5_CHUNK_PAYLOAD_ID = payload_id, DMAPI5_CHUNK_SEQUENCE_ID = (i - 1), DMAPI5_CHUNK_TOTAL = chunk_count, DMAPI5_CHUNK_PAYLOAD = chunk_payload) + + var/chunk_request = list(DMAPI5_CHUNK = chunk) + var/chunk_length + if(bridge) + chunk_request = CreateBridgeRequest(DMAPI5_BRIDGE_COMMAND_CHUNK, chunk_request) + chunk_length = length(chunk_request) + else + chunk_request = list(chunk_request) // wrap for adding to list + chunk_length = length(json_encode(chunk_request)) + + if(chunk_length > limit) + // Screwed by encoding, no way to preempt it though + chunk_requests = null + break + + chunk_requests += chunk_request + + return chunk_requests diff --git a/code/modules/tgs/v5/commands.dm b/code/modules/tgs/v5/commands.dm index e5b2562efe..9557f8a08e 100644 --- a/code/modules/tgs/v5/commands.dm +++ b/code/modules/tgs/v5/commands.dm @@ -1,16 +1,19 @@ /datum/tgs_api/v5/proc/ListCustomCommands() var/results = list() custom_commands = list() - for(var/I in subtypesof(/datum/tgs_chat_command)) + for(var/I in typesof(/datum/tgs_chat_command) - /datum/tgs_chat_command) var/datum/tgs_chat_command/stc = new I + if(stc.ignore_type == I) + continue + var/command_name = stc.name if(!command_name || findtext(command_name, " ") || findtext(command_name, "'") || findtext(command_name, "\"")) - TGS_WARNING_LOG("Custom command [command_name] ([I]) can't be used as it is empty or contains illegal characters!") + TGS_ERROR_LOG("Custom command [command_name] ([I]) can't be used as it is empty or contains illegal characters!") continue if(results[command_name]) var/datum/other = custom_commands[command_name] - TGS_WARNING_LOG("Custom commands [other.type] and [I] have the same name (\"[command_name]\"), only [other.type] will be available!") + TGS_ERROR_LOG("Custom commands [other.type] and [I] have the same name (\"[command_name]\"), only [other.type] will be available!") continue results += list(list(DMAPI5_CUSTOM_CHAT_COMMAND_NAME = command_name, DMAPI5_CUSTOM_CHAT_COMMAND_HELP_TEXT = stc.help_text, DMAPI5_CUSTOM_CHAT_COMMAND_ADMIN_ONLY = stc.admin_only)) custom_commands[command_name] = stc @@ -30,11 +33,28 @@ var/datum/tgs_chat_command/sc = custom_commands[command] if(sc) - var/text_response = sc.Run(u, params) - var/list/topic_response = list() - if(!istext(text_response)) - TGS_ERROR_LOG("Custom command [command] should return a string! Got: \"[text_response]\"") - text_response = null - topic_response[DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE_MESSAGE] = text_response - return json_encode(topic_response) + var/datum/tgs_message_content/response = sc.Run(u, params) + response = UpgradeDeprecatedCommandResponse(response, command) + + var/list/topic_response = TopicResponse() + topic_response[DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE_MESSAGE] = response ? response.text : null + topic_response[DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE] = response ? response._interop_serialize() : null + return topic_response return TopicResponse("Unknown custom chat command: [command]!") + +// Common proc b/c it's used by the V3/V4 APIs +/datum/tgs_api/proc/UpgradeDeprecatedCommandResponse(datum/tgs_message_content/response, command) + // Backwards compatibility, used to return a string + if(istext(response)) + warned_deprecated_command_runs = warned_deprecated_command_runs || list() + if(!warned_deprecated_command_runs[command]) + TGS_WARNING_LOG("Custom chat command \"[command]\" is still returning a string. This behaviour is deprecated, please upgrade it to return a [/datum/tgs_message_content].") + warned_deprecated_command_runs[command] = TRUE + + return new /datum/tgs_message_content(response) + + if(!istype(response)) + TGS_ERROR_LOG("Custom chat command \"[command]\" should return a [/datum/tgs_message_content]! Got: \"[response]\"") + return null + + return response diff --git a/code/modules/tgs/v5/serializers.dm b/code/modules/tgs/v5/serializers.dm new file mode 100644 index 0000000000..3a32848ad5 --- /dev/null +++ b/code/modules/tgs/v5/serializers.dm @@ -0,0 +1,59 @@ +/datum/tgs_message_content/proc/_interop_serialize() + return list("text" = text, "embed" = embed ? embed._interop_serialize() : null) + +/datum/tgs_chat_embed/proc/_interop_serialize() + CRASH("Base /proc/interop_serialize called on [type]!") + +/datum/tgs_chat_embed/structure/_interop_serialize() + var/list/serialized_fields + if(istype(fields, /list)) + serialized_fields = list() + for(var/datum/tgs_chat_embed/field/field as anything in fields) + serialized_fields += list(field._interop_serialize()) + return list( + "title" = title, + "description" = description, + "url" = url, + "timestamp" = timestamp, + "colour" = colour, + "image" = src.image ? src.image._interop_serialize() : null, + "thumbnail" = thumbnail ? thumbnail._interop_serialize() : null, + "video" = video ? video._interop_serialize() : null, + "footer" = footer ? footer._interop_serialize() : null, + "provider" = provider ? provider._interop_serialize() : null, + "author" = author ? author._interop_serialize() : null, + "fields" = serialized_fields + ) + +/datum/tgs_chat_embed/media/_interop_serialize() + return list( + "url" = url, + "width" = width, + "height" = height, + "proxyUrl" = proxy_url + ) + +/datum/tgs_chat_embed/provider/_interop_serialize() + return list( + "url" = url, + "name" = name + ) + +/datum/tgs_chat_embed/provider/author/_interop_serialize() + . = ..() + .["iconUrl"] = icon_url + .["proxyIconUrl"] = proxy_icon_url + +/datum/tgs_chat_embed/footer/_interop_serialize() + return list( + "text" = text, + "iconUrl" = icon_url, + "proxyIconUrl" = proxy_icon_url + ) + +/datum/tgs_chat_embed/field/_interop_serialize() + return list( + "name" = name, + "value" = value, + "isInline" = is_inline + ) diff --git a/code/modules/tgs/v5/topic.dm b/code/modules/tgs/v5/topic.dm new file mode 100644 index 0000000000..2928bbeadd --- /dev/null +++ b/code/modules/tgs/v5/topic.dm @@ -0,0 +1,289 @@ +/datum/tgs_api/v5/proc/TopicResponse(error_message = null) + var/list/response = list() + if(error_message) + response[DMAPI5_RESPONSE_ERROR_MESSAGE] = error_message + return response + +/datum/tgs_api/v5/proc/ProcessTopicJson(json, check_access_identifier) + TGS_DEBUG_LOG("ProcessTopicJson(..., [check_access_identifier])") + var/list/result = ProcessRawTopic(json, check_access_identifier) + if(!result) + result = TopicResponse("Runtime error!") + else if(!length(result)) + return "{}" // quirk of json_encode is an empty list returns "[]" + + var/response_json = json_encode(result) + if(length(response_json) > DMAPI5_TOPIC_RESPONSE_LIMIT) + // cache response chunks and send the first + var/list/chunks = GenerateChunks(response_json, FALSE) + var/payload_id = chunks[1][DMAPI5_CHUNK][DMAPI5_CHUNK_PAYLOAD_ID] + var/cache_key = ResponseTopicChunkCacheKey(payload_id) + + chunked_topics[cache_key] = chunks + + response_json = json_encode(chunks[1]) + + return response_json + +/datum/tgs_api/v5/proc/ProcessRawTopic(json, check_access_identifier) + TGS_DEBUG_LOG("ProcessRawTopic(..., [check_access_identifier])") + var/list/topic_parameters = json_decode(json) + if(!topic_parameters) + TGS_DEBUG_LOG("ProcessRawTopic: json_decode failed") + return TopicResponse("Invalid topic parameters json: [json]!"); + + var/their_sCK = topic_parameters[DMAPI5_PARAMETER_ACCESS_IDENTIFIER] + if(check_access_identifier && their_sCK != access_identifier) + TGS_DEBUG_LOG("ProcessRawTopic: access identifier check failed") + return TopicResponse("Failed to decode [DMAPI5_PARAMETER_ACCESS_IDENTIFIER] or it does not match!") + + var/command = topic_parameters[DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE] + if(!isnum(command)) + TGS_DEBUG_LOG("ProcessRawTopic: command type check failed") + return TopicResponse("Failed to decode [DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE]!") + + return ProcessTopicCommand(command, topic_parameters) + +/datum/tgs_api/v5/proc/ResponseTopicChunkCacheKey(payload_id) + return "response[payload_id]" + +/datum/tgs_api/v5/proc/ProcessTopicCommand(command, list/topic_parameters) + TGS_DEBUG_LOG("ProcessTopicCommand([command], ...)") + switch(command) + + if(DMAPI5_TOPIC_COMMAND_CHAT_COMMAND) + intercepted_message_queue = list() + var/list/result = HandleCustomCommand(topic_parameters[DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND]) + if(!result) + result = TopicResponse("Error running chat command!") + result[DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES] = intercepted_message_queue + intercepted_message_queue = null + return result + + // VOREStation Edit Start - GetChatCommands command + if(DMAPI5_TOPIC_COMMAND_GET_CHAT_COMMANDS) + var/topic_response = list(DMAPI5_PARAMETER_CUSTOM_COMMANDS = ListCustomCommands()) + return json_encode(topic_response) + // VOREStation Edit - End + + if(DMAPI5_TOPIC_COMMAND_EVENT_NOTIFICATION) + var/list/event_notification = topic_parameters[DMAPI5_TOPIC_PARAMETER_EVENT_NOTIFICATION] + if(!istype(event_notification)) + return TopicResponse("Invalid [DMAPI5_TOPIC_PARAMETER_EVENT_NOTIFICATION]!") + + var/event_type = event_notification[DMAPI5_EVENT_NOTIFICATION_TYPE] + if(!isnum(event_type)) + return TopicResponse("Invalid or missing [DMAPI5_EVENT_NOTIFICATION_TYPE]!") + + var/list/event_parameters = event_notification[DMAPI5_EVENT_NOTIFICATION_PARAMETERS] + if(event_parameters && !istype(event_parameters)) + . = TopicResponse("Invalid or missing [DMAPI5_EVENT_NOTIFICATION_PARAMETERS]!") + else + var/list/response = TopicResponse() + . = response + if(event_handler != null) + var/list/event_call = list(event_type) + if(event_parameters) + event_call += event_parameters + + intercepted_message_queue = list() + event_handler.HandleEvent(arglist(event_call)) + response[DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES] = intercepted_message_queue + intercepted_message_queue = null + + if (event_type == TGS_EVENT_WATCHDOG_DETACH) + detached = TRUE + chat_channels.Cut() // https://github.com/tgstation/tgstation-server/issues/1490 + + return + + if(DMAPI5_TOPIC_COMMAND_CHANGE_PORT) + var/new_port = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_PORT] + if (!isnum(new_port) || !(new_port > 0)) + return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_PORT]") + + if(event_handler != null) + event_handler.HandleEvent(TGS_EVENT_PORT_SWAP, new_port) + + //the topic still completes, miraculously + //I honestly didn't believe byond could do it without exploding + if(!world.OpenPort(new_port)) + return TopicResponse("Port change failed!") + + return TopicResponse() + + if(DMAPI5_TOPIC_COMMAND_CHANGE_REBOOT_STATE) + var/new_reboot_mode = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_REBOOT_STATE] + if(!isnum(new_reboot_mode)) + return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_REBOOT_STATE]!") + + if(event_handler != null) + event_handler.HandleEvent(TGS_EVENT_REBOOT_MODE_CHANGE, reboot_mode, new_reboot_mode) + + reboot_mode = new_reboot_mode + return TopicResponse() + + if(DMAPI5_TOPIC_COMMAND_INSTANCE_RENAMED) + var/new_instance_name = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_INSTANCE_NAME] + if(!istext(new_instance_name)) + return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_INSTANCE_NAME]!") + + if(event_handler != null) + event_handler.HandleEvent(TGS_EVENT_INSTANCE_RENAMED, new_instance_name) + + instance_name = new_instance_name + return TopicResponse() + + if(DMAPI5_TOPIC_COMMAND_CHAT_CHANNELS_UPDATE) + TGS_DEBUG_LOG("ProcessTopicCommand: It's a chat update") + var/list/chat_update_json = topic_parameters[DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE] + if(!istype(chat_update_json)) + TGS_DEBUG_LOG("ProcessTopicCommand: failed \"[DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE]\" check") + return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE]!") + + DecodeChannels(chat_update_json) + return TopicResponse() + + if(DMAPI5_TOPIC_COMMAND_SERVER_PORT_UPDATE) + var/new_port = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_PORT] + if (!isnum(new_port) || !(new_port > 0)) + return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_PORT]") + + server_port = new_port + return TopicResponse() + + if(DMAPI5_TOPIC_COMMAND_HEALTHCHECK) + if(event_handler && event_handler.receive_health_checks) + event_handler.HandleEvent(TGS_EVENT_HEALTH_CHECK) + return TopicResponse() + + if(DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH) + detached = FALSE + var/new_port = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_PORT] + var/error_message = null + if (new_port != null) + if (!isnum(new_port) || !(new_port > 0)) + error_message = "Invalid [DMAPI5_TOPIC_PARAMETER_NEW_PORT]" + else + server_port = new_port + + var/new_version_string = topic_parameters[DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION] + if (!istext(new_version_string)) + if(error_message != null) + error_message += ", " + error_message += "Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION]" + else + var/datum/tgs_version/new_version = new(new_version_string) + if (event_handler) + event_handler.HandleEvent(TGS_EVENT_WATCHDOG_REATTACH, new_version) + + version = new_version + + var/list/reattach_response = TopicResponse(error_message) + reattach_response[DMAPI5_PARAMETER_CUSTOM_COMMANDS] = ListCustomCommands() + reattach_response[DMAPI5_PARAMETER_TOPIC_PORT] = GetTopicPort() + return reattach_response + + if(DMAPI5_TOPIC_COMMAND_SEND_CHUNK) + var/list/chunk = topic_parameters[DMAPI5_CHUNK] + if(!istype(chunk)) + return TopicResponse("Invalid [DMAPI5_CHUNK]!") + + var/payload_id = chunk[DMAPI5_CHUNK_PAYLOAD_ID] + if(!isnum(payload_id)) + return TopicResponse("[DMAPI5_CHUNK_PAYLOAD_ID] is not a number!") + + // Always updated the highest known payload ID + chunked_requests = max(chunked_requests, payload_id) + + var/sequence_id = chunk[DMAPI5_CHUNK_SEQUENCE_ID] + if(!isnum(sequence_id)) + return TopicResponse("[DMAPI5_CHUNK_SEQUENCE_ID] is not a number!") + + var/total_chunks = chunk[DMAPI5_CHUNK_TOTAL] + if(!isnum(total_chunks)) + return TopicResponse("[DMAPI5_CHUNK_TOTAL] is not a number!") + + if(total_chunks == 0) + return TopicResponse("[DMAPI5_CHUNK_TOTAL] is zero!") + + var/payload = chunk[DMAPI5_CHUNK_PAYLOAD] + if(!istext(payload)) + return TopicResponse("[DMAPI5_CHUNK_PAYLOAD] is not text!") + + var/cache_key = "request[payload_id]" + var/payloads = chunked_topics[cache_key] + + if(!payloads) + payloads = new /list(total_chunks) + chunked_topics[cache_key] = payloads + + if(total_chunks != length(payloads)) + chunked_topics -= cache_key + return TopicResponse("Received differing total chunks for same [DMAPI5_CHUNK_PAYLOAD_ID]! Invalidating [DMAPI5_CHUNK_PAYLOAD_ID]!") + + var/pre_existing_chunk = payloads[sequence_id + 1] + if(pre_existing_chunk && pre_existing_chunk != payload) + chunked_topics -= cache_key + return TopicResponse("Received differing payload for same [DMAPI5_CHUNK_SEQUENCE_ID]! Invalidating [DMAPI5_CHUNK_PAYLOAD_ID]!") + + payloads[sequence_id + 1] = payload + + var/list/missing_sequence_ids = list() + for(var/i in 1 to total_chunks) + if(!payloads[i]) + missing_sequence_ids += i - 1 + + if(length(missing_sequence_ids)) + return list(DMAPI5_MISSING_CHUNKS = missing_sequence_ids) + + chunked_topics -= cache_key + var/full_json = jointext(payloads, "") + + return ProcessRawTopic(full_json, FALSE) + + if(DMAPI5_TOPIC_COMMAND_RECEIVE_CHUNK) + var/payload_id = topic_parameters[DMAPI5_CHUNK_PAYLOAD_ID] + if(!isnum(payload_id)) + return TopicResponse("[DMAPI5_CHUNK_PAYLOAD_ID] is not a number!") + + // Always updated the highest known payload ID + chunked_requests = max(chunked_requests, payload_id) + + var/list/missing_chunks = topic_parameters[DMAPI5_MISSING_CHUNKS] + if(!istype(missing_chunks) || !length(missing_chunks)) + return TopicResponse("Missing or empty [DMAPI5_MISSING_CHUNKS]!") + + var/sequence_id_to_send = missing_chunks[1] + if(!isnum(sequence_id_to_send)) + return TopicResponse("[DMAPI5_MISSING_CHUNKS] contained a non-number!") + + var/cache_key = ResponseTopicChunkCacheKey(payload_id) + var/list/chunks = chunked_topics[cache_key] + if(!chunks) + return TopicResponse("Unknown response chunk set: P[payload_id]!") + + // sequence IDs in interop chunking are always zero indexed + var/chunk_to_send = chunks[sequence_id_to_send + 1] + if(!chunk_to_send) + return TopicResponse("Sequence ID [sequence_id_to_send] is not present in response chunk P[payload_id]!") + + if(length(missing_chunks) == 1) + // sending last chunk, purge the cache + chunked_topics -= cache_key + + return chunk_to_send + + if(DMAPI5_TOPIC_COMMAND_RECEIVE_BROADCAST) + var/message = topic_parameters[DMAPI5_TOPIC_PARAMETER_BROADCAST_MESSAGE] + if (!istext(message)) + return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_BROADCAST_MESSAGE]") + + TGS_WORLD_ANNOUNCE(message) + return TopicResponse() + + return TopicResponse("Unknown command: [command]") + +/datum/tgs_api/v5/proc/WorldBroadcast(message) + set waitfor = FALSE + TGS_WORLD_ANNOUNCE(message) diff --git a/code/modules/tgs/v5/undefs.dm b/code/modules/tgs/v5/undefs.dm index 5885a60e75..d531d4b7b9 100644 --- a/code/modules/tgs/v5/undefs.dm +++ b/code/modules/tgs/v5/undefs.dm @@ -4,15 +4,28 @@ #undef DMAPI5_BRIDGE_DATA #undef DMAPI5_TOPIC_DATA -#undef DMAPI5_BRIDGE_COMMAND_PORT_UPDATE +#undef DMAPI5_BRIDGE_REQUEST_LIMIT +#undef DMAPI5_TOPIC_REQUEST_LIMIT +#undef DMAPI5_TOPIC_RESPONSE_LIMIT + #undef DMAPI5_BRIDGE_COMMAND_STARTUP #undef DMAPI5_BRIDGE_COMMAND_PRIME #undef DMAPI5_BRIDGE_COMMAND_REBOOT #undef DMAPI5_BRIDGE_COMMAND_KILL #undef DMAPI5_BRIDGE_COMMAND_CHAT_SEND +#undef DMAPI5_BRIDGE_COMMAND_CHUNK #undef DMAPI5_PARAMETER_ACCESS_IDENTIFIER #undef DMAPI5_PARAMETER_CUSTOM_COMMANDS +#undef DMAPI5_PARAMETER_TOPIC_PORT + +#undef DMAPI5_CHUNK +#undef DMAPI5_CHUNK_PAYLOAD +#undef DMAPI5_CHUNK_TOTAL +#undef DMAPI5_CHUNK_SEQUENCE_ID +#undef DMAPI5_CHUNK_PAYLOAD_ID + +#undef DMAPI5_MISSING_CHUNKS #undef DMAPI5_RESPONSE_ERROR_MESSAGE @@ -25,7 +38,6 @@ #undef DMAPI5_BRIDGE_RESPONSE_NEW_PORT #undef DMAPI5_BRIDGE_RESPONSE_RUNTIME_INFORMATION -#undef DMAPI5_CHAT_MESSAGE_TEXT #undef DMAPI5_CHAT_MESSAGE_CHANNEL_IDS #undef DMAPI5_RUNTIME_INFORMATION_ACCESS_IDENTIFIER @@ -36,6 +48,7 @@ #undef DMAPI5_RUNTIME_INFORMATION_REVISION #undef DMAPI5_RUNTIME_INFORMATION_TEST_MERGES #undef DMAPI5_RUNTIME_INFORMATION_SECURITY_LEVEL +#undef DMAPI5_RUNTIME_INFORMATION_VISIBILITY #undef DMAPI5_CHAT_UPDATE_CHANNELS @@ -63,8 +76,11 @@ #undef DMAPI5_TOPIC_COMMAND_INSTANCE_RENAMED #undef DMAPI5_TOPIC_COMMAND_CHAT_CHANNELS_UPDATE #undef DMAPI5_TOPIC_COMMAND_SERVER_PORT_UPDATE -#undef DMAPI5_TOPIC_COMMAND_HEARTBEAT +#undef DMAPI5_TOPIC_COMMAND_HEALTHCHECK #undef DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH +#undef DMAPI5_TOPIC_COMMAND_SEND_CHUNK +#undef DMAPI5_TOPIC_COMMAND_RECEIVE_CHUNK +#undef DMAPI5_TOPIC_COMMAND_RECEIVE_BROADCAST #undef DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE #undef DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND @@ -74,7 +90,9 @@ #undef DMAPI5_TOPIC_PARAMETER_NEW_INSTANCE_NAME #undef DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE #undef DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION +#undef DMAPI5_TOPIC_PARAMETER_BROADCAST_MESSAGE +#undef DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE #undef DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE_MESSAGE #undef DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES @@ -93,6 +111,7 @@ #undef DMAPI5_CHAT_CHANNEL_IS_ADMIN_CHANNEL #undef DMAPI5_CHAT_CHANNEL_IS_PRIVATE_CHANNEL #undef DMAPI5_CHAT_CHANNEL_TAG +#undef DMAPI5_CHAT_CHANNEL_EMBEDS_SUPPORTED #undef DMAPI5_CUSTOM_CHAT_COMMAND_NAME #undef DMAPI5_CUSTOM_CHAT_COMMAND_HELP_TEXT diff --git a/librust_g.so b/librust_g.so index 239f12d713..0dd65f23ef 100644 Binary files a/librust_g.so and b/librust_g.so differ diff --git a/rust_g.dll b/rust_g.dll index ebb6c9fd19..30f63e72f4 100644 Binary files a/rust_g.dll and b/rust_g.dll differ diff --git a/tools/tgs_scripts/PreCompile.bat b/tools/tgs_scripts/PreCompile.bat new file mode 100644 index 0000000000..90bd6dd673 --- /dev/null +++ b/tools/tgs_scripts/PreCompile.bat @@ -0,0 +1,34 @@ +@echo off + +FOR /F "tokens=* USEBACKQ" %%F IN (`powershell -NoLogo -ExecutionPolicy Bypass -File "get_dependencies.ps1"`) DO ( +SET RUST_G_VERSION=%%F +) + +SET "original_dir=%CD%" +cd "%~1" + +::set "RUST_G_VERSION=3.1.0" + +cd "%original_dir%" +IF NOT exist "rust-g"\ ( + echo "Cloning rust-g..." + git "clone" "https://github.com/tgstation/rust-g" + cd "rust-g" + rustup "target" "add" "i686-pc-windows-msvc" +) ELSE ( + echo "Fetching rust-g..." + cd "rust-g" + git "fetch" + rustup "target" "add" "i686-pc-windows-msvc" +) +echo "Deploying rust-g..." +git "checkout" "%RUST_G_VERSION%" +set PKG_CONFIG_ALLOW_CROSS=1 +cargo build --release --target=i686-pc-windows-msvc +move "%CD%\target\i686-pc-windows-msvc\release\rust_g.dll" "%~1/rust_g.dll" +cd ".." +echo "Compiling tgui..." +cd "%~1" +set TG_BOOTSTRAP_CACHE=%original_dir% +set "CBT_BUILD_MODE=TGS" +tools/bootstrap/node.bat tools/build/build.js diff --git a/tools/tgs_scripts/PreCompile.sh b/tools/tgs_scripts/PreCompile.sh new file mode 100755 index 0000000000..0f81c7d4d0 --- /dev/null +++ b/tools/tgs_scripts/PreCompile.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +set -e +set -x + +#load dep exports +#need to switch to game dir for Dockerfile weirdness +original_dir=$PWD +cd "$1" +. _build_dependencies.sh +cd "$original_dir" + + +# update rust-g +if [ ! -d "rust-g" ]; then + echo "Cloning rust-g..." + git clone https://github.com/tgstation/rust-g + cd rust-g + ~/.cargo/bin/rustup target add i686-unknown-linux-gnu +else + echo "Fetching rust-g..." + cd rust-g + git fetch + ~/.cargo/bin/rustup target add i686-unknown-linux-gnu +fi + +echo "Deploying rust-g..." +git checkout "$RUST_G_VERSION" +env PKG_CONFIG_ALLOW_CROSS=1 ~/.cargo/bin/cargo build --release --target=i686-unknown-linux-gnu +mv target/i686-unknown-linux-gnu/release/librust_g.so "$1/librust_g.so" +cd .. + +# compile tgui +echo "Compiling tgui..." +cd "$1" +env TG_BOOTSTRAP_CACHE="$original_dir" TG_BOOTSTRAP_NODE_LINUX=1 CBT_BUILD_MODE="TGS" tools/bootstrap/node tools/build/build.js diff --git a/tools/tgs_scripts/get_dependencies.ps1 b/tools/tgs_scripts/get_dependencies.ps1 new file mode 100644 index 0000000000..725db0861c --- /dev/null +++ b/tools/tgs_scripts/get_dependencies.ps1 @@ -0,0 +1,14 @@ +function Extract-Variable { + param([string] $Path, [string] $Key) + foreach ($Line in Get-Content $Path) { + if ($Line.StartsWith("export $Key=")) { + return $Line.Substring("export $Key=".Length) + } + } + throw "Couldn't find value for $Key in $Path" +} + +$BaseDir = Split-Path $script:MyInvocation.MyCommand.Path +$RustgVersion = Extract-Variable -Path "$BaseDir\..\..\_build_dependencies.sh" -Key "RUST_G_VERSION" + +return $RustgVersion \ No newline at end of file diff --git a/vorestation.dme b/vorestation.dme index b73438484d..0db0b3ba67 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -46,6 +46,7 @@ #include "code\__defines\cooldowns.dm" #include "code\__defines\crafting.dm" #include "code\__defines\damage_organs.dm" +#include "code\__defines\database.dm" #include "code\__defines\dna.dm" #include "code\__defines\exosuit_fab.dm" #include "code\__defines\flags.dm"