diff --git a/code/__DEFINES/tools.dm b/code/__DEFINES/tools.dm
index a1504265fa5..76031d5a383 100644
--- a/code/__DEFINES/tools.dm
+++ b/code/__DEFINES/tools.dm
@@ -18,6 +18,8 @@
#define TOOL_KNIFE "knife"
#define TOOL_BLOODFILTER "bloodfilter"
#define TOOL_ROLLINGPIN "rollingpin"
+/// Can be used to scrape rust off an any atom; which will result in the Rust Component being qdel'd
+#define TOOL_RUSTSCRAPER "rustscraper"
// If delay between the start and the end of tool operation is less than MIN_TOOL_SOUND_DELAY,
// tool sound is only played when op is started. If not, it's played twice.
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index ecdf85a398e..8971cfc322f 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -566,6 +566,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define HIGHLANDER_TRAIT "highlander"
///generic atom traits
+/// Trait from [/datum/component/rust]. Its rusty and should be applying a special overlay to denote this.
+#define TRAIT_RUSTY "rust_trait"
#define DO_NOT_SPLASH "do_not_splash"
// unique trait sources, still defines
diff --git a/code/datums/components/rust.dm b/code/datums/components/rust.dm
new file mode 100644
index 00000000000..92833de3d80
--- /dev/null
+++ b/code/datums/components/rust.dm
@@ -0,0 +1,85 @@
+/**
+ * Adding this component to an Atom will have it automatically render an overlay.
+ * The overlay can be specified in new as the first and only parameter; it defaults to "rust" if not included.
+ */
+/datum/component/rust
+ dupe_mode = COMPONENT_DUPE_UNIQUE
+ /// Internal variable to store the rust overlay we are using to avoid regenerating it every overlay update call
+ var/mutable_appearance/rust_overlay
+
+/turf/closed/wall/rust/New()
+ var/atom/wall_new = new /turf/closed/wall(src)
+ wall_new.AddComponent(/datum/component/rust)
+
+/turf/closed/wall/r_wall/rust/New()
+ var/atom/wall_new = new /turf/closed/wall/r_wall(src)
+ wall_new.AddComponent(/datum/component/rust)
+
+/turf/open/floor/plating/rust/New()
+ var/atom/wall_new = new /turf/open/floor/plating(src)
+ wall_new.AddComponent(/datum/component/rust)
+
+/datum/component/rust/Initialize(rust_iconstate = "rust")
+ . = ..()
+ if(!isatom(parent))
+ return COMPONENT_INCOMPATIBLE
+ var/atom/parent_atom = parent
+ if(!(rust_iconstate in icon_states(parent_atom.icon)))
+ return COMPONENT_INCOMPATIBLE
+
+ rust_overlay = mutable_appearance(parent_atom.icon, rust_iconstate)
+
+/datum/component/rust/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS, .proc/apply_rust_overlay)
+ RegisterSignal(parent, list(COMSIG_ATOM_SECONDARY_TOOL_ACT(TOOL_WELDER), COMSIG_ATOM_SECONDARY_TOOL_ACT(TOOL_RUSTSCRAPER)), .proc/secondary_tool_act)
+ RegisterSignal(parent, COMSIG_PARENT_PREQDELETED, .proc/parent_del)
+ RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/handle_examine)
+ ADD_TRAIT(parent, TRAIT_RUSTY, src)
+
+/datum/component/rust/UnregisterFromParent()
+ UnregisterSignal(parent,\
+ list(COMSIG_ATOM_UPDATE_OVERLAYS, COMSIG_ATOM_SECONDARY_TOOL_ACT(TOOL_WELDER), COMSIG_ATOM_SECONDARY_TOOL_ACT(TOOL_RUSTSCRAPER)), COMSIG_PARENT_PREQDELETED)
+ REMOVE_TRAIT(parent, TRAIT_RUSTY, src)
+ var/atom/parent_atom = parent
+ parent_atom.update_icon(UPDATE_OVERLAYS)
+
+/datum/component/rust/proc/handle_examine(datum/source, mob/user, list/examine_text)
+ SIGNAL_HANDLER
+ examine_text += span_notice("[source] is very rusty, you could probably burn or scrape it off.")
+
+/datum/component/rust/proc/apply_rust_overlay(atom/parent_atom, list/overlays)
+ SIGNAL_HANDLER
+ overlays |= rust_overlay
+
+/datum/component/rust/proc/parent_del()
+ SIGNAL_HANDLER
+ qdel(src)
+
+/datum/component/rust/Destroy()
+ rust_overlay = null
+ return ..()
+
+/// We call this from secondary_tool_act because we sleep with do_after
+/datum/component/rust/proc/handle_tool_use(atom/source, mob/user, obj/item/item)
+ switch(item.tool_behaviour)
+ if(TOOL_WELDER)
+ if(item.use(5))
+ user.balloon_alert(user, "burning off rust...")
+ if(!do_after(user, 5 SECONDS * item.toolspeed, parent))
+ return
+ user.balloon_alert(user, "burned off rust")
+ qdel(src)
+ return
+ if(TOOL_RUSTSCRAPER)
+ user.balloon_alert(user, "scraping off rust...")
+ if(!do_after(user, 2 SECONDS * item.toolspeed, parent))
+ return
+ user.balloon_alert(user, "scraped off rust")
+ qdel(src)
+ return
+
+/// Because do_after sleeps we register the signal here and defer via an async call
+/datum/component/rust/proc/secondary_tool_act(atom/source, mob/user, obj/item/item)
+ SIGNAL_HANDLER
+ INVOKE_ASYNC(src, .proc/handle_tool_use, source, user, item)
+ return COMPONENT_BLOCK_TOOL_ATTACK
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 5f4662cbfcb..ff042c73c5a 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -1936,7 +1936,7 @@
* Override this if you want custom behaviour in whatever gets hit by the rust
*/
/atom/proc/rust_heretic_act()
- return
+ AddComponent(/datum/component/rust)
/**
* Used to set something as 'open' if it's being used as a supplypod
diff --git a/code/game/objects/items/debug_items.dm b/code/game/objects/items/debug_items.dm
index 61913d89d50..86cbfb48700 100644
--- a/code/game/objects/items/debug_items.dm
+++ b/code/game/objects/items/debug_items.dm
@@ -66,7 +66,8 @@
"Bonesetter" = image(icon = 'icons/obj/surgery.dmi', icon_state = "bone setter"),
"Knife" = image(icon = 'icons/obj/kitchen.dmi', icon_state = "knife"),
"Blood Filter" = image(icon = 'icons/obj/surgery.dmi', icon_state = "bloodfilter"),
- "Rolling Pin" = image(icon = 'icons/obj/kitchen.dmi', icon_state = "rolling_pin")
+ "Rolling Pin" = image(icon = 'icons/obj/kitchen.dmi', icon_state = "rolling_pin"),
+ "Wire Brush" = image(icon = 'icons/obj/tools.dmi', icon_state = "wirebrush"),
)
var/tool_result = show_radial_menu(user, src, tool_list, custom_check = CALLBACK(src, .proc/check_menu, user), require_near = TRUE, tooltips = TRUE)
if(!check_menu(user))
@@ -110,3 +111,5 @@
tool_behaviour = TOOL_BLOODFILTER
if("Rolling Pin")
tool_behaviour = TOOL_ROLLINGPIN
+ if("Wire Brush")
+ tool_behaviour = TOOL_RUSTSCRAPER
diff --git a/code/game/objects/items/tools/wirebrush.dm b/code/game/objects/items/tools/wirebrush.dm
new file mode 100644
index 00000000000..cd193cf74a6
--- /dev/null
+++ b/code/game/objects/items/tools/wirebrush.dm
@@ -0,0 +1,63 @@
+/**
+ * The wirebrush is a tool whose sole purpose is to remove rust from anything that is rusty.
+ * Because of the inherent nature of hard countering rust heretics it does it very slowly.
+ */
+/obj/item/wirebrush
+ name = "wirebrush"
+ desc = "A tool that is used to scrub the rust thoroughly off walls. Not for hair!"
+ icon = 'icons/obj/tools.dmi'
+ icon_state = "wirebrush"
+ tool_behaviour = TOOL_RUSTSCRAPER
+ toolspeed = 1
+
+/**
+ * An advanced form of the wirebrush that trades the safety of the user for instant rust removal.
+ * If the person using this is unlucky they are going to die painfully.
+ */
+/obj/item/wirebrush/advanced
+ name = "advanced wirebrush"
+ desc = "An advanced wirebrush; uses radiation to almost instantly liquify rust."
+ icon_state = "wirebrush_adv"
+ toolspeed = 0.1
+
+ /// The amount of radiation to give to the user of this tool; regardless of what they did with it.
+ var/radiation_on_use = 20
+
+ /// How likely is a critical fail?
+ var/crit_fail_prob = 1
+
+ /// The amount of radiation to give to the user if they roll the worst effects. Negative numbers will heal radiation instead!
+ var/crit_fail_rads = 50
+
+ /// The amount of damage to take in BOTH Tox and Oxy on critical fail
+ var/crit_fail_damage = 15
+
+ /// We only apply damage and force vomit if the user has OVER this many rads
+ var/crit_fail_rads_threshold = 300
+
+/obj/item/wirebrush/advanced/examine(mob/user)
+ . = ..()
+ . += span_danger("There is a warning label that indicates extended use of [src] may result in loss of hair, yellowing skin, and death.")
+
+/obj/item/wirebrush/advanced/pre_attack(atom/A, mob/living/user)
+ . = ..()
+
+ if(!istype(user))
+ return
+
+ if(prob(crit_fail_prob))
+ to_chat(user, span_danger("You feel a sharp pain as your entire body grows oddly warm."))
+ user.radiation += crit_fail_rads
+ if(user.radiation > crit_fail_rads_threshold) // If you ignore the warning signs you get punished
+ user.emote("vomit")
+ user.adjustToxLoss(crit_fail_damage, forced=TRUE)
+ user.adjustOxyLoss(crit_fail_damage, forced=TRUE)
+ return
+
+ user.radiation += radiation_on_use
+
+ if(prob(25))
+ user.emote("cough")
+
+/obj/item/wirebrush/advanced/pre_attack_secondary()
+ return SECONDARY_ATTACK_CALL_NORMAL
diff --git a/code/game/objects/structures/statues.dm b/code/game/objects/structures/statues.dm
index e7323cf55a0..8eeb5b920a4 100644
--- a/code/game/objects/structures/statues.dm
+++ b/code/game/objects/structures/statues.dm
@@ -313,6 +313,8 @@
drop_sound = 'sound/items/handling/screwdriver_drop.ogg'
pickup_sound = 'sound/items/handling/screwdriver_pickup.ogg'
sharpness = SHARP_POINTY
+ tool_behaviour = TOOL_RUSTSCRAPER
+ toolspeed = 3 // You're gonna have a bad time
/// Block we're currently carving in
var/obj/structure/carving_block/prepared_block
diff --git a/code/game/turfs/closed/wall/misc_walls.dm b/code/game/turfs/closed/wall/misc_walls.dm
index af5b80b0649..f9b262d474c 100644
--- a/code/game/turfs/closed/wall/misc_walls.dm
+++ b/code/game/turfs/closed/wall/misc_walls.dm
@@ -83,9 +83,6 @@
smoothing_flags = SMOOTH_BITMASK
hardness = 45
-/turf/closed/wall/rust/rust_heretic_act()
- ScrapeAway()
-
/turf/closed/wall/r_wall/rust
name = "rusted reinforced wall"
desc = "A huge chunk of rusted reinforced metal."
@@ -95,11 +92,6 @@
smoothing_flags = SMOOTH_BITMASK
hardness = 15
-/turf/closed/wall/r_wall/rust/rust_heretic_act()
- if(prob(50))
- return
- ScrapeAway()
-
/turf/closed/wall/mineral/bronze
name = "clockwork wall"
desc = "A huge chunk of bronze, decorated like gears and cogs."
diff --git a/code/game/turfs/closed/wall/reinf_walls.dm b/code/game/turfs/closed/wall/reinf_walls.dm
index fbcff87ac79..a323ddf34e3 100644
--- a/code/game/turfs/closed/wall/reinf_walls.dm
+++ b/code/game/turfs/closed/wall/reinf_walls.dm
@@ -230,9 +230,10 @@
/turf/closed/wall/r_wall/rust_heretic_act()
if(prob(50))
return
- if(prob(70))
- new /obj/effect/temp_visual/glowing_rune(src)
- ChangeTurf(/turf/closed/wall/r_wall/rust)
+ if(HAS_TRAIT(src, TRAIT_RUSTY))
+ ScrapeAway()
+ return
+ return ..()
/turf/closed/wall/r_wall/syndicate
name = "hull"
diff --git a/code/game/turfs/closed/walls.dm b/code/game/turfs/closed/walls.dm
index 282caf3ae6e..e6fb3889adf 100644
--- a/code/game/turfs/closed/walls.dm
+++ b/code/game/turfs/closed/walls.dm
@@ -309,8 +309,11 @@
add_overlay(dent_decals)
/turf/closed/wall/rust_heretic_act()
+ if(HAS_TRAIT(src, TRAIT_RUSTY))
+ ScrapeAway()
+ return
if(prob(70))
new /obj/effect/temp_visual/glowing_rune(src)
- ChangeTurf(/turf/closed/wall/rust)
+ return ..()
#undef MAX_DENT_DECALS
diff --git a/code/game/turfs/open/floor/iron_floor.dm b/code/game/turfs/open/floor/iron_floor.dm
index 5ae6755cc1a..2d208217ec2 100644
--- a/code/game/turfs/open/floor/iron_floor.dm
+++ b/code/game/turfs/open/floor/iron_floor.dm
@@ -17,7 +17,8 @@
/turf/open/floor/iron/rust_heretic_act()
if(prob(70))
new /obj/effect/temp_visual/glowing_rune(src)
- ChangeTurf(/turf/open/floor/plating/rust)
+ ChangeTurf(/turf/open/floor/plating)
+ return ..()
/turf/open/floor/iron/update_icon_state()
diff --git a/code/game/turfs/open/floor/misc_floor.dm b/code/game/turfs/open/floor/misc_floor.dm
index 31c85564516..692550c8590 100644
--- a/code/game/turfs/open/floor/misc_floor.dm
+++ b/code/game/turfs/open/floor/misc_floor.dm
@@ -207,9 +207,6 @@
/turf/open/floor/plating/rust/plasma
initial_gas_mix = "plasma=104;TEMP=293.15"
-/turf/open/floor/plating/rust/rust_heretic_act()
- return
-
/turf/open/floor/stone
name = "stone brick floor"
desc = "Odd, really, how it looks exactly like the iron walls yet is stone instead of iron. Now, if that's really more of a complaint about\
diff --git a/code/game/turfs/open/floor/plating.dm b/code/game/turfs/open/floor/plating.dm
index 687386bc49d..99f4b875ef2 100644
--- a/code/game/turfs/open/floor/plating.dm
+++ b/code/game/turfs/open/floor/plating.dm
@@ -95,7 +95,7 @@
/turf/open/floor/plating/rust_heretic_act()
if(prob(70))
new /obj/effect/temp_visual/glowing_rune(src)
- ChangeTurf(/turf/open/floor/plating/rust)
+ return ..()
/turf/open/floor/plating/make_plating(force = FALSE)
return
diff --git a/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm
index d82cf4948ae..63ef9d195fb 100644
--- a/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm
+++ b/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm
@@ -262,8 +262,8 @@
edge_turfs = list()
var/list/removal_list = list()
var/max_dist = 1
- for(var/turfie in turfs)
- if(!istype(turfie,/turf/closed/wall/rust) && !istype(turfie,/turf/closed/wall/r_wall/rust) && !istype(turfie,/turf/open/floor/plating/rust))
+ for(var/atom/turfie as anything in turfs)
+ if(!HAS_TRAIT(turfie, TRAIT_RUSTY))
removal_list += turfie
max_dist = max(max_dist, get_dist(turfie,centre) +1)
turfs -= removal_list
diff --git a/code/modules/research/designs/tool_designs.dm b/code/modules/research/designs/tool_designs.dm
index ba5d12486ed..f2cbac71291 100644
--- a/code/modules/research/designs/tool_designs.dm
+++ b/code/modules/research/designs/tool_designs.dm
@@ -268,3 +268,24 @@
materials = list(/datum/material/iron = 2000, /datum/material/silver = 1500, /datum/material/plasma = 500, /datum/material/titanium = 1500)
category = list("Tool Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
+
+/datum/design/wirebrush
+ name = "Wirebrush"
+ desc = "A tool to remove rust from walls."
+ id = "wirebrush"
+ build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE
+ category = list("initial", "Tools")
+ materials = list(/datum/material/iron = 200, /datum/material/glass = 200)
+ build_path = /obj/item/wirebrush
+ category = list("Equipment")
+ departmental_flags = DEPARTMENTAL_FLAG_SERVICE
+
+/datum/design/wirebrush_adv
+ name = "Advanced Wirebrush"
+ desc = "An advanced wirebrush."
+ id = "wirebrush_adv"
+ build_type = PROTOLATHE | AWAY_LATHE
+ materials = list(/datum/material/iron = 200, /datum/material/glass = 200, /datum/material/uranium = 200, /datum/material/plasma = 200)
+ build_path = /obj/item/wirebrush/advanced
+ category = list("Equipment")
+ departmental_flags = DEPARTMENTAL_FLAG_SERVICE
diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm
index 4f84f54036e..0e8f23e5baa 100644
--- a/code/modules/research/techweb/all_nodes.dm
+++ b/code/modules/research/techweb/all_nodes.dm
@@ -151,6 +151,7 @@
"tscanner",
"welding_helmet",
"welding_tool",
+ "wirebrush",
"wirecutters",
"wrench",
)
@@ -1213,6 +1214,7 @@
"laserscalpel",
"mechanicalpinches",
"searingtool",
+ "wirebrush_adv",
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 7500)
discount_experiments = list(/datum/experiment/scanning/random/material/hard/one = 5000)
diff --git a/icons/obj/tools.dmi b/icons/obj/tools.dmi
index 5cc32e7af07..ebc2ec637a5 100644
Binary files a/icons/obj/tools.dmi and b/icons/obj/tools.dmi differ
diff --git a/icons/turf/walls/reinforced_wall.dmi b/icons/turf/walls/reinforced_wall.dmi
index c7b05315d50..35d64734e14 100644
Binary files a/icons/turf/walls/reinforced_wall.dmi and b/icons/turf/walls/reinforced_wall.dmi differ
diff --git a/icons/turf/walls/wall.dmi b/icons/turf/walls/wall.dmi
index 651227fe8de..4e7efd1e23e 100644
Binary files a/icons/turf/walls/wall.dmi and b/icons/turf/walls/wall.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 9df91f0c29b..272ffd2578c 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -552,6 +552,7 @@
#include "code\datums\components\rename.dm"
#include "code\datums\components\rot.dm"
#include "code\datums\components\rotation.dm"
+#include "code\datums\components\rust.dm"
#include "code\datums\components\shell.dm"
#include "code\datums\components\shielded.dm"
#include "code\datums\components\shrink.dm"
@@ -1371,6 +1372,7 @@
#include "code\game\objects\items\tools\crowbar.dm"
#include "code\game\objects\items\tools\screwdriver.dm"
#include "code\game\objects\items\tools\weldingtool.dm"
+#include "code\game\objects\items\tools\wirebrush.dm"
#include "code\game\objects\items\tools\wirecutters.dm"
#include "code\game\objects\items\tools\wrench.dm"
#include "code\game\objects\structures\ai_core.dm"