diff --git a/code/__DEFINES/tgs.dm b/code/__DEFINES/tgs.dm index 6187a67825a4..7e1ba820dd8b 100644 --- a/code/__DEFINES/tgs.dm +++ b/code/__DEFINES/tgs.dm @@ -1,18 +1,19 @@ // 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.5.3" +#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.. +// 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 @@ -50,6 +51,18 @@ #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. @@ -73,12 +86,12 @@ #define TGS_EVENT_REPO_MERGE_PULL_REQUEST 3 /// 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 +/// 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. @@ -108,7 +121,7 @@ // #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, byond version. +/// 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 @@ -122,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. @@ -129,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] (BEFORE ..()). - */ +/// Consumers MUST call this as late as possible in [world/proc/Reboot] (BEFORE ..()). /world/proc/TgsReboot() + CAN_BE_REDEFINED(TRUE) return // DATUM DEFINITIONS @@ -194,6 +222,7 @@ * Returns [TRUE]/[FALSE] based on if the [/datum/tgs_version] contains wildcards. */ /datum/tgs_version/proc/Wildcard() + CAN_BE_REDEFINED(TRUE) return /** @@ -202,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. @@ -249,7 +279,7 @@ /// The [/datum/tgs_chat_channel] the user was from. var/datum/tgs_chat_channel/channel -/// User definable handler for TGS events. +/// 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 @@ -263,7 +293,41 @@ 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 ...`. var/name = "" @@ -276,29 +340,36 @@ /** * 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 @@ -310,13 +381,13 @@ /// 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 @@ -325,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 @@ -333,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!") @@ -388,16 +469,19 @@ /// 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. @@ -409,67 +493,102 @@ * 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. 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. 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 /** * 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. This function may sleep if the call to [/world/proc/TgsNew] is sleeping! /world/proc/TgsVersion() + CAN_BE_REDEFINED(TRUE) + return + +/// 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. 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. 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. This function may sleep if the call to [/world/proc/TgsNew] is sleeping! /world/proc/TgsSecurityLevel() + CAN_BE_REDEFINED(TRUE) + return + +/// 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. 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-2023 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 85bca8c3f814..8991a68881b4 100644 --- a/code/modules/tgs/LICENSE +++ b/code/modules/tgs/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2017-2023 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/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/v4/api.dm b/code/modules/tgs/v4/api.dm index b9a75c4abb48..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]") diff --git a/code/modules/tgs/v5/__interop_version.dm b/code/modules/tgs/v5/__interop_version.dm index 5d3d491a7362..29ea239ad84d 100644 --- a/code/modules/tgs/v5/__interop_version.dm +++ b/code/modules/tgs/v5/__interop_version.dm @@ -1 +1 @@ -"5.6.1" +"5.10.0" diff --git a/code/modules/tgs/v5/_defines.dm b/code/modules/tgs/v5/_defines.dm index f973338daa03..a47bfd78000b 100644 --- a/code/modules/tgs/v5/_defines.dm +++ b/code/modules/tgs/v5/_defines.dm @@ -8,16 +8,17 @@ #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" @@ -79,6 +82,8 @@ #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 34cc43f8762f..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,15 +21,23 @@ 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 -/datum/tgs_api/v5/New() + 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) . = ..() - TGS_DEBUG_LOG("V5 API created") + 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( @@ -37,8 +50,14 @@ 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 @@ -50,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] @@ -100,15 +121,22 @@ 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() TGS_DEBUG_LOG("RequireInitialBridgeResponse()") var/logged = FALSE - while(!version) + while(!initial_bridge_request_received) if(!logged) TGS_DEBUG_LOG("RequireInitialBridgeResponse: Starting sleep") logged = TRUE - sleep(1) + sleep(world.tick_lag) TGS_DEBUG_LOG("RequireInitialBridgeResponse: Passed") @@ -181,17 +209,7 @@ var/datum/tgs_chat_channel/channel = I ids += channel.id - message2 = UpgradeDeprecatedChatMessage(message2) - - 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)) + SendChatMessageRaw(message2, ids) /datum/tgs_api/v5/ChatTargetedBroadcast(datum/tgs_message_content/message2, admin_only) var/list/channels = list() @@ -200,32 +218,82 @@ if (!channel.is_private_channel && ((channel.is_admin_channel && admin_only) || (!channel.is_admin_channel && !admin_only))) channels += channel.id + 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(channels)) + if (!length(channel_ids)) return var/list/data = message2._interop_serialize() - data[DMAPI5_CHAT_MESSAGE_CHANNEL_IDS] = channels + data[DMAPI5_CHAT_MESSAGE_CHANNEL_IDS] = channel_ids if(intercepted_message_queue) intercepted_message_queue += list(data) - else - Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = data)) + return -/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) + 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 = data)) + 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] @@ -252,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 37f58bcdf632..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) @@ -63,7 +65,7 @@ if(detached) // Wait up to one minute for(var/i in 1 to 600) - sleep(1) + sleep(world.tick_lag) if(!detached && (!require_channels || length(chat_channels))) break @@ -75,20 +77,25 @@ /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]") + 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/topic.dm b/code/modules/tgs/v5/topic.dm index d7d471213813..59e5e63e5cd4 100644 --- a/code/modules/tgs/v5/topic.dm +++ b/code/modules/tgs/v5/topic.dm @@ -94,7 +94,7 @@ 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) @@ -141,7 +141,7 @@ 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() @@ -149,7 +149,9 @@ if(DMAPI5_TOPIC_COMMAND_HEALTHCHECK) if(event_handler && event_handler.receive_health_checks) event_handler.HandleEvent(TGS_EVENT_HEALTH_CHECK) - return TopicResponse() + 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 @@ -157,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 @@ -165,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) @@ -175,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) @@ -267,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 c679737dfc49..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 @@ -77,6 +80,10 @@ #undef DMAPI5_TOPIC_COMMAND_SERVER_PORT_UPDATE #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