From 8d6fbc1512e976c8b84ed268b1ec1d3207d5b37b Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 15 Apr 2025 00:52:53 +0000 Subject: [PATCH] Update TGS DMAPI --- code/__DEFINES/tgs.dm | 292 ++++++++++++++------ code/modules/tgs/LICENSE | 2 +- code/modules/tgs/README.md | 2 +- code/modules/tgs/core/README.md | 2 +- code/modules/tgs/core/_definitions.dm | 8 + code/modules/tgs/core/byond_world_export.dm | 22 ++ code/modules/tgs/core/core.dm | 33 ++- code/modules/tgs/core/datum.dm | 19 +- code/modules/tgs/core/tgs_version.dm | 1 + code/modules/tgs/includes.dm | 1 + code/modules/tgs/v3210/api.dm | 2 +- code/modules/tgs/v3210/commands.dm | 4 +- code/modules/tgs/v4/api.dm | 26 +- code/modules/tgs/v4/commands.dm | 2 +- code/modules/tgs/v5/__interop_version.dm | 2 +- code/modules/tgs/v5/_defines.dm | 21 +- code/modules/tgs/v5/api.dm | 154 +++++++++-- code/modules/tgs/v5/bridge.dm | 39 ++- code/modules/tgs/v5/commands.dm | 4 +- code/modules/tgs/v5/serializers.dm | 16 +- code/modules/tgs/v5/topic.dm | 80 ++++-- code/modules/tgs/v5/undefs.dm | 19 +- 22 files changed, 569 insertions(+), 182 deletions(-) create mode 100644 code/modules/tgs/core/byond_world_export.dm diff --git a/code/__DEFINES/tgs.dm b/code/__DEFINES/tgs.dm index c8ef71112b33..7e1ba820dd8b 100644 --- a/code/__DEFINES/tgs.dm +++ b/code/__DEFINES/tgs.dm @@ -1,19 +1,20 @@ // tgstation-server DMAPI +// The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in IETF RFC 2119. -#define TGS_DMAPI_VERSION "6.4.2" +#define TGS_DMAPI_VERSION "7.3.1" // All functions and datums outside this document are subject to change with any version and should not be relied on. // CONFIGURATION -/// Create this define if you want to do TGS configuration outside of this file. +/// Consumers SHOULD create this define if you want to do TGS configuration outside of this file. #ifndef TGS_EXTERNAL_CONFIGURATION -// Comment this out once you've filled in the below. +// Consumers MUST comment this out once you've filled in the below and are not using [TGS_EXTERNAL_CONFIGURATION]. #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()() +// Consumers MUST uncomment this if you wish to allow the game to interact with TGS version 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): @@ -50,46 +51,60 @@ #endif +#ifndef TGS_FILE2TEXT_NATIVE +#ifdef file2text +#error Your codebase is re-defining the BYOND proc file2text. The DMAPI requires the native version to read the result of world.Export(). You SHOULD fix this by adding "#define TGS_FILE2TEXT_NATIVE file2text" before your override of file2text to allow the DMAPI to use the native version. This will only be used for world.Export(), not regular file accesses +#endif +#define TGS_FILE2TEXT_NATIVE file2text +#endif + +// SpacemanDMM compatibility +#ifndef CAN_BE_REDEFINED +#define CAN_BE_REDEFINED(X) +#endif + // 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 @@ -104,6 +119,12 @@ #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 @@ -114,6 +135,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. @@ -121,34 +143,48 @@ /// 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 /** - * Call this somewhere in [/world/proc/New] that is always run. This function may sleep! + * Consumers MUST call this somewhere in [/world/proc/New] that is always run. This function may sleep! * * * 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]. + * * http_handler - Optional user defined [/datum/tgs_http_handler]. */ -/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_ULTRASAFE, datum/tgs_http_handler/http_handler) + CAN_BE_REDEFINED(TRUE) return /** - * Call this when your initializations are complete and your game is ready to play before any player interactions happen. + * Consumers MUST call this when world initializations are complete and the game is ready to play before any player interactions happen. * * This may use [/world/var/sleep_offline] to make this happen so ensure no changes are made to it while this call is running. * Afterwards, consider explicitly setting it to what you want to avoid this BYOND bug: http://www.byond.com/forum/post/2575184 * This function should not be called before ..() in [/world/proc/New]. */ /world/proc/TgsInitializationComplete() + CAN_BE_REDEFINED(TRUE) return -/// Put this at the start of [/world/proc/Topic]. +/// Consumers MUST run this macro at the start of [/world/proc/Topic]. #define TGS_TOPIC var/tgs_topic_return = TgsTopic(args[1]); if(tgs_topic_return) return tgs_topic_return -/** - * Call this as late as possible in [world/proc/Reboot]. - */ +/// Consumers MUST call this as late as possible in [world/proc/Reboot] (BEFORE ..()). /world/proc/TgsReboot() + CAN_BE_REDEFINED(TRUE) return // DATUM DEFINITIONS @@ -158,34 +194,35 @@ /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 /** * Returns [TRUE]/[FALSE] based on if the [/datum/tgs_version] contains wildcards. */ /datum/tgs_version/proc/Wildcard() + CAN_BE_REDEFINED(TRUE) return /** @@ -194,6 +231,7 @@ * other_version - The [/datum/tgs_version] to compare against. */ /datum/tgs_version/proc/Equals(datum/tgs_version/other_version) + CAN_BE_REDEFINED(TRUE) return /// Represents a merge of a GitHub pull request. @@ -225,67 +263,113 @@ 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 + /// [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 This abstract version SHOULD be overridden to be used. +/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 handler for HTTP calls. This abstract version MUST be overridden to be used. +/datum/tgs_http_handler + +/** + * User definable callback for executing HTTP GET requests. + * MUST perform BYOND sleeps while the request is in flight. + * MUST return a [/datum/tgs_http_result]. + * SHOULD log its own errors + * + * url - The full URL to execute the GET request for including query parameters. + */ +/datum/tgs_http_handler/proc/PerformGet(url) + CRASH("[type]/PerformGet not implemented!") + +/// Result of a [/datum/tgs_http_handler] call. MUST NOT be overridden. +/datum/tgs_http_result + /// HTTP response as text + var/response_text + /// Boolean request success flag. Set for any 2XX response code. + var/success + +/** + * Create a [/datum/tgs_http_result]. + * + * * response_text - HTTP response as text. Must be provided in New(). + * * success - Boolean request success flag. Set for any 2XX response code. Must be provided in New(). + */ +/datum/tgs_http_result/New(response_text, success) + if(response_text && !istext(response_text)) + CRASH("response_text was not text!") + + src.response_text = response_text + src.success = success + +/// User definable chat command. This abstract version MUST be overridden to be used. /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 [/datum/tgs_message_content] to respond to the issuer with. + * MUST be implemented * - * sender - The [/datum/tgs_chat_user] who issued the command. - * params - The trimmed string following the command `/datum/tgs_chat_command/var/name]. + * * sender - The [/datum/tgs_chat_user] who issued the command. + * * params - The trimmed string following the command `/datum/tgs_chat_command/var/name]. */ /datum/tgs_chat_command/proc/Run(datum/tgs_chat_user/sender, params) CRASH("[type] has no implementation for Run()") -/// User definable chat message +/// User definable chat message. MUST NOT be overridden. /datum/tgs_message_content - /// The tring content of the message. Must be provided in New(). + /// The string 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 +/** + * Create a [/datum/tgs_message_content]. + * + * * text - The string content of the message. + */ /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. +/// User definable chat embed. Currently mirrors Discord chat embeds. See https://discord.com/developers/docs/resources/message#embed-object for details. /datum/tgs_chat_embed/structure var/title var/description @@ -294,16 +378,16 @@ /// 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 + /// 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. + /// See https://discord.com/developers/docs/resources/message#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. + /// See https://discord.com/developers/docs/resources/message#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. + /// See https://discord.com/developers/docs/resources/message#embed-object-embed-video-structure for details. var/datum/tgs_chat_embed/media/video var/datum/tgs_chat_embed/footer/footer @@ -312,7 +396,7 @@ var/list/datum/tgs_chat_embed/field/fields -/// Common datum for similar discord embed medias +/// Common datum for similar Discord embed medias. /datum/tgs_chat_embed/media /// Must be set in New(). var/url @@ -320,48 +404,58 @@ var/height var/proxy_url +/// Create a [/datum/tgs_chat_embed]. /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. +/// See https://discord.com/developers/docs/resources/message#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 +/// Create a [/datum/tgs_chat_embed/footer]. /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. +/// See https://discord.com/developers/docs/resources/message#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(). +/// See https://discord.com/developers/docs/resources/message#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 +/// Create a [/datum/tgs_chat_embed/footer]. /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(). +/// See https://discord.com/developers/docs/resources/message#embed-object-embed-field-structure for details. /datum/tgs_chat_embed/field + /// Must be set in New(). var/name + /// Must be set in New(). var/value var/is_inline +/// Create a [/datum/tgs_chat_embed/field]. /datum/tgs_chat_embed/field/New(name, value) + ..() if(!istext(name)) CRASH("[/datum/tgs_chat_embed/field] created with no name!") @@ -375,90 +469,126 @@ /// Returns the maximum supported [/datum/tgs_version] of the DMAPI. /world/proc/TgsMaximumApiVersion() + CAN_BE_REDEFINED(TRUE) return /// Returns the minimum supported [/datum/tgs_version] of the DMAPI. /world/proc/TgsMinimumApiVersion() + CAN_BE_REDEFINED(TRUE) return /** * Returns [TRUE] if DreamDaemon was launched under TGS, the API matches, and was properly initialized. [FALSE] will be returned otherwise. */ /world/proc/TgsAvailable() + CAN_BE_REDEFINED(TRUE) return // 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() + CAN_BE_REDEFINED(TRUE) return /** - * Send a message to connected chats. + * Send a message to connected chats. This function may sleep! + * If TGS is offline when called, the message may be placed in a queue to be sent and this function will return immediately. Your message will be sent when TGS reconnects to the game. * * 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(datum/tgs_message_content/message, admin_only = FALSE) + CAN_BE_REDEFINED(TRUE) return /** - * Send a private message to a specific user. + * Send a private message to a specific user. This function may sleep! + * If TGS is offline when called, the message may be placed in a queue to be sent and this function will return immediately. Your message will be sent when TGS reconnects to the game. * * message - The [/datum/tgs_message_content] to send. * user: The [/datum/tgs_chat_user] to PM. */ /world/proc/TgsChatPrivateMessage(datum/tgs_message_content/message, datum/tgs_chat_user/user) + CAN_BE_REDEFINED(TRUE) 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! + * If TGS is offline when called, the message may be placed in a queue to be sent and this function will return immediately. Your message will be sent when TGS reconnects to the game. * * 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(datum/tgs_message_content/message, list/channels = null) + CAN_BE_REDEFINED(TRUE) 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() + CAN_BE_REDEFINED(TRUE) 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() + CAN_BE_REDEFINED(TRUE) + 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() + CAN_BE_REDEFINED(TRUE) 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() + CAN_BE_REDEFINED(TRUE) 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() + CAN_BE_REDEFINED(TRUE) 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() + CAN_BE_REDEFINED(TRUE) 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() + CAN_BE_REDEFINED(TRUE) + 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() + CAN_BE_REDEFINED(TRUE) 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() + CAN_BE_REDEFINED(TRUE) + return + +/** + * Trigger an event in TGS. Requires TGS version >= 6.3.0. Returns [TRUE] if the event was triggered successfully, [FALSE] otherwise. This function may sleep! + * + * event_name - The name of the event to trigger + * parameters - Optional list of string parameters to pass as arguments to the event script. The first parameter passed to a script will always be the running game's directory followed by these parameters. + * wait_for_completion - If set, this function will not return until the event has run to completion. + */ +/world/proc/TgsTriggerEvent(event_name, list/parameters, wait_for_completion = FALSE) + CAN_BE_REDEFINED(TRUE) return /* The MIT License -Copyright (c) 2017 Jordan Brown +Copyright (c) 2017-2024 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/LICENSE b/code/modules/tgs/LICENSE index 687ebbd4236a..8991a68881b4 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-2024 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 6319028d8106..35ca73d7e9a8 100644 --- a/code/modules/tgs/README.md +++ b/code/modules/tgs/README.md @@ -1,6 +1,6 @@ # DMAPI Internals -This folder should be placed on it's own inside a codebase that wishes to use the TGS DMAPI. Warranty void if modified. +This folder should be placed on its own inside a codebase that wishes to use the TGS DMAPI. Warranty void if modified. - [includes.dm](./includes.dm) is the file that should be included by DM code, it handles including the rest. - The [core](./core) folder includes all code not directly part of any API version. diff --git a/code/modules/tgs/core/README.md b/code/modules/tgs/core/README.md index b82d8f49e297..965e21b549a3 100644 --- a/code/modules/tgs/core/README.md +++ b/code/modules/tgs/core/README.md @@ -3,7 +3,7 @@ This folder contains all DMAPI code not directly involved in an API. - [_definitions.dm](./definitions.dm) contains defines needed across DMAPI internals. +- [byond_world_export.dm](./byond_world_export.dm) contains the default `/datum/tgs_http_handler` implementation which uses `world.Export()`. - [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 -- diff --git a/code/modules/tgs/core/_definitions.dm b/code/modules/tgs/core/_definitions.dm index ebf6d17c2a07..fd98034eb716 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/byond_world_export.dm b/code/modules/tgs/core/byond_world_export.dm new file mode 100644 index 000000000000..6ef8d841b8f7 --- /dev/null +++ b/code/modules/tgs/core/byond_world_export.dm @@ -0,0 +1,22 @@ +/datum/tgs_http_handler/byond_world_export + +/datum/tgs_http_handler/byond_world_export/PerformGet(url) + // This is an infinite sleep until we get a response + var/export_response = world.Export(url) + TGS_DEBUG_LOG("byond_world_export: Export complete") + + if(!export_response) + TGS_ERROR_LOG("byond_world_export: Failed request: [url]") + return new /datum/tgs_http_result(null, FALSE) + + var/content = export_response["CONTENT"] + if(!content) + TGS_ERROR_LOG("byond_world_export: Failed request, missing content!") + return new /datum/tgs_http_result(null, FALSE) + + var/response_json = TGS_FILE2TEXT_NATIVE(content) + if(!response_json) + TGS_ERROR_LOG("byond_world_export: Failed request, failed to load content!") + return new /datum/tgs_http_result(null, FALSE) + + return new /datum/tgs_http_result(response_json, TRUE) diff --git a/code/modules/tgs/core/core.dm b/code/modules/tgs/core/core.dm index 41a047339452..63cb5a2c3514 100644 --- a/code/modules/tgs/core/core.dm +++ b/code/modules/tgs/core/core.dm @@ -1,4 +1,4 @@ -/world/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE) +/world/TgsNew(datum/tgs_event_handler/event_handler, minimum_required_security_level = TGS_SECURITY_ULTRASAFE, datum/tgs_http_handler/http_handler = null) var/current_api = TGS_READ_GLOBAL(tgs) if(current_api) TGS_ERROR_LOG("API datum already set (\ref[current_api] ([current_api]))! Was TgsNew() called more than once?") @@ -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]") @@ -55,7 +55,10 @@ TGS_ERROR_LOG("Invalid parameter for event_handler: [event_handler]") event_handler = null - var/datum/tgs_api/new_api = new api_datum(event_handler, version) + if(!http_handler) + http_handler = new /datum/tgs_http_handler/byond_world_export + + var/datum/tgs_api/new_api = new api_datum(event_handler, version, http_handler) TGS_WRITE_GLOBAL(tgs, new_api) @@ -107,6 +110,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 +163,17 @@ /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() + +/world/TgsTriggerEvent(event_name, list/parameters, wait_for_completion = FALSE) + var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs) + if(api) + if(!istype(parameters, /list)) + parameters = list() + + return api.TriggerEvent(event_name, parameters, wait_for_completion) diff --git a/code/modules/tgs/core/datum.dm b/code/modules/tgs/core/datum.dm index 68b0330fe860..3ca53e9bf7c6 100644 --- a/code/modules/tgs/core/datum.dm +++ b/code/modules/tgs/core/datum.dm @@ -6,11 +6,20 @@ TGS_DEFINE_AND_SET_GLOBAL(tgs, null) var/list/warned_deprecated_command_runs -/datum/tgs_api/New(datum/tgs_event_handler/event_handler, datum/tgs_version/version) - . = ..() +/datum/tgs_api/New(datum/tgs_event_handler/event_handler, datum/tgs_version/version, datum/tgs_http_handler/http_handler) + ..() 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(world.tick_lag) + 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 @@ -57,3 +66,9 @@ TGS_PROTECT_DATUM(/datum/tgs_api) /datum/tgs_api/proc/SecurityLevel() return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/Visibility() + return TGS_UNIMPLEMENTED + +/datum/tgs_api/proc/TriggerEvent(event_name, list/parameters, wait_for_completion) + return FALSE diff --git a/code/modules/tgs/core/tgs_version.dm b/code/modules/tgs/core/tgs_version.dm index a5dae1241a30..bc561e67487a 100644 --- a/code/modules/tgs/core/tgs_version.dm +++ b/code/modules/tgs/core/tgs_version.dm @@ -1,4 +1,5 @@ /datum/tgs_version/New(raw_parameter) + ..() src.raw_parameter = raw_parameter deprefixed_parameter = replacetext(raw_parameter, "/tg/station 13 Server v", "") var/list/version_bits = splittext(deprefixed_parameter, ".") diff --git a/code/modules/tgs/includes.dm b/code/modules/tgs/includes.dm index 23b714f9d064..f5118ed55a3c 100644 --- a/code/modules/tgs/includes.dm +++ b/code/modules/tgs/includes.dm @@ -1,4 +1,5 @@ #include "core\_definitions.dm" +#include "core\byond_world_export.dm" #include "core\core.dm" #include "core\datum.dm" #include "core\tgs_version.dm" diff --git a/code/modules/tgs/v3210/api.dm b/code/modules/tgs/v3210/api.dm index b881662d71cc..666201a32256 100644 --- a/code/modules/tgs/v3210/api.dm +++ b/code/modules/tgs/v3210/api.dm @@ -179,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 diff --git a/code/modules/tgs/v3210/commands.dm b/code/modules/tgs/v3210/commands.dm index d9bd287465b9..e65c816320dc 100644 --- a/code/modules/tgs/v3210/commands.dm +++ b/code/modules/tgs/v3210/commands.dm @@ -47,7 +47,7 @@ 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]>" @@ -55,4 +55,4 @@ var/datum/tgs_message_content/result = stc.Run(user, params) result = UpgradeDeprecatedCommandResponse(result, command) - return result?.text || TRUE + return result ? result.text : TRUE diff --git a/code/modules/tgs/v4/api.dm b/code/modules/tgs/v4/api.dm index 2f05c3863380..7c87922750b9 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"] @@ -181,14 +181,14 @@ var/json = json_encode(data) while(requesting_new_port && !override_requesting_new_port) - sleep(1) + sleep(world.tick_lag) //we need some port open at this point to facilitate return communication if(!world.port) 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,20 +196,20 @@ 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) - sleep(1) + sleep(world.tick_lag) export_lock = TRUE last_interop_response = null @@ -217,7 +217,7 @@ text2file(json, server_commands_json_path) for(var/I = 0; I < EXPORT_TIMEOUT_DS && !last_interop_response; ++I) - sleep(1) + sleep(world.tick_lag) if(!last_interop_response) TGS_ERROR_LOG("Failed to get export result for: [json]") @@ -263,7 +263,12 @@ for(var/I in channels) var/datum/tgs_chat_channel/channel = I ids += channel.id + message = UpgradeDeprecatedChatMessage(message) + + if (!length(channels)) + return + message = list("message" = message.text, "channelIds" = ids) if(intercepted_message_queue) intercepted_message_queue += list(message) @@ -276,7 +281,12 @@ 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 = UpgradeDeprecatedChatMessage(message) + + if (!length(channels)) + return + message = list("message" = message.text, "channelIds" = channels) if(intercepted_message_queue) intercepted_message_queue += list(message) diff --git a/code/modules/tgs/v4/commands.dm b/code/modules/tgs/v4/commands.dm index d6d3d718d471..25dd6740e3af 100644 --- a/code/modules/tgs/v4/commands.dm +++ b/code/modules/tgs/v4/commands.dm @@ -40,5 +40,5 @@ var/datum/tgs_message_content/result = sc.Run(u, params) result = UpgradeDeprecatedCommandResponse(result, command) - return result?.text + return result ? result.text : TRUE return "Unknown command: [command]!" diff --git a/code/modules/tgs/v5/__interop_version.dm b/code/modules/tgs/v5/__interop_version.dm index 6ef7c86ef75b..29ea239ad84d 100644 --- a/code/modules/tgs/v5/__interop_version.dm +++ b/code/modules/tgs/v5/__interop_version.dm @@ -1 +1 @@ -"5.6.0" +"5.10.0" diff --git a/code/modules/tgs/v5/_defines.dm b/code/modules/tgs/v5/_defines.dm index a3f949081f16..a47bfd78000b 100644 --- a/code/modules/tgs/v5/_defines.dm +++ b/code/modules/tgs/v5/_defines.dm @@ -5,19 +5,20 @@ #define DMAPI5_TOPIC_DATA "tgs_data" #define DMAPI5_BRIDGE_REQUEST_LIMIT 8198 -#define DMAPI5_TOPIC_REQUEST_LIMIT 65529 -#define DMAPI5_TOPIC_RESPONSE_LIMIT 65528 +#define DMAPI5_TOPIC_REQUEST_LIMIT 65528 +#define DMAPI5_TOPIC_RESPONSE_LIMIT 65529 -#define DMAPI5_BRIDGE_COMMAND_PORT_UPDATE 0 #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_CHUNK 6 +#define DMAPI5_BRIDGE_COMMAND_EVENT 7 #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" @@ -34,6 +35,7 @@ #define DMAPI5_BRIDGE_PARAMETER_VERSION "version" #define DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE "chatMessage" #define DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL "minimumSecurityLevel" +#define DMAPI5_BRIDGE_PARAMETER_EVENT_INVOCATION "eventInvocation" #define DMAPI5_BRIDGE_RESPONSE_NEW_PORT "newPort" #define DMAPI5_BRIDGE_RESPONSE_RUNTIME_INFORMATION "runtimeInformation" @@ -48,6 +50,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" @@ -75,10 +78,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_SEND_CHUNK 9 #define DMAPI5_TOPIC_COMMAND_RECEIVE_CHUNK 10 +#define DMAPI5_TOPIC_COMMAND_RECEIVE_BROADCAST 11 +#define DMAPI5_TOPIC_COMMAND_COMPLETE_EVENT 12 #define DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE "commandType" #define DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND "chatCommand" @@ -88,7 +93,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_CLIENT_COUNT "clientCount" #define DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE "commandResponse" #define DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE_MESSAGE "commandResponseMessage" #define DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES "chatResponses" @@ -113,3 +120,9 @@ #define DMAPI5_CUSTOM_CHAT_COMMAND_NAME "name" #define DMAPI5_CUSTOM_CHAT_COMMAND_HELP_TEXT "helpText" #define DMAPI5_CUSTOM_CHAT_COMMAND_ADMIN_ONLY "adminOnly" + +#define DMAPI5_EVENT_ID "eventId" + +#define DMAPI5_EVENT_INVOCATION_NAME "eventName" +#define DMAPI5_EVENT_INVOCATION_PARAMETERS "parameters" +#define DMAPI5_EVENT_INVOCATION_NOTIFY_COMPLETION "notifyCompletion" diff --git a/code/modules/tgs/v5/api.dm b/code/modules/tgs/v5/api.dm index 517240f12f8a..3e328fc7c27d 100644 --- a/code/modules/tgs/v5/api.dm +++ b/code/modules/tgs/v5/api.dm @@ -4,11 +4,16 @@ var/instance_name var/security_level + var/visibility var/reboot_mode = TGS_REBOOT_MODE_NORMAL + /// List of chat messages list()s that attempted to be sent during a topic call. To be bundled in the result of the call var/list/intercepted_message_queue + /// List of chat messages list()s that attempted to be sent during a topic call. To be bundled in the result of the call + var/list/offline_message_queue + var/list/custom_commands var/list/test_merges @@ -16,24 +21,43 @@ 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/list/pending_events = list() + var/detached = FALSE + var/datum/tgs_http_handler/http_handler + +/datum/tgs_api/v5/New(datum/tgs_event_handler/event_handler, datum/tgs_version/version, datum/tgs_http_handler/http_handler) + . = ..() + interop_version = version + src.http_handler = http_handler + TGS_DEBUG_LOG("V5 API created: [json_encode(args)]") + /datum/tgs_api/v5/ApiVersion() return new /datum/tgs_version( #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 + + // sleep once to prevent an issue where world.Export on the first tick can hang indefinitely + TGS_DEBUG_LOG("Starting Export bug prevention sleep tick. time:[world.time] sleep_offline:[world.sleep_offline]") + sleep(world.tick_lag) + TGS_DEBUG_LOG("Export bug prevention sleep complete") + + 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 @@ -45,10 +69,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] @@ -95,18 +121,36 @@ 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) - sleep(1) + TGS_DEBUG_LOG("RequireInitialBridgeResponse()") + var/logged = FALSE + while(!initial_bridge_request_received) + if(!logged) + TGS_DEBUG_LOG("RequireInitialBridgeResponse: Starting sleep") + logged = TRUE + + sleep(world.tick_lag) + + TGS_DEBUG_LOG("RequireInitialBridgeResponse: Passed") /datum/tgs_api/v5/OnInitializationComplete() Bridge(DMAPI5_BRIDGE_COMMAND_PRIME) /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 if(!initialized) @@ -156,7 +200,7 @@ 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/message, list/channels) +/datum/tgs_api/v5/ChatBroadcast(datum/tgs_message_content/message2, list/channels) if(!length(channels)) channels = ChatChannelInfo() @@ -165,43 +209,93 @@ var/datum/tgs_chat_channel/channel = I ids += channel.id - message = UpgradeDeprecatedChatMessage(message) - message = message._interop_serialize() - 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)) + SendChatMessageRaw(message2, ids) -/datum/tgs_api/v5/ChatTargetedBroadcast(datum/tgs_message_content/message, admin_only) +/datum/tgs_api/v5/ChatTargetedBroadcast(datum/tgs_message_content/message2, admin_only) var/list/channels = list() 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 = UpgradeDeprecatedChatMessage(message) - message = message._interop_serialize() - 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)) + SendChatMessageRaw(message2, channels) + +/datum/tgs_api/v5/ChatPrivateMessage(datum/tgs_message_content/message2, datum/tgs_chat_user/user) + SendChatMessageRaw(message2, list(user.channel.id)) + +/datum/tgs_api/v5/proc/SendChatMessageRaw(datum/tgs_message_content/message2, list/channel_ids) + message2 = UpgradeDeprecatedChatMessage(message2) + + if (!length(channel_ids)) + return -/datum/tgs_api/v5/ChatPrivateMessage(datum/tgs_message_content/message, datum/tgs_chat_user/user) - message = UpgradeDeprecatedChatMessage(message) - message = message._interop_serialize() - message[DMAPI5_CHAT_MESSAGE_CHANNEL_IDS] = list(user.channel.id) + var/list/data = message2._interop_serialize() + data[DMAPI5_CHAT_MESSAGE_CHANNEL_IDS] = channel_ids if(intercepted_message_queue) - intercepted_message_queue += list(message) + intercepted_message_queue += list(data) + return + + if(offline_message_queue) + offline_message_queue += list(data) + return + + if(detached) + offline_message_queue = list(data) + + WaitForReattach(FALSE) + + data = offline_message_queue + offline_message_queue = null + + for(var/queued_message in data) + SendChatDataRaw(queued_message) else - Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = message)) + SendChatDataRaw(data) + +/datum/tgs_api/v5/proc/SendChatDataRaw(list/data) + 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/TriggerEvent(event_name, list/parameters, wait_for_completion) + RequireInitialBridgeResponse() + WaitForReattach(TRUE) + + if(interop_version.minor < 9) + TGS_WARNING_LOG("Interop version too low for custom events!") + return FALSE + + var/str_parameters = list() + for(var/i in parameters) + str_parameters += "[i]" + + var/list/response = Bridge(DMAPI5_BRIDGE_COMMAND_EVENT, list(DMAPI5_BRIDGE_PARAMETER_EVENT_INVOCATION = list(DMAPI5_EVENT_INVOCATION_NAME = event_name, DMAPI5_EVENT_INVOCATION_PARAMETERS = str_parameters, DMAPI5_EVENT_INVOCATION_NOTIFY_COMPLETION = wait_for_completion))) + if(!response) + return FALSE + + var/event_id = response[DMAPI5_EVENT_ID] + if(!event_id) + return FALSE + + TGS_DEBUG_LOG("Created event ID: [event_id]") + if(!wait_for_completion) + return TRUE + + TGS_DEBUG_LOG("Waiting for completion of event ID: [event_id]") + + while(!pending_events[event_id]) + sleep(world.tick_lag) + + TGS_DEBUG_LOG("Completed wait on event ID: [event_id]") + pending_events -= event_id + + return TRUE + /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() @@ -226,3 +320,7 @@ /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 index b3cf77593974..62201fcc9e58 100644 --- a/code/modules/tgs/v5/bridge.dm +++ b/code/modules/tgs/v5/bridge.dm @@ -48,7 +48,9 @@ var/json = CreateBridgeData(command, data, TRUE) var/encoded_json = url_encode(json) - var/url = "http://127.0.0.1:[server_port]/Bridge?[DMAPI5_BRIDGE_DATA]=[encoded_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) @@ -59,32 +61,41 @@ var/json = json_encode(data) return json -/datum/tgs_api/v5/proc/PerformBridgeRequest(bridge_request) +/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) + sleep(world.tick_lag) + if(!detached && (!require_channels || length(chat_channels))) break - // dad went out for milk cigarettes 20 years ago... + // dad went out for milk and cigarettes 20 years ago... + // yes, this affects all other waiters, intentional if(i == 600) detached = 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]") +/datum/tgs_api/v5/proc/PerformBridgeRequest(bridge_request) + WaitForReattach(FALSE) + + TGS_DEBUG_LOG("Bridge request start") + var/datum/tgs_http_result/result = http_handler.PerformGet(bridge_request) + TGS_DEBUG_LOG("Bridge request complete") + + if(isnull(result)) + TGS_ERROR_LOG("Failed bridge request, handler returned null!") + return + + if(!istype(result) || result.type != /datum/tgs_http_result) + TGS_ERROR_LOG("Failed bridge request, handler returned non-[/datum/tgs_http_result]!") return - var/response_json = file2text(export_response["CONTENT"]) - if(!response_json) - TGS_ERROR_LOG("Failed bridge request, missing content!") + if(!result.success) + TGS_DEBUG_LOG("Failed bridge request, HTTP request failed!") return - var/list/bridge_response = json_decode(response_json) + var/list/bridge_response = json_decode(result.response_text) if(!bridge_response) - TGS_ERROR_LOG("Failed bridge request, bad json: [response_json]") + TGS_ERROR_LOG("Failed bridge request, bad json: [result.response_text]") return var/error = bridge_response[DMAPI5_RESPONSE_ERROR_MESSAGE] diff --git a/code/modules/tgs/v5/commands.dm b/code/modules/tgs/v5/commands.dm index c8f538db5932..9557f8a08ed5 100644 --- a/code/modules/tgs/v5/commands.dm +++ b/code/modules/tgs/v5/commands.dm @@ -37,8 +37,8 @@ response = UpgradeDeprecatedCommandResponse(response, command) var/list/topic_response = TopicResponse() - topic_response[DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE_MESSAGE] = response?.text - topic_response[DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE] = response?._interop_serialize() + 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]!") diff --git a/code/modules/tgs/v5/serializers.dm b/code/modules/tgs/v5/serializers.dm index 3a6fbe6be344..3a32848ad512 100644 --- a/code/modules/tgs/v5/serializers.dm +++ b/code/modules/tgs/v5/serializers.dm @@ -1,12 +1,12 @@ /datum/tgs_message_content/proc/_interop_serialize() - return list("text" = text, "embed" = embed?._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(islist(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()) @@ -16,12 +16,12 @@ "url" = url, "timestamp" = timestamp, "colour" = colour, - "image" = image?._interop_serialize(), - "thumbnail" = thumbnail?._interop_serialize(), - "video" = video?._interop_serialize(), - "footer" = footer?._interop_serialize(), - "provider" = provider?._interop_serialize(), - "author" = author?._interop_serialize(), + "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 ) diff --git a/code/modules/tgs/v5/topic.dm b/code/modules/tgs/v5/topic.dm index 28fcc14aef87..59e5e63e5cd4 100644 --- a/code/modules/tgs/v5/topic.dm +++ b/code/modules/tgs/v5/topic.dm @@ -5,6 +5,7 @@ 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!") @@ -25,16 +26,20 @@ 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) - return TopicResponse("Failed to decode [DMAPI5_PARAMETER_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) @@ -43,6 +48,7 @@ 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) @@ -55,7 +61,6 @@ return result 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]!") @@ -66,27 +71,30 @@ 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]!") + . = 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 - var/list/event_call = list(event_type) if (event_type == TGS_EVENT_WATCHDOG_DETACH) detached = TRUE + chat_channels.Cut() // https://github.com/tgstation/tgstation-server/issues/1490 - if(event_parameters) - event_call += event_parameters - - if(event_handler != null) - event_handler.HandleEvent(arglist(event_call)) - - var/list/response = TopicResponse() - response[DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES] = intercepted_message_queue - intercepted_message_queue = null - return response + 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]]") + return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_PORT]") if(event_handler != null) event_handler.HandleEvent(TGS_EVENT_PORT_SWAP, new_port) @@ -121,8 +129,10 @@ 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) @@ -131,13 +141,17 @@ 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]]") + 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_HEALTHCHECK) + if(event_handler && event_handler.receive_health_checks) + event_handler.HandleEvent(TGS_EVENT_HEALTH_CHECK) + var/list/health_check_response = TopicResponse() + health_check_response[DMAPI5_TOPIC_RESPONSE_CLIENT_COUNT] = TGS_CLIENT_COUNT + return health_check_response; if(DMAPI5_TOPIC_COMMAND_WATCHDOG_REATTACH) detached = FALSE @@ -145,7 +159,7 @@ var/error_message = null if (new_port != null) if (!isnum(new_port) || !(new_port > 0)) - error_message = "Invalid [DMAPI5_TOPIC_PARAMETER_NEW_PORT]]" + error_message = "Invalid [DMAPI5_TOPIC_PARAMETER_NEW_PORT]" else server_port = new_port @@ -153,7 +167,7 @@ if (!istext(new_version_string)) if(error_message != null) error_message += ", " - error_message += "Invalid or missing [DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION]]" + 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) @@ -163,6 +177,11 @@ var/list/reattach_response = TopicResponse(error_message) reattach_response[DMAPI5_PARAMETER_CUSTOM_COMMANDS] = ListCustomCommands() + reattach_response[DMAPI5_PARAMETER_TOPIC_PORT] = GetTopicPort() + + for(var/eventId in pending_events) + pending_events[eventId] = TRUE + return reattach_response if(DMAPI5_TOPIC_COMMAND_SEND_CHUNK) @@ -255,4 +274,25 @@ 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() + + if(DMAPI5_TOPIC_COMMAND_COMPLETE_EVENT) + var/event_id = topic_parameters[DMAPI5_EVENT_ID] + if (!istext(event_id)) + return TopicResponse("Invalid or missing [DMAPI5_EVENT_ID]") + + TGS_DEBUG_LOG("Completing event ID [event_id]...") + pending_events[event_id] = TRUE + 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 e3455b69d1c6..ca49e46cdffa 100644 --- a/code/modules/tgs/v5/undefs.dm +++ b/code/modules/tgs/v5/undefs.dm @@ -8,16 +8,17 @@ #undef DMAPI5_TOPIC_REQUEST_LIMIT #undef DMAPI5_TOPIC_RESPONSE_LIMIT -#undef DMAPI5_BRIDGE_COMMAND_PORT_UPDATE #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_BRIDGE_COMMAND_EVENT #undef DMAPI5_PARAMETER_ACCESS_IDENTIFIER #undef DMAPI5_PARAMETER_CUSTOM_COMMANDS +#undef DMAPI5_PARAMETER_TOPIC_PORT #undef DMAPI5_CHUNK #undef DMAPI5_CHUNK_PAYLOAD @@ -34,6 +35,7 @@ #undef DMAPI5_BRIDGE_PARAMETER_VERSION #undef DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE #undef DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL +#undef DMAPI5_BRIDGE_PARAMETER_EVENT_INVOCATION #undef DMAPI5_BRIDGE_RESPONSE_NEW_PORT #undef DMAPI5_BRIDGE_RESPONSE_RUNTIME_INFORMATION @@ -48,6 +50,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 @@ -75,8 +78,12 @@ #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_COMMAND_COMPLETE_EVENT #undef DMAPI5_TOPIC_PARAMETER_COMMAND_TYPE #undef DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND @@ -86,7 +93,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_CLIENT_COUNT #undef DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE #undef DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE_MESSAGE #undef DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES @@ -111,3 +120,9 @@ #undef DMAPI5_CUSTOM_CHAT_COMMAND_NAME #undef DMAPI5_CUSTOM_CHAT_COMMAND_HELP_TEXT #undef DMAPI5_CUSTOM_CHAT_COMMAND_ADMIN_ONLY + +#undef DMAPI5_EVENT_ID + +#undef DMAPI5_EVENT_INVOCATION_NAME +#undef DMAPI5_EVENT_INVOCATION_PARAMETERS +#undef DMAPI5_EVENT_INVOCATION_NOTIFY_COMPLETION