From 266e15ee40f695c64538cc8bc446c5bfdedd8592 Mon Sep 17 00:00:00 2001
From: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com>
Date: Mon, 25 Dec 2023 19:48:21 +0000
Subject: [PATCH 01/26] Upgrades TGS DMAPI to 7.0.0 (#23657)
* Upgardes TGS DMAPI to 7.0.0
* Fix spacing
* For this
---
code/__DEFINES/_tgs_defines.dm | 2 +-
code/__DEFINES/tgs.dm | 39 ++++++---
.../atmospherics/machinery/airalarm.dm | 2 +-
code/modules/tgs/core/README.md | 6 +-
code/modules/tgs/core/_definitions.dm | 8 ++
code/modules/tgs/core/tgs_core.dm | 18 ++++-
code/modules/tgs/core/tgs_datum.dm | 12 +++
code/modules/tgs/v3210/README.md | 4 +-
code/modules/tgs/v3210/v3_api.dm | 17 ++--
code/modules/tgs/v3210/v3_commands.dm | 12 ++-
code/modules/tgs/v4/README.md | 4 +-
code/modules/tgs/v4/v4_api.dm | 10 +--
code/modules/tgs/v4/v4_commands.dm | 2 +-
code/modules/tgs/v5/README.md | 2 +-
code/modules/tgs/v5/_v5_defines.dm | 9 ++-
code/modules/tgs/v5/v5_api.dm | 80 +++++++++++++------
code/modules/tgs/v5/v5_bridge.dm | 13 ++-
code/modules/tgs/v5/v5_commands.dm | 4 +-
code/modules/tgs/v5/v5_interop_version.dm | 2 +-
code/modules/tgs/v5/v5_serializers.dm | 16 ++--
code/modules/tgs/v5/v5_topic.dm | 60 +++++++++-----
code/modules/tgs/v5/v5_undefs.dm | 7 +-
22 files changed, 233 insertions(+), 96 deletions(-)
diff --git a/code/__DEFINES/_tgs_defines.dm b/code/__DEFINES/_tgs_defines.dm
index 23d1e8c39268..4aa8662ec4ca 100644
--- a/code/__DEFINES/_tgs_defines.dm
+++ b/code/__DEFINES/_tgs_defines.dm
@@ -3,7 +3,7 @@
#define TGS_DEFINE_AND_SET_GLOBAL(Name, Value) GLOBAL_VAR_INIT(##Name, ##Value); GLOBAL_PROTECT(##Name)
#define TGS_READ_GLOBAL(Name) GLOB.##Name
#define TGS_WRITE_GLOBAL(Name, Value) GLOB.##Name = ##Value
-#define TGS_WORLD_ANNOUNCE(message) to_chat(world, "[html_encode(##message)]")
+#define TGS_WORLD_ANNOUNCE(message) to_chat(world, "
Host Announcement: [html_encode(##message)]
")
#define TGS_INFO_LOG(message) log_tgs(message, "INF")
#define TGS_WARNING_LOG(message) log_tgs(message, "WRN")
#define TGS_ERROR_LOG(message) log_tgs(message, "ERR")
diff --git a/code/__DEFINES/tgs.dm b/code/__DEFINES/tgs.dm
index 89976c498422..c6596ea46c10 100644
--- a/code/__DEFINES/tgs.dm
+++ b/code/__DEFINES/tgs.dm
@@ -1,6 +1,6 @@
// tgstation-server DMAPI
-#define TGS_DMAPI_VERSION "6.5.0"
+#define TGS_DMAPI_VERSION "7.0.0"
// All functions and datums outside this document are subject to change with any version and should not be relied on.
@@ -73,12 +73,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 +108,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 +122,7 @@
/// The watchdog will restart on reboot.
#define TGS_REBOOT_MODE_RESTART 2
+// Note that security levels are currently meaningless in OpenDream
/// DreamDaemon Trusted security level.
#define TGS_SECURITY_TRUSTED 0
/// DreamDaemon Safe security level.
@@ -129,6 +130,18 @@
/// DreamDaemon Ultrasafe security level.
#define TGS_SECURITY_ULTRASAFE 2
+/// DreamDaemon public visibility level.
+#define TGS_VISIBILITY_PUBLIC 0
+/// DreamDaemon private visibility level.
+#define TGS_VISIBILITY_PRIVATE 1
+/// DreamDaemon invisible visibility level.
+#define TGS_VISIBILITY_INVISIBLE 2
+
+/// The Build Your Own Net Dream engine.
+#define TGS_ENGINE_TYPE_BYOND 0
+/// The OpenDream engine.
+#define TGS_ENGINE_TYPE_OPENDREAM 1
+
//REQUIRED HOOKS
/**
@@ -154,7 +167,7 @@
#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].
+ * Call this as late as possible in [world/proc/Reboot] (BEFORE ..()).
*/
/world/proc/TgsReboot()
return
@@ -442,6 +455,10 @@
/world/proc/TgsVersion()
return
+/// Returns the running engine type
+/world/proc/TgsEngine()
+ return
+
/// Returns the current [/datum/tgs_version] of the DMAPI being used if it was activated, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
/world/proc/TgsApiVersion()
return
@@ -458,6 +475,10 @@
/world/proc/TgsSecurityLevel()
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()
+ return
+
/// Returns a list of active [/datum/tgs_revision_information/test_merge]s if TGS is present, null otherwise. This function may sleep if the call to [/world/proc/TgsNew] is sleeping!
/world/proc/TgsTestMerges()
return
diff --git a/code/modules/atmospherics/machinery/airalarm.dm b/code/modules/atmospherics/machinery/airalarm.dm
index 8cc5e6cb6a3d..e4db8e8151d1 100644
--- a/code/modules/atmospherics/machinery/airalarm.dm
+++ b/code/modules/atmospherics/machinery/airalarm.dm
@@ -837,7 +837,7 @@
var/device_id = params["id_tag"]
var/cmd = params["cmd"]
switch(cmd)
- if ("power",
+ if("power",
"adjust_external_pressure",
"set_external_pressure",
"checks",
diff --git a/code/modules/tgs/core/README.md b/code/modules/tgs/core/README.md
index aa3c7a9c9db6..bb55caba2c4a 100644
--- a/code/modules/tgs/core/README.md
+++ b/code/modules/tgs/core/README.md
@@ -3,6 +3,6 @@
This folder contains all DMAPI code not directly involved in an API.
- [_definitions.dm](./definitions.dm) contains defines needed across DMAPI internals.
-- [core.dm](./core.dm) contains the implementations of the `/world/proc/TgsXXX()` procs. Many map directly to the `/datum/tgs_api` functions. It also contains the /datum selection and setup code.
-- [datum.dm](./datum.dm) contains the `/datum/tgs_api` declarations that all APIs must implement.
-- [tgs_version.dm](./tgs_version.dm) contains the `/datum/tgs_version` definition
\ No newline at end of file
+- [tgs_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.
+- [tgs_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/tgs_core.dm b/code/modules/tgs/core/tgs_core.dm
index 41a047339452..8be96f27404a 100644
--- a/code/modules/tgs/core/tgs_core.dm
+++ b/code/modules/tgs/core/tgs_core.dm
@@ -42,11 +42,11 @@
var/datum/tgs_version/max_api_version = TgsMaximumApiVersion();
if(version.suite != null && version.minor != null && version.patch != null && version.deprecated_patch != null && version.deprefixed_parameter > max_api_version.deprefixed_parameter)
- TGS_ERROR_LOG("Detected unknown API version! Defaulting to latest. Update the DMAPI to fix this problem.")
+ TGS_ERROR_LOG("Detected unknown Interop API version! Defaulting to latest. Update the DMAPI to fix this problem.")
api_datum = /datum/tgs_api/latest
if(!api_datum)
- TGS_ERROR_LOG("Found unsupported API version: [raw_parameter]. If this is a valid version please report this, backporting is done on demand.")
+ TGS_ERROR_LOG("Found unsupported Interop API version: [raw_parameter]. If this is a valid version please report this, backporting is done on demand.")
return
TGS_INFO_LOG("Activating API for version [version.deprefixed_parameter]")
@@ -107,6 +107,13 @@
if(api)
return api.ApiVersion()
+/world/TgsEngine()
+#ifdef OPENDREAM
+ return TGS_ENGINE_TYPE_OPENDREAM
+#else
+ return TGS_ENGINE_TYPE_BYOND
+#endif
+
/world/TgsInstanceName()
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
@@ -153,4 +160,9 @@
/world/TgsSecurityLevel()
var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
if(api)
- api.SecurityLevel()
+ return api.SecurityLevel()
+
+/world/TgsVisibility()
+ var/datum/tgs_api/api = TGS_READ_GLOBAL(tgs)
+ if(api)
+ return api.Visibility()
diff --git a/code/modules/tgs/core/tgs_datum.dm b/code/modules/tgs/core/tgs_datum.dm
index 68b0330fe860..07ce3b684584 100644
--- a/code/modules/tgs/core/tgs_datum.dm
+++ b/code/modules/tgs/core/tgs_datum.dm
@@ -11,6 +11,15 @@ TGS_DEFINE_AND_SET_GLOBAL(tgs, null)
src.event_handler = event_handler
src.version = version
+/datum/tgs_api/proc/TerminateWorld()
+ while(TRUE)
+ TGS_DEBUG_LOG("About to terminate world. Tick: [world.time], sleep_offline: [world.sleep_offline]")
+ world.sleep_offline = FALSE // https://www.byond.com/forum/post/2894866
+ del(world)
+ world.sleep_offline = FALSE // just in case, this is BYOND after all...
+ sleep(1)
+ TGS_DEBUG_LOG("BYOND DIDN'T TERMINATE THE WORLD!!! TICK IS: [world.time], sleep_offline: [world.sleep_offline]")
+
/datum/tgs_api/latest
parent_type = /datum/tgs_api/v5
@@ -57,3 +66,6 @@ TGS_PROTECT_DATUM(/datum/tgs_api)
/datum/tgs_api/proc/SecurityLevel()
return TGS_UNIMPLEMENTED
+
+/datum/tgs_api/proc/Visibility()
+ return TGS_UNIMPLEMENTED
diff --git a/code/modules/tgs/v3210/README.md b/code/modules/tgs/v3210/README.md
index 01ef44767ad7..a8073306e10d 100644
--- a/code/modules/tgs/v3210/README.md
+++ b/code/modules/tgs/v3210/README.md
@@ -2,5 +2,5 @@
This DMAPI implements bridge using file output which TGS monitors for.
-- [api.dm](./v3_api.dm) contains the bulk of the API code.
-- [commands.dm](./v3_commands.dm) contains functions relating to `/datum/tgs_chat_command`s.
+- [v3_api.dm](./v3_api.dm) contains the bulk of the API code.
+- [v3_commands.dm](./v3_commands.dm) contains functions relating to `/datum/tgs_chat_command`s.
diff --git a/code/modules/tgs/v3210/v3_api.dm b/code/modules/tgs/v3210/v3_api.dm
index c62118001f4b..ac5e8e4cf6d9 100644
--- a/code/modules/tgs/v3210/v3_api.dm
+++ b/code/modules/tgs/v3210/v3_api.dm
@@ -99,7 +99,11 @@
if(skip_compat_check && !fexists(SERVICE_INTERFACE_DLL))
TGS_ERROR_LOG("Service parameter present but no interface DLL detected. This is symptomatic of running a service less than version 3.1! Please upgrade.")
return
- CALL_EXT(SERVICE_INTERFACE_DLL, SERVICE_INTERFACE_FUNCTION)(instance_name, command) //trust no retval
+ #if DM_VERSION >= 515
+ call_ext(SERVICE_INTERFACE_DLL, SERVICE_INTERFACE_FUNCTION)(instance_name, command) //trust no retval
+ #else
+ call(SERVICE_INTERFACE_DLL, SERVICE_INTERFACE_FUNCTION)(instance_name, command) //trust no retval
+ #endif
return TRUE
/datum/tgs_api/v3210/OnTopic(T)
@@ -175,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
@@ -189,16 +193,19 @@
/datum/tgs_api/v3210/ChatChannelInfo()
return list() // :omegalul:
-/datum/tgs_api/v3210/ChatBroadcast(message, list/channels)
+/datum/tgs_api/v3210/ChatBroadcast(datum/tgs_message_content/message, list/channels)
if(channels)
return TGS_UNIMPLEMENTED
+ message = UpgradeDeprecatedChatMessage(message)
ChatTargetedBroadcast(message, TRUE)
ChatTargetedBroadcast(message, FALSE)
-/datum/tgs_api/v3210/ChatTargetedBroadcast(message, admin_only)
- ExportService("[admin_only ? SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE : SERVICE_REQUEST_IRC_BROADCAST] [message]")
+/datum/tgs_api/v3210/ChatTargetedBroadcast(datum/tgs_message_content/message, admin_only)
+ message = UpgradeDeprecatedChatMessage(message)
+ ExportService("[admin_only ? SERVICE_REQUEST_IRC_ADMIN_CHANNEL_MESSAGE : SERVICE_REQUEST_IRC_BROADCAST] [message.text]")
/datum/tgs_api/v3210/ChatPrivateMessage(message, datum/tgs_chat_user/user)
+ UpgradeDeprecatedChatMessage(message)
return TGS_UNIMPLEMENTED
/datum/tgs_api/v3210/SecurityLevel()
diff --git a/code/modules/tgs/v3210/v3_commands.dm b/code/modules/tgs/v3210/v3_commands.dm
index 4ccfc1a8a60b..e65c816320dc 100644
--- a/code/modules/tgs/v3210/v3_commands.dm
+++ b/code/modules/tgs/v3210/v3_commands.dm
@@ -10,9 +10,12 @@
var/warned_about_the_dangers_of_robutussin = !warnings_only
for(var/I in typesof(/datum/tgs_chat_command) - /datum/tgs_chat_command)
if(!warned_about_the_dangers_of_robutussin)
- TGS_ERROR_LOG("Custom chat commands in [ApiVersion()] lacks the /datum/tgs_chat_user/sender.channel field!")
+ TGS_WARNING_LOG("Custom chat commands in [ApiVersion()] lacks the /datum/tgs_chat_user/sender.channel field!")
warned_about_the_dangers_of_robutussin = TRUE
var/datum/tgs_chat_command/stc = I
+ if(stc.ignore_type == I)
+ continue
+
var/command_name = initial(stc.name)
if(!command_name || findtext(command_name, " ") || findtext(command_name, "'") || findtext(command_name, "\""))
if(warnings_only && !warned_command_names[command_name])
@@ -44,9 +47,12 @@
user.friendly_name = sender
// Discord hack, fix the mention if it's only numbers (fuck you IRC trolls)
- var/regex/discord_id_regex = regex(@"^[0-9]+$")
+ var/regex/discord_id_regex = regex("^\[0-9\]+$")
if(findtext(sender, discord_id_regex))
sender = "<@[sender]>"
user.mention = sender
- return stc.Run(user, params) || TRUE
+ var/datum/tgs_message_content/result = stc.Run(user, params)
+ result = UpgradeDeprecatedCommandResponse(result, command)
+
+ return result ? result.text : TRUE
diff --git a/code/modules/tgs/v4/README.md b/code/modules/tgs/v4/README.md
index c14d7d38872e..049d4fb0f6de 100644
--- a/code/modules/tgs/v4/README.md
+++ b/code/modules/tgs/v4/README.md
@@ -2,5 +2,5 @@
This DMAPI implements bridge requests using file output which TGS monitors for. It has a safe mode restriction.
-- [api.dm](./v4_api.dm) contains the bulk of the API code.
-- [commands.dm](./v4_commands.dm) contains functions relating to `/datum/tgs_chat_command`s.
+- [v4_api.dm](./v4_api.dm) contains the bulk of the API code.
+- [v4_commands.dm](./v4_commands.dm) contains functions relating to `/datum/tgs_chat_command`s.
diff --git a/code/modules/tgs/v4/v4_api.dm b/code/modules/tgs/v4/v4_api.dm
index cce3b00bb268..3992299e4374 100644
--- a/code/modules/tgs/v4/v4_api.dm
+++ b/code/modules/tgs/v4/v4_api.dm
@@ -73,7 +73,7 @@
if(cached_json["apiValidateOnly"])
TGS_INFO_LOG("Validating API and exiting...")
Export(TGS4_COMM_VALIDATE, list(TGS4_PARAMETER_DATA = "[minimum_required_security_level]"))
- del(world)
+ TerminateWorld()
security_level = cached_json["securityLevel"]
chat_channels_json_path = cached_json["chatChannelsJson"]
@@ -188,7 +188,7 @@
requesting_new_port = TRUE
if(!world.OpenPort(0)) //open any port
TGS_ERROR_LOG("Unable to open random port to retrieve new port![TGS4_PORT_CRITFAIL_MESSAGE]")
- del(world)
+ TerminateWorld()
//request a new port
export_lock = FALSE
@@ -196,16 +196,16 @@
if(!new_port_json)
TGS_ERROR_LOG("No new port response from server![TGS4_PORT_CRITFAIL_MESSAGE]")
- del(world)
+ TerminateWorld()
var/new_port = new_port_json[TGS4_PARAMETER_DATA]
if(!isnum(new_port) || new_port <= 0)
TGS_ERROR_LOG("Malformed new port json ([json_encode(new_port_json)])![TGS4_PORT_CRITFAIL_MESSAGE]")
- del(world)
+ TerminateWorld()
if(new_port != world.port && !world.OpenPort(new_port))
TGS_ERROR_LOG("Unable to open port [new_port]![TGS4_PORT_CRITFAIL_MESSAGE]")
- del(world)
+ TerminateWorld()
requesting_new_port = FALSE
while(export_lock)
diff --git a/code/modules/tgs/v4/v4_commands.dm b/code/modules/tgs/v4/v4_commands.dm
index d6d3d718d471..25dd6740e3af 100644
--- a/code/modules/tgs/v4/v4_commands.dm
+++ b/code/modules/tgs/v4/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/README.md b/code/modules/tgs/v5/README.md
index dd8da3bea439..8a782fb15510 100644
--- a/code/modules/tgs/v5/README.md
+++ b/code/modules/tgs/v5/README.md
@@ -10,4 +10,4 @@ This DMAPI implements bridge requests using HTTP GET requests to TGS. It has no
- [v5_commands.dm](./v5_commands.dm) contains functions relating to `/datum/tgs_chat_command`s.
- [v5_serializers.dm](./v5_serializers.dm) contains function to help convert interop `/datum`s into a JSON encodable `list()` format.
- [v5_topic.dm](./v5_topic.dm) contains functions related to processing topic requests.
-- [v5_undefs.dm](./v5_undefs.dm) Undoes the work of `_v5_defines.dm`.
+- [v5_undefs.dm](./v5_undefs.dm) Undoes the work of `_defines.dm`.
diff --git a/code/modules/tgs/v5/_v5_defines.dm b/code/modules/tgs/v5/_v5_defines.dm
index c7213cc24699..1c7d67d20cdf 100644
--- a/code/modules/tgs/v5/_v5_defines.dm
+++ b/code/modules/tgs/v5/_v5_defines.dm
@@ -5,10 +5,9 @@
#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
@@ -18,6 +17,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"
@@ -48,6 +48,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 +80,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_PARAMETER_COMMAND_TYPE "commandType"
#define DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND "chatCommand"
@@ -88,6 +90,7 @@
#define DMAPI5_TOPIC_PARAMETER_NEW_INSTANCE_NAME "newInstanceName"
#define DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE "chatUpdate"
#define DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION "newServerVersion"
+#define DMAPI5_TOPIC_PARAMETER_BROADCAST_MESSAGE "broadcastMessage"
#define DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE "commandResponse"
#define DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE_MESSAGE "commandResponseMessage"
diff --git a/code/modules/tgs/v5/v5_api.dm b/code/modules/tgs/v5/v5_api.dm
index fb2c46cc41ce..6b4e3ff9bd1c 100644
--- a/code/modules/tgs/v5/v5_api.dm
+++ b/code/modules/tgs/v5/v5_api.dm
@@ -4,6 +4,7 @@
var/instance_name
var/security_level
+ var/visibility
var/reboot_mode = TGS_REBOOT_MODE_NORMAL
@@ -16,24 +17,32 @@
var/list/chat_channels
var/initialized = FALSE
+ var/initial_bridge_request_received = FALSE
+ var/datum/tgs_version/interop_version
var/chunked_requests = 0
var/list/chunked_topics = list()
var/detached = FALSE
+/datum/tgs_api/v5/New()
+ . = ..()
+ interop_version = version
+ TGS_DEBUG_LOG("V5 API created: [json_encode(args)]")
+
/datum/tgs_api/v5/ApiVersion()
return new /datum/tgs_version(
#include "v5_interop_version.dm"
)
/datum/tgs_api/v5/OnWorldNew(minimum_required_security_level)
+ TGS_DEBUG_LOG("OnWorldNew()")
server_port = world.params[DMAPI5_PARAM_SERVER_PORT]
access_identifier = world.params[DMAPI5_PARAM_ACCESS_IDENTIFIER]
var/datum/tgs_version/api_version = ApiVersion()
- version = null
- var/list/bridge_response = Bridge(DMAPI5_BRIDGE_COMMAND_STARTUP, list(DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL = minimum_required_security_level, DMAPI5_BRIDGE_PARAMETER_VERSION = api_version.raw_parameter, DMAPI5_PARAMETER_CUSTOM_COMMANDS = ListCustomCommands()))
+ version = null // we want this to be the TGS version, not the interop version
+ var/list/bridge_response = Bridge(DMAPI5_BRIDGE_COMMAND_STARTUP, list(DMAPI5_BRIDGE_PARAMETER_MINIMUM_SECURITY_LEVEL = minimum_required_security_level, DMAPI5_BRIDGE_PARAMETER_VERSION = api_version.raw_parameter, DMAPI5_PARAMETER_CUSTOM_COMMANDS = ListCustomCommands(), DMAPI5_PARAMETER_TOPIC_PORT = GetTopicPort()))
if(!istype(bridge_response))
TGS_ERROR_LOG("Failed initial bridge request!")
return FALSE
@@ -45,10 +54,12 @@
if(runtime_information[DMAPI5_RUNTIME_INFORMATION_API_VALIDATE_ONLY])
TGS_INFO_LOG("DMAPI validation, exiting...")
- del(world)
+ TerminateWorld()
- version = new /datum/tgs_version(runtime_information[DMAPI5_RUNTIME_INFORMATION_SERVER_VERSION])
+ initial_bridge_request_received = TRUE
+ version = new /datum/tgs_version(runtime_information[DMAPI5_RUNTIME_INFORMATION_SERVER_VERSION]) // reassigning this because it can change if TGS updates
security_level = runtime_information[DMAPI5_RUNTIME_INFORMATION_SECURITY_LEVEL]
+ visibility = runtime_information[DMAPI5_RUNTIME_INFORMATION_VISIBILITY]
instance_name = runtime_information[DMAPI5_RUNTIME_INFORMATION_INSTANCE_NAME]
var/list/revisionData = runtime_information[DMAPI5_RUNTIME_INFORMATION_REVISION]
@@ -95,18 +106,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)
+ TGS_DEBUG_LOG("RequireInitialBridgeResponse()")
+ var/logged = FALSE
+ while(!initial_bridge_request_received)
+ if(!logged)
+ TGS_DEBUG_LOG("RequireInitialBridgeResponse: Starting sleep")
+ logged = TRUE
+
sleep(1)
+ TGS_DEBUG_LOG("RequireInitialBridgeResponse: Passed")
+
/datum/tgs_api/v5/OnInitializationComplete()
Bridge(DMAPI5_BRIDGE_COMMAND_PRIME)
/datum/tgs_api/v5/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 +185,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,45 +194,45 @@
var/datum/tgs_chat_channel/channel = I
ids += channel.id
- message = UpgradeDeprecatedChatMessage(message)
+ message2 = UpgradeDeprecatedChatMessage(message2)
if(!length(channels))
return
- message = message._interop_serialize()
- message[DMAPI5_CHAT_MESSAGE_CHANNEL_IDS] = ids
+ var/list/data = message2._interop_serialize()
+ data[DMAPI5_CHAT_MESSAGE_CHANNEL_IDS] = ids
if(intercepted_message_queue)
- intercepted_message_queue += list(message)
+ intercepted_message_queue += list(data)
else
- Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = message))
+ Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = data))
-/datum/tgs_api/v5/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)
+ message2 = UpgradeDeprecatedChatMessage(message2)
if(!length(channels))
return
- message = message._interop_serialize()
- message[DMAPI5_CHAT_MESSAGE_CHANNEL_IDS] = channels
+ var/list/data = message2._interop_serialize()
+ data[DMAPI5_CHAT_MESSAGE_CHANNEL_IDS] = channels
if(intercepted_message_queue)
- intercepted_message_queue += list(message)
+ intercepted_message_queue += list(data)
else
- Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = message))
+ Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = data))
-/datum/tgs_api/v5/ChatPrivateMessage(datum/tgs_message_content/message, datum/tgs_chat_user/user)
- message = UpgradeDeprecatedChatMessage(message)
- message = message._interop_serialize()
- message[DMAPI5_CHAT_MESSAGE_CHANNEL_IDS] = list(user.channel.id)
+/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(message)
+ intercepted_message_queue += list(data)
else
- Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = message))
+ Bridge(DMAPI5_BRIDGE_COMMAND_CHAT_SEND, list(DMAPI5_BRIDGE_PARAMETER_CHAT_MESSAGE = data))
/datum/tgs_api/v5/ChatChannelInfo()
RequireInitialBridgeResponse()
@@ -211,6 +240,7 @@
return chat_channels.Copy()
/datum/tgs_api/v5/proc/DecodeChannels(chat_update_json)
+ TGS_DEBUG_LOG("DecodeChannels()")
var/list/chat_channels_json = chat_update_json[DMAPI5_CHAT_UPDATE_CHANNELS]
if(istype(chat_channels_json))
chat_channels.Cut()
@@ -235,3 +265,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/v5_bridge.dm b/code/modules/tgs/v5/v5_bridge.dm
index 37f58bcdf632..60cbcbfb7dff 100644
--- a/code/modules/tgs/v5/v5_bridge.dm
+++ b/code/modules/tgs/v5/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 >= 7 ? "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)
@@ -81,11 +83,16 @@
TGS_ERROR_LOG("Failed bridge request: [bridge_request]")
return
- var/response_json = file2text(export_response["CONTENT"])
- if(!response_json)
+ var/content = export_response["CONTENT"]
+ if(!content)
TGS_ERROR_LOG("Failed bridge request, missing content!")
return
+ var/response_json = file2text(content)
+ if(!response_json)
+ TGS_ERROR_LOG("Failed bridge request, failed to load content!")
+ return
+
var/list/bridge_response = json_decode(response_json)
if(!bridge_response)
TGS_ERROR_LOG("Failed bridge request, bad json: [response_json]")
diff --git a/code/modules/tgs/v5/v5_commands.dm b/code/modules/tgs/v5/v5_commands.dm
index c8f538db5932..9557f8a08ed5 100644
--- a/code/modules/tgs/v5/v5_commands.dm
+++ b/code/modules/tgs/v5/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/v5_interop_version.dm b/code/modules/tgs/v5/v5_interop_version.dm
index 5d3d491a7362..83420d130a74 100644
--- a/code/modules/tgs/v5/v5_interop_version.dm
+++ b/code/modules/tgs/v5/v5_interop_version.dm
@@ -1 +1 @@
-"5.6.1"
+"5.7.0"
diff --git a/code/modules/tgs/v5/v5_serializers.dm b/code/modules/tgs/v5/v5_serializers.dm
index 3a6fbe6be344..3a32848ad512 100644
--- a/code/modules/tgs/v5/v5_serializers.dm
+++ b/code/modules/tgs/v5/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/v5_topic.dm b/code/modules/tgs/v5/v5_topic.dm
index 68d0db27f36a..309044a145dc 100644
--- a/code/modules/tgs/v5/v5_topic.dm
+++ b/code/modules/tgs/v5/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,28 +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)
@@ -122,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)
@@ -132,13 +141,13 @@
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_HEALTHCHECK)
- if(event_handler?.receive_health_checks)
+ if(event_handler && event_handler.receive_health_checks)
event_handler.HandleEvent(TGS_EVENT_HEALTH_CHECK)
return TopicResponse()
@@ -148,7 +157,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
@@ -156,7 +165,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)
@@ -166,6 +175,7 @@
var/list/reattach_response = TopicResponse(error_message)
reattach_response[DMAPI5_PARAMETER_CUSTOM_COMMANDS] = ListCustomCommands()
+ reattach_response[DMAPI5_PARAMETER_TOPIC_PORT] = GetTopicPort()
return reattach_response
if(DMAPI5_TOPIC_COMMAND_SEND_CHUNK)
@@ -258,4 +268,16 @@
return chunk_to_send
+ if(DMAPI5_TOPIC_COMMAND_RECEIVE_BROADCAST)
+ var/message = topic_parameters[DMAPI5_TOPIC_PARAMETER_BROADCAST_MESSAGE]
+ if(!istext(message))
+ return TopicResponse("Invalid or missing [DMAPI5_TOPIC_PARAMETER_BROADCAST_MESSAGE]")
+
+ TGS_WORLD_ANNOUNCE(message)
+ return TopicResponse()
+
return TopicResponse("Unknown command: [command]")
+
+/datum/tgs_api/v5/proc/WorldBroadcast(message)
+ set waitfor = FALSE
+ TGS_WORLD_ANNOUNCE(message)
diff --git a/code/modules/tgs/v5/v5_undefs.dm b/code/modules/tgs/v5/v5_undefs.dm
index c679737dfc49..d531d4b7b9dd 100644
--- a/code/modules/tgs/v5/v5_undefs.dm
+++ b/code/modules/tgs/v5/v5_undefs.dm
@@ -8,7 +8,6 @@
#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
@@ -18,6 +17,7 @@
#undef DMAPI5_PARAMETER_ACCESS_IDENTIFIER
#undef DMAPI5_PARAMETER_CUSTOM_COMMANDS
+#undef DMAPI5_PARAMETER_TOPIC_PORT
#undef DMAPI5_CHUNK
#undef DMAPI5_CHUNK_PAYLOAD
@@ -48,6 +48,7 @@
#undef DMAPI5_RUNTIME_INFORMATION_REVISION
#undef DMAPI5_RUNTIME_INFORMATION_TEST_MERGES
#undef DMAPI5_RUNTIME_INFORMATION_SECURITY_LEVEL
+#undef DMAPI5_RUNTIME_INFORMATION_VISIBILITY
#undef DMAPI5_CHAT_UPDATE_CHANNELS
@@ -77,6 +78,9 @@
#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_PARAMETER_COMMAND_TYPE
#undef DMAPI5_TOPIC_PARAMETER_CHAT_COMMAND
@@ -86,6 +90,7 @@
#undef DMAPI5_TOPIC_PARAMETER_NEW_INSTANCE_NAME
#undef DMAPI5_TOPIC_PARAMETER_CHAT_UPDATE
#undef DMAPI5_TOPIC_PARAMETER_NEW_SERVER_VERSION
+#undef DMAPI5_TOPIC_PARAMETER_BROADCAST_MESSAGE
#undef DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE
#undef DMAPI5_TOPIC_RESPONSE_COMMAND_RESPONSE_MESSAGE
From 5f9c9274fb8d0d2f6ec973e37d01ede0af40df07 Mon Sep 17 00:00:00 2001
From: Pierre-Louis <44984704+PIerreLouisH@users.noreply.github.com>
Date: Tue, 26 Dec 2023 18:48:08 +0100
Subject: [PATCH 02/26] Add health analyzer to coroner (#23486)
* Add health analyzer to coroner's starting equipment
This commit adds a health analyzer to the starting equipment of the coroner job. This will help coroners to better assess the condition of bodies they are examining.
* Add the health analyzer upon morgue maps
* Forgot cerestation
Silly beans
* Replaced the health analyzer from maps to locker
Optimization only fix: gotta remove some stress out of the map queue.
---------
Co-authored-by: Pierre-Louis
---
code/game/jobs/job/medical_jobs.dm | 1 +
code/game/objects/structures/crates_lockers/closets/wardrobe.dm | 1 +
2 files changed, 2 insertions(+)
diff --git a/code/game/jobs/job/medical_jobs.dm b/code/game/jobs/job/medical_jobs.dm
index 3449f62a653a..736351bb585c 100644
--- a/code/game/jobs/job/medical_jobs.dm
+++ b/code/game/jobs/job/medical_jobs.dm
@@ -113,6 +113,7 @@
/obj/item/clothing/head/surgery/black = 1,
/obj/item/autopsy_scanner = 1,
/obj/item/reagent_scanner = 1,
+ /obj/item/healthanalyzer = 1,
/obj/item/storage/box/bodybags = 1)
/datum/outfit/job/doctor/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
diff --git a/code/game/objects/structures/crates_lockers/closets/wardrobe.dm b/code/game/objects/structures/crates_lockers/closets/wardrobe.dm
index 5826b53f95cf..b5563eacc8d6 100644
--- a/code/game/objects/structures/crates_lockers/closets/wardrobe.dm
+++ b/code/game/objects/structures/crates_lockers/closets/wardrobe.dm
@@ -449,3 +449,4 @@
new /obj/item/clothing/head/surgery/black(src)
new /obj/item/reagent_containers/glass/bottle/reagent/formaldehyde(src)
new /obj/item/reagent_containers/dropper(src)
+ new /obj/item/healthanalyzer(src)
From 501fe336347b51cce4f27062fb9afe0bfdcb8ffe Mon Sep 17 00:00:00 2001
From: Daylight <18598676+Daylight2@users.noreply.github.com>
Date: Tue, 26 Dec 2023 19:59:02 +0200
Subject: [PATCH 03/26] You don't need to be unconcious to succumb anymore
(#23460)
* Ded
* Deconflicts with #23441
---
code/modules/mob/living/living.dm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index bbed15a5c3dd..daf0ab0dcd05 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -291,7 +291,7 @@
/mob/living/verb/succumb()
set hidden = TRUE
- if(health >= HEALTH_THRESHOLD_CRIT || stat != UNCONSCIOUS)
+ if(health >= HEALTH_THRESHOLD_CRIT)
to_chat(src, "You are unable to succumb to death! This life continues!")
return
From 20ec46b0ca2d86d51486f640a121b0b663ad5c34 Mon Sep 17 00:00:00 2001
From: DGamerL <108773801+DGamerL@users.noreply.github.com>
Date: Tue, 26 Dec 2023 19:26:27 +0100
Subject: [PATCH 04/26] At least this was caught now (#23563)
---
code/modules/paperwork/pen.dm | 4 ++--
code/modules/projectiles/projectile/bullets.dm | 6 +++---
code/modules/reagents/reagent_containers/hypospray.dm | 4 ++--
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/code/modules/paperwork/pen.dm b/code/modules/paperwork/pen.dm
index 44c94fc76bb6..83def2f64a9a 100644
--- a/code/modules/paperwork/pen.dm
+++ b/code/modules/paperwork/pen.dm
@@ -132,8 +132,8 @@
add_attack_logs(user, M, "Stabbed with (sleepy) [src]. [transfered]u of reagents transfered from pen containing [english_list(contained)].")
for(var/datum/reagent/R as anything in reagents.reagent_list)
if(initial(R.id) == "????") // Yes this is a specific case that we don't really want
- return TRUE
- reagents.reaction(M, REAGENT_INGEST, 0.1)
+ continue
+ reagents.reaction(M, REAGENT_INGEST, 0.1)
return TRUE
diff --git a/code/modules/projectiles/projectile/bullets.dm b/code/modules/projectiles/projectile/bullets.dm
index 264ac7f9de8f..b81b6a5690ec 100644
--- a/code/modules/projectiles/projectile/bullets.dm
+++ b/code/modules/projectiles/projectile/bullets.dm
@@ -264,12 +264,12 @@
if(blocked != INFINITY)
if(M.can_inject(null, FALSE, hit_zone, piercing)) // Pass the hit zone to see if it can inject by whether it hit the head or the body.
..()
+
for(var/datum/reagent/R as anything in reagents.reagent_list)
if(initial(R.id) == "????") // Yes this is a specific case that we don't really want
- reagents.trans_to(M, reagents.total_volume)
- return TRUE
+ continue
+ reagents.reaction(M, REAGENT_INGEST, 0.1)
reagents.trans_to(M, reagents.total_volume)
- reagents.reaction(M, REAGENT_INGEST, 0.1)
return TRUE
else
blocked = INFINITY
diff --git a/code/modules/reagents/reagent_containers/hypospray.dm b/code/modules/reagents/reagent_containers/hypospray.dm
index 72996bda5f3a..32ac08eaf6d3 100644
--- a/code/modules/reagents/reagent_containers/hypospray.dm
+++ b/code/modules/reagents/reagent_containers/hypospray.dm
@@ -53,8 +53,8 @@
add_attack_logs(user, M, "Injected with [src] containing ([contained])", reagents.harmless_helper() ? ATKLOG_ALMOSTALL : null)
for(var/datum/reagent/R as anything in reagents.reagent_list)
if(initial(R.id) == "????") // Yes this is a specific case that we don't really want
- return TRUE
- reagents.reaction(M, REAGENT_INGEST, 0.1)
+ continue
+ reagents.reaction(M, REAGENT_INGEST, 0.1)
return TRUE
/obj/item/reagent_containers/hypospray/attack(mob/living/M, mob/user)
From b678d0289d79990ca2599368cd5be976101ac9b8 Mon Sep 17 00:00:00 2001
From: Qwertytoforty <52090703+Qwertytoforty@users.noreply.github.com>
Date: Tue, 26 Dec 2023 13:27:16 -0500
Subject: [PATCH 05/26] Kidan, drask, and greys can be ERT now (#23552)
---
code/modules/response_team/ert.dm | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/code/modules/response_team/ert.dm b/code/modules/response_team/ert.dm
index 47b6a68617c0..5975eb7f1fb9 100644
--- a/code/modules/response_team/ert.dm
+++ b/code/modules/response_team/ert.dm
@@ -98,7 +98,7 @@ GLOBAL_LIST_EMPTY(ert_request_messages)
A.close()
var/list/ert_species_prefs = list()
for(var/mob/M in GLOB.response_team_members)
- ert_species_prefs.Add(input_async(M, "Please select a species (10 seconds):", list("Human", "Tajaran", "Skrell", "Unathi", "Diona", "Vulpkanin", "Nian", "Random")))
+ ert_species_prefs.Add(input_async(M, "Please select a species (10 seconds):", list("Human", "Tajaran", "Skrell", "Unathi", "Diona", "Vulpkanin", "Nian", "Drask", "Kidan", "Grey", "Random")))
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(get_ert_role_prefs), GLOB.response_team_members, ert_gender_prefs, ert_species_prefs), 10 SECONDS)
/proc/get_ert_role_prefs(list/response_team_members, list/ert_gender_prefs, list/ert_species_prefs) // Why the FUCK is this variable the EXACT SAME as the global one
@@ -164,7 +164,7 @@ GLOBAL_LIST_EMPTY(ert_request_messages)
if(!new_species)
new_species = "Human"
if(new_species == "Random")
- new_species = pick("Human", "Tajaran", "Skrell", "Unathi", "Diona", "Vulpkanin", "Nian")
+ new_species = pick("Human", "Tajaran", "Skrell", "Unathi", "Diona", "Vulpkanin", "Nian", "Drask", "Kidan", "Grey")
var/datum/species/S = GLOB.all_species[new_species]
var/species = S.type
M.set_species(species, TRUE)
@@ -172,10 +172,10 @@ GLOBAL_LIST_EMPTY(ert_request_messages)
M.cleanSE() //No fat/blind/colourblind/epileptic/whatever ERT.
M.overeatduration = 0
var/obj/item/organ/external/head/head_organ = M.get_organ("head")
- var/eye_c = pick("#000000", "#8B4513", "#1E90FF") // Black, brown, blue
+ var/eye_c = pick("#000000", "#8B4513", "#1E90FF", "#8c00ff", "#a80c0c", "#2fdb63") // Black, brown, blue, purple, red, green
var/skin_tone = rand(-120, 20) // A range of skin colors
- switch(new_species) //Diona not included as they don't use the hair colours
+ switch(new_species) //Diona not included as they don't use the hair colours, kidan use accessory, drask are skin tone Grey not included as they are BALD
if("Human", "Tajaran", "Vulpkanin", "Nian")
var/hair_c_htvn = pick("#8B4513", "#000000", "#FF4500", "#FFD700", "#d4d1bf") // Brown, black, red, blonde, grey
head_organ.facial_colour = hair_c_htvn
@@ -195,8 +195,10 @@ GLOBAL_LIST_EMPTY(ert_request_messages)
else
M.skin_colour = pick(su) //Pick a diffrent colour for body.
+
M.change_eye_color(eye_c)
M.s_tone = skin_tone
+ head_organ.headacc_colour = pick("#1f138b", "#272525", "#07a035", "#8c00ff", "#a80c0c")
head_organ.h_style = random_hair_style(M.gender, head_organ.dna.species.name)
head_organ.f_style = random_facial_hair_style(M.gender, head_organ.dna.species.name)
From 70346d2a6561b5ea5e6b42800b7e45cb2f3fe2bf Mon Sep 17 00:00:00 2001
From: Qwertytoforty <52090703+Qwertytoforty@users.noreply.github.com>
Date: Tue, 26 Dec 2023 14:24:42 -0500
Subject: [PATCH 06/26] a a hole (#23613)
---
code/modules/mining/lavaland/loot/tendril_loot.dm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/code/modules/mining/lavaland/loot/tendril_loot.dm b/code/modules/mining/lavaland/loot/tendril_loot.dm
index 08849a17f093..12459079fe90 100644
--- a/code/modules/mining/lavaland/loot/tendril_loot.dm
+++ b/code/modules/mining/lavaland/loot/tendril_loot.dm
@@ -408,7 +408,7 @@
if(cooldown < world.time)
SSblackbox.record_feedback("amount", "immortality_talisman_uses", 1) // usage
cooldown = world.time + 600
- user.visible_message("[user] vanishes from reality, leaving a a hole in [user.p_their()] place!")
+ user.visible_message("[user] vanishes from reality, leaving a hole in [user.p_their()] place!")
var/obj/effect/immortality_talisman/Z = new(get_turf(src.loc))
Z.name = "hole in reality"
Z.desc = "It's shaped an awful lot like [user.name]."
From 27722e4d7a183208be39a40639f31919cbe064d5 Mon Sep 17 00:00:00 2001
From: Daylight <18598676+Daylight2@users.noreply.github.com>
Date: Tue, 26 Dec 2023 21:45:50 +0200
Subject: [PATCH 07/26] Trying to talk with oxygen damage makes you whisper
instead of gasp (#23425)
* Basics working
* The rest of the PR
* formatting suggestion
Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
* Renames proc
* Darn merge conflicts
* Rename proc again
* this doesn't do anything
* I swear I commited this earlier. Grammar fix.
* Behold, the code compressor 9000!
Co-authored-by: Gaxeer <44334376+Gaxeer@users.noreply.github.com>
* Update code/modules/mob/living/carbon/human/human_say.dm
Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com>
---------
Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Co-authored-by: Gaxeer <44334376+Gaxeer@users.noreply.github.com>
Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com>
---
code/datums/spell.dm | 2 +-
code/game/gamemodes/cult/runes.dm | 2 +-
code/game/objects/items/devices/radio/radio_objects.dm | 2 +-
code/modules/mob/living/carbon/human/human_say.dm | 6 +++---
code/modules/mob/living/living_say.dm | 3 +++
code/modules/mob/mob.dm | 3 +++
6 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/code/datums/spell.dm b/code/datums/spell.dm
index a40432406b36..eac5f40356d8 100644
--- a/code/datums/spell.dm
+++ b/code/datums/spell.dm
@@ -198,7 +198,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
/obj/effect/proc_holder/spell/proc/invocation(mob/user) //spelling the spell out and setting it on recharge/reducing charges amount
switch(invocation_type)
if("shout")
- if(!user.IsVocal())
+ if(!user.IsVocal() || user.cannot_speak_loudly())
user.custom_emote(EMOTE_VISIBLE, "makes frantic gestures!")
else
if(prob(50))//Auto-mute? Fuck that noise
diff --git a/code/game/gamemodes/cult/runes.dm b/code/game/gamemodes/cult/runes.dm
index 6de5b7947979..25b2bd9e46c9 100644
--- a/code/game/gamemodes/cult/runes.dm
+++ b/code/game/gamemodes/cult/runes.dm
@@ -193,7 +193,7 @@ structure_check() searches for nearby cultist structures required for the invoca
if(L.has_status_effect(STATUS_EFFECT_SUMMONEDGHOST))
ghost_invokers++
if(invocation)
- if(!L.IsVocal())
+ if(!L.IsVocal() || L.cannot_speak_loudly())
L.custom_emote(EMOTE_VISIBLE, message = pick("draws arcane sigils in the air.","gestures ominously.","silently mouths out an invocation.","places their hands on the rune, activating it."))
else
L.say(invocation)
diff --git a/code/game/objects/items/devices/radio/radio_objects.dm b/code/game/objects/items/devices/radio/radio_objects.dm
index 717c3e1e2430..c93f9e190896 100644
--- a/code/game/objects/items/devices/radio/radio_objects.dm
+++ b/code/game/objects/items/devices/radio/radio_objects.dm
@@ -366,7 +366,7 @@ GLOBAL_LIST_EMPTY(deadsay_radio_systems)
if(wires.is_cut(WIRE_RADIO_TRANSMIT)) // The device has to have all its wires and shit intact
return 0
- if(!M.IsVocal())
+ if(!M.IsVocal() || M.cannot_speak_loudly())
return 0
if(is_type_in_list(get_area(src), blacklisted_areas))
diff --git a/code/modules/mob/living/carbon/human/human_say.dm b/code/modules/mob/living/carbon/human/human_say.dm
index 39158d342de5..551689b1ec6c 100644
--- a/code/modules/mob/living/carbon/human/human_say.dm
+++ b/code/modules/mob/living/carbon/human/human_say.dm
@@ -69,13 +69,13 @@
return FALSE
if((breathes && !L) || breathes && L && (L.status & ORGAN_DEAD))
return FALSE
- if(getOxyLoss() > 10 || AmountLoseBreath() >= 8 SECONDS)
- emote("gasp")
- return FALSE
if(mind)
return !mind.miming
return TRUE
+/mob/living/carbon/human/cannot_speak_loudly()
+ return getOxyLoss() > 10 || AmountLoseBreath() >= 8 SECONDS
+
/mob/living/carbon/human/proc/SetSpecialVoice(new_voice)
if(new_voice)
special_voice = new_voice
diff --git a/code/modules/mob/living/living_say.dm b/code/modules/mob/living/living_say.dm
index b38ef5137b37..995b0b68fe3c 100644
--- a/code/modules/mob/living/living_say.dm
+++ b/code/modules/mob/living/living_say.dm
@@ -182,6 +182,9 @@ GLOBAL_LIST_EMPTY(channel_to_radio_key)
var/list/hsp = handle_speech_problems(message_pieces, verb)
verb = hsp["verb"]
+ if(cannot_speak_loudly())
+ return whisper(message)
+
// Do this so it gets logged for all types of communication
var/log_message = "[message_mode ? "([message_mode])" : ""] '[message]'"
create_log(SAY_LOG, log_message)
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 6360aadf1289..c3b9c8c44422 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -1326,6 +1326,9 @@ GLOBAL_LIST_INIT(slot_equipment_priority, list( \
/mob/proc/IsVocal()
return TRUE
+/mob/proc/cannot_speak_loudly()
+ return FALSE
+
/mob/proc/get_access()
return list() //must return list or IGNORE_ACCESS
From 19cc5e166b4c8450a4fedbb6455645a7d2fe46e5 Mon Sep 17 00:00:00 2001
From: Pierre-Louis <44984704+PIerreLouisH@users.noreply.github.com>
Date: Tue, 26 Dec 2023 20:46:23 +0100
Subject: [PATCH 08/26] Added the Janicart to the available list of supply pack
(#23456)
Added both the Janicart and the keys to the supply list in the miscelleanous part.
Co-authored-by: Pierre-Louis
---
code/modules/supply/supply_packs/pack_miscellaneous.dm | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/code/modules/supply/supply_packs/pack_miscellaneous.dm b/code/modules/supply/supply_packs/pack_miscellaneous.dm
index 1c54a3b1f858..bce9c7952cba 100644
--- a/code/modules/supply/supply_packs/pack_miscellaneous.dm
+++ b/code/modules/supply/supply_packs/pack_miscellaneous.dm
@@ -192,6 +192,15 @@
cost = 100
containername = "replacement lights crate"
+/datum/supply_packs/misc/janicart
+ name = "Janicart Crate"
+ contains = list(/obj/vehicle/janicart,
+ /obj/item/key/janitor)
+ cost = 500
+ containertype = /obj/structure/largecrate
+ containername = "Janicart. Caution while driving is advised."
+ department_restrictions = list(DEPARTMENT_SERVICE)
+
/datum/supply_packs/misc/noslipfloor
name = "High-traction Floor Tiles"
contains = list(/obj/item/stack/tile/noslip/loaded)
From 3f5c57b7868d4173be6d6409babad95cd7118904 Mon Sep 17 00:00:00 2001
From: SteelSlayer <42044220+SteelSlayer@users.noreply.github.com>
Date: Tue, 26 Dec 2023 13:49:37 -0600
Subject: [PATCH 09/26] Runtime and GC fixes related to orbit UI bluescreens
(#23512)
* GC and runtime fixes
* BONUS FIX!
---
code/game/gamemodes/objective.dm | 1 +
code/game/gamemodes/objective_holder.dm | 3 +++
code/modules/antagonists/traitor/datum_mindslave.dm | 7 +++++--
3 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm
index e2ecd53358ef..a12e8e885425 100644
--- a/code/game/gamemodes/objective.dm
+++ b/code/game/gamemodes/objective.dm
@@ -49,6 +49,7 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective)
owner = null
target = null
team = null
+ holder = null
return ..()
/datum/objective/proc/check_completion()
diff --git a/code/game/gamemodes/objective_holder.dm b/code/game/gamemodes/objective_holder.dm
index 9d33db630dc8..2b65e1eb7b47 100644
--- a/code/game/gamemodes/objective_holder.dm
+++ b/code/game/gamemodes/objective_holder.dm
@@ -20,6 +20,9 @@
/datum/objective_holder/Destroy(force, ...)
clear()
+ objective_owner = null
+ QDEL_NULL(on_add_callback)
+ QDEL_NULL(on_remove_callback)
return ..()
/**
diff --git a/code/modules/antagonists/traitor/datum_mindslave.dm b/code/modules/antagonists/traitor/datum_mindslave.dm
index 9b219dd57959..e511df1ad0bd 100644
--- a/code/modules/antagonists/traitor/datum_mindslave.dm
+++ b/code/modules/antagonists/traitor/datum_mindslave.dm
@@ -27,8 +27,11 @@
if(owner.som)
owner.som.serv -= owner
owner.som.leave_serv_hud(owner)
- // Remove the reference but turn this into a string so it can still be used in /datum/antagonist/mindslave/farewell().
- master = "[master.current.real_name]"
+ // Remove the master reference but turn this into a string so it can still be used in /datum/antagonist/mindslave/farewell().
+ if(master.current)
+ master = "[master.current.real_name]"
+ else
+ master = "[master]"
return ..()
/datum/antagonist/mindslave/on_gain()
From 95c46662c5a5cd4911132da8e695a7cc21fb4297 Mon Sep 17 00:00:00 2001
From: Christasmurf <25437893+Christasmurf@users.noreply.github.com>
Date: Thu, 28 Dec 2023 22:39:36 +0000
Subject: [PATCH 10/26] Entertainer Drip (Mime/Clown) (#23546)
* entertainerdrip
* duffelshadingitem
---
code/game/jobs/job/support.dm | 2 +-
.../objects/items/weapons/storage/backpack.dm | 6 ++++++
.../crates_lockers/closets/secure/miscjobs.dm | 6 ++++++
.../preference/loadout/loadout_uniform.dm | 10 ++++++++++
code/modules/clothing/under/jobs/civilian.dm | 16 +++++++++++++++-
icons/mob/clothing/back.dmi | Bin 135709 -> 137019 bytes
.../clothing/species/drask/under/civilian.dmi | Bin 22423 -> 24774 bytes
icons/mob/clothing/species/grey/back.dmi | Bin 49565 -> 52423 bytes
.../clothing/species/grey/under/civilian.dmi | Bin 23516 -> 25295 bytes
.../clothing/species/kidan/under/civilian.dmi | Bin 23727 -> 25985 bytes
icons/mob/clothing/species/vox/back.dmi | Bin 43783 -> 44792 bytes
.../clothing/species/vox/under/civilian.dmi | Bin 23209 -> 25687 bytes
icons/mob/clothing/under/civilian.dmi | Bin 23550 -> 26031 bytes
icons/mob/inhands/clothing_lefthand.dmi | Bin 110695 -> 111921 bytes
icons/mob/inhands/clothing_righthand.dmi | Bin 106765 -> 107644 bytes
icons/obj/clothing/under/civilian.dmi | Bin 2994 -> 3140 bytes
icons/obj/storage.dmi | Bin 75508 -> 76121 bytes
17 files changed, 38 insertions(+), 2 deletions(-)
diff --git a/code/game/jobs/job/support.dm b/code/game/jobs/job/support.dm
index 58eec484c1cf..916a9d157dc9 100644
--- a/code/game/jobs/job/support.dm
+++ b/code/game/jobs/job/support.dm
@@ -293,7 +293,7 @@
implants = list(/obj/item/implant/sad_trombone)
backpack = /obj/item/storage/backpack/clown
- satchel = /obj/item/storage/backpack/clown
+ satchel = /obj/item/storage/backpack/satchel/clown
dufflebag = /obj/item/storage/backpack/duffel/clown
/datum/outfit/job/clown/pre_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
diff --git a/code/game/objects/items/weapons/storage/backpack.dm b/code/game/objects/items/weapons/storage/backpack.dm
index cba3393e3b23..0e812f8fc566 100644
--- a/code/game/objects/items/weapons/storage/backpack.dm
+++ b/code/game/objects/items/weapons/storage/backpack.dm
@@ -267,6 +267,12 @@
icon_state = "satchel-norm"
item_state = "satchel-norm"
+/obj/item/storage/backpack/satchel/clown
+ name = "Tickles Von Squeakerton"
+ desc = "A satchel with extra pockets for all your banana storing needs!"
+ icon_state = "satchel-clown"
+ item_state = "satchel-clown"
+
/obj/item/storage/backpack/satchel_eng
name = "industrial satchel"
desc = "A tough satchel with extra pockets."
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/miscjobs.dm b/code/game/objects/structures/crates_lockers/closets/secure/miscjobs.dm
index bfaf642d5a73..fc7bafad01d0 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/miscjobs.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/miscjobs.dm
@@ -6,7 +6,11 @@
/obj/structure/closet/secure_closet/clown/populate_contents()
new /obj/item/storage/backpack/clown(src)
+ new /obj/item/storage/backpack/duffel/clown(src)
+ new /obj/item/storage/backpack/satchel/clown(src)
new /obj/item/clothing/under/rank/civilian/clown(src)
+ new /obj/item/clothing/under/rank/civilian/clown/skirt(src)
+ new /obj/item/clothing/under/rank/civilian/clown/sexy(src)
new /obj/item/clothing/shoes/clown_shoes(src)
new /obj/item/radio/headset/headset_service(src)
new /obj/item/clothing/mask/gas/clown_hat(src)
@@ -33,6 +37,8 @@
new /obj/item/clothing/mask/gas/mime(src)
new /obj/item/radio/headset/headset_service(src)
new /obj/item/clothing/under/rank/civilian/mime(src)
+ new /obj/item/clothing/under/rank/civilian/mime/skirt(src)
+ new /obj/item/clothing/under/rank/civilian/mime/sexy(src)
new /obj/item/clothing/suit/suspenders(src)
new /obj/item/clothing/gloves/color/white(src)
new /obj/item/clothing/shoes/black(src)
diff --git a/code/modules/client/preference/loadout/loadout_uniform.dm b/code/modules/client/preference/loadout/loadout_uniform.dm
index b377d517e2bd..89b461bbcece 100644
--- a/code/modules/client/preference/loadout/loadout_uniform.dm
+++ b/code/modules/client/preference/loadout/loadout_uniform.dm
@@ -203,6 +203,16 @@
path = /obj/item/clothing/under/rank/security/head_of_security/skirt
allowed_roles = list("Head of Security")
+/datum/gear/uniform/skirt/job/clown
+ display_name = "Skirt, clown"
+ path = /obj/item/clothing/under/rank/civilian/clown/skirt
+ allowed_roles = list("Clown")
+
+/datum/gear/uniform/skirt/job/mime
+ display_name = "Skirt, mime"
+ path = /obj/item/clothing/under/rank/civilian/mime/skirt
+ allowed_roles = list("Mime")
+
/datum/gear/uniform/skirt/job/head_of_personnel
display_name = "Skirt, hop"
path = /obj/item/clothing/under/rank/civilian/hop/skirt
diff --git a/code/modules/clothing/under/jobs/civilian.dm b/code/modules/clothing/under/jobs/civilian.dm
index 3c91205d81ec..9098bfc10978 100644
--- a/code/modules/clothing/under/jobs/civilian.dm
+++ b/code/modules/clothing/under/jobs/civilian.dm
@@ -90,9 +90,16 @@
SSticker.score.score_clown_abuse++
return ..()
+/obj/item/clothing/under/rank/civilian/clown/skirt
+ name = "clown skirt"
+ desc = "'HONK!'"
+ icon_state = "clown_skirt"
+ item_state = "clown_skirt"
+ item_color = "clown_skirt"
+
/obj/item/clothing/under/rank/civilian/clown/sexy
name = "sexy-clown suit"
- desc = "It makes you look HONKable!"
+ desc = "It makes you want to practice clown law."
icon_state = "sexyclown"
item_state = "sexyclown"
item_color = "sexyclown"
@@ -107,6 +114,13 @@
item_state = "mime"
item_color = "mime"
+/obj/item/clothing/under/rank/civilian/mime/skirt
+ name = "mime's skirt"
+ desc = "It's not very colourful."
+ icon_state = "mime_skirt"
+ item_state = "mime_skirt"
+ item_color = "mime_skirt"
+
/obj/item/clothing/under/rank/civilian/mime/sexy
name = "sexy mime outfit"
desc = "The only time when you DON'T enjoy looking at someone's rack."
diff --git a/icons/mob/clothing/back.dmi b/icons/mob/clothing/back.dmi
index e9fb5b582b2d13438dfd88ad50a14a18b96e651c..3eea2f356ae1522a5155de3ee7eb012f91ead0ef 100644
GIT binary patch
literal 137019
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