diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm
index 78f6aa827f034..ff56ca0501d8e 100644
--- a/code/__DEFINES/status_effects.dm
+++ b/code/__DEFINES/status_effects.dm
@@ -140,10 +140,13 @@
#define STATUS_EFFECT_C_FOAMED /datum/status_effect/c_foamed
+#define STATUS_EFFECT_RUST_CORRUPTION /datum/status_effect/rust_corruption
+
#define STATUS_EFFECT_TEMPORAL_SLASH /datum/status_effect/temporal_slash
#define STATUS_EFFECT_TEMPORAL_SLASH_FINISHER /datum/status_effect/temporal_slash_finisher
+
//#define STATUS_EFFECT_NECROPOLIS_CURSE /datum/status_effect/necropolis_curse
//#define CURSE_BLINDING 1 //makes the edges of the target's screen obscured
//#define CURSE_SPAWNING 2 //spawns creatures that attack the target only
diff --git a/code/__HELPERS/trait_helpers.dm b/code/__HELPERS/trait_helpers.dm
index 2b3496855f570..6fa0ea9d42d9c 100644
--- a/code/__HELPERS/trait_helpers.dm
+++ b/code/__HELPERS/trait_helpers.dm
@@ -328,6 +328,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
/// A trait for determining if a atom/movable is currently crossing into another z-level by using of /turf/space z-level "destination-xyz" transfers
#define TRAIT_CURRENTLY_Z_MOVING "currently_z_moving" // please dont adminbus this
+//****** TURF TRAITS *****//
+#define TRAIT_RUSTY "rust_trait"
+
//
// common trait sources
#define TRAIT_GENERIC "generic"
diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm
index 7ce28f4c915f6..925dd2d981ca1 100644
--- a/code/_globalvars/traits.dm
+++ b/code/_globalvars/traits.dm
@@ -144,7 +144,8 @@ GLOBAL_LIST_INIT(traits_by_type, list(
),
/turf = list(
- "bluespace_speed_trait" = TRAIT_BLUESPACE_SPEED
+ "bluespace_speed_trait" = TRAIT_BLUESPACE_SPEED,
+ "TRAIT_RUSTY" = TRAIT_RUSTY
),
/obj/effect = list(
diff --git a/code/datums/elements/rust_element.dm b/code/datums/elements/rust_element.dm
new file mode 100644
index 0000000000000..d2464e016df77
--- /dev/null
+++ b/code/datums/elements/rust_element.dm
@@ -0,0 +1,123 @@
+/**
+ * Adding this element to an atom will have it automatically render an overlay.
+ */
+/datum/element/rust
+ element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH_ON_HOST_DESTROY // Detach for turfs
+ argument_hash_start_idx = 2
+ /// The rust image itself, since the icon and icon state are only used as an argument
+ var/image/rust_overlay
+
+/datum/element/rust/Attach(atom/target, rust_icon = 'icons/effects/rust_overlay.dmi', rust_icon_state = "rust_default")
+ . = ..()
+ if(!isatom(target))
+ return ELEMENT_INCOMPATIBLE
+
+ rust_overlay = image(rust_icon, "rust[rand(1, 6)]")
+ ADD_TRAIT(target, TRAIT_RUSTY, "rusted_turf")
+ RegisterSignal(target, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(apply_rust_overlay))
+ RegisterSignal(target, COMSIG_PARENT_EXAMINE, PROC_REF(handle_examine))
+ RegisterSignal(target, COMSIG_INTERACT_TARGET, PROC_REF(on_interaction))
+ RegisterSignal(target, COMSIG_TOOL_ATTACK, PROC_REF(welder_tool_act))
+ // Unfortunately registering with parent sometimes doesn't cause an overlay update
+ target.update_appearance()
+
+/datum/element/rust/Detach(atom/source)
+ . = ..()
+ UnregisterSignal(source, COMSIG_ATOM_UPDATE_OVERLAYS)
+ UnregisterSignal(source, COMSIG_PARENT_EXAMINE)
+ UnregisterSignal(source, COMSIG_TOOL_ATTACK)
+ UnregisterSignal(source, COMSIG_INTERACT_TARGET)
+ REMOVE_TRAIT(source, TRAIT_RUSTY, "rusted_turf")
+ source.cut_overlays()
+ source.update_appearance()
+
+/datum/element/rust/proc/handle_examine(datum/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER //COMSIG_PARENT_EXAMINE
+
+ examine_list += "[source] is very rusty, you could probably burn it off."
+
+/datum/element/rust/proc/apply_rust_overlay(atom/parent_atom, list/overlays)
+ SIGNAL_HANDLER //COMSIG_ATOM_UPDATE_OVERLAYS
+
+ if(rust_overlay)
+ parent_atom.add_overlay(rust_overlay)
+
+/// Because do_after sleeps we register the signal here and defer via an async call
+/datum/element/rust/proc/welder_tool_act(atom/source, obj/item/item, mob/user)
+ SIGNAL_HANDLER // COMSIG_TOOL_ATTACK
+
+ INVOKE_ASYNC(src, PROC_REF(handle_tool_use), source, item, user)
+ return COMPONENT_CANCEL_TOOLACT
+
+/// We call this from secondary_tool_act because we sleep with do_after
+/datum/element/rust/proc/handle_tool_use(atom/source, obj/item/item, mob/user)
+ switch(item.tool_behaviour)
+ if(TOOL_WELDER)
+ if(!item.tool_start_check(source, user, amount=1))
+ return
+ to_chat(user, "You start burning off the rust...")
+
+ if(!item.use_tool(source, user, 5 SECONDS, volume = item.tool_volume))
+ return
+ to_chat(user, "You burn off the rust!")
+ Detach(source)
+ return
+
+/// Prevents placing floor tiles on rusted turf
+/datum/element/rust/proc/on_interaction(datum/source, mob/living/user, obj/item/tool, list/modifiers)
+ SIGNAL_HANDLER // COMSIG_INTERACT_TARGET
+ if(istype(tool, /obj/item/stack/tile) || istype(tool, /obj/item/stack/rods) || istype(tool, /obj/item/rcd))
+ to_chat(user, "[source] is too rusted to build on!")
+ return ITEM_INTERACT_COMPLETE
+
+/// For rust applied by heretics (if that ever happens) / revenants
+/datum/element/rust/heretic
+
+/datum/element/rust/heretic/Attach(atom/target, rust_icon, rust_icon_state)
+ . = ..()
+ if(. == ELEMENT_INCOMPATIBLE)
+ return .
+ RegisterSignal(target, COMSIG_ATOM_ENTERED, PROC_REF(on_entered))
+ RegisterSignal(target, COMSIG_ATOM_EXITED, PROC_REF(on_exited))
+
+/datum/element/rust/heretic/Detach(atom/source)
+ . = ..()
+ UnregisterSignal(source, COMSIG_ATOM_ENTERED)
+ UnregisterSignal(source, COMSIG_ATOM_EXITED)
+ for(var/obj/effect/glowing_rune/rune_to_remove in source)
+ qdel(rune_to_remove)
+ for(var/mob/living/victim in source)
+ victim.remove_status_effect(STATUS_EFFECT_RUST_CORRUPTION)
+
+/datum/element/rust/heretic/proc/on_entered(turf/source, atom/movable/entered, ...)
+ SIGNAL_HANDLER
+
+ if(!isliving(entered))
+ return
+ var/mob/living/victim = entered
+ if(istype(victim, /mob/living/simple_animal/revenant))
+ return
+ victim.apply_status_effect(STATUS_EFFECT_RUST_CORRUPTION)
+
+/datum/element/rust/heretic/proc/on_exited(turf/source, atom/movable/gone)
+ SIGNAL_HANDLER
+ if(!isliving(gone))
+ return
+ var/mob/living/leaver = gone
+ leaver.remove_status_effect(STATUS_EFFECT_RUST_CORRUPTION)
+
+// Small visual effect imparted onto rusted things by revenants.
+/obj/effect/glowing_rune
+ icon = 'icons/effects/eldritch.dmi'
+ icon_state = "small_rune_1"
+ anchored = TRUE
+ plane = FLOOR_PLANE
+ layer = SIGIL_LAYER
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+
+/obj/effect/glowing_rune/Initialize(mapload)
+ . = ..()
+ pixel_y = rand(-6, 6)
+ pixel_x = rand(-6, 6)
+ icon_state = "small_rune_[rand(1, 12)]"
+ update_appearance()
diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm
index 5e3b3357b5ee1..2c5acaf69cc07 100644
--- a/code/datums/status_effects/debuffs.dm
+++ b/code/datums/status_effects/debuffs.dm
@@ -1431,6 +1431,23 @@
alert_type = null
status_type = STATUS_EFFECT_REPLACE
+/datum/status_effect/rust_corruption
+ alert_type = null
+ id = "rust_turf_effects"
+ tick_interval = 2 SECONDS
+
+/datum/status_effect/rust_corruption/tick()
+ . = ..()
+ if(issilicon(owner))
+ owner.adjustBruteLoss(10)
+ return
+ //We don't have disgust, so...
+ if(ishuman(owner))
+ owner.adjustBrainLoss(2.5)
+ owner.reagents?.remove_all(0.75)
+ else
+ owner.adjustBruteLoss(3) //Weaker than borgs but still constant.
+
/// This is the threshold where the attack will stun on the last hit. Why? Because it is cool, that's why.
#define FINISHER_THRESHOLD 7
diff --git a/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm b/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm
index ad6da8561d80c..4fae962fa5136 100644
--- a/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm
+++ b/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm
@@ -495,18 +495,18 @@
/turf/simulated/wall/defile()
..()
- if(prob(15) && !rusted)
+ if(prob(15))
new/obj/effect/temp_visual/revenant(loc)
- rust()
+ magic_rust_turf()
/turf/simulated/wall/indestructible/defile()
return
/turf/simulated/wall/r_wall/defile()
..()
- if(prob(15) && !rusted)
+ if(prob(15))
new/obj/effect/temp_visual/revenant(loc)
- rust()
+ magic_rust_turf()
/mob/living/carbon/human/defile()
to_chat(src, "You suddenly feel [pick("sick and tired", "tired and confused", "nauseated", "dizzy")].")
@@ -531,8 +531,10 @@
broken = FALSE
burnt = FALSE
make_plating(1)
+ magic_rust_turf()
/turf/simulated/floor/plating/defile()
+ magic_rust_turf()
if(flags & BLESSED_TILE)
flags &= ~BLESSED_TILE
new /obj/effect/temp_visual/revenant(loc)
diff --git a/code/game/turfs/simulated/floor/misc_floor.dm b/code/game/turfs/simulated/floor/misc_floor.dm
index 1fdab8f14835a..9e5a4241d59ed 100644
--- a/code/game/turfs/simulated/floor/misc_floor.dm
+++ b/code/game/turfs/simulated/floor/misc_floor.dm
@@ -307,3 +307,21 @@
if(prob(50))
break_tile_to_plating()
hotspot_expose(1000,CELL_VOLUME)
+
+/turf/open/floor/plating/rust
+ //SDMM supports colors, this is simply for easier mapping
+ //and should be removed on initialize
+ color = COLOR_BROWN
+
+/turf/simulated/floor/plating/rust/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/rust)
+ color = null
+
+/turf/open/floor/plating/heretic_rust
+ color = COLOR_GREEN_GRAY
+
+/turf/simulated/floor/plating/heretic_rust/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/rust/heretic)
+ color = null
diff --git a/code/game/turfs/simulated/floor/plating.dm b/code/game/turfs/simulated/floor/plating.dm
index 3ddc3948d4660..1f75cad5ef91e 100644
--- a/code/game/turfs/simulated/floor/plating.dm
+++ b/code/game/turfs/simulated/floor/plating.dm
@@ -129,7 +129,7 @@
/turf/simulated/floor/plating/welder_act(mob/user, obj/item/I)
if(!broken && !burnt && !unfastened)
return
- . = TRUE
+ . = ..()
if(!I.tool_use_check(user, 0))
return
if(user.a_intent == INTENT_HARM) // no repairing on harm intent, so you can use the welder in a fight near damaged paneling without welding your eyes out
diff --git a/code/game/turfs/simulated/walls.dm b/code/game/turfs/simulated/walls.dm
index f7050f5af9340..422cf388c2dff 100644
--- a/code/game/turfs/simulated/walls.dm
+++ b/code/game/turfs/simulated/walls.dm
@@ -40,10 +40,6 @@
var/sheet_type = /obj/item/stack/sheet/metal
var/sheet_amount = 2
var/girder_type = /obj/structure/girder
- /// Are we a rusty wall or not?
- var/rusted = FALSE
- /// Have we got a rusty overlay?
- var/rusted_overlay
/// Are we a explodable turf?
var/explodable = FALSE
/// Do we have a explodable overlay?
@@ -89,26 +85,12 @@
if(can_dismantle_with_welder)
. += "Using a lit welding tool on this item will allow you to slice through it, eventually removing the outer layer."
-/// Apply rust effects to the wall
-/turf/simulated/wall/proc/rust()
- if(rusted)
- return
- rusted = TRUE
- update_appearance(UPDATE_NAME|UPDATE_OVERLAYS)
-
-/turf/simulated/wall/update_name()
- . = ..()
- name = "[rusted ? "rusted " : ""][name]"
-
/turf/simulated/wall/update_overlays()
. = ..()
if(!damage_overlays[1]) //list hasn't been populated
generate_overlays()
QUEUE_SMOOTH(src)
- if(rusted && !rusted_overlay)
- rusted_overlay = icon('icons/turf/overlays.dmi', pick("rust", "rust2"), pick(NORTH, SOUTH, EAST, WEST))
- . += rusted_overlay
if(explodable && !explodable_overlay)
explodable_overlay = icon('icons/turf/overlays.dmi', pick("explodable"), pick(NORTH, SOUTH, EAST, WEST))
@@ -368,7 +350,7 @@
return CONTINUE_ATTACK
/turf/simulated/wall/welder_act(mob/user, obj/item/I)
- . = TRUE
+ . = ..()
if(reagents?.get_reagent_amount("thermite") && I.use_tool(src, user, volume = I.tool_volume))
thermitemelt(user)
return
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index c8d6fe4feb96f..09341eaea3543 100644
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -487,7 +487,6 @@
/turf/attack_by(obj/item/attacking, mob/user, params)
if(..())
return TRUE
-
if(can_lay_cable())
if(istype(attacking, /obj/item/stack/cable_coil))
var/obj/item/stack/cable_coil/C = attacking
@@ -606,6 +605,19 @@
C.take_organ_damage(damage)
C.KnockDown(3 SECONDS)
+/turf/proc/rust_turf()
+ if(HAS_TRAIT(src, TRAIT_RUSTY))
+ return
+
+ AddElement(/datum/element/rust)
+
+/turf/proc/magic_rust_turf()
+ if(HAS_TRAIT(src, TRAIT_RUSTY))
+ return
+
+ AddElement(/datum/element/rust/heretic)
+ new /obj/effect/glowing_rune(src)
+
/// Returns a list of all attached /datum/element/decal/ for this turf
/turf/proc/get_decals()
var/list/datum/element/decals = list()
diff --git a/code/modules/mapping/mapping_helpers.dm b/code/modules/mapping/mapping_helpers.dm
index 7f1e35bb744f4..ac92796cc50fa 100644
--- a/code/modules/mapping/mapping_helpers.dm
+++ b/code/modules/mapping/mapping_helpers.dm
@@ -222,7 +222,8 @@
T.burn_tile()
/obj/effect/mapping_helpers/turfs/rust
- icon_state = "rustwall"
+ icon = 'icons/effects/rust_overlay.dmi'
+ icon_state = "rust1"
var/spawn_probability = 100
/obj/effect/mapping_helpers/turfs/rust/payload(turf/simulated/wall/T)
@@ -230,7 +231,12 @@
return
if(prob(spawn_probability))
- T.rust()
+ rustify(T)
+
+/obj/effect/mapping_helpers/turfs/proc/rustify(turf/T)
+ var/turf/simulated/wall/W = T
+ if(istype(W) && !HAS_TRAIT(W, TRAIT_RUSTY))
+ W.rust_turf()
/obj/effect/mapping_helpers/turfs/rust/probably
spawn_probability = 75
diff --git a/icons/effects/eldritch.dmi b/icons/effects/eldritch.dmi
new file mode 100644
index 0000000000000..6481aa7591bd8
Binary files /dev/null and b/icons/effects/eldritch.dmi differ
diff --git a/icons/effects/rust_overlay.dmi b/icons/effects/rust_overlay.dmi
new file mode 100644
index 0000000000000..f4d6cc33c4179
Binary files /dev/null and b/icons/effects/rust_overlay.dmi differ
diff --git a/icons/turf/overlays.dmi b/icons/turf/overlays.dmi
index 4b9aa80e14eb2..79fddb79b5120 100644
Binary files a/icons/turf/overlays.dmi and b/icons/turf/overlays.dmi differ
diff --git a/paradise.dme b/paradise.dme
index 2959029f31e4a..90e5468b42641 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -543,6 +543,7 @@
#include "code\datums\elements\high_value_item.dm"
#include "code\datums\elements\rad_insulation.dm"
#include "code\datums\elements\ridable.dm"
+#include "code\datums\elements\rust_element.dm"
#include "code\datums\elements\shatters_when_thrown.dm"
#include "code\datums\elements\strippable.dm"
#include "code\datums\elements\waddling.dm"