From 5c1322e8c51d0ffc005d25b6dc051e61dadeb307 Mon Sep 17 00:00:00 2001 From: AnturK Date: Sat, 21 Dec 2024 21:49:21 +0100 Subject: [PATCH 001/235] Fixes sass warnings (#88604) Stops sass from screaming about deprecated apis. --- .../tgui-panel/styles/tgchat/chat-dark.scss | 12 ++++++++++-- .../tgui-panel/styles/tgchat/chat-light.scss | 10 ++++++---- tgui/packages/tgui-say/styles/button.scss | 6 +++++- tgui/packages/tgui-say/styles/colors.scss | 2 +- tgui/packages/tgui-say/styles/main.scss | 14 +++++++------- tgui/packages/tgui/styles/layouts/TitleBar.scss | 2 +- 6 files changed, 30 insertions(+), 16 deletions(-) diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss index 2308d720436c8..b3cdfa6e88074 100644 --- a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss +++ b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss @@ -1182,10 +1182,18 @@ $border-width-px: $border-width * 1px; } .chat_alert_#{$color-name} .minor_announcement_text { - background-color: darken(map.get($alert-stripe-colors, $color-name), 5); + background-color: color.adjust( + map.get($alert-stripe-colors, $color-name), + $lightness: -5%, + $space: hsl + ); } .chat_alert_#{$color-name} .major_announcement_text { - background-color: darken(map.get($alert-stripe-colors, $color-name), 5); + background-color: color.adjust( + map.get($alert-stripe-colors, $color-name), + $lightness: -5%, + $space: hsl + ); } } diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss index a3d0688ce362c..e46cd52d5075f 100644 --- a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss +++ b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss @@ -1200,16 +1200,18 @@ $border-width-px: $border-width * 1px; } .chat_alert_#{$color-name} .minor_announcement_text { - background-color: lighten( + background-color: color.adjust( map.get($alert-stripe-alternate-colors, $color-name), - 5 + $lightness: 5%, + $space: hsl ); } .chat_alert_#{$color-name} .major_announcement_text { - background-color: lighten( + background-color: color.adjust( map.get($alert-stripe-alternate-colors, $color-name), - 5 + $lightness: 5%, + $space: hsl ); } } diff --git a/tgui/packages/tgui-say/styles/button.scss b/tgui/packages/tgui-say/styles/button.scss index 89e1cceca943a..69802c045ec5e 100644 --- a/tgui/packages/tgui-say/styles/button.scss +++ b/tgui/packages/tgui-say/styles/button.scss @@ -15,7 +15,11 @@ padding: 0; width: 2.6rem; &:hover { - background-color: lighten(colors.$button, 10%); + background-color: color.adjust( + colors.$button, + $lightness: 10%, + $space: hsl + ); } } diff --git a/tgui/packages/tgui-say/styles/colors.scss b/tgui/packages/tgui-say/styles/colors.scss index 5113af953e740..ce76ba10a1622 100644 --- a/tgui/packages/tgui-say/styles/colors.scss +++ b/tgui/packages/tgui-say/styles/colors.scss @@ -33,7 +33,7 @@ $channel_keys: map.keys($_channel_map) !default; $channel-map: (); @each $channel in $channel_keys { - $channel-map: map-merge( + $channel-map: map.merge( $channel-map, ( $channel: map.get($_channel_map, $channel), diff --git a/tgui/packages/tgui-say/styles/main.scss b/tgui/packages/tgui-say/styles/main.scss index 8e164aa21fa7f..bafc850e242a0 100644 --- a/tgui/packages/tgui-say/styles/main.scss +++ b/tgui/packages/tgui-say/styles/main.scss @@ -26,14 +26,14 @@ } @each $channel, $color in colors.$channel-map { - $darkened: darken($color, 20%); + $darkened: color.adjust($color, $lightness: -20%, $space: hsl); .button-#{$channel} { - border-color: darken($color, 10%); + border-color: color.adjust($color, $lightness: -10%, $space: hsl); color: $color; &:hover { - border-color: lighten($color, 10%); - color: lighten($color, 5%); + border-color: color.adjust($color, $lightness: 10%, $space: hsl); + color: color.adjust($color, $lightness: 5%, $space: hsl); } } @@ -50,11 +50,11 @@ animation: gradient 10s linear infinite; background: linear-gradient( to right, - darken($color, 35%), + color.adjust($color, $lightness: -35%, $space: hsl), $color, - lighten($color, 10%), + color.adjust($color, $lightness: 10%, $space: hsl), $color, - darken($color, 35%) + color.adjust($color, $lightness: -35%, $space: hsl) ); background-position: 0% 0%; background-size: 500% auto; diff --git a/tgui/packages/tgui/styles/layouts/TitleBar.scss b/tgui/packages/tgui/styles/layouts/TitleBar.scss index 517f2b8ad811e..8fd7239d55b1f 100644 --- a/tgui/packages/tgui/styles/layouts/TitleBar.scss +++ b/tgui/packages/tgui/styles/layouts/TitleBar.scss @@ -105,7 +105,7 @@ $shadow-color: hsla(0, 0%, 0%, 0.1) !default; min-width: base.rem(20px); padding: 2px 4px; padding: base.rem(2px) base.rem(4px); - background-color: darken(colors.$good, 10%); + background-color: color.adjust(colors.$good, $lightness: -10%, $space: hsl); color: hsl(120, 100%, 100%); text-align: center; } From ce84f71bafaad76e5ac008c79f26d4b85bbb6c99 Mon Sep 17 00:00:00 2001 From: SyncIt21 <110812394+SyncIt21@users.noreply.github.com> Date: Sun, 22 Dec 2024 02:19:44 +0530 Subject: [PATCH 002/235] Fixes access nesting for personal ordered crates (#88603) ## About The Pull Request - Fixes #87235 Nested access levels after you unlock the crate for the 2nd time was too much even for the captain to deal with ## Changelog :cl: fix: personal ordered crates can be unlocked & relocked as many times again after the 1st attempt /:cl: --- code/modules/cargo/packs/_packs.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/modules/cargo/packs/_packs.dm b/code/modules/cargo/packs/_packs.dm index b6c533050f675..bfa95b2a7ed25 100644 --- a/code/modules/cargo/packs/_packs.dm +++ b/code/modules/cargo/packs/_packs.dm @@ -106,7 +106,7 @@ name = "mining order" hidden = TRUE crate_name = "shaft mining delivery crate" - access = list(ACCESS_MINING) + access = ACCESS_MINING /datum/supply_pack/custom/New(purchaser, cost, list/contains) . = ..() @@ -117,7 +117,7 @@ /datum/supply_pack/custom/minerals name = "materials order" crate_name = "galactic materials market delivery crate" - access = list() + access = FALSE crate_type = /obj/structure/closet/crate/cardboard /datum/supply_pack/custom/minerals/New(purchaser, cost, list/contains) From ed0cb867aa1a5936ed411f7a908fc1a00c7243a2 Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Sat, 21 Dec 2024 23:49:56 +0300 Subject: [PATCH 003/235] Adds a Condi-Master to Birdshot's bar (#88602) ## About The Pull Request Closes #88592 ![image](https://github.com/user-attachments/assets/adf09947-985b-48c8-8f08-e91dd02ffce3) ## Why It's Good For The Game Map consistency, bartenders need it to do their job ## Changelog :cl: map: Added a Condi-Master to Birdshot's bar /:cl: --- _maps/map_files/Birdshot/birdshot.dmm | 30 ++++++++++++--------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/_maps/map_files/Birdshot/birdshot.dmm b/_maps/map_files/Birdshot/birdshot.dmm index de9e1338f2939..125324bb07ea4 100644 --- a/_maps/map_files/Birdshot/birdshot.dmm +++ b/_maps/map_files/Birdshot/birdshot.dmm @@ -9394,10 +9394,9 @@ /obj/machinery/airalarm/directional/north, /obj/structure/table/wood, /obj/machinery/chem_dispenser/drinks, -/obj/effect/turf_decal/siding/wood{ - dir = 5 +/obj/effect/turf_decal/siding/wood/end{ + dir = 4 }, -/obj/effect/turf_decal/siding/wood, /turf/open/floor/iron/dark/diagonal, /area/station/service/bar) "dxZ" = ( @@ -10072,6 +10071,7 @@ }, /obj/structure/disposalpipe/segment, /obj/structure/cable, +/obj/structure/extinguisher_cabinet/directional/west, /turf/open/floor/stone, /area/station/service/bar) "dMm" = ( @@ -33247,11 +33247,10 @@ pixel_x = -7; pixel_y = 15 }, -/obj/effect/turf_decal/siding/wood{ - dir = 9 - }, -/obj/effect/turf_decal/siding/wood, /obj/structure/sign/warning/no_smoking/circle/directional/north, +/obj/effect/turf_decal/siding/wood/end{ + dir = 8 + }, /turf/open/floor/iron/dark/diagonal, /area/station/service/bar) "lnI" = ( @@ -50833,12 +50832,6 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /turf/open/floor/iron/dark, /area/station/service/lawoffice) -"rrX" = ( -/obj/effect/turf_decal/siding/wood/corner{ - dir = 4 - }, -/turf/open/floor/stone, -/area/station/service/bar) "rrZ" = ( /obj/structure/closet/crate/trashcart, /obj/effect/spawner/random/trash/garbage, @@ -56445,11 +56438,14 @@ /turf/open/floor/eighties/red, /area/station/hallway/primary/central/fore) "tjT" = ( -/obj/structure/extinguisher_cabinet/directional/north, +/obj/machinery/chem_master/condimaster, +/obj/effect/turf_decal/siding/wood/end{ + dir = 8 + }, /obj/effect/turf_decal/siding/wood{ - dir = 5 + dir = 4 }, -/turf/open/floor/stone, +/turf/open/floor/iron/dark/diagonal, /area/station/service/bar) "tjY" = ( /obj/machinery/atmospherics/components/binary/pump/on{ @@ -100086,7 +100082,7 @@ iXW dRb sON tjT -rrX +xkV lAV fYJ eGU From 9d109030f08764d7d230efa6bd835ad355e3413c Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 20:50:05 +0000 Subject: [PATCH 004/235] Automatic changelog for PR #88603 [ci skip] --- html/changelogs/AutoChangeLog-pr-88603.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88603.yml diff --git a/html/changelogs/AutoChangeLog-pr-88603.yml b/html/changelogs/AutoChangeLog-pr-88603.yml new file mode 100644 index 0000000000000..363c021935304 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88603.yml @@ -0,0 +1,4 @@ +author: "SyncIt21" +delete-after: True +changes: + - bugfix: "personal ordered crates can be unlocked & relocked as many times again after the 1st attempt" \ No newline at end of file From 15d49160e416356849e33c23c07476d71972f79e Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 20:50:16 +0000 Subject: [PATCH 005/235] Automatic changelog for PR #88602 [ci skip] --- html/changelogs/AutoChangeLog-pr-88602.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88602.yml diff --git a/html/changelogs/AutoChangeLog-pr-88602.yml b/html/changelogs/AutoChangeLog-pr-88602.yml new file mode 100644 index 0000000000000..c9698b7f26b81 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88602.yml @@ -0,0 +1,4 @@ +author: "SmArtKar" +delete-after: True +changes: + - map: "Added a Condi-Master to Birdshot's bar" \ No newline at end of file From ad4108d6f3ec23a447dd1e0fef10f5ff4bad062a Mon Sep 17 00:00:00 2001 From: SyncIt21 <110812394+SyncIt21@users.noreply.github.com> Date: Sun, 22 Dec 2024 02:42:32 +0530 Subject: [PATCH 006/235] Adds better combined colour check for greyscale modify menu (#88598) ## About The Pull Request - Fixes #87829 Ensures we can't enter invalid values that actually run timed in this specific case ## Changelog :cl: fix: greyscale modify menu has better validation for player entered colours /:cl: --- code/modules/admin/greyscale_modify_menu.dm | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/code/modules/admin/greyscale_modify_menu.dm b/code/modules/admin/greyscale_modify_menu.dm index 0bc1ec01f5d4f..c87c424661fd2 100644 --- a/code/modules/admin/greyscale_modify_menu.dm +++ b/code/modules/admin/greyscale_modify_menu.dm @@ -241,14 +241,24 @@ This is highly likely to cause massive amounts of lag as every object in the gam config.EnableAutoRefresh(config_owner_type) /datum/greyscale_modify_menu/proc/ReadColorsFromString(colorString) - var/list/new_split_colors = list() + //length validation var/list/colors = splittext(colorString, "#") - for(var/index in 2 to min(length(colors), config.expected_colors + 1)) + if(length(colors) <= 1) //doesn't even begin with a # so isn't even a color + return FALSE + colors.Cut(1, 2) //removes the white space as a consequence of the string beginning with a # + if(colors.len != config.expected_colors) //not the expected length + return FALSE + + //value validation + var/list/new_split_colors = list() + for(var/index in 1 to config.expected_colors) var/color = "#[colors[index]]" if(!findtext(color, GLOB.is_color) && (!unlocked || !findtext(color, GLOB.is_alpha_color))) return FALSE new_split_colors += color split_colors = new_split_colors + + //all good return TRUE /datum/greyscale_modify_menu/proc/randomize_color(color_index) From dd0db8502fbd8be1c75562cad92aac7da3c5d935 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 21:12:50 +0000 Subject: [PATCH 007/235] Automatic changelog for PR #88598 [ci skip] --- html/changelogs/AutoChangeLog-pr-88598.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88598.yml diff --git a/html/changelogs/AutoChangeLog-pr-88598.yml b/html/changelogs/AutoChangeLog-pr-88598.yml new file mode 100644 index 0000000000000..6feb07ff376e8 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88598.yml @@ -0,0 +1,4 @@ +author: "SyncIt21" +delete-after: True +changes: + - bugfix: "greyscale modify menu has better validation for player entered colours" \ No newline at end of file From 188af3c2a91e18aab52c27faac2ec5f660766376 Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Sun, 22 Dec 2024 00:13:15 +0300 Subject: [PATCH 008/235] Fixes shoes slot being semi-transparent when you have digitigrade legs (#88600) ## About The Pull Request You can now wear shoes with digitigrade legs thanks to #88096, but UI slots weren't informed of this change. ## Changelog :cl: fix: Fixed shoes slot being semi-transparent when you have digitigrade legs /:cl: --- code/_onclick/hud/human.dm | 2 -- 1 file changed, 2 deletions(-) diff --git a/code/_onclick/hud/human.dm b/code/_onclick/hud/human.dm index 918f5f62a329b..e17757501cf9e 100644 --- a/code/_onclick/hud/human.dm +++ b/code/_onclick/hud/human.dm @@ -322,8 +322,6 @@ var/obj/item/organ/eyes/eyes = human_mob.get_organ_slot(ORGAN_SLOT_EYES) if(eyes?.no_glasses) blocked_slots |= ITEM_SLOT_EYES - if(human_mob.bodyshape & BODYSHAPE_DIGITIGRADE) - blocked_slots |= ITEM_SLOT_FEET for(var/atom/movable/screen/inventory/inv in (static_inventory + toggleable_inventory)) if(!inv.slot_id) From 7d821f8d2a107c830dc60c8005da4dc45ab21c60 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 21:13:35 +0000 Subject: [PATCH 009/235] Automatic changelog for PR #88600 [ci skip] --- html/changelogs/AutoChangeLog-pr-88600.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88600.yml diff --git a/html/changelogs/AutoChangeLog-pr-88600.yml b/html/changelogs/AutoChangeLog-pr-88600.yml new file mode 100644 index 0000000000000..f8f6ef99810f9 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88600.yml @@ -0,0 +1,4 @@ +author: "SmArtKar" +delete-after: True +changes: + - bugfix: "Fixed shoes slot being semi-transparent when you have digitigrade legs" \ No newline at end of file From 537ac104c4a398b63bd6ad8bd9b1f8fc1d1cd062 Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Sat, 21 Dec 2024 15:14:43 -0600 Subject: [PATCH 010/235] Nitroglycerin heals heart damage (#88571) ## About The Pull Request Nitroglycerin heals heart damage when metabolized ## Why It's Good For The Game - I thought it'd be a fun nod to one of the actual uses of Nitroglycerin. - "Plausible Deniability" to chemists making Nitroglycerin. - Adds more niche chem effects to the ecosystem chemists may experiment with. ## Changelog :cl: Melbert add: Nitroglycerin now heals heart damage. /:cl: --- .../reagents/chemistry/reagents/pyrotechnic_reagents.dm | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm index c401d39f020ed..0ad3b55fb59d3 100644 --- a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm @@ -19,11 +19,17 @@ /datum/reagent/nitroglycerin name = "Nitroglycerin" - description = "Nitroglycerin is a heavy, colorless, oily, explosive liquid obtained by nitrating glycerol." + description = "Nitroglycerin is a heavy, colorless, oily liquid obtained by nitrating glycerol. \ + It is commonly used to treat heart conditions, but also in the creation of explosives." color = COLOR_GRAY taste_description = "oil" chemical_flags = REAGENT_CAN_BE_SYNTHESIZED +/datum/reagent/nitroglycerin/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) + . = ..() + if(affected_mob.adjustOrganLoss(ORGAN_SLOT_HEART, -1 * REM * seconds_per_tick * normalise_creation_purity(), required_organ_flag = affected_organ_flags)) + return UPDATE_MOB_HEALTH + /datum/reagent/stabilizing_agent name = "Stabilizing Agent" description = "Keeps unstable chemicals stable. This does not work on everything." From 43c46cc65091ddf6d8cda93f12bcb431f59bc4cc Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 21:15:05 +0000 Subject: [PATCH 011/235] Automatic changelog for PR #88571 [ci skip] --- html/changelogs/AutoChangeLog-pr-88571.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88571.yml diff --git a/html/changelogs/AutoChangeLog-pr-88571.yml b/html/changelogs/AutoChangeLog-pr-88571.yml new file mode 100644 index 0000000000000..f8895bb194691 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88571.yml @@ -0,0 +1,4 @@ +author: "Melbert" +delete-after: True +changes: + - rscadd: "Nitroglycerin now heals heart damage." \ No newline at end of file From 15a0a1754af79be66ad452e81ec8e49ae58e3293 Mon Sep 17 00:00:00 2001 From: Tim Date: Sat, 21 Dec 2024 15:15:34 -0600 Subject: [PATCH 012/235] Update teleporter description code (#88564) ## About The Pull Request Teleporter code is at least +15 years old. This no longer applies. ## Why It's Good For The Game Accuracy. ## Changelog :cl: spellcheck: Update teleporter machine desc to be accurate /:cl: --- code/game/machinery/teleporter.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/game/machinery/teleporter.dm b/code/game/machinery/teleporter.dm index c46f6b351543d..262e014ee0b79 100644 --- a/code/game/machinery/teleporter.dm +++ b/code/game/machinery/teleporter.dm @@ -33,7 +33,7 @@ /obj/machinery/teleport/hub/examine(mob/user) . = ..() if(in_range(user, src) || isobserver(user)) - . += span_notice("The status display reads: Probability of malfunction decreased by [(accuracy*25)-25]%.") + . += span_notice("The status display reads: Success chance is [70 + (accuracy * 10)]%.") /obj/machinery/teleport/hub/proc/link_power_station() if(power_station) From d0f78d689db37fe38f54c7ac8af8d9b68cec0e30 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 21:15:54 +0000 Subject: [PATCH 013/235] Automatic changelog for PR #88564 [ci skip] --- html/changelogs/AutoChangeLog-pr-88564.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88564.yml diff --git a/html/changelogs/AutoChangeLog-pr-88564.yml b/html/changelogs/AutoChangeLog-pr-88564.yml new file mode 100644 index 0000000000000..d9a83b2f69da3 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88564.yml @@ -0,0 +1,4 @@ +author: "timothymtorres" +delete-after: True +changes: + - spellcheck: "Update teleporter machine desc to be accurate" \ No newline at end of file From a0ffc1fd2c3027e95e7b8f0d34f2fd0d6f7bcc00 Mon Sep 17 00:00:00 2001 From: SyncIt21 <110812394+SyncIt21@users.noreply.github.com> Date: Sun, 22 Dec 2024 02:46:11 +0530 Subject: [PATCH 014/235] Fixes runtime for mecha air tank processing (#88601) ## About The Pull Request - Fixes #88103 ## Changelog :cl: fix: fixed runtime when sealing mecha cabin with air tank installed /:cl: --- code/modules/vehicles/mecha/equipment/tools/air_tank.dm | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/code/modules/vehicles/mecha/equipment/tools/air_tank.dm b/code/modules/vehicles/mecha/equipment/tools/air_tank.dm index f00444ae598b0..6d9765e6b0588 100644 --- a/code/modules/vehicles/mecha/equipment/tools/air_tank.dm +++ b/code/modules/vehicles/mecha/equipment/tools/air_tank.dm @@ -79,11 +79,10 @@ var/datum/gas_mixture/tank_air = internal_tank.return_air() var/datum/gas_mixture/cabin_air = chassis.cabin_air var/release_pressure = internal_tank.release_pressure - var/cabin_pressure = cabin_air.return_pressure() - if(cabin_pressure < release_pressure) + if(cabin_air.return_pressure() < release_pressure) tank_air.release_gas_to(cabin_air, release_pressure) - if(cabin_pressure) - cabin_air.pump_gas_to(external_air, PUMP_MAX_PRESSURE, GAS_CO2) + if(cabin_air.has_gas(/datum/gas/carbon_dioxide)) + cabin_air.pump_gas_to(external_air, PUMP_MAX_PRESSURE, /datum/gas/carbon_dioxide) /obj/item/mecha_parts/mecha_equipment/air_tank/proc/process_pump(seconds_per_tick) if(!tank_pump_active) From d2297600cef3ec701c5f4dd45846813ed68857f5 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 21:16:31 +0000 Subject: [PATCH 015/235] Automatic changelog for PR #88601 [ci skip] --- html/changelogs/AutoChangeLog-pr-88601.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88601.yml diff --git a/html/changelogs/AutoChangeLog-pr-88601.yml b/html/changelogs/AutoChangeLog-pr-88601.yml new file mode 100644 index 0000000000000..b3e38d483d9ff --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88601.yml @@ -0,0 +1,4 @@ +author: "SyncIt21" +delete-after: True +changes: + - bugfix: "fixed runtime when sealing mecha cabin with air tank installed" \ No newline at end of file From c9d99e5ebe79ca85f80db21f1413fb50687f54f8 Mon Sep 17 00:00:00 2001 From: Kocma-san <112967882+Kocma-san@users.noreply.github.com> Date: Sun, 22 Dec 2024 04:16:47 +0700 Subject: [PATCH 016/235] fix radio sound output when receiving a message (#88596) ## About The Pull Request There were two variables mixed up. It was `SEND_SOUND(holder, radio_noise)`, though it should be `SEND_SOUND(holder, radio_receive)`. Now everything is in place And also added `COOLDOWN_START(src, audio_cooldown, 0.5 SECONDS)` so that you don't hear two sounds at the same time when you talk into the radio
Screenshots/Videos https://github.com/user-attachments/assets/1afc1b36-4b6e-41d0-825d-aab6d9bdf694
## Why It's Good For The Game it's just a code issue ## Changelog :cl: fix: fix radio sound output when receiving a message sound: the sound of receiving your own messages over the radio is no longer played /:cl: --- code/game/objects/items/devices/radio/radio.dm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm index ac9cbfec8211f..f00ac8128960b 100644 --- a/code/game/objects/items/devices/radio/radio.dm +++ b/code/game/objects/items/devices/radio/radio.dm @@ -351,7 +351,8 @@ if(isliving(talking_movable)) var/mob/living/talking_living = talking_movable var/volume_modifier = (talking_living.client?.prefs.read_preference(/datum/preference/numeric/sound_radio_noise)) - if(radio_noise && talking_living.can_hear() && volume_modifier && signal.frequency != FREQ_COMMON && !LAZYACCESS(message_mods, MODE_SEQUENTIAL)) + if(radio_noise && talking_living.can_hear() && volume_modifier && signal.frequency != FREQ_COMMON && !LAZYACCESS(message_mods, MODE_SEQUENTIAL) && COOLDOWN_FINISHED(src, audio_cooldown)) + COOLDOWN_START(src, audio_cooldown, 0.5 SECONDS) var/sound/radio_noise = sound('sound/items/radio/radio_talk.ogg', volume = volume_modifier) radio_noise.frequency = get_rand_frequency_low_range() SEND_SOUND(talking_living, radio_noise) @@ -439,7 +440,7 @@ COOLDOWN_START(src, audio_cooldown, 0.5 SECONDS) var/sound/radio_receive = sound('sound/items/radio/radio_receive.ogg', volume = volume_modifier) radio_receive.frequency = get_rand_frequency_low_range() - SEND_SOUND(holder, radio_noise) + SEND_SOUND(holder, radio_receive) if((SPAN_COMMAND in spans) && COOLDOWN_FINISHED(src, important_audio_cooldown)) COOLDOWN_START(src, important_audio_cooldown, 0.5 SECONDS) var/sound/radio_important = sound('sound/items/radio/radio_important.ogg', volume = volume_modifier) From 42bd7434efde88b38d5dfa7e7a4140a98abb0543 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 21:17:09 +0000 Subject: [PATCH 017/235] Automatic changelog for PR #88596 [ci skip] --- html/changelogs/AutoChangeLog-pr-88596.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88596.yml diff --git a/html/changelogs/AutoChangeLog-pr-88596.yml b/html/changelogs/AutoChangeLog-pr-88596.yml new file mode 100644 index 0000000000000..d59028d86fc4e --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88596.yml @@ -0,0 +1,5 @@ +author: "Kocma-san" +delete-after: True +changes: + - bugfix: "fix radio sound output when receiving a message" + - sound: "the sound of receiving your own messages over the radio is no longer played" \ No newline at end of file From d1e437ee29ad5e171aa6a1a89bd0e11c4812fdcd Mon Sep 17 00:00:00 2001 From: grungussuss <96586172+Sadboysuss@users.noreply.github.com> Date: Sun, 22 Dec 2024 00:19:28 +0300 Subject: [PATCH 018/235] You will no longer die of motion sickness from gibtonite on icebox (#88608) ## About The Pull Request - if an explosion epicenter is not on a station area, then it will not screen shake all mobs on the z level if it was really far ## Why It's Good For The Game - why should a bomb from 100 tiles away screenshake the entire z level? - players have brought up their annoyance with icebox gitbtonite shaking the entire z level every time one explodes. ## Changelog :cl: grungussuss del: screenshake from explosions will no longer happen when it's really far and not on a station area (turf) /:cl: --- code/controllers/subsystem/explosions.dm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/controllers/subsystem/explosions.dm b/code/controllers/subsystem/explosions.dm index 20194e66626ca..2b61cabb86074 100644 --- a/code/controllers/subsystem/explosions.dm +++ b/code/controllers/subsystem/explosions.dm @@ -524,6 +524,7 @@ ADMIN_VERB(check_bomb_impacts, R_DEBUG, "Check Bomb Impact", "See what the effec /datum/controller/subsystem/explosions/proc/shake_the_room(turf/epicenter, near_distance, far_distance, quake_factor, echo_factor, creaking, sound/near_sound = sound(get_sfx(SFX_EXPLOSION)), sound/far_sound = sound('sound/effects/explosion/explosionfar.ogg'), sound/echo_sound = sound('sound/effects/explosion/explosion_distant.ogg'), sound/creaking_sound = sound(get_sfx(SFX_EXPLOSION_CREAKING)), hull_creaking_sound = sound(get_sfx(SFX_HULL_CREAKING))) var/frequency = get_rand_frequency() var/blast_z = epicenter.z + var/area/epicenter_area = get_area(epicenter) if(isnull(creaking)) // Autoset creaking. var/on_station = SSmapping.level_trait(epicenter.z, ZTRAIT_STATION) if(on_station && prob((quake_factor * QUAKE_CREAK_PROB) + (echo_factor * ECHO_CREAK_PROB))) // Huge explosions are near guaranteed to make the station creak and whine, smaller ones might. @@ -559,7 +560,7 @@ ADMIN_VERB(check_bomb_impacts, R_DEBUG, "Check Bomb Impact", "See what the effec base_shake_amount = max(base_shake_amount, quake_factor * 3, 0) // Devastating explosions rock the station and ground shake_camera(listener, FAR_SHAKE_DURATION, min(base_shake_amount, FAR_SHAKE_CAP)) - else if(!isspaceturf(listener_turf) && echo_factor) // Big enough explosions echo through the hull. + else if(!isspaceturf(listener_turf) && !(!(epicenter_area.type in GLOB.the_station_areas) && SSmapping.is_planetary()) && echo_factor) // Big enough explosions echo through the hull. Except on planetary maps if the epicenter is not on the station's area. var/echo_volume if(quake_factor) echo_volume = 60 From be8862b647e32084d03dd230018f0c72f08342da Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 21:20:06 +0000 Subject: [PATCH 019/235] Automatic changelog for PR #88608 [ci skip] --- html/changelogs/AutoChangeLog-pr-88608.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88608.yml diff --git a/html/changelogs/AutoChangeLog-pr-88608.yml b/html/changelogs/AutoChangeLog-pr-88608.yml new file mode 100644 index 0000000000000..e7ed3dd34af6a --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88608.yml @@ -0,0 +1,4 @@ +author: "grungussuss" +delete-after: True +changes: + - rscdel: "screenshake from explosions will no longer happen when it's really far and not on a station area (turf)" \ No newline at end of file From 00fbea8748dbf53332774bf0df86d32b81e74e04 Mon Sep 17 00:00:00 2001 From: Tim Date: Sat, 21 Dec 2024 15:20:30 -0600 Subject: [PATCH 020/235] Add seconds define to teleporter (#88563) ## About The Pull Request `5 SECONDS` instead of `50` ## Why It's Good For The Game Better code. ## Changelog :cl: code: Improved teleporter SECONDS defines /:cl: --- code/game/machinery/computer/teleporter.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/game/machinery/computer/teleporter.dm b/code/game/machinery/computer/teleporter.dm index 8cd12610c748b..74e6d22e36355 100644 --- a/code/game/machinery/computer/teleporter.dm +++ b/code/game/machinery/computer/teleporter.dm @@ -116,7 +116,7 @@ say("Processing hub calibration to target...") calibrating = TRUE power_station.update_appearance() - addtimer(CALLBACK(src, PROC_REF(finish_calibration)), 50 * (3 - power_station.teleporter_hub.accuracy)) //Better parts mean faster calibration + addtimer(CALLBACK(src, PROC_REF(finish_calibration)), 5 SECONDS * (3 - power_station.teleporter_hub.accuracy)) //Better parts mean faster calibration return TRUE /obj/machinery/computer/teleporter/proc/set_teleport_target(new_target) From dd0893569c5630b6954592a6bac3da29d22f0712 Mon Sep 17 00:00:00 2001 From: Tim Date: Sat, 21 Dec 2024 15:21:02 -0600 Subject: [PATCH 021/235] Holodeck grammar tweak (#88566) ## About The Pull Request Apparently adding a tiny coffee machine worth of power to make this accurate: `to_chat(user, span_warning("You vastly increase projector power and override the safety and security protocols."))` was too much. So let's just do a grammar tweak to fix it. Is ## Why It's Good For The Game Accurate descriptions. ## Changelog :cl: spellcheck: Fix holodeck emag message claiming to increase power /:cl: --- code/game/objects/items/teleportation.dm | 12 +++++++++++- code/modules/holodeck/computer.dm | 4 ++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/code/game/objects/items/teleportation.dm b/code/game/objects/items/teleportation.dm index 6fca04c7a3f40..ad62f3c086735 100644 --- a/code/game/objects/items/teleportation.dm +++ b/code/game/objects/items/teleportation.dm @@ -179,7 +179,17 @@ var/area/computer_area = get_area(target) if(!computer_area || (computer_area.area_flags & NOTELEPORT)) continue - if(computer.power_station?.teleporter_hub && computer.power_station.engaged) + + if(!computer.power_station || !computer.power_station.teleporter_hub) + continue + + if((computer.power_station.machine_stat & (NOPOWER|BROKEN|MAINT)) || computer.power_station.panel_open) + continue + + if((computer.power_station.teleporter_hub.machine_stat & (NOPOWER|BROKEN|MAINT)) || computer.power_station.teleporter_hub.panel_open) + continue + + if(computer.power_station.engaged) locations["[get_area(target)] (Active)"] = computer else locations["[get_area(target)] (Inactive)"] = computer diff --git a/code/modules/holodeck/computer.dm b/code/modules/holodeck/computer.dm index 6d9c380112b7c..6d80e1683fbe1 100644 --- a/code/modules/holodeck/computer.dm +++ b/code/modules/holodeck/computer.dm @@ -434,8 +434,8 @@ GLOBAL_LIST_INIT(typecache_holodeck_linked_floorcheck_ok, typecacheof(list(/turf playsound(src, SFX_SPARKS, 75, TRUE) obj_flags |= EMAGGED if (user) - balloon_alert(user, "safety protocols destroyed") // im gonna keep this once since this perfectly describes it, and the to_chat is just flavor - to_chat(user, span_warning("You vastly increase projector power and override the safety and security protocols.")) + balloon_alert(user, "safety protocols destroyed") // im gonna keep this once since this perfectly describes it + to_chat(user, span_warning("You override the safety and security protocols.")) user.log_message("emagged the Holodeck Control Console.", LOG_GAME) message_admins("[ADMIN_LOOKUPFLW(user)] emagged the Holodeck Control Console.") From b82d15df0e75b0b0623a95987de714b9e60f7743 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 21:21:24 +0000 Subject: [PATCH 022/235] Automatic changelog for PR #88566 [ci skip] --- html/changelogs/AutoChangeLog-pr-88566.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88566.yml diff --git a/html/changelogs/AutoChangeLog-pr-88566.yml b/html/changelogs/AutoChangeLog-pr-88566.yml new file mode 100644 index 0000000000000..9e11b9250c684 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88566.yml @@ -0,0 +1,4 @@ +author: "timothymtorres" +delete-after: True +changes: + - spellcheck: "Fix holodeck emag message claiming to increase power" \ No newline at end of file From dc7cc0086c6016add94858c1e2b50b14ae5163eb Mon Sep 17 00:00:00 2001 From: Lucy Date: Sat, 21 Dec 2024 16:22:24 -0500 Subject: [PATCH 023/235] Rust heretic healing now uses delta time (#88569) ## About The Pull Request This makes it so leeching walk and ascended rust heretic healing effects (the healing that occurs during life ticks) have the healing amount multiplied by `DELTA_WORLD_TIME(SSmobs)`, to compensate for skipped/delayed fires. ## Why It's Good For The Game Delays in SSmobs firing (i.e explosions pausing all non-ticker subsystems) can very easily get you killed if you're relying on the rust heretic healing mid-combat. ## Changelog :cl: qol: Rust heretic healing (leeching walk, rust ascension) now, so server lag shouldn't fuck you over nearly as much if you're relying on the healing. /:cl: --- code/datums/elements/leeching_walk.dm | 22 +++++++++---------- .../heretic/knowledge/rust_lore.dm | 13 ++++++----- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/code/datums/elements/leeching_walk.dm b/code/datums/elements/leeching_walk.dm index c9f547189e699..f5148b43a5a52 100644 --- a/code/datums/elements/leeching_walk.dm +++ b/code/datums/elements/leeching_walk.dm @@ -24,9 +24,8 @@ var/turf/mover_turf = get_turf(source) if(HAS_TRAIT(mover_turf, TRAIT_RUSTY)) ADD_TRAIT(source, TRAIT_BATON_RESISTANCE, type) - return - - REMOVE_TRAIT(source, TRAIT_BATON_RESISTANCE, type) + else + REMOVE_TRAIT(source, TRAIT_BATON_RESISTANCE, type) /** * Signal proc for [COMSIG_LIVING_LIFE]. @@ -43,17 +42,18 @@ // Heals all damage + Stamina var/need_mob_update = FALSE - need_mob_update += source.adjustBruteLoss(-3, updating_health = FALSE) - need_mob_update += source.adjustFireLoss(-3, updating_health = FALSE) - need_mob_update += source.adjustToxLoss(-3, updating_health = FALSE, forced = TRUE) // Slimes are people to - need_mob_update += source.adjustOxyLoss(-1.5, updating_health = FALSE) - need_mob_update += source.adjustStaminaLoss(-10, updating_stamina = FALSE) + var/delta_time = DELTA_WORLD_TIME(SSmobs) * 0.5 // SSmobs.wait is 2 secs, so this should be halved. + need_mob_update += source.adjustBruteLoss(-3 * delta_time, updating_health = FALSE) + need_mob_update += source.adjustFireLoss(-3 * delta_time, updating_health = FALSE) + need_mob_update += source.adjustToxLoss(-3 * delta_time, updating_health = FALSE, forced = TRUE) // Slimes are people too + need_mob_update += source.adjustOxyLoss(-1.5 * delta_time, updating_health = FALSE) + need_mob_update += source.adjustStaminaLoss(-10 * delta_time, updating_stamina = FALSE) if(need_mob_update) source.updatehealth() // Reduces duration of stuns/etc - source.AdjustAllImmobility(-0.5 SECONDS) + source.AdjustAllImmobility((-0.5 SECONDS) * delta_time) // Heals blood loss if(source.blood_volume < BLOOD_VOLUME_NORMAL) - source.blood_volume += 2.5 * seconds_per_tick + source.blood_volume += 2.5 * delta_time // Slowly regulates your body temp - source.adjust_bodytemperature((source.get_body_temp_normal() - source.bodytemperature)/5) + source.adjust_bodytemperature((source.get_body_temp_normal() - source.bodytemperature) / 5) diff --git a/code/modules/antagonists/heretic/knowledge/rust_lore.dm b/code/modules/antagonists/heretic/knowledge/rust_lore.dm index 41db760f53ae6..4a60d372445e1 100644 --- a/code/modules/antagonists/heretic/knowledge/rust_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/rust_lore.dm @@ -287,12 +287,13 @@ return var/need_mob_update = FALSE - need_mob_update += source.adjustBruteLoss(-5, updating_health = FALSE) - need_mob_update += source.adjustFireLoss(-5, updating_health = FALSE) - need_mob_update += source.adjustToxLoss(-5, updating_health = FALSE, forced = TRUE) - need_mob_update += source.adjustOxyLoss(-5, updating_health = FALSE) - need_mob_update += source.adjustStaminaLoss(-20, updating_stamina = FALSE) + var/base_heal_amt = 2.5 * DELTA_WORLD_TIME(SSmobs) + need_mob_update += source.adjustBruteLoss(-base_heal_amt, updating_health = FALSE) + need_mob_update += source.adjustFireLoss(-base_heal_amt, updating_health = FALSE) + need_mob_update += source.adjustToxLoss(-base_heal_amt, updating_health = FALSE, forced = TRUE) + need_mob_update += source.adjustOxyLoss(-base_heal_amt, updating_health = FALSE) + need_mob_update += source.adjustStaminaLoss(-base_heal_amt * 4, updating_stamina = FALSE) if(source.blood_volume < BLOOD_VOLUME_NORMAL) - source.blood_volume += 5 * seconds_per_tick + source.blood_volume += base_heal_amt if(need_mob_update) source.updatehealth() From c810ae6948a2d85dd913ec3d1aa80e64cda7dae3 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 21:22:58 +0000 Subject: [PATCH 024/235] Automatic changelog for PR #88569 [ci skip] --- html/changelogs/AutoChangeLog-pr-88569.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88569.yml diff --git a/html/changelogs/AutoChangeLog-pr-88569.yml b/html/changelogs/AutoChangeLog-pr-88569.yml new file mode 100644 index 0000000000000..f9db5c10ca6ff --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88569.yml @@ -0,0 +1,4 @@ +author: "Absolucy" +delete-after: True +changes: + - qol: "Rust heretic healing (leeching walk, rust ascension) now, so server lag shouldn't fuck you over nearly as much if you're relying on the healing." \ No newline at end of file From eaebd9d8ea7b0d42731d7b054d8d9a30f1d79bbf Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Sun, 22 Dec 2024 00:23:07 +0300 Subject: [PATCH 025/235] Bioscrambler no longer informs you about "your armor softening the blow!" (#88610) ## About The Pull Request Made bioscrabmle() proc's armor check silent and moved text from a successful block into its early return. ## Why It's Good For The Game Less than 100 protection from bioscramblers doesn't do anything, so text about "armor softening the blow" is nonsensical, both as there is no blow to soften, and as armor below 100 doesn't actually do anything. A bit of a nitpick, but its been annoying me to all hell every time I encounter one (as RD's coat provides some protection). Only other source of BIO armor checks is Phazon, but the text makes sense there and nothing runs armor checks against FIRE and ACID, so there's nothing else to fix. ## Changelog :cl: spellcheck: Bioscrambler no longer informs you about "your armor softening the blow!" /:cl: --- code/modules/mob/living/carbon/carbon_defense.dm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index ee06686faafee..b5bc0f4909edc 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -662,7 +662,8 @@ if (HAS_TRAIT(src, TRAIT_GENELESS)) return FALSE - if (run_armor_check(attack_flag = BIO, absorb_text = "Your armor protects you from [scramble_source]!") >= 100) + if (run_armor_check(attack_flag = BIO, silent = TRUE) >= 100) + to_chat(src, span_warning("Your armor shields you from [scramble_source]!")) return FALSE if (!length(GLOB.bioscrambler_valid_organs) || !length(GLOB.bioscrambler_valid_parts)) From ddf04e1a16d67a1a3692101ae6ce9799498192b0 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 21:23:29 +0000 Subject: [PATCH 026/235] Automatic changelog for PR #88610 [ci skip] --- html/changelogs/AutoChangeLog-pr-88610.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88610.yml diff --git a/html/changelogs/AutoChangeLog-pr-88610.yml b/html/changelogs/AutoChangeLog-pr-88610.yml new file mode 100644 index 0000000000000..958a64f99451b --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88610.yml @@ -0,0 +1,4 @@ +author: "SmArtKar" +delete-after: True +changes: + - spellcheck: "Bioscrambler no longer informs you about \"your armor softening the blow!\"" \ No newline at end of file From 8c3b0a4f4bad4a5d28d0de8420ded78c3c0123c1 Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Sun, 22 Dec 2024 00:26:14 +0300 Subject: [PATCH 027/235] Chemmaster no longer rounds reagent amounts when trying to transfer a custom amount (#88590) ## About The Pull Request #82002 changed custom transfer amount into a TGUI popup, which has rounding on by default. This prevents players from being able to transfer decimal amounts of reagents like they could previously. ## Why It's Good For The Game This is useful for high-purity recipes, or when you REALLY want to microdose something. Right now the only way to do this is by printing tiny pills and dissolving those which is very bootleg. ## Changelog :cl: fix: Chemmaster no longer rounds reagent amounts when trying to transfer a custom amount /:cl: --- code/modules/reagents/chemistry/machinery/chem_master.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/reagents/chemistry/machinery/chem_master.dm b/code/modules/reagents/chemistry/machinery/chem_master.dm index bcb6cac2f183d..22abc46d04009 100644 --- a/code/modules/reagents/chemistry/machinery/chem_master.dm +++ b/code/modules/reagents/chemistry/machinery/chem_master.dm @@ -415,7 +415,7 @@ if(amount == -1) // Set custom amount var/mob/user = ui.user //Hold a reference of the user if the UI is closed - amount = tgui_input_number(user, "Enter amount to transfer", "Transfer amount") + amount = FLOOR(tgui_input_number(user, "Enter amount to transfer", "Transfer amount", round_value = FALSE), CHEMICAL_VOLUME_ROUNDING) if(!amount || !user.can_perform_action(src)) return FALSE From d0ba3918b8259a4cf044e2b284f313db31dbc94f Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 21:26:34 +0000 Subject: [PATCH 028/235] Automatic changelog for PR #88590 [ci skip] --- html/changelogs/AutoChangeLog-pr-88590.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88590.yml diff --git a/html/changelogs/AutoChangeLog-pr-88590.yml b/html/changelogs/AutoChangeLog-pr-88590.yml new file mode 100644 index 0000000000000..114a95b290c91 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88590.yml @@ -0,0 +1,4 @@ +author: "SmArtKar" +delete-after: True +changes: + - bugfix: "Chemmaster no longer rounds reagent amounts when trying to transfer a custom amount" \ No newline at end of file From bdca5fc2a3867dcdfb504cc83d1d867314b1f619 Mon Sep 17 00:00:00 2001 From: SyncIt21 <110812394+SyncIt21@users.noreply.github.com> Date: Sun, 22 Dec 2024 02:58:31 +0530 Subject: [PATCH 029/235] General maintenance for mecha RCD (#88614) ## About The Pull Request **1. Code Improvements** - Converted `rcd_range` from a var into a define to save memory - Removed var `rcd_type` as it was redundant with the type of `internal_rcd` var - Moved attack chain code from `attackby()` to `interact_with_atom()` when installing disk upgrade **2. Fixes** Fixes https://github.com/tgstation/tgstation/pull/88589#issuecomment-2552405018, https://github.com/tgstation/tgstation/pull/88589#issuecomment-2552294674. i.e. the RCD cancels it's construction & deconstruction action if - The mech rotates away from where its building - The mech moves ## Changelog :cl: code: improved code for mecha rcd fix: mecha rcd will cancel its action when the mech is rotated or moves during the action /:cl: --- code/game/objects/items/rcd/RCD.dm | 12 +++++ code/game/objects/items/rcd/RHD.dm | 2 + .../mecha/equipment/tools/work_tools.dm | 49 ++++++++++++------- 3 files changed, 46 insertions(+), 17 deletions(-) diff --git a/code/game/objects/items/rcd/RCD.dm b/code/game/objects/items/rcd/RCD.dm index 60f84e14651b9..3dab53cb8ff27 100644 --- a/code/game/objects/items/rcd/RCD.dm +++ b/code/game/objects/items/rcd/RCD.dm @@ -565,6 +565,18 @@ return owner.ui_status(user) return UI_CLOSE +/obj/item/construction/rcd/exosuit/build_delay(mob/user, delay, atom/target) + if(delay <= 0) + return TRUE + + var/obj/item/mecha_parts/mecha_equipment/rcd/module = loc + + //deconstruction can't be cancelled by ui changes + if(mode != RCD_DECONSTRUCT) + blueprint_changed = FALSE + + return module.do_after_mecha(target, user, delay) + /obj/item/construction/rcd/exosuit/get_matter(mob/user) if(silo_link) return ..() diff --git a/code/game/objects/items/rcd/RHD.dm b/code/game/objects/items/rcd/RHD.dm index ce9f211b6942e..85cdc21947b6d 100644 --- a/code/game/objects/items/rcd/RHD.dm +++ b/code/game/objects/items/rcd/RHD.dm @@ -63,6 +63,8 @@ return do_after(user, delay, target, extra_checks = CALLBACK(src, PROC_REF(blueprint_change))) /obj/item/construction/proc/blueprint_change() + PRIVATE_PROC(TRUE) + return !blueprint_changed ///used for examining the RCD and for its UI diff --git a/code/modules/vehicles/mecha/equipment/tools/work_tools.dm b/code/modules/vehicles/mecha/equipment/tools/work_tools.dm index c30e67a274633..f080675af4673 100644 --- a/code/modules/vehicles/mecha/equipment/tools/work_tools.dm +++ b/code/modules/vehicles/mecha/equipment/tools/work_tools.dm @@ -217,31 +217,32 @@ attempt_refill(usr) return TRUE +///Maximum range the RCD can construct at. +#define RCD_RANGE 3 + /obj/item/mecha_parts/mecha_equipment/rcd name = "mounted RCD" desc = "An exosuit-mounted Rapid Construction Device." icon_state = "mecha_rcd" equip_cooldown = 0 // internal RCD already handles it energy_drain = 0 // internal RCD handles power consumption based on matter use - range = MECHA_MELEE|MECHA_RANGED + range = MECHA_MELEE | MECHA_RANGED item_flags = NO_MAT_REDEMPTION - ///Maximum range the RCD can construct at. - var/rcd_range = 3 + + ///The location the mech was when it began using the rcd + var/atom/initial_location = FALSE ///Whether or not to deconstruct instead. var/deconstruct_active = FALSE - ///The type of internal RCD this equipment uses. - var/rcd_type = /obj/item/construction/rcd/exosuit ///The internal RCD item used by this equipment. - var/obj/item/construction/rcd/internal_rcd + var/obj/item/construction/rcd/exosuit/internal_rcd /obj/item/mecha_parts/mecha_equipment/rcd/Initialize(mapload) . = ..() - internal_rcd = new rcd_type(src) - GLOB.rcd_list += src + internal_rcd = new(src) /obj/item/mecha_parts/mecha_equipment/rcd/Destroy() - GLOB.rcd_list -= src - qdel(internal_rcd) + initial_location = null + QDEL_NULL(internal_rcd) return ..() /obj/item/mecha_parts/mecha_equipment/rcd/get_snowflake_data() @@ -277,16 +278,28 @@ internal_rcd.ui_interact(driver) return TRUE + +/obj/item/mecha_parts/mecha_equipment/rcd/do_after_checks(atom/target) + // Checks if mech moved during operation + if(chassis.loc != initial_location) + return FALSE + + // Cancel build if design changes + if(!deconstruct_active && internal_rcd.blueprint_changed) + return FALSE + + return ..() + /obj/item/mecha_parts/mecha_equipment/rcd/action(mob/source, atom/target, list/modifiers) if(!action_checks(target)) return - if(get_dist(chassis, target) > rcd_range) + if(get_dist(chassis, target) > RCD_RANGE) balloon_alert(source, "out of range!") return - if(!internal_rcd) // if it somehow went missing - internal_rcd = new rcd_type(src) - stack_trace("Exosuit-mounted RCD had no internal RCD!") + initial_location = chassis.loc + ..() // do this now because the do_after can take a while + var/construction_mode = internal_rcd.mode if(deconstruct_active) // deconstruct isn't in the RCD menu so switch it to deconstruct mode and set it back when it's done internal_rcd.mode = RCD_DECONSTRUCT @@ -294,11 +307,13 @@ internal_rcd.mode = construction_mode return TRUE -/obj/item/mecha_parts/mecha_equipment/rcd/attackby(obj/item/attacking_item, mob/user, params) +/obj/item/mecha_parts/mecha_equipment/rcd/interact_with_atom(obj/item/attacking_item, mob/living/user, list/modifiers) + . = NONE if(istype(attacking_item, /obj/item/rcd_upgrade)) internal_rcd.install_upgrade(attacking_item, user) - return - return ..() + return ITEM_INTERACT_SUCCESS + +#undef RCD_RANGE //Dunno where else to put this so shrug /obj/item/mecha_parts/mecha_equipment/ripleyupgrade From 529674bfabd176257e0de4dbafaeb23043e03cb2 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 21:28:50 +0000 Subject: [PATCH 030/235] Automatic changelog for PR #88614 [ci skip] --- html/changelogs/AutoChangeLog-pr-88614.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88614.yml diff --git a/html/changelogs/AutoChangeLog-pr-88614.yml b/html/changelogs/AutoChangeLog-pr-88614.yml new file mode 100644 index 0000000000000..fb92e384cb2da --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88614.yml @@ -0,0 +1,5 @@ +author: "SyncIt21" +delete-after: True +changes: + - code_imp: "improved code for mecha rcd" + - bugfix: "mecha rcd will cancel its action when the mech is rotated or moves during the action" \ No newline at end of file From d69bb900c6de9c0d03880bc54a5e020fe77dd8cc Mon Sep 17 00:00:00 2001 From: Kocma-san <112967882+Kocma-san@users.noreply.github.com> Date: Sun, 22 Dec 2024 04:29:13 +0700 Subject: [PATCH 031/235] enhancements for fax requests in request manager (#88391) ## About The Pull Request Addition to [#84910](https://github.com/tgstation/tgstation/pull/84910) Added a "print" button to the request manager. It simply prints at the centcom station the paper that was faxed to the admins. Also added "Auto-print Faxes" button to the request manager, which allows you to enable automatic printing of requests on the admin fax
Screenshots ![image](https://github.com/user-attachments/assets/35cbda0a-c759-4e9e-8899-8e2d81069b4c) ![image](https://github.com/user-attachments/assets/bbb918da-8b84-4a6b-a42e-c06359ab5651) ![image](https://github.com/user-attachments/assets/b380cc13-a9e2-496e-a296-60fb827c4c55)
## Why It's Good For The Game The message in the chat may get lost. In this case, it will no longer be possible to print the paper sent to the CC Changing the allow_exotic_faxes variable will allow you to fax something unusual to the station in (very rare) situations. No need to manually change this variable ## Changelog :cl: add: added a "print" button to the request manager add: admin fax can now send exotic items add: added "Auto-print Faxes" button to the request manager, which allows you to enable automatic printing of requests on the admin fax fix: fixed a bug where pressing the print button would cause the receive animation to appear on all admin faxes. /:cl: --- code/modules/admin/topic.dm | 2 +- code/modules/paperwork/fax.dm | 11 ++++++++++- code/modules/requests/request_manager.dm | 18 +++++++++++++++++- .../tgui/interfaces/RequestManager.tsx | 18 +++++++++++++++++- 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index 6a2666eef9bed..f517a7a7a51cb 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1731,7 +1731,7 @@ if(FAX.fax_id != href_list["destination"]) continue FAX.receive(locate(href_list["print_fax"]), href_list["sender_name"]) - + return else if(href_list["play_internet"]) if(!check_rights(R_SOUND)) diff --git a/code/modules/paperwork/fax.dm b/code/modules/paperwork/fax.dm index f2935853a6db5..62a269d477e46 100644 --- a/code/modules/paperwork/fax.dm +++ b/code/modules/paperwork/fax.dm @@ -1,4 +1,5 @@ GLOBAL_VAR_INIT(nt_fax_department, pick("NT HR Department", "NT Legal Department", "NT Complaint Department", "NT Customer Relations", "Nanotrasen Tech Support", "NT Internal Affairs Dept")) +GLOBAL_VAR_INIT(fax_autoprinting, FALSE) /obj/machinery/fax name = "Fax Machine" @@ -325,7 +326,7 @@ GLOBAL_VAR_INIT(nt_fax_department, pick("NT HR Department", "NT Legal Department history_add("Send", params["name"]) - GLOB.requests.fax_request(usr.client, "sent a fax message from [fax_name]/[fax_id] to [params["name"]]", fax_paper) + GLOB.requests.fax_request(usr.client, "sent a fax message from [fax_name]/[fax_id] to [params["name"]]", list("paper" = fax_paper, "destination_id" = params["id"], "sender_name" = fax_name)) to_chat(GLOB.admins, span_adminnotice("[icon2html(src.icon, GLOB.admins)]FAX REQUEST: [ADMIN_FULLMONTY(usr)]: [span_linkify("sent a fax message from [fax_name]/[fax_id][ADMIN_FLW(src)] to [html_encode(params["name"])]")] [ADMIN_SHOW_PAPER(fax_paper)] [ADMIN_PRINT_FAX(fax_paper, fax_name, params["id"])]"), type = MESSAGE_TYPE_PRAYER, @@ -333,6 +334,14 @@ GLOBAL_VAR_INIT(nt_fax_department, pick("NT HR Department", "NT Legal Department for(var/client/staff as anything in GLOB.admins) if(staff?.prefs.read_preference(/datum/preference/toggle/comms_notification)) SEND_SOUND(staff, sound('sound/misc/server-ready.ogg')) + + if(GLOB.fax_autoprinting) + for(var/obj/machinery/fax/admin/FAX as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/fax/admin)) + if(FAX.fax_id != params["id"]) + continue + FAX.receive(fax_paper, fax_name) + break + log_fax(fax_paper, params["id"], params["name"]) loaded_item_ref = null update_appearance() diff --git a/code/modules/requests/request_manager.dm b/code/modules/requests/request_manager.dm index 99a9bba1cc84f..3106a925acd2c 100644 --- a/code/modules/requests/request_manager.dm +++ b/code/modules/requests/request_manager.dm @@ -154,6 +154,10 @@ GLOBAL_DATUM_INIT(requests, /datum/request_manager, new) to_chat(usr, "You do not have permission to do this, you require +ADMIN", confidential = TRUE) return + if (action == "toggleprint") + GLOB.fax_autoprinting = !GLOB.fax_autoprinting + return TRUE + // Get the request this relates to var/id = params["id"] != null ? text2num(params["id"]) : null if (!id) @@ -228,9 +232,20 @@ GLOBAL_DATUM_INIT(requests, /datum/request_manager, new) if(request.req_type != REQUEST_FAX) to_chat(usr, "Request doesn't have a paper to read.", confidential = TRUE) return TRUE - var/obj/item/paper/request_message = request.additional_information + var/obj/item/paper/request_message = request.additional_information["paper"] request_message.ui_interact(usr) return TRUE + if ("print") + if (request.req_type != REQUEST_FAX) + to_chat(usr, "Request doesn't have a paper to print.", confidential = TRUE) + return TRUE + for(var/obj/machinery/fax/admin/FAX as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/fax/admin)) + if(FAX.fax_id != request.additional_information["destination_id"]) + continue + var/obj/item/paper/request_message = request.additional_information["paper"] + var/sender_name = request.additional_information["sender_name"] + FAX.receive(request_message, sender_name) + return TRUE if ("play") if(request.req_type != REQUEST_INTERNET_SOUND) to_chat(usr, "Request doesn't have a sound to play.", confidential = TRUE) @@ -257,6 +272,7 @@ GLOBAL_DATUM_INIT(requests, /datum/request_manager, new) "timestamp" = request.timestamp, "timestamp_str" = gameTimestamp(wtime = request.timestamp) )) + data["fax_autoprinting"] = GLOB.fax_autoprinting return data #undef REQUEST_PRAYER diff --git a/tgui/packages/tgui/interfaces/RequestManager.tsx b/tgui/packages/tgui/interfaces/RequestManager.tsx index 9a009106788b7..5bc63512f3fbb 100644 --- a/tgui/packages/tgui/interfaces/RequestManager.tsx +++ b/tgui/packages/tgui/interfaces/RequestManager.tsx @@ -3,6 +3,7 @@ * @copyright 2021 bobbahbrown (https://github.com/bobbahbrown) * @license MIT */ +import { BooleanLike } from 'common/react'; import { createSearch, decodeHtmlEntities } from 'common/string'; import { useState } from 'react'; @@ -12,6 +13,7 @@ import { Window } from '../layouts'; type Data = { requests: Request[]; + fax_autoprinting: BooleanLike; }; type Request = { @@ -72,6 +74,15 @@ export const RequestManager = (props) => { buttons={ + act('toggleprint')} + tooltip={ + 'Enables automatic printing of fax requests to the admin fax machine. By default, this fax is located in the briefing room at the central command station' + } + > + Auto-print Faxes + setSearchText(value)} @@ -146,7 +157,12 @@ const RequestControls = (props) => { )} {request.req_type === 'request_fax' && ( - + <> + + + )} {request.req_type === 'request_internet_sound' && ( From 3652c6ead935051500d54ef32659c89f1fd7933d Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 21:30:01 +0000 Subject: [PATCH 032/235] Automatic changelog for PR #88391 [ci skip] --- html/changelogs/AutoChangeLog-pr-88391.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88391.yml diff --git a/html/changelogs/AutoChangeLog-pr-88391.yml b/html/changelogs/AutoChangeLog-pr-88391.yml new file mode 100644 index 0000000000000..12bfe4a8a99b2 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88391.yml @@ -0,0 +1,7 @@ +author: "Kocma-san" +delete-after: True +changes: + - rscadd: "added a \"print\" button to the request manager" + - rscadd: "admin fax can now send exotic items" + - rscadd: "added \"Auto-print Faxes\" button to the request manager, which allows you to enable automatic printing of requests on the admin fax" + - bugfix: "fixed a bug where pressing the print button would cause the receive animation to appear on all admin faxes." \ No newline at end of file From cdda52aba97fd5b18eb91d6e9ce70b4c2d5fdf29 Mon Sep 17 00:00:00 2001 From: Gaxeer <44334376+Gaxeer@users.noreply.github.com> Date: Sat, 21 Dec 2024 23:32:57 +0200 Subject: [PATCH 033/235] add laptop interact on RMB click, screentips (#88612) ## About The Pull Request Modular computer laptops can now be interacted with RMB when open, or opened with RMB when closed. Also screentips for this added. It was inteded for laptops to be interactable when open. but didn't work because old code was not adjusted for attack chains. ## Why It's Good For The Game Laptop usage is more user friendly :D
Demonstration :D ![image](https://github.com/user-attachments/assets/59b125af-2c2a-4948-a0ba-1289005a03fa)
## Changelog :cl: fix: modular computer laptops can now be interacted with RMB, instead of picked up qol: modular computer laptops can now be interacted with RMB when open, or opened with RMB when closed. Also screentips for this added /:cl: --------- Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com> --- .../computers/item/laptop.dm | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/code/modules/modular_computers/computers/item/laptop.dm b/code/modules/modular_computers/computers/item/laptop.dm index 5053b6c6b2cbe..343a09a980d30 100644 --- a/code/modules/modular_computers/computers/item/laptop.dm +++ b/code/modules/modular_computers/computers/item/laptop.dm @@ -29,6 +29,16 @@ if(screen_on) . += span_notice("Alt-click to close it.") +/obj/item/modular_computer/laptop/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) + . = ..() + if(screen_on) + context[SCREENTIP_CONTEXT_ALT_LMB] = "Close" + context[SCREENTIP_CONTEXT_RMB] = "Interact" + else + context[SCREENTIP_CONTEXT_RMB] = "Open" + + return CONTEXTUAL_SCREENTIP_SET + /obj/item/modular_computer/laptop/Initialize(mapload) . = ..() @@ -70,13 +80,6 @@ return user.put_in_hand(src, H.held_index) -/obj/item/modular_computer/laptop/attack_hand(mob/user, list/modifiers) - . = ..() - if(.) - return - if(screen_on && isturf(loc)) - return attack_self(user) - /obj/item/modular_computer/laptop/proc/try_toggle_open(mob/living/user) if(issilicon(user)) return @@ -94,6 +97,14 @@ try_toggle_open(user) // Close it. return CLICK_ACTION_SUCCESS +/obj/item/modular_computer/laptop/attack_hand_secondary(mob/user, list/modifiers) + . = ..() + if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN) + return + + attack_self(user) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + /obj/item/modular_computer/laptop/proc/toggle_open(mob/living/user=null) if(screen_on) to_chat(user, span_notice("You close \the [src].")) From 6109e8e8e5464eb3fb6aa38ee97e013fc59c7fcf Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Sun, 22 Dec 2024 00:34:17 +0300 Subject: [PATCH 034/235] [NO GBP] Fixes floodlights not being affected by spraycan painting (#88531) ## About The Pull Request Missed that one ## Changelog :cl: fix: Fixed floodlights not being affected by spraycan painting /:cl: --- code/modules/power/floodlight.dm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/modules/power/floodlight.dm b/code/modules/power/floodlight.dm index 5b9d983cf1dd6..7db89987d2d44 100644 --- a/code/modules/power/floodlight.dm +++ b/code/modules/power/floodlight.dm @@ -173,6 +173,8 @@ var/light_color = NONSENSICAL_VALUE if(!isnull(color)) light_color = color + if (cached_color_filter) + light_color = apply_matrix_to_color(COLOR_WHITE, cached_color_filter["color"], cached_color_filter["space"] || COLORSPACE_RGB) set_light(light_setting_list[setting], light_power, light_color) /obj/machinery/power/floodlight/add_context( From f1696412d645f9ac328a132eac7edc9253198ae5 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 21:34:23 +0000 Subject: [PATCH 035/235] Automatic changelog for PR #88612 [ci skip] --- html/changelogs/AutoChangeLog-pr-88612.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88612.yml diff --git a/html/changelogs/AutoChangeLog-pr-88612.yml b/html/changelogs/AutoChangeLog-pr-88612.yml new file mode 100644 index 0000000000000..208928a92f3cb --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88612.yml @@ -0,0 +1,5 @@ +author: "Gaxeer" +delete-after: True +changes: + - bugfix: "modular computer laptops can now be interacted with RMB, instead of picked up" + - qol: "modular computer laptops can now be interacted with RMB when open, or opened with RMB when closed. Also screentips for this added" \ No newline at end of file From eefd4931bce93063648ed88d7b456f33aee2e3d3 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 21:38:21 +0000 Subject: [PATCH 036/235] Automatic changelog for PR #88531 [ci skip] --- html/changelogs/AutoChangeLog-pr-88531.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88531.yml diff --git a/html/changelogs/AutoChangeLog-pr-88531.yml b/html/changelogs/AutoChangeLog-pr-88531.yml new file mode 100644 index 0000000000000..7fbd94c9aed14 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88531.yml @@ -0,0 +1,4 @@ +author: "SmArtKar" +delete-after: True +changes: + - bugfix: "Fixed floodlights not being affected by spraycan painting" \ No newline at end of file From dbdcd69c1fd6994d89b150cf91c94f3dc96349b8 Mon Sep 17 00:00:00 2001 From: mcbalaam <104003807+mcbalaam@users.noreply.github.com> Date: Sun, 22 Dec 2024 05:09:20 +0700 Subject: [PATCH 037/235] Fixes the mail sorter runtiming processing assistant mail (#88562) ## About The Pull Request The mail sorter doesn't runtime when processing assistant mail now. Also now it properly ejects envelopes upon deconstruction. ## Why It's Good For The Game Because that caused the mail sorter to freeze, not even being able to get the mail back. Closes https://github.com/tgstation/tgstation/issues/88549. ## Changelog :cl: fix: The mail sorter no longer runtimes processing assistant mail /:cl: --- code/modules/vending/mail.dm | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/code/modules/vending/mail.dm b/code/modules/vending/mail.dm index 1e091a3128756..6bc3648b056d1 100644 --- a/code/modules/vending/mail.dm +++ b/code/modules/vending/mail.dm @@ -46,11 +46,13 @@ /obj/machinery/mailsorter/proc/get_unload_turf() return get_step(src, output_dir) +/// Opening the maintenance panel. /obj/machinery/mailsorter/screwdriver_act(mob/living/user, obj/item/tool) default_deconstruction_screwdriver(user, "[base_icon_state]-off", base_icon_state, tool) update_appearance(UPDATE_OVERLAYS) return ITEM_INTERACT_SUCCESS +/// Deconstructing the mail sorter. /obj/machinery/mailsorter/crowbar_act(mob/living/user, obj/item/tool) default_deconstruction_crowbar(tool) return ITEM_INTERACT_SUCCESS @@ -63,11 +65,15 @@ . += span_notice("Alt-click to rotate the output direction.") /obj/machinery/mailsorter/Destroy() + QDEL_LIST(mail_list) + . = ..() + +/obj/machinery/mailsorter/on_deconstruction(disassembled) drop_all_mail() . = ..() -/// Drops all enevlopes on the machine turf. Only occurs when the machine is broken. -/obj/machinery/mailsorter/proc/drop_all_mail(damage_flag) +/// Drops all enevlopes on the machine turf. +/obj/machinery/mailsorter/proc/drop_all_mail() if(!isturf(get_turf(src))) QDEL_LIST(mail_list) return @@ -90,10 +96,6 @@ /obj/machinery/mailsorter/proc/accept_check(obj/item/weapon) var/static/list/accepted_items = list( /obj/item/mail, - /obj/item/mail/envelope, - /obj/item/mail/junkmail, - /obj/item/mail/mail_strike, - /obj/item/mail/traitor, /obj/item/paper, ) return is_type_in_list(weapon, accepted_items) @@ -151,10 +153,13 @@ if (some_recipient) var/datum/job/recipient_job = some_recipient.assigned_role var/datum/job_department/primary_department = recipient_job.departments_list?[1] - var/datum/job_department/main_department = primary_department.department_name - if (main_department == sorting_dept) - sorted_mail.Add(some_mail) - sorted ++ + if (primary_department == null) // permabrig is temporary, tide is forever + unable_to_sort ++ + else + var/datum/job_department/main_department = primary_department.department_name + if (main_department == sorting_dept) + sorted_mail.Add(some_mail) + sorted ++ else unable_to_sort ++ if (length(sorted_mail) == 0) From 854b08a482d985af24ba6f36b2dba97da40a67db Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 22:09:57 +0000 Subject: [PATCH 038/235] Automatic changelog for PR #88562 [ci skip] --- html/changelogs/AutoChangeLog-pr-88562.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88562.yml diff --git a/html/changelogs/AutoChangeLog-pr-88562.yml b/html/changelogs/AutoChangeLog-pr-88562.yml new file mode 100644 index 0000000000000..9405b08a5a2d2 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88562.yml @@ -0,0 +1,4 @@ +author: "mcbalaam" +delete-after: True +changes: + - bugfix: "The mail sorter no longer runtimes processing assistant mail" \ No newline at end of file From 3a25ac7f941ede1667aec311f37ce184a8c78237 Mon Sep 17 00:00:00 2001 From: Cruix Date: Sat, 21 Dec 2024 14:10:15 -0800 Subject: [PATCH 039/235] Nav computer icons (#88169) ## About The Pull Request To add context when placing a custom shuttle location, added additional icons to the shuttle navigation computer view for a few important objects: * The shuttle computer * The shuttle navigation computer * Turrets * The firing trajectory of the Yamato cannon * External airlocks
Images Old: ![oldSyndiShuttle](https://github.com/user-attachments/assets/2cd5fd5c-bdb3-4dc9-9e1f-f48157c839be) New: ![newSyndiShuttle](https://github.com/user-attachments/assets/c8a2d82d-1d5c-41f6-b37a-c54d536ec051)
## Why It's Good For The Game It's much easier to place the syndicate shuttle / white ship in a convenient location when you know exactly where the turrets and external airlocks are going to be when you land. Showing the shuttle computers also helps to communicate the orientation of the ship to anyone who is not already familiar with the outline, since the computers are usually placed right at the front, where the pilot is sitting. ## Changelog :cl: add: Shuttle navigation computers now show the location of airlocks, turrets, and the shuttle control consoles on the ship outline while placing a custom landing location. /:cl: --- code/__DEFINES/dcs/signals/signals_object.dm | 3 + code/datums/elements/nav_computer_icon.dm | 44 ++++++++++++++ code/game/machinery/doors/airlock.dm | 2 + code/game/machinery/doors/poddoor.dm | 6 ++ code/game/machinery/doors/shutters.dm | 1 + .../machinery/porta_turret/portable_turret.dm | 1 + .../shuttle_consoles/navigation_computer.dm | 57 +++++++++++++++++- .../shuttle_consoles/shuttle_console.dm | 1 + icons/effects/nav_computer_indicators.dmi | Bin 0 -> 1872 bytes tgstation.dme | 1 + 10 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 code/datums/elements/nav_computer_icon.dm create mode 100644 icons/effects/nav_computer_indicators.dmi diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index 63ebfdf98b21f..a83badb9ee067 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -547,6 +547,9 @@ ///Sent from /obj/item/skillchip/on_remove() #define COMSIG_SKILLCHIP_REMOVED "skillchip_removed" +/// from /obj/machinery/computer/camera_advanced/shuttle_docker/gatherNavComputerOverlays() : (list/images_out) +#define COMSIG_SHUTTLE_NAV_COMPUTER_IMAGE_REQUESTED "shuttle_nav_computer_image_requested" + /// Sent from /obj/item/organ/wings/functional/proc/open_wings(): (mob/living/carbon/owner) #define COMSIG_WINGS_OPENED "wings_opened" /// Sent from /obj/item/organ/wings/functional/proc/close_wings(): (mob/living/carbon/owner) diff --git a/code/datums/elements/nav_computer_icon.dm b/code/datums/elements/nav_computer_icon.dm new file mode 100644 index 0000000000000..4e9b6a3a18893 --- /dev/null +++ b/code/datums/elements/nav_computer_icon.dm @@ -0,0 +1,44 @@ +/** + * element for atoms that have helper icons overlayed on their position in the shuttle navigation computer, such as airlocks + */ +/datum/element/nav_computer_icon + element_flags = ELEMENT_BESPOKE + argument_hash_start_idx = 2 + var/use_icon + var/use_icon_state + var/only_show_on_shuttle_edge + +/datum/element/nav_computer_icon/Attach(datum/target, use_icon, use_icon_state, only_show_on_shuttle_edge) + . = ..() + if(!isatom(target)) + return ELEMENT_INCOMPATIBLE + + src.use_icon = use_icon + src.use_icon_state = use_icon_state + src.only_show_on_shuttle_edge = only_show_on_shuttle_edge + + RegisterSignal(target, COMSIG_SHUTTLE_NAV_COMPUTER_IMAGE_REQUESTED, PROC_REF(provide_image)) + +/datum/element/nav_computer_icon/proc/provide_image(datum/source, list/images_out) + SIGNAL_HANDLER + var/obj/source_obj = source + var/turf/source_turf = get_turf(source_obj) + if(!source_turf) + return + if(only_show_on_shuttle_edge) + var/isOnEdge = FALSE + for(var/direction in GLOB.cardinals) + var/turf/turf = get_step(source_obj, direction) + if(!istype(turf?.loc, /area/shuttle)) + isOnEdge = TRUE + break + if(!isOnEdge) + return + + var/image/the_image = image(use_icon, source_turf, use_icon_state) + the_image.dir = source_obj.dir + images_out += the_image + +/datum/element/nav_computer_icon/Detach(datum/source) + . = ..() + UnregisterSignal(source, COMSIG_SHUTTLE_NAV_COMPUTER_IMAGE_REQUESTED) diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index 1be4325638d37..c3657c6cac391 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -181,6 +181,8 @@ // Click on the floor to close airlocks AddComponent(/datum/component/redirect_attack_hand_from_turf) + AddElement(/datum/element/nav_computer_icon, 'icons/effects/nav_computer_indicators.dmi', "airlock", TRUE) + RegisterSignal(src, COMSIG_MACHINERY_BROKEN, PROC_REF(on_break)) RegisterSignal(SSdcs, COMSIG_GLOB_GREY_TIDE, PROC_REF(grey_tide)) diff --git a/code/game/machinery/doors/poddoor.dm b/code/game/machinery/doors/poddoor.dm index cb33ed6c14f5a..b7ae0c9876a83 100644 --- a/code/game/machinery/doors/poddoor.dm +++ b/code/game/machinery/doors/poddoor.dm @@ -22,6 +22,12 @@ var/id = 1 /// The sound that plays when the door opens/closes var/animation_sound = 'sound/machines/blastdoor.ogg' + var/show_nav_computer_icon = TRUE + +/obj/machinery/door/poddoor/Initialize(mapload) + . = ..() + if(show_nav_computer_icon) + AddElement(/datum/element/nav_computer_icon, 'icons/effects/nav_computer_indicators.dmi', "airlock", TRUE) /datum/armor/door_poddoor melee = 50 diff --git a/code/game/machinery/doors/shutters.dm b/code/game/machinery/doors/shutters.dm index 56e2f5a9743b2..52c12835c2797 100644 --- a/code/game/machinery/doors/shutters.dm +++ b/code/game/machinery/doors/shutters.dm @@ -10,6 +10,7 @@ max_integrity = 100 recipe_type = /datum/crafting_recipe/shutters animation_sound = 'sound/machines/shutter.ogg' + show_nav_computer_icon = FALSE /obj/machinery/door/poddoor/shutters/animation_length(animation) switch(animation) diff --git a/code/game/machinery/porta_turret/portable_turret.dm b/code/game/machinery/porta_turret/portable_turret.dm index d1f785f9b53d5..796ee58984565 100644 --- a/code/game/machinery/porta_turret/portable_turret.dm +++ b/code/game/machinery/porta_turret/portable_turret.dm @@ -762,6 +762,7 @@ DEFINE_BITFIELD(turret_flags, list( /obj/machinery/porta_turret/syndicate/Initialize(mapload) . = ..() AddElement(/datum/element/empprotection, EMP_PROTECT_SELF | EMP_PROTECT_WIRES) + AddElement(/datum/element/nav_computer_icon, 'icons/effects/nav_computer_indicators.dmi', "turret", FALSE) /obj/machinery/porta_turret/syndicate/setup() return diff --git a/code/modules/shuttle/shuttle_consoles/navigation_computer.dm b/code/modules/shuttle/shuttle_consoles/navigation_computer.dm index 7c588e06dc005..3e9bf0cbe3be0 100644 --- a/code/modules/shuttle/shuttle_consoles/navigation_computer.dm +++ b/code/modules/shuttle/shuttle_consoles/navigation_computer.dm @@ -34,7 +34,7 @@ . = ..() actions += new /datum/action/innate/shuttledocker_rotate(src) actions += new /datum/action/innate/shuttledocker_place(src) - + AddElement(/datum/element/nav_computer_icon, 'icons/effects/nav_computer_indicators.dmi', "computer", FALSE) set_init_ports() if(connect_to_shuttle(mapload, SSshuttle.get_containing_shuttle(src))) @@ -124,6 +124,7 @@ SET_PLANE(I, ABOVE_GAME_PLANE, shuttle_turf) I.mouse_opacity = MOUSE_OPACITY_TRANSPARENT the_eye.placement_images[I] = list(x_off, y_off) + gatherNavComputerIcons() return TRUE @@ -134,6 +135,7 @@ var/list/to_add = list() to_add += the_eye.placement_images to_add += the_eye.placed_images + to_add += the_eye.extra_images if(!see_hidden) to_add += SSshuttle.hidden_shuttle_turf_images @@ -147,12 +149,55 @@ var/list/to_remove = list() to_remove += the_eye.placement_images to_remove += the_eye.placed_images + to_remove += the_eye.extra_images if(!see_hidden) to_remove += SSshuttle.hidden_shuttle_turf_images user.client.images -= to_remove user.client.view_size.resetToDefault() +/obj/machinery/computer/camera_advanced/shuttle_docker/proc/shuttle_turf_from_coords(list/coords) + var/mob/eye/camera/remote/shuttle_docker/the_eye = eyeobj + var/shuttleDir = shuttle_port.dir + var/curDir = the_eye.dir + var/list/adjustedCoords = coords.Copy() + + // Rotate coords so they match the current shuttle docking port's dir + if(turn(curDir, -90) == shuttleDir) + adjustedCoords[1] = coords[2] + y_offset + adjustedCoords[2] = -(coords[1] + x_offset) + else if(turn(curDir, 90) == shuttleDir) + adjustedCoords[1] = -(coords[2] + y_offset) + adjustedCoords[2] = coords[1] + x_offset + else if(turn(curDir, 180) == shuttleDir) + adjustedCoords[1] = -(coords[1] + x_offset) + adjustedCoords[2] = -(coords[2] + y_offset) + else + adjustedCoords[1] = coords[1] + x_offset + adjustedCoords[2] = coords[2] + y_offset + + return locate(shuttle_port.x + adjustedCoords[1], shuttle_port.y + adjustedCoords[2], shuttle_port.z) + +/obj/machinery/computer/camera_advanced/shuttle_docker/proc/gatherNavComputerIcons() + var/mob/eye/camera/remote/shuttle_docker/the_eye = eyeobj + var/list/placement_image_cache = the_eye.placement_images + var/list/extra_image_cache = the_eye.extra_images + for(var/i in 1 to placement_image_cache.len) + var/image/placement_image = placement_image_cache[i] + var/list/coords = placement_image_cache[placement_image] + var/turf/shuttle_turf = shuttle_turf_from_coords(coords) + var/list/images_to_add = list() + for(var/atom/atom as anything in shuttle_turf) + SEND_SIGNAL(atom, COMSIG_SHUTTLE_NAV_COMPUTER_IMAGE_REQUESTED, images_to_add) + for(var/i2 in 1 to images_to_add.len) + var/image/extra_image = images_to_add[i2] + extra_image.dir = turn(extra_image.dir, dir2angle(the_eye.dir) - dir2angle(shuttle_port.dir)) + extra_image.loc = locate(the_eye.x + coords[1], the_eye.y + coords[2], the_eye.z) + extra_image.layer = ABOVE_NORMAL_TURF_LAYER + SET_PLANE(extra_image, ABOVE_GAME_PLANE, the_eye) + extra_image.mouse_opacity = MOUSE_OPACITY_TRANSPARENT + extra_image_cache[extra_image] = coords.Copy() + /obj/machinery/computer/camera_advanced/shuttle_docker/proc/placeLandingSpot() if(designating_target_loc || !current_user) return @@ -226,7 +271,7 @@ /obj/machinery/computer/camera_advanced/shuttle_docker/proc/rotateLandingSpot() var/mob/eye/camera/remote/shuttle_docker/the_eye = eyeobj - var/list/image_cache = the_eye.placement_images + var/list/image_cache = the_eye.placement_images + the_eye.extra_images the_eye.setDir(turn(the_eye.dir, -90)) for(var/i in 1 to image_cache.len) var/image/pic = image_cache[i] @@ -235,6 +280,7 @@ coords[1] = coords[2] coords[2] = -Tmp pic.loc = locate(the_eye.x + coords[1], the_eye.y + coords[2], the_eye.z) + pic.dir = turn(pic.dir, -90) var/Tmp = x_offset x_offset = y_offset y_offset = -Tmp @@ -267,6 +313,12 @@ else I.icon_state = "red" . = SHUTTLE_DOCKER_BLOCKED + var/list/extra_image_cache = the_eye.extra_images + for(var/i in 1 to extra_image_cache.len) + var/image/image = extra_image_cache[i] + var/list/coords = extra_image_cache[image] + var/turf/turf = locate(eyeturf.x + coords[1], eyeturf.y + coords[2], eyeturf.z) + image.loc = turf /obj/machinery/computer/camera_advanced/shuttle_docker/proc/checkLandingTurf(turf/T, list/overlappers) // Too close to the map edge is never allowed @@ -322,6 +374,7 @@ use_visibility = FALSE var/list/image/placement_images = list() var/list/image/placed_images = list() + var/list/image/extra_images = list() /mob/eye/camera/remote/shuttle_docker/setLoc(turf/destination, force_update = FALSE) . = ..() diff --git a/code/modules/shuttle/shuttle_consoles/shuttle_console.dm b/code/modules/shuttle/shuttle_consoles/shuttle_console.dm index 5ce62f8c04226..df1922ae9daab 100644 --- a/code/modules/shuttle/shuttle_consoles/shuttle_console.dm +++ b/code/modules/shuttle/shuttle_consoles/shuttle_console.dm @@ -33,6 +33,7 @@ /obj/machinery/computer/shuttle/Initialize(mapload) . = ..() + AddElement(/datum/element/nav_computer_icon, 'icons/effects/nav_computer_indicators.dmi', "computer", FALSE) connect_to_shuttle(mapload, SSshuttle.get_containing_shuttle(src)) /obj/machinery/computer/shuttle/ui_interact(mob/user, datum/tgui/ui) diff --git a/icons/effects/nav_computer_indicators.dmi b/icons/effects/nav_computer_indicators.dmi new file mode 100644 index 0000000000000000000000000000000000000000..ff6a616c2968ccf90f048132672f477fddf057a1 GIT binary patch literal 1872 zcmV-W2e0^vP)V=-0C=2j%DoE0FcgO2Ip-=q+FktV;F3jBBtv@-p*@BUk~e!!yHY{|USs1X*2J(95mSgmq=d|7^(9b5@oWHb1aN+dH_<(+B)lGD`_11-bwL27yULK~#90?VZ_i>pBdE zk<95O1*8D!;Xa)nJn7&RlMXWOADPoNO;A$m;xh+TKajwzS$-P!nCvOZOFh0>^uFv%r zU>Ak>ubQqh>;Z(vpSitF|ExR#V6zcH$SZ^e@J`>uk8;WenF0Lsw0KK@s#Cw3w@$|)ruPU?T1G9+V_DX z_`3kD!C>g7_!WTr$xZbsm(&5Wu3b0B~dB3Zd?awSk2^lV2B#8$aF5(_ zPn=U{G7$*wJBtV`Vj#r=PzFe-8z?k>bbWz#h}uAu!~?oS2fzkr0Xr4PdgCo%qvS_Dkv zv%H<}nqU{Oev_pl_c)pHXT%~9K4-58LQ}+A7lO`Y{2BH=X~!IEZ_O#E$)#W>CIi9L z9?!VGwFrW@ltMVV6L$Fx~Qa3#=a8mLmgWa-s1WsEzmvTB>BY+{xlRqs;(i5G%mmx4_; zfUI*1E!*ZPn*!w@!K%B;g&-G$V`4UC9o?&8KONQQJNJE|gwMSC&7f7iYl@x*;+Z87 zQTA={HSA~7#C9;F9oa>Yv+k7J;mrhu1lo~R1O@9PG9Gg@6%ytnCM2qb`quT5VMyF5 z1Td51aOToqL*o%azKEdONokAMe9gtO%+Z1p00=5tsFYD_#E) zfwqatTEiX#l+JtwUam2oi!FedBU6E#@jiUbFUI9+5r}315J4Usm;XF_fPqQ`;z6J* zf-|m+H4aMz?wBEF9iaLzMQjbDS;#f?a_dnEz=@272#gu;!+$yJQ?CQKv3h$VK=;rF z3l9+SY2LUQc!oAu2*7xh4-quLW(>d;fh2cb+c=PfOK=u2i@;|*P42q9ogfFHS%50% zoKp^{1Me*0V3Z$~yN=mGT^xDd;5tQSUjOp$6QF1@Nbp55s84z-q^PY1* zelL)sbp55s6%$zG5M{7S{9b?%7{6jt3joHi9F_pI=i)f;1(hNS2_wD;VEjf9hlEjB z1Temm$IM{w`R<-VVuAGuAyLLJJr6Hk|EdkX=WB9k*W8BK6G9d_>O}FM{KWI|U<`dF z_0vHcq$1dO4z~>!5kZT;Y0F!Y?V!Zk>SwG-&fNj~t80!#S50OR_$Aa8IU)&(An0)* zfQIpO7YpwN0d#=tF{FD85J2ac>;}f3b0C1ufdD!O0_YqFpmUxA!2W)pzyov+1kgDUK<7XJ zos&uczU0NIZ@2>Xq!55r4m-%eGbHRFEm@k24&yh(A}G{zc%md1{qQ=t|N1!03lJQi zddYdzDzJl_7~%$sY;w`(Pc}Z`?GY4!r(4+lA7~G8Y8Z@uF8&8BqHO2rrbILV0000< KMNUMnLSTXq@M?_! literal 0 HcmV?d00001 diff --git a/tgstation.dme b/tgstation.dme index 9bc4e18b10421..345b15060c4f7 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1536,6 +1536,7 @@ #include "code\datums\elements\movement_turf_changer.dm" #include "code\datums\elements\movetype_handler.dm" #include "code\datums\elements\muffles_speech.dm" +#include "code\datums\elements\nav_computer_icon.dm" #include "code\datums\elements\nerfed_pulling.dm" #include "code\datums\elements\no_crit_hitting.dm" #include "code\datums\elements\noisy_movement.dm" From a77ec6e68db22687c225a3dbc2dc7b4af94b75f2 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 22:10:34 +0000 Subject: [PATCH 040/235] Automatic changelog for PR #88169 [ci skip] --- html/changelogs/AutoChangeLog-pr-88169.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88169.yml diff --git a/html/changelogs/AutoChangeLog-pr-88169.yml b/html/changelogs/AutoChangeLog-pr-88169.yml new file mode 100644 index 0000000000000..d7dcb2b749538 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88169.yml @@ -0,0 +1,4 @@ +author: "Cruix" +delete-after: True +changes: + - rscadd: "Shuttle navigation computers now show the location of airlocks, turrets, and the shuttle control consoles on the ship outline while placing a custom landing location." \ No newline at end of file From d8c18d803ae48012b076b7a4be9f3064fd8c8137 Mon Sep 17 00:00:00 2001 From: Echo <118005474+Darkened-Earth@users.noreply.github.com> Date: Sat, 21 Dec 2024 22:11:38 +0000 Subject: [PATCH 041/235] Fire Alarm In Meta's Customs (#88585) ## About The Pull Request Removes an obsolete (suspected to be misplaced) fire alarm at (055, 141) from MetaStation Fixes the following bug report: https://github.com/tgstation/tgstation/issues/88241#issue-2696529590 ## Why It's Good For The Game The alarm in question achieves nothing useful in its current state, and isn't even reachable without climbing up on the tables. A functional fire alarm sits next to it in the Arrival Shuttle Hallway, and opposite it in the Customs office. Hidden below a Space Law book, it appears to be a mapping issue that was missed due to object layering. Wrangling up this fire alarm would be Quite Good(TM) for cleaning up the station of unintentional electronics. ## Changelog :cl: fix: Alarm ranchers have wrangled up a rogue fire alarm in Meta class station custom offices and relocated it to a safe habitat /:cl: --- _maps/map_files/MetaStation/MetaStation.dmm | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm index 6b4adfe0d4896..e6aabf579de2a 100644 --- a/_maps/map_files/MetaStation/MetaStation.dmm +++ b/_maps/map_files/MetaStation/MetaStation.dmm @@ -67095,10 +67095,6 @@ }, /turf/open/floor/iron, /area/station/ai_monitored/command/storage/eva) -"xBX" = ( -/obj/machinery/firealarm/directional/east, -/turf/closed/wall, -/area/station/security/checkpoint/customs) "xCf" = ( /obj/item/clothing/suit/jacket/straight_jacket, /obj/item/electropack, @@ -83046,7 +83042,7 @@ wdr fcq xfI fcq -xBX +xjh qhW xjh yme From ce9e556d00844018feda531fa097b7341598af8a Mon Sep 17 00:00:00 2001 From: Sparex <87827746+JagOfTroy@users.noreply.github.com> Date: Sat, 21 Dec 2024 16:12:17 -0600 Subject: [PATCH 042/235] IceBoxStation.dmm - Added Fitness equipment to fitness room (#88588) ## About The Pull Request When playing on Icebox station with another player, we went to the fitness room to workout to only realize they don't have any actual fitness equipment in the room. Without totally tearing apart the layout, I added two lifting benches so that people can still get their muscles swole and still give the room the same ease of flow for people to move through it. ## Why It's Good For The Game This adds equipment to the map that needs to be there so that players can build up their fitness levels besides using the boxing ring and gives players more variety/flavor for roleplay. ![Screenshot 2024-12-18 125040](https://github.com/user-attachments/assets/17345960-ee53-43f1-b86d-86ccd287ead6) ## Changelog :cl: Sparex map: Added lifting benches/workout benches to the fitness room for IceboxStation.dmm /:cl: --- _maps/map_files/IceBoxStation/IceBoxStation.dmm | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/_maps/map_files/IceBoxStation/IceBoxStation.dmm b/_maps/map_files/IceBoxStation/IceBoxStation.dmm index aa3df4dfd8a93..6ee4db7790ea9 100644 --- a/_maps/map_files/IceBoxStation/IceBoxStation.dmm +++ b/_maps/map_files/IceBoxStation/IceBoxStation.dmm @@ -46005,6 +46005,13 @@ dir = 4 }, /area/station/hallway/secondary/entry) +"nqI" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, +/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, +/obj/effect/turf_decal/tile/neutral/half/contrasted, +/obj/effect/landmark/start/hangover, +/turf/open/floor/iron, +/area/station/commons/fitness) "nqP" = ( /obj/machinery/camera/directional/north{ c_tag = "Research Division West"; @@ -53846,8 +53853,8 @@ /obj/effect/turf_decal/tile/neutral{ dir = 1 }, -/obj/item/kirbyplants/random, /obj/structure/sign/flag/terragov/directional/north, +/obj/structure/weightmachine/weightlifter, /turf/open/floor/iron, /area/station/commons/fitness) "pyn" = ( @@ -81087,6 +81094,10 @@ /obj/structure/cable, /turf/open/floor/plating, /area/station/security/detectives_office) +"xyg" = ( +/obj/structure/weightmachine, +/turf/open/floor/iron, +/area/station/commons/fitness) "xyl" = ( /obj/effect/turf_decal/tile/blue/half/contrasted{ dir = 1 @@ -249315,7 +249326,7 @@ cGB cGB gsI tLL -spy +nqI kKL kKL kKL @@ -250338,8 +250349,8 @@ dyA skl gaC vfW -eOl vfW +xyg vfW lvk crv From 322712bbb39709cd5e9b3e5384d44585f7ef5a00 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 22:12:19 +0000 Subject: [PATCH 043/235] Automatic changelog for PR #88585 [ci skip] --- html/changelogs/AutoChangeLog-pr-88585.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88585.yml diff --git a/html/changelogs/AutoChangeLog-pr-88585.yml b/html/changelogs/AutoChangeLog-pr-88585.yml new file mode 100644 index 0000000000000..c4e002241a6e1 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88585.yml @@ -0,0 +1,4 @@ +author: "Darkened-Earth" +delete-after: True +changes: + - bugfix: "Alarm ranchers have wrangled up a rogue fire alarm in Meta class station custom offices and relocated it to a safe habitat" \ No newline at end of file From 4799894cc4db66906ff04486f666b396f3183d05 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 22:13:10 +0000 Subject: [PATCH 044/235] Automatic changelog for PR #88588 [ci skip] --- html/changelogs/AutoChangeLog-pr-88588.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88588.yml diff --git a/html/changelogs/AutoChangeLog-pr-88588.yml b/html/changelogs/AutoChangeLog-pr-88588.yml new file mode 100644 index 0000000000000..84a9c268d1f1b --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88588.yml @@ -0,0 +1,4 @@ +author: "Sparex" +delete-after: True +changes: + - map: "Added lifting benches/workout benches to the fitness room for IceboxStation.dmm" \ No newline at end of file From 95ae128c8de856afd1f56e91bc9837a3bf6dfd29 Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Sun, 22 Dec 2024 01:41:17 +0300 Subject: [PATCH 045/235] Prevents mech RCD from being used with mesons, doubles R-wall deconstruction time (#88589) ## About The Pull Request Mech RCD has a unique quality of being usable at range, but does not check for LoS which allows you to instantly bore holes in multilayered walls with no warning. R-walls won't save you either, since they can be deconstructed by it just fine. Now, mech RCDs need LoS on turfs you click and deconstructs r-walls twice as slowly (the same time it'd take a normal RCD to deconstruct a normal wall). ## Why It's Good For The Game ![mech_rcd](https://github.com/user-attachments/assets/c10a1ad1-23f4-4da7-8a65-5dc110a74099) Screen from a recent round - this can be done in 6 seconds, as with correct mech positioning that's as long as you need to deconstruct all the walls and doors. This is incredibly oppressive and I won't be surprised if this started getting abused like hell. This should prevent further issues while still keeping it a good (but not absurdly overpowered) option for when you need to breach into somewhere, since its still quicker (and safer) than thermite. ## Changelog :cl: balance: Mech-mounted RCD no longer has wallhacks balance: Reinforced walls now take double the time to get deconstructed when using an RCD /:cl: Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com> --- code/__DEFINES/construction/rcd.dm | 3 +++ code/game/turfs/closed/wall/reinf_walls.dm | 8 ++++++-- code/modules/vehicles/mecha/equipment/tools/work_tools.dm | 3 +++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/code/__DEFINES/construction/rcd.dm b/code/__DEFINES/construction/rcd.dm index a8d98215af1dc..4f898d5ae86ec 100644 --- a/code/__DEFINES/construction/rcd.dm +++ b/code/__DEFINES/construction/rcd.dm @@ -51,3 +51,6 @@ #define RCD_MEMORY_COST_BUFF 8 /// If set to TRUE in rcd_vals, will bypass the cooldown on slowing down frequent use #define RCD_RESULT_BYPASS_FREQUENT_USE_COOLDOWN "bypass_frequent_use_cooldown" + +/// How much longer does it take to deconstruct rwalls? +#define RCD_RWALL_DELAY_MULT 2 diff --git a/code/game/turfs/closed/wall/reinf_walls.dm b/code/game/turfs/closed/wall/reinf_walls.dm index 739ee5aeae0f4..2a17160ae4c85 100644 --- a/code/game/turfs/closed/wall/reinf_walls.dm +++ b/code/game/turfs/closed/wall/reinf_walls.dm @@ -215,9 +215,13 @@ dismantle_wall() /turf/closed/wall/r_wall/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd) - if(the_rcd.canRturf || the_rcd.construction_mode == RCD_WALLFRAME) + if (the_rcd.construction_mode == RCD_WALLFRAME) return ..() - + if(!the_rcd.canRturf) + return + . = ..() + if (.) + .["delay"] *= RCD_RWALL_DELAY_MULT /turf/closed/wall/r_wall/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data) if(the_rcd.canRturf || rcd_data["[RCD_DESIGN_MODE]"] == RCD_WALLFRAME) diff --git a/code/modules/vehicles/mecha/equipment/tools/work_tools.dm b/code/modules/vehicles/mecha/equipment/tools/work_tools.dm index f080675af4673..651ccf999bb70 100644 --- a/code/modules/vehicles/mecha/equipment/tools/work_tools.dm +++ b/code/modules/vehicles/mecha/equipment/tools/work_tools.dm @@ -293,6 +293,9 @@ /obj/item/mecha_parts/mecha_equipment/rcd/action(mob/source, atom/target, list/modifiers) if(!action_checks(target)) return + // No meson action! + if (!(target in view(RCD_RANGE, get_turf(chassis)))) + return if(get_dist(chassis, target) > RCD_RANGE) balloon_alert(source, "out of range!") return From 97d04a383383d689976db867d6430363d9c218ed Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 21 Dec 2024 22:41:56 +0000 Subject: [PATCH 046/235] Automatic changelog for PR #88589 [ci skip] --- html/changelogs/AutoChangeLog-pr-88589.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88589.yml diff --git a/html/changelogs/AutoChangeLog-pr-88589.yml b/html/changelogs/AutoChangeLog-pr-88589.yml new file mode 100644 index 0000000000000..afc96a8df3a3c --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88589.yml @@ -0,0 +1,5 @@ +author: "SmArtKar" +delete-after: True +changes: + - balance: "Mech-mounted RCD no longer has wallhacks" + - balance: "Reinforced walls now take double the time to get deconstructed when using an RCD" \ No newline at end of file From 97f54ecd89939865ac8a680d96e33f641ae957b0 Mon Sep 17 00:00:00 2001 From: UnokiAs <82665345+UnokiAs@users.noreply.github.com> Date: Sun, 22 Dec 2024 01:11:38 +0100 Subject: [PATCH 047/235] Restore Spear's Ability to Destroy Lockers (#88377) ## About The Pull Request This change make the lockers "electronics" weaker to the spear, restoring its ability to destroy lockers. While this functionality was not originally intended (throwing spears on lockers) ( #88141 ), it has been present for so long that it has effectively become a feature. This pull request rework that feature by making the spear do damage by hitting lockers in melee. https://github.com/user-attachments/assets/67058342-57e2-4670-9c68-df4c3b6d6193 ## Why It's Good For The Game Restoring the spear's ability to destroy lockers maintains its previous utility and aligns with player expectations, preserving and reworking feature that has become a staple of gameplay and lower the burden on antagonist to open security or command lockers. ## Changelog :cl: UnokiAs add: Make spears able to break open lockers in melee. /:cl: --- code/game/objects/structures/crates_lockers/closets.dm | 10 ++++++++++ strings/tips.txt | 1 + 2 files changed, 11 insertions(+) diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index 207b85122dd21..edfbe8e38c0e4 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -1222,4 +1222,14 @@ GLOBAL_LIST_EMPTY(roundstart_station_closets) /obj/structure/closet/proc/add_to_roundstart_list() GLOB.roundstart_station_closets += src +///Spears deal bonus damages to lockers +/obj/structure/closet/attacked_by(obj/item/attacking_item, mob/living/user) + if(istype(attacking_item, /obj/item/spear)) + take_damage(attacking_item.force * 2, attacking_item.damtype, MELEE, 1, get_dir(src, user)) + user.visible_message(span_danger("[user] stabs with precision [src]'s electronics with [attacking_item]!"), + span_danger("You stab with precision [src]'s electronics with [attacking_item]!"), null, COMBAT_MESSAGE_RANGE) + log_combat(user, src, "attacked", attacking_item) + return + return ..() + #undef LOCKER_FULL diff --git a/strings/tips.txt b/strings/tips.txt index 5f33cb87bc7fe..e6fc672c5c494 100644 --- a/strings/tips.txt +++ b/strings/tips.txt @@ -227,6 +227,7 @@ Basketball requires skill. Spinning drains stamina, reduces accuracy, but gives Basketball requires teamwork. Passing the ball with LMB is instant and costs no stamina. Clicking on a windoor rather then bumping into it will keep it open, you can click it again to close it. Different weapons have different strengths. Some weapons, such as spears, floor tiles, and throwing stars, deal more damage when thrown compared to when attacked normally. +Spears are capable of breaking into secure lockers by striking them in melee! Don't be afraid to ask for help, whether from your peers or from admins. Experiment with different setups of the Supermatter engine to maximize output, but don't risk the crew's safety to do so! Felinids get temporarily distracted by laser pointers. Use this to your advantage when being pursued by one. From c89ca103dbcc3a5eede7d855351358b0cd85fa67 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 22 Dec 2024 00:17:51 +0000 Subject: [PATCH 048/235] Automatic changelog for PR #88377 [ci skip] --- html/changelogs/AutoChangeLog-pr-88377.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88377.yml diff --git a/html/changelogs/AutoChangeLog-pr-88377.yml b/html/changelogs/AutoChangeLog-pr-88377.yml new file mode 100644 index 0000000000000..7cb5b8629e4be --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88377.yml @@ -0,0 +1,4 @@ +author: "UnokiAs" +delete-after: True +changes: + - rscadd: "Make spears able to break open lockers in melee." \ No newline at end of file From 51f4127771036288bb6d6073c8f61d734c51015c Mon Sep 17 00:00:00 2001 From: Tim Date: Sat, 21 Dec 2024 18:23:38 -0600 Subject: [PATCH 049/235] Add power efficiency when stasis bed stock parts are upgraded (#88555) ## About The Pull Request Upgrading a stasis bed caused it to drain a massive amount of power without ANY benefit. The roundstart power draw is 300/3,000 when inactive/active. If you max upgrade the stock parts it results in 2,100/21,000 power draw which is insane since it does nothing! Seeing as how there is no other effects, I just made the upgrades divide power (instead of multiply) resultings in better efficiency. Roundstart is still 300/3,000 but now at max upgrade it is 30/300. ## Why It's Good For The Game Upgrading a machine should have some upside. --- code/game/machinery/stasis.dm | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/code/game/machinery/stasis.dm b/code/game/machinery/stasis.dm index 49f00741895fe..3e3fc5af30757 100644 --- a/code/game/machinery/stasis.dm +++ b/code/game/machinery/stasis.dm @@ -14,6 +14,9 @@ fair_market_price = 10 payment_department = ACCOUNT_MED interaction_flags_click = ALLOW_SILICON_REACH + use_power = IDLE_POWER_USE + idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 3 + active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 3 var/stasis_enabled = TRUE var/last_stasis_sound = FALSE var/stasis_can_toggle = 0 @@ -25,6 +28,20 @@ AddElement(/datum/element/elevation, pixel_shift = 6) update_buckle_vars(dir) +/obj/machinery/stasis/RefreshParts() + . = ..() + + var/energy_rating = 0 + for(var/datum/stock_part/part in component_parts) + energy_rating += part.energy_rating() + + for(var/obj/item/stock_parts/part in component_parts) + energy_rating += part.energy_rating + + idle_power_usage = initial(idle_power_usage) / (energy_rating/2) + active_power_usage = initial(active_power_usage) / (energy_rating/2) + update_current_power_usage() + /obj/machinery/stasis/examine(mob/user) . = ..() . += span_notice("Alt-click to [stasis_enabled ? "turn off" : "turn on"] the machine.") From 7d63fdaa08242d9076ae921ee15b947348efffc5 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 22 Dec 2024 00:23:57 +0000 Subject: [PATCH 050/235] Automatic changelog for PR #88555 [ci skip] --- html/changelogs/AutoChangeLog-pr-88555.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88555.yml diff --git a/html/changelogs/AutoChangeLog-pr-88555.yml b/html/changelogs/AutoChangeLog-pr-88555.yml new file mode 100644 index 0000000000000..97e1e9ef3267c --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88555.yml @@ -0,0 +1,4 @@ +author: "timothymtorres" +delete-after: True +changes: + - rscadd: "Add power efficiency when stasis bed stock parts are upgraded" \ No newline at end of file From ac6e5e7a91a1f0dd282b698862f7cfdee93d18af Mon Sep 17 00:00:00 2001 From: tonty <39193182+tontyGH@users.noreply.github.com> Date: Sat, 21 Dec 2024 19:24:27 -0500 Subject: [PATCH 051/235] Correctly unregisters COMSIG_QDELETED signal from old avatar_connection parent (#88524) --- code/modules/bitrunning/components/avatar_connection.dm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/modules/bitrunning/components/avatar_connection.dm b/code/modules/bitrunning/components/avatar_connection.dm index 9fdfe1f629ec2..cefaeef91f3c3 100644 --- a/code/modules/bitrunning/components/avatar_connection.dm +++ b/code/modules/bitrunning/components/avatar_connection.dm @@ -107,8 +107,9 @@ COMSIG_BITRUNNER_ALERT_SEVER, COMSIG_BITRUNNER_CACHE_SEVER, COMSIG_BITRUNNER_LADDER_SEVER, - COMSIG_LIVING_DEATH, COMSIG_LIVING_PILL_CONSUMED, + COMSIG_LIVING_DEATH, + COMSIG_QDELETING, COMSIG_MOB_APPLY_DAMAGE, )) From 7d283d57914073e3ba92deca5af85c0671ace340 Mon Sep 17 00:00:00 2001 From: Lucy Date: Sat, 21 Dec 2024 19:25:21 -0500 Subject: [PATCH 052/235] Add a notice to the stat panel for clients on 516 (#88622) ## About The Pull Request This adds a constant notice to the stat panel for clients on BYOND 516. ![2024-12-20 (1734739281) ~ dreamseeker](https://github.com/user-attachments/assets/af220a22-241d-4456-9b5c-652dc0953be0) ## Why It's Good For The Game Best to make sure everyone knows that various UIs will likely be janky/borked on BYOND 516. Hopefully this is hard to miss. --- code/controllers/subsystem/statpanel.dm | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm index 9d33e977e2b7f..382ff90cdc844 100644 --- a/code/controllers/subsystem/statpanel.dm +++ b/code/controllers/subsystem/statpanel.dm @@ -92,11 +92,14 @@ SUBSYSTEM_DEF(statpanels) return /datum/controller/subsystem/statpanels/proc/set_status_tab(client/target) +#if MIN_COMPILER_VERSION > 515 + #warn 516 is most certainly out of beta, remove this beta notice if you haven't already +#endif + var/static/list/beta_notice = list("", "You are on the BYOND 516 beta, various UIs and such may be broken!", "Please report issues, and switch back to BYOND 515 if things are causing too many issues for you.") if(!global_data)//statbrowser hasnt fired yet and we were called from immediate_send_stat_data() return - target.stat_panel.send_message("update_stat", list( - "global_data" = global_data, + "global_data" = (target.byond_version < 516) ? global_data : (global_data + beta_notice), "ping_str" = "Ping: [round(target.lastping, 1)]ms (Average: [round(target.avgping, 1)]ms)", "other_str" = target.mob?.get_status_tab_items(), )) From 5425d4df15fcacfbd7be218ab86a020686e8cf29 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 22 Dec 2024 00:25:44 +0000 Subject: [PATCH 053/235] Automatic changelog for PR #88622 [ci skip] --- html/changelogs/AutoChangeLog-pr-88622.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88622.yml diff --git a/html/changelogs/AutoChangeLog-pr-88622.yml b/html/changelogs/AutoChangeLog-pr-88622.yml new file mode 100644 index 0000000000000..ff58b2b49f4e1 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88622.yml @@ -0,0 +1,4 @@ +author: "Absolucy" +delete-after: True +changes: + - rscadd: "Added a notice for clients on BYOND 516 that 516 is still in beta, and UI elements may not be fully compatible yet." \ No newline at end of file From d4363df781a98a85df90c42bf0b8aa5ff2e24d04 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 22 Dec 2024 00:28:37 +0000 Subject: [PATCH 054/235] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-88169.yml | 4 -- html/changelogs/AutoChangeLog-pr-88377.yml | 4 -- html/changelogs/AutoChangeLog-pr-88391.yml | 7 --- html/changelogs/AutoChangeLog-pr-88522.yml | 4 -- html/changelogs/AutoChangeLog-pr-88531.yml | 4 -- html/changelogs/AutoChangeLog-pr-88555.yml | 4 -- html/changelogs/AutoChangeLog-pr-88562.yml | 4 -- html/changelogs/AutoChangeLog-pr-88564.yml | 4 -- html/changelogs/AutoChangeLog-pr-88566.yml | 4 -- html/changelogs/AutoChangeLog-pr-88569.yml | 4 -- html/changelogs/AutoChangeLog-pr-88571.yml | 4 -- html/changelogs/AutoChangeLog-pr-88585.yml | 4 -- html/changelogs/AutoChangeLog-pr-88588.yml | 4 -- html/changelogs/AutoChangeLog-pr-88589.yml | 5 -- html/changelogs/AutoChangeLog-pr-88590.yml | 4 -- html/changelogs/AutoChangeLog-pr-88596.yml | 5 -- html/changelogs/AutoChangeLog-pr-88598.yml | 4 -- html/changelogs/AutoChangeLog-pr-88600.yml | 4 -- html/changelogs/AutoChangeLog-pr-88601.yml | 4 -- html/changelogs/AutoChangeLog-pr-88602.yml | 4 -- html/changelogs/AutoChangeLog-pr-88603.yml | 4 -- html/changelogs/AutoChangeLog-pr-88608.yml | 4 -- html/changelogs/AutoChangeLog-pr-88610.yml | 4 -- html/changelogs/AutoChangeLog-pr-88612.yml | 5 -- html/changelogs/AutoChangeLog-pr-88614.yml | 5 -- html/changelogs/AutoChangeLog-pr-88622.yml | 4 -- html/changelogs/archive/2024-12.yml | 63 ++++++++++++++++++++++ 27 files changed, 63 insertions(+), 111 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-88169.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88377.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88391.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88522.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88531.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88555.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88562.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88564.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88566.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88569.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88571.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88585.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88588.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88589.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88590.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88596.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88598.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88600.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88601.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88602.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88603.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88608.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88610.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88612.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88614.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88622.yml diff --git a/html/changelogs/AutoChangeLog-pr-88169.yml b/html/changelogs/AutoChangeLog-pr-88169.yml deleted file mode 100644 index d7dcb2b749538..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88169.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Cruix" -delete-after: True -changes: - - rscadd: "Shuttle navigation computers now show the location of airlocks, turrets, and the shuttle control consoles on the ship outline while placing a custom landing location." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88377.yml b/html/changelogs/AutoChangeLog-pr-88377.yml deleted file mode 100644 index 7cb5b8629e4be..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88377.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "UnokiAs" -delete-after: True -changes: - - rscadd: "Make spears able to break open lockers in melee." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88391.yml b/html/changelogs/AutoChangeLog-pr-88391.yml deleted file mode 100644 index 12bfe4a8a99b2..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88391.yml +++ /dev/null @@ -1,7 +0,0 @@ -author: "Kocma-san" -delete-after: True -changes: - - rscadd: "added a \"print\" button to the request manager" - - rscadd: "admin fax can now send exotic items" - - rscadd: "added \"Auto-print Faxes\" button to the request manager, which allows you to enable automatic printing of requests on the admin fax" - - bugfix: "fixed a bug where pressing the print button would cause the receive animation to appear on all admin faxes." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88522.yml b/html/changelogs/AutoChangeLog-pr-88522.yml deleted file mode 100644 index 84aa7e7ee8b81..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88522.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "TealSeer" -delete-after: True -changes: - - qol: "Atmos devices like valves and pumps can now be renamed with a pen." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88531.yml b/html/changelogs/AutoChangeLog-pr-88531.yml deleted file mode 100644 index 7fbd94c9aed14..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88531.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SmArtKar" -delete-after: True -changes: - - bugfix: "Fixed floodlights not being affected by spraycan painting" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88555.yml b/html/changelogs/AutoChangeLog-pr-88555.yml deleted file mode 100644 index 97e1e9ef3267c..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88555.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothymtorres" -delete-after: True -changes: - - rscadd: "Add power efficiency when stasis bed stock parts are upgraded" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88562.yml b/html/changelogs/AutoChangeLog-pr-88562.yml deleted file mode 100644 index 9405b08a5a2d2..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88562.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "mcbalaam" -delete-after: True -changes: - - bugfix: "The mail sorter no longer runtimes processing assistant mail" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88564.yml b/html/changelogs/AutoChangeLog-pr-88564.yml deleted file mode 100644 index d9a83b2f69da3..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88564.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothymtorres" -delete-after: True -changes: - - spellcheck: "Update teleporter machine desc to be accurate" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88566.yml b/html/changelogs/AutoChangeLog-pr-88566.yml deleted file mode 100644 index 9e11b9250c684..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88566.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "timothymtorres" -delete-after: True -changes: - - spellcheck: "Fix holodeck emag message claiming to increase power" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88569.yml b/html/changelogs/AutoChangeLog-pr-88569.yml deleted file mode 100644 index f9db5c10ca6ff..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88569.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Absolucy" -delete-after: True -changes: - - qol: "Rust heretic healing (leeching walk, rust ascension) now, so server lag shouldn't fuck you over nearly as much if you're relying on the healing." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88571.yml b/html/changelogs/AutoChangeLog-pr-88571.yml deleted file mode 100644 index f8895bb194691..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88571.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Melbert" -delete-after: True -changes: - - rscadd: "Nitroglycerin now heals heart damage." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88585.yml b/html/changelogs/AutoChangeLog-pr-88585.yml deleted file mode 100644 index c4e002241a6e1..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88585.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Darkened-Earth" -delete-after: True -changes: - - bugfix: "Alarm ranchers have wrangled up a rogue fire alarm in Meta class station custom offices and relocated it to a safe habitat" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88588.yml b/html/changelogs/AutoChangeLog-pr-88588.yml deleted file mode 100644 index 84a9c268d1f1b..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88588.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Sparex" -delete-after: True -changes: - - map: "Added lifting benches/workout benches to the fitness room for IceboxStation.dmm" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88589.yml b/html/changelogs/AutoChangeLog-pr-88589.yml deleted file mode 100644 index afc96a8df3a3c..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88589.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "SmArtKar" -delete-after: True -changes: - - balance: "Mech-mounted RCD no longer has wallhacks" - - balance: "Reinforced walls now take double the time to get deconstructed when using an RCD" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88590.yml b/html/changelogs/AutoChangeLog-pr-88590.yml deleted file mode 100644 index 114a95b290c91..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88590.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SmArtKar" -delete-after: True -changes: - - bugfix: "Chemmaster no longer rounds reagent amounts when trying to transfer a custom amount" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88596.yml b/html/changelogs/AutoChangeLog-pr-88596.yml deleted file mode 100644 index d59028d86fc4e..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88596.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Kocma-san" -delete-after: True -changes: - - bugfix: "fix radio sound output when receiving a message" - - sound: "the sound of receiving your own messages over the radio is no longer played" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88598.yml b/html/changelogs/AutoChangeLog-pr-88598.yml deleted file mode 100644 index 6feb07ff376e8..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88598.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SyncIt21" -delete-after: True -changes: - - bugfix: "greyscale modify menu has better validation for player entered colours" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88600.yml b/html/changelogs/AutoChangeLog-pr-88600.yml deleted file mode 100644 index f8f6ef99810f9..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88600.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SmArtKar" -delete-after: True -changes: - - bugfix: "Fixed shoes slot being semi-transparent when you have digitigrade legs" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88601.yml b/html/changelogs/AutoChangeLog-pr-88601.yml deleted file mode 100644 index b3e38d483d9ff..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88601.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SyncIt21" -delete-after: True -changes: - - bugfix: "fixed runtime when sealing mecha cabin with air tank installed" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88602.yml b/html/changelogs/AutoChangeLog-pr-88602.yml deleted file mode 100644 index c9698b7f26b81..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88602.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SmArtKar" -delete-after: True -changes: - - map: "Added a Condi-Master to Birdshot's bar" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88603.yml b/html/changelogs/AutoChangeLog-pr-88603.yml deleted file mode 100644 index 363c021935304..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88603.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SyncIt21" -delete-after: True -changes: - - bugfix: "personal ordered crates can be unlocked & relocked as many times again after the 1st attempt" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88608.yml b/html/changelogs/AutoChangeLog-pr-88608.yml deleted file mode 100644 index e7ed3dd34af6a..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88608.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "grungussuss" -delete-after: True -changes: - - rscdel: "screenshake from explosions will no longer happen when it's really far and not on a station area (turf)" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88610.yml b/html/changelogs/AutoChangeLog-pr-88610.yml deleted file mode 100644 index 958a64f99451b..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88610.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SmArtKar" -delete-after: True -changes: - - spellcheck: "Bioscrambler no longer informs you about \"your armor softening the blow!\"" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88612.yml b/html/changelogs/AutoChangeLog-pr-88612.yml deleted file mode 100644 index 208928a92f3cb..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88612.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Gaxeer" -delete-after: True -changes: - - bugfix: "modular computer laptops can now be interacted with RMB, instead of picked up" - - qol: "modular computer laptops can now be interacted with RMB when open, or opened with RMB when closed. Also screentips for this added" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88614.yml b/html/changelogs/AutoChangeLog-pr-88614.yml deleted file mode 100644 index fb92e384cb2da..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88614.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "SyncIt21" -delete-after: True -changes: - - code_imp: "improved code for mecha rcd" - - bugfix: "mecha rcd will cancel its action when the mech is rotated or moves during the action" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88622.yml b/html/changelogs/AutoChangeLog-pr-88622.yml deleted file mode 100644 index ff58b2b49f4e1..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88622.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Absolucy" -delete-after: True -changes: - - rscadd: "Added a notice for clients on BYOND 516 that 516 is still in beta, and UI elements may not be fully compatible yet." \ No newline at end of file diff --git a/html/changelogs/archive/2024-12.yml b/html/changelogs/archive/2024-12.yml index 8a2acc276d298..5a594a7616886 100644 --- a/html/changelogs/archive/2024-12.yml +++ b/html/changelogs/archive/2024-12.yml @@ -477,3 +477,66 @@ on purity. Melbert: - bugfix: Lobby button sprites +2024-12-22: + Absolucy: + - rscadd: Added a notice for clients on BYOND 516 that 516 is still in beta, and + UI elements may not be fully compatible yet. + - qol: Rust heretic healing (leeching walk, rust ascension) now, so server lag shouldn't + fuck you over nearly as much if you're relying on the healing. + Cruix: + - rscadd: Shuttle navigation computers now show the location of airlocks, turrets, + and the shuttle control consoles on the ship outline while placing a custom + landing location. + Darkened-Earth: + - bugfix: Alarm ranchers have wrangled up a rogue fire alarm in Meta class station + custom offices and relocated it to a safe habitat + Gaxeer: + - bugfix: modular computer laptops can now be interacted with RMB, instead of picked + up + - qol: modular computer laptops can now be interacted with RMB when open, or opened + with RMB when closed. Also screentips for this added + Kocma-san: + - rscadd: added a "print" button to the request manager + - rscadd: admin fax can now send exotic items + - rscadd: added "Auto-print Faxes" button to the request manager, which allows you + to enable automatic printing of requests on the admin fax + - bugfix: fixed a bug where pressing the print button would cause the receive animation + to appear on all admin faxes. + - bugfix: fix radio sound output when receiving a message + - sound: the sound of receiving your own messages over the radio is no longer played + Melbert: + - rscadd: Nitroglycerin now heals heart damage. + SmArtKar: + - bugfix: Fixed floodlights not being affected by spraycan painting + - balance: Mech-mounted RCD no longer has wallhacks + - balance: Reinforced walls now take double the time to get deconstructed when using + an RCD + - map: Added a Condi-Master to Birdshot's bar + - bugfix: Fixed shoes slot being semi-transparent when you have digitigrade legs + - spellcheck: Bioscrambler no longer informs you about "your armor softening the + blow!" + - bugfix: Chemmaster no longer rounds reagent amounts when trying to transfer a + custom amount + Sparex: + - map: Added lifting benches/workout benches to the fitness room for IceboxStation.dmm + SyncIt21: + - bugfix: fixed runtime when sealing mecha cabin with air tank installed + - bugfix: personal ordered crates can be unlocked & relocked as many times again + after the 1st attempt + - bugfix: greyscale modify menu has better validation for player entered colours + - code_imp: improved code for mecha rcd + - bugfix: mecha rcd will cancel its action when the mech is rotated or moves during + the action + TealSeer: + - qol: Atmos devices like valves and pumps can now be renamed with a pen. + UnokiAs: + - rscadd: Make spears able to break open lockers in melee. + grungussuss: + - rscdel: screenshake from explosions will no longer happen when it's really far + and not on a station area (turf) + mcbalaam: + - bugfix: The mail sorter no longer runtimes processing assistant mail + timothymtorres: + - spellcheck: Update teleporter machine desc to be accurate + - spellcheck: Fix holodeck emag message claiming to increase power + - rscadd: Add power efficiency when stasis bed stock parts are upgraded From acb883cfec8aca5231cd763b42e77a7b1a99d0b5 Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Sat, 21 Dec 2024 18:36:13 -0600 Subject: [PATCH 055/235] Reduce Carpenter hammer force (this thing can open locked gun crates!) (#88380) ## About The Pull Request Carpenter hammer force 20 -> 17 Carpenter hammer throwforce 20 -> 14 Carpenter hammer demo mod 1.25 -> 1.15 ## Why It's Good For The Game You can buy this thing in the black market for 250 credits and it's strong enough to open gun crates from cargo ![image](https://github.com/user-attachments/assets/c8a352c6-dfc3-40fd-90fd-a1d0a0b4ba0b) ## Changelog :cl: Melbert balance: Carpenter hammer force 20 -> 17 balance: Carpenter hammer throwforce 20 -> 14 balance: Carpenter hammer demo mod 1.25 -> 1.15 /:cl: --- code/game/objects/items/weaponry.dm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm index bc34783a853e1..4427294aef8ec 100644 --- a/code/game/objects/items/weaponry.dm +++ b/code/game/objects/items/weaponry.dm @@ -464,12 +464,12 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 lefthand_file = 'icons/mob/inhands/weapons/hammers_lefthand.dmi' righthand_file = 'icons/mob/inhands/weapons/hammers_righthand.dmi' desc = "Uncanny looking hammer." - force = 20 - throwforce = 20 + force = 17 + throwforce = 14 throw_range = 4 w_class = WEIGHT_CLASS_NORMAL wound_bonus = 20 - demolition_mod = 1.25 + demolition_mod = 1.15 slot_flags = ITEM_SLOT_BELT /obj/item/carpenter_hammer/Initialize(mapload) From df1b7991d13a646e6dad13ed15bbdcf24f433056 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 22 Dec 2024 00:36:35 +0000 Subject: [PATCH 056/235] Automatic changelog for PR #88380 [ci skip] --- html/changelogs/AutoChangeLog-pr-88380.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88380.yml diff --git a/html/changelogs/AutoChangeLog-pr-88380.yml b/html/changelogs/AutoChangeLog-pr-88380.yml new file mode 100644 index 0000000000000..27f47a64a6165 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88380.yml @@ -0,0 +1,6 @@ +author: "Melbert" +delete-after: True +changes: + - balance: "Carpenter hammer force 20 -> 17" + - balance: "Carpenter hammer throwforce 20 -> 14" + - balance: "Carpenter hammer demo mod 1.25 -> 1.15" \ No newline at end of file From c1a180c35d0fb89534571ba144c8071778847f0a Mon Sep 17 00:00:00 2001 From: necromanceranne <40847847+necromanceranne@users.noreply.github.com> Date: Sun, 22 Dec 2024 12:39:59 +1100 Subject: [PATCH 057/235] Traitors can purchase weapon cases for the Makarov and Riot Toy Pistol. Basic ammo comes in ammo packs. Other stuff. (#88482) ## About The Pull Request Makarovs and Donksoft Riot Toy Pistols now come in weapon cases. These cases come with the gun, two spare magazines and a box of spare loose ammo. Basic ammunition for these guns come in case packs of three, with a spare box of loose ammo. The basic ammo for Makarovs is slightly more expensive. Donksoft Toy Pistols from the uplink (and given to clown ops) now deal substantially more damage. For riot darts, this goes from 25 to 35 stamina force. However, the case now costs 6 TC. Makarov and Donksoft toy pistols have had their magazines increased by 4 bullets, for a total of 12. The Ansem is still at 8. The pistol cases now have a unique sprite thanks to SmartKar. ![image](https://github.com/user-attachments/assets/e5726d45-47bc-405a-9d14-c68fd4cb95d5) All included cases come with a disposal bomb built into the case. Use Alt-Right-Click on the case to start the countdown, and stand back. Or chuck it at someone you don't like. ## Why It's Good For The Game >Makarovs and Donksoft Riot Toy Pistols now come in weapon cases. These cases come with the gun, two spare magazines and a box of spare loose ammo. >Basic ammunition for these guns come in case packs of three, with a spare box of loose ammo. The basic ammo for Makarovs is slightly more expensive. A long time ago, I proposed to a maintainer that maybe traitor weapons should be bundled together with some starting ammo, to give them a little bit of a necessary boost for longevity for their cost. This being in a state of the game with only 20 TC and no way to get more. I thought progtot may alleviate this issue somewhat, but I don't believe it has at all. I narrowed this down to just the Makarov and Toy Pistol as I think they're the ones in need of help, and my reasoning is thus; A) The TC value is extremely deceptive. The weapons themselves are not very good at doing anything without additionally putting in more TC to load up on ammunition or support equipment. While it does say 7 TC on the header for the Makarov, if you want it to be silent, you have to spend more TC on a suppressor (3 TC). Then, if you're not entirely sure that only 8+1 shots you get from the beginning is enough to take out a target (maybe they're known to have armor), then you may need to get either specialized ammo or additional ammunition. At a certain point, it begs the question 'why didn't I just buy the bigger stick and get more value out of my purchase as well as more reliability?' Particularly since drawing heat probably means all that TC is just going straight into more ammo, one way or another. Or a bigger stick if you're doing side objectives, making that early purchase redundant. B) Whenever you look at either, I want you to consider. 'Could I get better results by just getting a station weapon?' The answer is almost universally 'yes'. Even some improvised weapons can be more reliable. Even spending TC on getting that weapon (like C-4) is miles more worthwhile than spending TC to get either weapon upfront. C) It significantly overvalues autolathe access just to make the weapon feel less like a ripoff. If the uplink can't in of itself justify using the weapons, they're just flat out not good. and that's assuming players even know to keep magazines. It's just a bad value purchase and deceptive in just how expensive it can end up to use them. The weapon is overshadowed by fairly comparable items once you factor additional expenditures. Even the toy pistol is a bad value purchase and it's literally just 2 TC, because the damage it does is dogshit. It's worse than a disabler. It's worse than a sleepy pen for value to effect. It's so shit, people put them on maps for free grabs. Let's resolve that with the following. > Donksoft Toy Pistols from the uplink (and given to clown ops) now deal substantially more damage. For riot darts, this goes from 25 to 35 stamina force. However, the case now costs 6 TC. Subtle weapon, good, reliable damage. Fantastic for kidnapping at 40 damage. All without ever actually inflicting a real point of damage. Great for clowns, great for pacifist tots. Decent deniability. Silicons may have a harder time justifying an intervention. Genuinely a budget tool that increases the value of any TC you put into it. Since it has so much more value, this is why I've increased the price to offset this. It is actually competing with similar tools well enough to not go too overboard. > Makarov and Donksoft toy pistols have had their magazines increased by 4 bullets, for a total of 12. The Ansem is still at 8. Longevity is the only thing Makarovs seem to want to claim to have over the revolver, and it isn't much more longevity from just the gun itself (7 bullets on the revolver compared to 8+1 on the pistol). If we're talking real longevity, and particularly if you're considering getting a silencer, than the ebow is literally 3 TC more expensive, silent, and has endless ammunition with high damage output. You still need to dump a lot of TC into the Makarov to satiate its ammo needs if you happen to be skirmishing a lot. By comparison, just getting a single laser can often times do a much better job at skirmishing than the tot with a Makarov can. And do comparable damage no less. From experience, I've always done better with lasers than the Makarov. Making it more directly able to maintain a good ammo count during a fight hopefully makes the Makarov feel more like a value purchase for what I think should be its strength. Staying power and the ability to be aggressive with ammo expenditure. Particularly against larger numbers, which traitors are almost always expected to go up against. If those opponents have lasers, the Makarov just always gets outgunned by an absurd amount (lasers on their own have like 16 shots, so you can do the math if there are even two people with a laser each). Simply put; let's not make make tots feel like a dumbass for not just getting a laser themselves or buying a ebow to maintain firepower over long fights, especially with rechargers usually being in pretty nicely secluded locations for them to access. ## Changelog :cl: NecromancerAnne (code), SmArtKar (sprites) balance: Makarovs and Toy Pistols come in weapon cases. Complete with spare ammo. balance: Basic ammo for either weapon comes in weapon cases of three extra magazines at an affordable price. balance: Donksoft Toy Pistols from the uplink are much stronger than their standard counterparts, but now priced at 6 TC. balance: Makarovs and Toy pistols have a magazine capacity of 12 rounds. balance: Gun/Ammo cases from the traitor uplink can be destroyed by activating the disposal bomb. Press Alt-Right-Click on the case to start the timer. /:cl: --------- Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com> --- code/game/objects/items/storage/toolbox.dm | 72 +++++++++++ code/modules/antagonists/clown_ops/outfits.dm | 1 + .../boxes_magazines/external/pistol.dm | 113 ++++++++++-------- .../boxes_magazines/external/toy.dm | 2 +- .../modules/projectiles/guns/ballistic/toy.dm | 4 + .../modules/uplink/uplink_items/ammunition.dm | 16 +-- code/modules/uplink/uplink_items/dangerous.dm | 21 ++-- icons/obj/storage/case.dmi | Bin 2111 -> 2410 bytes 8 files changed, 159 insertions(+), 70 deletions(-) diff --git a/code/game/objects/items/storage/toolbox.dm b/code/game/objects/items/storage/toolbox.dm index 0374b7a3744e7..b3fac29554de6 100644 --- a/code/game/objects/items/storage/toolbox.dm +++ b/code/game/objects/items/storage/toolbox.dm @@ -463,6 +463,78 @@ for(var/i in 1 to 3) new extra_to_spawn (src) +/obj/item/storage/toolbox/guncase/traitor + name = "makarov gun case" + desc = "A weapon's case. Has a blood-red 'S' stamped on the cover. There seems to be a strange switch along the side inside a plastic flap." + icon_state = "pistol_case" + base_icon_state = "pistol_case" + // What ammo box do we spawn in our case? + var/ammo_box_to_spawn = /obj/item/ammo_box/c9mm + // Timer for the bomb in the case. + var/explosion_timer + // Whether or not our case is exploding. Used for determining sprite changes. + var/currently_exploding = FALSE + +/obj/item/storage/toolbox/guncase/traitor/Initialize(mapload) + . = ..() + register_context() + +/obj/item/storage/toolbox/guncase/traitor/examine(mob/user) + . = ..() + . += span_notice("Activate the Evidence Disposal Explosive using Alt-Right-Click.") + +/obj/item/storage/toolbox/guncase/traitor/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + + context[SCREENTIP_CONTEXT_ALT_RMB] = "Activate Evidence Disposal Explosive" + return CONTEXTUAL_SCREENTIP_SET + +/obj/item/storage/toolbox/guncase/traitor/PopulateContents() + new weapon_to_spawn (src) + for(var/i in 1 to 2) + new extra_to_spawn (src) + new ammo_box_to_spawn(src) + +/obj/item/storage/toolbox/guncase/traitor/update_icon_state() + . = ..() + if(currently_exploding) + icon_state = "[base_icon_state]_exploding" + else + icon_state = "[base_icon_state]" + +/obj/item/storage/toolbox/guncase/traitor/click_alt_secondary(mob/user) + . = ..() + var/i_dont_even_think_once_about_blowing_stuff_up = tgui_alert(user, "Would you like to activate the evidence disposal bomb now?", "BYE BYE", list("Yes","No")) + if(i_dont_even_think_once_about_blowing_stuff_up == "No") + return + explosion_timer = addtimer(CALLBACK(src, PROC_REF(think_fast_chucklenuts)), 5 SECONDS, (TIMER_UNIQUE|TIMER_OVERRIDE)) + to_chat(user, span_warning("You prime [src]'s evidence disposal bomb!")) + log_bomber(user, "has activated a", src, "for detonation") + playsound(src, 'sound/items/weapons/armbomb.ogg', 50, TRUE) + currently_exploding = TRUE + update_appearance() + +/// proc to handle our detonation +/obj/item/storage/toolbox/guncase/traitor/proc/think_fast_chucklenuts() + explosion(src, devastation_range = 0, heavy_impact_range = 0, light_impact_range = 2, explosion_cause = src) + qdel(src) + +/obj/item/storage/toolbox/guncase/traitor/ammunition + name = "makarov 9mm magazine case" + weapon_to_spawn = /obj/item/ammo_box/magazine/m9mm + +/obj/item/storage/toolbox/guncase/traitor/donksoft + name = "\improper Donksoft riot pistol gun case" + weapon_to_spawn = /obj/item/gun/ballistic/automatic/pistol/toy/riot/clandestine + extra_to_spawn = /obj/item/ammo_box/magazine/toy/pistol/riot + ammo_box_to_spawn = /obj/item/ammo_box/foambox/riot + +/obj/item/storage/toolbox/guncase/traitor/ammunition/donksoft + name = "\improper Donksoft riot pistol magazine case" + weapon_to_spawn = /obj/item/ammo_box/magazine/toy/pistol/riot + extra_to_spawn = /obj/item/ammo_box/magazine/toy/pistol/riot + ammo_box_to_spawn = /obj/item/ammo_box/foambox/riot + /obj/item/storage/toolbox/guncase/bulldog name = "bulldog gun case" weapon_to_spawn = /obj/item/gun/ballistic/shotgun/bulldog diff --git a/code/modules/antagonists/clown_ops/outfits.dm b/code/modules/antagonists/clown_ops/outfits.dm index fb025e40dbd20..10793237c33c9 100644 --- a/code/modules/antagonists/clown_ops/outfits.dm +++ b/code/modules/antagonists/clown_ops/outfits.dm @@ -10,6 +10,7 @@ r_pocket = /obj/item/bikehorn id = /obj/item/card/id/advanced/chameleon backpack_contents = list( + /obj/item/gun/ballistic/automatic/pistol/toy/riot/clandestine = 1, //The clown op equivalent to the Ansem /obj/item/pen/edagger = 1, /obj/item/dnainjector/clumsymut = 1, //in case you want to be clumsy for the memes /obj/item/storage/box/syndie_kit/clownpins = 1, //for any guns that you get your grubby little clown op mitts on diff --git a/code/modules/projectiles/boxes_magazines/external/pistol.dm b/code/modules/projectiles/boxes_magazines/external/pistol.dm index 8b0bc1da7e5b8..05d4c4bf4c99e 100644 --- a/code/modules/projectiles/boxes_magazines/external/pistol.dm +++ b/code/modules/projectiles/boxes_magazines/external/pistol.dm @@ -1,44 +1,4 @@ -/obj/item/ammo_box/magazine/m10mm - name = "pistol magazine (10mm)" - desc = "A gun magazine." - icon_state = "9x19p" - base_icon_state = "9x19p" - ammo_type = /obj/item/ammo_casing/c10mm - caliber = CALIBER_10MM - max_ammo = 8 - multiple_sprites = AMMO_BOX_FULL_EMPTY - multiple_sprite_use_base = TRUE - -/obj/item/ammo_box/magazine/m10mm/fire - name = "pistol magazine (10mm incendiary)" - icon_state = "9x19pI" - base_icon_state = "9x19pI" - desc = "A 10mm pistol magazine. Loaded with rounds which ignite the target." - ammo_type = /obj/item/ammo_casing/c10mm/fire - -/obj/item/ammo_box/magazine/m10mm/hp - name = "pistol magazine (10mm HP)" - icon_state = "9x19pH" - base_icon_state = "9x19pH" - desc= "A 10mm pistol magazine. Loaded with hollow-point rounds, extremely effective against unarmored targets, but nearly useless against protective clothing." - ammo_type = /obj/item/ammo_casing/c10mm/hp - -/obj/item/ammo_box/magazine/m10mm/ap - name = "pistol magazine (10mm AP)" - icon_state = "9x19pA" - base_icon_state = "9x19pA" - desc= "A 10mm pistol magazine. Loaded with rounds which penetrate armour, but are less effective against normal targets." - ammo_type = /obj/item/ammo_casing/c10mm/ap - -/obj/item/ammo_box/magazine/m45 - name = "handgun magazine (.45)" - icon_state = "45-8" - base_icon_state = "45" - ammo_type = /obj/item/ammo_casing/c45 - caliber = CALIBER_45 - max_ammo = 8 - multiple_sprites = AMMO_BOX_PER_BULLET - multiple_sprite_use_base = TRUE +// Makarov (9mm) // /obj/item/ammo_box/magazine/m9mm name = "pistol magazine (9mm)" @@ -46,7 +6,7 @@ base_icon_state = "9x19p" ammo_type = /obj/item/ammo_casing/c9mm caliber = CALIBER_9MM - max_ammo = 8 + max_ammo = 12 multiple_sprites = AMMO_BOX_FULL_EMPTY multiple_sprite_use_base = TRUE @@ -71,6 +31,8 @@ desc= "A gun magazine. Loaded with rounds which penetrate armour, but are less effective against normal targets." ammo_type = /obj/item/ammo_casing/c9mm/ap +// Stechkin APS (9mm) // + /obj/item/ammo_box/magazine/m9mm_aps name = "stechkin pistol magazine (9mm)" icon_state = "9mmaps-15" @@ -86,25 +48,50 @@ /obj/item/ammo_box/magazine/m9mm_aps/fire name = "stechkin pistol magazine (9mm incendiary)" ammo_type = /obj/item/ammo_casing/c9mm/fire - max_ammo = 15 /obj/item/ammo_box/magazine/m9mm_aps/hp name = "stechkin pistol magazine (9mm HP)" ammo_type = /obj/item/ammo_casing/c9mm/hp - max_ammo = 15 /obj/item/ammo_box/magazine/m9mm_aps/ap name = "stechkin pistol magazine (9mm AP)" ammo_type = /obj/item/ammo_casing/c9mm/ap - max_ammo = 15 -/obj/item/ammo_box/magazine/m50 - name = "handgun magazine (.50ae)" - icon_state = "50ae" - ammo_type = /obj/item/ammo_casing/a50ae - caliber = CALIBER_50AE - max_ammo = 7 - multiple_sprites = AMMO_BOX_PER_BULLET +// Ansem (10mm) // + +/obj/item/ammo_box/magazine/m10mm + name = "pistol magazine (10mm)" + desc = "A gun magazine." + icon_state = "9x19p" + base_icon_state = "9x19p" + ammo_type = /obj/item/ammo_casing/c10mm + caliber = CALIBER_10MM + max_ammo = 8 + multiple_sprites = AMMO_BOX_FULL_EMPTY + multiple_sprite_use_base = TRUE + +/obj/item/ammo_box/magazine/m10mm/fire + name = "pistol magazine (10mm incendiary)" + icon_state = "9x19pI" + base_icon_state = "9x19pI" + desc = "A 10mm pistol magazine. Loaded with rounds which ignite the target." + ammo_type = /obj/item/ammo_casing/c10mm/fire + +/obj/item/ammo_box/magazine/m10mm/hp + name = "pistol magazine (10mm HP)" + icon_state = "9x19pH" + base_icon_state = "9x19pH" + desc= "A 10mm pistol magazine. Loaded with hollow-point rounds, extremely effective against unarmored targets, but nearly useless against protective clothing." + ammo_type = /obj/item/ammo_casing/c10mm/hp + +/obj/item/ammo_box/magazine/m10mm/ap + name = "pistol magazine (10mm AP)" + icon_state = "9x19pA" + base_icon_state = "9x19pA" + desc= "A 10mm pistol magazine. Loaded with rounds which penetrate armour, but are less effective against normal targets." + ammo_type = /obj/item/ammo_casing/c10mm/ap + +// Regal Condor (10mm) // /obj/item/ammo_box/magazine/r10mm name = "regal condor magazine (10mm Reaper)" @@ -115,3 +102,25 @@ max_ammo = 8 multiple_sprites = AMMO_BOX_PER_BULLET multiple_sprite_use_base = TRUE + +// M1911 (.45) // + +/obj/item/ammo_box/magazine/m45 + name = "handgun magazine (.45)" + icon_state = "45-8" + base_icon_state = "45" + ammo_type = /obj/item/ammo_casing/c45 + caliber = CALIBER_45 + max_ammo = 8 + multiple_sprites = AMMO_BOX_PER_BULLET + multiple_sprite_use_base = TRUE + +// Desert Eagle (.50 AE) // + +/obj/item/ammo_box/magazine/m50 + name = "handgun magazine (.50ae)" + icon_state = "50ae" + ammo_type = /obj/item/ammo_casing/a50ae + caliber = CALIBER_50AE + max_ammo = 7 + multiple_sprites = AMMO_BOX_PER_BULLET diff --git a/code/modules/projectiles/boxes_magazines/external/toy.dm b/code/modules/projectiles/boxes_magazines/external/toy.dm index 4f666e119b84d..695388280ebc4 100644 --- a/code/modules/projectiles/boxes_magazines/external/toy.dm +++ b/code/modules/projectiles/boxes_magazines/external/toy.dm @@ -20,7 +20,7 @@ /obj/item/ammo_box/magazine/toy/pistol name = "foam force pistol magazine" icon_state = "9x19p" - max_ammo = 8 + max_ammo = 12 multiple_sprites = AMMO_BOX_FULL_EMPTY /obj/item/ammo_box/magazine/toy/pistol/riot diff --git a/code/modules/projectiles/guns/ballistic/toy.dm b/code/modules/projectiles/guns/ballistic/toy.dm index bd84e5f794188..dae77b0936833 100644 --- a/code/modules/projectiles/guns/ballistic/toy.dm +++ b/code/modules/projectiles/guns/ballistic/toy.dm @@ -17,6 +17,7 @@ /obj/item/gun/ballistic/automatic/toy/riot spawn_magazine_type = /obj/item/ammo_box/magazine/toy/smg/riot + /obj/item/gun/ballistic/automatic/pistol/toy name = "foam force pistol" desc = "A small, easily concealable toy handgun. Ages 8 and up." @@ -31,6 +32,9 @@ magazine = new /obj/item/ammo_box/magazine/toy/pistol/riot(src) return ..() +/obj/item/gun/ballistic/automatic/pistol/toy/riot/clandestine + projectile_damage_multiplier = 1.4 + /obj/item/gun/ballistic/shotgun/toy name = "foam force shotgun" desc = "A toy shotgun with wood furniture and a four-shell capacity underneath. Ages 8 and up." diff --git a/code/modules/uplink/uplink_items/ammunition.dm b/code/modules/uplink/uplink_items/ammunition.dm index 2276485a2b7b5..7000fb7e9a766 100644 --- a/code/modules/uplink/uplink_items/ammunition.dm +++ b/code/modules/uplink/uplink_items/ammunition.dm @@ -7,19 +7,19 @@ surplus = 40 /datum/uplink_item/ammo/toydarts - name = "Box of Riot Darts" - desc = "A box of 40 Donksoft riot darts, for reloading any compatible foam dart magazine. Don't forget to share!" - item = /obj/item/ammo_box/foambox/riot + name = "Donksoft Riot Pistol Ammunition Case" + desc = "A case containing three spare magazines for the Donksoft riot pistol, along with a box of loose riot darts." + item = /obj/item/storage/toolbox/guncase/traitor/ammunition/donksoft cost = 2 - surplus = 0 uplink_item_flags = SYNDIE_TRIPS_CONTRABAND purchasable_from = ~UPLINK_SERIOUS_OPS /datum/uplink_item/ammo/pistol - name = "9mm Handgun Magazine" - desc = "An additional 8-round 9mm magazine, compatible with the Makarov pistol." - item = /obj/item/ammo_box/magazine/m9mm - cost = 1 + name = "9mm Magazine Case" + desc = "A case containing three additional 8-round 9mm magazines, compatible with the Makarov pistol, as well as \ + a box of loose 9mm ammunition." + item = /obj/item/storage/toolbox/guncase/traitor/ammunition + cost = 2 purchasable_from = ~UPLINK_ALL_SYNDIE_OPS uplink_item_flags = SYNDIE_TRIPS_CONTRABAND diff --git a/code/modules/uplink/uplink_items/dangerous.dm b/code/modules/uplink/uplink_items/dangerous.dm index 0c86d8731e00b..092ec4c384782 100644 --- a/code/modules/uplink/uplink_items/dangerous.dm +++ b/code/modules/uplink/uplink_items/dangerous.dm @@ -7,19 +7,22 @@ category = /datum/uplink_category/dangerous /datum/uplink_item/dangerous/foampistol - name = "Toy Pistol with Riot Darts" - desc = "An innocent-looking toy pistol designed to fire foam darts. Comes loaded with riot-grade \ - darts effective at incapacitating a target." - item = /obj/item/gun/ballistic/automatic/pistol/toy/riot - cost = 2 + name = "Donksoft Riot Pistol Case" + desc = "A case containing an innocent-looking toy pistol designed to fire foam darts at higher than normal velocity. \ + Comes loaded with riot-grade darts effective at incapacitating a target, two spare magazines and a box of loose \ + riot darts. Perfect for nonlethal takedowns at range, as well as deniability. While not included in the kit, the \ + pistol is compatible with suppressors, which can be purchased separately." + item = /obj/item/storage/toolbox/guncase/traitor/donksoft + cost = 6 surplus = 10 purchasable_from = ~UPLINK_SERIOUS_OPS /datum/uplink_item/dangerous/pistol - name = "Makarov Pistol" - desc = "A small, easily concealable handgun that uses 9mm auto rounds in 8-round magazines and is compatible \ - with suppressors." - item = /obj/item/gun/ballistic/automatic/pistol + name = "Makarov Pistol Case" + desc = "A weapon case containing an unknown variant of the Makarov pistol, along with two spare magazines and a box of loose 9mm ammunition. \ + Chambered in 9mm. Perfect for frequent skirmishes with security, as well as ensuring you have enough firepower to outlast the competition. \ + While not included in the kit, the pistol is compatible with suppressors, which can be purchased seperately." + item = /obj/item/storage/toolbox/guncase/traitor cost = 7 purchasable_from = ~UPLINK_ALL_SYNDIE_OPS diff --git a/icons/obj/storage/case.dmi b/icons/obj/storage/case.dmi index 94b7251f93f46c385563b007d6bc1b2609f4c234..65b40a403ea2e83307bbffd2b3dd66bd0e6141bf 100644 GIT binary patch delta 2184 zcmV;32zU3t5b6?;7Y@J(0{{R3WdfWv0003jktNMGEG;omQ(CvTw~dXBqYe|`;NVM3 zOZyf8udlDZ-RU#`0BUY^)YQ~)aBv(PA5&9PJU&F$*4C3K0Um!yN>ISSz=b$J-3btt zNKZ=7X4e1!00DGTPE!Ct=GbNc00A0$R9JLGWpiV4X>fFDZ*Bkpc$}5g!ES>v3;@tG zd4Bt?f!l+)^VyZ$|Z@OoEYqEllQ~N@s#X8kMS2e zPhyj|O9nwF%S02M9P!H@PFM3h=mzqmrB-_MYOXa?s*5|Cb(% z-f3CJQZ!gD>XGGgxnr$VN6Z&xPQmrolC2fT&v;GTerqY-ur6~>GFG+~88j_|D z=KueP&TW5}ot@j_E-tj^<#~2y{E$80nVCh;I1T^+0FWAU1cQzU3G_)bmm}yMpgchz zp-p!Rl!@px_qiNSAaF zB1tj1;t?84cU(-NxI_}TR4kBDy880+^87OT65)TAT<38s=qoELiz~=QxFy#`+>WKY z#bUWwSSl=GaDxX$H%;AHB}p&&kin{y;f1`nM>KpxZycSgBMN^R$nYI(;d0x@tuVY3j~uHHu;h zE#!Y)Y`~=}M74ylPG1V$y(8hKsmC*rm4p?5JqJ{ue*23-;|9R zE`Mv$D<*yN2JV)>D;qOh{@$WjO!_U%0A2#d3@6iXVFo1JG<9dS6eVIH9|eu2Py8vw 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 Vy7e9Q`v3p{07(Z$PDHLkV1k)bs_6g# From 8dba72fb5d7c63513f317f8dc3cda15c5cb23aab Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 22 Dec 2024 01:40:21 +0000 Subject: [PATCH 058/235] Automatic changelog for PR #88482 [ci skip] --- html/changelogs/AutoChangeLog-pr-88482.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88482.yml diff --git a/html/changelogs/AutoChangeLog-pr-88482.yml b/html/changelogs/AutoChangeLog-pr-88482.yml new file mode 100644 index 0000000000000..f94bf1b4da1fa --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88482.yml @@ -0,0 +1,8 @@ +author: "NecromancerAnne (code), SmArtKar (sprites)" +delete-after: True +changes: + - balance: "Makarovs and Toy Pistols come in weapon cases. Complete with spare ammo." + - balance: "Basic ammo for either weapon comes in weapon cases of three extra magazines at an affordable price." + - balance: "Donksoft Toy Pistols from the uplink are much stronger than their standard counterparts, but now priced at 6 TC." + - balance: "Makarovs and Toy pistols have a magazine capacity of 12 rounds." + - balance: "Gun/Ammo cases from the traitor uplink can be destroyed by activating the disposal bomb. Press Alt-Right-Click on the case to start the timer." \ No newline at end of file From 1116df9b98bca17653807f24ba4ba1863b4fb3d9 Mon Sep 17 00:00:00 2001 From: Aylong <69762909+AyIong@users.noreply.github.com> Date: Sun, 22 Dec 2024 03:42:28 +0200 Subject: [PATCH 059/235] [NO GBP] Fix 516 scrollbar and background image (#88617) ## About The Pull Request Ports - https://github.com/ParadiseSS13/Paradise/pull/26433 Fixes scrollbar color for Byond 516, and background position ## Why It's Good For The Game Colorfull scrollbar
Videos https://github.com/user-attachments/assets/969b4f6f-f553-4786-86c7-df11c034675b
## Changelog :cl: fix: Fixed scrollbar colors and background position in TGUI on Byond 516 /:cl: --- html/statbrowser.css | 23 ++++++++++++++++--- html/statbrowser.js | 2 ++ tgui/packages/tgui/layouts/Layout.tsx | 1 + tgui/packages/tgui/styles/layouts/Layout.scss | 18 ++++++++++++++- tgui/packages/tgui/styles/main.scss | 2 +- 5 files changed, 41 insertions(+), 5 deletions(-) diff --git a/html/statbrowser.css b/html/statbrowser.css index d8c0f92b626f4..d49cb3d6e2667 100644 --- a/html/statbrowser.css +++ b/html/statbrowser.css @@ -1,3 +1,13 @@ +.light:root { + --scrollbar-base: #f2f2f2; + --scrollbar-thumb: #a7a7a7; +} + +html, +body { + scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-base); +} + body { font-family: Verdana, Geneva, Tahoma, sans-serif; font-size: 12px; @@ -177,9 +187,16 @@ img { margin-bottom: 1em; } -/* Dark theme colors */ +/** + * MARK: Dark theme colors + */ +.dark:root { + --scrollbar-base: #151515; + --scrollbar-thumb: #363636; +} + body.dark { - background-color: #131313; + background-color: #151515; color: #b2c4dd; scrollbar-base-color: #1c1c1c; scrollbar-face-color: #3b3b3b; @@ -201,7 +218,7 @@ body.dark { } .dark #menu { - background-color: #131313; + background-color: #151515; } .dark #menu.tabs-classic .button.active { diff --git a/html/statbrowser.js b/html/statbrowser.js index 3fe115943a702..d33713f65f746 100644 --- a/html/statbrowser.js +++ b/html/statbrowser.js @@ -705,9 +705,11 @@ function draw_verbs(cat) { function set_theme(which) { if (which == "light") { document.body.className = ""; + document.documentElement.className = 'light'; set_style_sheet("browserOutput_white"); } else if (which == "dark") { document.body.className = "dark"; + document.documentElement.className = 'dark'; set_style_sheet("browserOutput"); } } diff --git a/tgui/packages/tgui/layouts/Layout.tsx b/tgui/packages/tgui/layouts/Layout.tsx index 173ed1cbb432d..f3f395e540eca 100644 --- a/tgui/packages/tgui/layouts/Layout.tsx +++ b/tgui/packages/tgui/layouts/Layout.tsx @@ -21,6 +21,7 @@ type Props = Partial<{ export function Layout(props: Props) { const { className, theme = 'nanotrasen', children, ...rest } = props; + document.documentElement.className = `theme-${theme}`; return (
diff --git a/tgui/packages/tgui/styles/layouts/Layout.scss b/tgui/packages/tgui/styles/layouts/Layout.scss index ecf750ecc0740..745112eab47c8 100644 --- a/tgui/packages/tgui/styles/layouts/Layout.scss +++ b/tgui/packages/tgui/styles/layouts/Layout.scss @@ -5,12 +5,28 @@ @use 'sass:color'; @use '../base'; +@use '../functions.scss' as *; +$luminance: luminance(base.$color-bg); $scrollbar-color-multiplier: 1 !default; +$scrollbar-base: color.scale( + base.$color-bg, + $lightness: -33% * $scrollbar-color-multiplier +); +$scrollbar-face: color.scale( + base.$color-bg, + $lightness: if($luminance > 0.05, 30%, 10%) * $scrollbar-color-multiplier +); +// Fancy scrollbar +html, +body { + scrollbar-color: $scrollbar-face $scrollbar-base; +} + +// Remove with 516, IE legacy code .Layout, .Layout * { - // Fancy scrollbar scrollbar-base-color: color.scale( base.$color-bg, $lightness: -25% * $scrollbar-color-multiplier diff --git a/tgui/packages/tgui/styles/main.scss b/tgui/packages/tgui/styles/main.scss index 64c54a9debf2a..a00d7cd973461 100644 --- a/tgui/packages/tgui/styles/main.scss +++ b/tgui/packages/tgui/styles/main.scss @@ -66,7 +66,7 @@ // NT Theme .Layout__content { background-image: url('../assets/bg-nanotrasen.svg'); - background-size: 70%; + background-size: 70% 70%; background-position: center; background-repeat: no-repeat; } From f20661e44ea9a8ebdc24e1ba5d2202622128a99a Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 22 Dec 2024 01:42:49 +0000 Subject: [PATCH 060/235] Automatic changelog for PR #88617 [ci skip] --- html/changelogs/AutoChangeLog-pr-88617.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88617.yml diff --git a/html/changelogs/AutoChangeLog-pr-88617.yml b/html/changelogs/AutoChangeLog-pr-88617.yml new file mode 100644 index 0000000000000..530ff693e1eda --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88617.yml @@ -0,0 +1,4 @@ +author: "AyIong" +delete-after: True +changes: + - bugfix: "Fixed scrollbar colors and background position in TGUI on Byond 516" \ No newline at end of file From 4eab0a91d0b738aa8c90308942bb7e2226c58cb7 Mon Sep 17 00:00:00 2001 From: Ivory Date: Sun, 22 Dec 2024 02:43:05 +0100 Subject: [PATCH 061/235] [no gbp] Fixes an erroneous moved call (#88512) ![image](https://github.com/user-attachments/assets/e86f90b6-b02f-4447-8e1c-5e8bd6004382) ![image](https://github.com/user-attachments/assets/d83eba4a-b5e0-4406-8476-3e1d4b82a516) ![image](https://github.com/user-attachments/assets/6544874a-47f5-44f8-9cb4-6b5fafa5683c) oops --- code/game/atoms_movable.dm | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 7c2840afde19a..e6fe68ea4c487 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -1137,7 +1137,6 @@ pulledby.stop_pulling() var/same_loc = oldloc == destination - var/movement_successful = TRUE var/area/old_area = get_area(oldloc) var/area/destarea = get_area(destination) var/movement_dir = get_dir(src, destination) @@ -1146,13 +1145,13 @@ loc = destination - if(!same_loc && loc == oldloc) - // when attempting to move an atom A into an atom B which already contains A, BYOND seems - // to silently refuse to move A to the new loc. This can really break stuff (see #77067) - stack_trace("Attempt to move [src] to [destination] was rejected by BYOND, possibly due to cyclic contents") - movement_successful = FALSE + if(!same_loc) + if(loc == oldloc) + // when attempting to move an atom A into an atom B which already contains A, BYOND seems + // to silently refuse to move A to the new loc. This can really break stuff (see #77067) + stack_trace("Attempt to move [src] to [destination] was rejected by BYOND, possibly due to cyclic contents") + return FALSE - if(movement_successful && !same_loc) if(is_multi_tile && isturf(destination)) var/list/new_locs = block( destination, @@ -1181,7 +1180,7 @@ if(destarea && old_area != destarea) destarea.Entered(src, old_area) - . = movement_successful + . = TRUE //If no destination, move the atom into nullspace (don't do this unless you know what you're doing) else From 165be8f3b02f82bba5b8ad63d39b92009712511b Mon Sep 17 00:00:00 2001 From: DATA <44149906+DATA-xPUNGED@users.noreply.github.com> Date: Sat, 21 Dec 2024 22:56:01 -0300 Subject: [PATCH 062/235] Removes the sec record computer from the icemoons agent (#88545) ## About The Pull Request it is gone, no more record deletion. ## Why It's Good For The Game i made this map but i'm pretty sure i didn't add this computer there, having the opwer to delete records just completely fucks over sec and isn't fun to go against ## Changelog :cl: map: removed the sec record computer from the Icemoon Listening Post. /:cl: --- _maps/RandomRuins/IceRuins/icemoon_underground_comms_agent.dmm | 3 --- 1 file changed, 3 deletions(-) diff --git a/_maps/RandomRuins/IceRuins/icemoon_underground_comms_agent.dmm b/_maps/RandomRuins/IceRuins/icemoon_underground_comms_agent.dmm index d5c344e9cd31a..3553672b1080c 100644 --- a/_maps/RandomRuins/IceRuins/icemoon_underground_comms_agent.dmm +++ b/_maps/RandomRuins/IceRuins/icemoon_underground_comms_agent.dmm @@ -993,9 +993,6 @@ /area/ruin/comms_agent) "UI" = ( /obj/structure/table/reinforced, -/obj/machinery/computer/records/security/laptop/syndie{ - dir = 1 - }, /obj/item/paper/monitorkey{ pixel_x = -15; pixel_y = 7 From d39dd980b860c7f11f91c166b5c12434a1adce5f Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 22 Dec 2024 01:56:20 +0000 Subject: [PATCH 063/235] Automatic changelog for PR #88545 [ci skip] --- html/changelogs/AutoChangeLog-pr-88545.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88545.yml diff --git a/html/changelogs/AutoChangeLog-pr-88545.yml b/html/changelogs/AutoChangeLog-pr-88545.yml new file mode 100644 index 0000000000000..971d526e305de --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88545.yml @@ -0,0 +1,4 @@ +author: "DATA-xPUNGED" +delete-after: True +changes: + - map: "removed the sec record computer from the Icemoon Listening Post." \ No newline at end of file From efe62c5a728a5135d5d92a058fbcb57ffffa90f3 Mon Sep 17 00:00:00 2001 From: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com> Date: Sun, 22 Dec 2024 04:05:30 +0200 Subject: [PATCH 064/235] Pet Commands QOL . makes pet commands easier to use (#88495) ## About The Pull Request this PR improves the UX of pet commands a bit. i decided to expand on their radial menu. You can now hold shift and hover over your pet to display a menu of commands which you can choose from. alternatively, you can still type out commands in chat https://github.com/user-attachments/assets/9da7f7ea-58a3-4fd6-b040-45cc05cda51d ## Why It's Good For The Game makes pet commands easier to give out when you're managing more than 1 pet. also fixes the fishing command not working. ## Changelog :cl: qol: holding shift and hovering over your pet will display a list of commands you can click from fix: fixes the fishing pet command not working /:cl: --- .../signals_atom/signals_atom_x_act.dm | 3 + code/__DEFINES/keybinding.dm | 4 + code/__DEFINES/radial_defines.dm | 6 + code/_onclick/hud/radial.dm | 55 +++++--- code/_onclick/hud/radial_persistent.dm | 2 +- code/datums/components/callouts.dm | 2 +- code/datums/components/pet_commands/fetch.dm | 23 +-- .../components/pet_commands/obeys_commands.dm | 82 ++++++++--- .../components/pet_commands/pet_command.dm | 131 ++++++++++-------- .../pet_commands/pet_commands_basic.dm | 109 ++++++++++----- code/datums/elements/pet_cult.dm | 3 +- code/datums/keybinding/_keybindings.dm | 2 + code/datums/keybinding/living.dm | 3 + code/datums/keybinding/mob.dm | 7 + code/game/atom/_atom.dm | 2 + .../heretic/knowledge/cosmic_lore.dm | 4 +- .../living/basic/bots/cleanbot/cleanbot.dm | 2 +- .../living/basic/bots/cleanbot/cleanbot_ai.dm | 7 +- .../mob/living/basic/farm_animals/bee/_bee.dm | 2 +- .../basic/farm_animals/bee/bee_ai_behavior.dm | 9 +- .../mob/living/basic/heretic/star_gazer.dm | 2 +- .../mob/living/basic/icemoon/wolf/wolf.dm | 5 +- .../mob/living/basic/jungle/leaper/leaper.dm | 7 +- .../living/basic/jungle/leaper/leaper_ai.dm | 12 +- .../living/basic/jungle/seedling/seedling.dm | 6 +- .../basic/jungle/seedling/seedling_ai.dm | 11 +- .../basic/lavaland/goldgrub/goldgrub.dm | 2 +- .../basic/lavaland/goldgrub/goldgrub_ai.dm | 5 + .../basic/lavaland/gutlunchers/gutlunchers.dm | 9 +- .../lavaland/gutlunchers/gutlunchers_ai.dm | 10 +- .../basic/lavaland/lobstrosity/lobstrosity.dm | 15 +- .../mob/living/basic/lavaland/mook/mook.dm | 4 +- .../mob/living/basic/lavaland/mook/mook_ai.dm | 8 +- .../living/basic/lavaland/raptor/_raptor.dm | 8 +- .../mob/living/basic/minebots/minebot.dm | 3 +- .../mob/living/basic/minebots/minebot_ai.dm | 16 ++- .../modules/mob/living/basic/pets/dog/_dog.dm | 9 +- code/modules/mob/living/basic/pets/fox.dm | 3 +- .../mob/living/basic/pets/orbie/orbie.dm | 3 +- .../mob/living/basic/pets/orbie/orbie_ai.dm | 18 ++- .../living/basic/pets/pet_cult/pet_cult_ai.dm | 3 + .../mob/living/basic/slime/ai/pet_command.dm | 4 +- code/modules/mob/living/basic/slime/slime.dm | 2 +- .../mob/living/basic/space_fauna/carp/carp.dm | 2 +- .../basic/space_fauna/carp/carp_ai_actions.dm | 2 +- .../living/basic/space_fauna/carp/magicarp.dm | 4 +- .../regal_rat/regal_rat_actions.dm | 8 +- icons/effects/mouse_pointers/pet_paw.dmi | Bin 0 -> 372 bytes icons/hud/radial_pets.dmi | Bin 0 -> 3317 bytes tgstation.dme | 1 + 50 files changed, 423 insertions(+), 217 deletions(-) create mode 100644 code/__DEFINES/radial_defines.dm create mode 100644 icons/effects/mouse_pointers/pet_paw.dmi create mode 100644 icons/hud/radial_pets.dmi diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm index bedfaf2fa0374..bb5b344a89a48 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm @@ -92,3 +92,6 @@ /// from /obj/projectile/energy/fisher/on_hit() or /obj/item/gun/energy/recharge/fisher when striking a target #define COMSIG_ATOM_SABOTEUR_ACT "hit_by_saboteur" #define COMSIG_SABOTEUR_SUCCESS 1 + +/// signal sent when a mouse is hovering over us, sent by atom/proc/on_mouse_entered +#define COMSIG_ATOM_MOUSE_ENTERED "mouse_entered" diff --git a/code/__DEFINES/keybinding.dm b/code/__DEFINES/keybinding.dm index 5f025ad99cffb..8ae95933e646b 100644 --- a/code/__DEFINES/keybinding.dm +++ b/code/__DEFINES/keybinding.dm @@ -4,6 +4,9 @@ #define COMSIG_KB_ACTIVATED (1<<0) #define COMSIG_KB_EMOTE "keybinding_emote_down" +///Signal sent when a keybind is deactivated +#define DEACTIVATE_KEYBIND(A) "[A]_DEACTIVATED" + //Admin #define COMSIG_KB_ADMIN_ASAY_DOWN "keybinding_admin_asay_down" #define COMSIG_KB_ADMIN_DSAY_DOWN "keybinding_admin_dsay_down" @@ -54,6 +57,7 @@ #define COMSIG_KB_LIVING_DISABLE_COMBAT_DOWN "keybinding_living_disable_combat_down" #define COMSIG_KB_LIVING_TOGGLEMOVEINTENT_DOWN "keybinding_mob_togglemoveintent_down" #define COMSIG_KB_LIVING_TOGGLEMOVEINTENTALT_DOWN "keybinding_mob_togglemoveintentalt_down" +#define COMSIG_KB_LIVING_VIEW_PET_COMMANDS "keybinding_living_view_pet_commands" //Mob #define COMSIG_KB_MOB_FACENORTH_DOWN "keybinding_mob_facenorth_down" diff --git a/code/__DEFINES/radial_defines.dm b/code/__DEFINES/radial_defines.dm new file mode 100644 index 0000000000000..35ae6eebb5984 --- /dev/null +++ b/code/__DEFINES/radial_defines.dm @@ -0,0 +1,6 @@ +#define NEXT_PAGE_ID "__next__" +#define DEFAULT_CHECK_DELAY 2 SECONDS + +#define BUTTON_SLIDE_IN (1<<0) +#define BUTTON_FADE_IN (1<<1) +#define BUTTON_FADE_OUT (1<<2) diff --git a/code/_onclick/hud/radial.dm b/code/_onclick/hud/radial.dm index ab95d3bb392eb..041a16b12a132 100644 --- a/code/_onclick/hud/radial.dm +++ b/code/_onclick/hud/radial.dm @@ -1,6 +1,3 @@ -#define NEXT_PAGE_ID "__next__" -#define DEFAULT_CHECK_DELAY 20 - GLOBAL_LIST_EMPTY(radial_menus) /atom/movable/screen/radial @@ -113,7 +110,7 @@ GLOBAL_LIST_EMPTY(radial_menus) var/hudfix_method = TRUE //TRUE to change anchor to user, FALSE to shift by py_shift var/py_shift = 0 - var/entry_animation = TRUE + var/button_animation_flags = BUTTON_SLIDE_IN ///A replacement icon state for the generic radial slice bg icon. Doesn't affect the next page nor the center buttons var/radial_slice_icon @@ -163,6 +160,8 @@ GLOBAL_LIST_EMPTY(radial_menus) var/atom/movable/screen/radial/slice/new_element = new /atom/movable/screen/radial/slice new_element.tooltips = use_tooltips new_element.set_parent(src) + if(button_animation_flags & BUTTON_FADE_IN) + new_element.alpha = 0 elements += new_element var/page = 1 @@ -186,9 +185,9 @@ GLOBAL_LIST_EMPTY(radial_menus) page_data[page] = current pages = page current_page = clamp(set_page, 1, pages) - update_screen_objects(entry_animation, click_on_hover) + update_screen_objects(button_animation_flags, click_on_hover) -/datum/radial_menu/proc/update_screen_objects(anim = FALSE, click_on_hover = FALSE) +/datum/radial_menu/proc/update_screen_objects(anim_flag = NONE, click_on_hover = FALSE) var/list/page_choices = page_data[current_page] var/angle_per_element = round(zone / page_choices.len) for(var/i in 1 to elements.len) @@ -198,11 +197,11 @@ GLOBAL_LIST_EMPTY(radial_menus) HideElement(element) element.click_on_hover = FALSE else - SetElement(element,page_choices[i],angle,anim = anim,anim_order = i) + SetElement(element,page_choices[i],angle,anim_flag = anim_flag,anim_order = i) // Only activate click on hover after the animation plays if (!click_on_hover) continue - if (anim) + if (anim_flag) addtimer(VARSET_CALLBACK(element, click_on_hover, TRUE), i * 0.5) else element.click_on_hover = TRUE @@ -217,11 +216,11 @@ GLOBAL_LIST_EMPTY(radial_menus) E.choice = null E.next_page = FALSE -/datum/radial_menu/proc/SetElement(atom/movable/screen/radial/slice/E,choice_id,angle,anim,anim_order) +/datum/radial_menu/proc/SetElement(atom/movable/screen/radial/slice/E, choice_id, angle, anim_flag, anim_order) //Position var/py = round(cos(angle) * radius) + py_shift var/px = round(sin(angle) * radius) - if(anim) + if(anim_flag & BUTTON_SLIDE_IN) var/timing = anim_order * 0.5 var/matrix/starting = matrix() starting.Scale(0.1,0.1) @@ -232,8 +231,11 @@ GLOBAL_LIST_EMPTY(radial_menus) E.pixel_y = py E.pixel_x = px - //Visuals - E.alpha = 255 + if(anim_flag & BUTTON_FADE_IN) + animate(E, alpha = 255, time = 0.15 SECONDS, easing = EASE_OUT) + else + E.alpha = 255 + E.mouse_opacity = MOUSE_OPACITY_ICON E.cut_overlays() E.vis_contents.Cut() @@ -266,7 +268,9 @@ GLOBAL_LIST_EMPTY(radial_menus) info_button.layer = RADIAL_CONTENT_LAYER E.vis_contents += info_button -/datum/radial_menu/New() +/datum/radial_menu/New(display_close_button) + if(!display_close_button) + return close_button = new close_button.set_parent(src) @@ -327,7 +331,9 @@ GLOBAL_LIST_EMPTY(radial_menus) menu_holder = image(icon='icons/effects/effects.dmi',loc=anchor,icon_state="nothing", layer = RADIAL_BACKGROUND_LAYER, pixel_x = offset_x, pixel_y = offset_y) SET_PLANE_EXPLICIT(menu_holder, ABOVE_HUD_PLANE, M) menu_holder.appearance_flags |= KEEP_APART|RESET_ALPHA|RESET_COLOR|RESET_TRANSFORM - menu_holder.vis_contents += elements + close_button + menu_holder.vis_contents += elements + if(!isnull(close_button)) + menu_holder.vis_contents += close_button current_user.images += menu_holder /datum/radial_menu/proc/hide() @@ -345,6 +351,14 @@ GLOBAL_LIST_EMPTY(radial_menus) next_check = world.time + check_delay stoplag(1) +/datum/radial_menu/proc/remove_menu() + if(!(button_animation_flags & BUTTON_FADE_OUT)) + qdel(src) + return + for(var/atom/movable/element as anything in elements) + animate(element, alpha = 0, time = 0.15 SECONDS) + QDEL_IN(src, 0.5 SECONDS) + /datum/radial_menu/Destroy() Reset() hide() @@ -356,11 +370,11 @@ GLOBAL_LIST_EMPTY(radial_menus) Choices should be a list where list keys are movables or text used for element names and return value and list values are movables/icons/images used for element icons */ -/proc/show_radial_menu(mob/user, atom/anchor, list/choices, uniqueid, radius, datum/callback/custom_check, require_near = FALSE, tooltips = FALSE, no_repeat_close = FALSE, radial_slice_icon = "radial_slice", autopick_single_option = TRUE, entry_animation = TRUE, click_on_hover = FALSE, user_space = FALSE) +/proc/show_radial_menu(mob/user, atom/anchor, list/choices, uniqueid, radius, datum/callback/custom_check, require_near = FALSE, tooltips = FALSE, no_repeat_close = FALSE, radial_slice_icon = "radial_slice", autopick_single_option = TRUE, button_animation_flags = BUTTON_SLIDE_IN, click_on_hover = FALSE, user_space = FALSE, check_delay = DEFAULT_CHECK_DELAY, display_close_button = TRUE, radial_menu_offset = list(0, 0)) if(!user || !anchor || !length(choices)) return - if(length(choices)==1 && autopick_single_option) + if(length(choices) == 1 && autopick_single_option) return choices[1] if(!uniqueid) @@ -372,8 +386,9 @@ GLOBAL_LIST_EMPTY(radial_menus) menu.finished = TRUE return - var/datum/radial_menu/menu = new - menu.entry_animation = entry_animation + var/datum/radial_menu/menu = new(display_close_button) + menu.button_animation_flags = button_animation_flags + menu.check_delay = check_delay GLOB.radial_menus[uniqueid] = menu if(radius) menu.radius = radius @@ -390,10 +405,12 @@ GLOBAL_LIST_EMPTY(radial_menus) var/turf/anchor_turf = get_turf(anchor) offset_x = (anchor_turf.x - user_turf.x) * ICON_SIZE_X + anchor.pixel_x - user.pixel_x offset_y = (anchor_turf.y - user_turf.y) * ICON_SIZE_Y + anchor.pixel_y - user.pixel_y + offset_x += radial_menu_offset[1] + offset_y += radial_menu_offset[2] menu.show_to(user, offset_x, offset_y) menu.wait(user, anchor, require_near) var/answer = menu.selected_choice - qdel(menu) + menu.remove_menu() GLOB.radial_menus -= uniqueid if(require_near && !in_range(anchor, user)) return diff --git a/code/_onclick/hud/radial_persistent.dm b/code/_onclick/hud/radial_persistent.dm index 5fe81d005bd43..d48c8d9eb456c 100644 --- a/code/_onclick/hud/radial_persistent.dm +++ b/code/_onclick/hud/radial_persistent.dm @@ -43,7 +43,7 @@ /datum/radial_menu/persistent/proc/change_choices(list/newchoices, tooltips = FALSE, animate = FALSE, keep_same_page = FALSE) if(!newchoices.len) return - entry_animation = FALSE + button_animation_flags = NONE var/target_page = keep_same_page ? current_page : 1 //Stores the current_page value before it's set back to 1 on Reset() Reset() set_choices(newchoices,tooltips, set_page = target_page) diff --git a/code/datums/components/callouts.dm b/code/datums/components/callouts.dm index 52a3e007905c3..854f769f8a10a 100644 --- a/code/datums/components/callouts.dm +++ b/code/datums/components/callouts.dm @@ -111,7 +111,7 @@ for(var/datum/callout_option/callout_option as anything in callout_options) callout_items[callout_option] = image(icon = 'icons/hud/radial.dmi', icon_state = callout_option::icon_state) - var/datum/callout_option/selection = show_radial_menu(user, get_turf(clicked_atom), callout_items, entry_animation = FALSE, click_on_hover = TRUE, user_space = TRUE) + var/datum/callout_option/selection = show_radial_menu(user, get_turf(clicked_atom), callout_items, button_animation_flags = NONE, click_on_hover = TRUE, user_space = TRUE) if (!selection) return diff --git a/code/datums/components/pet_commands/fetch.dm b/code/datums/components/pet_commands/fetch.dm index 9a42c485d5c06..143ac9ca1014c 100644 --- a/code/datums/components/pet_commands/fetch.dm +++ b/code/datums/components/pet_commands/fetch.dm @@ -3,11 +3,11 @@ * Watch for someone throwing or pointing at something and then go get it and bring it back. * If it's food we might eat it instead. */ -/datum/pet_command/point_targeting/fetch +/datum/pet_command/fetch command_name = "Fetch" command_desc = "Command your pet to retrieve something you throw or point at." - radial_icon = 'icons/mob/actions/actions_spells.dmi' - radial_icon_state = "summons" + radial_icon_state = "fetch" + requires_pointing = TRUE speech_commands = list("fetch") command_feedback = "bounces" pointed_reaction = "with great interest" @@ -16,22 +16,25 @@ /// If true, this is a poorly trained pet who will eat food you throw instead of bringing it back var/will_eat_targets = TRUE -/datum/pet_command/point_targeting/fetch/New(mob/living/parent) +/datum/pet_command/fetch/New(mob/living/parent) . = ..() if(isnull(parent)) return parent.AddElement(/datum/element/ai_held_item) // We don't remove this on destroy because they might still be holding something -/datum/pet_command/point_targeting/fetch/add_new_friend(mob/living/tamer) +/datum/pet_command/fetch/add_new_friend(mob/living/tamer) . = ..() RegisterSignal(tamer, COMSIG_MOB_THROW, PROC_REF(listened_throw)) -/datum/pet_command/point_targeting/fetch/remove_friend(mob/living/unfriended) +/datum/pet_command/fetch/remove_friend(mob/living/unfriended) . = ..() UnregisterSignal(unfriended, COMSIG_MOB_THROW) +/datum/pet_command/fetch/retrieve_command_text(atom/living_pet, atom/target) + return isnull(target) ? null : "signals [living_pet] to fetch [target]!" + /// A friend has thrown something, if we're listening or at least not busy then go get it -/datum/pet_command/point_targeting/fetch/proc/listened_throw(mob/living/carbon/thrower) +/datum/pet_command/fetch/proc/listened_throw(mob/living/carbon/thrower) SIGNAL_HANDLER var/mob/living/parent = weak_parent.resolve() @@ -57,7 +60,7 @@ RegisterSignal(thrown_thing, COMSIG_MOVABLE_THROW_LANDED, PROC_REF(listen_throw_land)) /// A throw we were listening to has finished, see if it's in range for us to try grabbing it -/datum/pet_command/point_targeting/fetch/proc/listen_throw_land(obj/item/thrown_thing, datum/thrownthing/throwingdatum) +/datum/pet_command/fetch/proc/listen_throw_land(obj/item/thrown_thing, datum/thrownthing/throwingdatum) SIGNAL_HANDLER UnregisterSignal(thrown_thing, COMSIG_MOVABLE_THROW_LANDED) @@ -76,7 +79,7 @@ parent.ai_controller.set_blackboard_key(BB_FETCH_DELIVER_TO, thrower) // Don't try and fetch turfs or anchored objects if someone points at them -/datum/pet_command/point_targeting/fetch/look_for_target(mob/living/pointing_friend, obj/item/pointed_atom) +/datum/pet_command/fetch/look_for_target(mob/living/pointing_friend, obj/item/pointed_atom) if (!istype(pointed_atom)) return FALSE if (pointed_atom.anchored) @@ -89,7 +92,7 @@ parent.ai_controller.set_blackboard_key(BB_FETCH_DELIVER_TO, pointing_friend) // Finally, plan our actions -/datum/pet_command/point_targeting/fetch/execute_action(datum/ai_controller/controller) +/datum/pet_command/fetch/execute_action(datum/ai_controller/controller) controller.queue_behavior(/datum/ai_behavior/forget_failed_fetches) var/atom/target = controller.blackboard[BB_CURRENT_PET_TARGET] diff --git a/code/datums/components/pet_commands/obeys_commands.dm b/code/datums/components/pet_commands/obeys_commands.dm index 8aaa7e7179294..4a68574d6e08c 100644 --- a/code/datums/components/pet_commands/obeys_commands.dm +++ b/code/datums/components/pet_commands/obeys_commands.dm @@ -3,12 +3,23 @@ * Manages a list of pet command datums, allowing you to boss it around * Creates a radial menu of pet commands when this creature is alt-clicked, if it has any */ +#define DEFAULT_RADIAL_VIEWING_DISTANCE 9 /datum/component/obeys_commands /// List of commands you can give to the owner of this component var/list/available_commands = list() + ///Users currently viewing our radial options + var/list/radial_viewers = list() + ///radius of our radial menu + var/radial_menu_radius = 48 + ///after how long we shutdown radial menus + var/radial_menu_lifetime = 7 SECONDS + ///offset to display the radial menu + var/list/radial_menu_offset + ///should the commands move with the pet owner's screen? + var/radial_relative_to_user /// The available_commands parameter should be passed as a list of typepaths -/datum/component/obeys_commands/Initialize(list/command_typepaths = list()) +/datum/component/obeys_commands/Initialize(list/command_typepaths = list(), list/radial_menu_offset = list(0, 0), radial_relative_to_user = FALSE) . = ..() if (!isliving(parent)) return COMPONENT_INCOMPATIBLE @@ -17,7 +28,8 @@ return COMPONENT_INCOMPATIBLE if (!length(command_typepaths)) CRASH("Initialised obedience component with no commands.") - + src.radial_menu_offset = radial_menu_offset + src.radial_relative_to_user = radial_relative_to_user for (var/command_path in command_typepaths) var/datum/pet_command/new_command = new command_path(parent) available_commands[new_command.command_name] = new_command @@ -30,7 +42,6 @@ RegisterSignal(parent, COMSIG_LIVING_BEFRIENDED, PROC_REF(add_friend)) RegisterSignal(parent, COMSIG_LIVING_UNFRIENDED, PROC_REF(remove_friend)) RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) - RegisterSignal(parent, COMSIG_CLICK_ALT, PROC_REF(display_menu)) /datum/component/obeys_commands/UnregisterFromParent() UnregisterSignal(parent, list(COMSIG_LIVING_BEFRIENDED, COMSIG_LIVING_UNFRIENDED, COMSIG_ATOM_EXAMINE, COMSIG_CLICK_ALT)) @@ -38,15 +49,26 @@ /// Add someone to our friends list /datum/component/obeys_commands/proc/add_friend(datum/source, mob/living/new_friend) SIGNAL_HANDLER - + RegisterSignal(new_friend, COMSIG_KB_LIVING_VIEW_PET_COMMANDS, PROC_REF(on_key_pressed)) + RegisterSignal(new_friend, DEACTIVATE_KEYBIND(COMSIG_KB_LIVING_VIEW_PET_COMMANDS), PROC_REF(on_key_unpressed)) for (var/command_name as anything in available_commands) var/datum/pet_command/command = available_commands[command_name] INVOKE_ASYNC(command, TYPE_PROC_REF(/datum/pet_command, add_new_friend), new_friend) +/datum/component/obeys_commands/proc/on_key_unpressed(mob/living/source) + SIGNAL_HANDLER + UnregisterSignal(source, COMSIG_ATOM_MOUSE_ENTERED) + +/datum/component/obeys_commands/proc/remove_from_viewers(mob/living/source) + radial_viewers -= REF(source) + /// Remove someone from our friends list /datum/component/obeys_commands/proc/remove_friend(datum/source, mob/living/old_friend) SIGNAL_HANDLER - + UnregisterSignal(old_friend, list( + COMSIG_KB_LIVING_VIEW_PET_COMMANDS, + DEACTIVATE_KEYBIND(COMSIG_KB_LIVING_VIEW_PET_COMMANDS), + )) for (var/command_name as anything in available_commands) var/datum/pet_command/command = available_commands[command_name] INVOKE_ASYNC(command, TYPE_PROC_REF(/datum/pet_command, remove_friend), old_friend) @@ -61,21 +83,34 @@ return examine_list += span_notice("[source.p_They()] seem[source.p_s()] happy to see you!") -/// Displays a radial menu of commands -/datum/component/obeys_commands/proc/display_menu(datum/source, mob/living/clicker) +/datum/component/obeys_commands/proc/on_key_pressed(mob/living/friend) + SIGNAL_HANDLER + RegisterSignal(friend, COMSIG_ATOM_MOUSE_ENTERED, PROC_REF(on_mouse_hover)) + +/datum/component/obeys_commands/proc/on_mouse_hover(mob/living/friend, atom/mouse_hovered) SIGNAL_HANDLER + if(mouse_hovered == parent) + display_menu(friend) + return + if(isliving(mouse_hovered)) + remove_from_viewers(friend) + +/// Displays a radial menu of commands +/datum/component/obeys_commands/proc/display_menu(mob/living/friend) var/mob/living/living_parent = parent - if (IS_DEAD_OR_INCAP(living_parent) || !clicker.can_perform_action(living_parent)) + if (IS_DEAD_OR_INCAP(living_parent) || friend.stat != CONSCIOUS) return - if (!(clicker in living_parent.ai_controller?.blackboard[BB_FRIENDS_LIST])) + if (!(friend in living_parent.ai_controller?.blackboard[BB_FRIENDS_LIST])) return // Not our friend, can't boss us around - - INVOKE_ASYNC(src, PROC_REF(display_radial_menu), clicker) - return CLICK_ACTION_SUCCESS + if(radial_viewers[REF(friend)]) + return + if(!can_see(friend, parent, DEFAULT_RADIAL_VIEWING_DISTANCE)) + return + INVOKE_ASYNC(src, PROC_REF(display_radial_menu), friend) /// Actually display the radial menu and then do something with the result -/datum/component/obeys_commands/proc/display_radial_menu(mob/living/clicker) +/datum/component/obeys_commands/proc/display_radial_menu(mob/living/friend) var/list/radial_options = list() for (var/command_name as anything in available_commands) var/datum/pet_command/command = available_commands[command_name] @@ -83,9 +118,22 @@ if (!choice) continue radial_options += choice - - var/pick = show_radial_menu(clicker, clicker, radial_options, tooltips = TRUE) - if (!pick) + radial_viewers[REF(friend)] = world.time + radial_menu_lifetime + var/pick = show_radial_menu(friend, parent, radial_options, radius = radial_menu_radius, button_animation_flags = BUTTON_FADE_IN | BUTTON_FADE_OUT, custom_check = CALLBACK(src, PROC_REF(check_menu_viewer), friend), check_delay = 0.15 SECONDS, display_close_button = FALSE, radial_menu_offset = radial_menu_offset, user_space = radial_relative_to_user) + remove_from_viewers(friend) + if(!pick) return var/datum/pet_command/picked_command = available_commands[pick] - picked_command.try_activate_command(clicker) + picked_command.try_activate_command(friend, radial_command = TRUE) + +/datum/component/obeys_commands/proc/check_menu_viewer(mob/living/user) + if(QDELETED(user) || !radial_viewers[REF(user)]) + return FALSE + if(world.time > radial_viewers[REF(user)]) + return FALSE + var/viewing_distance = DEFAULT_RADIAL_VIEWING_DISTANCE + if(!can_see(user, parent, viewing_distance)) + return FALSE + return TRUE + +#undef DEFAULT_RADIAL_VIEWING_DISTANCE diff --git a/code/datums/components/pet_commands/pet_command.dm b/code/datums/components/pet_commands/pet_command.dm index 52b4cc8834920..fab3de70b79f1 100644 --- a/code/datums/components/pet_commands/pet_command.dm +++ b/code/datums/components/pet_commands/pet_command.dm @@ -13,7 +13,7 @@ /// If true, command will not appear in radial menu and can only be accessed through speech var/hidden = FALSE /// Icon to display in radial menu - var/icon/radial_icon + var/icon/radial_icon = 'icons/hud/radial_pets.dmi' /// Icon state to display in radial menu var/radial_icon_state /// Speech strings to listen out for @@ -24,6 +24,12 @@ var/command_feedback /// How close a mob needs to be to a target to respond to a command var/sense_radius = 7 + /// does this pet command need a point to activate? + var/requires_pointing = FALSE + /// Blackboard key for targeting strategy, this is likely going to need it + var/targeting_strategy_key = BB_PET_TARGETING_STRATEGY + ///our pointed reaction we play + var/pointed_reaction /datum/pet_command/New(mob/living/parent) . = ..() @@ -34,10 +40,17 @@ RegisterSignal(tamer, COMSIG_MOB_SAY, PROC_REF(respond_to_command)) RegisterSignal(tamer, COMSIG_MOB_AUTOMUTE_CHECK, PROC_REF(waive_automute)) RegisterSignal(tamer, COMSIG_MOB_CREATED_CALLOUT, PROC_REF(respond_to_callout)) + if(requires_pointing) + RegisterSignal(tamer, COMSIG_MOVABLE_POINTED, PROC_REF(point_on_target)) /// Stop listening to a guy /datum/pet_command/proc/remove_friend(mob/living/unfriended) - UnregisterSignal(unfriended, list(COMSIG_MOB_SAY, COMSIG_MOB_AUTOMUTE_CHECK, COMSIG_MOB_CREATED_CALLOUT)) + UnregisterSignal(unfriended, list( + COMSIG_MOB_SAY, + COMSIG_MOB_AUTOMUTE_CHECK, + COMSIG_MOB_CREATED_CALLOUT, + COMSIG_MOVABLE_POINTED, + )) /// Stop the automute from triggering for commands (unless the spoken text is suspiciously longer than the command) /datum/pet_command/proc/waive_automute(mob/living/speaker, client/client, last_message, mute_type) @@ -49,7 +62,6 @@ /// Respond to something that one of our friends has asked us to do /datum/pet_command/proc/respond_to_command(mob/living/speaker, speech_args) SIGNAL_HANDLER - var/mob/living/parent = weak_parent.resolve() if (!parent) return @@ -60,7 +72,7 @@ if (!find_command_in_text(spoken_text)) return - try_activate_command(speaker) + try_activate_command(commander = speaker, radial_command = FALSE) /// Respond to a callout /datum/pet_command/proc/respond_to_callout(mob/living/caller, datum/callout_option/callout, atom/target) @@ -83,7 +95,7 @@ if (!found_new_target) return - if (try_activate_command(caller)) + if (try_activate_command(commander = caller, radial_command = FALSE)) look_for_target(parent, target) /// Does this callout with this target trigger this command? @@ -103,48 +115,77 @@ return TRUE return FALSE -/// Apply a command state if conditions are right, return command if successful -/datum/pet_command/proc/try_activate_command(mob/living/commander) +/datum/pet_command/proc/pet_able_to_respond() var/mob/living/parent = weak_parent.resolve() - if (!parent) + if(isnull(parent) || isnull(parent.ai_controller)) return FALSE - if (!parent.ai_controller) // We stopped having a brain at some point + if(IS_DEAD_OR_INCAP(parent)) // Probably can't hear them if we're dead return FALSE - if (IS_DEAD_OR_INCAP(parent)) // Probably can't hear them if we're dead - return FALSE - if (parent.ai_controller.blackboard[BB_ACTIVE_PET_COMMAND] == src) // We're already doing it + return TRUE + +/// Apply a command state if conditions are right, return command if successful +/datum/pet_command/proc/try_activate_command(mob/living/commander, radial_command) + if(!pet_able_to_respond()) return FALSE - set_command_active(parent, commander) + var/mob/living/parent = weak_parent.resolve() + set_command_active(parent, commander, radial_command) return TRUE +/datum/pet_command/proc/generate_emote_command(atom/target) + var/mob/living/living_pet = weak_parent?.resolve() + return isnull(living_pet) ? null : retrieve_command_text(living_pet, target) + +/datum/pet_command/proc/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to spring into action!" + /// Target the pointed atom for actions -/datum/pet_command/proc/look_for_target(mob/living/friend, atom/pointed_atom) +/datum/pet_command/proc/look_for_target(mob/living/friend, atom/potential_target) var/mob/living/parent = weak_parent.resolve() - if (!parent) - return FALSE - if (!parent.ai_controller) - return FALSE - if (IS_DEAD_OR_INCAP(parent)) + if(!pet_able_to_respond()) return FALSE - if (parent.ai_controller.blackboard[BB_ACTIVE_PET_COMMAND] != src) // We're not listening right now + if (parent.ai_controller.blackboard[BB_CURRENT_PET_TARGET] == potential_target) // That's already our target return FALSE - if (parent.ai_controller.blackboard[BB_CURRENT_PET_TARGET] == pointed_atom) // That's already our target - return FALSE - if (!can_see(parent, pointed_atom, sense_radius)) + if (!can_see(parent, potential_target, sense_radius)) return FALSE parent.ai_controller.CancelActions() - set_command_target(parent, pointed_atom) + set_command_target(parent, potential_target) return TRUE /// Activate the command, extend to add visible messages and the like -/datum/pet_command/proc/set_command_active(mob/living/parent, mob/living/commander) - set_command_target(parent, null) +/datum/pet_command/proc/set_command_active(mob/living/parent, mob/living/commander, radial_command = FALSE) + parent.ai_controller.clear_blackboard_key(BB_CURRENT_PET_TARGET) parent.ai_controller.CancelActions() // Stop whatever you're doing and do this instead parent.ai_controller.set_blackboard_key(BB_ACTIVE_PET_COMMAND, src) if (command_feedback) parent.balloon_alert_to_viewers("[command_feedback]") // If we get a nicer runechat way to do this, refactor this + if(!radial_command) + return + if(!requires_pointing) + var/manual_emote_text = generate_emote_command() + commander.manual_emote(manual_emote_text) + return + RegisterSignal(commander, COMSIG_MOB_CLICKON, PROC_REF(click_on_target)) + commander.client?.mouse_override_icon = 'icons/effects/mouse_pointers/pet_paw.dmi' + commander.update_mouse_pointer() + +/datum/pet_command/proc/click_on_target(mob/living/source, atom/target, list/modifiers) + SIGNAL_HANDLER + if(!can_see(source, target, 9)) + return COMSIG_MOB_CANCEL_CLICKON + on_target_set(source, target) + UnregisterSignal(source, COMSIG_MOB_CLICKON) + source.client?.mouse_override_icon = source.client::mouse_override_icon + source.update_mouse_pointer() + var/manual_emote_text = generate_emote_command(target) + if(!isnull(manual_emote_text)) + INVOKE_ASYNC(source, TYPE_PROC_REF(/atom, manual_emote), manual_emote_text) + return COMSIG_MOB_CANCEL_CLICKON + +/datum/pet_command/proc/point_on_target(mob/living/friend, atom/potential_target) + SIGNAL_HANDLER + on_target_set(friend, potential_target) /// Store the target for the AI blackboard /datum/pet_command/proc/set_command_target(mob/living/parent, atom/target) @@ -158,11 +199,6 @@ var/datum/radial_menu_choice/choice = new() choice.name = command_name choice.image = icon(icon = radial_icon, icon_state = radial_icon_state) - var/tooltip = command_desc - if (length(speech_commands)) - tooltip += "
Speak this command with the words [speech_commands.Join(", ")]." - choice.info = tooltip - return list("[command_name]" = choice) /** @@ -174,34 +210,15 @@ SHOULD_CALL_PARENT(FALSE) CRASH("Pet command execute action not implemented.") -/** - * # Point Targeting Pet Command - * As above but also listens for you pointing at something and marks it as a target - */ -/datum/pet_command/point_targeting - /// Text describing an action we perform upon receiving a new target - var/pointed_reaction - /// Blackboard key for targeting strategy, this is likely going to need it - var/targeting_strategy_key = BB_PET_TARGETING_STRATEGY - -/datum/pet_command/point_targeting/add_new_friend(mob/living/tamer) - . = ..() - RegisterSignal(tamer, COMSIG_MOVABLE_POINTED, PROC_REF(on_point)) - -/datum/pet_command/point_targeting/remove_friend(mob/living/unfriended) - . = ..() - UnregisterSignal(unfriended, COMSIG_MOVABLE_POINTED) - /// Target the pointed atom for actions -/datum/pet_command/point_targeting/proc/on_point(mob/living/friend, atom/pointed_atom, obj/effect/temp_visual/point/point) - SIGNAL_HANDLER - +/datum/pet_command/proc/on_target_set(mob/living/friend, atom/potential_target) var/mob/living/parent = weak_parent.resolve() if (!parent) - return FALSE + return parent.ai_controller.CancelActions() - if (look_for_target(friend, pointed_atom) && set_command_target(parent, pointed_atom)) - parent.visible_message(span_warning("[parent] follows [friend]'s gesture towards [pointed_atom] [pointed_reaction]!")) - return TRUE - return FALSE + if(!look_for_target(friend, potential_target) || !set_command_target(parent, potential_target)) + return + parent.visible_message(span_warning("[parent] follows [friend]'s gesture towards [potential_target] [pointed_reaction]!")) + + diff --git a/code/datums/components/pet_commands/pet_commands_basic.dm b/code/datums/components/pet_commands/pet_commands_basic.dm index fd4e1f922c8b4..06d11d353e8d5 100644 --- a/code/datums/components/pet_commands/pet_commands_basic.dm +++ b/code/datums/components/pet_commands/pet_commands_basic.dm @@ -7,14 +7,16 @@ /datum/pet_command/idle command_name = "Stay" command_desc = "Command your pet to stay idle in this location." - radial_icon = 'icons/obj/bed.dmi' - radial_icon_state = "dogbed" + radial_icon_state = "halt" speech_commands = list("sit", "stay", "stop") command_feedback = "sits" /datum/pet_command/idle/execute_action(datum/ai_controller/controller) return SUBTREE_RETURN_FINISH_PLANNING // This cancels further AI planning +/datum/pet_command/idle/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to stay idle!" + /** * # Pet Command: Stop * Tells a pet to exit command mode and resume its normal behaviour, which includes regular target-seeking and what have you @@ -22,8 +24,7 @@ /datum/pet_command/free command_name = "Loose" command_desc = "Allow your pet to resume its natural behaviours." - radial_icon = 'icons/mob/actions/actions_spells.dmi' - radial_icon_state = "repulse" + radial_icon_state = "free" speech_commands = list("free", "loose") command_feedback = "relaxes" @@ -31,6 +32,9 @@ controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) return // Just move on to the next planning subtree. +/datum/pet_command/free/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to go free!" + /** * # Pet Command: Follow * Tells a pet to follow you until you tell it to do something else @@ -38,8 +42,7 @@ /datum/pet_command/follow command_name = "Follow" command_desc = "Command your pet to accompany you." - radial_icon = 'icons/testing/turf_analysis.dmi' - radial_icon_state = "red_arrow" + radial_icon_state = "follow" speech_commands = list("heel", "follow") callout_type = /datum/callout_option/move ///the behavior we use to follow @@ -49,6 +52,9 @@ . = ..() set_command_target(parent, commander) +/datum/pet_command/follow/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to follow!" + /datum/pet_command/follow/execute_action(datum/ai_controller/controller) controller.queue_behavior(follow_behavior, BB_CURRENT_PET_TARGET) return SUBTREE_RETURN_FINISH_PLANNING @@ -60,14 +66,16 @@ /datum/pet_command/play_dead command_name = "Play Dead" command_desc = "Play a macabre trick." - radial_icon = 'icons/mob/simple/pets.dmi' - radial_icon_state = "puppy_dead" + radial_icon_state = "play_dead" speech_commands = list("play dead") // Don't get too creative here, people talk about dying pretty often /datum/pet_command/play_dead/execute_action(datum/ai_controller/controller) controller.queue_behavior(/datum/ai_behavior/play_dead) return SUBTREE_RETURN_FINISH_PLANNING +/datum/pet_command/play_dead/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to play dead!" + /** * # Pet Command: Good Boy * React if complimented @@ -115,16 +123,18 @@ controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) return SUBTREE_RETURN_FINISH_PLANNING +/datum/pet_command/untargeted_ability/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to use an ability!" + /** * # Pet Command: Attack * Tells a pet to chase and bite the next thing you point at */ -/datum/pet_command/point_targeting/attack +/datum/pet_command/attack command_name = "Attack" command_desc = "Command your pet to attack things that you point out to it." - radial_icon = 'icons/effects/effects.dmi' - radial_icon_state = "bite" - + radial_icon_state = "attack" + requires_pointing = TRUE callout_type = /datum/callout_option/attack speech_commands = list("attack", "sic", "kill") command_feedback = "growl" @@ -135,7 +145,7 @@ var/attack_behaviour = /datum/ai_behavior/basic_melee_attack // Refuse to target things we can't target, chiefly other friends -/datum/pet_command/point_targeting/attack/set_command_target(mob/living/parent, atom/target) +/datum/pet_command/attack/set_command_target(mob/living/parent, atom/target) if (!target) return var/mob/living/living_parent = parent @@ -149,28 +159,32 @@ return return ..() +/datum/pet_command/attack/retrieve_command_text(atom/living_pet, atom/target) + return isnull(target) ? null : "signals [living_pet] to attack [target]!" + /// Display feedback about not targeting something -/datum/pet_command/point_targeting/attack/proc/refuse_target(mob/living/parent, atom/target) +/datum/pet_command/attack/proc/refuse_target(mob/living/parent, atom/target) var/mob/living/living_parent = parent living_parent.balloon_alert_to_viewers("[refuse_reaction]") living_parent.visible_message(span_notice("[living_parent] refuses to attack [target].")) -/datum/pet_command/point_targeting/attack/execute_action(datum/ai_controller/controller) +/datum/pet_command/attack/execute_action(datum/ai_controller/controller) controller.queue_behavior(attack_behaviour, BB_CURRENT_PET_TARGET, targeting_strategy_key) return SUBTREE_RETURN_FINISH_PLANNING /** * # Breed command. breed with a partner! */ -/datum/pet_command/point_targeting/breed +/datum/pet_command/breed command_name = "Breed" command_desc = "Command your pet to attempt to breed with a partner." - radial_icon = 'icons/mob/simple/animal.dmi' - radial_icon_state = "heart" + requires_pointing = TRUE + radial_icon_state = "breed" speech_commands = list("breed", "consummate") + ///the behavior we use to make babies var/datum/ai_behavior/reproduce_behavior = /datum/ai_behavior/make_babies -/datum/pet_command/point_targeting/breed/set_command_target(mob/living/parent, atom/target) +/datum/pet_command/breed/set_command_target(mob/living/parent, atom/target) if(isnull(target) || !isliving(target)) return if(!HAS_TRAIT(parent, TRAIT_MOB_BREEDER) || !HAS_TRAIT(target, TRAIT_MOB_BREEDER)) @@ -184,21 +198,25 @@ return return ..() -/datum/pet_command/point_targeting/breed/execute_action(datum/ai_controller/controller) +/datum/pet_command/breed/execute_action(datum/ai_controller/controller) if(is_type_in_list(controller.blackboard[BB_CURRENT_PET_TARGET], controller.blackboard[BB_BABIES_PARTNER_TYPES])) controller.queue_behavior(reproduce_behavior, BB_CURRENT_PET_TARGET) controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) return SUBTREE_RETURN_FINISH_PLANNING +/datum/pet_command/breed/retrieve_command_text(atom/living_pet, atom/target) + return isnull(target) ? null : "signals [living_pet] to breed with [target]!" + /** * # Pet Command: Targetted Ability * Tells a pet to use some kind of ability on the next thing you point at */ -/datum/pet_command/point_targeting/use_ability +/datum/pet_command/use_ability command_name = "Use ability" command_desc = "Command your pet to use one of its special skills on something that you point out to it." radial_icon = 'icons/mob/actions/actions_spells.dmi' radial_icon_state = "projectile" + requires_pointing = TRUE speech_commands = list("shoot", "blast", "cast") command_feedback = "growl" pointed_reaction = "and growls" @@ -207,7 +225,7 @@ /// The AI behavior to use for the ability var/ability_behavior = /datum/ai_behavior/pet_use_ability -/datum/pet_command/point_targeting/use_ability/execute_action(datum/ai_controller/controller) +/datum/pet_command/use_ability/execute_action(datum/ai_controller/controller) if (!pet_ability_key) return var/datum/action/cooldown/using_action = controller.blackboard[pet_ability_key] @@ -218,6 +236,9 @@ controller.queue_behavior(ability_behavior, pet_ability_key, BB_CURRENT_PET_TARGET) return SUBTREE_RETURN_FINISH_PLANNING +/datum/pet_command/use_ability/retrieve_command_text(atom/living_pet, atom/target) + return isnull(target) ? null : "signals [living_pet] to use an ability on [target]!" + /datum/pet_command/protect_owner command_name = "Protect owner" command_desc = "Your pet will run to your aid." @@ -278,25 +299,39 @@ /** * # Fish command: command the mob to fish at the next fishing spot you point at. Requires the profound fisher component */ -/datum/pet_command/point_targeting/fish +/datum/pet_command/fish command_name = "Fish" command_desc = "Command your pet to try fishing at a nearby fishing spot." - radial_icon = 'icons/obj/aquarium/fish.dmi' - radial_icon_state = "goldfish" + requires_pointing = TRUE + radial_icon_state = "fish" speech_commands = list("fish") -// Refuse to target things we can't target, chiefly other friends -/datum/pet_command/point_targeting/fish/set_command_target(mob/living/parent, atom/target) - if (!target) - return - if(!parent.ai_controller || !HAS_TRAIT(parent, TRAIT_PROFOUND_FISHER)) - return - var/datum/targeting_strategy/targeter = GET_TARGETING_STRATEGY(/datum/targeting_strategy/fishing) - if (!targeter?.can_attack(parent, target)) - parent.balloon_alert_to_viewers("shakes head!") - return +/datum/pet_command/fish/execute_action(datum/ai_controller/controller) + if(controller.blackboard_key_exists(BB_CURRENT_PET_TARGET)) + controller.queue_behavior(/datum/ai_behavior/interact_with_target/fishing, BB_CURRENT_PET_TARGET) + return SUBTREE_RETURN_FINISH_PLANNING + +/datum/pet_command/fish/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to go fish!" + +/datum/pet_command/move + command_name = "Move" + command_desc = "Command your pet to move to a location!" + requires_pointing = TRUE + radial_icon_state = "move" + speech_commands = list("move", "walk") + ///the behavior we use to walk towards targets + var/datum/ai_behavior/walk_behavior = /datum/ai_behavior/travel_towards + +/datum/pet_command/move/set_command_target(mob/living/parent, atom/target) + if(isnull(target) || !can_see(parent, target, 9)) + return FALSE return ..() -/datum/pet_command/point_targeting/fish/execute_action(datum/ai_controller/controller) - controller.queue_behavior(/datum/ai_behavior/hunt_target/interact_with_target/reset_target_combat_mode_off, BB_CURRENT_PET_TARGET) +/datum/pet_command/move/execute_action(datum/ai_controller/controller) + if(controller.blackboard_key_exists(BB_CURRENT_PET_TARGET)) + controller.queue_behavior(walk_behavior, BB_CURRENT_PET_TARGET) return SUBTREE_RETURN_FINISH_PLANNING + +/datum/pet_command/move/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to move!" diff --git a/code/datums/elements/pet_cult.dm b/code/datums/elements/pet_cult.dm index 36941e7b74299..f26e6e343e666 100644 --- a/code/datums/elements/pet_cult.dm +++ b/code/datums/elements/pet_cult.dm @@ -90,7 +90,8 @@ source.ai_controller.set_blackboard_key(BB_CULT_TEAM, team) var/static/list/new_pet_commands = list( - /datum/pet_command/point_targeting/attack, + /datum/pet_command/move, + /datum/pet_command/attack, /datum/pet_command/follow, /datum/pet_command/free, /datum/pet_command/idle, diff --git a/code/datums/keybinding/_keybindings.dm b/code/datums/keybinding/_keybindings.dm index dfcf492c1809f..a989d0d22a881 100644 --- a/code/datums/keybinding/_keybindings.dm +++ b/code/datums/keybinding/_keybindings.dm @@ -21,6 +21,8 @@ return SEND_SIGNAL(user.mob, keybind_signal) & COMSIG_KB_ACTIVATED /datum/keybinding/proc/up(client/user) + SHOULD_CALL_PARENT(TRUE) + SEND_SIGNAL(user.mob, DEACTIVATE_KEYBIND(keybind_signal)) return FALSE /datum/keybinding/proc/can_use(client/user) diff --git a/code/datums/keybinding/living.dm b/code/datums/keybinding/living.dm index 7e50e6928f9dd..61716ee6fe95a 100644 --- a/code/datums/keybinding/living.dm +++ b/code/datums/keybinding/living.dm @@ -46,6 +46,7 @@ return TRUE /datum/keybinding/living/look_up/up(client/user) + . = ..() var/mob/living/L = user.mob L.end_look_up() return TRUE @@ -66,6 +67,7 @@ return TRUE /datum/keybinding/living/look_down/up(client/user) + . = ..() var/mob/living/L = user.mob L.end_look_down() return TRUE @@ -144,6 +146,7 @@ return TRUE /datum/keybinding/living/toggle_move_intent/up(client/user) + . = ..() var/mob/living/M = user.mob M.toggle_move_intent() return TRUE diff --git a/code/datums/keybinding/mob.dm b/code/datums/keybinding/mob.dm index 4963e87266cbe..a1bd06742b555 100644 --- a/code/datums/keybinding/mob.dm +++ b/code/datums/keybinding/mob.dm @@ -191,3 +191,10 @@ if(.) return user.movement_locked = FALSE + +/datum/keybinding/living/view_pet_data + hotkey_keys = list("Shift") + name = "view_pet_commands" + full_name = "View Pet Commands" + description = "Hold down to see all the commands you can give your pets!" + keybind_signal = COMSIG_KB_LIVING_VIEW_PET_COMMANDS diff --git a/code/game/atom/_atom.dm b/code/game/atom/_atom.dm index 9afda5b0ddea5..c8c3111889eac 100644 --- a/code/game/atom/_atom.dm +++ b/code/game/atom/_atom.dm @@ -864,6 +864,8 @@ if (isnull(user)) return + SEND_SIGNAL(user, COMSIG_ATOM_MOUSE_ENTERED, src) + // Screentips var/datum/hud/active_hud = user.hud_used if(!active_hud) diff --git a/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm b/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm index d545e258a302e..d3e1765a0b92c 100644 --- a/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm @@ -226,7 +226,7 @@ /datum/pet_command/idle, /datum/pet_command/free, /datum/pet_command/follow, - /datum/pet_command/point_targeting/attack/star_gazer + /datum/pet_command/attack/star_gazer ) /datum/heretic_knowledge/ultimate/cosmic_final/is_valid_sacrifice(mob/living/carbon/human/sacrifice) @@ -242,7 +242,7 @@ star_gazer_mob.maxHealth = INFINITY star_gazer_mob.health = INFINITY user.AddComponent(/datum/component/death_linked, star_gazer_mob) - star_gazer_mob.AddComponent(/datum/component/obeys_commands, star_gazer_commands) + star_gazer_mob.AddComponent(/datum/component/obeys_commands, star_gazer_commands, radial_menu_lifetime = 15 SECONDS, radial_relative_to_user = TRUE) star_gazer_mob.AddComponent(/datum/component/damage_aura, range = 7, burn_damage = 0.5, simple_damage = 0.5, immune_factions = list(FACTION_HERETIC), current_owner = user) star_gazer_mob.befriend(user) var/datum/action/cooldown/open_mob_commands/commands_action = new /datum/action/cooldown/open_mob_commands() diff --git a/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm b/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm index 68cd6a231e326..14de9f04068d2 100644 --- a/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm +++ b/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm @@ -127,7 +127,7 @@ var/static/list/pet_commands = list( /datum/pet_command/idle, /datum/pet_command/free, - /datum/pet_command/point_targeting/clean, + /datum/pet_command/clean, ) /mob/living/basic/bot/cleanbot/Initialize(mapload) diff --git a/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm b/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm index 1b6d840062208..0a6a4b03b4354 100644 --- a/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm +++ b/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm @@ -200,14 +200,15 @@ return return ..() -/datum/pet_command/point_targeting/clean +/datum/pet_command/clean command_name = "Clean" command_desc = "Command a cleanbot to clean the mess." + requires_pointing = TRUE radial_icon = 'icons/obj/service/janitor.dmi' radial_icon_state = "mop" speech_commands = list("clean", "mop") -/datum/pet_command/point_targeting/clean/set_command_target(mob/living/parent, atom/target) +/datum/pet_command/clean/set_command_target(mob/living/parent, atom/target) if(isnull(target) || !istype(target, /obj/effect/decal/cleanable)) return if(isnull(parent.ai_controller)) @@ -216,7 +217,7 @@ return return ..() -/datum/pet_command/point_targeting/clean/execute_action(datum/ai_controller/basic_controller/bot/controller) +/datum/pet_command/clean/execute_action(datum/ai_controller/basic_controller/bot/controller) if(controller.blackboard_key_exists(BB_CURRENT_PET_TARGET)) controller.queue_behavior(/datum/ai_behavior/execute_clean, BB_CURRENT_PET_TARGET) return SUBTREE_RETURN_FINISH_PLANNING diff --git a/code/modules/mob/living/basic/farm_animals/bee/_bee.dm b/code/modules/mob/living/basic/farm_animals/bee/_bee.dm index e9dd79fd5ef06..09bf3a5819592 100644 --- a/code/modules/mob/living/basic/farm_animals/bee/_bee.dm +++ b/code/modules/mob/living/basic/farm_animals/bee/_bee.dm @@ -57,7 +57,7 @@ /datum/pet_command/beehive/enter, /datum/pet_command/beehive/exit, /datum/pet_command/follow/bee, - /datum/pet_command/point_targeting/attack/swirl, + /datum/pet_command/attack/swirl, /datum/pet_command/scatter, ) diff --git a/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm b/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm index 1081c9b7b63b8..8472098662466 100644 --- a/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm +++ b/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm @@ -113,8 +113,9 @@ required_distance = 0 ///swirl around the owner in menacing fashion -/datum/pet_command/point_targeting/attack/swirl +/datum/pet_command/attack/swirl command_name = "Swirl" + requires_pointing = TRUE command_desc = "Your pets will swirl around you and attack whoever you point at!" speech_commands = list("swirl", "spiral", "swarm") pointed_reaction = null @@ -123,7 +124,7 @@ ///the owner we will swarm around var/key_to_swarm = BB_SWARM_TARGET -/datum/pet_command/point_targeting/attack/swirl/try_activate_command(mob/living/commander) +/datum/pet_command/attack/swirl/try_activate_command(mob/living/commander, radial_command) var/mob/living/living_pawn = weak_parent.resolve() if(isnull(living_pawn)) return @@ -134,7 +135,7 @@ controller.set_blackboard_key(key_to_swarm, commander) return ..() -/datum/pet_command/point_targeting/attack/swirl/execute_action(datum/ai_controller/controller) +/datum/pet_command/attack/swirl/execute_action(datum/ai_controller/controller) if(controller.blackboard_key_exists(BB_CURRENT_PET_TARGET)) return ..() controller.queue_behavior(/datum/ai_behavior/swirl_around_target, BB_SWARM_TARGET) @@ -186,7 +187,7 @@ radial_icon = 'icons/obj/service/hydroponics/equipment.dmi' radial_icon_state = "beebox" -/datum/pet_command/beehive/try_activate_command(mob/living/commander) +/datum/pet_command/beehive/try_activate_command(mob/living/commander, radial_command) var/mob/living/living_pawn = weak_parent.resolve() if(isnull(living_pawn)) return diff --git a/code/modules/mob/living/basic/heretic/star_gazer.dm b/code/modules/mob/living/basic/heretic/star_gazer.dm index 57a0ddfe322d8..7ffd7bc657ef6 100644 --- a/code/modules/mob/living/basic/heretic/star_gazer.dm +++ b/code/modules/mob/living/basic/heretic/star_gazer.dm @@ -103,7 +103,7 @@ can_attack_turfs = TRUE can_attack_dense_objects = TRUE -/datum/pet_command/point_targeting/attack/star_gazer +/datum/pet_command/attack/star_gazer speech_commands = list("attack", "sic", "kill", "slash them") command_feedback = "stares!" pointed_reaction = "stares intensely!" diff --git a/code/modules/mob/living/basic/icemoon/wolf/wolf.dm b/code/modules/mob/living/basic/icemoon/wolf/wolf.dm index b82092147f67d..6056b1919090b 100644 --- a/code/modules/mob/living/basic/icemoon/wolf/wolf.dm +++ b/code/modules/mob/living/basic/icemoon/wolf/wolf.dm @@ -43,11 +43,12 @@ //commands to give when tamed var/static/list/pet_commands = list( /datum/pet_command/idle, + /datum/pet_command/move, /datum/pet_command/free, /datum/pet_command/good_boy/wolf, /datum/pet_command/follow/wolf, - /datum/pet_command/point_targeting/attack, - /datum/pet_command/point_targeting/fetch, + /datum/pet_command/attack, + /datum/pet_command/fetch, /datum/pet_command/play_dead, /datum/pet_command/protect_owner, ) diff --git a/code/modules/mob/living/basic/jungle/leaper/leaper.dm b/code/modules/mob/living/basic/jungle/leaper/leaper.dm index 94babd0218e5b..d4f310d07aba1 100644 --- a/code/modules/mob/living/basic/jungle/leaper/leaper.dm +++ b/code/modules/mob/living/basic/jungle/leaper/leaper.dm @@ -40,13 +40,14 @@ ///list of pet commands we can issue var/list/pet_commands = list( /datum/pet_command/idle, + /datum/pet_command/move, /datum/pet_command/free, /datum/pet_command/follow, /datum/pet_command/untargeted_ability/blood_rain, /datum/pet_command/untargeted_ability/summon_toad, - /datum/pet_command/point_targeting/attack, - /datum/pet_command/point_targeting/use_ability/flop, - /datum/pet_command/point_targeting/use_ability/bubble, + /datum/pet_command/attack, + /datum/pet_command/use_ability/flop, + /datum/pet_command/use_ability/bubble, ) /mob/living/basic/leaper/Initialize(mapload) diff --git a/code/modules/mob/living/basic/jungle/leaper/leaper_ai.dm b/code/modules/mob/living/basic/jungle/leaper/leaper_ai.dm index e776117a3a596..b5c917bf2a46e 100644 --- a/code/modules/mob/living/basic/jungle/leaper/leaper_ai.dm +++ b/code/modules/mob/living/basic/jungle/leaper/leaper_ai.dm @@ -39,7 +39,7 @@ ability_key = BB_LEAPER_SUMMON finish_planning = FALSE -/datum/pet_command/point_targeting/use_ability/flop +/datum/pet_command/use_ability/flop command_name = "Flop" command_desc = "Command your pet to belly flop your target!" radial_icon = 'icons/mob/actions/actions_items.dmi' @@ -47,7 +47,7 @@ speech_commands = list("flop", "crush") pet_ability_key = BB_LEAPER_FLOP -/datum/pet_command/point_targeting/use_ability/bubble +/datum/pet_command/use_ability/bubble command_name = "Poison Bubble" command_desc = "Launch poisonous bubbles at your target!" radial_icon = 'icons/obj/weapons/guns/projectiles.dmi' @@ -55,6 +55,9 @@ speech_commands = list("bubble", "shoot") pet_ability_key = BB_LEAPER_BUBBLE +/datum/pet_command/use_ability/bubble/retrieve_command_text(atom/living_pet, atom/target) + return isnull(target) ? null : "signals [living_pet] to shoot a bubble towards [target]!" + /datum/pet_command/untargeted_ability/blood_rain command_name = "Blood Rain" command_desc = "Let it rain poisonous blood!" @@ -63,6 +66,8 @@ speech_commands = list("blood", "rain", "volley") ability_key = BB_LEAPER_VOLLEY +/datum/pet_command/untargeted_ability/blood_rain/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to unleash a volley of rain!" /datum/pet_command/untargeted_ability/summon_toad command_name = "Summon Toads" @@ -71,3 +76,6 @@ radial_icon_state = "frog_trash" speech_commands = list("frogs", "bombers") ability_key = BB_LEAPER_SUMMON + +/datum/pet_command/untargeted_ability/summon_toad/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to summon some explosive frogs!" diff --git a/code/modules/mob/living/basic/jungle/seedling/seedling.dm b/code/modules/mob/living/basic/jungle/seedling/seedling.dm index 0df19c5c27d31..9778aff8753df 100644 --- a/code/modules/mob/living/basic/jungle/seedling/seedling.dm +++ b/code/modules/mob/living/basic/jungle/seedling/seedling.dm @@ -214,9 +214,9 @@ /datum/pet_command/idle, /datum/pet_command/free, /datum/pet_command/follow, - /datum/pet_command/point_targeting/attack, - /datum/pet_command/point_targeting/use_ability/solarbeam, - /datum/pet_command/point_targeting/use_ability/rapidseeds, + /datum/pet_command/attack, + /datum/pet_command/use_ability/solarbeam, + /datum/pet_command/use_ability/rapidseeds, ) //abilities diff --git a/code/modules/mob/living/basic/jungle/seedling/seedling_ai.dm b/code/modules/mob/living/basic/jungle/seedling/seedling_ai.dm index ee93a9c12366f..ae4e7b75c92b0 100644 --- a/code/modules/mob/living/basic/jungle/seedling/seedling_ai.dm +++ b/code/modules/mob/living/basic/jungle/seedling/seedling_ai.dm @@ -166,7 +166,7 @@ finish_planning = FALSE ///pet commands -/datum/pet_command/point_targeting/use_ability/solarbeam +/datum/pet_command/use_ability/solarbeam command_name = "Launch solarbeam" command_desc = "Command your pet to launch a solarbeam at your target!" radial_icon = 'icons/effects/beam.dmi' @@ -174,10 +174,17 @@ speech_commands = list("beam", "solar") pet_ability_key = BB_SOLARBEAM_ABILITY -/datum/pet_command/point_targeting/use_ability/rapidseeds +/datum/pet_command/use_ability/solarbeam/retrieve_command_text(atom/living_pet, atom/target) + return isnull(target) ? null : "signals [living_pet] to use a solar beam on [target]!" + + +/datum/pet_command/use_ability/rapidseeds command_name = "Rapid seeds" command_desc = "Command your pet to launch a volley of seeds at your target!" radial_icon = 'icons/obj/weapons/guns/projectiles.dmi' radial_icon_state = "seedling" speech_commands = list("rapid", "seeds", "volley") pet_ability_key = BB_RAPIDSEEDS_ABILITY + +/datum/pet_command/use_ability/rapidseeds/retrieve_command_text(atom/living_pet, atom/target) + return isnull(target) ? null : "signals [living_pet] to unleash a volley of seeds on [target]!" diff --git a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm index 5a1166962be55..ce1c4e7cf1982 100644 --- a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm +++ b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm @@ -36,7 +36,7 @@ /datum/pet_command/free, /datum/pet_command/grub_spit, /datum/pet_command/follow, - /datum/pet_command/point_targeting/fetch, + /datum/pet_command/fetch, ) /mob/living/basic/mining/goldgrub/Initialize(mapload) diff --git a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm index 8ea2467a2a813..3d5d73a7343d1 100644 --- a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm +++ b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm @@ -211,6 +211,8 @@ /datum/pet_command/grub_spit command_name = "Spit" + radial_icon = 'icons/obj/ore.dmi' + radial_icon_state = "uranium" command_desc = "Ask your grub pet to spit out its ores." speech_commands = list("spit", "ores") @@ -222,4 +224,7 @@ controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) return SUBTREE_RETURN_FINISH_PLANNING +/datum/pet_command/grub_spit/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to spit its ores!" + #undef BURROW_RANGE diff --git a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers.dm b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers.dm index 5bfbbb1051a7f..347e9a95b61ce 100644 --- a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers.dm +++ b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers.dm @@ -61,7 +61,7 @@ if(isnull(ore_food)) balloon_alert(src, "no food!") else - melee_attack(ore_food) + UnarmedAttack(ore_food, TRUE, modifiers) return FALSE /mob/living/basic/mining/gutlunch/proc/after_birth(mob/living/basic/mining/gutlunch/grub/baby, mob/living/partner) @@ -111,11 +111,12 @@ //pet commands when we tame the gutluncher var/static/list/pet_commands = list( /datum/pet_command/idle, + /datum/pet_command/move, /datum/pet_command/free, - /datum/pet_command/point_targeting/attack, - /datum/pet_command/point_targeting/breed/gutlunch, + /datum/pet_command/attack, + /datum/pet_command/breed/gutlunch, /datum/pet_command/follow, - /datum/pet_command/point_targeting/fetch, + /datum/pet_command/fetch, /datum/pet_command/mine_walls, ) diff --git a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_ai.dm b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_ai.dm index 261f6d22a021b..4b329a0003aa8 100644 --- a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_ai.dm +++ b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_ai.dm @@ -102,10 +102,11 @@ /datum/pet_command/mine_walls command_name = "Mine" + radial_icon_state = "mine" command_desc = "Command your pet to mine down walls." speech_commands = list("mine", "smash") -/datum/pet_command/mine_walls/try_activate_command(mob/living/commander) +/datum/pet_command/mine_walls/try_activate_command(mob/living/commander, radial_command) var/mob/living/parent = weak_parent.resolve() if(isnull(parent)) return @@ -121,10 +122,13 @@ return SUBTREE_RETURN_FINISH_PLANNING controller.queue_behavior(/datum/ai_behavior/find_mineral_wall, BB_CURRENT_PET_TARGET) +/datum/pet_command/mine_walls/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to start mining!" + //pet commands -/datum/pet_command/point_targeting/breed/gutlunch +/datum/pet_command/breed/gutlunch -/datum/pet_command/point_targeting/breed/gutlunch/set_command_target(mob/living/parent, atom/target) +/datum/pet_command/breed/gutlunch/set_command_target(mob/living/parent, atom/target) if(GLOB.gutlunch_count >= MAXIMUM_GUTLUNCH_POP) parent.balloon_alert_to_viewers("can't reproduce anymore!") return diff --git a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm index 0b8babf82ec30..7e7d3e71819bf 100644 --- a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm +++ b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm @@ -32,7 +32,7 @@ /// The type of charging ability we give this mob var/charge_type = /datum/action/cooldown/mob_cooldown/charge/basic_charge/lobster /// The pet command for the charging ability we give this mob - var/charge_command = /datum/pet_command/point_targeting/use_ability/lob_charge + var/charge_command = /datum/pet_command/use_ability/lob_charge /// At which speed do we amputate limbs var/snip_speed = 5 SECONDS ///Lobstrosities are natural anglers. This rapresent their proficiency at fishing when not mindless @@ -72,10 +72,11 @@ var/list/pet_commands = list( /datum/pet_command/idle, /datum/pet_command/free, - /datum/pet_command/point_targeting/attack, + /datum/pet_command/move, + /datum/pet_command/attack, charge_command, /datum/pet_command/follow, - /datum/pet_command/point_targeting/fish, + /datum/pet_command/fish, ) AddComponent(/datum/component/happiness) AddComponent(/datum/component/obeys_commands, pet_commands) @@ -153,7 +154,7 @@ ai_controller = /datum/ai_controller/basic_controller/lobstrosity/juvenile snip_speed = 6.5 SECONDS charge_type = /datum/action/cooldown/mob_cooldown/charge/basic_charge/lobster/shrimp - charge_command = /datum/pet_command/point_targeting/use_ability/lob_charge/shrimp + charge_command = /datum/pet_command/use_ability/lob_charge/shrimp base_fishing_level = SKILL_LEVEL_NOVICE /// What do we become when we grow up? var/mob/living/basic/mining/lobstrosity/grow_type = /mob/living/basic/mining/lobstrosity @@ -254,7 +255,7 @@ charger.apply_status_effect(/datum/status_effect/tired_post_charge/lesser) ///Command the lobster to charge at someone. -/datum/pet_command/point_targeting/use_ability/lob_charge +/datum/pet_command/use_ability/lob_charge command_name = "Charge" command_desc = "Command your lobstrosity to charge against someone." radial_icon = 'icons/mob/actions/actions_items.dmi' @@ -265,7 +266,7 @@ pet_ability_key = BB_TARGETED_ACTION ability_behavior = /datum/ai_behavior/pet_use_ability/then_attack/long_ranged -/datum/pet_command/point_targeting/use_ability/lob_charge/set_command_target(mob/living/parent, atom/target) +/datum/pet_command/use_ability/lob_charge/set_command_target(mob/living/parent, atom/target) if (!target) return var/datum/targeting_strategy/targeter = GET_TARGETING_STRATEGY(parent.ai_controller.blackboard[targeting_strategy_key]) @@ -274,5 +275,5 @@ return FALSE return ..() -/datum/pet_command/point_targeting/use_ability/lob_charge/shrimp +/datum/pet_command/use_ability/lob_charge/shrimp ability_behavior = /datum/ai_behavior/pet_use_ability/then_attack/short_ranged diff --git a/code/modules/mob/living/basic/lavaland/mook/mook.dm b/code/modules/mob/living/basic/lavaland/mook/mook.dm index f492c83e74bac..539aa47cb287d 100644 --- a/code/modules/mob/living/basic/lavaland/mook/mook.dm +++ b/code/modules/mob/living/basic/lavaland/mook/mook.dm @@ -41,8 +41,8 @@ var/list/pet_commands = list( /datum/pet_command/idle, /datum/pet_command/free, - /datum/pet_command/point_targeting/attack, - /datum/pet_command/point_targeting/fetch, + /datum/pet_command/attack, + /datum/pet_command/fetch, ) /mob/living/basic/mining/mook/Initialize(mapload) diff --git a/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm b/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm index 15da812a0b237..2f374e2dda4b2 100644 --- a/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm +++ b/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm @@ -1,7 +1,7 @@ ///commands the chief can pick from GLOBAL_LIST_INIT(mook_commands, list( - new /datum/pet_command/point_targeting/attack, - new /datum/pet_command/point_targeting/fetch, + new /datum/pet_command/attack, + new /datum/pet_command/fetch, )) /datum/ai_controller/basic_controller/mook @@ -346,7 +346,7 @@ GLOBAL_LIST_INIT(mook_commands, list( if(!locate(/mob/living/basic/mining/mook) in oview(command_distance, controller.pawn)) return if(controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) - controller.queue_behavior(/datum/ai_behavior/issue_commands, BB_BASIC_MOB_CURRENT_TARGET, /datum/pet_command/point_targeting/attack) + controller.queue_behavior(/datum/ai_behavior/issue_commands, BB_BASIC_MOB_CURRENT_TARGET, /datum/pet_command/attack) return var/atom/ore_target = controller.blackboard[BB_ORE_TARGET] @@ -356,7 +356,7 @@ GLOBAL_LIST_INIT(mook_commands, list( if(get_dist(ore_target, living_pawn) <= 1) return - controller.queue_behavior(/datum/ai_behavior/issue_commands, BB_ORE_TARGET, /datum/pet_command/point_targeting/fetch) + controller.queue_behavior(/datum/ai_behavior/issue_commands, BB_ORE_TARGET, /datum/pet_command/fetch) /datum/ai_behavior/issue_commands action_cooldown = 5 SECONDS diff --git a/code/modules/mob/living/basic/lavaland/raptor/_raptor.dm b/code/modules/mob/living/basic/lavaland/raptor/_raptor.dm index 8bf54b7165b28..3909a392195a8 100644 --- a/code/modules/mob/living/basic/lavaland/raptor/_raptor.dm +++ b/code/modules/mob/living/basic/lavaland/raptor/_raptor.dm @@ -47,11 +47,13 @@ GLOBAL_LIST_EMPTY(raptor_population) var/ridable_component = /datum/component/riding/creature/raptor //pet commands when we tame the raptor var/static/list/pet_commands = list( + /datum/pet_command/breed, /datum/pet_command/idle, + /datum/pet_command/move, /datum/pet_command/free, - /datum/pet_command/point_targeting/attack, + /datum/pet_command/attack, /datum/pet_command/follow, - /datum/pet_command/point_targeting/fetch, + /datum/pet_command/fetch, ) ///things we inherited from our parent var/datum/raptor_inheritance/inherited_stats @@ -158,7 +160,7 @@ GLOBAL_LIST_EMPTY(raptor_population) if(isnull(ore_food)) balloon_alert(src, "no food!") else - melee_attack(ore_food) + UnarmedAttack(ore_food, TRUE, modifiers) return FALSE /mob/living/basic/raptor/melee_attack(mob/living/target, list/modifiers, ignore_cooldown) diff --git a/code/modules/mob/living/basic/minebots/minebot.dm b/code/modules/mob/living/basic/minebots/minebot.dm index c9edfb0471f27..b14d1469ccd1f 100644 --- a/code/modules/mob/living/basic/minebots/minebot.dm +++ b/code/modules/mob/living/basic/minebots/minebot.dm @@ -42,13 +42,14 @@ ///the commands our owner can give us var/static/list/pet_commands = list( /datum/pet_command/idle/minebot, + /datum/pet_command/move, /datum/pet_command/protect_owner/minebot, /datum/pet_command/minebot_ability/light, /datum/pet_command/minebot_ability/dump, /datum/pet_command/automate_mining, /datum/pet_command/free/minebot, /datum/pet_command/follow, - /datum/pet_command/point_targeting/attack/minebot, + /datum/pet_command/attack/minebot, ) ///possible colors the bot can have var/static/list/possible_colors= list( diff --git a/code/modules/mob/living/basic/minebots/minebot_ai.dm b/code/modules/mob/living/basic/minebots/minebot_ai.dm index 8043fda65d0a6..51a4e43f66ab5 100644 --- a/code/modules/mob/living/basic/minebots/minebot_ai.dm +++ b/code/modules/mob/living/basic/minebots/minebot_ai.dm @@ -281,14 +281,16 @@ /datum/pet_command/automate_mining command_name = "Automate mining" command_desc = "Make your minebot automatically mine!" - radial_icon = 'icons/obj/mining.dmi' - radial_icon_state = "pickaxe" + radial_icon_state = "mine" speech_commands = list("mine") callout_type = /datum/callout_option/mine /datum/pet_command/automate_mining/valid_callout_target(mob/living/caller, datum/callout_option/callout, atom/target) return ismineralturf(target) +/datum/pet_command/automate_mining/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to start mining!" + /datum/pet_command/automate_mining/execute_action(datum/ai_controller/controller) controller.set_blackboard_key(BB_AUTOMATED_MINING, TRUE) controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) @@ -315,6 +317,9 @@ radial_icon_state = "mech_lights_off" ability_key = BB_MINEBOT_LIGHT_ABILITY +/datum/pet_command/minebot_ability/light/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to toggle its lights!" + /datum/pet_command/minebot_ability/dump command_name = "Dump ore" command_desc = "Make your minebot dump all its ore!" @@ -322,10 +327,13 @@ radial_icon_state = "mech_eject" ability_key = BB_MINEBOT_DUMP_ABILITY -/datum/pet_command/point_targeting/attack/minebot +/datum/pet_command/minebot_ability/light/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to dump its ore!" + +/datum/pet_command/attack/minebot attack_behaviour = /datum/ai_behavior/basic_ranged_attack/minebot -/datum/pet_command/point_targeting/attack/minebot/execute_action(datum/ai_controller/controller) +/datum/pet_command/attack/minebot/execute_action(datum/ai_controller/controller) controller.set_blackboard_key(BB_AUTOMATED_MINING, FALSE) var/mob/living/living_pawn = controller.pawn if(!living_pawn.combat_mode) diff --git a/code/modules/mob/living/basic/pets/dog/_dog.dm b/code/modules/mob/living/basic/pets/dog/_dog.dm index fd8920d2ca0e3..8fb053eef094c 100644 --- a/code/modules/mob/living/basic/pets/dog/_dog.dm +++ b/code/modules/mob/living/basic/pets/dog/_dog.dm @@ -7,10 +7,10 @@ speech_commands = list("good dog") // Set correct attack behaviour -/datum/pet_command/point_targeting/attack/dog +/datum/pet_command/attack/dog attack_behaviour = /datum/ai_behavior/basic_melee_attack/dog -/datum/pet_command/point_targeting/attack/dog/set_command_active(mob/living/parent, mob/living/commander) +/datum/pet_command/attack/dog/set_command_active(mob/living/parent, mob/living/commander) . = ..() parent.ai_controller.set_blackboard_key(BB_DOG_HARASS_HARM, TRUE) @@ -38,11 +38,12 @@ var/static/list/pet_commands = list( /datum/pet_command/idle, /datum/pet_command/free, + /datum/pet_command/move, /datum/pet_command/good_boy/dog, /datum/pet_command/follow/dog, /datum/pet_command/perform_trick_sequence, - /datum/pet_command/point_targeting/attack/dog, - /datum/pet_command/point_targeting/fetch, + /datum/pet_command/attack/dog, + /datum/pet_command/fetch, /datum/pet_command/play_dead, ) ///icon state of the collar we can wear diff --git a/code/modules/mob/living/basic/pets/fox.dm b/code/modules/mob/living/basic/pets/fox.dm index 737f7b21391fd..fab5e79a3134b 100644 --- a/code/modules/mob/living/basic/pets/fox.dm +++ b/code/modules/mob/living/basic/pets/fox.dm @@ -31,9 +31,10 @@ ///list of our pet commands we follow var/static/list/pet_commands = list( /datum/pet_command/idle, + /datum/pet_command/move, /datum/pet_command/free, /datum/pet_command/follow, - /datum/pet_command/point_targeting/attack, + /datum/pet_command/attack, /datum/pet_command/perform_trick_sequence, ) diff --git a/code/modules/mob/living/basic/pets/orbie/orbie.dm b/code/modules/mob/living/basic/pets/orbie/orbie.dm index b4099a8c6344c..202bc84d37eb1 100644 --- a/code/modules/mob/living/basic/pets/orbie/orbie.dm +++ b/code/modules/mob/living/basic/pets/orbie/orbie.dm @@ -39,8 +39,9 @@ var/static/list/pet_commands = list( /datum/pet_command/idle, /datum/pet_command/free, + /datum/pet_command/move, /datum/pet_command/untargeted_ability/pet_lights, - /datum/pet_command/point_targeting/use_ability/take_photo, + /datum/pet_command/use_ability/take_photo, /datum/pet_command/follow/orbie, /datum/pet_command/perform_trick_sequence, ) diff --git a/code/modules/mob/living/basic/pets/orbie/orbie_ai.dm b/code/modules/mob/living/basic/pets/orbie/orbie_ai.dm index a978b750d5036..aef780533795f 100644 --- a/code/modules/mob/living/basic/pets/orbie/orbie_ai.dm +++ b/code/modules/mob/living/basic/pets/orbie/orbie_ai.dm @@ -123,17 +123,24 @@ return SUBTREE_RETURN_FINISH_PLANNING return ..() -/datum/pet_command/point_targeting/use_ability/take_photo +/datum/pet_command/use_ability/pet_lights/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to toggle its lights!" + +/datum/pet_command/use_ability/take_photo command_name = "Photo" command_desc = "Make your pet take a photo!" - radial_icon = 'icons/mob/simple/pets.dmi' - radial_icon_state = "orbie_lights_action" + radial_icon = 'icons/obj/art/camera.dmi' + radial_icon_state = "camera" speech_commands = list("photo", "picture", "image") command_feedback = "Readys camera mode" pet_ability_key = BB_PHOTO_ABILITY targeting_strategy_key = BB_TARGETING_STRATEGY -/datum/pet_command/point_targeting/use_ability/take_photo/execute_action(datum/ai_controller/controller) +/datum/pet_command/use_ability/take_photo/retrieve_command_text(atom/living_pet, atom/target) + return isnull(target) ? null : "signals [living_pet] to take a photo of [target]!" + + +/datum/pet_command/use_ability/take_photo/execute_action(datum/ai_controller/controller) if(controller.blackboard[BB_VIRTUAL_PET_LEVEL] < 3) controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) return SUBTREE_RETURN_FINISH_PLANNING @@ -152,6 +159,9 @@ return FALSE return findtext(spoken_text, text_command) +/datum/pet_command/perform_trick_sequence/light/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to dance!" + /datum/pet_command/perform_trick_sequence/execute_action(datum/ai_controller/controller) var/mob/living/living_pawn = controller.pawn var/list/trick_sequence = controller.blackboard[BB_TRICK_SEQUENCE] diff --git a/code/modules/mob/living/basic/pets/pet_cult/pet_cult_ai.dm b/code/modules/mob/living/basic/pets/pet_cult/pet_cult_ai.dm index 77263b1748963..41b928aa75103 100644 --- a/code/modules/mob/living/basic/pets/pet_cult/pet_cult_ai.dm +++ b/code/modules/mob/living/basic/pets/pet_cult/pet_cult_ai.dm @@ -236,3 +236,6 @@ radial_icon_state = "1" speech_commands = list("rune", "revival") ability_key = BB_RUNE_ABILITY + +/datum/pet_command/untargeted_ability/draw_rune/retrieve_command_text(atom/living_pet, atom/target) + return "signals [living_pet] to draw a rune!" diff --git a/code/modules/mob/living/basic/slime/ai/pet_command.dm b/code/modules/mob/living/basic/slime/ai/pet_command.dm index 211d7aa552cd8..4b50b2b32b9e8 100644 --- a/code/modules/mob/living/basic/slime/ai/pet_command.dm +++ b/code/modules/mob/living/basic/slime/ai/pet_command.dm @@ -1,4 +1,4 @@ -/datum/pet_command/point_targeting/attack/slime +/datum/pet_command/attack/slime speech_commands = list("attack", "sic", "kill", "eat", "feed") command_feedback = "blorbles" pointed_reaction = "and blorbles" @@ -6,7 +6,7 @@ var/hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/slime -/datum/pet_command/point_targeting/attack/slime/execute_action(datum/ai_controller/controller) +/datum/pet_command/attack/slime/execute_action(datum/ai_controller/controller) var/mob/living/basic/slime/slime_pawn = controller.pawn if(isslime(slime_pawn) && slime_pawn.can_feed_on(controller.blackboard[BB_CURRENT_PET_TARGET], check_friendship = TRUE)) diff --git a/code/modules/mob/living/basic/slime/slime.dm b/code/modules/mob/living/basic/slime/slime.dm index 010913f44258b..8623f69b0e4cd 100644 --- a/code/modules/mob/living/basic/slime/slime.dm +++ b/code/modules/mob/living/basic/slime/slime.dm @@ -97,7 +97,7 @@ /datum/pet_command/idle, /datum/pet_command/free, /datum/pet_command/follow, - /datum/pet_command/point_targeting/attack/slime, + /datum/pet_command/attack/slime, ) /// Our evolve action diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp.dm b/code/modules/mob/living/basic/space_fauna/carp/carp.dm index 6f843857578a6..0e0ba3b39a1ae 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp.dm @@ -59,7 +59,7 @@ /datum/pet_command/idle, /datum/pet_command/free, /datum/pet_command/follow, - /datum/pet_command/point_targeting/attack + /datum/pet_command/attack ) /// Carp want to eat raw meat var/static/list/desired_food = list(/obj/item/food/meat/slab, /obj/item/food/meat/rawcutlet) diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm index 810c45603862b..26894850b1989 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm @@ -1,6 +1,6 @@ #define MAGICARP_SPELL_TARGET_SEEK_RANGE 4 -/datum/pet_command/point_targeting/use_ability/magicarp +/datum/pet_command/use_ability/magicarp pet_ability_key = BB_MAGICARP_SPELL /datum/ai_planning_subtree/attack_obstacle_in_path/carp diff --git a/code/modules/mob/living/basic/space_fauna/carp/magicarp.dm b/code/modules/mob/living/basic/space_fauna/carp/magicarp.dm index 65d16cfb490dd..3b6d1e5e922fd 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/magicarp.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/magicarp.dm @@ -56,8 +56,8 @@ GLOBAL_LIST_INIT(magicarp_spell_colours, list( /datum/pet_command/idle, /datum/pet_command/free, /datum/pet_command/follow, - /datum/pet_command/point_targeting/attack, - /datum/pet_command/point_targeting/use_ability/magicarp, + /datum/pet_command/attack, + /datum/pet_command/use_ability/magicarp, ) /// List of all projectiles we can fire. /// Non-static, because subtypes can have their own lists. diff --git a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_actions.dm b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_actions.dm index f7997e589695d..74f6c76b459e7 100644 --- a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_actions.dm +++ b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_actions.dm @@ -65,7 +65,7 @@ /datum/pet_command/free, /datum/pet_command/protect_owner, /datum/pet_command/follow, - /datum/pet_command/point_targeting/attack/mouse + /datum/pet_command/attack/mouse ) /// Commands you can give to glockroaches var/static/list/glockroach_commands = list( @@ -73,7 +73,7 @@ /datum/pet_command/free, /datum/pet_command/protect_owner/glockroach, /datum/pet_command/follow, - /datum/pet_command/point_targeting/attack/glockroach + /datum/pet_command/attack/glockroach ) /datum/action/cooldown/mob_cooldown/riot/IsAvailable(feedback = FALSE) @@ -211,7 +211,7 @@ return TRUE // Command you can give to a mouse to make it kill someone -/datum/pet_command/point_targeting/attack/mouse +/datum/pet_command/attack/mouse speech_commands = list("attack", "sic", "kill", "cheese em") command_feedback = "squeak!" // Frogs and roaches can squeak too it's fine pointed_reaction = "and squeaks aggressively" @@ -219,7 +219,7 @@ attack_behaviour = /datum/ai_behavior/basic_melee_attack // Command you can give to a mouse to make it kill someone -/datum/pet_command/point_targeting/attack/glockroach +/datum/pet_command/attack/glockroach speech_commands = list("attack", "sic", "kill", "cheese em") command_feedback = "squeak!" pointed_reaction = "and cocks its gun" diff --git a/icons/effects/mouse_pointers/pet_paw.dmi b/icons/effects/mouse_pointers/pet_paw.dmi new file mode 100644 index 0000000000000000000000000000000000000000..a4443a6896721c22686c526c79991c7388b32792 GIT binary patch literal 372 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 literal 0 HcmV?d00001 diff --git a/tgstation.dme b/tgstation.dme index 345b15060c4f7..bec1ca1f5a46d 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -187,6 +187,7 @@ #include "code\__DEFINES\pronouns.dm" #include "code\__DEFINES\qdel.dm" #include "code\__DEFINES\quirks.dm" +#include "code\__DEFINES\radial_defines.dm" #include "code\__DEFINES\radiation.dm" #include "code\__DEFINES\radio.dm" #include "code\__DEFINES\radioactive_nebula.dm" From 0bee351680a8b74ec8d03200ce7471c89ec19405 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 22 Dec 2024 02:05:56 +0000 Subject: [PATCH 065/235] Automatic changelog for PR #88495 [ci skip] --- html/changelogs/AutoChangeLog-pr-88495.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88495.yml diff --git a/html/changelogs/AutoChangeLog-pr-88495.yml b/html/changelogs/AutoChangeLog-pr-88495.yml new file mode 100644 index 0000000000000..05df567f9fb27 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88495.yml @@ -0,0 +1,5 @@ +author: "Ben10Omintrix" +delete-after: True +changes: + - qol: "holding shift and hovering over your pet will display a list of commands you can click from" + - bugfix: "fixes the fishing pet command not working" \ No newline at end of file From 6014464c8d7b0275fb86045bb50e60f30bfe234b Mon Sep 17 00:00:00 2001 From: grungussuss <96586172+Sadboysuss@users.noreply.github.com> Date: Sun, 22 Dec 2024 07:42:33 +0300 Subject: [PATCH 066/235] Removes an extra proc override in digitigrade pref logic (#88525) ## About The Pull Request they're doing the same thing and this one does a bitfield check when the value it's checking is a number, technically it works as intended right now but if we ever added more - it would break. ## Changelog :cl: grungussuss code: removed an extra proc override in digitigrade legs preference logic code /:cl: --- .../client/preferences/species_features/lizard.dm | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/code/modules/client/preferences/species_features/lizard.dm b/code/modules/client/preferences/species_features/lizard.dm index fbf27521365a3..3487edb78fdd4 100644 --- a/code/modules/client/preferences/species_features/lizard.dm +++ b/code/modules/client/preferences/species_features/lizard.dm @@ -128,17 +128,6 @@ var/datum/species/species_type = preferences.read_preference(/datum/preference/choiced/species) return initial(species_type.digitigrade_customization) == DIGITIGRADE_OPTIONAL - -/datum/preference/choiced/lizard_legs/is_accessible(datum/preferences/preferences) - . = ..() - - if(!.) - return - - var/datum/species/species_type = preferences.read_preference(/datum/preference/choiced/species) - - return initial(species_type.digitigrade_customization) & DIGITIGRADE_OPTIONAL - /datum/preference/choiced/lizard_snout savefile_key = "feature_lizard_snout" savefile_identifier = PREFERENCE_CHARACTER From c7dee65c75e5628887965e0587c19aeb1b693f6c Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 22 Dec 2024 04:42:52 +0000 Subject: [PATCH 067/235] Automatic changelog for PR #88525 [ci skip] --- html/changelogs/AutoChangeLog-pr-88525.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88525.yml diff --git a/html/changelogs/AutoChangeLog-pr-88525.yml b/html/changelogs/AutoChangeLog-pr-88525.yml new file mode 100644 index 0000000000000..7946ba940d96f --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88525.yml @@ -0,0 +1,4 @@ +author: "grungussuss" +delete-after: True +changes: + - code_imp: "removed an extra proc override in digitigrade legs preference logic code" \ No newline at end of file From 78aad8fe29f57566b195e71c03ea3153a72ded24 Mon Sep 17 00:00:00 2001 From: grungussuss <96586172+Sadboysuss@users.noreply.github.com> Date: Sun, 22 Dec 2024 07:44:11 +0300 Subject: [PATCH 068/235] Obliterates the survivors of sound/voice from orbit (#88429) ## About The Pull Request merge skew from a pr before the sound file reorganization ## Changelog no player facing changes --- .../living/basic/bots/repairbot/repairbot.dm | 18 +++++++++--------- .../non-humanoids}/repairbot/brick.ogg | Bin .../non-humanoids}/repairbot/cantanymore.ogg | Bin .../non-humanoids}/repairbot/entropy.ogg | Bin .../non-humanoids}/repairbot/fixit.ogg | Bin .../non-humanoids}/repairbot/fixtouch.ogg | Bin .../repairbot/passionproject.ogg | Bin .../repairbot/patchingholes.ogg | Bin .../non-humanoids}/repairbot/pay.ogg | Bin .../non-humanoids}/repairbot/strings.ogg | Bin 10 files changed, 9 insertions(+), 9 deletions(-) rename sound/{voice => mobs/non-humanoids}/repairbot/brick.ogg (100%) rename sound/{voice => mobs/non-humanoids}/repairbot/cantanymore.ogg (100%) rename sound/{voice => mobs/non-humanoids}/repairbot/entropy.ogg (100%) rename sound/{voice => mobs/non-humanoids}/repairbot/fixit.ogg (100%) rename sound/{voice => mobs/non-humanoids}/repairbot/fixtouch.ogg (100%) rename sound/{voice => mobs/non-humanoids}/repairbot/passionproject.ogg (100%) rename sound/{voice => mobs/non-humanoids}/repairbot/patchingholes.ogg (100%) rename sound/{voice => mobs/non-humanoids}/repairbot/pay.ogg (100%) rename sound/{voice => mobs/non-humanoids}/repairbot/strings.ogg (100%) diff --git a/code/modules/mob/living/basic/bots/repairbot/repairbot.dm b/code/modules/mob/living/basic/bots/repairbot/repairbot.dm index 17b257d8c987b..775e22101bc17 100644 --- a/code/modules/mob/living/basic/bots/repairbot/repairbot.dm +++ b/code/modules/mob/living/basic/bots/repairbot/repairbot.dm @@ -49,18 +49,18 @@ ) ///our neutral voicelines var/static/list/neutral_voicelines = list( - REPAIRBOT_VOICED_BRICK = 'sound/voice/repairbot/brick.ogg', - REPAIRBOT_VOICED_ENTROPY = 'sound/voice/repairbot/entropy.ogg', - REPAIRBOT_VOICED_FIX_IT = 'sound/voice/repairbot/fixit.ogg', - REPAIRBOT_VOICED_FIX_TOUCH = 'sound/voice/repairbot/fixtouch.ogg', - REPAIRBOT_VOICED_HOLE = 'sound/voice/repairbot/patchingholes.ogg', - REPAIRBOT_VOICED_PAY = 'sound/voice/repairbot/pay.ogg', + REPAIRBOT_VOICED_BRICK = 'sound/mobs/non-humanoids/repairbot/brick.ogg', + REPAIRBOT_VOICED_ENTROPY = 'sound/mobs/non-humanoids/repairbot/entropy.ogg', + REPAIRBOT_VOICED_FIX_IT = 'sound/mobs/non-humanoids/repairbot/fixit.ogg', + REPAIRBOT_VOICED_FIX_TOUCH = 'sound/mobs/non-humanoids/repairbot/fixtouch.ogg', + REPAIRBOT_VOICED_HOLE = 'sound/mobs/non-humanoids/repairbot/patchingholes.ogg', + REPAIRBOT_VOICED_PAY = 'sound/mobs/non-humanoids/repairbot/pay.ogg', ) ///our emagged voicelines var/static/list/emagged_voicelines = list( - REPAIRBOT_VOICED_ENTROPY = 'sound/voice/repairbot/entropy.ogg', - REPAIRBOT_VOICED_STRINGS = 'sound/voice/repairbot/strings.ogg', - REPAIRBOT_VOICED_PASSION = 'sound/voice/repairbot/passionproject.ogg', + REPAIRBOT_VOICED_ENTROPY = 'sound/mobs/non-humanoids/repairbot/entropy.ogg', + REPAIRBOT_VOICED_STRINGS = 'sound/mobs/non-humanoids/repairbot/strings.ogg', + REPAIRBOT_VOICED_PASSION = 'sound/mobs/non-humanoids/repairbot/passionproject.ogg', ) ///types we can retrieve from our ui var/static/list/retrievable_types = list( diff --git a/sound/voice/repairbot/brick.ogg b/sound/mobs/non-humanoids/repairbot/brick.ogg similarity index 100% rename from sound/voice/repairbot/brick.ogg rename to sound/mobs/non-humanoids/repairbot/brick.ogg diff --git a/sound/voice/repairbot/cantanymore.ogg b/sound/mobs/non-humanoids/repairbot/cantanymore.ogg similarity index 100% rename from sound/voice/repairbot/cantanymore.ogg rename to sound/mobs/non-humanoids/repairbot/cantanymore.ogg diff --git a/sound/voice/repairbot/entropy.ogg b/sound/mobs/non-humanoids/repairbot/entropy.ogg similarity index 100% rename from sound/voice/repairbot/entropy.ogg rename to sound/mobs/non-humanoids/repairbot/entropy.ogg diff --git a/sound/voice/repairbot/fixit.ogg b/sound/mobs/non-humanoids/repairbot/fixit.ogg similarity index 100% rename from sound/voice/repairbot/fixit.ogg rename to sound/mobs/non-humanoids/repairbot/fixit.ogg diff --git a/sound/voice/repairbot/fixtouch.ogg b/sound/mobs/non-humanoids/repairbot/fixtouch.ogg similarity index 100% rename from sound/voice/repairbot/fixtouch.ogg rename to sound/mobs/non-humanoids/repairbot/fixtouch.ogg diff --git a/sound/voice/repairbot/passionproject.ogg b/sound/mobs/non-humanoids/repairbot/passionproject.ogg similarity index 100% rename from sound/voice/repairbot/passionproject.ogg rename to sound/mobs/non-humanoids/repairbot/passionproject.ogg diff --git a/sound/voice/repairbot/patchingholes.ogg b/sound/mobs/non-humanoids/repairbot/patchingholes.ogg similarity index 100% rename from sound/voice/repairbot/patchingholes.ogg rename to sound/mobs/non-humanoids/repairbot/patchingholes.ogg diff --git a/sound/voice/repairbot/pay.ogg b/sound/mobs/non-humanoids/repairbot/pay.ogg similarity index 100% rename from sound/voice/repairbot/pay.ogg rename to sound/mobs/non-humanoids/repairbot/pay.ogg diff --git a/sound/voice/repairbot/strings.ogg b/sound/mobs/non-humanoids/repairbot/strings.ogg similarity index 100% rename from sound/voice/repairbot/strings.ogg rename to sound/mobs/non-humanoids/repairbot/strings.ogg From 2603a4f3eeac6abb5e7457c1e07e1f3f256c9e3f Mon Sep 17 00:00:00 2001 From: Roxy <75404941+TealSeer@users.noreply.github.com> Date: Sat, 21 Dec 2024 23:46:09 -0500 Subject: [PATCH 069/235] Add reboot countdown to stat panel (#88438) ## About The Pull Request Rewrite ticker `Reboot` proc slightly to use a timer and callback for the delay before the reboot, tracks this timer in the stat panel for players to see. Also adds a verb to cancel a pending reboot independent of the delay round end verb. ## Why It's Good For The Game It's nice to be able to see exactly how much time is left until the server restarts, and it's even nicer to see that the round end has been delayed when the "admin has delayed round end" message gets buried in a fast moving chat. ## Changelog :cl: qol: The time until the server reboots is now visible in the status tab. admin: Added a cancel reboot verb to the server tab. /:cl: --- code/controllers/subsystem/statpanel.dm | 9 +++++++++ code/controllers/subsystem/ticker.dm | 25 +++++++++++++++++++++---- code/modules/admin/verbs/server.dm | 8 ++++++++ 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm index 382ff90cdc844..be89e7fa6271a 100644 --- a/code/controllers/subsystem/statpanel.dm +++ b/code/controllers/subsystem/statpanel.dm @@ -37,6 +37,15 @@ SUBSYSTEM_DEF(statpanels) var/ETA = SSshuttle.emergency.getModeStr() if(ETA) global_data += "[ETA] [SSshuttle.emergency.getTimerStr()]" + + if(SSticker.reboot_timer) + var/reboot_time = timeleft(SSticker.reboot_timer) + if(reboot_time) + global_data += "Reboot: [DisplayTimeText(reboot_time, 1)]" + // admin must have delayed round end + else if(SSticker.ready_for_reboot) + global_data += "Reboot: DELAYED" + src.currentrun = GLOB.clients.Copy() mc_data = null diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index bb18a45b72d9a..03e7b6dcbd885 100644 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -67,6 +67,9 @@ SUBSYSTEM_DEF(ticker) /// Why an emergency shuttle was called var/emergency_reason + /// ID of round reboot timer, if it exists + var/reboot_timer = null + /datum/controller/subsystem/ticker/Initialize() var/list/byond_sound_formats = list( "mid" = TRUE, @@ -698,11 +701,10 @@ SUBSYSTEM_DEF(ticker) var/start_wait = world.time UNTIL(round_end_sound_sent || (world.time - start_wait) > (delay * 2)) //don't wait forever - sleep(delay - (world.time - start_wait)) + reboot_timer = addtimer(CALLBACK(src, PROC_REF(reboot_callback), reason, end_string), delay - (world.time - start_wait), TIMER_STOPPABLE) - if(delay_end && !skip_delay) - to_chat(world, span_boldannounce("Reboot was cancelled by an admin.")) - return + +/datum/controller/subsystem/ticker/proc/reboot_callback(reason, end_string) if(end_string) end_state = end_string @@ -710,6 +712,21 @@ SUBSYSTEM_DEF(ticker) world.Reboot() +/** + * Deletes the current reboot timer and nulls the var + * + * Arguments: + * * user - the user that cancelled the reboot, may be null + */ +/datum/controller/subsystem/ticker/proc/cancel_reboot(mob/user) + if(!reboot_timer) + to_chat(user, span_warning("There is no pending reboot!")) + return FALSE + to_chat(world, span_boldannounce("An admin has delayed the round end.")) + deltimer(reboot_timer) + reboot_timer = null + return TRUE + /datum/controller/subsystem/ticker/Shutdown() gather_newscaster() //called here so we ensure the log is created even upon admin reboot if(!round_end_sound) diff --git a/code/modules/admin/verbs/server.dm b/code/modules/admin/verbs/server.dm index 5ac9fd272b45c..133d36c3dd0f0 100644 --- a/code/modules/admin/verbs/server.dm +++ b/code/modules/admin/verbs/server.dm @@ -66,6 +66,12 @@ ADMIN_VERB(restart, R_SERVER, "Reboot World", "Restarts the world immediately.", #undef HARDEST_RESTART #undef TGS_RESTART +ADMIN_VERB(cancel_reboot, R_SERVER, "Cancel Reboot", "Cancels a pending world reboot.", ADMIN_CATEGORY_SERVER) + if(!SSticker.cancel_reboot(user)) + return + log_admin("[key_name(user)] cancelled the pending world reboot.") + message_admins("[key_name_admin(user)] cancelled the pending world reboot.") + ADMIN_VERB(end_round, R_SERVER, "End Round", "Forcibly ends the round and allows the server to restart normally.", ADMIN_CATEGORY_SERVER) var/confirm = tgui_alert(user, "End the round and restart the game world?", "End Round", list("Yes", "Cancel")) if(confirm != "Yes") @@ -131,6 +137,8 @@ ADMIN_VERB(delay_round_end, R_SERVER, "Delay Round End", "Prevent the server fro SSticker.delay_end = TRUE SSticker.admin_delay_notice = delay_reason + if(SSticker.reboot_timer) + SSticker.cancel_reboot(user) log_admin("[key_name(user)] delayed the round end for reason: [SSticker.admin_delay_notice]") message_admins("[key_name_admin(user)] delayed the round end for reason: [SSticker.admin_delay_notice]") From 0c2ef51b146affd639db68e71b3576cf83ff3f0f Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 22 Dec 2024 04:46:28 +0000 Subject: [PATCH 070/235] Automatic changelog for PR #88438 [ci skip] --- html/changelogs/AutoChangeLog-pr-88438.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88438.yml diff --git a/html/changelogs/AutoChangeLog-pr-88438.yml b/html/changelogs/AutoChangeLog-pr-88438.yml new file mode 100644 index 0000000000000..f76306a4b90a5 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88438.yml @@ -0,0 +1,5 @@ +author: "TealSeer" +delete-after: True +changes: + - qol: "The time until the server reboots is now visible in the status tab." + - admin: "Added a cancel reboot verb to the server tab." \ No newline at end of file From c0b7ec5fbd731aff38bbce41ce26d5bc05b51f62 Mon Sep 17 00:00:00 2001 From: Rhials <28870487+Rhials@users.noreply.github.com> Date: Sun, 22 Dec 2024 13:16:43 -0500 Subject: [PATCH 071/235] Nuclear Operatives can buy a vintage pinpointer from the Badassery shop section (#88621) --- code/modules/uplink/uplink_items/badass.dm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/code/modules/uplink/uplink_items/badass.dm b/code/modules/uplink/uplink_items/badass.dm index 5c5e0390b5046..9a346da2d5351 100644 --- a/code/modules/uplink/uplink_items/badass.dm +++ b/code/modules/uplink/uplink_items/badass.dm @@ -106,3 +106,9 @@ Contains enough special solution to spray a single super-size seditious symbol, subjecting station staff to slippery suffering." item = /obj/item/traitor_spraycan cost = 1 + +/datum/uplink_item/badass/pinpointer + name = "Surplus Pinpointer" + desc = "Provides a surplus pinpointer, left over from the previous models that were abandoned in favor of a SAAS cloud-based PDA app." + item = /obj/item/pinpointer/nuke/syndicate + cost = 2 From 2ca491e194b59426337db60641020680ae8a37b6 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sun, 22 Dec 2024 18:17:01 +0000 Subject: [PATCH 072/235] Automatic changelog for PR #88621 [ci skip] --- html/changelogs/AutoChangeLog-pr-88621.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88621.yml diff --git a/html/changelogs/AutoChangeLog-pr-88621.yml b/html/changelogs/AutoChangeLog-pr-88621.yml new file mode 100644 index 0000000000000..c326d59dfc745 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88621.yml @@ -0,0 +1,4 @@ +author: "Rhials" +delete-after: True +changes: + - rscadd: "Nukie uplinks now offer an OG-style nuke pinpointer in their Badassery shop section." \ No newline at end of file From 7d9386bdcecc30615727818725e458e708fac800 Mon Sep 17 00:00:00 2001 From: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com> Date: Mon, 23 Dec 2024 01:35:45 +0200 Subject: [PATCH 073/235] Turtles (#87493) ## About The Pull Request adds turtles to the game! but these aren't your typical turtles. ![turtlestates](https://github.com/user-attachments/assets/b9d35a32-5f04-4255-bbac-d1ed57b2abb1) these are flora-turtles, with giant trees growing on their shells. These trees can emit fields which affects nearby hydroponic plants. Initially, the trees start out as young buds, from there the tree can evolve into different types depending on what you feed the turtle. Feeding them pesticides causes the tree to blossom to be purple. this tree's fields will help kill some pests and weeds in nearby plants. Feeding them nutrients gives you the green tree, the fields will heal nearby plants Feeding them mutators like uranium or left 4 zed gives you the yellow tree. the fields increase instability of plants The turtle will emit these fields every once in a while, ONLY when its feeling happy. therefore you'll have to pet it, clean it and feed it every once in a while to keep it satisfied. You can view the turtle's happiness by shift clicking it. https://github.com/user-attachments/assets/a47136a1-06a1-419e-acc2-2f6f4468e296 The turtle only eats seeds. after eating a seed, itll process it and spit out its corresponding fruit! (for example, feeding it an apple seed gives you an apple). itll also sometimes playfully headbutt your legs and it loves going around smelling the scent from nearby plants you can get these turtles by fishing the hydroponics tray or by ordering them through cargo. ## Why It's Good For The Game adds a new fun way for botanists to take care of their plants. While these turtles alone arent enough to fully replace plant dedicated nutrients, they add small extra support. ## Changelog :cl: add: adds flora-turtles. obtainable through cargo or by fishing from the hydroponics tray /:cl: --- code/__DEFINES/ai/ai_blackboard.dm | 8 + code/__DEFINES/ai/monsters.dm | 8 + .../basic_subtrees/express_happiness.dm | 6 +- code/datums/elements/basic_eating.dm | 6 +- code/modules/cargo/packs/livestock.dm | 7 + code/modules/fishing/sources/_fish_source.dm | 5 +- code/modules/fishing/sources/source_types.dm | 1 + code/modules/hydroponics/hydroponics.dm | 2 +- .../modules/mob/living/basic/turtle/turtle.dm | 240 ++++++++++++++++++ .../mob/living/basic/turtle/turtle_ability.dm | 140 ++++++++++ .../mob/living/basic/turtle/turtle_ai.dm | 92 +++++++ icons/mob/simple/pets.dmi | Bin 87208 -> 91503 bytes icons/mob/simple/turtle_trees.dmi | Bin 0 -> 1263 bytes tgstation.dme | 3 + 14 files changed, 511 insertions(+), 7 deletions(-) create mode 100644 code/modules/mob/living/basic/turtle/turtle.dm create mode 100644 code/modules/mob/living/basic/turtle/turtle_ability.dm create mode 100644 code/modules/mob/living/basic/turtle/turtle_ai.dm create mode 100644 icons/mob/simple/turtle_trees.dmi diff --git a/code/__DEFINES/ai/ai_blackboard.dm b/code/__DEFINES/ai/ai_blackboard.dm index b5a7ad1ddfaac..f7f77a7169ea2 100644 --- a/code/__DEFINES/ai/ai_blackboard.dm +++ b/code/__DEFINES/ai/ai_blackboard.dm @@ -195,6 +195,14 @@ #define BB_DRILLABLE_ICE "BB_drillable_ice" +//emotions we displays depending on our happiness +///emotions we display when happy +#define BB_HAPPY_EMOTIONS "happy_emotions" +///emotions we display when neutral +#define BB_MODERATE_EMOTIONS "moderate_emotions" +///emotions we display when depressed +#define BB_SAD_EMOTIONS "sad_emotions" + // Keys used by one and only one behavior // Used to hold state without making bigass lists /// For /datum/ai_behavior/find_potential_targets, what if any field are we using currently diff --git a/code/__DEFINES/ai/monsters.dm b/code/__DEFINES/ai/monsters.dm index 330e2d48eb226..d77817a203980 100644 --- a/code/__DEFINES/ai/monsters.dm +++ b/code/__DEFINES/ai/monsters.dm @@ -304,3 +304,11 @@ #define BB_DEER_RESTING "deer_resting" ///time till our next rest duration #define BB_DEER_NEXT_REST_TIMER "deer_next_rest_timer" + +//turtle +///our tree's ability +#define BB_TURTLE_TREE_ABILITY "turtle_tree_ability" +///people we headbutt! +#define BB_TURTLE_HEADBUTT_VICTIM "turtle_headbutt_victim" +///flore we must smell +#define BB_TURTLE_FLORA_TARGET "turtle_flora_target" diff --git a/code/datums/ai/basic_mobs/basic_subtrees/express_happiness.dm b/code/datums/ai/basic_mobs/basic_subtrees/express_happiness.dm index 6cae6132d3688..4d7a3e7ad3064 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/express_happiness.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/express_happiness.dm @@ -31,11 +31,11 @@ var/list/final_list switch(happiness_value) if(HIGH_HAPPINESS_THRESHOLD to INFINITY) - final_list = happy_emotions + final_list = controller.blackboard[BB_HAPPY_EMOTIONS] || happy_emotions if(MODERATE_HAPPINESS_THRESHOLD to HIGH_HAPPINESS_THRESHOLD) - final_list = moderate_emotions + final_list = controller.blackboard[BB_MODERATE_EMOTIONS] || moderate_emotions else - final_list = depressed_emotions + final_list = controller.blackboard[BB_SAD_EMOTIONS] || depressed_emotions if(!length(final_list)) return controller.queue_behavior(/datum/ai_behavior/perform_emote, pick(final_list)) diff --git a/code/datums/elements/basic_eating.dm b/code/datums/elements/basic_eating.dm index 75caa272ef9bd..4be983b32113c 100644 --- a/code/datums/elements/basic_eating.dm +++ b/code/datums/elements/basic_eating.dm @@ -46,8 +46,10 @@ SIGNAL_HANDLER if(user.combat_mode || !is_type_in_list(possible_food, food_types)) return NONE - - try_eating(source, possible_food, user) + var/mob/living/living_source = source + if(living_source.stat != CONSCIOUS) + return NONE + return try_eating(source, possible_food, user) ? ITEM_INTERACT_SUCCESS : NONE /datum/element/basic_eating/proc/on_unarm_attack(mob/living/eater, atom/target, proximity, modifiers) SIGNAL_HANDLER diff --git a/code/modules/cargo/packs/livestock.dm b/code/modules/cargo/packs/livestock.dm index 62008d8be48e0..da51ee497f66d 100644 --- a/code/modules/cargo/packs/livestock.dm +++ b/code/modules/cargo/packs/livestock.dm @@ -259,3 +259,10 @@ cost = CARGO_CRATE_VALUE * 2 contains = list(/obj/item/storage/fish_case/tiziran = 2) crate_name = "tiziran fish crate" + +/datum/supply_pack/critter/turtle + name = "Turtle Crate" + desc = "Cute flora turtles that'll emit good vibes to nearby plants!" + cost = CARGO_CRATE_VALUE * 2 + contains = list(/mob/living/basic/turtle) + crate_name = "flora-turtle crate" diff --git a/code/modules/fishing/sources/_fish_source.dm b/code/modules/fishing/sources/_fish_source.dm index abf3a298462cb..cb0eaecc9890d 100644 --- a/code/modules/fishing/sources/_fish_source.dm +++ b/code/modules/fishing/sources/_fish_source.dm @@ -367,7 +367,10 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons()) final_table[result] *= rod.hook.get_hook_bonus_multiplicative(result) final_table[result] += rod.hook.get_hook_bonus_additive(result)//Decide on order here so it can be multiplicative - if(ispath(result, /obj/item/fish) || isfish(result)) + if(ispath(result, /mob/living) && bait && (HAS_TRAIT(bait, TRAIT_GOOD_QUALITY_BAIT) || HAS_TRAIT(bait, TRAIT_GREAT_QUALITY_BAIT))) + final_table[result] = round(final_table[result] * result_multiplier, 1) + + else if(ispath(result, /obj/item/fish) || isfish(result)) if(bait) final_table[result] = round(final_table[result] * result_multiplier, 1) var/mult = bait.check_bait(result) diff --git a/code/modules/fishing/sources/source_types.dm b/code/modules/fishing/sources/source_types.dm index 6f2a38d4d6146..8177651973546 100644 --- a/code/modules/fishing/sources/source_types.dm +++ b/code/modules/fishing/sources/source_types.dm @@ -507,6 +507,7 @@ /obj/item/seeds/random = 1, /mob/living/basic/frog = 1, /mob/living/basic/axolotl = 1, + /mob/living/basic/turtle = 2, ) fish_counts = list( /obj/item/food/grown/grass = 10, diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm index c8a3e10b8e931..ac2e0e3b767f6 100644 --- a/code/modules/hydroponics/hydroponics.dm +++ b/code/modules/hydroponics/hydroponics.dm @@ -559,7 +559,7 @@ if(weedlevel == new_weedlevel) return SEND_SIGNAL(src, COMSIG_HYDROTRAY_SET_WEEDLEVEL, new_weedlevel) - weedlevel = new_weedlevel + weedlevel = max(new_weedlevel, 0) if(update_icon) update_appearance() diff --git a/code/modules/mob/living/basic/turtle/turtle.dm b/code/modules/mob/living/basic/turtle/turtle.dm new file mode 100644 index 0000000000000..4d7c73edec6ca --- /dev/null +++ b/code/modules/mob/living/basic/turtle/turtle.dm @@ -0,0 +1,240 @@ +#define PATH_PEST_KILLER "path_pest_killer" +#define PATH_PLANT_HEALER "path_plant_healer" +#define PATH_PLANT_MUTATOR "path_plant_mutator" +#define REQUIRED_TREE_GROWTH 250 +#define UPPER_BOUND_VOLUME 50 +#define LOWER_BOUND_VOLUME 10 + +/mob/living/basic/turtle + name = "turtle" + desc = "Dog." + icon_state = "turtle" + icon_living = "turtle" + icon_dead = "turtle_dead" + base_icon_state = "turtle" + icon = 'icons/mob/simple/pets.dmi' + butcher_results = list(/obj/item/food/meat/slab = 3, /obj/item/food/pickle = 1, /obj/item/stack/sheet/mineral/wood = 10) + mob_biotypes = MOB_ORGANIC | MOB_PLANT + mobility_flags = MOBILITY_FLAGS_REST_CAPABLE_DEFAULT + health = 100 + maxHealth = 100 + speed = 5 + verb_say = "snaps" + verb_ask = "snaps curiously" + verb_exclaim = "snaps loudly" + verb_yell = "snaps loudly" + faction = list(FACTION_NEUTRAL) + ai_controller = /datum/ai_controller/basic_controller/turtle + ///our displayed tree + var/mutable_appearance/grown_tree + ///growth progress of our tree + var/list/path_growth_progress = list( + PATH_PLANT_HEALER = 0, + PATH_PLANT_MUTATOR = 0, + PATH_PEST_KILLER = 0, + ) + ///what nutrients leads to each evolution path + var/static/list/path_requirements = list( + //plant healers + /datum/reagent/plantnutriment/eznutriment = PATH_PLANT_HEALER, + /datum/reagent/plantnutriment/robustharvestnutriment = PATH_PLANT_HEALER, + /datum/reagent/plantnutriment/endurogrow = PATH_PLANT_HEALER, + //plant mutators + /datum/reagent/plantnutriment/left4zednutriment = PATH_PLANT_MUTATOR, + /datum/reagent/uranium = PATH_PLANT_MUTATOR, + //pest killers + /datum/reagent/toxin/pestkiller = PATH_PEST_KILLER, + ) + ///if we are fully grown, what is our path + var/developed_path + ///our last east/west direction + var/last_direction = WEST + +/mob/living/basic/turtle/Initialize(mapload) + . = ..() + + desc = pick( + "Likely Dog...", + "Praise the Dog!", + "Dog ahead.", + "Could this be a Dog?", + ) + var/static/list/eatable_food = list(/obj/item/seeds) + ai_controller.set_blackboard_key(BB_BASIC_FOODS, typecacheof(eatable_food)) + AddElement(/datum/element/basic_eating, food_types = eatable_food) + AddComponent(/datum/component/happiness) + RegisterSignal(src, COMSIG_MOB_PRE_EAT, PROC_REF(pre_eat_food)) + update_appearance() + create_reagents(150, REAGENT_HOLDER_ALIVE) + add_verb(src, /mob/living/proc/toggle_resting) + START_PROCESSING(SSprocessing, src) + +/mob/living/basic/turtle/setDir(newdir) + if(REVERSE_DIR(last_direction) & newdir) + transform = transform.Scale(-1, 1) + last_direction = REVERSE_DIR(last_direction) + return ..() + +/mob/living/basic/turtle/proc/retrieve_destined_path() + var/current_max_growth = 0 + var/destined_path + for(var/evolution_path in path_growth_progress) + if(path_growth_progress[evolution_path] > current_max_growth) + destined_path = evolution_path + current_max_growth = path_growth_progress[evolution_path] + if(isnull(destined_path)) + destined_path = PATH_PLANT_HEALER + return destined_path + +/mob/living/basic/turtle/process(seconds_per_tick) + if(isnull(reagents) || !length(reagents.reagent_list)) //if we have no reagents, default to our highest destined path + set_plant_growth(retrieve_destined_path(), 0.5) + return + + for(var/datum/reagent/existing_reagent as anything in reagents.reagent_list) + var/evolution_path = path_requirements[existing_reagent.type] + + switch(existing_reagent.volume) + if(UPPER_BOUND_VOLUME to INFINITY) + set_plant_growth(evolution_path, 3) + if(LOWER_BOUND_VOLUME to UPPER_BOUND_VOLUME) + set_plant_growth(evolution_path, 2) + if(1 to LOWER_BOUND_VOLUME) + set_plant_growth(evolution_path, 1) + + reagents.remove_reagent(existing_reagent.type, 0.5) + +/mob/living/basic/turtle/proc/set_plant_growth(evolution_path, amount) + path_growth_progress[evolution_path] += amount + if(path_growth_progress[evolution_path] >= REQUIRED_TREE_GROWTH) + evolve_turtle(evolution_path) + +/mob/living/basic/turtle/examine(mob/user) + . = ..() + + if(stat == DEAD) + . += span_notice("Its tree seems to be all withered...") + return + + var/destined_path = retrieve_destined_path() + var/current_max_growth = path_growth_progress[destined_path] + + var/text_to_display = "Its tree seems to be exuding " + switch(destined_path) + if(PATH_PEST_KILLER) + text_to_display += "pest killing" + if(PATH_PLANT_HEALER) + text_to_display += "plant healing" + if(PATH_PLANT_MUTATOR) + text_to_display += "plant mutating" + + text_to_display += " properties... which [current_max_growth >= REQUIRED_TREE_GROWTH ? "seems to be fully grown" : "is yet to develop"]." + . += span_notice(text_to_display) + + +/mob/living/basic/turtle/proc/evolve_turtle(evolution_path) + var/static/list/evolution_gains = list( + PATH_PLANT_HEALER = list( + "tree_appearance" = "healer_tree", + "tree_ability" = /datum/action/cooldown/mob_cooldown/turtle_tree/healer, + ), + PATH_PEST_KILLER = list( + "tree_appearance" = "killer_tree", + "tree_ability" = /datum/action/cooldown/mob_cooldown/turtle_tree/killer, + ), + PATH_PLANT_MUTATOR = list( + "tree_appearance" = "mutator_tree", + "tree_ability" = /datum/action/cooldown/mob_cooldown/turtle_tree/mutator, + ), + ) + + var/tree_icon_state = evolution_gains[evolution_path]["tree_appearance"] + grown_tree = mutable_appearance(icon = 'icons/mob/simple/turtle_trees.dmi', icon_state = tree_icon_state) + + var/new_ability_path = evolution_gains[evolution_path]["tree_ability"] + developed_path = evolution_path + var/datum/action/cooldown/tree_ability = new new_ability_path(src) + tree_ability?.Grant(src) + ai_controller?.set_blackboard_key(BB_TURTLE_TREE_ABILITY, tree_ability) + STOP_PROCESSING(SSprocessing, src) + update_appearance() + +/mob/living/basic/turtle/update_icon_state() + . = ..() + if(stat == DEAD) + return + icon_state = resting ? "[base_icon_state]_resting" : base_icon_state + +/mob/living/basic/turtle/update_overlays() + . = ..() + if(stat == DEAD) + var/mutable_appearance/dead_overlay = mutable_appearance(icon = 'icons/mob/simple/pets.dmi', icon_state = developed_path ? "dead_tree" : "growing_tree") + dead_overlay.pixel_y = -2 + . += dead_overlay + return + var/pixel_offset = resting ? -2 : 2 + var/mutable_appearance/living_tree = grown_tree ? grown_tree : mutable_appearance(icon = icon, icon_state = "growing_tree") + living_tree.pixel_y = pixel_offset + . += living_tree + +/mob/living/basic/turtle/update_resting() + . = ..() + if(stat == DEAD) + return + update_appearance() + +/mob/living/basic/turtle/item_interaction(mob/living/user, obj/item/used_item, list/modifiers) + if(!istype(used_item, /obj/item/reagent_containers)) + return NONE + + if(isnull(used_item.reagents)) + balloon_alert(user, "empty!") + return ITEM_INTERACT_SUCCESS + + if(stat == DEAD) + balloon_alert(user, "its dead!") + return ITEM_INTERACT_SUCCESS + + var/should_transfer = FALSE + for(var/reagent in path_requirements) + if(used_item.reagents.has_reagent(reagent)) + should_transfer = TRUE + break + + if(!should_transfer) + balloon_alert(user, "refuses to drink!") + return ITEM_INTERACT_SUCCESS + + if(!do_after(user, 1.5 SECONDS, target = src)) + return ITEM_INTERACT_SUCCESS + + used_item.reagents.trans_to(reagents, 5) + balloon_alert(user, "drinks happily") + playsound(src, 'sound/items/drink.ogg', vol = 25, vary = TRUE) + return ITEM_INTERACT_SUCCESS + +/mob/living/basic/turtle/proc/pre_eat_food(datum/source, obj/item/seeds/potential_food) + SIGNAL_HANDLER + + if(!istype(potential_food)) + return NONE + if(ispath(potential_food.product, /obj/item/food/grown)) + addtimer(CALLBACK(src, PROC_REF(process_food), potential_food.product), 30 SECONDS) + return NONE + +/mob/living/basic/turtle/proc/process_food(food_path) + if(QDELETED(src) || stat != CONSCIOUS) + return + new food_path(drop_location()) + balloon_alert_to_viewers("spits out some food") + +/mob/living/basic/turtle/death(gibbed) + . = ..() + STOP_PROCESSING(SSprocessing, src) + +#undef PATH_PEST_KILLER +#undef PATH_PLANT_HEALER +#undef PATH_PLANT_MUTATOR +#undef REQUIRED_TREE_GROWTH +#undef UPPER_BOUND_VOLUME +#undef LOWER_BOUND_VOLUME diff --git a/code/modules/mob/living/basic/turtle/turtle_ability.dm b/code/modules/mob/living/basic/turtle/turtle_ability.dm new file mode 100644 index 0000000000000..3c3172b8cd558 --- /dev/null +++ b/code/modules/mob/living/basic/turtle/turtle_ability.dm @@ -0,0 +1,140 @@ +#define TREE_FIELD_DURATION_EFFECT 10 SECONDS +#define WARP_ANIMATE_TIME 0.35 SECONDS + +/datum/action/cooldown/mob_cooldown/turtle_tree + name = "Tree Ability" + desc = "Invoke your tree's special ability." + cooldown_time = 2 MINUTES + click_to_activate = FALSE + button_icon = 'icons/mob/simple/pets.dmi' + button_icon_state = "turtle" + ///type of effect our tree releases + var/effect_path + ///how many times our ability affects surroundings + var/maximum_intervals = 5 + ///time between each interval + var/time_between_intervals = 3 SECONDS + ///range our tree affects + var/tree_range = 5 + ///warp effect to apply some distortion to our field + var/atom/movable/warp_effect/turtle_field/warp + +/datum/action/cooldown/mob_cooldown/turtle_tree/Activate(atom/target) + . = ..() + warp = new(owner) + RegisterSignal(warp, COMSIG_QDELETING, PROC_REF(remove_warp)) + warp.alpha = 0 + owner.vis_contents += warp + + for(var/index in 0 to maximum_intervals) + addtimer(CALLBACK(src, PROC_REF(tree_effect)), time_between_intervals * index) + + animate(warp, transform = matrix(), alpha = warp::alpha, time = WARP_ANIMATE_TIME) + addtimer(CALLBACK(src, PROC_REF(warp_extinguish)), (time_between_intervals * maximum_intervals) + 3 SECONDS) + +/datum/action/cooldown/mob_cooldown/turtle_tree/proc/warp_extinguish() + if(QDELETED(warp)) + return + animate(warp, alpha = 0, time = WARP_ANIMATE_TIME) + addtimer(CALLBACK(src, PROC_REF(remove_warp)), WARP_ANIMATE_TIME) + +/datum/action/cooldown/mob_cooldown/turtle_tree/proc/remove_warp() + SIGNAL_HANDLER + + UnregisterSignal(warp, COMSIG_QDELETING) + warp = null + +///effect we apply on our trees +/datum/action/cooldown/mob_cooldown/turtle_tree/proc/tree_effect() + SHOULD_CALL_PARENT(TRUE) + return (pre_effect_apply()) + +///things we should check for before applying our effects +/datum/action/cooldown/mob_cooldown/turtle_tree/proc/pre_effect_apply() + if(QDELETED(owner) || owner.stat == DEAD) + return FALSE + var/obj/effect/tree_effect = new effect_path + owner.vis_contents += tree_effect + return TRUE + +///healer tree, heals nearby plants by small amounts +/datum/action/cooldown/mob_cooldown/turtle_tree/healer + effect_path = /obj/effect/temp_visual/circle_wave/tree/healer + ///amount we heal plants by + var/heal_amount = 5 + +/datum/action/cooldown/mob_cooldown/turtle_tree/healer/tree_effect() + . = ..() + if(!.) + return + for(var/obj/machinery/hydroponics/hydro in oview(tree_range, owner)) + if(isnull(hydro.myseed)) + continue + hydro.adjust_plant_health(heal_amount) + +///killer tree, kills plant's pests and weeds, aswell as nearby vermin +/datum/action/cooldown/mob_cooldown/turtle_tree/killer + effect_path = /obj/effect/temp_visual/circle_wave/tree/killer + ///amount we heal plants by + var/vermin_damage_amount = 20 + ///type of vermin our field affects + var/static/list/vermin_mob_targets = typecacheof(list( + /mob/living/basic/cockroach, + /mob/living/basic/mouse/rat, + )) + ///how much we reduce weed levels + var/weed_level_reduce = 2 + +/datum/action/cooldown/mob_cooldown/turtle_tree/killer/tree_effect() + . = ..() + if(!.) + return + + for(var/atom/possible_target as anything in oview(tree_range, owner)) + + if(is_type_in_typecache(possible_target, vermin_mob_targets)) + var/mob/living/living_target = possible_target + living_target.apply_damage(vermin_damage_amount) + continue + + if(!istype(possible_target, /obj/machinery/hydroponics)) + continue + + var/obj/machinery/hydroponics/hydro = possible_target + if(isnull(hydro.myseed)) + continue + hydro.set_weedlevel(hydro.weedlevel - weed_level_reduce) + +///mutator tree, mutates nearby plants! +/datum/action/cooldown/mob_cooldown/turtle_tree/mutator + effect_path = /obj/effect/temp_visual/circle_wave/tree/mutator + ///how much we mutate plants + var/mutator_boost = 1 + +/datum/action/cooldown/mob_cooldown/turtle_tree/mutator/tree_effect() + . = ..() + if(!.) + return + for(var/obj/machinery/hydroponics/hydro in oview(tree_range, owner)) + hydro.myseed?.adjust_instability(mutator_boost) + +/atom/movable/warp_effect/turtle_field + alpha = 75 + +///effects we give our tree abilities depending on their type +/obj/effect/temp_visual/circle_wave/tree + vis_flags = VIS_INHERIT_DIR | VIS_INHERIT_PLANE + duration = 10 SECONDS + amount_to_scale = 3 + +/obj/effect/temp_visual/circle_wave/tree/healer + color = "#28a3bc" + +/obj/effect/temp_visual/circle_wave/tree/killer + color = "#ce3ebf" + +/obj/effect/temp_visual/circle_wave/tree/mutator + color = "#c49f26" + +#undef TREE_FIELD_DURATION_EFFECT +#undef WARP_ANIMATE_TIME diff --git a/code/modules/mob/living/basic/turtle/turtle_ai.dm b/code/modules/mob/living/basic/turtle/turtle_ai.dm new file mode 100644 index 0000000000000..1af7c3111f78c --- /dev/null +++ b/code/modules/mob/living/basic/turtle/turtle_ai.dm @@ -0,0 +1,92 @@ +/datum/ai_controller/basic_controller/turtle + blackboard = list( + BB_HAPPY_EMOTIONS = list( + "wiggles its tree in excitement!", + "raises its head up high!", + "wags its tail enthusiastically!", + ), + BB_MODERATE_EMOTIONS = list( + "keeps its head level, eyes half-closed.", + "basks in the light peacefully.", + ), + BB_SAD_EMOTIONS = list( + "looks towards the floor in dissapointment...", + "the leaves on its tree droop...", + ), + ) + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking + planning_subtrees = list( + /datum/ai_planning_subtree/find_food, + /datum/ai_planning_subtree/express_happiness, + /datum/ai_planning_subtree/use_mob_ability/turtle_tree, + /datum/ai_planning_subtree/find_and_hunt_target/headbutt_people, //playfully headbutt people's legs + /datum/ai_planning_subtree/find_and_hunt_target/sniff_flora, //mmm the aroma + ) + +/datum/ai_planning_subtree/use_mob_ability/turtle_tree + ability_key = BB_TURTLE_TREE_ABILITY + +/datum/ai_planning_subtree/use_mob_ability/turtle_tree/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/happiness_count = controller.blackboard[BB_BASIC_HAPPINESS] * 100 + if(happiness_count > 75) + return ..() + if(!SPT_PROB(happiness_count / 50, seconds_per_tick)) + return + return ..() + +/datum/ai_planning_subtree/find_and_hunt_target/sniff_flora + target_key = BB_TURTLE_FLORA_TARGET + finding_behavior = /datum/ai_behavior/find_hunt_target/sniff_flora + hunting_behavior = /datum/ai_behavior/hunt_target/sniff_flora + hunt_targets = list( + /obj/machinery/hydroponics, + /obj/item/kirbyplants, + ) + hunt_range = 5 + hunt_chance = 45 + +/datum/ai_behavior/find_hunt_target/sniff_flora + action_cooldown = 1 MINUTES + behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + +/datum/ai_behavior/find_hunt_target/sniff_flora/valid_dinner(mob/living/source, obj/machinery/hydroponics/dinner, radius, datum/ai_controller/controller, seconds_per_tick) + if(!istype(dinner)) + return TRUE + if(isnull(dinner.myseed)) + return FALSE + if(dinner.weedlevel > 5 || dinner.pestlevel > 5) //too smelly + return FALSE + return can_see(source, dinner, radius) + +/datum/ai_behavior/hunt_target/sniff_flora + always_reset_target = TRUE + +/datum/ai_behavior/hunt_target/sniff_flora/target_caught(mob/living/hunter, atom/hunted) + hunter.manual_emote("Enjoys the sweet scent eminating from [hunted::name]!") + +/datum/ai_planning_subtree/find_and_hunt_target/headbutt_people + target_key = BB_TURTLE_HEADBUTT_VICTIM + finding_behavior = /datum/ai_behavior/find_hunt_target/human_to_headbutt + hunting_behavior = /datum/ai_behavior/hunt_target/headbutt_leg + hunt_targets = list(/mob/living/carbon/human) + hunt_range = 4 + hunt_chance = 45 + +/datum/ai_behavior/find_hunt_target/human_to_headbutt + action_cooldown = 2 MINUTES + behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + +/datum/ai_behavior/find_hunt_target/human_to_headbutt/valid_dinner(mob/living/source, mob/living/carbon/human/dinner, radius, datum/ai_controller/controller, seconds_per_tick) + if(dinner.stat != CONSCIOUS) + return FALSE + if(isnull(dinner.get_bodypart(BODY_ZONE_R_LEG)) && isnull(dinner.get_bodypart(BODY_ZONE_L_LEG))) //no legs to headbutt! + return FALSE + return can_see(source, dinner, radius) + +/datum/ai_behavior/hunt_target/headbutt_leg + always_reset_target = TRUE + +/datum/ai_behavior/hunt_target/headbutt_leg/target_caught(mob/living/hunter, atom/hunted) + hunter.manual_emote("playfully headbutts [hunted]'s legs!") + diff --git a/icons/mob/simple/pets.dmi b/icons/mob/simple/pets.dmi index b7e8642387228a2e5fb1eec39f69f13f509ad591..512a48e813b0d41075bc3753b557136bcde451a7 100644 GIT binary patch literal 91503 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

rL0k$1xW)M9`t< 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 Z_6Le)KI)p@`<4Iz002ovPDHLkV1gzjIT`=} literal 0 HcmV?d00001 diff --git a/tgstation.dme b/tgstation.dme index bec1ca1f5a46d..269325a558b54 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -5162,6 +5162,9 @@ #include "code\modules\mob\living\basic\trooper\syndicate.dm" #include "code\modules\mob\living\basic\trooper\trooper.dm" #include "code\modules\mob\living\basic\trooper\trooper_ai.dm" +#include "code\modules\mob\living\basic\turtle\turtle.dm" +#include "code\modules\mob\living\basic\turtle\turtle_ability.dm" +#include "code\modules\mob\living\basic\turtle\turtle_ai.dm" #include "code\modules\mob\living\basic\vermin\axolotl.dm" #include "code\modules\mob\living\basic\vermin\butterfly.dm" #include "code\modules\mob\living\basic\vermin\cockroach.dm" From e264e94d6ef5ec398d447611cf9f8a833418d510 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 00:10:18 +0000 Subject: [PATCH 074/235] Automatic changelog for PR #87493 [ci skip] --- html/changelogs/AutoChangeLog-pr-87493.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-87493.yml diff --git a/html/changelogs/AutoChangeLog-pr-87493.yml b/html/changelogs/AutoChangeLog-pr-87493.yml new file mode 100644 index 0000000000000..03d0c7f827173 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-87493.yml @@ -0,0 +1,4 @@ +author: "Ben10Omintrix" +delete-after: True +changes: + - rscadd: "adds flora-turtles. obtainable through cargo or by fishing from the hydroponics tray" \ No newline at end of file From e4569598629d7ef9a87c609d1eb9e8613a3b1a2e Mon Sep 17 00:00:00 2001 From: SyncIt21 <110812394+SyncIt21@users.noreply.github.com> Date: Mon, 23 Dec 2024 06:03:42 +0530 Subject: [PATCH 075/235] Rolling tables can be rolled up again (#88628) --- code/game/objects/items/rollertable_dock.dm | 17 ----------------- code/game/objects/structures/tables_racks.dm | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/code/game/objects/items/rollertable_dock.dm b/code/game/objects/items/rollertable_dock.dm index d0067e8c67596..9b2c34bc61b41 100644 --- a/code/game/objects/items/rollertable_dock.dm +++ b/code/game/objects/items/rollertable_dock.dm @@ -9,23 +9,6 @@ . = ..() loaded = new(src) -/obj/structure/table/rolling/attackby(obj/item/wtable, mob/user, params) - if(!istype(wtable, /obj/item/rolling_table_dock)) - return ..() - var/obj/item/rolling_table_dock/rable = wtable - var/turf/target_table = get_turf(src) - if(rable.loaded) - to_chat(user, span_warning("You already have a roller table docked!")) - return - if(locate(/mob/living) in target_table) - to_chat(user, span_warning("You can't collect the table with that much on top!")) - return - else - rable.loaded = src - forceMove(rable) - user.visible_message(span_notice("[user] collects [src]."), balloon_alert(user, "you collect the [src].")) - return TRUE - /obj/item/rolling_table_dock/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) var/turf/target_turf = get_turf(interacting_with) if(target_turf.is_blocked_turf(TRUE) || (locate(/mob/living) in target_turf)) diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm index 448c3eb5425a7..9adb4f75a2f94 100644 --- a/code/game/objects/structures/tables_racks.dm +++ b/code/game/objects/structures/tables_racks.dm @@ -390,6 +390,23 @@ LAZYNULL(attached_items) // safety return ..() +/obj/structure/table/rolling/item_interaction(mob/living/user, obj/item/rolling_table_dock/rable, list/modifiers) + . = NONE + if(!istype(rable)) + return + + if(rable.loaded) + to_chat(user, span_warning("You already have \a [rable.loaded] docked!")) + return ITEM_INTERACT_FAILURE + if(locate(/mob/living) in get_turf(src)) + to_chat(user, span_warning("You can't collect \the [src] with that much on top!")) + return ITEM_INTERACT_FAILURE + + rable.loaded = src + forceMove(rable) + user.visible_message(span_notice("[user] collects \the [src]."), span_notice("you collect \the [src].")) + return ITEM_INTERACT_SUCCESS + /obj/structure/table/rolling/AfterPutItemOnTable(obj/item/thing, mob/living/user) . = ..() LAZYADD(attached_items, thing) From c5e5db219abc440bd3a7944688b3ee50d5b9d0e6 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 00:46:28 +0000 Subject: [PATCH 076/235] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-87493.yml | 4 --- html/changelogs/AutoChangeLog-pr-88380.yml | 6 ---- html/changelogs/AutoChangeLog-pr-88438.yml | 5 ---- html/changelogs/AutoChangeLog-pr-88482.yml | 8 ------ html/changelogs/AutoChangeLog-pr-88495.yml | 5 ---- html/changelogs/AutoChangeLog-pr-88525.yml | 4 --- html/changelogs/AutoChangeLog-pr-88545.yml | 4 --- html/changelogs/AutoChangeLog-pr-88617.yml | 4 --- html/changelogs/AutoChangeLog-pr-88621.yml | 4 --- html/changelogs/archive/2024-12.yml | 33 ++++++++++++++++++++++ 10 files changed, 33 insertions(+), 44 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-87493.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88380.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88438.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88482.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88495.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88525.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88545.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88617.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88621.yml diff --git a/html/changelogs/AutoChangeLog-pr-87493.yml b/html/changelogs/AutoChangeLog-pr-87493.yml deleted file mode 100644 index 03d0c7f827173..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-87493.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Ben10Omintrix" -delete-after: True -changes: - - rscadd: "adds flora-turtles. obtainable through cargo or by fishing from the hydroponics tray" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88380.yml b/html/changelogs/AutoChangeLog-pr-88380.yml deleted file mode 100644 index 27f47a64a6165..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88380.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "Melbert" -delete-after: True -changes: - - balance: "Carpenter hammer force 20 -> 17" - - balance: "Carpenter hammer throwforce 20 -> 14" - - balance: "Carpenter hammer demo mod 1.25 -> 1.15" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88438.yml b/html/changelogs/AutoChangeLog-pr-88438.yml deleted file mode 100644 index f76306a4b90a5..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88438.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "TealSeer" -delete-after: True -changes: - - qol: "The time until the server reboots is now visible in the status tab." - - admin: "Added a cancel reboot verb to the server tab." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88482.yml b/html/changelogs/AutoChangeLog-pr-88482.yml deleted file mode 100644 index f94bf1b4da1fa..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88482.yml +++ /dev/null @@ -1,8 +0,0 @@ -author: "NecromancerAnne (code), SmArtKar (sprites)" -delete-after: True -changes: - - balance: "Makarovs and Toy Pistols come in weapon cases. Complete with spare ammo." - - balance: "Basic ammo for either weapon comes in weapon cases of three extra magazines at an affordable price." - - balance: "Donksoft Toy Pistols from the uplink are much stronger than their standard counterparts, but now priced at 6 TC." - - balance: "Makarovs and Toy pistols have a magazine capacity of 12 rounds." - - balance: "Gun/Ammo cases from the traitor uplink can be destroyed by activating the disposal bomb. Press Alt-Right-Click on the case to start the timer." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88495.yml b/html/changelogs/AutoChangeLog-pr-88495.yml deleted file mode 100644 index 05df567f9fb27..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88495.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Ben10Omintrix" -delete-after: True -changes: - - qol: "holding shift and hovering over your pet will display a list of commands you can click from" - - bugfix: "fixes the fishing pet command not working" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88525.yml b/html/changelogs/AutoChangeLog-pr-88525.yml deleted file mode 100644 index 7946ba940d96f..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88525.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "grungussuss" -delete-after: True -changes: - - code_imp: "removed an extra proc override in digitigrade legs preference logic code" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88545.yml b/html/changelogs/AutoChangeLog-pr-88545.yml deleted file mode 100644 index 971d526e305de..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88545.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "DATA-xPUNGED" -delete-after: True -changes: - - map: "removed the sec record computer from the Icemoon Listening Post." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88617.yml b/html/changelogs/AutoChangeLog-pr-88617.yml deleted file mode 100644 index 530ff693e1eda..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88617.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "AyIong" -delete-after: True -changes: - - bugfix: "Fixed scrollbar colors and background position in TGUI on Byond 516" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88621.yml b/html/changelogs/AutoChangeLog-pr-88621.yml deleted file mode 100644 index c326d59dfc745..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88621.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Rhials" -delete-after: True -changes: - - rscadd: "Nukie uplinks now offer an OG-style nuke pinpointer in their Badassery shop section." \ No newline at end of file diff --git a/html/changelogs/archive/2024-12.yml b/html/changelogs/archive/2024-12.yml index 5a594a7616886..3a7c86e40739d 100644 --- a/html/changelogs/archive/2024-12.yml +++ b/html/changelogs/archive/2024-12.yml @@ -540,3 +540,36 @@ - spellcheck: Update teleporter machine desc to be accurate - spellcheck: Fix holodeck emag message claiming to increase power - rscadd: Add power efficiency when stasis bed stock parts are upgraded +2024-12-23: + AyIong: + - bugfix: Fixed scrollbar colors and background position in TGUI on Byond 516 + Ben10Omintrix: + - rscadd: adds flora-turtles. obtainable through cargo or by fishing from the hydroponics + tray + - qol: holding shift and hovering over your pet will display a list of commands + you can click from + - bugfix: fixes the fishing pet command not working + DATA-xPUNGED: + - map: removed the sec record computer from the Icemoon Listening Post. + Melbert: + - balance: Carpenter hammer force 20 -> 17 + - balance: Carpenter hammer throwforce 20 -> 14 + - balance: Carpenter hammer demo mod 1.25 -> 1.15 + NecromancerAnne (code), SmArtKar (sprites): + - balance: Makarovs and Toy Pistols come in weapon cases. Complete with spare ammo. + - balance: Basic ammo for either weapon comes in weapon cases of three extra magazines + at an affordable price. + - balance: Donksoft Toy Pistols from the uplink are much stronger than their standard + counterparts, but now priced at 6 TC. + - balance: Makarovs and Toy pistols have a magazine capacity of 12 rounds. + - balance: Gun/Ammo cases from the traitor uplink can be destroyed by activating + the disposal bomb. Press Alt-Right-Click on the case to start the timer. + Rhials: + - rscadd: Nukie uplinks now offer an OG-style nuke pinpointer in their Badassery + shop section. + TealSeer: + - qol: The time until the server reboots is now visible in the status tab. + - admin: Added a cancel reboot verb to the server tab. + grungussuss: + - code_imp: removed an extra proc override in digitigrade legs preference logic + code From 6d65734d1ad3503c686d57ffd1dfe0f7d0ab7c34 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 00:58:59 +0000 Subject: [PATCH 077/235] Automatic changelog for PR #88628 [ci skip] --- html/changelogs/AutoChangeLog-pr-88628.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88628.yml diff --git a/html/changelogs/AutoChangeLog-pr-88628.yml b/html/changelogs/AutoChangeLog-pr-88628.yml new file mode 100644 index 0000000000000..267a741b68ed0 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88628.yml @@ -0,0 +1,4 @@ +author: "SyncIt21" +delete-after: True +changes: + - bugfix: "rolling tables can be rolled up again" \ No newline at end of file From 2660e6f41286ced719371bbefc83dd3053090d94 Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Mon, 23 Dec 2024 08:01:07 +0300 Subject: [PATCH 078/235] Admins can now return the shuttle back to the station without ending the round (#88521) ## About The Pull Request Per DarkenedEarth's request on discord, adds a new Fun menu secret which makes the shuttle go back to the station after a configurable delay with an announcement (also configurable). After docking at the station it behaves like BYOS (still requiring to wait for it's arrival despite it being docked). ## Why It's Good For The Game Admins want it to hunt early EORGers or make events or something like that. ## Changelog :cl: admin: Admins can now return the shuttle back to the station without ending the round /:cl: --- code/modules/admin/verbs/secrets.dm | 49 +++++++++++++++++++++++ tgui/packages/tgui/interfaces/Secrets.jsx | 12 +++--- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/code/modules/admin/verbs/secrets.dm b/code/modules/admin/verbs/secrets.dm index e43164419d59c..177d1f3172f7d 100644 --- a/code/modules/admin/verbs/secrets.dm +++ b/code/modules/admin/verbs/secrets.dm @@ -348,6 +348,34 @@ ADMIN_VERB(secrets, R_NONE, "Secrets", "Abuse harder than you ever have before w priority_announce("The NAP is now in full effect.", null, SSstation.announcer.get_rand_report_sound()) else priority_announce("The NAP has been revoked.", null, SSstation.announcer.get_rand_report_sound()) + if("send_shuttle_back") + if (!is_funmin) + return + if (SSshuttle.emergency.mode != SHUTTLE_ESCAPE) + to_chat(usr, span_warning("Emergency shuttle not currently in transit!"), confidential = TRUE) + return + var/make_announcement = tgui_alert(usr, "Make a CentCom announcement?", "Emergency shuttle return", list("Yes", "Custom Text", "No")) || "No" + var/announcement_text = "Emergency shuttle trajectory overriden, rerouting course back to [station_name()]." + if (make_announcement == "Custom Text") + announcement_text = tgui_input_text(usr, "Custom CentCom announcement", "Emergency shuttle return", multiline = TRUE) || announcement_text + var/new_timer = tgui_input_number(usr, "How long should the shuttle remain in transit?", "When are we droppin' boys?", 180, 600) + if (isnull(new_timer) || SSshuttle.emergency.mode != SHUTTLE_ESCAPE) + return + SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Send Shuttle Back")) + message_admins("[key_name_admin(holder)] sent the escape shuttle back to the station") + if (make_announcement != "No") + priority_announce( + text = announcement_text, + title = "Shuttle Trajectory Override", + sound = 'sound/announcer/announcement/announce_dig.ogg', + sender_override = "Emergency Shuttle Uplink Alert", + color_override = "grey", + ) + SSshuttle.emergency.timer = INFINITY + if (new_timer > 0) + addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(return_escape_shuttle), make_announcement), new_timer SECONDS) + else + INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(return_escape_shuttle), make_announcement) if("blackout") if(!is_funmin) return @@ -673,6 +701,27 @@ ADMIN_VERB(secrets, R_NONE, "Secrets", "Abuse harder than you ever have before w T.flick_overlay_static(portal_appearance[GET_TURF_PLANE_OFFSET(T) + 1], 15) playsound(T, 'sound/effects/magic/lightningbolt.ogg', rand(80, 100), TRUE) +/// Docks the emergency shuttle back to the station and resets its' state +/proc/return_escape_shuttle(make_announcement) + if (SSshuttle.emergency.initiate_docking(SSshuttle.getDock("emergency_home"), force = TRUE) != DOCKING_SUCCESS) + message_admins("Emergency shuttle was unable to dock back to the station!") + SSshuttle.emergency.timer = 1 // Prevents softlocks + return + if (make_announcement != "No") + priority_announce( + text = "[SSshuttle.emergency] has returned to the station.", + title = "Emergency Shuttle Override", + sound = ANNOUNCER_SHUTTLEDOCK, + sender_override = "Emergency Shuttle Uplink Alert", + color_override = "grey", + ) + SSshuttle.emergency.mode = SHUTTLE_IDLE + SSshuttle.emergency.timer = 0 + // Docks the pods back (don't ask about physics) + for (var/obj/docking_port/mobile/pod/pod in SSshuttle.mobile_docking_ports) + if (pod.previous) + pod.initiate_docking(pod.previous, force = TRUE) + /datum/everyone_is_an_antag_controller var/chosen_antag = "" var/objective = "" diff --git a/tgui/packages/tgui/interfaces/Secrets.jsx b/tgui/packages/tgui/interfaces/Secrets.jsx index 3e3ccd70cc0ff..fbe0f56a9124a 100644 --- a/tgui/packages/tgui/interfaces/Secrets.jsx +++ b/tgui/packages/tgui/interfaces/Secrets.jsx @@ -425,13 +425,13 @@ const FunTab = (props) => { /> - - Your admin button here, coder! - + content="Send Shuttle Back" + onClick={() => act('send_shuttle_back')} + /> From 0909cb9bc7582857018412e7e78edc6cae8c1cb6 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 05:01:27 +0000 Subject: [PATCH 079/235] Automatic changelog for PR #88521 [ci skip] --- html/changelogs/AutoChangeLog-pr-88521.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88521.yml diff --git a/html/changelogs/AutoChangeLog-pr-88521.yml b/html/changelogs/AutoChangeLog-pr-88521.yml new file mode 100644 index 0000000000000..4435358250283 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88521.yml @@ -0,0 +1,4 @@ +author: "SmArtKar" +delete-after: True +changes: + - admin: "Admins can now return the shuttle back to the station without ending the round" \ No newline at end of file From c095e295687661d30a92c134152a2f249f3fbcc8 Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Mon, 23 Dec 2024 22:36:22 +0300 Subject: [PATCH 080/235] Fixes showers not passively washing objects (#88666) ## About The Pull Request Closes #88643 ## Changelog :cl: fix: Fixed showers not passively washing objects /:cl: --- code/game/objects/structures/shower.dm | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/code/game/objects/structures/shower.dm b/code/game/objects/structures/shower.dm index ce6c987ba10dd..26d05f6edd178 100644 --- a/code/game/objects/structures/shower.dm +++ b/code/game/objects/structures/shower.dm @@ -273,7 +273,11 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/shower, (-16)) if(!radioactive_shower) // note it is possible to have a clean_shower that is radioactive (+70% water mixed with +20% radiation) wash_flags |= CLEAN_RAD - target.wash(wash_flags) + + if (isturf(target)) + target.wash(wash_flags, TRUE) + else + target.wash(wash_flags) reagents.expose(target, (TOUCH), SHOWER_EXPOSURE_MULTIPLIER * SHOWER_SPRAY_VOLUME / max(reagents.total_volume, SHOWER_SPRAY_VOLUME)) if(!isliving(target)) @@ -344,7 +348,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/shower, (-16)) return mode == SHOWER_MODE_FOREVER ? 0 : PROCESS_KILL // Wash up. - wash_atom(loc, TRUE) + wash_atom(loc) reagents.remove_all(SHOWER_SPRAY_VOLUME) /obj/machinery/shower/on_deconstruction(disassembled = TRUE) From ca98802053621668a772179622d03ea5c8b8cea7 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 19:36:45 +0000 Subject: [PATCH 081/235] Automatic changelog for PR #88666 [ci skip] --- html/changelogs/AutoChangeLog-pr-88666.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88666.yml diff --git a/html/changelogs/AutoChangeLog-pr-88666.yml b/html/changelogs/AutoChangeLog-pr-88666.yml new file mode 100644 index 0000000000000..28c236c8187d1 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88666.yml @@ -0,0 +1,4 @@ +author: "SmArtKar" +delete-after: True +changes: + - bugfix: "Fixed showers not passively washing objects" \ No newline at end of file From 76d80bb8a97e2584cc0acd9984be6e4ecc101fa6 Mon Sep 17 00:00:00 2001 From: carlarctg <53100513+carlarctg@users.noreply.github.com> Date: Mon, 23 Dec 2024 16:36:51 -0300 Subject: [PATCH 082/235] Kissing while you have the ink infusion ability off cooldown gives you an ink kiss (#88556) ## About The Pull Request Kissing while you have the ink infusion ability off cooldown gives you an ink kiss. This ink kiss behaves identically to a normal kiss but also creates a ink spit projectile and calls on_hit(target) on its own on_hit (not on_harmless_hit) ## Why It's Good For The Game I think it's funny to kiss people and splat them with ink. I did not test this because apparently worktrees dont work with pressing f5 ## Changelog :cl: add: Kissing while you have the ink infusion ability off cooldown gives you an ink kiss /:cl: --------- Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com> --- code/game/objects/items/hand_items.dm | 22 ++++++++++++++++++++++ code/modules/mob/living/emote.dm | 8 ++++++++ 2 files changed, 30 insertions(+) diff --git a/code/game/objects/items/hand_items.dm b/code/game/objects/items/hand_items.dm index d5c2a58dcd822..a1ea5b1011a2e 100644 --- a/code/game/objects/items/hand_items.dm +++ b/code/game/objects/items/hand_items.dm @@ -539,6 +539,12 @@ color = COLOR_SYNDIE_RED kiss_type = /obj/projectile/kiss/syndie +/obj/item/hand_item/kisser/ink + name = "ink kiss" + desc = "Is that a blot of ink in your pocket or are you just happy to see me?" + color = COLOR_ALMOST_BLACK + kiss_type = /obj/projectile/kiss/ink + /obj/projectile/kiss name = "kiss" icon = 'icons/mob/simple/animal.dmi' @@ -639,6 +645,22 @@ var/obj/item/organ/heart/dont_go_breakin_my_heart = heartbreakee.get_organ_slot(ORGAN_SLOT_HEART) dont_go_breakin_my_heart.apply_organ_damage(999) +/obj/projectile/kiss/ink + name = "ink kiss" + color = COLOR_ALMOST_BLACK + damage = /obj/projectile/ink_spit::damage + damage_type = /obj/projectile/ink_spit::damage_type + armor_flag = /obj/projectile/ink_spit::armor_flag + armour_penetration = /obj/projectile/ink_spit::armour_penetration + impact_effect_type = /obj/projectile/ink_spit::impact_effect_type + hitsound = /obj/projectile/ink_spit::hitsound + hitsound_wall = /obj/projectile/ink_spit::hitsound_wall + +/obj/projectile/kiss/ink/on_hit(atom/target, blocked, pierce_hit) + . = ..() + var/obj/projectile/ink_spit/ink_spit = new (target) + ink_spit.on_hit(target) + // Based on energy gun characteristics /obj/projectile/kiss/syndie name = "syndie kiss" diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm index a8431d790b7ca..fad518a323465 100644 --- a/code/modules/mob/living/emote.dm +++ b/code/modules/mob/living/emote.dm @@ -262,12 +262,20 @@ if(HAS_TRAIT(user, TRAIT_KISS_OF_DEATH)) kiss_type = /obj/item/hand_item/kisser/death + var/datum/action/cooldown/ink_spit/ink_action = locate() in user.actions + if(ink_action?.IsAvailable()) + kiss_type = /obj/item/hand_item/kisser/ink + ink_action.StartCooldown() + else + ink_action = null + var/obj/item/kiss_blower = new kiss_type(user) if(user.put_in_hands(kiss_blower)) to_chat(user, span_notice("You ready your kiss-blowing hand.")) else qdel(kiss_blower) to_chat(user, span_warning("You're incapable of blowing a kiss in your current state.")) + ink_action?.ResetCooldown() /datum/emote/living/laugh key = "laugh" From 8a678dd95906bac368a19a5d450b8e2dde57e1e3 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 19:37:11 +0000 Subject: [PATCH 083/235] Automatic changelog for PR #88556 [ci skip] --- html/changelogs/AutoChangeLog-pr-88556.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88556.yml diff --git a/html/changelogs/AutoChangeLog-pr-88556.yml b/html/changelogs/AutoChangeLog-pr-88556.yml new file mode 100644 index 0000000000000..a4bffbbe12807 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88556.yml @@ -0,0 +1,4 @@ +author: "carlarctg" +delete-after: True +changes: + - rscadd: "Kissing while you have the ink infusion ability off cooldown gives you an ink kiss" \ No newline at end of file From f1cd77b70fa43388e780a849f184a8e1e06d9375 Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Mon, 23 Dec 2024 22:52:22 +0300 Subject: [PATCH 084/235] [NO GBP] Fixes moth wing stabilization working in zero-g as long as you keep moving (#88552) ## About The Pull Request Didn't run the check in stabilization code, also fixed a forgotten comsig unreg ## Changelog :cl: fix: Fixed moth wing stabilization working in zero-g as long as you keep moving /:cl: --- code/datums/components/jetpack.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/datums/components/jetpack.dm b/code/datums/components/jetpack.dm index c7ff096029b97..5498a8a81ef85 100644 --- a/code/datums/components/jetpack.dm +++ b/code/datums/components/jetpack.dm @@ -119,7 +119,7 @@ /datum/component/jetpack/proc/deactivate(datum/source, mob/old_user) SIGNAL_HANDLER - UnregisterSignal(old_user, list(COMSIG_MOVABLE_PRE_MOVE, COMSIG_MOVABLE_MOVED, COMSIG_MOB_CLIENT_MOVE_NOGRAV, COMSIG_MOB_ATTEMPT_HALT_SPACEMOVE)) + UnregisterSignal(old_user, list(COMSIG_MOVABLE_PRE_MOVE, COMSIG_MOVABLE_MOVED, COMSIG_MOB_CLIENT_MOVE_NOGRAV, COMSIG_MOB_ATTEMPT_HALT_SPACEMOVE, COMSIG_MOVABLE_DRIFT_BLOCK_INPUT)) STOP_PROCESSING(SSnewtonian_movement, src) user = null @@ -159,7 +159,7 @@ last_stabilization_tick = world.time - if (!should_trigger(user) || !stabilize || isnull(user.drift_handler)) + if (!should_trigger(user) || !stabilize || !check_on_move.Invoke(FALSE) || isnull(user.drift_handler)) return var/max_drift_force = MOVE_DELAY_TO_DRIFT(user.cached_multiplicative_slowdown) From 668464732e302cfa3194a0da7a38ad27b49da181 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 19:52:45 +0000 Subject: [PATCH 085/235] Automatic changelog for PR #88552 [ci skip] --- html/changelogs/AutoChangeLog-pr-88552.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88552.yml diff --git a/html/changelogs/AutoChangeLog-pr-88552.yml b/html/changelogs/AutoChangeLog-pr-88552.yml new file mode 100644 index 0000000000000..5c0d4ba83e61d --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88552.yml @@ -0,0 +1,4 @@ +author: "SmArtKar" +delete-after: True +changes: + - bugfix: "Fixed moth wing stabilization working in zero-g as long as you keep moving" \ No newline at end of file From a46523adb68553a49be819eabce6966bc130eb29 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Tue, 24 Dec 2024 00:25:59 +0000 Subject: [PATCH 086/235] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-88521.yml | 4 ---- html/changelogs/AutoChangeLog-pr-88552.yml | 4 ---- html/changelogs/AutoChangeLog-pr-88556.yml | 4 ---- html/changelogs/AutoChangeLog-pr-88628.yml | 4 ---- html/changelogs/AutoChangeLog-pr-88666.yml | 4 ---- html/changelogs/archive/2024-12.yml | 11 +++++++++++ 6 files changed, 11 insertions(+), 20 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-88521.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88552.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88556.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88628.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88666.yml diff --git a/html/changelogs/AutoChangeLog-pr-88521.yml b/html/changelogs/AutoChangeLog-pr-88521.yml deleted file mode 100644 index 4435358250283..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88521.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SmArtKar" -delete-after: True -changes: - - admin: "Admins can now return the shuttle back to the station without ending the round" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88552.yml b/html/changelogs/AutoChangeLog-pr-88552.yml deleted file mode 100644 index 5c0d4ba83e61d..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88552.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SmArtKar" -delete-after: True -changes: - - bugfix: "Fixed moth wing stabilization working in zero-g as long as you keep moving" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88556.yml b/html/changelogs/AutoChangeLog-pr-88556.yml deleted file mode 100644 index a4bffbbe12807..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88556.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "carlarctg" -delete-after: True -changes: - - rscadd: "Kissing while you have the ink infusion ability off cooldown gives you an ink kiss" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88628.yml b/html/changelogs/AutoChangeLog-pr-88628.yml deleted file mode 100644 index 267a741b68ed0..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88628.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SyncIt21" -delete-after: True -changes: - - bugfix: "rolling tables can be rolled up again" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88666.yml b/html/changelogs/AutoChangeLog-pr-88666.yml deleted file mode 100644 index 28c236c8187d1..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88666.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SmArtKar" -delete-after: True -changes: - - bugfix: "Fixed showers not passively washing objects" \ No newline at end of file diff --git a/html/changelogs/archive/2024-12.yml b/html/changelogs/archive/2024-12.yml index 3a7c86e40739d..d3055633ddf57 100644 --- a/html/changelogs/archive/2024-12.yml +++ b/html/changelogs/archive/2024-12.yml @@ -573,3 +573,14 @@ grungussuss: - code_imp: removed an extra proc override in digitigrade legs preference logic code +2024-12-24: + SmArtKar: + - admin: Admins can now return the shuttle back to the station without ending the + round + - bugfix: Fixed moth wing stabilization working in zero-g as long as you keep moving + - bugfix: Fixed showers not passively washing objects + SyncIt21: + - bugfix: rolling tables can be rolled up again + carlarctg: + - rscadd: Kissing while you have the ink infusion ability off cooldown gives you + an ink kiss From a2b1aa91788495a9460bc70047318c83c5b47f22 Mon Sep 17 00:00:00 2001 From: lovegreenstuff <59631103+lovegreenstuff@users.noreply.github.com> Date: Tue, 24 Dec 2024 00:51:58 -0800 Subject: [PATCH 087/235] plumbing catalyst storag (#88404) ## About The Pull Request see title also see video https://github.com/user-attachments/assets/23e2d128-1175-4779-87cf-74a4d4e6e1c1 ## Why It's Good For The Game doing in 1 machine what was once done by 3-4 is nicer for the chemist. less materials wasted and more space available to play around in. also has the side effect of making the output slightly higher since time spent transferring catalysts can now be spent on transferring reagents that are consumed instead ## Changelog :cl: add: catalyst function for plumbing reaction chambers /:cl: --------- Co-authored-by: SyncIt21 --- code/datums/components/plumbing/_plumbing.dm | 2 + .../components/plumbing/reaction_chamber.dm | 16 +- .../plumbing/plumbers/_plumb_machinery.dm | 114 -------- .../plumbing/plumbers/_plumb_reagents.dm | 261 ++++++++++++++++++ .../plumbing/plumbers/reaction_chamber.dm | 60 +++- tgstation.dme | 1 + .../tgui/interfaces/ChemMixingChamber.tsx | 2 +- .../tgui/interfaces/ChemReactionChamber.tsx | 71 ++++- 8 files changed, 393 insertions(+), 134 deletions(-) create mode 100644 code/modules/plumbing/plumbers/_plumb_reagents.dm diff --git a/code/datums/components/plumbing/_plumbing.dm b/code/datums/components/plumbing/_plumbing.dm index a1be66654a2c0..ed916c29edf94 100644 --- a/code/datums/components/plumbing/_plumbing.dm +++ b/code/datums/components/plumbing/_plumbing.dm @@ -121,6 +121,8 @@ ///returns TRUE when they can give the specified amount and reagent. called by process request /datum/component/plumbing/proc/can_give(amount, reagent, datum/ductnet/net) + SHOULD_BE_PURE(TRUE) + if(amount <= 0) return diff --git a/code/datums/components/plumbing/reaction_chamber.dm b/code/datums/components/plumbing/reaction_chamber.dm index d0aff2f50708c..4f2acf4710a7e 100644 --- a/code/datums/components/plumbing/reaction_chamber.dm +++ b/code/datums/components/plumbing/reaction_chamber.dm @@ -8,20 +8,25 @@ return COMPONENT_INCOMPATIBLE /datum/component/plumbing/reaction_chamber/can_give(amount, reagent, datum/ductnet/net) - . = ..() var/obj/machinery/plumbing/reaction_chamber/reaction_chamber = parent - if(!. || !reaction_chamber.emptying || reagents.is_reacting) + + //cannot give when we outselves are requesting or reacting the reagents + if(!reaction_chamber.emptying || reagents.is_reacting) return FALSE + return ..() + /datum/component/plumbing/reaction_chamber/send_request(dir) var/obj/machinery/plumbing/reaction_chamber/chamber = parent + if(chamber.emptying) return //take in reagents var/present_amount var/diff - for(var/required_reagent in chamber.required_reagents) + var/list/datum/reagent/required_reagents = chamber.catalysts | chamber.required_reagents + for(var/datum/reagent/required_reagent as anything in required_reagents) //find how much amount is already present if at all and get the reagent reference present_amount = 0 for(var/datum/reagent/present_reagent as anything in reagents.reagent_list) @@ -30,10 +35,11 @@ break //compute how much more is needed - diff = min(chamber.required_reagents[required_reagent] - present_amount, MACHINE_REAGENT_TRANSFER) + diff = min(required_reagents[required_reagent] - present_amount, MACHINE_REAGENT_TRANSFER) if(diff >= CHEMICAL_QUANTISATION_LEVEL) // the closest we can ask for so values like 0.9999 become 1 process_request(diff, required_reagent, dir) - return + if(!chamber.catalysts[required_reagent]) //only block if not a catalyst as they can come in whenever they are available + return reagents.flags &= ~NO_REACT reagents.handle_reactions() diff --git a/code/modules/plumbing/plumbers/_plumb_machinery.dm b/code/modules/plumbing/plumbers/_plumb_machinery.dm index 33c063bbfed20..f16091cc21b26 100644 --- a/code/modules/plumbing/plumbers/_plumb_machinery.dm +++ b/code/modules/plumbing/plumbers/_plumb_machinery.dm @@ -102,117 +102,3 @@ user.balloon_alert_to_viewers("finished plunging") reagents.expose(get_turf(src), TOUCH) //splash on the floor reagents.clear_reagents() - -/** - * Specialized reagent container for plumbing. Uses the round robin approach of transferring reagents - * so transfer 5 from 15 water, 15 sugar and 15 plasma becomes 10, 15, 15 instead of 13.3333, 13.3333 13.3333. Good if you hate floating point errors - */ -/datum/reagents/plumbing - -/** - * Same as the parent trans_to except only a few arguments have impact here & the rest of the arguments are discarded. - * Arguments - * - * * atom/target - the target we are transfering to - * * amount - amount to transfer - * * datum/reagent/target_id - the reagent id we want to transfer. if null everything gets transfered - * * methods - this is key for deciding between round-robin or proportional transfer. It does not mean the same as the - * parent proc. LINEAR for round robin(in this technique reagents are missing/lost/not preserved when there isn't enough space to hold them) - * NONE means everything is transfered regardless of how much space is available in the receiver in proportions - */ -/datum/reagents/plumbing/trans_to( - atom/target, - amount = 1, - multiplier = 1, //unused for plumbing - datum/reagent/target_id, - preserve_data = TRUE, //unused for plumbing - no_react = FALSE, //unused for plumbing we always want reactions - mob/transferred_by, //unused for plumbing logging is not important inside plumbing machines - remove_blacklisted = FALSE, //unused for plumbing, we don't care what reagents are inside us - methods = LINEAR, //default round robin technique for transferring reagents - show_message = TRUE, //unused for plumbing, used for logging only - ignore_stomach = FALSE //unused for plumbing, reagents flow only between machines & is not injected to mobs at any point in time -) - if(QDELETED(target) || !total_volume) - return FALSE - - if(!IS_FINITE(amount)) - stack_trace("non finite amount passed to trans_to [amount] amount of reagents") - return FALSE - - if(!isnull(target_id) && !ispath(target_id)) - stack_trace("invalid target reagent id [target_id] passed to trans_to") - return FALSE - - var/datum/reagents/target_holder - if(istype(target, /datum/reagents)) - target_holder = target - else - target_holder = target.reagents - - // Prevents small amount problems, as well as zero and below zero amounts. - amount = round(min(amount, total_volume, target_holder.maximum_volume - target_holder.total_volume), CHEMICAL_QUANTISATION_LEVEL) - if(amount <= 0) - return FALSE - - //Set up new reagents to inherit the old ongoing reactions - transfer_reactions(target_holder) - - var/list/cached_reagents = reagent_list - var/list/reagents_to_remove = list() - var/transfer_amount - var/transfered_amount - var/total_transfered_amount = 0 - - var/round_robin = methods & LINEAR - var/part - var/to_transfer - if(round_robin) - to_transfer = amount - else - part = amount / total_volume - - //first add reagents to target - for(var/datum/reagent/reagent as anything in cached_reagents) - if(round_robin && !to_transfer) - break - - if(!isnull(target_id)) - if(reagent.type == target_id) - force_stop_reagent_reacting(reagent) - transfer_amount = min(amount, reagent.volume) - else - continue - else - if(round_robin) - transfer_amount = min(to_transfer, reagent.volume) - else - transfer_amount = reagent.volume * part - - if(reagent.intercept_reagents_transfer(target_holder, amount)) - update_total() - target_holder.update_total() - continue - - transfered_amount = target_holder.add_reagent(reagent.type, transfer_amount, copy_data(reagent), chem_temp, reagent.purity, reagent.ph, no_react = TRUE, ignore_splitting = reagent.chemical_flags & REAGENT_DONOTSPLIT) //we only handle reaction after every reagent has been transferred. - if(!transfered_amount) - continue - reagents_to_remove += list(list("R" = reagent, "T" = transfer_amount)) - total_transfered_amount += transfered_amount - if(round_robin) - to_transfer -= transfered_amount - - if(!isnull(target_id)) - break - - //remove chemicals that were added above - for(var/list/data as anything in reagents_to_remove) - var/datum/reagent/reagent = data["R"] - transfer_amount = data["T"] - remove_reagent(reagent.type, transfer_amount) - - //handle reactions - target_holder.handle_reactions() - src.handle_reactions() - - return round(total_transfered_amount, CHEMICAL_VOLUME_ROUNDING) diff --git a/code/modules/plumbing/plumbers/_plumb_reagents.dm b/code/modules/plumbing/plumbers/_plumb_reagents.dm new file mode 100644 index 0000000000000..4cce127c8b822 --- /dev/null +++ b/code/modules/plumbing/plumbers/_plumb_reagents.dm @@ -0,0 +1,261 @@ + +/** + * Specialized reagent container for plumbing. Uses the round robin approach of transferring reagents + * so transfer 5 from 15 water, 15 sugar and 15 plasma becomes 10, 15, 15 instead of 13.3333, 13.3333 13.3333. Good if you hate floating point errors + */ +/datum/reagents/plumbing + +/** + * Same as the parent trans_to except only a few arguments have impact here & the rest of the arguments are discarded. + * Arguments + * + * * atom/target - the target we are transfering to + * * amount - amount to transfer + * * datum/reagent/target_id - the reagent id we want to transfer. if null everything gets transfered + * * methods - this is key for deciding between round-robin or proportional transfer. It does not mean the same as the + * parent proc. LINEAR for round robin(in this technique reagents are missing/lost/not preserved when there isn't enough space to hold them) + * NONE means everything is transfered regardless of how much space is available in the receiver in proportions + */ +/datum/reagents/plumbing/trans_to( + atom/target, + amount = 1, + multiplier = 1, //unused for plumbing + datum/reagent/target_id, + preserve_data = TRUE, //unused for plumbing + no_react = FALSE, //unused for plumbing we always want reactions + mob/transferred_by, //unused for plumbing logging is not important inside plumbing machines + remove_blacklisted = FALSE, //unused for plumbing, we don't care what reagents are inside us + methods = LINEAR, //default round robin technique for transferring reagents + show_message = TRUE, //unused for plumbing, used for logging only + ignore_stomach = FALSE //unused for plumbing, reagents flow only between machines & is not injected to mobs at any point in time +) + if(QDELETED(target) || !total_volume) + return FALSE + + if(!IS_FINITE(amount)) + stack_trace("non finite amount passed to trans_to [amount] amount of reagents") + return FALSE + + if(!isnull(target_id) && !ispath(target_id)) + stack_trace("invalid target reagent id [target_id] passed to trans_to") + return FALSE + + var/datum/reagents/target_holder + if(istype(target, /datum/reagents)) + target_holder = target + else + target_holder = target.reagents + + // Prevents small amount problems, as well as zero and below zero amounts. + amount = round(min(amount, total_volume, target_holder.maximum_volume - target_holder.total_volume), CHEMICAL_QUANTISATION_LEVEL) + if(amount <= 0) + return FALSE + + //Set up new reagents to inherit the old ongoing reactions + transfer_reactions(target_holder) + + var/list/cached_reagents = reagent_list + var/list/reagents_to_remove = list() + var/transfer_amount + var/transfered_amount + var/total_transfered_amount = 0 + + var/round_robin = methods & LINEAR + var/part + var/to_transfer + if(round_robin) + to_transfer = amount + else + part = amount / total_volume + + //first add reagents to target + for(var/datum/reagent/reagent as anything in cached_reagents) + if(round_robin && !to_transfer) + break + + if(!isnull(target_id)) + if(reagent.type == target_id) + force_stop_reagent_reacting(reagent) + transfer_amount = min(amount, reagent.volume) + else + continue + else + if(round_robin) + transfer_amount = min(to_transfer, reagent.volume) + else + transfer_amount = reagent.volume * part + + if(reagent.intercept_reagents_transfer(target_holder, amount)) + update_total() + target_holder.update_total() + continue + + transfered_amount = target_holder.add_reagent(reagent.type, transfer_amount, copy_data(reagent), chem_temp, reagent.purity, reagent.ph, no_react = TRUE, ignore_splitting = reagent.chemical_flags & REAGENT_DONOTSPLIT) //we only handle reaction after every reagent has been transferred. + if(!transfered_amount) + continue + reagents_to_remove += list(list("R" = reagent, "T" = transfer_amount)) + total_transfered_amount += transfered_amount + if(round_robin) + to_transfer -= transfered_amount + + if(!isnull(target_id)) + break + + //remove chemicals that were added above + for(var/list/data as anything in reagents_to_remove) + var/datum/reagent/reagent = data["R"] + transfer_amount = data["T"] + remove_reagent(reagent.type, transfer_amount) + + //handle reactions + target_holder.handle_reactions() + src.handle_reactions() + + return round(total_transfered_amount, CHEMICAL_VOLUME_ROUNDING) + +///Excludes catalysts during the emptying process +/datum/reagents/plumbing/reaction_chamber + +///Returns the total volume of reagents without the catalysts +/datum/reagents/plumbing/reaction_chamber/proc/get_catalyst_excluded_volume() + SHOULD_NOT_OVERRIDE(TRUE) + + . = 0 + if(!total_volume) + return + + var/obj/machinery/plumbing/reaction_chamber/reactor = my_atom + var/list/datum/reagent/catalysts = reactor.catalysts + + var/working_volume + var/catalyst_volume + var/list/cached_reagents = reagent_list + for(var/datum/reagent/reagent as anything in cached_reagents) + catalyst_volume = catalysts[reagent.type] + working_volume = reagent.volume + + //regular reagent add to total as normal + if(!catalyst_volume) + . += working_volume + continue + + //only add the excess to total as that's what will get transferred + if(working_volume > catalyst_volume) + . += working_volume - catalyst_volume + . = min(round(., CHEMICAL_VOLUME_ROUNDING), maximum_volume) + +/datum/reagents/plumbing/reaction_chamber/trans_to( + atom/target, + amount = 1, + multiplier = 1, + datum/reagent/target_id, + preserve_data = TRUE, + no_react = FALSE, + mob/transferred_by, + remove_blacklisted = FALSE, + methods = LINEAR, + show_message = TRUE, + ignore_stomach = FALSE +) + var/obj/machinery/plumbing/reaction_chamber/reactor = my_atom + var/list/datum/reagent/catalysts = reactor.catalysts + + //usual stuff + if(!catalysts.len) + return ..() + + if(QDELETED(target)) + return FALSE + + if(!IS_FINITE(amount)) + stack_trace("non finite amount passed to trans_to [amount] amount of reagents") + return FALSE + + if(!isnull(target_id) && !ispath(target_id)) + stack_trace("invalid target reagent id [target_id] passed to trans_to") + return FALSE + + var/datum/reagents/target_holder + if(istype(target, /datum/reagents)) + target_holder = target + else + target_holder = target.reagents + var/list/cached_reagents = reagent_list + + var/actual_volume = get_catalyst_excluded_volume() + + // Prevents small amount problems, as well as zero and below zero amounts. + amount = round(min(amount, actual_volume, target_holder.maximum_volume - target_holder.total_volume), CHEMICAL_QUANTISATION_LEVEL) + if(amount <= 0) + return FALSE + + //Set up new reagents to inherit the old ongoing reactions + transfer_reactions(target_holder) + + var/list/reagents_to_remove = list() + var/working_volume + var/catalyst_volume + var/transfer_amount + var/transfered_amount + var/total_transfered_amount = 0 + + var/round_robin = methods & LINEAR + var/part + var/to_transfer + if(round_robin) + to_transfer = amount + else + part = amount / actual_volume + + //first add reagents to target + for(var/datum/reagent/reagent as anything in cached_reagents) + if(round_robin && !to_transfer) + break + working_volume = reagent.volume + + catalyst_volume = catalysts[reagent.type] + if(catalyst_volume) //we have a working catalyst + if(reagent.volume <= catalyst_volume) //dont transfer since we have the required volume + continue + else + working_volume -= catalyst_volume //dump out the excess + + if(!isnull(target_id)) + if(reagent.type == target_id) + force_stop_reagent_reacting(reagent) + transfer_amount = min(amount, working_volume) + else + continue + else + if(round_robin) + transfer_amount = min(to_transfer, working_volume) + else + transfer_amount = working_volume * part + + if(reagent.intercept_reagents_transfer(target_holder, amount)) + update_total() + target_holder.update_total() + continue + + transfered_amount = target_holder.add_reagent(reagent.type, transfer_amount, copy_data(reagent), chem_temp, reagent.purity, reagent.ph, no_react = TRUE, ignore_splitting = reagent.chemical_flags & REAGENT_DONOTSPLIT) //we only handle reaction after every reagent has been transferred. + if(!transfered_amount) + continue + reagents_to_remove += list(list("R" = reagent, "T" = transfer_amount)) + total_transfered_amount += transfered_amount + if(round_robin) + to_transfer -= transfered_amount + + if(!isnull(target_id)) + break + + //remove chemicals that were added above + for(var/list/data as anything in reagents_to_remove) + var/datum/reagent/reagent = data["R"] + transfer_amount = data["T"] + remove_reagent(reagent.type, transfer_amount) + + //handle reactions + target_holder.handle_reactions() + src.handle_reactions() + + return round(total_transfered_amount, CHEMICAL_VOLUME_ROUNDING) diff --git a/code/modules/plumbing/plumbers/reaction_chamber.dm b/code/modules/plumbing/plumbers/reaction_chamber.dm index 9828c9e697f85..551b95fc0b7d7 100644 --- a/code/modules/plumbing/plumbers/reaction_chamber.dm +++ b/code/modules/plumbing/plumbers/reaction_chamber.dm @@ -9,19 +9,23 @@ icon_state = "reaction_chamber" buffer = 200 reagent_flags = TRANSPARENT | NO_REACT + reagents = /datum/reagents/plumbing/reaction_chamber /** * list of set reagents that the reaction_chamber allows in, and must all be present before mixing is enabled. * example: list(/datum/reagent/water = 20, /datum/reagent/fuel/oil = 50) */ - var/list/required_reagents = list() - + var/list/datum/reagent/required_reagents = list() + ///list of catalyst reagents to take + var/list/datum/reagent/catalysts = list() ///our reagent goal has been reached, so now we lock our inputs and start emptying var/emptying = FALSE - ///towards which temperature do we build (except during draining)? var/target_temperature = 300 +/obj/machinery/plumbing/reaction_chamber/Destroy() + return ..() + /obj/machinery/plumbing/reaction_chamber/Initialize(mapload, bolt, layer) . = ..() AddComponent(/datum/component/plumbing/reaction_chamber, bolt, layer) @@ -36,15 +40,17 @@ SIGNAL_HANDLER UnregisterSignal(reagents, list(COMSIG_REAGENTS_REM_REAGENT, COMSIG_REAGENTS_DEL_REAGENT, COMSIG_REAGENTS_CLEAR_REAGENTS, COMSIG_REAGENTS_REACTED, COMSIG_QDELETING)) + return NONE /// Handles stopping the emptying process when the chamber empties. -/obj/machinery/plumbing/reaction_chamber/proc/on_reagent_change(datum/reagents/holder, ...) +/obj/machinery/plumbing/reaction_chamber/proc/on_reagent_change(datum/reagents/plumbing/reaction_chamber/holder, ...) SIGNAL_HANDLER - if(!holder.total_volume && emptying) //we were emptying, but now we aren't + if(!holder.get_catalyst_excluded_volume() && emptying) //we were emptying, but now we aren't emptying = FALSE holder.flags |= NO_REACT + return NONE /obj/machinery/plumbing/reaction_chamber/process(seconds_per_tick) @@ -60,8 +66,15 @@ //do other stuff with final solution handle_reagents(seconds_per_tick) -///For subtypes that want to do additional reagent handling +/** + * For subtypes that want to do additional reagent handling + * Arguments + * + * * seconds_per_tick - passed down from process() + */ /obj/machinery/plumbing/reaction_chamber/proc/handle_reagents(seconds_per_tick) + PROTECTED_PROC(TRUE) + return /obj/machinery/plumbing/reaction_chamber/power_change() @@ -81,11 +94,21 @@ var/list/reagents_data = list() for(var/datum/reagent/required_reagent as anything in required_reagents) //make a list where the key is text, because that looks alot better in the ui than a typepath var/list/reagent_data = list() + if(catalysts[required_reagent]) + continue reagent_data["name"] = initial(required_reagent.name) reagent_data["volume"] = required_reagents[required_reagent] reagents_data += list(reagent_data) + var/list/catalyst_data = list() + for(var/datum/reagent/required_catalyst as anything in catalysts) + var/list/reagent_data = list() + reagent_data["name"] = initial(required_catalyst.name) + reagent_data["volume"] = catalysts[required_catalyst] + catalyst_data += list(reagent_data) + .["reagents"] = reagents_data + .["catalysts"] = catalyst_data .["emptying"] = emptying .["temperature"] = round(reagents.chem_temp, 0.1) .["targetTemp"] = target_temperature @@ -108,9 +131,9 @@ if(!input_reagent) return FALSE - if(!required_reagents.Find(input_reagent)) + if(!required_reagents[input_reagent]) var/input_amount = text2num(params["amount"]) - if(!isnull(input_amount)) + if(input_amount) required_reagents[input_reagent] = input_amount return TRUE return FALSE @@ -129,6 +152,25 @@ return TRUE return FALSE + if("catalyst") + var/reagent = get_chem_id(params["chem"]) + + if(!reagent) + return FALSE + + if(reagent && !catalysts[reagent]) + catalysts[reagent] = required_reagents[reagent] + return TRUE + else + return FALSE + + if("catremove") + var/reagent = get_chem_id(params["chem"]) + if(reagent) + catalysts -= reagent + return TRUE + return FALSE + var/result = handle_ui_act(action, params, ui, state) if(isnull(result)) result = FALSE @@ -136,6 +178,8 @@ /// For custom handling of ui actions from inside a subtype /obj/machinery/plumbing/reaction_chamber/proc/handle_ui_act(action, params, datum/tgui/ui, datum/ui_state/state) + PROTECTED_PROC(TRUE) + return null ///Chemistry version of reaction chamber that allows for acid and base buffers to be used while reacting diff --git a/tgstation.dme b/tgstation.dme index 269325a558b54..0080adde3560a 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -5529,6 +5529,7 @@ #include "code\modules\photography\photos\photo.dm" #include "code\modules\plumbing\ducts.dm" #include "code\modules\plumbing\plumbers\_plumb_machinery.dm" +#include "code\modules\plumbing\plumbers\_plumb_reagents.dm" #include "code\modules\plumbing\plumbers\acclimator.dm" #include "code\modules\plumbing\plumbers\bottler.dm" #include "code\modules\plumbing\plumbers\destroyer.dm" diff --git a/tgui/packages/tgui/interfaces/ChemMixingChamber.tsx b/tgui/packages/tgui/interfaces/ChemMixingChamber.tsx index 765862ab4b86c..2f9a9cd901630 100644 --- a/tgui/packages/tgui/interfaces/ChemMixingChamber.tsx +++ b/tgui/packages/tgui/interfaces/ChemMixingChamber.tsx @@ -13,7 +13,7 @@ import { BooleanLike } from 'tgui-core/react'; import { useBackend } from '../backend'; import { Window } from '../layouts'; -type Reagent = { +export type Reagent = { name: string; volume: number; }; diff --git a/tgui/packages/tgui/interfaces/ChemReactionChamber.tsx b/tgui/packages/tgui/interfaces/ChemReactionChamber.tsx index cdc2b6fac97b9..255e669efcf04 100644 --- a/tgui/packages/tgui/interfaces/ChemReactionChamber.tsx +++ b/tgui/packages/tgui/interfaces/ChemReactionChamber.tsx @@ -13,12 +13,13 @@ import { round, toFixed } from 'tgui-core/math'; import { useBackend } from '../backend'; import { Window } from '../layouts'; -import { MixingData } from './ChemMixingChamber'; +import { MixingData, Reagent } from './ChemMixingChamber'; type ReactingData = MixingData & { ph: number; reagentAcidic: number; reagentAlkaline: number; + catalysts: Reagent[]; }; export const ChemReactionChamber = (props) => { @@ -36,8 +37,9 @@ export const ChemReactionChamber = (props) => { reagentAlkaline, } = data; const reagents = data.reagents || []; + const catalysts = data.catalysts || []; return ( - + @@ -130,8 +132,9 @@ export const ChemReactionChamber = (props) => {

@@ -189,7 +192,6 @@ export const ChemReactionChamber = (props) => { { {reagent.volume} + + + +
+ +
+ + + {catalysts.map((reagent) => ( + + + + {reagent.name + ':'} + + + {reagent.volume} + + + + + + + ))} + + +
+
From 5cae186c73b4da5aa8ab0abbee92851f31bc32b6 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Tue, 24 Dec 2024 08:52:17 +0000 Subject: [PATCH 088/235] Automatic changelog for PR #88404 [ci skip] --- html/changelogs/AutoChangeLog-pr-88404.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88404.yml diff --git a/html/changelogs/AutoChangeLog-pr-88404.yml b/html/changelogs/AutoChangeLog-pr-88404.yml new file mode 100644 index 0000000000000..0e26dc409bf11 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88404.yml @@ -0,0 +1,4 @@ +author: "lovegreenstuff" +delete-after: True +changes: + - rscadd: "catalyst function for plumbing reaction chambers" \ No newline at end of file From 49fcd0f11db18b28c87649f4d2eb4859978c83a8 Mon Sep 17 00:00:00 2001 From: Ghom <42542238+Ghommie@users.noreply.github.com> Date: Tue, 24 Dec 2024 17:30:44 +0100 Subject: [PATCH 089/235] [NO GBP] Fixed aquariums auto-feeding (#88660) Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com> --- code/datums/components/aquarium.dm | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/code/datums/components/aquarium.dm b/code/datums/components/aquarium.dm index f207ecd9f5510..69939cb67b92e 100644 --- a/code/datums/components/aquarium.dm +++ b/code/datums/components/aquarium.dm @@ -100,11 +100,12 @@ ADD_KEEP_TOGETHER(movable, AQUARIUM_TRAIT) //render the fish on the same layer of the aquarium. if(reagents_size > 0) - RegisterSignal(movable.reagents, COMSIG_REAGENTS_NEW_REAGENT, PROC_REF(start_autofeed)) if(!movable.reagents) movable.create_reagents(reagents_size, SEALED_CONTAINER) - else if(movable.reagents.total_volume) + if(movable.reagents.total_volume) start_autofeed(movable.reagents) + else + RegisterSignal(movable.reagents, COMSIG_REAGENTS_NEW_REAGENT, PROC_REF(start_autofeed)) RegisterSignal(movable, COMSIG_PLUNGER_ACT, PROC_REF(on_plunger_act)) RegisterSignal(movable, COMSIG_ATOM_ITEM_INTERACTION, PROC_REF(on_item_interaction)) @@ -240,10 +241,10 @@ return ITEM_INTERACT_SUCCESS ///Called when the feed storage is no longer empty. -/datum/component/aquarium/proc/start_autofeed(atom/movable/source, new_reagent, amount, reagtemp, data, no_react) +/datum/component/aquarium/proc/start_autofeed(datum/reagents/source, new_reagent, amount, reagtemp, data, no_react) SIGNAL_HANDLER START_PROCESSING(SSobj, src) - UnregisterSignal(source.reagents, COMSIG_REAGENTS_NEW_REAGENT) + UnregisterSignal(source, COMSIG_REAGENTS_NEW_REAGENT) ///Feed the fish at defined intervals until the feed storage is empty. /datum/component/aquarium/process(seconds_per_tick) From 73febdadae0b21d15f78b19d7ec90471591b2c5d Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Tue, 24 Dec 2024 16:31:03 +0000 Subject: [PATCH 090/235] Automatic changelog for PR #88660 [ci skip] --- html/changelogs/AutoChangeLog-pr-88660.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88660.yml diff --git a/html/changelogs/AutoChangeLog-pr-88660.yml b/html/changelogs/AutoChangeLog-pr-88660.yml new file mode 100644 index 0000000000000..e8c6f88b58fca --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88660.yml @@ -0,0 +1,4 @@ +author: "Ghommie" +delete-after: True +changes: + - bugfix: "Fixed aquariums auto-feeding." \ No newline at end of file From de07fe3e25f501291df836c92beede4dd265d9d8 Mon Sep 17 00:00:00 2001 From: grungussuss <96586172+Sadboysuss@users.noreply.github.com> Date: Tue, 24 Dec 2024 20:02:01 +0300 Subject: [PATCH 091/235] Computers can act like cover and will no longer never allow projectiles to pass over them (much l like barricades) (#88646) Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com> --- code/game/machinery/computer/_computer.dm | 19 +++++++++++++++++++ .../game/machinery/computer/arcade/_arcade.dm | 1 + .../machinery/computer/records/medical.dm | 1 + .../machinery/computer/records/security.dm | 1 + code/game/machinery/computer/telescreen.dm | 1 + code/game/machinery/constructable_frame.dm | 5 +++++ 6 files changed, 28 insertions(+) diff --git a/code/game/machinery/computer/_computer.dm b/code/game/machinery/computer/_computer.dm index eb48792a523bc..d51e197a0bcdd 100644 --- a/code/game/machinery/computer/_computer.dm +++ b/code/game/machinery/computer/_computer.dm @@ -19,6 +19,8 @@ var/time_to_unscrew = 2 SECONDS /// Are we authenticated to use this? Used by things like comms console, security and medical data, and apc controller. var/authenticated = FALSE + /// Will projectiles be able to pass over this computer? + var/projectiles_pass_chance = 65 /datum/armor/machinery_computer fire = 40 @@ -28,6 +30,23 @@ . = ..() power_change() +/obj/machinery/computer/CanAllowThrough(atom/movable/mover, border_dir) // allows projectiles to fly over the computer + . = ..() + if(.) + return + if(!projectiles_pass_chance) + return FALSE + if(!isprojectile(mover)) + return FALSE + var/obj/projectile/proj = mover + if(!anchored) + return TRUE + if(proj.firer && Adjacent(proj.firer)) + return TRUE + if(prob(projectiles_pass_chance)) + return TRUE + return FALSE + /obj/machinery/computer/process() if(machine_stat & (NOPOWER|BROKEN)) return FALSE diff --git a/code/game/machinery/computer/arcade/_arcade.dm b/code/game/machinery/computer/arcade/_arcade.dm index eb91fa44f1c70..1627a3d0fe81b 100644 --- a/code/game/machinery/computer/arcade/_arcade.dm +++ b/code/game/machinery/computer/arcade/_arcade.dm @@ -7,6 +7,7 @@ icon_screen = "invaders" light_color = LIGHT_COLOR_GREEN interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON|INTERACT_MACHINE_REQUIRES_LITERACY + projectiles_pass_chance = 0 // I guess gambling can save your life huh? ///If set, will dispense these as prizes instead of the default GLOB.arcade_prize_pool ///Like prize pool, it must be a list of the prize and the weight of being selected. diff --git a/code/game/machinery/computer/records/medical.dm b/code/game/machinery/computer/records/medical.dm index 6dd12acbdb678..c5f11ec89ca7c 100644 --- a/code/game/machinery/computer/records/medical.dm +++ b/code/game/machinery/computer/records/medical.dm @@ -18,6 +18,7 @@ icon_screen = "medlaptop" icon_keyboard = "laptop_key" pass_flags = PASSTABLE + projectiles_pass_chance = 100 /obj/machinery/computer/records/medical/attacked_by(obj/item/attacking_item, mob/living/user) . = ..() diff --git a/code/game/machinery/computer/records/security.dm b/code/game/machinery/computer/records/security.dm index 89f49cf754b8a..693bd7daa7185 100644 --- a/code/game/machinery/computer/records/security.dm +++ b/code/game/machinery/computer/records/security.dm @@ -27,6 +27,7 @@ icon_screen = "seclaptop" icon_keyboard = "laptop_key" pass_flags = PASSTABLE + projectiles_pass_chance = 100 /obj/machinery/computer/records/security/laptop/syndie desc = "A cheap, jailbroken security laptop. It functions as a security records console. It's bolted to the table." diff --git a/code/game/machinery/computer/telescreen.dm b/code/game/machinery/computer/telescreen.dm index f3a6a9879b4d1..3b96ae111fba3 100644 --- a/code/game/machinery/computer/telescreen.dm +++ b/code/game/machinery/computer/telescreen.dm @@ -12,6 +12,7 @@ light_power = 0 /// The kind of wallframe that this telescreen drops var/frame_type = /obj/item/wallframe/telescreen + projectiles_pass_chance = 100 /obj/item/wallframe/telescreen name = "telescreen frame" diff --git a/code/game/machinery/constructable_frame.dm b/code/game/machinery/constructable_frame.dm index b90302111ab7b..0a41adcac98f2 100644 --- a/code/game/machinery/constructable_frame.dm +++ b/code/game/machinery/constructable_frame.dm @@ -22,6 +22,11 @@ if(circuit) . += "It has \a [circuit] installed." +/obj/structure/frame/CanAllowThrough(atom/movable/mover, border_dir) + if(isprojectile(mover)) + return TRUE + return ..() + /obj/structure/frame/atom_deconstruct(disassembled = TRUE) var/atom/movable/drop_loc = drop_location() new /obj/item/stack/sheet/iron(drop_loc, 5) From 211a881d45e14d812c15c2824bfb486957de3e5e Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Tue, 24 Dec 2024 17:02:23 +0000 Subject: [PATCH 092/235] Automatic changelog for PR #88646 [ci skip] --- html/changelogs/AutoChangeLog-pr-88646.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88646.yml diff --git a/html/changelogs/AutoChangeLog-pr-88646.yml b/html/changelogs/AutoChangeLog-pr-88646.yml new file mode 100644 index 0000000000000..cbd53fa0fe35a --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88646.yml @@ -0,0 +1,5 @@ +author: "grungussuss" +delete-after: True +changes: + - balance: "computers can now be used as cover, firing a projectile over them is now possible, while they may block projectiles if you are not adjacent to them when firing." + - bugfix: "computer laptops will not block projectiles" \ No newline at end of file From eeadfda40c3ff078436fb05175538c158a66dc69 Mon Sep 17 00:00:00 2001 From: KazooBard <65713506+KazooBard@users.noreply.github.com> Date: Tue, 24 Dec 2024 18:56:59 +0100 Subject: [PATCH 093/235] Lowers cooldown on hotkey spell selection (#88503) --- code/__DEFINES/combat.dm | 1 + code/datums/actions/action.dm | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm index 5cb838603c98e..867d8a51d4163 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -98,6 +98,7 @@ DEFINE_BITFIELD(status_flags, list( #define CLICK_CD_RAPID 2 #define CLICK_CD_HYPER_RAPID 1 #define CLICK_CD_SLOW 10 +#define CLICK_CD_ACTIVATE_ABILITY 1 #define CLICK_CD_THROW 8 #define CLICK_CD_RANGE 4 diff --git a/code/datums/actions/action.dm b/code/datums/actions/action.dm index 2f297f480ae66..18525a8c04e94 100644 --- a/code/datums/actions/action.dm +++ b/code/datums/actions/action.dm @@ -435,5 +435,5 @@ if(source.next_click > world.time) return else - source.next_click = world.time + CLICK_CD_RANGE + source.next_click = world.time + CLICK_CD_ACTIVATE_ABILITY INVOKE_ASYNC(src, PROC_REF(Trigger)) From 789980cfb9b2f3c34dd4f51c32bbac4ebf8639c2 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Tue, 24 Dec 2024 17:57:18 +0000 Subject: [PATCH 094/235] Automatic changelog for PR #88503 [ci skip] --- html/changelogs/AutoChangeLog-pr-88503.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88503.yml diff --git a/html/changelogs/AutoChangeLog-pr-88503.yml b/html/changelogs/AutoChangeLog-pr-88503.yml new file mode 100644 index 0000000000000..ab4287205b27a --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88503.yml @@ -0,0 +1,4 @@ +author: "KazooBard" +delete-after: True +changes: + - qol: "Selecting which spells to cast with hotkeys, and using them in general is faster." \ No newline at end of file From fe5398b368ff7685a476dc98e7afb4e02950b73f Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Tue, 24 Dec 2024 11:57:54 -0600 Subject: [PATCH 095/235] Navigate verb now indicates areas above or below you (#88505) --- code/modules/mob/living/navigation.dm | 44 +++++++++++++++++---------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/code/modules/mob/living/navigation.dm b/code/modules/mob/living/navigation.dm index a18342c445616..fea7d9a787153 100644 --- a/code/modules/mob/living/navigation.dm +++ b/code/modules/mob/living/navigation.dm @@ -24,40 +24,50 @@ addtimer(CALLBACK(src, PROC_REF(create_navigation)), world.tick_lag) /mob/living/proc/create_navigation() + var/can_go_down = SSmapping.level_trait(z, ZTRAIT_DOWN) + var/can_go_up = SSmapping.level_trait(z, ZTRAIT_UP) var/list/destination_list = list() - for(var/atom/destination in GLOB.navigate_destinations) - if(!isatom(destination) || destination.z != z || get_dist(destination, src) > MAX_NAVIGATE_RANGE) + for(var/atom/destination as anything in GLOB.navigate_destinations) + if(get_dist(destination, src) > MAX_NAVIGATE_RANGE) continue var/destination_name = GLOB.navigate_destinations[destination] + if(destination.z != z && (can_go_down || can_go_up)) // up or down is just a good indicator "we're on the station", we don't need to check specifics + destination_name += ((get_dir_multiz(src, destination) & UP) ? " (Above)" : " (Below)") + destination_list[destination_name] = destination - if(!is_reserved_level(z)) //don't let us path to nearest staircase or ladder on shuttles in transit - if(z > 1) - destination_list["Nearest Way Down"] = DOWN - if(z < world.maxz) - destination_list["Nearest Way Up"] = UP + if(can_go_down) + destination_list["Nearest Way Down"] = DOWN + if(can_go_up) + destination_list["Nearest Way Up"] = UP if(!length(destination_list)) balloon_alert(src, "no navigation signals!") return var/platform_code = tgui_input_list(src, "Select a location", "Navigate", sort_list(destination_list)) - var/navigate_target = destination_list[platform_code] + var/atom/navigate_target = destination_list[platform_code] - if(isnull(navigate_target)) - return - if(incapacitated) + if(isnull(navigate_target) || incapacitated) return - COOLDOWN_START(src, navigate_cooldown, 15 SECONDS) - if(navigate_target == UP || navigate_target == DOWN) - var/new_target = find_nearest_stair_or_ladder(navigate_target) + + var/finding_zchange = FALSE + COOLDOWN_START(src, navigate_cooldown, 15 SECONDS) + if(navigate_target == UP || navigate_target == DOWN || (isatom(navigate_target) && navigate_target.z != z)) + // lowering the cooldown to 5 seconds if we're navigating to a ladder or staircase instead of a proper destination + // (so we can decide to move to another destination right off the bat, rather than needing to wait) + COOLDOWN_START(src, navigate_cooldown, 5 SECONDS) + var/direction_name = isatom(navigate_target) ? "there" : (navigate_target == UP ? "up" : "down") + var/nav_dir = isatom(navigate_target) ? (get_dir_multiz(src, navigate_target) & (UP|DOWN)) : navigate_target + var/atom/new_target = find_nearest_stair_or_ladder(nav_dir) if(!new_target) - balloon_alert(src, "can't find ladder or staircase going [navigate_target == UP ? "up" : "down"]!") + balloon_alert(src, "can't find ladder or staircase going [direction_name]!") return navigate_target = new_target + finding_zchange = TRUE if(!isatom(navigate_target)) stack_trace("Navigate target ([navigate_target]) is not an atom, somehow.") @@ -92,6 +102,8 @@ animate(path_image, 0.5 SECONDS, alpha = 150) addtimer(CALLBACK(src, PROC_REF(shine_navigation)), 0.5 SECONDS) RegisterSignal(src, COMSIG_LIVING_DEATH, PROC_REF(cut_navigation)) + if(finding_zchange) + RegisterSignal(src, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(cut_navigation)) balloon_alert(src, "navigation path created") /mob/living/proc/shine_navigation() @@ -107,7 +119,7 @@ for(var/image/navigation_path in client.navigation_images) client.images -= navigation_path client.navigation_images.Cut() - UnregisterSignal(src, COMSIG_LIVING_DEATH) + UnregisterSignal(src, list(COMSIG_LIVING_DEATH, COMSIG_MOVABLE_Z_CHANGED)) /** * Finds nearest ladder or staircase either up or down. From 91ac612707b0e796522c45ab1b01eb20b92c12a5 Mon Sep 17 00:00:00 2001 From: larentoun <31931237+larentoun@users.noreply.github.com> Date: Tue, 24 Dec 2024 20:58:02 +0300 Subject: [PATCH 096/235] Renames UpdatePath script name for consistency (#88520) --- .../{repaths_a357_to_c357.txt => 88095_repaths_a357_to_c357.txt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tools/UpdatePaths/Scripts/{repaths_a357_to_c357.txt => 88095_repaths_a357_to_c357.txt} (100%) diff --git a/tools/UpdatePaths/Scripts/repaths_a357_to_c357.txt b/tools/UpdatePaths/Scripts/88095_repaths_a357_to_c357.txt similarity index 100% rename from tools/UpdatePaths/Scripts/repaths_a357_to_c357.txt rename to tools/UpdatePaths/Scripts/88095_repaths_a357_to_c357.txt From edf19efecde5967f7d7f69bed28e4453491af3b0 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Tue, 24 Dec 2024 17:58:19 +0000 Subject: [PATCH 097/235] Automatic changelog for PR #88505 [ci skip] --- html/changelogs/AutoChangeLog-pr-88505.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88505.yml diff --git a/html/changelogs/AutoChangeLog-pr-88505.yml b/html/changelogs/AutoChangeLog-pr-88505.yml new file mode 100644 index 0000000000000..1ac4f6cb6788a --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88505.yml @@ -0,0 +1,5 @@ +author: "Melbert" +delete-after: True +changes: + - qol: "Navigate verb better indicates areas on a separate level. Selecting an area on a different level will direct you to the nearest staircase (as with \"Nearest Way Up/Down\")" + - qol: "Navigating to a staircase or ladder will shorten the cooldown of navigate and clear your existing path for you upon finding it" \ No newline at end of file From 60b03d74aab79ee64dfd80a41bd8d155083b7f86 Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Tue, 24 Dec 2024 12:09:10 -0600 Subject: [PATCH 098/235] Lava ignores thrown mobs (#88640) --- code/game/turfs/open/lava.dm | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/code/game/turfs/open/lava.dm b/code/game/turfs/open/lava.dm index 2ec0b8e9a9c42..a71ed37f374f9 100644 --- a/code/game/turfs/open/lava.dm +++ b/code/game/turfs/open/lava.dm @@ -266,12 +266,9 @@ /turf/open/lava/proc/can_burn_stuff(atom/movable/burn_target) if(QDELETED(burn_target)) return LAVA_BE_IGNORING - if(burn_target.movement_type & MOVETYPES_NOT_TOUCHING_GROUND || !burn_target.has_gravity()) //you're flying over it. + if((burn_target.movement_type & MOVETYPES_NOT_TOUCHING_GROUND) || burn_target.throwing || !burn_target.has_gravity()) //you're flying over it. return LAVA_BE_IGNORING - if(isobj(burn_target)) - if(burn_target.throwing) // to avoid gulag prisoners easily escaping, throwing only works for objects. - return LAVA_BE_IGNORING var/obj/burn_obj = burn_target if((burn_obj.resistance_flags & immunity_resistance_flags)) return LAVA_BE_PROCESSING @@ -285,7 +282,7 @@ var/mob/living/burn_living = burn_target var/atom/movable/burn_buckled = burn_living.buckled if(burn_buckled) - if(burn_buckled.movement_type & MOVETYPES_NOT_TOUCHING_GROUND || !burn_buckled.has_gravity()) + if((burn_buckled.movement_type & MOVETYPES_NOT_TOUCHING_GROUND) || burn_buckled.throwing || !burn_buckled.has_gravity()) return LAVA_BE_PROCESSING if(isobj(burn_buckled)) var/obj/burn_buckled_obj = burn_buckled From ead9df466e700d585bcbe8274b7bedbad14598ba Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Tue, 24 Dec 2024 18:09:51 +0000 Subject: [PATCH 099/235] Automatic changelog for PR #88640 [ci skip] --- html/changelogs/AutoChangeLog-pr-88640.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88640.yml diff --git a/html/changelogs/AutoChangeLog-pr-88640.yml b/html/changelogs/AutoChangeLog-pr-88640.yml new file mode 100644 index 0000000000000..ef98c0c7323af --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88640.yml @@ -0,0 +1,4 @@ +author: "Melbert" +delete-after: True +changes: + - balance: "Lava no longer burns mobs thrown over it" \ No newline at end of file From 7705cca72be4e822909ba9adfe5e77d893b223a0 Mon Sep 17 00:00:00 2001 From: araeotu Date: Wed, 25 Dec 2024 02:10:04 +0800 Subject: [PATCH 100/235] Add circuit component for slime processor (#88463) Co-authored-by: Arae <230020345@stu.vtc.edu.hk> --- .../food_and_drinks/machinery/processor.dm | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/code/modules/food_and_drinks/machinery/processor.dm b/code/modules/food_and_drinks/machinery/processor.dm index 383a7c34e2756..03c42761da379 100644 --- a/code/modules/food_and_drinks/machinery/processor.dm +++ b/code/modules/food_and_drinks/machinery/processor.dm @@ -143,10 +143,14 @@ if(!LAZYLEN(processor_contents)) to_chat(user, span_warning("[src] is empty!")) return TRUE - processing = TRUE user.visible_message(span_notice("[user] turns on [src]."), \ span_notice("You turn on [src]."), \ span_hear("You hear a food processor.")) + processing() + + +/obj/machinery/processor/proc/processing() + processing = TRUE playsound(src.loc, 'sound/machines/blender.ogg', 50, TRUE) use_energy(active_power_usage) var/total_time = 0 @@ -197,6 +201,12 @@ desc = "An industrial grinder with a sticker saying appropriated for science department. Keep hands clear of intake area while operating." circuit = /obj/item/circuitboard/machine/processor/slime +/obj/machinery/processor/slime/Initialize(mapload) + . = ..() + AddComponent(/datum/component/usb_port, list( + /obj/item/circuit_component/slime_processor, + )) + /obj/machinery/processor/slime/adjust_item_drop_location(atom/movable/atom_to_drop) var/static/list/slimecores = subtypesof(/obj/item/slime_extract) var/i = 0 @@ -249,4 +259,42 @@ SSblackbox.record_feedback("tally", "slime_core_harvested", 1, processed_slime.slime_type.colour) return ..() +/obj/item/circuit_component/slime_processor + display_name = "Slime Processor" + desc = "Allows to activate process and get the amount of processor contents." + circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL + + ///Activate process + var/datum/port/input/active + ///Amount of processor contents + var/datum/port/output/amount + + var/obj/machinery/processor/slime/attached_processor + +/obj/item/circuit_component/slime_processor/populate_ports() + active = add_input_port("Activate", PORT_TYPE_SIGNAL, trigger = PROC_REF(activate)) + amount = add_output_port("Amount", PORT_TYPE_NUMBER) + +/obj/item/circuit_component/slime_processor/register_usb_parent(atom/movable/parent) + . = ..() + if(istype(parent, /obj/machinery/processor/slime)) + attached_processor = parent + +/obj/item/circuit_component/slime_processor/unregister_usb_parent(atom/movable/parent) + attached_processor = null + return ..() + +/obj/item/circuit_component/slime_processor/proc/activate() + SIGNAL_HANDLER + input_received() + if(attached_processor.processing) + return + if(!LAZYLEN(attached_processor.processor_contents)) + return + attached_processor.processing() + +/obj/item/circuit_component/slime_processor/input_received() + var/list/contents = attached_processor.processor_contents + amount.set_output(LAZYLEN(contents)) + #undef PROCESSOR_SELECT_RECIPE From a2c82670c2f3adbb211d57999684e1b1bd91ea34 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Tue, 24 Dec 2024 18:17:54 +0000 Subject: [PATCH 101/235] Automatic changelog for PR #88463 [ci skip] --- html/changelogs/AutoChangeLog-pr-88463.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88463.yml diff --git a/html/changelogs/AutoChangeLog-pr-88463.yml b/html/changelogs/AutoChangeLog-pr-88463.yml new file mode 100644 index 0000000000000..09ba5ff91e503 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88463.yml @@ -0,0 +1,5 @@ +author: "araeotu" +delete-after: True +changes: + - rscadd: "slime processor now have usb port and circuit component" + - code_imp: "the process code of processor now separate from interact" \ No newline at end of file From 78bb4f44cf299b7f51f5e48c52e4b7f2152c4385 Mon Sep 17 00:00:00 2001 From: SyncIt21 <110812394+SyncIt21@users.noreply.github.com> Date: Tue, 24 Dec 2024 23:59:41 +0530 Subject: [PATCH 102/235] Fixes big manipulator manipulating laws of physics (#88677) --- code/game/machinery/big_manipulator.dm | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/code/game/machinery/big_manipulator.dm b/code/game/machinery/big_manipulator.dm index 21a7de89fe619..a14a6b8531174 100644 --- a/code/game/machinery/big_manipulator.dm +++ b/code/game/machinery/big_manipulator.dm @@ -288,7 +288,12 @@ target.forceMove(drop_turf) target.dir = get_dir(get_turf(target), get_turf(src)) else - target.forceMove(where_we_drop) + var/atom/drop_target = where_we_drop + if(drop_target.atom_storage) + if(!drop_target.atom_storage.attempt_insert(target, override = TRUE, messages = FALSE)) + target.forceMove(drop_target.drop_location()) + else + target.forceMove(where_we_drop) finish_manipulation() /// 3.3 take and drop proc from [take and drop procs loop]: From 900bba2c1ba2fff8359a6678cf1757fa81dd3f0e Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Tue, 24 Dec 2024 18:30:09 +0000 Subject: [PATCH 103/235] Automatic changelog for PR #88677 [ci skip] --- html/changelogs/AutoChangeLog-pr-88677.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88677.yml diff --git a/html/changelogs/AutoChangeLog-pr-88677.yml b/html/changelogs/AutoChangeLog-pr-88677.yml new file mode 100644 index 0000000000000..9489b57d3c01e --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88677.yml @@ -0,0 +1,4 @@ +author: "SyncIt21" +delete-after: True +changes: + - bugfix: "Big manipulator respects storage limits of storages when dropping stuff into them" \ No newline at end of file From 946a481c8181473e06bf8b3cc56ad369dc0eda00 Mon Sep 17 00:00:00 2001 From: Lucy Date: Tue, 24 Dec 2024 14:31:00 -0500 Subject: [PATCH 104/235] Fix tgui chat panel z-fighting on BYOND 516 (#88663) ## About The Pull Request Port of https://github.com/ParadiseSS13/Paradise/pull/27676 and https://github.com/VOREStation/VOREStation/pull/16734 > Instead of relying on `is-disabled` and `is-visible`, which BYOND happily will automatically change for you whenever you send a client text, we now use a Child element to swap between the legacy output and browser output in separate preset panes. > > TL;DR: chat would flash white under 516, now doesn't I cleared cache before each test video below, just to be 100% sure
Testing Evidence: BYOND 515 https://github.com/user-attachments/assets/8d661cc3-585e-4f8e-9399-76df8bc0a281
Testing Evidence: BYOND 516 https://github.com/user-attachments/assets/c0d31fb4-6ef5-4d49-81a8-c767c5e24cc2
## Why It's Good For The Game flickering chat hurts my eyes ## Changelog :cl: Absolucy, ShadowLarkens, S34N fix: Fixed chat rapidly flickering in BYOND 516. /:cl: --- code/modules/tgui_panel/external.dm | 9 +++---- interface/skin.dmf | 42 ++++++++++++++++++++++++----- tgui/packages/tgui-panel/index.tsx | 10 ++----- 3 files changed, 41 insertions(+), 20 deletions(-) diff --git a/code/modules/tgui_panel/external.dm b/code/modules/tgui_panel/external.dm index 3e7dbc3178f2a..e48ddeb223904 100644 --- a/code/modules/tgui_panel/external.dm +++ b/code/modules/tgui_panel/external.dm @@ -19,22 +19,19 @@ // Failed to fix, using tgalert as fallback action = tgalert(src, "Did that work?", "", "Yes", "No, switch to old ui") if (action == "No, switch to old ui") - winset(src, "output", "on-show=&is-disabled=0&is-visible=1") - winset(src, "browseroutput", "is-disabled=1;is-visible=0") + winset(src, "legacy_output_selector", "left=output_legacy") log_tgui(src, "Failed to fix.", context = "verb/fix_tgui_panel") /client/proc/nuke_chat() // Catch all solution (kick the whole thing in the pants) - winset(src, "output", "on-show=&is-disabled=0&is-visible=1") - winset(src, "browseroutput", "is-disabled=1;is-visible=0") + winset(src, "legacy_output_selector", "left=output_legacy") if(!tgui_panel || !istype(tgui_panel)) log_tgui(src, "tgui_panel datum is missing", context = "verb/fix_tgui_panel") tgui_panel = new(src) tgui_panel.initialize(force = TRUE) // Force show the panel to see if there are any errors - winset(src, "output", "is-disabled=1&is-visible=0") - winset(src, "browseroutput", "is-disabled=0;is-visible=1") + winset(src, "legacy_output_selector", "left=output_browser") /client/verb/refresh_tgui() set name = "Refresh TGUI" diff --git a/interface/skin.dmf b/interface/skin.dmf index 584c88b8d5bc2..7bf5d6b58a713 100644 --- a/interface/skin.dmf +++ b/interface/skin.dmf @@ -224,7 +224,7 @@ window "infowindow" window "outputwindow" elem "outputwindow" type = MAIN - pos = 281,0 + pos = 0,0 size = 640x480 anchor1 = -1,-1 anchor2 = -1,-1 @@ -281,15 +281,26 @@ window "outputwindow" command = ".winset \"mebutton.is-checked=true ? input.command=\"!me \\\"\" : input.command=\"\"mebutton.is-checked=true ? saybutton.is-checked=false\"\"mebutton.is-checked=true ? oocbutton.is-checked=false\"" is-flat = true button-type = pushbox - elem "browseroutput" - type = BROWSER + elem "legacy_output_selector" + type = CHILD pos = 0,0 size = 640x456 anchor1 = 0,0 anchor2 = 100,100 - is-visible = false - is-disabled = true - saved-params = "" + saved-params = "splitter" + left = "output_legacy" + is-vert = false + +window "output_legacy" + elem "output_legacy" + type = MAIN + pos = 0,0 + size = 640x456 + anchor1 = -1,-1 + anchor2 = -1,-1 + background-color = none + saved-params = "pos;size;is-minimized;is-maximized" + is-pane = true elem "output" type = OUTPUT pos = 0,0 @@ -299,6 +310,25 @@ window "outputwindow" is-default = true saved-params = "" +window "output_browser" + elem "output_browser" + type = MAIN + pos = 0,0 + size = 640x456 + anchor1 = -1,-1 + anchor2 = -1,-1 + background-color = none + saved-params = "pos;size;is-minimized;is-maximized" + is-pane = true + elem "browseroutput" + type = BROWSER + pos = 0,0 + size = 640x456 + anchor1 = 0,0 + anchor2 = 100,100 + background-color = none + saved-params = "" + window "popupwindow" elem "popupwindow" type = MAIN diff --git a/tgui/packages/tgui-panel/index.tsx b/tgui/packages/tgui-panel/index.tsx index a70a53bac251e..9fccceb4d2178 100644 --- a/tgui/packages/tgui-panel/index.tsx +++ b/tgui/packages/tgui-panel/index.tsx @@ -75,14 +75,8 @@ const setupApp = () => { Byond.subscribe((type, payload) => store.dispatch({ type, payload })); // Unhide the panel - Byond.winset('output', { - 'is-visible': false, - }); - Byond.winset('browseroutput', { - 'is-visible': true, - 'is-disabled': false, - pos: '0x0', - size: '0x0', + Byond.winset('legacy_output_selector', { + left: 'output_browser', }); // Resize the panel to match the non-browser output From 7a817300fd90ea53e4a476f0e119066cf8425646 Mon Sep 17 00:00:00 2001 From: Aylong <69762909+AyIong@users.noreply.github.com> Date: Tue, 24 Dec 2024 21:32:38 +0200 Subject: [PATCH 105/235] Improve and extend `fieldset_block` and `examine_block` (#88678) ## About The Pull Request Maked `fieldset_block` and `examine_block` more stylish and neat, also `fieldset_block` no longer has a centred title. Renamed `examine_block` to `boxed_message` and adds `custom_boxed_message` which can be colored. - AdminPMs, admin tickets and vote results has been wrapped into `fieldset_block` for comfort and visibility - Health Analyzer results painted to blue - Vote notice and tips of the round wrapped to purple `custom_boxed_message` - Tooltip text border color, now uses text color, not just white ## Why It's Good For The Game Demonstration in both themes
Dark ![image](https://github.com/user-attachments/assets/7175379b-b053-4fb7-bd25-65c744a21c56) ![image](https://github.com/user-attachments/assets/1728e72b-0110-4b81-9d61-8779f5fdc3a0) ![image](https://github.com/user-attachments/assets/5e6f9604-35b8-4840-b6b4-35a68f49a997)
Light ![image](https://github.com/user-attachments/assets/6a3d693b-e0dc-4a4b-b4d7-2ded54ce0d67) ![image](https://github.com/user-attachments/assets/c4f5e089-180f-4d13-806a-fa64f01740a3) ![image](https://github.com/user-attachments/assets/a46d52c4-ad37-4637-8cae-c4b00139efc1)
## Changelog :cl: qol: AdminPMs, admin tickets, vote results and started vote notification are now much more visible in the chat. qol: Boxed messages in chat (like examine), has been restyled. /:cl: --- code/__DEFINES/chat.dm | 8 +- code/__HELPERS/game.dm | 2 +- code/_onclick/hud/alert.dm | 2 +- code/controllers/subsystem/map_vote.dm | 4 +- code/controllers/subsystem/polling.dm | 2 +- code/controllers/subsystem/vote.dm | 6 +- code/datums/ai_laws/ai_laws.dm | 2 +- code/datums/components/trader/trader.dm | 4 +- code/datums/elements/slapcrafting.dm | 4 +- code/datums/elements/weapon_description.dm | 2 +- code/datums/mind/skills.dm | 2 +- code/datums/mood.dm | 2 +- code/datums/votes/_vote_datum.dm | 9 ++- .../objects/items/AI_modules/_AI_modules.dm | 2 +- code/game/objects/items/devices/flashlight.dm | 2 +- .../items/devices/scanners/gas_analyzer.dm | 2 +- .../items/devices/scanners/health_analyzer.dm | 8 +- .../items/devices/scanners/slime_scanner.dm | 2 +- code/modules/admin/topic.dm | 2 +- code/modules/admin/verbs/adminhelp.dm | 7 +- code/modules/admin/verbs/adminpm.dm | 28 ++----- code/modules/admin/verbs/mapping.dm | 2 +- code/modules/buildmode/submodes/advanced.dm | 2 +- code/modules/buildmode/submodes/area_edit.dm | 2 +- code/modules/buildmode/submodes/basic.dm | 2 +- code/modules/buildmode/submodes/boom.dm | 2 +- code/modules/buildmode/submodes/copy.dm | 2 +- code/modules/buildmode/submodes/delete.dm | 2 +- code/modules/buildmode/submodes/fill.dm | 2 +- code/modules/buildmode/submodes/map_export.dm | 2 +- code/modules/buildmode/submodes/mapgen.dm | 2 +- code/modules/buildmode/submodes/outfit.dm | 2 +- code/modules/buildmode/submodes/proccall.dm | 2 +- code/modules/buildmode/submodes/smite.dm | 2 +- code/modules/buildmode/submodes/throwing.dm | 2 +- code/modules/buildmode/submodes/tweakcomps.dm | 2 +- .../buildmode/submodes/variable_edit.dm | 2 +- code/modules/client/verbs/who.dm | 2 +- code/modules/clothing/clothing.dm | 2 +- code/modules/clothing/neck/_neck.dm | 2 +- code/modules/fishing/fishing_rod.dm | 6 +- code/modules/hydroponics/hydroitemdefines.dm | 16 ++-- code/modules/jobs/job_types/_job.dm | 2 +- code/modules/lootpanel/misc.dm | 2 +- code/modules/mob/emote.dm | 2 +- .../basic/space_fauna/revenant/_revenant.dm | 2 +- code/modules/mob/living/carbon/human/human.dm | 4 +- .../mob/living/carbon/human/human_defense.dm | 2 +- code/modules/mob/living/carbon/human/login.dm | 2 +- code/modules/mob/living/silicon/laws.dm | 2 +- code/modules/mob/mob.dm | 4 +- code/modules/reagents/chemistry/items.dm | 2 +- .../chemistry/machinery/reagentgrinder.dm | 2 +- .../research/xenobiology/xenobio_camera.dm | 2 +- .../tgui-panel/styles/tgchat/chat-dark.scss | 78 +++++++++++++++++-- .../tgui-panel/styles/tgchat/chat-light.scss | 42 ++++++++-- 56 files changed, 196 insertions(+), 114 deletions(-) diff --git a/code/__DEFINES/chat.dm b/code/__DEFINES/chat.dm index 516fe8c4e193a..80fb1a07eccf3 100644 --- a/code/__DEFINES/chat.dm +++ b/code/__DEFINES/chat.dm @@ -44,9 +44,11 @@ /// Used for debug messages to the server #define debug_world_log(msg) if (GLOB.Debug2) log_world("DEBUG: [msg]") /// Adds a generic box around whatever message you're sending in chat. Really makes things stand out. -#define examine_block(str) ("
" + str + "
") -/// Makes a fieldset with a name in the middle top part. Can apply additional classes -#define fieldset_block(title, content, classes) ("
" + title + "
" + content + "
") +#define boxed_message(str) ("
" + str + "
") +/// Adds a box around whatever message you're sending in chat. Can apply color and/or additional classes. Available colors: red, green, blue, purple. Use it like red_box +#define custom_boxed_message(classes, str) ("
" + str + "
") +/// Makes a fieldset with a neaty styled name. Can apply additional classes. +#define fieldset_block(title, content, classes) ("
" + title + "" + content + "
") /// Makes a horizontal line with text in the middle #define separator_hr(str) ("
" + str + "
") /// Emboldens runechat messages diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm index 2c285a348fac2..9b80dbfc169c4 100644 --- a/code/__HELPERS/game.dm +++ b/code/__HELPERS/game.dm @@ -349,4 +349,4 @@ message = html_encode(message) else message = copytext(message, 2) - to_chat(target, span_purple(examine_block("[source]: [message]"))) + to_chat(target, custom_boxed_message("purple_box", span_purple("[source]: [message]"))) diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm index 7cab2074c5485..6b6c6bc9050ab 100644 --- a/code/_onclick/hud/alert.dm +++ b/code/_onclick/hud/alert.dm @@ -1136,7 +1136,7 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." return FALSE var/list/modifiers = params2list(params) if(LAZYACCESS(modifiers, SHIFT_CLICK)) // screen objects don't do the normal Click() stuff so we'll cheat - to_chat(usr, examine_block(jointext(examine(usr), "\n"))) + to_chat(usr, boxed_message(jointext(examine(usr), "\n"))) return FALSE var/datum/our_master = master_ref?.resolve() if(our_master && click_master) diff --git a/code/controllers/subsystem/map_vote.dm b/code/controllers/subsystem/map_vote.dm index ced1e65e3a215..f57d73d773e63 100644 --- a/code/controllers/subsystem/map_vote.dm +++ b/code/controllers/subsystem/map_vote.dm @@ -53,7 +53,7 @@ SUBSYSTEM_DEF(map_vote) last_message_at = world.time var/list/messages = args.Copy() - to_chat(world, span_purple(examine_block("Map Vote\n
[messages.Join("\n")]"))) + to_chat(world, span_purple(boxed_message("Map Vote\n
[messages.Join("\n")]"))) /datum/controller/subsystem/map_vote/proc/finalize_map_vote(datum/vote/map_vote/map_vote) if(already_voted) @@ -170,4 +170,4 @@ SUBSYSTEM_DEF(map_vote) for(var/map_id in map_vote_cache) var/datum/map_config/map = config.maplist[map_id] data += "[map.map_name] - [map_vote_cache[map_id]]" - tally_printout = examine_block("Current Tallies\n
[data.Join("\n")]") + tally_printout = boxed_message("Current Tallies\n
[data.Join("\n")]") diff --git a/code/controllers/subsystem/polling.dm b/code/controllers/subsystem/polling.dm index c9c7d3ea31167..5cb81498c3df9 100644 --- a/code/controllers/subsystem/polling.dm +++ b/code/controllers/subsystem/polling.dm @@ -165,7 +165,7 @@ SUBSYSTEM_DEF(polling) else surrounding_image = image(chat_text_border_icon) surrounding_icon = icon2html(surrounding_image, candidate_mob, extra_classes = "bigicon") - var/final_message = examine_block("[surrounding_icon] [span_ooc(question)] [surrounding_icon]\n[act_jump] [act_signup] [act_never]") + var/final_message = boxed_message("[surrounding_icon] [span_ooc(question)] [surrounding_icon]\n[act_jump] [act_signup] [act_never]") to_chat(candidate_mob, final_message) // Start processing it so it updates visually the timer diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm index d0e642bd3aa2d..d9d796782c2b2 100644 --- a/code/controllers/subsystem/vote.dm +++ b/code/controllers/subsystem/vote.dm @@ -119,7 +119,7 @@ SUBSYSTEM_DEF(vote) ) log_vote("vote finalized", vote_log_data) if(to_display) - to_chat(world, span_infoplain(vote_font("\n[to_display]"))) + to_chat(world, span_infoplain(vote_font("[to_display]"))) // Finally, doing any effects on vote completion current_vote.finalize_vote(final_winner) @@ -230,9 +230,9 @@ SUBSYSTEM_DEF(vote) var/to_display = current_vote.initiate_vote(vote_initiator_name, duration) log_vote(to_display) - to_chat(world, span_infoplain(vote_font("\n[span_bold(to_display)]\n\ + to_chat(world, custom_boxed_message("purple_box center", span_infoplain(vote_font("[span_bold(to_display)]
\ Type vote or click
here to place your votes.\n\ - You have [DisplayTimeText(duration)] to vote."))) + You have [DisplayTimeText(duration)] to vote.")))) // And now that it's going, give everyone a voter action for(var/client/new_voter as anything in GLOB.clients) diff --git a/code/datums/ai_laws/ai_laws.dm b/code/datums/ai_laws/ai_laws.dm index a0d1d629fc8d3..a25f7e694a9ad 100644 --- a/code/datums/ai_laws/ai_laws.dm +++ b/code/datums/ai_laws/ai_laws.dm @@ -442,7 +442,7 @@ GLOBAL_VAR(round_default_lawset) /datum/ai_laws/proc/show_laws(mob/to_who) var/list/printable_laws = get_law_list(include_zeroth = TRUE) - to_chat(to_who, examine_block(jointext(printable_laws, "\n"))) + to_chat(to_who, boxed_message(jointext(printable_laws, "\n"))) /datum/ai_laws/proc/associate(mob/living/silicon/M) if(owner) diff --git a/code/datums/components/trader/trader.dm b/code/datums/components/trader/trader.dm index d623a9943b893..3b2675785aec3 100644 --- a/code/datums/components/trader/trader.dm +++ b/code/datums/components/trader/trader.dm @@ -394,7 +394,7 @@ Can accept both a type path, and an instance of a datum. Type path has priority. else buy_info += span_notice("• [initial(thing.name)] for [product_info[TRADER_PRODUCT_INFO_PRICE]] [trader_data.currency_name][product_info[TRADER_PRODUCT_INFO_PRICE_MOD_DESCRIPTION]]; willing to buy [span_green("[tern_op_result]")]") - to_chat(customer, examine_block(buy_info.Join("\n"))) + to_chat(customer, boxed_message(buy_info.Join("\n"))) ///Displays to the customer what the trader is selling and how much is in stock /datum/component/trader/proc/trader_sells_what(mob/customer) @@ -413,7 +413,7 @@ Can accept both a type path, and an instance of a datum. Type path has priority. sell_info += span_notice("• [span_red("(OUT OF STOCK)")] [initial(thing.name)] for [product_info[TRADER_PRODUCT_INFO_PRICE]] [trader_data.currency_name]; [span_red("[tern_op_result]")] left in stock") else sell_info += span_notice("• [initial(thing.name)] for [product_info[TRADER_PRODUCT_INFO_PRICE]] [trader_data.currency_name]; [span_green("[tern_op_result]")] left in stock") - to_chat(customer, examine_block(sell_info.Join("\n"))) + to_chat(customer, boxed_message(sell_info.Join("\n"))) ///Sets quantity of all products to initial(quanity); this proc is currently called during initialize /datum/component/trader/proc/restock_products() diff --git a/code/datums/elements/slapcrafting.dm b/code/datums/elements/slapcrafting.dm index 4b58bddd2a0f4..925b2fd575597 100644 --- a/code/datums/elements/slapcrafting.dm +++ b/code/datums/elements/slapcrafting.dm @@ -180,7 +180,7 @@ // If we did find ingredients then add them onto the list. if(length(string_ingredient_list)) to_chat(user, span_boldnotice("Extra Ingredients:")) - to_chat(user, examine_block(span_notice(string_ingredient_list))) + to_chat(user, boxed_message(span_notice(string_ingredient_list))) var/list/tool_list = "" @@ -194,7 +194,7 @@ if(length(tool_list)) to_chat(user, span_boldnotice("Required Tools:")) - to_chat(user, examine_block(span_notice(tool_list))) + to_chat(user, boxed_message(span_notice(tool_list))) qdel(cur_recipe) diff --git a/code/datums/elements/weapon_description.dm b/code/datums/elements/weapon_description.dm index 64d044fb74a3d..110a490b5ca12 100644 --- a/code/datums/elements/weapon_description.dm +++ b/code/datums/elements/weapon_description.dm @@ -57,7 +57,7 @@ SIGNAL_HANDLER if(href_list["examine"]) - to_chat(user, span_notice(examine_block("[build_label_text(source)]"))) + to_chat(user, span_notice(boxed_message("[build_label_text(source)]"))) /** * diff --git a/code/datums/mind/skills.dm b/code/datums/mind/skills.dm index 474291d5ae0d4..80a78c3d950f2 100644 --- a/code/datums/mind/skills.dm +++ b/code/datums/mind/skills.dm @@ -75,4 +75,4 @@ var/datum/skill/the_skill = i msg += "[initial(the_skill.name)] - [get_skill_level_name(the_skill)]\n" msg += "" - to_chat(user, examine_block(msg)) + to_chat(user, boxed_message(msg)) diff --git a/code/datums/mood.dm b/code/datums/mood.dm index d3f2ac70861e0..e10be827eb279 100644 --- a/code/datums/mood.dm +++ b/code/datums/mood.dm @@ -389,7 +389,7 @@ msg += span_boldnicegreen(event.description + "\n") else msg += "[span_grey("I don't have much of a reaction to anything right now.")]\n" - to_chat(user, examine_block(msg)) + to_chat(user, boxed_message(msg)) /// Updates the mob's moodies, if the area provides a mood bonus /datum/mood/proc/check_area_mood(datum/source, area/new_area) diff --git a/code/datums/votes/_vote_datum.dm b/code/datums/votes/_vote_datum.dm index 76833f73ff5b0..957160dea7d3e 100644 --- a/code/datums/votes/_vote_datum.dm +++ b/code/datums/votes/_vote_datum.dm @@ -170,13 +170,14 @@ * Return a formatted string of text to be displayed to everyone. */ /datum/vote/proc/get_result_text(list/all_winners, real_winner, list/non_voters) + var/title_text = "" var/returned_text = "" if(override_question) - returned_text += span_bold(override_question) + title_text += span_bold(override_question) else - returned_text += span_bold("[capitalize(name)] Vote") + title_text += span_bold("[capitalize(name)] Vote") - returned_text += "\nWinner Selection: " + returned_text += "Winner Selection: " switch(winner_method) if(VOTE_WINNER_METHOD_NONE) returned_text += "None" @@ -215,7 +216,7 @@ returned_text += "\n" returned_text += get_winner_text(all_winners, real_winner, non_voters) - return returned_text + return fieldset_block(title_text, returned_text, "boxed_message purple_box") /** * Gets the text that displays the winning options within the result text. diff --git a/code/game/objects/items/AI_modules/_AI_modules.dm b/code/game/objects/items/AI_modules/_AI_modules.dm index a7f8dbb1798c0..905443569b2eb 100644 --- a/code/game/objects/items/AI_modules/_AI_modules.dm +++ b/code/game/objects/items/AI_modules/_AI_modules.dm @@ -36,7 +36,7 @@ /obj/item/ai_module/attack_self(mob/user as mob) ..() - to_chat(user, examine_block(display_laws())) + to_chat(user, boxed_message(display_laws())) /// Returns a text display of the laws for the module. /obj/item/ai_module/proc/display_laws() diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm index 94b7e14fe80fb..943896f29f4be 100644 --- a/code/game/objects/items/devices/flashlight.dm +++ b/code/game/objects/items/devices/flashlight.dm @@ -267,7 +267,7 @@ if(length(render_list)) //display our packaged information in an examine block for easy reading - to_chat(user, examine_block(jointext(render_list, "")), type = MESSAGE_TYPE_INFO) + to_chat(user, boxed_message(jointext(render_list, "")), type = MESSAGE_TYPE_INFO) return ITEM_INTERACT_SUCCESS return ITEM_INTERACT_BLOCKING diff --git a/code/game/objects/items/devices/scanners/gas_analyzer.dm b/code/game/objects/items/devices/scanners/gas_analyzer.dm index 112833877c4d3..8fc6ff4426bba 100644 --- a/code/game/objects/items/devices/scanners/gas_analyzer.dm +++ b/code/game/objects/items/devices/scanners/gas_analyzer.dm @@ -220,7 +220,7 @@ message += span_notice("Volume: [volume] L") // don't want to change the order volume appears in, suck it // we let the join apply newlines so we do need handholding - to_chat(user, examine_block(jointext(message, "\n")), type = MESSAGE_TYPE_INFO) + to_chat(user, boxed_message(jointext(message, "\n")), type = MESSAGE_TYPE_INFO) return TRUE /obj/item/analyzer/ranged diff --git a/code/game/objects/items/devices/scanners/health_analyzer.dm b/code/game/objects/items/devices/scanners/health_analyzer.dm index 3535bef007407..83d7df3a75a05 100644 --- a/code/game/objects/items/devices/scanners/health_analyzer.dm +++ b/code/game/objects/items/devices/scanners/health_analyzer.dm @@ -81,7 +81,7 @@ floor_text += "Body temperature: [scan_turf?.return_air()?.return_temperature() || "???"]
" if(user.can_read(src) && !user.is_blind()) - to_chat(user, examine_block(floor_text)) + to_chat(user, custom_boxed_message("blue_box", floor_text)) last_scan_text = floor_text return @@ -410,7 +410,7 @@ . = jointext(render_list, "") if(tochat) - to_chat(user, examine_block(.), trailing_newline = FALSE, type = MESSAGE_TYPE_INFO) + to_chat(user, custom_boxed_message("blue_box", .), trailing_newline = FALSE, type = MESSAGE_TYPE_INFO) return . /obj/item/healthanalyzer/click_ctrl_shift(mob/user) @@ -507,7 +507,7 @@ render_list += "[allergies]
" // we handled the last
so we don't need handholding - to_chat(user, examine_block(jointext(render_list, "")), trailing_newline = FALSE, type = MESSAGE_TYPE_INFO) + to_chat(user, custom_boxed_message("blue_box", jointext(render_list, "")), trailing_newline = FALSE, type = MESSAGE_TYPE_INFO) /obj/item/healthanalyzer/click_alt(mob/user) if(mode == SCANNER_NO_MODE) @@ -558,7 +558,7 @@ simple_scanner.show_emotion(AID_EMOTION_HAPPY) to_chat(user, "No wounds detected in subject.") else - to_chat(user, examine_block(jointext(render_list, "")), type = MESSAGE_TYPE_INFO) + to_chat(user, custom_boxed_message("blue_box", jointext(render_list, "")), type = MESSAGE_TYPE_INFO) if(simple_scan) var/obj/item/healthanalyzer/simple/simple_scanner = scanner simple_scanner.show_emotion(AID_EMOTION_WARN) diff --git a/code/game/objects/items/devices/scanners/slime_scanner.dm b/code/game/objects/items/devices/scanners/slime_scanner.dm index 79050f0a78c67..c355da8ba7d4a 100644 --- a/code/game/objects/items/devices/scanners/slime_scanner.dm +++ b/code/game/objects/items/devices/scanners/slime_scanner.dm @@ -57,4 +57,4 @@ to_render += "\n[span_notice("Core mutation in progress: [scanned_slime.crossbreed_modification]")]\ \n[span_notice("Progress in core mutation: [scanned_slime.applied_crossbreed_amount] / [SLIME_EXTRACT_CROSSING_REQUIRED]")]" - to_chat(user, examine_block(jointext(to_render,""))) + to_chat(user, boxed_message(jointext(to_render,""))) diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index f517a7a7a51cb..0ea5e67fbf71f 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -847,7 +847,7 @@ exportable_text += "[special_role_description]
" exportable_text += ADMIN_FULLMONTY_NONAME(subject) - to_chat(src.owner, examine_block(exportable_text), confidential = TRUE) + to_chat(src.owner, boxed_message(exportable_text), confidential = TRUE) else if(href_list["addjobslot"]) if(!check_rights(R_ADMIN)) diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm index a389980f533fd..4ff36ec2d0130 100644 --- a/code/modules/admin/verbs/adminhelp.dm +++ b/code/modules/admin/verbs/adminhelp.dm @@ -403,7 +403,12 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) msg = sanitize(copytext_char(msg, 1, MAX_MESSAGE_LEN)) var/ref_src = "[REF(src)]" //Message to be sent to all admins - var/admin_msg = span_adminnotice(span_adminhelp("Ticket [TicketHref("#[id]", ref_src)]: [LinkedReplyName(ref_src)] [FullMonty(ref_src)]: [span_linkify(keywords_lookup(msg))]")) + var/admin_msg = fieldset_block( + span_adminhelp("Ticket [TicketHref("#[id]", ref_src)]"), + "[LinkedReplyName(ref_src)]\n\n\ + [span_linkify(keywords_lookup(msg))]\n\n\ + [FullMonty(ref_src)]", + "boxed_message red_box") AddInteraction("[LinkedReplyName(ref_src)]: [msg]", player_message = "[LinkedReplyName(ref_src)]: [msg]") log_admin_private("Ticket #[id]: [key_name(initiator)]: [msg]") diff --git a/code/modules/admin/verbs/adminpm.dm b/code/modules/admin/verbs/adminpm.dm index abbcbb62ab648..4b668082d4ba1 100644 --- a/code/modules/admin/verbs/adminpm.dm +++ b/code/modules/admin/verbs/adminpm.dm @@ -387,20 +387,11 @@ ADMIN_VERB(cmd_admin_pm_panel, R_NONE, "Admin PM", "Show a list of clients to PM recipient_ticket_id = recipient_ticket?.id SSblackbox.LogAhelp(recipient_ticket_id, "Ticket Opened", send_message, recipient.ckey, src.ckey) - to_chat(recipient, - type = MESSAGE_TYPE_ADMINPM, - html = "-- Administrator private message --", - confidential = TRUE) - recipient.receive_ahelp( link_to_us, span_linkify(send_message), ) - to_chat(recipient, - type = MESSAGE_TYPE_ADMINPM, - html = span_adminsay("Click on the administrator's name to reply."), - confidential = TRUE) to_chat(src, type = MESSAGE_TYPE_ADMINPM, html = span_notice("Admin PM to-[their_name_with_link]: [span_linkify(send_message)]"), @@ -707,21 +698,11 @@ ADMIN_VERB(cmd_admin_pm_panel, R_NONE, "Admin PM", "Show a list of clients to PM message_admins("External message from [sender] to [recipient_name_linked] : [message]") log_admin_private("External PM: [sender] -> [recipient_name] : [message]") - to_chat(recipient, - type = MESSAGE_TYPE_ADMINPM, - html = "-- Administrator private message --", - confidential = TRUE) - recipient.receive_ahelp( "[adminname]", message, ) - to_chat(recipient, - type = MESSAGE_TYPE_ADMINPM, - html = span_adminsay("Click on the administrator's name to reply."), - confidential = TRUE) - admin_ticket_log(recipient, "PM From [tgs_tagged]: [message]", log_in_blackbox = FALSE) window_flash(recipient, ignorepref = TRUE) @@ -766,8 +747,13 @@ ADMIN_VERB(cmd_admin_pm_panel, R_NONE, "Admin PM", "Show a list of clients to PM to_chat( src, type = MESSAGE_TYPE_ADMINPM, - html = "Admin PM from-[reply_to]: [message]", - confidential = TRUE, + html = fieldset_block( + span_adminhelp("Administrator private message"), + "Admin PM from-[reply_to]\n\n\ + [message]\n\n\ + Click on the administrator's name to reply.", + "boxed_message red_box"), + confidential = TRUE ) current_ticket?.player_replied = FALSE diff --git a/code/modules/admin/verbs/mapping.dm b/code/modules/admin/verbs/mapping.dm index 8504cd5262cab..277b7ad36d541 100644 --- a/code/modules/admin/verbs/mapping.dm +++ b/code/modules/admin/verbs/mapping.dm @@ -224,7 +224,7 @@ ADMIN_VERB(create_mapping_job_icons, R_DEBUG, "Generate job landmarks icons", "G ADMIN_VERB_VISIBILITY(debug_z_levels, ADMIN_VERB_VISIBLITY_FLAG_MAPPING_DEBUG) ADMIN_VERB(debug_z_levels, R_DEBUG, "Debug Z-Levels", "Displays a list of all z-levels and their linkages.", ADMIN_CATEGORY_MAPPING) - to_chat(user, examine_block(gather_z_level_information(append_grid = TRUE)), confidential = TRUE) + to_chat(user, boxed_message(gather_z_level_information(append_grid = TRUE)), confidential = TRUE) /// Returns all necessary z-level information. Argument `append_grid` allows the user to see a table showing all of the z-level linkages, which is only visible and useful in-game. /proc/gather_z_level_information(append_grid = FALSE) diff --git a/code/modules/buildmode/submodes/advanced.dm b/code/modules/buildmode/submodes/advanced.dm index f4ebb204d6ee0..bb08ddaf5298e 100644 --- a/code/modules/buildmode/submodes/advanced.dm +++ b/code/modules/buildmode/submodes/advanced.dm @@ -3,7 +3,7 @@ var/atom/objholder = null /datum/buildmode_mode/advanced/show_help(client/builder) - to_chat(builder, span_purple(examine_block( + to_chat(builder, span_purple(boxed_message( "[span_bold("Set object type")] -> Right Mouse Button on buildmode button\n\ [span_bold("Copy object type")] -> Left Mouse Button + Alt on turf/obj\n\ [span_bold("Place objects")] -> Left Mouse Button on turf/obj\n\ diff --git a/code/modules/buildmode/submodes/area_edit.dm b/code/modules/buildmode/submodes/area_edit.dm index 0d9ef8dd480bd..e91ea26be7746 100644 --- a/code/modules/buildmode/submodes/area_edit.dm +++ b/code/modules/buildmode/submodes/area_edit.dm @@ -9,7 +9,7 @@ ..() /datum/buildmode_mode/area_edit/show_help(client/builder) - to_chat(builder, span_purple(examine_block( + to_chat(builder, span_purple(boxed_message( "[span_bold("Select corner")] -> Left Mouse Button on obj/turf/mob\n\ [span_bold("Paint area")] -> Left Mouse Button + Alt on turf/obj/mob\n\ [span_bold("Select area to paint")] -> Right Mouse Button on obj/turf/mob\n\ diff --git a/code/modules/buildmode/submodes/basic.dm b/code/modules/buildmode/submodes/basic.dm index b0108a3a37f32..e68ccda926de7 100644 --- a/code/modules/buildmode/submodes/basic.dm +++ b/code/modules/buildmode/submodes/basic.dm @@ -2,7 +2,7 @@ key = "basic" /datum/buildmode_mode/basic/show_help(client/builder) - to_chat(builder, span_purple(examine_block( + to_chat(builder, span_purple(boxed_message( "[span_bold("Construct / Upgrade")] -> Left Mouse Button\n\ [span_bold("Deconstruct / Delete / Downgrade")] -> Right Mouse Button\n\ [span_bold("R-Window")] -> Left Mouse Button + Ctrl\n\ diff --git a/code/modules/buildmode/submodes/boom.dm b/code/modules/buildmode/submodes/boom.dm index 727d001f5cab4..e86d377d5d84c 100644 --- a/code/modules/buildmode/submodes/boom.dm +++ b/code/modules/buildmode/submodes/boom.dm @@ -16,7 +16,7 @@ ) /datum/buildmode_mode/boom/show_help(client/builder) - to_chat(builder, span_purple(examine_block( + to_chat(builder, span_purple(boxed_message( "[span_bold("Set explosion destructiveness")] -> Right Mouse Button on buildmode button\n\ [span_bold("Kaboom")] -> Mouse Button on obj\n\n\ [span_warning("NOTE:")] Using the \"Config/Launch Supplypod\" verb allows you to do this in an IC way (i.e., making a cruise missile come down from the sky and explode wherever you click!)")) diff --git a/code/modules/buildmode/submodes/copy.dm b/code/modules/buildmode/submodes/copy.dm index 88a1160a8a8fb..19dd34d3b4b52 100644 --- a/code/modules/buildmode/submodes/copy.dm +++ b/code/modules/buildmode/submodes/copy.dm @@ -7,7 +7,7 @@ return ..() /datum/buildmode_mode/copy/show_help(client/builder) - to_chat(builder, span_purple(examine_block( + to_chat(builder, span_purple(boxed_message( "[span_bold("Spawn a copy of selected target")] -> Left Mouse Button on obj/turf/mob\n\ [span_bold("Select target to copy")] -> Right Mouse Button on obj/mob")) ) diff --git a/code/modules/buildmode/submodes/delete.dm b/code/modules/buildmode/submodes/delete.dm index 7bb1a1472c0c5..1980375a33cd4 100644 --- a/code/modules/buildmode/submodes/delete.dm +++ b/code/modules/buildmode/submodes/delete.dm @@ -2,7 +2,7 @@ key = "delete" /datum/buildmode_mode/delete/show_help(client/builder) - to_chat(builder, span_purple(examine_block( + to_chat(builder, span_purple(boxed_message( "[span_bold("Delete an object")] -> Left Mouse Button on obj/turf/mob\n\ [span_bold("Delete all objects of a type")] -> Right Mouse Button on obj/turf/mob")) ) diff --git a/code/modules/buildmode/submodes/fill.dm b/code/modules/buildmode/submodes/fill.dm index e2bd3a5c16ad5..1646f6b3a0da5 100644 --- a/code/modules/buildmode/submodes/fill.dm +++ b/code/modules/buildmode/submodes/fill.dm @@ -7,7 +7,7 @@ var/atom/objholder = null /datum/buildmode_mode/fill/show_help(client/builder) - to_chat(builder, span_purple(examine_block( + to_chat(builder, span_purple(boxed_message( "[span_bold("Select corner")] -> Left Mouse Button on turf/obj/mob\n\ [span_bold("Delete region")] -> Left Mouse Button + Alt on turf/obj/mob\n\ [span_bold("Select object type")] -> Right Mouse Button on buildmode button")) diff --git a/code/modules/buildmode/submodes/map_export.dm b/code/modules/buildmode/submodes/map_export.dm index 19c0d57b57f73..9d59263aa47e1 100644 --- a/code/modules/buildmode/submodes/map_export.dm +++ b/code/modules/buildmode/submodes/map_export.dm @@ -22,7 +22,7 @@ to_chat(builder, span_notice("[what_to_change] is now [save_flag & options[what_to_change] ? "ENABLED" : "DISABLED"].")) /datum/buildmode_mode/map_export/show_help(client/builder) - to_chat(builder, span_purple(examine_block( + to_chat(builder, span_purple(boxed_message( "[span_bold("Select corner")] -> Left Mouse Button on obj/turf/mob\n\ [span_bold("Set export options")] -> Right Mouse Button on buildmode button")) ) diff --git a/code/modules/buildmode/submodes/mapgen.dm b/code/modules/buildmode/submodes/mapgen.dm index 102660425c874..d55898724490f 100644 --- a/code/modules/buildmode/submodes/mapgen.dm +++ b/code/modules/buildmode/submodes/mapgen.dm @@ -5,7 +5,7 @@ var/generator_path /datum/buildmode_mode/mapgen/show_help(client/builder) - to_chat(builder, span_purple(examine_block( + to_chat(builder, span_purple(boxed_message( "[span_bold("Select corner")] -> Left Mouse Button on turf/obj/mob\n\ [span_bold("Select generator")] -> Right Mouse Button on buildmode button")) ) diff --git a/code/modules/buildmode/submodes/outfit.dm b/code/modules/buildmode/submodes/outfit.dm index c3d507bf1e6c7..30b0f33cec165 100644 --- a/code/modules/buildmode/submodes/outfit.dm +++ b/code/modules/buildmode/submodes/outfit.dm @@ -7,7 +7,7 @@ return ..() /datum/buildmode_mode/outfit/show_help(client/builder) - to_chat(builder, span_purple(examine_block( + to_chat(builder, span_purple(boxed_message( "[span_bold("Select outfit to equip")] -> Right Mouse Button on buildmode button\n\ [span_bold("Equip the selected outfit")] -> Left Mouse Button on mob/living/carbon/human\n\ [span_bold("Strip and delete current outfit")] -> Right Mouse Button on mob/living/carbon/human")) diff --git a/code/modules/buildmode/submodes/proccall.dm b/code/modules/buildmode/submodes/proccall.dm index 3df1b8105380c..612a44cde4ca7 100644 --- a/code/modules/buildmode/submodes/proccall.dm +++ b/code/modules/buildmode/submodes/proccall.dm @@ -6,7 +6,7 @@ var/list/proc_args = null /datum/buildmode_mode/proccall/show_help(client/builder) - to_chat(builder, span_purple(examine_block( + to_chat(builder, span_purple(boxed_message( "[span_bold("Choose procedure and arguments")] -> Right Mouse Button on buildmode button\n\ [span_bold("Apply procedure on object")] -> Left Mouse Button on machinery")) ) diff --git a/code/modules/buildmode/submodes/smite.dm b/code/modules/buildmode/submodes/smite.dm index 1ea5f415e35f3..7e382926c5969 100644 --- a/code/modules/buildmode/submodes/smite.dm +++ b/code/modules/buildmode/submodes/smite.dm @@ -7,7 +7,7 @@ return ..() /datum/buildmode_mode/smite/show_help(client/builder) - to_chat(builder, span_purple(examine_block( + to_chat(builder, span_purple(boxed_message( "[span_bold("Select smite to use")] -> Right Mouse Button on buildmode button\n\ [span_bold("Smite the mob")] -> Left Mouse Button on mob/living")) ) diff --git a/code/modules/buildmode/submodes/throwing.dm b/code/modules/buildmode/submodes/throwing.dm index 587bf456e2a0c..c1bc4c2184ef3 100644 --- a/code/modules/buildmode/submodes/throwing.dm +++ b/code/modules/buildmode/submodes/throwing.dm @@ -8,7 +8,7 @@ return ..() /datum/buildmode_mode/throwing/show_help(client/builder) - to_chat(builder, span_purple(examine_block( + to_chat(builder, span_purple(boxed_message( "[span_bold("Select")] -> Left Mouse Button on turf/obj/mob\n\ [span_bold("Throw")] -> Right Mouse Button on turf/obj/mob")) ) diff --git a/code/modules/buildmode/submodes/tweakcomps.dm b/code/modules/buildmode/submodes/tweakcomps.dm index 89f233f9687b9..b0e41589917d3 100644 --- a/code/modules/buildmode/submodes/tweakcomps.dm +++ b/code/modules/buildmode/submodes/tweakcomps.dm @@ -4,7 +4,7 @@ var/rating = null /datum/buildmode_mode/tweakcomps/show_help(client/builder) - to_chat(builder, span_purple(examine_block( + to_chat(builder, span_purple(boxed_message( "[span_bold("Choose the rating of the components")] -> Right Mouse Button on buildmode button\n\ [span_bold("Sets the chosen rating of the components on the machinery")] -> Left Mouse Button on machinery")) ) diff --git a/code/modules/buildmode/submodes/variable_edit.dm b/code/modules/buildmode/submodes/variable_edit.dm index f132cd196ce71..4162af69faa43 100644 --- a/code/modules/buildmode/submodes/variable_edit.dm +++ b/code/modules/buildmode/submodes/variable_edit.dm @@ -10,7 +10,7 @@ return ..() /datum/buildmode_mode/varedit/show_help(client/builder) - to_chat(builder, span_purple(examine_block( + to_chat(builder, span_purple(boxed_message( "[span_bold("Select var(type) & value")] -> Right Mouse Button on buildmode button\n\ [span_bold("Set var(type) & value")] -> Left Mouse Button on turf/obj/mob\n\ [span_bold("Reset var's value")] -> Right Mouse Button on turf/obj/mob")) diff --git a/code/modules/client/verbs/who.dm b/code/modules/client/verbs/who.dm index bd023ede6308e..c9e500e7dbc78 100644 --- a/code/modules/client/verbs/who.dm +++ b/code/modules/client/verbs/who.dm @@ -85,7 +85,7 @@ lines += span_bold(header) lines += payload_string - var/finalized_string = examine_block(jointext(lines, "\n")) + var/finalized_string = boxed_message(jointext(lines, "\n")) to_chat(src, finalized_string) /// Proc that generates the applicable string to dispatch to the client for adminwho. diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm index 9f68581e3ab6f..12600338f97d1 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -456,7 +456,7 @@ readout += "No armor or durability information available." var/formatted_readout = span_notice("PROTECTION CLASSES
[jointext(readout, "\n")]") - to_chat(usr, examine_block(formatted_readout)) + to_chat(usr, boxed_message(formatted_readout)) /** * Rounds armor_value down to the nearest 10, divides it by 10 and then converts it to Roman numerals. diff --git a/code/modules/clothing/neck/_neck.dm b/code/modules/clothing/neck/_neck.dm index 6d97c116ab11a..7fe2074175d31 100644 --- a/code/modules/clothing/neck/_neck.dm +++ b/code/modules/clothing/neck/_neck.dm @@ -336,7 +336,7 @@ render_list += "[target.p_Their()] pulse is [pulse_pressure] and [heart_strength].\n" //display our packaged information in an examine block for easy reading - to_chat(user, examine_block(jointext(render_list, "")), type = MESSAGE_TYPE_INFO) + to_chat(user, boxed_message(jointext(render_list, "")), type = MESSAGE_TYPE_INFO) /////////// //SCARVES// diff --git a/code/modules/fishing/fishing_rod.dm b/code/modules/fishing/fishing_rod.dm index 5fcfdd07ff350..8190b21c84c4d 100644 --- a/code/modules/fishing/fishing_rod.dm +++ b/code/modules/fishing/fishing_rod.dm @@ -150,7 +150,7 @@ block += get_stat_info(get_percent, gravity_mult, "The lure will sink", "faster", "slower", span_info = TRUE) list_clear_nulls(block) - . += examine_block(block.Join("\n")) + . += boxed_message(block.Join("\n")) if(get_percent && (material_flags & MATERIAL_EFFECTS) && length(custom_materials)) block = list() @@ -159,7 +159,7 @@ if(material.fish_weight_modifier != 1) var/heavier = material.fish_weight_modifier > 1 ? "heavier" : "lighter" block += span_info("Fish made of the same material as this rod tend to be [abs(material.fish_weight_modifier - 1) * 100]% [heavier].") - . += examine_block(block.Join("\n")) + . += boxed_message(block.Join("\n")) block = list() if(HAS_TRAIT(src, TRAIT_ROD_ATTRACT_SHINY_LOVERS)) @@ -171,7 +171,7 @@ if(HAS_TRAIT(src, TRAIT_ROD_LAVA_USABLE)) block += span_info("This fishing rod can be used to fish on lava.") if(length(block)) - . += examine_block(block.Join("\n")) + . += boxed_message(block.Join("\n")) ///Used in examine_more to reduce all the copypasta when getting more information about the various stats of the fishing rod. /obj/item/fishing_rod/proc/get_stat_info(get_percent, value, prefix, easier, harder, suffix = "with this fishing rod", span_info = FALSE, less_is_better = FALSE, offset = 1) diff --git a/code/modules/hydroponics/hydroitemdefines.dm b/code/modules/hydroponics/hydroitemdefines.dm index 0ffc337204244..216c913d79210 100644 --- a/code/modules/hydroponics/hydroitemdefines.dm +++ b/code/modules/hydroponics/hydroitemdefines.dm @@ -75,22 +75,22 @@ /obj/item/plant_analyzer/proc/do_plant_stats_scan(atom/scan_target, mob/user) if(istype(scan_target, /obj/machinery/hydroponics)) playsound(src, SFX_INDUSTRIAL_SCAN, 20, TRUE, -2, TRUE, FALSE) - to_chat(user, examine_block(scan_tray_stats(scan_target))) + to_chat(user, boxed_message(scan_tray_stats(scan_target))) return TRUE if(istype(scan_target, /obj/structure/glowshroom)) playsound(src, SFX_INDUSTRIAL_SCAN, 20, TRUE, -2, TRUE, FALSE) var/obj/structure/glowshroom/shroom_plant = scan_target - to_chat(user, examine_block(scan_plant_stats(shroom_plant.myseed))) + to_chat(user, boxed_message(scan_plant_stats(shroom_plant.myseed))) return TRUE if(istype(scan_target, /obj/item/graft)) playsound(src, SFX_INDUSTRIAL_SCAN, 20, TRUE, -2, TRUE, FALSE) - to_chat(user, examine_block(get_graft_text(scan_target))) + to_chat(user, boxed_message(get_graft_text(scan_target))) return TRUE if(isitem(scan_target)) playsound(src, SFX_INDUSTRIAL_SCAN, 20, TRUE, -2, TRUE, FALSE) var/obj/item/scanned_object = scan_target if(scanned_object.get_plant_seed() || istype(scanned_object, /obj/item/seeds)) - to_chat(user, examine_block(scan_plant_stats(scanned_object))) + to_chat(user, boxed_message(scan_plant_stats(scanned_object))) return TRUE if(isliving(scan_target)) playsound(src, SFX_INDUSTRIAL_SCAN, 20, TRUE, -2, TRUE, FALSE) @@ -112,19 +112,19 @@ */ /obj/item/plant_analyzer/proc/do_plant_chem_scan(atom/scan_target, mob/user) if(istype(scan_target, /obj/machinery/hydroponics)) - to_chat(user, examine_block(scan_tray_chems(scan_target))) + to_chat(user, boxed_message(scan_tray_chems(scan_target))) return TRUE if(istype(scan_target, /obj/structure/glowshroom)) var/obj/structure/glowshroom/shroom_plant = scan_target - to_chat(user, examine_block(scan_plant_chems(shroom_plant.myseed))) + to_chat(user, boxed_message(scan_plant_chems(shroom_plant.myseed))) return TRUE if(istype(scan_target, /obj/item/graft)) - to_chat(user, examine_block(get_graft_text(scan_target))) + to_chat(user, boxed_message(get_graft_text(scan_target))) return TRUE if(isitem(scan_target)) var/obj/item/scanned_object = scan_target if(scanned_object.get_plant_seed() || istype(scanned_object, /obj/item/seeds)) - to_chat(user, examine_block(scan_plant_chems(scanned_object))) + to_chat(user, boxed_message(scan_plant_chems(scanned_object))) return TRUE if(isliving(scan_target)) var/mob/living/L = scan_target diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm index 0646d1d1df350..0f229181b3879 100644 --- a/code/modules/jobs/job_types/_job.dm +++ b/code/modules/jobs/job_types/_job.dm @@ -299,7 +299,7 @@ /// Gets the message that shows up when spawning as this job /datum/job/proc/get_spawn_message() SHOULD_NOT_OVERRIDE(TRUE) - return examine_block(span_infoplain(jointext(get_spawn_message_information(), "\n• "))) + return boxed_message(span_infoplain(jointext(get_spawn_message_information(), "\n• "))) /// Returns a list of strings that correspond to chat messages sent to this mob when they join the round. /datum/job/proc/get_spawn_message_information() diff --git a/code/modules/lootpanel/misc.dm b/code/modules/lootpanel/misc.dm index 6e8448b820528..4fc3af2da29bf 100644 --- a/code/modules/lootpanel/misc.dm +++ b/code/modules/lootpanel/misc.dm @@ -7,7 +7,7 @@ var/build = owner.byond_build var/version = owner.byond_version if(build < 515 || (build == 515 && version < 1635)) - to_chat(owner.mob, examine_block(span_info("\ + to_chat(owner.mob, boxed_message(span_info("\ Your version of Byond doesn't support fast image loading.\n\ Detected: [version].[build]\n\ Required version for this feature: 515.1635 or later.\n\ diff --git a/code/modules/mob/emote.dm b/code/modules/mob/emote.dm index 924498332ecc5..d6bfa48303a67 100644 --- a/code/modules/mob/emote.dm +++ b/code/modules/mob/emote.dm @@ -72,7 +72,7 @@ message += keys.Join(", ") message += "." message = message.Join("") - to_chat(user, examine_block(message)) + to_chat(user, boxed_message(message)) /datum/emote/flip key = "flip" diff --git a/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm b/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm index 615f314bf2e4b..5ac15722a5b35 100644 --- a/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm +++ b/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm @@ -117,7 +117,7 @@ var/static/cached_string = null if(isnull(cached_string)) - cached_string = examine_block(jointext(create_login_string(), "\n")) + cached_string = boxed_message(jointext(create_login_string(), "\n")) to_chat(src, cached_string, type = MESSAGE_TYPE_INFO) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index b63195b2f5988..72afd4e107ad7 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -141,7 +141,7 @@ id_examine += "
" // container id_examine += "" // text - to_chat(viewer, examine_block(span_info(id_examine))) + to_chat(viewer, boxed_message(span_info(id_examine))) ///////HUDs/////// if(href_list["hud"]) @@ -318,7 +318,7 @@ sec_record_message += "\nCrime: [crime.name]" sec_record_message += "\nDetails: [crime.details]" sec_record_message += "\nAdded by [crime.author] at [crime.time]" - to_chat(human_or_ghost_user, examine_block(sec_record_message)) + to_chat(human_or_ghost_user, boxed_message(sec_record_message)) return if(ishuman(human_or_ghost_user)) var/mob/living/carbon/human/human_user = human_or_ghost_user diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 73f63b8bfe110..95774e8b54ef8 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -676,7 +676,7 @@ if(quirks.len) combined_msg += span_notice("You have these quirks: [get_quirk_string(FALSE, CAT_QUIRK_ALL)].") - to_chat(src, examine_block(combined_msg.Join("\n"))) + to_chat(src, boxed_message(combined_msg.Join("\n"))) /mob/living/carbon/human/damage_clothes(damage_amount, damage_type = BRUTE, damage_flag = 0, def_zone) if(damage_type != BRUTE && damage_type != BURN) diff --git a/code/modules/mob/living/carbon/human/login.dm b/code/modules/mob/living/carbon/human/login.dm index 03795be168e43..9b3e23a378672 100644 --- a/code/modules/mob/living/carbon/human/login.dm +++ b/code/modules/mob/living/carbon/human/login.dm @@ -31,5 +31,5 @@ if(LAZYLEN(afk_thefts) >= AFK_THEFT_MAX_MESSAGES) print_msg += span_warning("There may have been more, but that's all you can remember...") - to_chat(src, examine_block(print_msg.Join("\n"))) + to_chat(src, boxed_message(print_msg.Join("\n"))) LAZYNULL(afk_thefts) diff --git a/code/modules/mob/living/silicon/laws.dm b/code/modules/mob/living/silicon/laws.dm index 1754a89aa5cb9..8a1861246acba 100644 --- a/code/modules/mob/living/silicon/laws.dm +++ b/code/modules/mob/living/silicon/laws.dm @@ -2,7 +2,7 @@ laws_sanity_check() var/list/law_box = list(span_bold("Obey these laws:")) law_box += laws.get_law_list(include_zeroth = TRUE) - to_chat(src, examine_block(jointext(law_box, "\n"))) + to_chat(src, boxed_message(jointext(law_box, "\n"))) /mob/living/silicon/proc/try_sync_laws() return diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 0ff10d1f1dcd3..acc14dc7a003d 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -532,7 +532,7 @@ var/list/result = examinify.examine_more(src) if(!length(result)) result += span_notice("You examine [examinify] closer, but find nothing of interest...") - result_combined = examine_block(jointext(result, "
")) + result_combined = boxed_message(jointext(result, "
")) else client.recent_examines[ref_to_atom] = world.time // set to when we last normal examine'd them @@ -543,7 +543,7 @@ var/list/result = examinify.examine(src) var/atom_title = examinify.examine_title(src, thats = TRUE) SEND_SIGNAL(src, COMSIG_MOB_EXAMINING, examinify, result) - result_combined = (atom_title ? fieldset_block("[span_slightly_larger(atom_title)].", jointext(result, "
"), "examine_block") : examine_block(jointext(result, "
"))) + result_combined = (atom_title ? fieldset_block("[atom_title]", jointext(result, "
"), "boxed_message") : boxed_message(jointext(result, "
"))) to_chat(src, span_infoplain(result_combined)) SEND_SIGNAL(src, COMSIG_MOB_EXAMINATE, examinify) diff --git a/code/modules/reagents/chemistry/items.dm b/code/modules/reagents/chemistry/items.dm index 1e712db9c23ef..f7501f36fc47c 100644 --- a/code/modules/reagents/chemistry/items.dm +++ b/code/modules/reagents/chemistry/items.dm @@ -132,7 +132,7 @@ out_message += "[round(reagent.volume, 0.01)]u of [reagent.name], Purity: [round(reagent.purity, 0.000001)*100]%, [(scanmode?"[(reagent.overdose_threshold?"Overdose: [reagent.overdose_threshold]u, ":"")]Base pH: [initial(reagent.ph)], Current pH: [reagent.ph].":"Current pH: [reagent.ph].")]\n" if(scanmode) out_message += "Analysis: [reagent.description]\n" - to_chat(user, examine_block(span_notice("[out_message.Join()]"))) + to_chat(user, boxed_message(span_notice("[out_message.Join()]"))) desc = "An electrode attached to a small circuit box that will display details of a solution. Can be toggled to provide a description of each of the reagents. The screen currently displays detected vol: [round(cont.volume, 0.01)] detected pH:[round(cont.reagents.ph, 0.1)]." return ITEM_INTERACT_SUCCESS diff --git a/code/modules/reagents/chemistry/machinery/reagentgrinder.dm b/code/modules/reagents/chemistry/machinery/reagentgrinder.dm index a009ab35dd1f3..a6938f5dcf5ca 100644 --- a/code/modules/reagents/chemistry/machinery/reagentgrinder.dm +++ b/code/modules/reagents/chemistry/machinery/reagentgrinder.dm @@ -387,7 +387,7 @@ if("mix") mix(5 SECONDS, user) if("examine") - to_chat(user, examine_block(jointext(examine(user), "\n"))) + to_chat(user, boxed_message(jointext(examine(user), "\n"))) /** * Checks if the radial menu can interact with this machine diff --git a/code/modules/research/xenobiology/xenobio_camera.dm b/code/modules/research/xenobiology/xenobio_camera.dm index 66cbd4b93de5b..d98f62596ea47 100644 --- a/code/modules/research/xenobiology/xenobio_camera.dm +++ b/code/modules/research/xenobiology/xenobio_camera.dm @@ -350,7 +350,7 @@ Due to keyboard shortcuts, the second one is not necessarily the remote eye's lo render_list += "• Alt-click a slime to feed it a potion." render_list += "• Ctrl-click or a dead monkey to recycle it, or the floor to place a new monkey." - to_chat(owner, examine_block(jointext(render_list, "\n"))) + to_chat(owner, boxed_message(jointext(render_list, "\n"))) // // Alternate clicks for slime, monkey and open turf if using a xenobio console diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss index b3cdfa6e88074..e009982f43f9f 100644 --- a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss +++ b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss @@ -42,6 +42,10 @@ a.popt { text-decoration: none; } +.center { + text-align: center; +} + /* POPUPS */ .popup { @@ -769,6 +773,10 @@ em { font-size: 60%; } +.smaller { + font-size: 80%; +} + .slightly_larger { font-size: 115%; } @@ -966,16 +974,70 @@ em { margin-left: 2.5em; } -.examine_block { - background: hsl(220, 5.3%, 11.2%); - border: 1px solid hsl(213.6, 37.9%, 74.1%); - margin: 0.5em; - padding: 0.5em 0.75em; -} - .tooltip { font-style: italic; - border-bottom: 1px dashed #fff; + border-bottom: 1px dashed; +} + +.fieldset_legend { + position: relative; + max-width: 95%; + font-size: 120%; + padding: 0.2em 0.5em; + background: #151515; // Chat background color + border: 1px solid; + border-color: inherit; + border-radius: 0.33em; + z-index: 1; + + // "Mask" a half of the border + // It very rough but it only possible way i see with IE compat + // Replace it with normal mask-image when 516 got stable + &:before { + content: ''; + position: absolute; + left: 0; + height: 1.15em; + width: 100%; + background: #151515; // Chat background color + transform: translateY(-50%) scaleX(1.05); + z-index: -1; + } +} + +.boxed_message { + background: hsl(220, 10%, 10%); + border: 1em * calc(1px / 12px) solid; + border-left: 1em * calc(4px / 12px) solid; + border-color: hsla(220, 40%, 75%, 0.25); + margin: 0.5em 0; + padding: 0.5em 0.75em; + border-radius: 0.33em; + + &.red_box { + background: hsl(0, 20%, 10%); + border-color: hsla(0, 100%, 50%, 0.5); + } + + &.green_box { + background: hsl(140, 20%, 10%); + border-color: hsla(120, 100%, 50%, 0.5); + } + + &.blue_box { + background: hsl(220, 20%, 10%); + border-color: hsla(225, 90%, 65%, 0.5); + } + + &.purple_box { + background: hsl(260, 25%, 12.5%); + border-color: hsla(260, 100%, 75%, 0.5); + } + + hr { + margin: 0.5em -0.75em; + border-color: inherit; + } } // Provides a horizontal bar with text in the middle diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss index e46cd52d5075f..a35c927ae796e 100644 --- a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss +++ b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss @@ -984,16 +984,42 @@ h2.alert { margin-left: 3em; } -.examine_block { - background: hsl(202.5, 44.4%, 96.5%); - border: 1px solid hsl(215.5, 39.3%, 11%); - margin: 0.5em; - padding: 0.5em 0.75em; -} - .tooltip { font-style: italic; - border-bottom: 1px dashed #000; + border-bottom: 1px dashed; +} + +.fieldset_legend { + background: #ffffff; // Chat background color + + &:before { + background: #ffffff; // Chat background color + } +} + +.boxed_message { + background: hsl(220, 100%, 97.5%); + border-color: hsla(220, 75%, 25%, 0.5); + + &.red_box { + background: hsl(0, 100%, 97.5%); + border-color: hsla(0, 100%, 50%, 0.5); + } + + &.green_box { + background: hsl(140, 100%, 97.5%); + border-color: hsl(120, 100%, 33%, 0.5); + } + + &.blue_box { + background: hsl(220, 100%, 97.5%); + border-color: hsla(225, 100%, 50%, 0.5); + } + + &.purple_box { + background: hsl(260, 100%, 97.5%); + border-color: hsla(260, 100%, 50%, 0.5); + } } // Provides a horizontal bar with text in the middle From c665ea8bf83c7ec3e9f0b8951091446e28ff1414 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Tue, 24 Dec 2024 19:36:13 +0000 Subject: [PATCH 106/235] Automatic changelog for PR #88663 [ci skip] --- html/changelogs/AutoChangeLog-pr-88663.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88663.yml diff --git a/html/changelogs/AutoChangeLog-pr-88663.yml b/html/changelogs/AutoChangeLog-pr-88663.yml new file mode 100644 index 0000000000000..12fa33e4de4c0 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88663.yml @@ -0,0 +1,4 @@ +author: "Absolucy, ShadowLarkens, S34N" +delete-after: True +changes: + - bugfix: "Fixed chat rapidly flickering in BYOND 516." \ No newline at end of file From 56e56ceac9b3ca221b7bf0f7f30bf0c499a75123 Mon Sep 17 00:00:00 2001 From: Aylong <69762909+AyIong@users.noreply.github.com> Date: Tue, 24 Dec 2024 21:36:25 +0200 Subject: [PATCH 107/235] Implement Edge DevTools (#88679) ## About The Pull Request Implements Edge DevTools for 516 users (coders) Thanks to [S34N](https://github.com/ParadiseSS13/Paradise/pull/25363)
Images ![image](https://github.com/user-attachments/assets/9025d45a-a1be-4e95-b1ee-4adfb29d687e) ![image](https://github.com/user-attachments/assets/abe1b936-9fb5-4001-8f21-cbeeb62def45)
--------- Co-authored-by: AnturK <4047233+anturk@users.noreply.github.com> --- code/modules/admin/verbs/debug.dm | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index 13f1995c9ba3d..6169527b57f76 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -743,6 +743,14 @@ ADMIN_VERB(reestablish_tts_connection, R_DEBUG, "Re-establish Connection To TTS" message_admins("[key_name_admin(user)] successfully re-established the connection to the TTS HTTP server.") log_admin("[key_name(user)] successfully re-established the connection to the TTS HTTP server.") +ADMIN_VERB(allow_browser_inspect, R_DEBUG, "Allow Browser Inspect", "Allow browser debugging via inspect", ADMIN_CATEGORY_DEBUG) + if(user.byond_version < 516) + to_chat(user, span_warning("You can only use this on 516!")) + return + + to_chat(user, span_notice("You can now right click to use inspect on browsers.")) + winset(user, null, list("browser-options" = "+devtools")) + /proc/generate_timer_source_output(list/datum/timedevent/events) var/list/per_source = list() From b9019110ea69271ddabfb6dee2ba5a3be9c11019 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Tue, 24 Dec 2024 19:36:48 +0000 Subject: [PATCH 108/235] Automatic changelog for PR #88678 [ci skip] --- html/changelogs/AutoChangeLog-pr-88678.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88678.yml diff --git a/html/changelogs/AutoChangeLog-pr-88678.yml b/html/changelogs/AutoChangeLog-pr-88678.yml new file mode 100644 index 0000000000000..2bd8b871336d3 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88678.yml @@ -0,0 +1,5 @@ +author: "AyIong" +delete-after: True +changes: + - qol: "AdminPMs, admin tickets, vote results and started vote notification are now much more visible in the chat." + - qol: "Boxed messages in chat (like examine), has been restyled." \ No newline at end of file From 2e4d70afe5eba8a2c50653944c2240ed6d49ce8e Mon Sep 17 00:00:00 2001 From: John Willard <53777086+JohnFulpWillard@users.noreply.github.com> Date: Tue, 24 Dec 2024 14:42:20 -0500 Subject: [PATCH 109/235] Updates href uses for 516 (#88699) ## About The Pull Request Was just scrolling through the Paradise github since they seem to have more work done for 516 to see if there's anything I can port over, found this and thought why not. Ports parts of https://github.com/ParadiseSS13/Paradise/pull/25105 Specifically, updaing all hrefs to use the internal ``byond://``, and adding it to grep. ## Why It's Good For The Game More work towards 516. ## Changelog Nothing player-facing. --- code/__DEFINES/admin.dm | 44 +++--- code/__DEFINES/say.dm | 6 +- code/__DEFINES/vv.dm | 2 +- code/__HELPERS/logging/_logging.dm | 4 +- code/__HELPERS/roundend.dm | 4 +- code/controllers/subsystem/dbcore.dm | 2 +- code/controllers/subsystem/dynamic/dynamic.dm | 20 +-- .../subsystem/dynamic/ruleset_picking.dm | 4 +- code/controllers/subsystem/lag_switch.dm | 2 +- code/controllers/subsystem/polling.dm | 6 +- code/controllers/subsystem/shuttle.dm | 2 +- code/controllers/subsystem/ticker.dm | 4 +- code/datums/browser.dm | 14 +- code/datums/elements/slapcrafting.dm | 2 +- code/datums/elements/weapon_description.dm | 2 +- code/datums/world_topic.dm | 2 +- code/game/atom/atom_vv.dm | 2 +- .../machinery/camera/camera_construction.dm | 10 +- .../game/machinery/computer/communications.dm | 2 +- code/game/machinery/computer/robot.dm | 2 +- code/game/machinery/hologram.dm | 2 +- code/game/objects/items.dm | 8 +- code/game/objects/items/charter.dm | 2 +- code/game/objects/items/devices/powersink.dm | 4 +- .../objects/items/devices/transfer_valve.dm | 2 +- code/game/objects/structures/votingbox.dm | 14 +- code/modules/admin/admin.dm | 52 +++---- code/modules/admin/antag_panel.dm | 40 +++--- code/modules/admin/check_antagonists.dm | 32 ++--- code/modules/admin/known_alts.dm | 4 +- code/modules/admin/permissionedit.dm | 30 ++-- code/modules/admin/player_panel.dm | 24 ++-- code/modules/admin/poll_management.dm | 16 +-- code/modules/admin/sound_emitter.dm | 12 +- code/modules/admin/sql_ban_system.dm | 10 +- code/modules/admin/sql_message_system.dm | 62 ++++---- code/modules/admin/stickyban.dm | 14 +- code/modules/admin/tag.dm | 6 +- code/modules/admin/team_panel.dm | 18 +-- code/modules/admin/verbs/SDQL2/SDQL_2.dm | 2 +- code/modules/admin/verbs/admin.dm | 2 +- code/modules/admin/verbs/admingame.dm | 134 +++++++++--------- code/modules/admin/verbs/adminhelp.dm | 32 ++--- code/modules/admin/verbs/adminpm.dm | 2 +- code/modules/admin/verbs/debug.dm | 4 +- .../modules/admin/verbs/individual_logging.dm | 2 +- .../admin/verbs/map_template_loadverb.dm | 4 +- code/modules/admin/verbs/mapping.dm | 2 +- .../debug_variable_appearance.dm | 2 +- .../admin/view_variables/debug_variables.dm | 14 +- .../admin/view_variables/topic_basic.dm | 2 +- .../admin/view_variables/view_variables.dm | 2 +- .../nukeop/datums/operative_team.dm | 8 +- .../antagonists/revolution/revolution.dm | 10 +- .../antagonists/traitor/datum_traitor.dm | 12 +- .../modules/asset_cache/asset_cache_client.dm | 2 +- code/modules/client/client_procs.dm | 2 +- code/modules/client/preferences_savefile.dm | 2 +- code/modules/clothing/clothing.dm | 4 +- .../deathmatch/deathmatch_controller.dm | 2 +- code/modules/error_handler/error_viewer.dm | 12 +- code/modules/events/_event.dm | 2 +- code/modules/interview/interview.dm | 2 +- code/modules/mob/dead/new_player/poll.dm | 2 +- code/modules/mob/dead/observer/observer.dm | 2 +- .../mob/living/carbon/carbon_defense.dm | 4 +- code/modules/mob/living/carbon/examine.dm | 22 +-- code/modules/mob/living/living.dm | 12 +- code/modules/mob/living/silicon/ai/ai.dm | 6 +- code/modules/mob/living/silicon/ai/ai_say.dm | 4 +- code/modules/mob/living/silicon/ai/life.dm | 2 +- code/modules/mob/living/silicon/ai/login.dm | 2 +- code/modules/mob/living/silicon/laws.dm | 2 +- .../modules/mob/living/silicon/robot/robot.dm | 4 +- code/modules/mob/living/silicon/silicon.dm | 4 +- .../modules/mob/living/silicon/silicon_say.dm | 2 +- code/modules/mob/mob_helpers.dm | 4 +- .../programs/messenger/messenger_program.dm | 2 +- code/modules/reagents/chemistry/recipes.dm | 2 +- .../shuttle_consoles/shuttle_console.dm | 2 +- code/modules/surgery/bodyparts/_bodyparts.dm | 2 +- code/modules/tgui_panel/tgui_panel.dm | 2 +- .../vehicles/mecha/mecha_ai_interaction.dm | 4 +- .../wiremod/shell/brain_computer_interface.dm | 2 +- tools/ci/check_grep.sh | 8 ++ 85 files changed, 427 insertions(+), 419 deletions(-) diff --git a/code/__DEFINES/admin.dm b/code/__DEFINES/admin.dm index 74c9120d804d4..ea5c52bf36f0f 100644 --- a/code/__DEFINES/admin.dm +++ b/code/__DEFINES/admin.dm @@ -45,40 +45,40 @@ #define R_EVERYTHING (1<<15)-1 //the sum of all other rank permissions, used for +EVERYTHING -#define ADMIN_QUE(user) "(?)" -#define ADMIN_FLW(user) "(FLW)" -#define ADMIN_PP(user) "(PP)" -#define ADMIN_VV(atom) "(VV)" -#define ADMIN_SM(user) "(SM)" -#define ADMIN_TP(user) "(TP)" -#define ADMIN_SP(user) "(SP)" -#define ADMIN_KICK(user) "(KICK)" -#define ADMIN_CENTCOM_REPLY(user) "(RPLY)" -#define ADMIN_SYNDICATE_REPLY(user) "(RPLY)" -#define ADMIN_SC(user) "(SC)" -#define ADMIN_SMITE(user) "(SMITE)" +#define ADMIN_QUE(user) "(?)" +#define ADMIN_FLW(user) "(FLW)" +#define ADMIN_PP(user) "(PP)" +#define ADMIN_VV(atom) "(VV)" +#define ADMIN_SM(user) "(SM)" +#define ADMIN_TP(user) "(TP)" +#define ADMIN_SP(user) "(SP)" +#define ADMIN_KICK(user) "(KICK)" +#define ADMIN_CENTCOM_REPLY(user) "(RPLY)" +#define ADMIN_SYNDICATE_REPLY(user) "(RPLY)" +#define ADMIN_SC(user) "(SC)" +#define ADMIN_SMITE(user) "(SMITE)" #define ADMIN_LOOKUP(user) "[key_name_admin(user)][ADMIN_QUE(user)]" #define ADMIN_LOOKUPFLW(user) "[key_name_admin(user)][ADMIN_QUE(user)] [ADMIN_FLW(user)]" -#define ADMIN_SET_SD_CODE "(SETCODE)" +#define ADMIN_SET_SD_CODE "(SETCODE)" #define ADMIN_FULLMONTY_NONAME(user) "[ADMIN_QUE(user)] [ADMIN_PP(user)] [ADMIN_VV(user)] [ADMIN_SM(user)] [ADMIN_FLW(user)] [ADMIN_TP(user)] [ADMIN_INDIVIDUALLOG(user)] [ADMIN_SMITE(user)]" #define ADMIN_FULLMONTY(user) "[key_name_admin(user)] [ADMIN_FULLMONTY_NONAME(user)]" -#define ADMIN_JMP(src) "(JMP)" +#define ADMIN_JMP(src) "(JMP)" #define COORD(src) "[src ? src.Admin_Coordinates_Readable() : "nonexistent location"]" #define AREACOORD(src) "[src ? src.Admin_Coordinates_Readable(TRUE) : "nonexistent location"]" #define ADMIN_COORDJMP(src) "[src ? src.Admin_Coordinates_Readable(FALSE, TRUE) : "nonexistent location"]" #define ADMIN_VERBOSEJMP(src) "[src ? src.Admin_Coordinates_Readable(TRUE, TRUE) : "nonexistent location"]" -#define ADMIN_INDIVIDUALLOG(user) "(LOGS)" -#define ADMIN_TAG(datum) "(TAG)" -#define ADMIN_LUAVIEW(state) "(VIEW STATE)" -#define ADMIN_LUAVIEW_CHUNK(state, log_index) "(VIEW CODE)" +#define ADMIN_INDIVIDUALLOG(user) "(LOGS)" +#define ADMIN_TAG(datum) "(TAG)" +#define ADMIN_LUAVIEW(state) "(VIEW STATE)" +#define ADMIN_LUAVIEW_CHUNK(state, log_index) "(VIEW CODE)" /// Displays "(SHOW)" in the chat, when clicked it tries to show atom(paper). First you need to set the request_state variable to TRUE for the paper. -#define ADMIN_SHOW_PAPER(atom) "(SHOW)" +#define ADMIN_SHOW_PAPER(atom) "(SHOW)" /// Displays "(PRINT)" in the chat, when clicked it will try to print the atom(paper) on the CentCom/Syndicate fax machine. -#define ADMIN_PRINT_FAX(atom, sender, destination) "(PRINT)" +#define ADMIN_PRINT_FAX(atom, sender, destination) "(PRINT)" /// Displays "(PLAY)" in the chat, when clicked it tries to play internet sounds from the request. -#define ADMIN_PLAY_INTERNET(text, credit) "(PLAY)" +#define ADMIN_PLAY_INTERNET(text, credit) "(PLAY)" /// Displays "(SEE Z-LEVEL LAYOUT)" in the chat, when clicked it shows the z-level layouts for the current world state. -#define ADMIN_SEE_ZLEVEL_LAYOUT "(SEE Z-LEVEL LAYOUT)" +#define ADMIN_SEE_ZLEVEL_LAYOUT "(SEE Z-LEVEL LAYOUT)" /atom/proc/Admin_Coordinates_Readable(area_name, admin_jump_ref) var/turf/turf_at_coords = Safe_COORD_Location() diff --git a/code/__DEFINES/say.dm b/code/__DEFINES/say.dm index d905129b19b74..c3bd425af0a65 100644 --- a/code/__DEFINES/say.dm +++ b/code/__DEFINES/say.dm @@ -99,9 +99,9 @@ #define MODE_RANGE_INTERCOM 1 // A link given to ghost alice to follow bob -#define FOLLOW_LINK(alice, bob) "(F)" -#define TURF_LINK(alice, turfy) "(T)" -#define FOLLOW_OR_TURF_LINK(alice, bob, turfy) "(F)" +#define FOLLOW_LINK(alice, bob) "(F)" +#define TURF_LINK(alice, turfy) "(T)" +#define FOLLOW_OR_TURF_LINK(alice, bob, turfy) "(F)" //Don't set this very much higher then 1024 unless you like inviting people in to dos your server with message spam #define MAX_MESSAGE_LEN 1024 diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm index 88f46a53fd92c..1ce2d5d46cb9c 100644 --- a/code/__DEFINES/vv.dm +++ b/code/__DEFINES/vv.dm @@ -50,7 +50,7 @@ #define GET_VV_VAR_TARGET href_list[VV_HK_VARNAME] //Helper for getting something to vv_do_topic in general -#define VV_TOPIC_LINK(datum, href_key, text) "text" +#define VV_TOPIC_LINK(datum, href_key, text) "text" //Helpers for vv_get_dropdown() #define VV_DROPDOWN_OPTION(href_key, name) . += "" diff --git a/code/__HELPERS/logging/_logging.dm b/code/__HELPERS/logging/_logging.dm index bfcaded67f021..8f8f733e6ba8b 100644 --- a/code/__HELPERS/logging/_logging.dm +++ b/code/__HELPERS/logging/_logging.dm @@ -215,11 +215,11 @@ GLOBAL_LIST_INIT(testing_global_profiler, list("_PROFILE_NAME" = "Global")) if(key) if(C?.holder && C.holder.fakekey && !include_name) if(include_link) - . += "" + . += "" . += "Administrator" else if(include_link) - . += "" + . += "" . += key if(!C) . += "\[DC\]" diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm index 292639f387682..19dec4a7fd442 100644 --- a/code/__HELPERS/roundend.dm +++ b/code/__HELPERS/roundend.dm @@ -335,7 +335,7 @@ GLOBAL_LIST_INIT(achievements_unlocked, list()) if(GLOB.round_id) var/statspage = CONFIG_GET(string/roundstatsurl) - var/info = statspage ? "[GLOB.round_id]" : GLOB.round_id + var/info = statspage ? "[GLOB.round_id]" : GLOB.round_id parts += "[FOURSPACES]Round ID: [info]" parts += "[FOURSPACES]Shift Duration: [DisplayTimeText(world.time - SSticker.round_start_time)]" parts += "[FOURSPACES]Station Integrity: [GLOB.station_was_nuked ? span_redtext("Destroyed") : "[popcount["station_integrity"]]%"]" @@ -660,7 +660,7 @@ GLOBAL_LIST_INIT(achievements_unlocked, list()) var/datum/action/report/R = new C.player_details.player_actions += R R.Grant(C.mob) - to_chat(C,span_infoplain("Show roundend report again")) + to_chat(C,span_infoplain("Show roundend report again")) /datum/action/report name = "Show roundend report" diff --git a/code/controllers/subsystem/dbcore.dm b/code/controllers/subsystem/dbcore.dm index 115250104f008..805fe419c94b7 100644 --- a/code/controllers/subsystem/dbcore.dm +++ b/code/controllers/subsystem/dbcore.dm @@ -674,7 +674,7 @@ Ignore_errors instructes mysql to continue inserting rows if some of them have e /datum/db_query/proc/slow_query_check() - message_admins("HEY! A database query timed out. Did the server just hang? \[YES\]|\[NO\]") + message_admins("HEY! A database query timed out. Did the server just hang? \[YES\]|\[NO\]") /datum/db_query/proc/NextRow(async = TRUE) Activity("NextRow") diff --git a/code/controllers/subsystem/dynamic/dynamic.dm b/code/controllers/subsystem/dynamic/dynamic.dm index 1e1a9afec4759..73109d0dccaf0 100644 --- a/code/controllers/subsystem/dynamic/dynamic.dm +++ b/code/controllers/subsystem/dynamic/dynamic.dm @@ -203,23 +203,23 @@ SUBSYSTEM_DEF(dynamic) /datum/controller/subsystem/dynamic/proc/admin_panel() var/list/dat = list("Game Mode Panel

Game Mode Panel

") - dat += "Dynamic Mode \[VV\] \[Refresh\]
" + dat += "Dynamic Mode \[VV\] \[Refresh\]
" dat += "Threat Level: [threat_level]
" dat += "Budgets (Roundstart/Midrounds): [initial_round_start_budget]/[threat_level - initial_round_start_budget]
" - dat += "Midround budget to spend: [mid_round_budget] \[Adjust\] \[View Log\]
" + dat += "Midround budget to spend: [mid_round_budget] \[Adjust\] \[View Log\]
" dat += "
" dat += "Parameters: centre = [threat_curve_centre] ; width = [threat_curve_width].
" dat += "Split parameters: centre = [roundstart_split_curve_centre] ; width = [roundstart_split_curve_width].
" dat += "On average, [clamp(peaceful_percentage, 1, 99)]% of the rounds are more peaceful.
" - dat += "Forced extended: [GLOB.dynamic_forced_extended ? "On" : "Off"]
" - dat += "No stacking (only one round-ender): [GLOB.dynamic_no_stacking ? "On" : "Off"]
" - dat += "Stacking limit: [GLOB.dynamic_stacking_limit] \[Adjust\]" + dat += "Forced extended: [GLOB.dynamic_forced_extended ? "On" : "Off"]
" + dat += "No stacking (only one round-ender): [GLOB.dynamic_no_stacking ? "On" : "Off"]
" + dat += "Stacking limit: [GLOB.dynamic_stacking_limit] \[Adjust\]" dat += "
" - dat += "\[Force Next Latejoin Ruleset\]
" + dat += "\[Force Next Latejoin Ruleset\]
" if (forced_latejoin_rule) - dat += {"-> [forced_latejoin_rule.name] <-
"} - dat += "\[Execute Midround Ruleset\]
" + dat += {"-> [forced_latejoin_rule.name] <-
"} + dat += "\[Execute Midround Ruleset\]
" dat += "
" dat += "Executed rulesets: " if (executed_rules.len > 0) @@ -229,13 +229,13 @@ SUBSYSTEM_DEF(dynamic) else dat += "none.
" dat += "
Injection Timers: ([get_heavy_midround_injection_chance(dry_run = TRUE)]% heavy midround chance)
" - dat += "Latejoin: [DisplayTimeText(latejoin_injection_cooldown-world.time)] \[Now!\]
" + dat += "Latejoin: [DisplayTimeText(latejoin_injection_cooldown-world.time)] \[Now!\]
" var/next_injection = next_midround_injection() if (next_injection == INFINITY) dat += "All midrounds have been exhausted." else - dat += "Midround: [DisplayTimeText(next_injection - world.time)] \[Now!\]
" + dat += "Midround: [DisplayTimeText(next_injection - world.time)] \[Now!\]
" usr << browse(dat.Join(), "window=gamemode_panel;size=500x500") diff --git a/code/controllers/subsystem/dynamic/ruleset_picking.dm b/code/controllers/subsystem/dynamic/ruleset_picking.dm index e3de3289899f7..f22ce3315740e 100644 --- a/code/controllers/subsystem/dynamic/ruleset_picking.dm +++ b/code/controllers/subsystem/dynamic/ruleset_picking.dm @@ -69,8 +69,8 @@ log_dynamic("[rule] ruleset executing...") message_admins("DYNAMIC: Executing midround ruleset [rule] in [DisplayTimeText(ADMIN_CANCEL_MIDROUND_TIME)]. \ - CANCEL | \ - SOMETHING ELSE") + CANCEL | \ + SOMETHING ELSE") return rule diff --git a/code/controllers/subsystem/lag_switch.dm b/code/controllers/subsystem/lag_switch.dm index c79db05186017..291e80fe18f1f 100644 --- a/code/controllers/subsystem/lag_switch.dm +++ b/code/controllers/subsystem/lag_switch.dm @@ -34,7 +34,7 @@ SUBSYSTEM_DEF(lag_switch) auto_switch = FALSE UnregisterSignal(SSdcs, COMSIG_GLOB_CLIENT_CONNECT) veto_timer_id = addtimer(CALLBACK(src, PROC_REF(set_all_measures), TRUE, TRUE), 20 SECONDS, TIMER_STOPPABLE) - message_admins("Lag Switch population threshold reached. Automatic activation of lag mitigation measures occuring in 20 seconds. (CANCEL)") + message_admins("Lag Switch population threshold reached. Automatic activation of lag mitigation measures occuring in 20 seconds. (CANCEL)") log_admin("Lag Switch population threshold reached. Automatic activation of lag mitigation measures occuring in 20 seconds.") /// (En/Dis)able automatic triggering of switches based on client count diff --git a/code/controllers/subsystem/polling.dm b/code/controllers/subsystem/polling.dm index 5cb81498c3df9..6624c984cbb6a 100644 --- a/code/controllers/subsystem/polling.dm +++ b/code/controllers/subsystem/polling.dm @@ -148,11 +148,11 @@ SUBSYSTEM_DEF(polling) var/custom_link_style_start = "" var/custom_link_style_end = "style='color:DodgerBlue;font-weight:bold;-dm-text-outline: 1px black'" if(isatom(alert_pic) && isobserver(candidate_mob)) - act_jump = "[custom_link_style_start]\[Teleport\]" - var/act_signup = "[custom_link_style_start]\[[start_signed_up ? "Opt out" : "Sign Up"]\]" + act_jump = "[custom_link_style_start]\[Teleport\]" + var/act_signup = "[custom_link_style_start]\[[start_signed_up ? "Opt out" : "Sign Up"]\]" var/act_never = "" if(ignore_category) - act_never = "[custom_link_style_start]\[Never For This Round\]" + act_never = "[custom_link_style_start]\[Never For This Round\]" if(!duplicate_message_check(alert_poll)) //Only notify people once. They'll notice if there are multiple and we don't want to spam people. SEND_SOUND(candidate_mob, 'sound/announcer/notice/notice2.ogg') diff --git a/code/controllers/subsystem/shuttle.dm b/code/controllers/subsystem/shuttle.dm index 4f573ee2f2224..ff19d6d4b265a 100644 --- a/code/controllers/subsystem/shuttle.dm +++ b/code/controllers/subsystem/shuttle.dm @@ -385,7 +385,7 @@ SUBSYSTEM_DEF(shuttle) SSblackbox.record_feedback("text", "shuttle_reason", 1, "[call_reason]") log_shuttle("Shuttle call reason: [call_reason]") SSticker.emergency_reason = call_reason - message_admins("[ADMIN_LOOKUPFLW(user)] has called the shuttle. (TRIGGER CENTCOM RECALL)") + message_admins("[ADMIN_LOOKUPFLW(user)] has called the shuttle. (TRIGGER CENTCOM RECALL)") /// Call the emergency shuttle. /// If you are doing this on behalf of a player, use requestEvac instead. diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index 03e7b6dcbd885..96cde0ef0dded 100644 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -490,7 +490,7 @@ SUBSYSTEM_DEF(ticker) if(!hard_popcap) list_clear_nulls(queued_players) for (var/mob/dead/new_player/new_player in queued_players) - to_chat(new_player, span_userdanger("The alive players limit has been released!
[html_encode(">>Join Game<<")]")) + to_chat(new_player, span_userdanger("The alive players limit has been released!
[html_encode(">>Join Game<<")]")) SEND_SOUND(new_player, sound('sound/announcer/notice/notice1.ogg')) GLOB.latejoin_menu.ui_interact(new_player) queued_players.len = 0 @@ -505,7 +505,7 @@ SUBSYSTEM_DEF(ticker) list_clear_nulls(queued_players) if(living_player_count() < hard_popcap) if(next_in_line?.client) - to_chat(next_in_line, span_userdanger("A slot has opened! You have approximately 20 seconds to join. \>\>Join Game\<\<")) + to_chat(next_in_line, span_userdanger("A slot has opened! You have approximately 20 seconds to join. \>\>Join Game\<\<")) SEND_SOUND(next_in_line, sound('sound/announcer/notice/notice1.ogg')) next_in_line.ui_interact(next_in_line) return diff --git a/code/datums/browser.dm b/code/datums/browser.dm index b9d859552389d..cf0df8dac5017 100644 --- a/code/datums/browser.dm +++ b/code/datums/browser.dm @@ -138,13 +138,13 @@ var/output = {"
[Message]

- [Button1]"} + [Button1]"} if (Button2) - output += {"[Button2]"} + output += {"[Button2]"} if (Button3) - output += {"[Button3]"} + output += {"[Button3]"} output += {"
"} @@ -356,11 +356,11 @@ var/setting = settings["mainsettings"][name] if (setting["type"] == "datum") if (setting["subtypesonly"]) - dat += "[setting["desc"]]: [setting["value"]]
" + dat += "[setting["desc"]]: [setting["value"]]
" else - dat += "[setting["desc"]]: [setting["value"]]
" + dat += "[setting["desc"]]: [setting["value"]]
" else - dat += "[setting["desc"]]: [setting["value"]]
" + dat += "[setting["desc"]]: [setting["value"]]
" if (preview_icon) dat += "" @@ -371,7 +371,7 @@ dat += "" - dat += "
Ok " + dat += "
Ok " dat += "
" diff --git a/code/datums/elements/slapcrafting.dm b/code/datums/elements/slapcrafting.dm index 925b2fd575597..046a8668a2974 100644 --- a/code/datums/elements/slapcrafting.dm +++ b/code/datums/elements/slapcrafting.dm @@ -134,7 +134,7 @@ for(var/datum/crafting_recipe/recipe as anything in slapcraft_recipes) var/atom/result = initial(recipe.result) - examine_list += "See Recipe For [initial(result.name)]" + examine_list += "See Recipe For [initial(result.name)]" /datum/element/slapcrafting/proc/topic_handler(atom/source, user, href_list) SIGNAL_HANDLER diff --git a/code/datums/elements/weapon_description.dm b/code/datums/elements/weapon_description.dm index 110a490b5ca12..022318abfcf0d 100644 --- a/code/datums/elements/weapon_description.dm +++ b/code/datums/elements/weapon_description.dm @@ -39,7 +39,7 @@ SIGNAL_HANDLER if(item.force >= 5 || item.throwforce >= 5 || item.override_notes || item.offensive_notes || attached_proc) /// Only show this tag for items that could feasibly be weapons, shields, or those that have special notes - examine_texts += span_notice("See combat information.") + examine_texts += span_notice("See combat information.") /** * diff --git a/code/datums/world_topic.dm b/code/datums/world_topic.dm index 2a4af3a781aa0..c34c018d59c65 100644 --- a/code/datums/world_topic.dm +++ b/code/datums/world_topic.dm @@ -114,7 +114,7 @@ var/message = "CROSS-SECTOR MESSAGE (INCOMING): [input["sender_ckey"]] (from [input["source"]]) is about to send \ the following message (will autoapprove in [soft_filter_passed ? "[extended_time_display]" : "[normal_time_display]"]): \ - REJECT

\ + REJECT

\ [html_encode(input["message"])]" if(soft_filter_passed) diff --git a/code/game/atom/atom_vv.dm b/code/game/atom/atom_vv.dm index 7a7dc8d3a877d..14a8b41e6e10e 100644 --- a/code/game/atom/atom_vv.dm +++ b/code/game/atom/atom_vv.dm @@ -199,7 +199,7 @@ . = ..() var/refid = REF(src) . += "[VV_HREF_TARGETREF(refid, VV_HK_AUTO_RENAME, "[src]")]" - . += "
<< [dir2text(dir) || dir] >>" + . += "
<< [dir2text(dir) || dir] >>" /** * call back when a var is edited on this atom diff --git a/code/game/machinery/camera/camera_construction.dm b/code/game/machinery/camera/camera_construction.dm index b3593aad6e7e2..19d7d2a39567b 100644 --- a/code/game/machinery/camera/camera_construction.dm +++ b/code/game/machinery/camera/camera_construction.dm @@ -189,9 +189,9 @@ ai.last_tablet_note_seen = "[itemname][info]" if(user.name == "Unknown") - to_chat(ai, "[span_name(user)] holds \a [itemname] up to one of your cameras ...") + to_chat(ai, "[span_name(user)] holds \a [itemname] up to one of your cameras ...") else - to_chat(ai, "[user] holds \a [itemname] up to one of your cameras ...") + to_chat(ai, "[user] holds \a [itemname] up to one of your cameras ...") continue if (potential_viewer.client?.eye == src) @@ -232,16 +232,16 @@ log_paper("[key_name(user)] held [last_shown_paper] up to [src], requesting [key_name(ai)] read it.") if(user.name == "Unknown") - to_chat(ai, "[span_name(user.name)] holds \a [item_name] up to one of your cameras ...") + to_chat(ai, "[span_name(user.name)] holds \a [item_name] up to one of your cameras ...") else - to_chat(ai, "[user] holds \a [item_name] up to one of your cameras ...") + to_chat(ai, "[user] holds \a [item_name] up to one of your cameras ...") continue // If it's not an AI, eye if the client's eye is set to the camera. I wonder if this even works anymore with tgui camera apps and stuff? if (potential_viewer.client?.eye == src) log_paper("[key_name(user)] held [last_shown_paper] up to [src], and [key_name(potential_viewer)] may read it.") potential_viewer.log_talk(item_name, LOG_VICTIM, tag="Pressed to camera from [key_name(user)]", log_globally=FALSE) - to_chat(potential_viewer, "[span_name(user)] holds \a [item_name] up to your camera...") + to_chat(potential_viewer, "[span_name(user)] holds \a [item_name] up to your camera...") return return ..() diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm index b399223522295..7e9ef45e8f5cf 100644 --- a/code/game/machinery/computer/communications.dm +++ b/code/game/machinery/computer/communications.dm @@ -346,7 +346,7 @@ span_adminnotice( \ "CROSS-SECTOR MESSAGE (OUTGOING): [ADMIN_LOOKUPFLW(user)] is about to send \ the following message to [destination] (will autoapprove in [GLOB.communications_controller.soft_filtering ? DisplayTimeText(EXTENDED_CROSS_SECTOR_CANCEL_TIME) : DisplayTimeText(CROSS_SECTOR_CANCEL_TIME)]): \ - REJECT
\ + REJECT
\ [html_encode(message)]" \ ) ) diff --git a/code/game/machinery/computer/robot.dm b/code/game/machinery/computer/robot.dm index 12aa1c3ce0362..228391e6bdeec 100644 --- a/code/game/machinery/computer/robot.dm +++ b/code/game/machinery/computer/robot.dm @@ -176,7 +176,7 @@ if(!isnull(console_location)) to_chat(R, span_alert("The approximate location of the console that is keeping you locked down is [console_location]")) if(R.connected_ai) - to_chat(R.connected_ai, "[!R.lockcharge ? span_notice("NOTICE - Cyborg lockdown lifted") : span_alert("ALERT - Cyborg lockdown detected")]: [R.name]
") + to_chat(R.connected_ai, "[!R.lockcharge ? span_notice("NOTICE - Cyborg lockdown lifted") : span_alert("ALERT - Cyborg lockdown detected")]: [R.name]
") /obj/machinery/computer/robotics/proc/borg_destroyed() SIGNAL_HANDLER diff --git a/code/game/machinery/hologram.dm b/code/game/machinery/hologram.dm index a40eaf710a6be..4cb2386056cfd 100644 --- a/code/game/machinery/hologram.dm +++ b/code/game/machinery/hologram.dm @@ -327,7 +327,7 @@ Possible to do for anyone motivated enough: for(var/mob/living/silicon/ai/AI in GLOB.silicon_mobs) if(!AI.client) continue - to_chat(AI, span_info("Your presence is requested at \the [area]. Project Hologram?")) + to_chat(AI, span_info("Your presence is requested at \the [area]. Project Hologram?")) return TRUE else to_chat(usr, span_info("A request for AI presence was already sent recently.")) diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index b83d9a78ac37f..01c506d6fd708 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -2042,9 +2042,9 @@ . = ..() . += {"
- DAMTYPE: [uppertext(damtype)] - FORCE: [force] - WOUND: [wound_bonus] - BARE WOUND: [bare_wound_bonus] + DAMTYPE: [uppertext(damtype)] + FORCE: [force] + WOUND: [wound_bonus] + BARE WOUND: [bare_wound_bonus] "} diff --git a/code/game/objects/items/charter.dm b/code/game/objects/items/charter.dm index 69b57fe4e7323..6f8ad945621e5 100644 --- a/code/game/objects/items/charter.dm +++ b/code/game/objects/items/charter.dm @@ -58,7 +58,7 @@ // Autoapproves after a certain time response_timer_id = addtimer(CALLBACK(src, PROC_REF(rename_station), new_name, user.name, user.real_name, key_name(user)), approval_time, TIMER_STOPPABLE) to_chat(GLOB.admins, - span_adminnotice("CUSTOM STATION RENAME:[ADMIN_LOOKUPFLW(user)] proposes to rename the [name_type] to [new_name] (will autoapprove in [DisplayTimeText(approval_time)]). [ADMIN_SMITE(user)] (REJECT) [ADMIN_CENTCOM_REPLY(user)]"), + span_adminnotice("CUSTOM STATION RENAME:[ADMIN_LOOKUPFLW(user)] proposes to rename the [name_type] to [new_name] (will autoapprove in [DisplayTimeText(approval_time)]). [ADMIN_SMITE(user)] (REJECT) [ADMIN_CENTCOM_REPLY(user)]"), type = MESSAGE_TYPE_PRAYER) for(var/client/admin_client in GLOB.admins) if(admin_client.prefs.toggles & SOUND_ADMINHELP) diff --git a/code/game/objects/items/devices/powersink.dm b/code/game/objects/items/devices/powersink.dm index 364550f062aa7..c6aa10784eeef 100644 --- a/code/game/objects/items/devices/powersink.dm +++ b/code/game/objects/items/devices/powersink.dm @@ -151,7 +151,7 @@ air_update_turf(FALSE, FALSE) if(warning_given && internal_heat < max_heat * 0.75) warning_given = FALSE - message_admins("Power sink at ([x],[y],[z] - JMP) has cooled down and will not explode.") + message_admins("Power sink at ([x],[y],[z] - JMP) has cooled down and will not explode.") if(mode != OPERATING && internal_heat < MINIMUM_HEAT) internal_heat = 0 STOP_PROCESSING(SSobj, src) @@ -188,7 +188,7 @@ if(internal_heat > max_heat * ALERT / 100) if (!warning_given) warning_given = TRUE - message_admins("Power sink at ([x],[y],[z] - JMP) has reached [ALERT]% of max heat. Explosion imminent.") + message_admins("Power sink at ([x],[y],[z] - JMP) has reached [ALERT]% of max heat. Explosion imminent.") notify_ghosts( "[src] is about to reach critical heat capacity!", source = src, diff --git a/code/game/objects/items/devices/transfer_valve.dm b/code/game/objects/items/devices/transfer_valve.dm index 4f0c0a84aa317..ffa172707bc7f 100644 --- a/code/game/objects/items/devices/transfer_valve.dm +++ b/code/game/objects/items/devices/transfer_valve.dm @@ -245,7 +245,7 @@ if(attached_device) if(issignaler(attached_device)) var/obj/item/assembly/signaler/attached_signaller = attached_device - attachment = "[attached_signaller]" + attachment = "[attached_signaller]" attachment_signal_log = attached_signaller.last_receive_signal_log ? "The following log entry is the last one associated with the attached signaller
[attached_signaller.last_receive_signal_log]" : "There is no signal log entry." else attachment = attached_device diff --git a/code/game/objects/structures/votingbox.dm b/code/game/objects/structures/votingbox.dm index 55909978fe2f7..013556febbd52 100644 --- a/code/game/objects/structures/votingbox.dm +++ b/code/game/objects/structures/votingbox.dm @@ -44,13 +44,13 @@ dat += "

Unregistered. Swipe ID card to register as voting box operator

" dat += "

[vote_description]

" if(is_operator(user)) - dat += "Voting: [voting_active ? "Active" : "Maintenance Mode"]
" - dat += "Set Description: Set Description
" - dat += "One vote per ID: [id_auth ? "Yes" : "No"]
" - dat += "Reset voted ID's: Reset
" - dat += "Draw random vote: Raffle
" - dat += "Shred votes: Shred
" - dat += "Tally votes: Tally
" + dat += "Voting: [voting_active ? "Active" : "Maintenance Mode"]
" + dat += "Set Description: Set Description
" + dat += "One vote per ID: [id_auth ? "Yes" : "No"]
" + dat += "Reset voted ID's: Reset
" + dat += "Draw random vote: Raffle
" + dat += "Shred votes: Shred
" + dat += "Tally votes: Tally
" var/datum/browser/popup = new(user, "votebox", "Voting Box", 300, 300) popup.set_content(dat.Join()) diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index 5f40de037f4ae..10f0cd7269341 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -21,27 +21,27 @@ var/dat = "
Game Panel

" if(SSticker.current_state <= GAME_STATE_PREGAME) - dat += "(Manage Dynamic Rulesets)
" - dat += "(Force Roundstart Rulesets)
" + dat += "(Manage Dynamic Rulesets)
" + dat += "(Force Roundstart Rulesets)
" if (GLOB.dynamic_forced_roundstart_ruleset.len > 0) for(var/datum/dynamic_ruleset/roundstart/rule in GLOB.dynamic_forced_roundstart_ruleset) - dat += {"-> [rule.name] <-
"} - dat += "(Clear Rulesets)
" - dat += "(Dynamic mode options)
" + dat += {"-> [rule.name] <-
"} + dat += "(Clear Rulesets)
" + dat += "(Dynamic mode options)
" dat += "
" if(SSticker.IsRoundInProgress()) - dat += "(Game Mode Panel)
" - dat += "(Manage Dynamic Rulesets)
" + dat += "(Game Mode Panel)
" + dat += "(Manage Dynamic Rulesets)
" dat += {"
- Create Object
- Quick Create Object
- Create Turf
- Create Mob
+ Create Object
+ Quick Create Object
+ Create Turf
+ Create Mob
"} if(marked_datum && istype(marked_datum, /atom)) - dat += "Duplicate Marked Datum
" + dat += "Duplicate Marked Datum
" usr << browse(dat, "window=admin2;size=240x280") return @@ -110,17 +110,17 @@ ADMIN_VERB(spawn_cargo, R_SPAWN, "Spawn Cargo", "Spawn a cargo crate.", ADMIN_CA

Common options

All these options can be changed midround.

- Force extended: - Option is [GLOB.dynamic_forced_extended ? "ON" : "OFF"]. + Force extended: - Option is [GLOB.dynamic_forced_extended ? "ON" : "OFF"].
This will force the round to be extended. No rulesets will be drafted.

- No stacking: - Option is [GLOB.dynamic_no_stacking ? "ON" : "OFF"]. + No stacking: - Option is [GLOB.dynamic_no_stacking ? "ON" : "OFF"].
Unless the threat goes above [GLOB.dynamic_stacking_limit], only one "round-ender" ruleset will be drafted.

- Forced threat level: Current value : [GLOB.dynamic_forced_threat_level]. + Forced threat level: Current value : [GLOB.dynamic_forced_threat_level].
The value threat is set to if it is higher than -1.


- Stacking threeshold: Current value : [GLOB.dynamic_stacking_limit]. + Stacking threeshold: Current value : [GLOB.dynamic_stacking_limit].
The threshold at which "round-ender" rulesets will stack. A value higher than 100 ensure this never happens.
"} @@ -131,9 +131,9 @@ ADMIN_VERB(spawn_cargo, R_SPAWN, "Spawn Cargo", "Spawn a cargo crate.", ADMIN_CA Change these options to forcibly enable or disable dynamic rulesets.
\ Disabled rulesets will never run, even if they would otherwise be valid.
\ Enabled rulesets will run even if the qualifying minimum of threat or player count is not present, this does not guarantee that they will necessarily be chosen (for example their weight may be set to 0 in config).
\ - \[force enable all / \ - force disable all / \ - reset all\]" + \[force enable all / \ + force disable all / \ + reset all\]" if (SSticker.current_state <= GAME_STATE_PREGAME) // Don't bother displaying after the round has started var/static/list/rulesets_by_context = list() @@ -166,9 +166,9 @@ ADMIN_VERB(spawn_cargo, R_SPAWN, "Spawn Cargo", "Spawn a cargo crate.", ADMIN_CA if (RULESET_FORCE_DISABLED) color = COLOR_RED dat += "[initial(rule.name)]\[[forced]\]\[\ - force enabled /\ - force disabled /\ - reset\]" + force enabled /\ + force disabled /\ + reset\]" dat += "" return dat @@ -199,10 +199,10 @@ ADMIN_VERB(spawn_cargo, R_SPAWN, "Spawn Cargo", "Spawn a cargo crate.", ADMIN_CA dat += "[rule.name]\ \[Weight : [rule.weight]\]\ \[[active][explanation]\]\[\ - force enabled /\ - force disabled /\ - reset\]\ - \[VV\]" + force enabled /\ + force disabled /\ + reset\]\ + \[VV\]" dat += "" return dat diff --git a/code/modules/admin/antag_panel.dm b/code/modules/admin/antag_panel.dm index e348321326473..33d12f9be22bc 100644 --- a/code/modules/admin/antag_panel.dm +++ b/code/modules/admin/antag_panel.dm @@ -30,7 +30,7 @@ GLOBAL_VAR(antag_prototypes) /datum/antagonist/proc/antag_panel() var/list/commands = list() for(var/command in get_admin_commands()) - commands += "[command]" + commands += "[command]" var/command_part = commands.Join(" | ") var/data_part = antag_panel_data() var/objective_part = antag_panel_objectives() @@ -47,30 +47,30 @@ GLOBAL_VAR(antag_prototypes) var/obj_count = 1 for(var/datum/objective/objective as anything in objectives) result += "[obj_count]: [objective.explanation_text] \ - Edit \ - Delete \ - [objective.completed ? "Mark as incomplete" : "Mark as complete"] \ + Edit \ + Delete \ + [objective.completed ? "Mark as incomplete" : "Mark as complete"] \
" obj_count++ - result += "Add objective
" - result += "Prompt custom objective entry
" - result += "Announce objectives
" + result += "Add objective
" + result += "Prompt custom objective entry
" + result += "Announce objectives
" return result /datum/mind/proc/get_common_admin_commands() var/common_commands = "Common Commands:" if(ishuman(current)) - common_commands += "undress" + common_commands += "undress" else if(iscyborg(current)) var/mob/living/silicon/robot/R = current if(R.emagged) - common_commands += "Unemag" + common_commands += "Unemag" else if(isAI(current)) var/mob/living/silicon/ai/A = current if (A.connected_robots.len) for (var/mob/living/silicon/robot/R in A.connected_robots) if (R.emagged) - common_commands += "Unemag slaved cyborgs" + common_commands += "Unemag slaved cyborgs" break return common_commands @@ -99,9 +99,9 @@ GLOBAL_VAR(antag_prototypes) var/out = "[name][(current && (current.real_name != name))?" (as [current.real_name])":""]
" out += "Mind currently owned by key: [key] [active?"(synced)":"(not synced)"]
" - out += "Assigned role: [assigned_role.title]. Edit
" + out += "Assigned role: [assigned_role.title]. Edit
" out += "Faction and special role: [special_role]
" - out += "Show Teams

" + out += "Show Teams

" var/special_statuses = get_special_statuses() if(length(special_statuses)) @@ -138,7 +138,7 @@ GLOBAL_VAR(antag_prototypes) continue //Let's skip subtypes of what we already shown. else if(prototype.show_in_antagpanel) if(prototype.can_be_owned(src)) - possible_admin_antags += "[prototype.name]" + possible_admin_antags += "[prototype.name]" else possible_admin_antags += "[prototype.name]" else @@ -155,8 +155,8 @@ GLOBAL_VAR(antag_prototypes) else //Show removal and current one priority_sections |= antag_category antag_header_parts += span_bad("[current_antag.name]") - antag_header_parts += "Remove" - antag_header_parts += "Open VV" + antag_header_parts += "Remove" + antag_header_parts += "Open VV" //We aren't antag of this category, grab first prototype to check the prefs (This is pretty vague but really not sure how else to do this) @@ -196,19 +196,19 @@ GLOBAL_VAR(antag_prototypes) var/datum/component/uplink/U = find_syndicate_uplink() if(U) if(!U.uplink_handler.has_objectives) - uplink_info += "take" + uplink_info += "take" if (check_rights(R_FUN, 0)) - uplink_info += ", [U.uplink_handler.telecrystals] TC" + uplink_info += ", [U.uplink_handler.telecrystals] TC" if(U.uplink_handler.has_progression) - uplink_info += ", [U.uplink_handler.progression_points] PR" + uplink_info += ", [U.uplink_handler.progression_points] PR" if(U.uplink_handler.has_objectives) - uplink_info += ", Force Give Objective" + uplink_info += ", Force Give Objective" else uplink_info += ", [U.uplink_handler.telecrystals] TC" if(U.uplink_handler.has_progression) uplink_info += ", [U.uplink_handler.progression_points] PR" else - uplink_info += "give" + uplink_info += "give" uplink_info += "." //hiel grammar out += uplink_info + "
" diff --git a/code/modules/admin/check_antagonists.dm b/code/modules/admin/check_antagonists.dm index 04db519ffb50a..30071504c9199 100644 --- a/code/modules/admin/check_antagonists.dm +++ b/code/modules/admin/check_antagonists.dm @@ -5,9 +5,9 @@ if(!owner) return "Unassigned" if(owner.current) - return "[owner.current.real_name] " + return "[owner.current.real_name] " else - return "[owner.name] " + return "[owner.name] " //Whatever interesting things happened to the antag admins should know about //Include additional information about antag in this part @@ -28,12 +28,12 @@ if(!owner) return var/list/parts = list() - parts += "PM" + parts += "PM" if(owner.current) //There's body to follow - parts += "FLW" + parts += "FLW" else parts += "" - parts += "Show Objective" + parts += "Show Objective" return parts //Better as one cell or two/three //Builds table row for the antag @@ -95,26 +95,26 @@ tgui_alert(usr, "The game hasn't started yet!") return var/list/dat = list("Round Status

Round Status

") - dat += "Game Mode Panel
" + dat += "Game Mode Panel
" dat += "Round Duration: [DisplayTimeText(world.time - SSticker.round_start_time)]
" dat += "Emergency shuttle
" if(EMERGENCY_IDLE_OR_RECALLED) - dat += "Call Shuttle
" + dat += "Call Shuttle
" else var/timeleft = SSshuttle.emergency.timeLeft() if(SSshuttle.emergency.mode == SHUTTLE_CALL) - dat += "ETA: [(timeleft / 60) % 60]:[add_leading(num2text(timeleft % 60), 2, "0")]
" - dat += "Send Back
" + dat += "ETA: [(timeleft / 60) % 60]:[add_leading(num2text(timeleft % 60), 2, "0")]
" + dat += "Send Back
" else - dat += "ETA: [(timeleft / 60) % 60]:[add_leading(num2text(timeleft % 60), 2, "0")]
" - dat += "End Round Now
" + dat += "ETA: [(timeleft / 60) % 60]:[add_leading(num2text(timeleft % 60), 2, "0")]
" + dat += "End Round Now
" if(SSticker.delay_end) - dat += "Undelay Round End
" + dat += "Undelay Round End
" else - dat += "Delay Round End
" - dat += "Enable/Disable CTF
" - dat += "Reboot World
" - dat += "Check Teams" + dat += "Delay Round End
" + dat += "Enable/Disable CTF
" + dat += "Reboot World
" + dat += "Check Teams" var/connected_players = GLOB.clients.len var/lobby_players = 0 var/observers = 0 diff --git a/code/modules/admin/known_alts.dm b/code/modules/admin/known_alts.dm index 4105c7f4edc93..3c51ca68bdf1b 100644 --- a/code/modules/admin/known_alts.dm +++ b/code/modules/admin/known_alts.dm @@ -168,7 +168,7 @@ GLOBAL_DATUM_INIT(known_alts, /datum/known_alts, new) var/list/known_alts_html = list() for (var/known_alt in load_known_alts()) - known_alts_html += "\[-\] Delete [known_alt[1]] is an alt of [known_alt[2]] (added by [known_alt[3]])." + known_alts_html += "\[-\] Delete [known_alt[1]] is an alt of [known_alt[2]] (added by [known_alt[3]])." var/html = {" @@ -179,7 +179,7 @@ GLOBAL_DATUM_INIT(known_alts, /datum/known_alts, new)

Any two ckeys in this panel will not show in "banned connection history".

Sometimes players switch account, and it's customary to perma-ban the old one.

-

All Known Alts:

\[+\] Add
+

All Known Alts:

\[+\] Add
[known_alts_html.Join("
")] diff --git a/code/modules/admin/permissionedit.dm b/code/modules/admin/permissionedit.dm index 6bd97dcaa2005..e474fcff0955d 100644 --- a/code/modules/admin/permissionedit.dm +++ b/code/modules/admin/permissionedit.dm @@ -7,11 +7,11 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm return var/datum/asset/asset_cache_datum = get_asset_datum(/datum/asset/group/permissions) asset_cache_datum.send(usr) - var/list/output = list("\[Permissions\]") + var/list/output = list("\[Permissions\]") if(action) - output += " | \[Log\] | \[Management\]
" + output += " | \[Log\] | \[Management\]
" else - output += "
\[Log\]
\[Management\]" + output += "
\[Log\]
\[Management\]" if(action == 1) var/logcount = 0 var/logssperpage = 20 @@ -30,7 +30,7 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm if(logcount > logssperpage) output += "
Page: " while(logcount > 0) - output += "|[pagecount == page ? "\[[pagecount]\]" : "\[[pagecount]\]"]" + output += "|[pagecount == page ? "\[[pagecount]\]" : "\[[pagecount]\]"]" logcount -= logssperpage pagecount++ output += "|" @@ -69,7 +69,7 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm while(query_check_admin_errors.NextRow()) var/admin_key = query_check_admin_errors.item[1] var/admin_rank = query_check_admin_errors.item[2] - output += "[admin_key] has non-existent rank [admin_rank] | \[Change Rank\] | \[Remove\]" + output += "[admin_key] has non-existent rank [admin_rank] | \[Change Rank\] | \[Remove\]" output += "
" qdel(query_check_admin_errors) output += "

Unused ranks

" @@ -79,7 +79,7 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm return while(query_check_unused_rank.NextRow()) var/admin_rank = query_check_unused_rank.item[1] - output += {"Rank [admin_rank] is not held by any admin | \[Remove\] + output += {"Rank [admin_rank] is not held by any admin | \[Remove\]
Permissions: [rights2text(text2num(query_check_unused_rank.item[2])," ")]
Denied: [rights2text(text2num(query_check_unused_rank.item[3])," ", "-")]
Allowed to edit: [rights2text(text2num(query_check_unused_rank.item[4])," ", "*")] @@ -95,7 +95,7 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm
- + @@ -110,18 +110,18 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm if(D.owner) adm_ckey = D.owner.key if (D.deadmined) - deadminlink = " \[RA\]" + deadminlink = " \[RA\]" else - deadminlink = " \[DA\]" + deadminlink = " \[DA\]" var/verify_link = "" if (D.blocked_by_2fa) - verify_link += " | \[2FA VERIFY\]" + verify_link += " | \[2FA VERIFY\]" output += "" - output += "" - output += "" - output += "" + output += "" + output += "" + output += "" output += "" output += "
CKEY \[+\]CKEY \[+\] RANK PERMISSIONS
[adm_ckey]
[deadminlink]\[-\]\[SYNC TGDB\][verify_link]
[D.rank_names()][rights2text(D.rank_flags(), " ")][adm_ckey]
[deadminlink]\[-\]\[SYNC TGDB\][verify_link]
[D.rank_names()][rights2text(D.rank_flags(), " ")]
Search:
" if(QDELETED(usr)) @@ -140,7 +140,7 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm permissions_assets.send(usr.client) var/admin_key = href_list["key"] var/admin_ckey = ckey(admin_key) - + var/task = href_list["editrights"] var/datum/admins/target_admin_datum = GLOB.admin_datums[admin_ckey] if(!target_admin_datum) @@ -178,7 +178,7 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm use_db = FALSE if(QDELETED(usr)) return - + if(target_admin_datum && (task != "sync" && task != "verify") && !check_if_greater_rights_than_holder(target_admin_datum)) message_admins("[key_name_admin(usr)] attempted to change the rank of [admin_key] without sufficient rights.") log_admin("[key_name(usr)] attempted to change the rank of [admin_key] without sufficient rights.") diff --git a/code/modules/admin/player_panel.dm b/code/modules/admin/player_panel.dm index 31c34957544e4..ddb02b78b8acc 100644 --- a/code/modules/admin/player_panel.dm +++ b/code/modules/admin/player_panel.dm @@ -80,19 +80,19 @@ body += ""; - body += "PP - " - body += "N - " - body += "VV - " - body += "SP - " - body += "TP - " + body += "PP - " + body += "N - " + body += "VV - " + body += "SP - " + body += "TP - " if (job == "Cyborg") - body += "BP - " - body += "PM - " - body += "SM - " - body += "FLW - " - body += "LOGS
" + body += "BP - " + body += "PM - " + body += "SM - " + body += "FLW - " + body += "LOGS
" if(antagonist > 0) - body += "Antagonist"; + body += "Antagonist"; body += ""; @@ -198,7 +198,7 @@ Player panel
- Hover over a line to see more information - Check antagonists - Kick everyone/AFKers in lobby + Hover over a line to see more information - Check antagonists - Kick everyone/AFKers in lobby

diff --git a/code/modules/admin/poll_management.dm b/code/modules/admin/poll_management.dm index 86f075d311340..88a38c90166b4 100644 --- a/code/modules/admin/poll_management.dm +++ b/code/modules/admin/poll_management.dm @@ -71,12 +71,12 @@ * */ /datum/admins/proc/poll_list_panel() - var/list/output = list("Current and future polls
Note when editing polls or their options changes are not saved until you press Submit Poll.
New PollReload Polls


") + var/list/output = list("Current and future polls
Note when editing polls or their options changes are not saved until you press Submit Poll.
New PollReload Polls
") for(var/p in GLOB.polls) var/datum/poll_question/poll = p output += {"[poll.question] - Edit - Delete + Edit + Delete "} if(poll.subtitle) output += "
[poll.subtitle]" @@ -204,20 +204,20 @@
"} if(poll.poll_type == POLLTYPE_TEXT) - output += "Clear poll responses [poll.poll_votes] players have responded" + output += "Clear poll responses [poll.poll_votes] players have responded" else - output += "Clear poll votes [poll.poll_votes] players have voted" + output += "Clear poll votes [poll.poll_votes] players have voted" if(poll.poll_type == POLLTYPE_TEXT) output += "" else - output += "
Add Option
" + output += "
Add Option
" if(length(poll.options)) for(var/o in poll.options) var/datum/poll_option/option = o option_count++ output += {"Option [option_count] - Edit - Delete + Edit + Delete
[option.text] "} if(poll.poll_type == POLLTYPE_RATING) diff --git a/code/modules/admin/sound_emitter.dm b/code/modules/admin/sound_emitter.dm index 9f1d430a46c03..165b882ab46e7 100644 --- a/code/modules/admin/sound_emitter.dm +++ b/code/modules/admin/sound_emitter.dm @@ -61,16 +61,16 @@ /obj/effect/sound_emitter/proc/edit_emitter(mob/user) var/dat = "" - dat += "Label: [maptext ? maptext : "No label set!"]
" + dat += "Label: [maptext ? maptext : "No label set!"]
" dat += "
" - dat += "Sound File: [sound_file ? sound_file : "No file chosen!"]
" - dat += "Volume: [sound_volume]%
" + dat += "Sound File: [sound_file ? sound_file : "No file chosen!"]
" + dat += "Volume: [sound_volume]%
" dat += "
" - dat += "Mode: [motus_operandi]
" + dat += "Mode: [motus_operandi]
" if(motus_operandi != SOUND_EMITTER_LOCAL) - dat += "Range: [emitter_range][emitter_range == SOUND_EMITTER_RADIUS ? "[play_radius]-tile radius" : ""]
" + dat += "Range: [emitter_range][emitter_range == SOUND_EMITTER_RADIUS ? "[play_radius]-tile radius" : ""]
" dat += "
" - dat += "Play Sound (interrupts other sound emitter sounds)" + dat += "Play Sound (interrupts other sound emitter sounds)" var/datum/browser/popup = new(user, "emitter", "", 500, 600) popup.set_content(dat) popup.open() diff --git a/code/modules/admin/sql_ban_system.dm b/code/modules/admin/sql_ban_system.dm index 74955324dffd4..a8f16e92daa11 100644 --- a/code/modules/admin/sql_ban_system.dm +++ b/code/modules/admin/sql_ban_system.dm @@ -689,7 +689,7 @@ var/pagecount = 1 var/list/pagelist = list() while(bancount > 0) - pagelist += "[pagecount == page ? "\[[pagecount]\]" : "\[[pagecount]\]"]" + pagelist += "[pagecount == page ? "\[[pagecount]\]" : "\[[pagecount]\]"]" bancount -= bansperpage pagecount++ output += pagelist.Join(" | ") @@ -775,13 +775,13 @@ var/un_or_reban_href if(unban_datetime) - un_or_reban_href = "Reban" + un_or_reban_href = "Reban" else - un_or_reban_href = "Unban" - output += "Edit
[un_or_reban_href]" + un_or_reban_href = "Unban" + output += "Edit
[un_or_reban_href]" if(edits) - output += "
Edit log" + output += "
Edit log" output += "" qdel(query_unban_search_bans) output += "" diff --git a/code/modules/admin/sql_message_system.dm b/code/modules/admin/sql_message_system.dm index 7aab49d1c3649..f7d0023534529 100644 --- a/code/modules/admin/sql_message_system.dm +++ b/code/modules/admin/sql_message_system.dm @@ -377,10 +377,10 @@ var/list/output = list() var/ruler = "
" - var/list/navbar = list("All#") + var/list/navbar = list("All#") for(var/letter in GLOB.alphabet) - navbar += "[letter]" - navbar += "MemosWatchlist" + navbar += "[letter]" + navbar += "MemosWatchlist" navbar += "
\ \ [HrefTokenFormField()]\ @@ -391,14 +391,14 @@ if(type == "memo" || type == "watchlist entry") if(type == "memo") output += "

Admin memos

" - output += "Add memo
" + output += "Add memo" else if(type == "watchlist entry") output += "

Watchlist entries

" - output += "Add watchlist entry" + output += "Add watchlist entry" if(filter) - output += "Unfilter clients" + output += "Unfilter clients" else - output += "Filter offline clients" + output += "Filter offline clients" output += ruler var/datum/db_query/query_get_type_messages = SSdbcore.NewQuery({" SELECT @@ -444,11 +444,11 @@ if(expire_timestamp) output += " | Expires [expire_timestamp]" output += "" - output += " Change Expiry Time" - output += " Delete" - output += " Edit" + output += " Change Expiry Time" + output += " Delete" + output += " Edit" if(editor_key) - output += " Last edit by [editor_key] (Click here to see edit log)" + output += " Last edit by [editor_key] (Click here to see edit log)" output += "
[text]
" qdel(query_get_type_messages) if(target_ckey) @@ -524,21 +524,21 @@ if(!linkless) if(type == "note") if(severity) - data += "[severity == "none" ? "No" : "[capitalize(severity)]"] Severity" + data += "[severity == "none" ? "No" : "[capitalize(severity)]"] Severity" else - data += "N/A Severity" - data += " Change Expiry Time" - data += " Delete" + data += "N/A Severity" + data += " Change Expiry Time" + data += " Delete" if(type == "note") - data += " [secret ? "Secret" : "Not secret"]" + data += " [secret ? "Secret" : "Not secret"]" if(type == "message sent") data += " Message has been sent" if(editor_key) data += "|" else - data += " Edit" + data += " Edit" if(editor_key) - data += " Last edit by [editor_key] (Click here to see edit log)" + data += " Last edit by [editor_key] (Click here to see edit log)" data += "" data += "

[text]


" switch(type) @@ -563,12 +563,12 @@ qdel(query_get_message_key) output += "

[target_key]

" if(!linkless) - output += "Add note" - output += " Add message" - output += " Add to watchlist" - output += " Refresh page
" + output += "Add note" + output += " Add message" + output += " Add to watchlist" + output += " Refresh page" else - output += " Refresh page" + output += " Refresh page" output += ruler if(messagedata) output += "

Messages

" @@ -582,14 +582,14 @@ if(!linkless) if (agegate) if (skipped) //the first skipped message is still shown so that we can put this link over it. - output += "
Show [skipped] hidden messages
" + output += "
Show [skipped] hidden messages
" else - output += "
Show All
" + output += "
Show All
" else - output += "
Hide Old
" + output += "
Hide Old
" if(index) var/search - output += "
Add messageAdd watchlist entryAdd note
" + output += "
Add messageAdd watchlist entryAdd note
" output += ruler switch(index) if(1) @@ -619,10 +619,10 @@ var/index_key = query_list_messages.item[2] if(!index_key) index_key = index_ckey - output += "[index_key]
" + output += "[index_key]
" qdel(query_list_messages) else if(!type && !target_ckey && !index) - output += "
Add messageAdd watchlist entryAdd note
" + output += "
Add messageAdd watchlist entryAdd note
" output += ruler var/datum/browser/browser = new(usr, "Note panel", "Manage player notes", 1000, 500) notes_assets.send(usr.client) @@ -690,7 +690,7 @@ var/list/text = list() for(var/datum/admin_message/message in get_message_output("message", display_to.ckey)) text += "Admin message left by [span_prefix("[message.admin_key]")] on [message.timestamp]" - text += "
[message.text] (Click here to verify you have read this message)
" + text += "
[message.text] (Click here to verify you have read this message)
" if(length(text)) to_chat(display_to, text.Join()) @@ -707,7 +707,7 @@ for(var/datum/admin_message/message in get_message_output("memo", display_to.ckey)) text += "[span_memo("Memo by [message.admin_key]")] on [message.timestamp]" if(message.editor_key) - text += "
[span_memoedit("Last edit by [message.editor_key] (Click here to see edit log)")]" + text += "
[span_memoedit("Last edit by [message.editor_key] (Click here to see edit log)")]" text += "
[message.text]

" if(length(text)) to_chat(display_to, text.Join()) diff --git a/code/modules/admin/stickyban.dm b/code/modules/admin/stickyban.dm index fede9724ab181..4295080c3f6ca 100644 --- a/code/modules/admin/stickyban.dm +++ b/code/modules/admin/stickyban.dm @@ -348,15 +348,15 @@ return var/timeout if (SSdbcore.Connect()) - timeout = "\[[(ban["timeout"] ? "untimeout" : "timeout" )]\]" + timeout = "\[[(ban["timeout"] ? "untimeout" : "timeout" )]\]" else - timeout = "\[revert\]" + timeout = "\[revert\]" . = list({" - \[-\] + \[-\] [timeout] [ckey]
" - [ban["message"]] \[Edit\]
+ [ban["message"]] \[Edit\]
"}) if (ban["admin"]) . += "[ban["admin"]]
" @@ -366,12 +366,12 @@ for (var/key in ban["keys"]) if (ckey(key) == ckey) continue - . += "
  • \[-\][key]\[E\]
  • " + . += "
  • \[-\][key]\[E\]
  • " for (var/key in ban["whitelist"]) if (ckey(key) == ckey) continue - . += "
  • \[-\][key]\[UE\]
  • " + . += "
  • \[-\][key]\[UE\]
  • " . += "\n" @@ -390,7 +390,7 @@ Sticky Bans -

    All Sticky Bans:

    \[+\]
    +

    All Sticky Bans:

    \[+\]
    [banhtml.Join("")] "} diff --git a/code/modules/admin/tag.dm b/code/modules/admin/tag.dm index e52112eba1495..ec0ed3b56fa7b 100644 --- a/code/modules/admin/tag.dm +++ b/code/modules/admin/tag.dm @@ -40,8 +40,8 @@ to_chat(owner, span_warning("[target_datum] was not already tagged.")) /// Quick define for readability -#define TAG_DEL(X) "(UNTAG)" -#define TAG_MARK(X) "(MARK)" +#define TAG_DEL(X) "(UNTAG)" +#define TAG_MARK(X) "(MARK)" #define TAG_SIMPLE_HEALTH(X) "Health: [X.health]" #define TAG_CARBON_HEALTH(X) "Health: [X.health] (\ [X.getBruteLoss()] \ @@ -56,7 +56,7 @@ ADMIN_VERB(display_tags, R_ADMIN, "View Tags", "Display all of the tagged datums var/list/tagged_datums = user.holder.tagged_datums var/list/marked_datum = user.holder.marked_datum - dat += "
    Refresh
    " + dat += "
    Refresh
    " if(LAZYLEN(tagged_datums)) for(var/datum/iter_datum as anything in tagged_datums) index++ diff --git a/code/modules/admin/team_panel.dm b/code/modules/admin/team_panel.dm index 30311c491e6f2..3eba8be949417 100644 --- a/code/modules/admin/team_panel.dm +++ b/code/modules/admin/team_panel.dm @@ -3,22 +3,22 @@ var/list/content = list() for(var/datum/team/T in GLOB.antagonist_teams) content += "

    [T.name] - [T.type]

    " - content += "Rename" - content += "Delete" - content += "Communicate" + content += "Rename" + content += "Delete" + content += "Communicate" for(var/command in T.get_admin_commands()) - content += "[command]" + content += "[command]" content += "
    " content += "Objectives:
      " for(var/datum/objective/O in T.objectives) - content += "
    1. [O.explanation_text] - Remove
    2. " - content += "
    Add Objective
    " + content += "
  • [O.explanation_text] - Remove
  • " + content += "Add Objective
    " content += "Members:
      " for(var/datum/mind/M in T.members) - content += "
    • [M.name] - Remove Member
    • " - content += "
    Add Member" + content += "
  • [M.name] - Remove Member
  • " + content += "Add Member" content += "
    " - content += "Create Team
    " + content += "Create Team
    " return content.Join() diff --git a/code/modules/admin/verbs/SDQL2/SDQL_2.dm b/code/modules/admin/verbs/SDQL2/SDQL_2.dm index 4ee2b79f04459..f74df71272106 100644 --- a/code/modules/admin/verbs/SDQL2/SDQL_2.dm +++ b/code/modules/admin/verbs/SDQL2/SDQL_2.dm @@ -722,7 +722,7 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/sdql2_vv_all, new(null /datum/sdql2_query/proc/SDQL_print(object, list/text_list, print_nulls = TRUE) if(isdatum(object)) - text_list += "[REF(object)] : [object]" + text_list += "[REF(object)] : [object]" if(istype(object, /atom)) var/atom/A = object var/turf/T = A.loc diff --git a/code/modules/admin/verbs/admin.dm b/code/modules/admin/verbs/admin.dm index edd362938af58..629423e713faa 100644 --- a/code/modules/admin/verbs/admin.dm +++ b/code/modules/admin/verbs/admin.dm @@ -45,7 +45,7 @@ ADMIN_VERB(cmd_admin_check_player_exp, R_ADMIN, "Player Playtime", "View player var/list/msg = list() msg += "Playtime ReportPlaytime:
    " user << browse(msg.Join(), "window=Player_playtime_check") diff --git a/code/modules/admin/verbs/admingame.dm b/code/modules/admin/verbs/admingame.dm index f9a081445b8ea..8e7fd97cc1f8b 100644 --- a/code/modules/admin/verbs/admingame.dm +++ b/code/modules/admin/verbs/admingame.dm @@ -12,28 +12,28 @@ ADMIN_VERB_ONLY_CONTEXT_MENU(show_player_panel, R_ADMIN, "Show Player Panel", mo body += "Options panel for [player]" if(player.client) body += " played by [player.client] " - body += "\[[player.client.holder ? player.client.holder.rank_names() : "Player"]\]" + body += "\[[player.client.holder ? player.client.holder.rank_names() : "Player"]\]" if(CONFIG_GET(flag/use_exp_tracking)) - body += "\[" + player.client.get_exp_living(FALSE) + "\]" + body += "\[" + player.client.get_exp_living(FALSE) + "\]" if(isnewplayer(player)) body += " Hasn't Entered Game " else - body += " \[Heal\] " + body += " \[Heal\] " if(player.ckey) - body += "
    \[Find Updated Panel\]" + body += "
    \[Find Updated Panel\]" if(player.client) body += "
    \[First Seen: [player.client.player_join_date]\]\[Byond account registered on: [player.client.account_join_date]\]" body += "

    CentCom Galactic Ban DB: " if(CONFIG_GET(string/centcom_ban_db)) - body += "Search" + body += "Search" else body += "Disabled" body += "

    Show related accounts by: " - body += "\[ CID | " - body += "IP \]" + body += "\[ CID | " + body += "IP \]" var/full_version = "Unknown" if(player.client.byond_version) full_version = "[player.client.byond_version].[player.client.byond_build ? player.client.byond_build : "xxx"]" @@ -41,24 +41,24 @@ ADMIN_VERB_ONLY_CONTEXT_MENU(show_player_panel, R_ADMIN, "Show Player Panel", mo body += "

    \[ " - body += "VV - " + body += "VV - " if(player.mind) - body += "TP - " - body += "SKILLS - " + body += "TP - " + body += "SKILLS - " else - body += "Init Mind - " + body += "Init Mind - " if (iscyborg(player)) - body += "BP - " - body += "PM - " - body += "SM - " + body += "BP - " + body += "PM - " + body += "SM - " if (ishuman(player) && player.mind) - body += "HM - " - body += "FLW - " + body += "HM - " + body += "FLW - " //Default to client logs if available var/source = LOGSRC_MOB if(player.ckey) source = LOGSRC_CKEY - body += "LOGS\]
    " + body += "LOGS\]
    " body += "Mob type = [player.type]

    " @@ -71,37 +71,37 @@ ADMIN_VERB_ONLY_CONTEXT_MENU(show_player_panel, R_ADMIN, "Show Player Panel", mo body += "None?!" body += "

    " - body += "Kick | " + body += "Kick | " if(player.client) - body += "Ban | " + body += "Ban | " else - body += "Ban | " + body += "Ban | " - body += "Notes | Messages | Watchlist | " + body += "Notes | Messages | Watchlist | " if(player.client) - body += "| Prison | " - body += "\ Send back to Lobby | " + body += "| Prison | " + body += "\ Send back to Lobby | " var/muted = player.client.prefs.muted body += "
    Mute: " - body += "\[IC | " - body += "OOC | " - body += "PRAY | " - body += "ADMINHELP | " - body += "WEBREQ | " - body += "DEADCHAT\]" - body += "(toggle all)" + body += "\[IC | " + body += "OOC | " + body += "PRAY | " + body += "ADMINHELP | " + body += "WEBREQ | " + body += "DEADCHAT\]" + body += "(toggle all)" body += "

    " - body += "Jump to | " - body += "Get | " - body += "Send To" + body += "Jump to | " + body += "Get | " + body += "Send To" body += "

    " - body += "Traitor panel | " - body += "Narrate to | " - body += "Subtle message | " - body += "Play sound to | " - body += "Language Menu" + body += "Traitor panel | " + body += "Narrate to | " + body += "Subtle message | " + body += "Play sound to | " + body += "Language Menu" if(player.client) if(!isnewplayer(player)) @@ -110,39 +110,39 @@ ADMIN_VERB_ONLY_CONTEXT_MENU(show_player_panel, R_ADMIN, "Show Player Panel", mo if(isobserver(player)) body += "Ghost | " else - body += "Make Ghost | " + body += "Make Ghost | " if(ishuman(player) && !ismonkey(player)) body += "Human | " else - body += "Make Human | " + body += "Make Human | " if(ismonkey(player)) body += "Monkey | " else - body += "Make Monkey | " + body += "Make Monkey | " if(iscyborg(player)) body += "Cyborg | " else - body += "Make Cyborg | " + body += "Make Cyborg | " if(isAI(player)) body += "AI" else - body += "Make AI" + body += "Make AI" body += "

    " body += "Other actions:" body += "
    " if(!isnewplayer(player)) - body += "Forcesay | " - body += "Apply Client Quirks | " - body += "Thunderdome 1 | " - body += "Thunderdome 2 | " - body += "Thunderdome Admin | " - body += "Thunderdome Observer | " - body += "Commend Behavior | " + body += "Forcesay | " + body += "Apply Client Quirks | " + body += "Thunderdome 1 | " + body += "Thunderdome 2 | " + body += "Thunderdome Admin | " + body += "Thunderdome Observer | " + body += "Commend Behavior | " body += "
    " body += "" @@ -325,15 +325,15 @@ ADMIN_VERB(manage_job_slots, R_ADMIN, "Manage Job Slots", "Manage the number of dat += "" dat += "" if(job.total_positions >= 0) - dat += "Custom | " - dat += "Add 1 | " + dat += "Custom | " + dat += "Add 1 | " if(job.total_positions > job.current_positions) - dat += "Remove | " + dat += "Remove | " else dat += "Remove | " - dat += "Unlimit" + dat += "Unlimit" else - dat += "Limit" + dat += "Limit" browser.height = min(100 + count * 20, 650) browser.set_content(dat.Join()) @@ -417,20 +417,20 @@ ADMIN_VERB(lag_switch_panel, R_ADMIN, "Show Lag Switches", "Display the controls to_chat(user, span_notice("The Lag Switch subsystem has not yet been initialized.")) return var/list/dat = list("Lag Switches

    Lag (Reduction) Switches

    ") - dat += "Automatic Trigger: [SSlag_switch.auto_switch ? "On" : "Off"]
    " - dat += "Population Threshold: [SSlag_switch.trigger_pop]
    " - dat += "Slowmode Cooldown (toggle On/Off below): [SSlag_switch.slowmode_cooldown/10] seconds
    " - dat += "
    SET ALL MEASURES: ON | OFF
    " - dat += "
    Disable ghosts zoom and t-ray verbs (except staff): [SSlag_switch.measures[DISABLE_GHOST_ZOOM_TRAY] ? "On" : "Off"]
    " - dat += "Disable late joining: [SSlag_switch.measures[DISABLE_NON_OBSJOBS] ? "On" : "Off"]
    " + dat += "Automatic Trigger: [SSlag_switch.auto_switch ? "On" : "Off"]
    " + dat += "Population Threshold: [SSlag_switch.trigger_pop]
    " + dat += "Slowmode Cooldown (toggle On/Off below): [SSlag_switch.slowmode_cooldown/10] seconds
    " + dat += "
    SET ALL MEASURES: ON | OFF
    " + dat += "
    Disable ghosts zoom and t-ray verbs (except staff): [SSlag_switch.measures[DISABLE_GHOST_ZOOM_TRAY] ? "On" : "Off"]
    " + dat += "Disable late joining: [SSlag_switch.measures[DISABLE_NON_OBSJOBS] ? "On" : "Off"]
    " dat += "
    ============! MAD GHOSTS ZONE !============
    " - dat += "Disable deadmob keyLoop (except staff, informs dchat): [SSlag_switch.measures[DISABLE_DEAD_KEYLOOP] ? "On" : "Off"]
    " + dat += "Disable deadmob keyLoop (except staff, informs dchat): [SSlag_switch.measures[DISABLE_DEAD_KEYLOOP] ? "On" : "Off"]
    " dat += "==========================================
    " dat += "
    Measures below can be bypassed with a special trait
    " - dat += "Slowmode say verb (informs world): [SSlag_switch.measures[SLOWMODE_SAY] ? "On" : "Off"]
    " - dat += "Disable runechat: [SSlag_switch.measures[DISABLE_RUNECHAT] ? "On" : "Off"] - trait applies to speaker
    " - dat += "Disable examine icons: [SSlag_switch.measures[DISABLE_USR_ICON2HTML] ? "On" : "Off"] - trait applies to examiner
    " - dat += "Disable parallax: [SSlag_switch.measures[DISABLE_PARALLAX] ? "On" : "Off"] - trait applies to character
    " - dat += "Disable footsteps: [SSlag_switch.measures[DISABLE_FOOTSTEPS] ? "On" : "Off"] - trait applies to character
    " + dat += "Slowmode say verb (informs world): [SSlag_switch.measures[SLOWMODE_SAY] ? "On" : "Off"]
    " + dat += "Disable runechat: [SSlag_switch.measures[DISABLE_RUNECHAT] ? "On" : "Off"] - trait applies to speaker
    " + dat += "Disable examine icons: [SSlag_switch.measures[DISABLE_USR_ICON2HTML] ? "On" : "Off"] - trait applies to examiner
    " + dat += "Disable parallax: [SSlag_switch.measures[DISABLE_PARALLAX] ? "On" : "Off"] - trait applies to character
    " + dat += "Disable footsteps: [SSlag_switch.measures[DISABLE_FOOTSTEPS] ? "On" : "Off"] - trait applies to character
    " dat += "" user << browse(dat.Join(), "window=lag_switch_panel;size=420x480") diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm index 4ff36ec2d0130..da09e19d0debe 100644 --- a/code/modules/admin/verbs/adminhelp.dm +++ b/code/modules/admin/verbs/adminhelp.dm @@ -84,10 +84,10 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) if(!l2b) return var/list/dat = list("[title]") - dat += "Refresh

    " + dat += "Refresh

    " for(var/I in l2b) var/datum/admin_help/AH = I - dat += "[span_adminnotice("[span_adminhelp("Ticket #[AH.id]")]: [AH.initiator_key_name]: [AH.name]")]
    " + dat += "[span_adminnotice("[span_adminhelp("Ticket #[AH.id]")]: [AH.initiator_key_name]: [AH.name]")]
    " usr << browse(dat.Join(), "window=ahelp_list[state];size=600x480") @@ -370,32 +370,32 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) if(!ref_src) ref_src = "[REF(src)]" . = ADMIN_FULLMONTY_NONAME(initiator.mob) - . += " (NOTES)" + . += " (NOTES)" if(state == AHELP_ACTIVE) if (CONFIG_GET(flag/popup_admin_pm)) - . += " (POPUP)" + . += " (POPUP)" . += ClosureLinks(ref_src) //private /datum/admin_help/proc/ClosureLinks(ref_src) if(!ref_src) ref_src = "[REF(src)]" - . = " (REJT)" - . += " (IC)" - . += " (CLOSE)" - . += " (RSLVE)" + . = " (REJT)" + . += " (IC)" + . += " (CLOSE)" + . += " (RSLVE)" //private /datum/admin_help/proc/LinkedReplyName(ref_src) if(!ref_src) ref_src = "[REF(src)]" - return "[initiator_key_name]" + return "[initiator_key_name]" //private /datum/admin_help/proc/TicketHref(msg, ref_src, action = "ticket") if(!ref_src) ref_src = "[REF(src)]" - return "[msg]" + return "[msg]" //message from the initiator without a target, all admins will see this //won't bug irc/discord @@ -580,7 +580,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) // Helper for opening directly to player ticket history dat += "

    Player Ticket History:" - dat += "[FOURSPACES]Open" + dat += "[FOURSPACES]Open" // Append any tickets also opened by this user if relevant var/list/related_tickets = GLOB.ahelp_tickets.TicketsByCKey(initiator_ckey) @@ -664,7 +664,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) dat += "CLOSED" else dat += "UNKNOWN" - dat += "\n[FOURSPACES]Refresh" + dat += "\n[FOURSPACES]Refresh" dat += "

    Opened at: [gameTimestamp("hh:mm:ss", opened_at)] (Approx [DisplayTimeText(world.time - opened_at)] ago)" if(closed_at) dat += "
    Closed at: [gameTimestamp("hh:mm:ss", closed_at)] (Approx [DisplayTimeText(world.time - closed_at)] ago)" @@ -806,7 +806,7 @@ GLOBAL_DATUM_INIT(admin_help_ui_handler, /datum/admin_help_ui_handler, new) set category = "Admin" set name = "Adminhelp" GLOB.admin_help_ui_handler.ui_interact(mob) - to_chat(src, span_boldnotice("Adminhelp failing to open or work? Click here")) + to_chat(src, span_boldnotice("Adminhelp failing to open or work? Click here")) /client/verb/view_latest_ticket() set category = "Admin" @@ -1025,7 +1025,7 @@ GLOBAL_DATUM_INIT(admin_help_ui_handler, /datum/admin_help_ui_handler, new) if(is_special_character(found)) is_antag = 1 founds += "Name: [found.name]([found.real_name]) Key: [found.key] Ckey: [found.ckey] [is_antag ? "(Antag)" : null] " - msg += "[original_word](?|F) " + msg += "[original_word](?|F) " continue msg += "[original_word] " if(external) @@ -1108,7 +1108,7 @@ GLOBAL_DATUM_INIT(admin_help_ui_handler, /datum/admin_help_ui_handler, new) var/datum/datum_check = locate(word_with_brackets) if(!istype(datum_check)) continue - msglist[i] = "[word]" + msglist[i] = "[word]" modified = TRUE if("#") // check if we're linking a ticket @@ -1129,7 +1129,7 @@ GLOBAL_DATUM_INIT(admin_help_ui_handler, /datum/admin_help_ui_handler, new) if(AHELP_RESOLVED) state_word = "Resolved" - msglist[i]= "[word] ([state_word] | [ahelp_check.initiator_key_name])" + msglist[i]= "[word] ([state_word] | [ahelp_check.initiator_key_name])" modified = TRUE if(modified) diff --git a/code/modules/admin/verbs/adminpm.dm b/code/modules/admin/verbs/adminpm.dm index 4b668082d4ba1..5d2598fc0f7d5 100644 --- a/code/modules/admin/verbs/adminpm.dm +++ b/code/modules/admin/verbs/adminpm.dm @@ -699,7 +699,7 @@ ADMIN_VERB(cmd_admin_pm_panel, R_NONE, "Admin PM", "Show a list of clients to PM log_admin_private("External PM: [sender] -> [recipient_name] : [message]") recipient.receive_ahelp( - "[adminname]", + "[adminname]", message, ) diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index 6169527b57f76..804613f3f1b36 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -480,8 +480,8 @@ ADMIN_VERB(modify_goals, R_ADMIN, "Modify Goals", "Modify the station goals for /datum/admins/proc/modify_goals() var/dat = "" for(var/datum/station_goal/goal as anything in SSstation.get_station_goals()) - dat += "[goal.name] - Announce | Remove
    " - dat += "
    Add New Goal" + dat += "[goal.name] - Announce | Remove
    " + dat += "
    Add New Goal" usr << browse(dat, "window=goals;size=400x400") ADMIN_VERB(debug_mob_lists, R_DEBUG, "Debug Mob Lists", "For when you just gotta know.", ADMIN_CATEGORY_DEBUG) diff --git a/code/modules/admin/verbs/individual_logging.dm b/code/modules/admin/verbs/individual_logging.dm index d9df055ba1bb3..eb665d35f658e 100644 --- a/code/modules/admin/verbs/individual_logging.dm +++ b/code/modules/admin/verbs/individual_logging.dm @@ -75,4 +75,4 @@ slabel = "\[[label]\]" //This is necessary because num2text drops digits and rounds on big numbers. If more defines get added in the future it could break again. log_type = num2text(log_type, MAX_BITFLAG_DIGITS) - return "[slabel]" + return "[slabel]" diff --git a/code/modules/admin/verbs/map_template_loadverb.dm b/code/modules/admin/verbs/map_template_loadverb.dm index a27aca0f0147b..3251e9febd27c 100644 --- a/code/modules/admin/verbs/map_template_loadverb.dm +++ b/code/modules/admin/verbs/map_template_loadverb.dm @@ -61,8 +61,8 @@ ADMIN_VERB(map_template_upload, R_DEBUG, "Map Template - Upload", "Upload a map var/report_link if(report) report.show_to(user) - report_link = " - validation report" - to_chat(user, span_warning("Map template '[map]' failed validation."), confidential = TRUE) + report_link = " - validation report" + to_chat(user, span_warning("Map template '[map]' failed validation."), confidential = TRUE) if(report.loadable) var/response = tgui_alert(user, "The map failed validation, would you like to load it anyways?", "Map Errors", list("Cancel", "Upload Anyways")) if(response != "Upload Anyways") diff --git a/code/modules/admin/verbs/mapping.dm b/code/modules/admin/verbs/mapping.dm index 277b7ad36d541..73196dbcdb1b6 100644 --- a/code/modules/admin/verbs/mapping.dm +++ b/code/modules/admin/verbs/mapping.dm @@ -84,7 +84,7 @@ ADMIN_VERB(show_map_reports, R_DEBUG, "Show Map Reports", "Displays a list of ma var/dat = {"List of all map reports:
    "} for(var/datum/map_report/report as anything in GLOB.map_reports) - dat += "[report.tag] ([report.original_path]) - View
    " + dat += "[report.tag] ([report.original_path]) - View
    " user << browse(dat, "window=map_reports") diff --git a/code/modules/admin/view_variables/debug_variable_appearance.dm b/code/modules/admin/view_variables/debug_variable_appearance.dm index c5a367e83a064..537ef5e611b51 100644 --- a/code/modules/admin/view_variables/debug_variable_appearance.dm +++ b/code/modules/admin/view_variables/debug_variable_appearance.dm @@ -23,7 +23,7 @@ display_value = "[display_value]:[icon_state]" var/display_ref = get_vv_link_ref() - return "[display_name] ([display_value]) [display_ref]" + return "[display_name] ([display_value]) [display_ref]" /// Returns the ref string to use when displaying this image in the vv menu of something else /image/proc/get_vv_link_ref() diff --git a/code/modules/admin/view_variables/debug_variables.dm b/code/modules/admin/view_variables/debug_variables.dm index 835da1a0b39cb..b776f1b5d7974 100644 --- a/code/modules/admin/view_variables/debug_variables.dm +++ b/code/modules/admin/view_variables/debug_variables.dm @@ -18,10 +18,10 @@ var/name_part = VV_HTML_ENCODE(name) if(level > 0 || islist(owner)) //handling keys in assoc lists if(istype(name,/datum)) - name_part = "[VV_HTML_ENCODE(name)] [REF(name)]" + name_part = "[VV_HTML_ENCODE(name)] [REF(name)]" else if(islist(name)) var/list/list_value = name - name_part = " /list ([length(list_value)]) [REF(name)]" + name_part = " /list ([length(list_value)]) [REF(name)]" . = "[.][name_part] = " @@ -85,9 +85,9 @@ items += debug_variable(key, val, level + 1, sanitize = sanitize) - return "/list ([list_value.len])
      [items.Join()]
    " + return "/list ([list_value.len])
      [items.Join()]
    " else - return "/list ([list_value.len])" + return "/list ([list_value.len])" if(name in GLOB.bitfields) var/list/flags = list() @@ -103,13 +103,13 @@ /datum/proc/debug_variable_value(name, level, datum/owner, sanitize, display_flags) if("[src]" != "[type]") // If we have a name var, let's use it. - return "[src] [type] [REF(src)]" + return "[src] [type] [REF(src)]" else - return "[type] [REF(src)]" + return "[type] [REF(src)]" /datum/weakref/debug_variable_value(name, level, datum/owner, sanitize, display_flags) . = ..() - return "[.] (Resolve)" + return "[.] (Resolve)" /matrix/debug_variable_value(name, level, datum/owner, sanitize, display_flags) return {" diff --git a/code/modules/admin/view_variables/topic_basic.dm b/code/modules/admin/view_variables/topic_basic.dm index 4f36365312701..52b87fda12512 100644 --- a/code/modules/admin/view_variables/topic_basic.dm +++ b/code/modules/admin/view_variables/topic_basic.dm @@ -37,7 +37,7 @@ if(!target) to_chat(usr, span_warning("The object you tried to expose to [C] no longer exists (nulled or hard-deled)"), confidential = TRUE) return - message_admins("[key_name_admin(usr)] Showed [key_name_admin(C)] a VV window") + message_admins("[key_name_admin(usr)] Showed [key_name_admin(C)] a VV window") log_admin("Admin [key_name(usr)] Showed [key_name(C)] a VV window of a [target]") to_chat(C, "[holder.fakekey ? "an Administrator" : "[usr.client.key]"] has granted you access to view a View Variables window", confidential = TRUE) C.debug_variables(target) diff --git a/code/modules/admin/view_variables/view_variables.dm b/code/modules/admin/view_variables/view_variables.dm index 66ac70f3f62f7..fbebccd445981 100644 --- a/code/modules/admin/view_variables/view_variables.dm +++ b/code/modules/admin/view_variables/view_variables.dm @@ -257,7 +257,7 @@ ADMIN_VERB_AND_CONTEXT_MENU(debug_variables, R_NONE, "View Variables", "View the
    - Refresh