From bad46d86008c1dee6b4efa16889e04192edcfd39 Mon Sep 17 00:00:00 2001
From: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
Date: Fri, 3 Apr 2026 09:32:09 -0500
Subject: [PATCH] Walking in snow leaves snowprints, snowstorms clear them
(#95569)
## About The Pull Request
Walking through snow or sand leaves behind a trail of prints similar to
that of bloody footprints (they use the old sprites, in fact). Like
bloody footprints, you can examine them to get a hint at what left the
prints (shoe type or leg type if barefoot).
When weather comes through (snow storm or sand storm), all prints will
be cleared off. During a raging snow or sand storm, you won't leave
prints at all.
Snowprints can be filled with snow to remove them as well.
Some equipment or effects will stop you from leaving snowprints (ski
shoes, ice shoes*, heretic's cloak of shadows). Also you obviously don't
leave prints if you're flying or crawling or riding something.
**We don't have snow shoes*
## Why It's Good For The Game
Initially I made this as a cute little detail, but I realized it has far
reaching implications for play on Icebox (and many, many sandbox
applications)
On Icebox, any average joe can run into the wastes and become
untraceable, which makes it almost impossible to track down antagonists.
Snowprints... are a method for tracking people who run off into the
snow.
But you can play around it - You can make fake trails and retrace your
steps. You can wait for a snowstorm to clear your tracks. You can even
fill in your tracks with snow if you're patient. Alternatively,
acquiring snow shoes means you leave no trail... or you could learn to
fly, crawl through the snow, use a vehicle, so on.
If you're assigned to assassinate a miner, you can follow their
snowprint trail as they head into the wastes. However, this may only
works early in the round, as the caverns have no snowstorm to clear
prints - as miners come and go, it will become harder to follow trails.
Speaking of miners, they can follow their co-workers trail if they get
injured, or look for prints to see if a ruin has been looted. Similarly
if a crew member falls down to the mining layers, they could find a
miner's trail to follow it back to the station.
And If you're a paramedic and someone was wounded out in the wastes, you
might be able to find their trail to track them down. But you can't
dilly-dally, as the storm will wipe away any chance of that.
Ultimately I think a small detail like this has some big potential for
emergent gameplay which could be cool. My intent is not to affect
antagonists too negatively, so more tools to avoid leaving prints (snow
shoes) could be added.
## Changelog
:cl: Melbert
add: Walking in snow or sand leaves behind prints. You can examine the
turf to see what left the prints, like with bloodprints.
add: Snowstorms or sandstorms clear away any footprints left behind in
the snow or sand.
add: Some equipment or effects, like skis or snowshoes, prevent you from
leaving prints in the snow.
add: You can fill in snowprints with snow.
/:cl:
---
code/__DEFINES/traits/declarations.dm | 3 +
code/_globalvars/traits/_traits.dm | 1 +
code/_globalvars/traits/admin_tooling.dm | 1 +
code/game/turfs/open/_open.dm | 146 +++++++++++++++---
code/game/turfs/open/asteroid.dm | 81 +++++++++-
.../turfs/open/floor/plating/misc_plating.dm | 6 +-
code/game/turfs/open/sand.dm | 28 ++++
code/game/turfs/open/snow.dm | 30 ++++
.../antagonists/heretic/magic/shadow_cloak.dm | 4 +-
code/modules/clothing/shoes/boots.dm | 2 +-
code/modules/clothing/shoes/wheelys.dm | 1 +
.../mecha/equipment/tools/mining_tools.dm | 2 +-
icons/effects/footprints.dmi | Bin 4204 -> 5087 bytes
13 files changed, 269 insertions(+), 36 deletions(-)
diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm
index 69af372eb87..3428e73a600 100644
--- a/code/__DEFINES/traits/declarations.dm
+++ b/code/__DEFINES/traits/declarations.dm
@@ -357,6 +357,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
/// Stops all slipping and sliding from occurring
#define TRAIT_NO_SLIP_ALL "noslip_all"
+/// Stops the mob from leaving prints in the snow
+#define TRAIT_NO_SNOWPRINTS "no_snowprints"
+
/// Unlinks gliding from movement speed, meaning that there will be a delay between movements rather than a single move movement between tiles
#define TRAIT_NO_GLIDE "no_glide"
diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm
index 0e3607877a3..813d7d13e9e 100644
--- a/code/_globalvars/traits/_traits.dm
+++ b/code/_globalvars/traits/_traits.dm
@@ -457,6 +457,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_NO_SLIP_ICE" = TRAIT_NO_SLIP_ICE,
"TRAIT_NO_SLIP_SLIDE" = TRAIT_NO_SLIP_SLIDE,
"TRAIT_NO_SLIP_WATER" = TRAIT_NO_SLIP_WATER,
+ "TRAIT_NO_SNOWPRINTS" = TRAIT_NO_SNOWPRINTS,
"TRAIT_NO_SOUL" = TRAIT_NO_SOUL,
"TRAIT_NO_SPLIT_PERSONALITY" = TRAIT_NO_SPLIT_PERSONALITY,
"TRAIT_NO_STAGGER" = TRAIT_NO_STAGGER,
diff --git a/code/_globalvars/traits/admin_tooling.dm b/code/_globalvars/traits/admin_tooling.dm
index c2c929746f4..0b1e2f2ba84 100644
--- a/code/_globalvars/traits/admin_tooling.dm
+++ b/code/_globalvars/traits/admin_tooling.dm
@@ -205,6 +205,7 @@ GLOBAL_LIST_INIT(admin_visible_traits, list(
"TRAIT_NO_SLIP_ICE" = TRAIT_NO_SLIP_ICE,
"TRAIT_NO_SLIP_SLIDE" = TRAIT_NO_SLIP_SLIDE,
"TRAIT_NO_SLIP_WATER" = TRAIT_NO_SLIP_WATER,
+ "TRAIT_NO_SNOWPRINTS" = TRAIT_NO_SNOWPRINTS,
"TRAIT_NO_SOUL" = TRAIT_NO_SOUL,
"TRAIT_NO_SPLIT_PERSONALITY" = TRAIT_NO_SPLIT_PERSONALITY,
"TRAIT_NO_STAGGER" = TRAIT_NO_STAGGER,
diff --git a/code/game/turfs/open/_open.dm b/code/game/turfs/open/_open.dm
index 9948c3ea26c..9a2f293bbde 100644
--- a/code/game/turfs/open/_open.dm
+++ b/code/game/turfs/open/_open.dm
@@ -26,6 +26,17 @@
/// Custom destination for mirages
var/destination_y
+ /// If TRUE, humans walking in and out of this turf will leave behind footprints, applied as a generic overlay
+ var/leave_footprints = FALSE
+ /// Lazylist of all shoe types that have walked over this turf
+ VAR_PRIVATE/list/footprint_shoe_types
+ /// Lazylist of all species that have walked over this turf
+ VAR_PRIVATE/list/footprint_species_types
+ /// All dirs from which footprints have entered this turf
+ VAR_PRIVATE/footprint_entrance_dirs = NONE
+ /// All dirs from which footprints have exited this turf
+ VAR_PRIVATE/footprint_exit_dirs = NONE
+
/// Returns a list of every turf state considered "broken".
/// Will be randomly chosen if a turf breaks at runtime.
/turf/open/proc/broken_states()
@@ -40,50 +51,100 @@
if(isnull(damaged_dmi) || broken)
return FALSE
broken = TRUE
- update_appearance()
+ if(leave_footprints)
+ clear_footprints() // calls update_appearance() for us
+ else
+ update_appearance()
return TRUE
/turf/open/burn_tile()
if(isnull(damaged_dmi) || burnt)
return FALSE
burnt = TRUE
- update_appearance()
+ if(leave_footprints)
+ clear_footprints() // calls update_appearance() for us
+ else
+ update_appearance()
return TRUE
/turf/open/update_overlays()
- if(isnull(damaged_dmi))
- return ..()
-
. = ..()
-
if(broken)
- var/mutable_appearance/broken_appearance = mutable_appearance(damaged_dmi, pick(broken_states()))
+ if(isnull(damaged_dmi))
+ stack_trace("[type] has broken set to TRUE but doesn't have a damaged_dmi set.")
+ else
+ var/mutable_appearance/broken_appearance = mutable_appearance(damaged_dmi, pick(broken_states()))
- if(smoothing_flags && !smooth_broken)
- var/matrix/translation = new
- translation.Translate(-LARGE_TURF_SMOOTHING_X_OFFSET, -LARGE_TURF_SMOOTHING_Y_OFFSET)
- broken_appearance.transform = translation
+ if(smoothing_flags && !smooth_broken)
+ var/matrix/translation = new
+ translation.Translate(-LARGE_TURF_SMOOTHING_X_OFFSET, -LARGE_TURF_SMOOTHING_Y_OFFSET)
+ broken_appearance.transform = translation
- . += broken_appearance
+ . += broken_appearance
else if(burnt)
- var/list/burnt_states = burnt_states()
- var/mutable_appearance/burnt_appearance
- if(burnt_states.len)
- burnt_appearance = mutable_appearance(damaged_dmi, pick(burnt_states))
+ if(isnull(damaged_dmi))
+ stack_trace("[type] has burnt set to TRUE but doesn't have a damaged_dmi set.")
else
- burnt_appearance = mutable_appearance(damaged_dmi, pick(broken_states()))
+ var/list/burnt_states = burnt_states()
+ var/mutable_appearance/burnt_appearance
+ if(burnt_states.len)
+ burnt_appearance = mutable_appearance(damaged_dmi, pick(burnt_states))
+ else
+ burnt_appearance = mutable_appearance(damaged_dmi, pick(broken_states()))
- if(smoothing_flags && !smooth_burnt)
- var/matrix/translation = new
- translation.Translate(-LARGE_TURF_SMOOTHING_X_OFFSET, -LARGE_TURF_SMOOTHING_Y_OFFSET)
- burnt_appearance.transform = translation
+ if(smoothing_flags && !smooth_burnt)
+ var/matrix/translation = new
+ translation.Translate(-LARGE_TURF_SMOOTHING_X_OFFSET, -LARGE_TURF_SMOOTHING_Y_OFFSET)
+ burnt_appearance.transform = translation
- . += burnt_appearance
+ . += burnt_appearance
+
+ if(leave_footprints && (footprint_entrance_dirs || footprint_exit_dirs))
+ var/static/list/footprint_cache = list()
+
+ var/icon_state_to_use = "footprint"
+ if(LAZYACCESS(footprint_species_types, BODYPART_ID_DIGITIGRADE))
+ icon_state_to_use += "_claw"
+ else if(LAZYACCESS(footprint_species_types, SPECIES_MONKEY))
+ icon_state_to_use += "_paw"
+
+ for(var/print_dir in GLOB.cardinals)
+ if(footprint_entrance_dirs & print_dir)
+ var/enter_state = "entered-[icon_state_to_use]-[print_dir]"
+ var/image/enter_overlay = footprint_cache[enter_state]
+ if(!enter_overlay)
+ enter_overlay = image('icons/effects/footprints.dmi', "[icon_state_to_use]1", dir = print_dir)
+ enter_overlay.color = COLOR_DARK
+ enter_overlay.alpha *= 0.2
+ footprint_cache[enter_state] = enter_overlay
+ . += enter_overlay
+
+ if(footprint_exit_dirs & print_dir)
+ var/exit_state = "exited-[icon_state_to_use]-[print_dir]"
+ var/image/exit_overlay = footprint_cache[exit_state]
+ if(!exit_overlay)
+ exit_overlay = image('icons/effects/footprints.dmi', "[icon_state_to_use]2", dir = print_dir)
+ exit_overlay.color = COLOR_DARK
+ exit_overlay.alpha *= 0.2
+ footprint_cache[exit_state] = exit_overlay
+ . += exit_overlay
/turf/open/examine_descriptor(mob/user)
return "floor"
+/turf/open/examine(mob/user)
+ . = ..()
+ if(leave_footprints && (footprint_entrance_dirs || footprint_exit_dirs) && (LAZYLEN(footprint_shoe_types) || LAZYLEN(footprint_species_types)))
+ . += "You recognise the footprints as belonging to:"
+ for(var/obj/item/clothing/shoes/sole as anything in footprint_shoe_types)
+ var/article = initial(sole.article) || (initial(sole.gender) == PLURAL ? "Some" : "A")
+ . += "[icon2html(initial(sole.icon), user, initial(sole.icon_state))] [article] [initial(sole.name)]."
+
+ for(var/species in footprint_species_types)
+ var/datum/species/species_type = GLOB.species_list[species]
+ . += "• Some [species_type ? format_text(species_type::plural_form) : "unknown"] feet."
+
//direction is direction of travel of A
/turf/open/zPassIn(direction)
if(direction != DOWN)
@@ -130,11 +191,46 @@
if(destination_x || destination_y || destination_z)
return TRUE
+/// Add the passed mob's footprint to the turf with the given movement direction
+/turf/open/proc/add_footprint(mob/living/carbon/human/walker, movement_direction)
+ if(walker.body_position != STANDING_UP || walker.buckled || (walker.movement_type & MOVETYPES_NOT_TOUCHING_GROUND))
+ return
+
+ if(walker.loc == src)
+ footprint_entrance_dirs |= movement_direction
+ else
+ footprint_exit_dirs |= movement_direction
+
+ if(walker.shoes)
+ LAZYOR(footprint_shoe_types, walker.shoes.type)
+
+ else
+ // we can't actually get which leg is the "front foot" so we'll just guess
+ var/front_foot = prob(50) ? BODY_ZONE_R_LEG : BODY_ZONE_L_LEG
+ var/backup_foot = front_foot == BODY_ZONE_R_LEG ? BODY_ZONE_L_LEG : BODY_ZONE_R_LEG
+ var/obj/item/bodypart/leg/walking_on = walker.get_bodypart(front_foot) || walker.get_bodypart(backup_foot)
+ if(isnull(walking_on))
+ return
+ LAZYOR(footprint_species_types, walking_on.limb_id)
+
+ update_appearance()
+
+/// Clear all footprints on the turf
+/turf/open/proc/clear_footprints()
+ footprint_entrance_dirs = NONE
+ footprint_exit_dirs = NONE
+ LAZYNULL(footprint_shoe_types)
+ LAZYNULL(footprint_species_types)
+ update_appearance()
+
/turf/open/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs)
. = ..()
if(!arrived || src != arrived.loc)
return
+ if(old_loc && leave_footprints && !broken && !burnt && ishuman(arrived))
+ add_footprint(arrived, get_dir(old_loc, src))
+
if(!destination_z || !destination_x || !destination_y || arrived.pulledby || arrived.currently_z_moving)
return
@@ -166,6 +262,12 @@
var/turf/target_turf = get_step(current_pull.pulledby.loc, REVERSE_DIR(current_pull.pulledby.dir)) || current_pull.pulledby.loc
current_pull.zMove(null, target_turf, ZMOVE_ALLOW_BUCKLED)
current_pull = current_pull.pulling
+
+/turf/open/Exited(atom/movable/gone, direction)
+ . = ..()
+ if(gone && direction && leave_footprints && !broken && !burnt && isturf(gone.loc) && ishuman(gone))
+ add_footprint(gone, direction)
+
/**
* Replace an open turf with another open turf while avoiding the pitfall of replacing plating with a floor tile, leaving a hole underneath.
* This replaces the current turf if it is plating and is passed plating, is tile and is passed tile.
diff --git a/code/game/turfs/open/asteroid.dm b/code/game/turfs/open/asteroid.dm
index 4e131d0bf98..9675f0c45d3 100644
--- a/code/game/turfs/open/asteroid.dm
+++ b/code/game/turfs/open/asteroid.dm
@@ -1,5 +1,7 @@
/**********************Asteroid**************************/
+#define DIG_SHEET_AMOUNT 5
+
/turf/open/misc/asteroid //floor piece
gender = PLURAL
name = "asteroid sand"
@@ -86,14 +88,12 @@
/// Drops itemstack when dug and changes icon
/turf/open/misc/asteroid/proc/getDug()
- if(dug || broken)
- return
- dug = TRUE
- broken = TRUE
- new dig_result(src, 5)
+ if(!break_tile())
+ return FALSE
+ new dig_result(src, DIG_SHEET_AMOUNT)
if(prob(worm_chance))
new /obj/item/food/bait/worm(src)
- update_appearance()
+ return TRUE
/// If the user can dig the turf
/turf/open/misc/asteroid/proc/can_dig(mob/user)
@@ -101,6 +101,7 @@
return TRUE
if(user)
balloon_alert(user, "already excavated!")
+ return FALSE
///Refills the previously dug tile
/turf/open/misc/asteroid/proc/refill_dug()
@@ -141,9 +142,11 @@ GLOBAL_LIST_EMPTY(dug_up_basalt)
dig_result = /obj/item/stack/ore/glass/basalt
/turf/open/misc/asteroid/basalt/getDug()
+ . = ..()
+ if(!.)
+ return
set_light(0)
GLOB.dug_up_basalt |= src
- return ..()
/turf/open/misc/asteroid/basalt/Destroy()
GLOB.dug_up_basalt -= src
@@ -214,6 +217,7 @@ GLOBAL_LIST_EMPTY(dug_up_basalt)
bullet_sizzle = TRUE
bullet_bounce_sound = null
dig_result = /obj/item/stack/sheet/mineral/snow
+ leave_footprints = TRUE
/turf/open/misc/asteroid/snow/burn_tile()
if(!burnt)
@@ -227,6 +231,67 @@ GLOBAL_LIST_EMPTY(dug_up_basalt)
/turf/open/misc/asteroid/snow/burnt_states()
return list("snow_dug")
+/turf/open/misc/asteroid/snow/add_footprint(mob/living/carbon/human/walker, movement_direction)
+ if(HAS_TRAIT(walker, TRAIT_NO_SNOWPRINTS))
+ return
+ // skip the special logic if the level doesn't naturally have snowstorms
+ if(!SSmapping.level_trait(z, ZTRAIT_SNOWSTORM))
+ return ..()
+
+ // if an active snow storm affecting this turf is currently in its main or wind down stage, skip footprint creation
+ for(var/datum/weather/snow_storm/active_weather in SSweather.processing)
+ if(active_weather.stage != MAIN_STAGE && active_weather.stage != WIND_DOWN_STAGE)
+ continue
+ if(!(loc in active_weather.impacted_areas))
+ continue
+ return
+
+ . = ..()
+ // when a snow storm enters its main stage, clear all of our footprints
+ for(var/snow_type in typesof(/datum/weather/snow_storm))
+ RegisterSignal(SSdcs, COMSIG_WEATHER_START(snow_type), PROC_REF(snow_clear_footprints), override = TRUE)
+
+/turf/open/misc/asteroid/snow/proc/snow_clear_footprints(datum/source, datum/weather/storm)
+ SIGNAL_HANDLER
+
+ if(!(loc in storm.impacted_areas))
+ return
+
+ clear_footprints()
+ for(var/snow_type in typesof(/datum/weather/snow_storm))
+ UnregisterSignal(SSdcs, COMSIG_WEATHER_START(snow_type))
+
+/turf/open/misc/asteroid/snow/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
+ if(!istype(tool, /obj/item/stack/sheet/mineral/snow))
+ return ..()
+
+ if(dug)
+ if(tool.use(DIG_SHEET_AMOUNT))
+ user.visible_message(
+ span_notice("[user] packs [src] back in."),
+ span_notice("You pack [src] back in."),
+ vision_distance = COMBAT_MESSAGE_RANGE,
+ )
+ refill_dug()
+ return ITEM_INTERACT_SUCCESS
+
+ to_chat(user, "You don't have enough [tool.name] to fill the hole.")
+ return ITEM_INTERACT_BLOCKING
+
+ if(footprint_entrance_dirs || footprint_exit_dirs)
+ if(tool.use(1))
+ user.visible_message(
+ span_notice("[user] fills in the footprints in [src]."),
+ span_notice("You fill in the footprints in [src]."),
+ vision_distance = COMBAT_MESSAGE_RANGE,
+ )
+ clear_footprints()
+ return ITEM_INTERACT_SUCCESS
+
+ return NONE
+
+ return NONE
+
/turf/open/misc/asteroid/snow/icemoon
baseturfs = /turf/open/openspace/icemoon
initial_gas_mix = ICEMOON_DEFAULT_ATMOS
@@ -259,6 +324,7 @@ GLOBAL_LIST_EMPTY(dug_up_basalt)
clawfootstep = FOOTSTEP_HARD_CLAW
heavyfootstep = FOOTSTEP_GENERIC_HEAVY
damaged_dmi = null
+ leave_footprints = FALSE
/turf/open/misc/asteroid/snow/ice/break_tile()
return FALSE
@@ -327,3 +393,4 @@ GLOBAL_LIST_EMPTY(dug_up_basalt)
initial_gas_mix = "co2=173.4;n2=135.1;plasma=229.8;TEMP=351.9"
planetary_atmos = TRUE
+#undef DIG_SHEET_AMOUNT
diff --git a/code/game/turfs/open/floor/plating/misc_plating.dm b/code/game/turfs/open/floor/plating/misc_plating.dm
index 2b6fe12a158..750f47ecf11 100644
--- a/code/game/turfs/open/floor/plating/misc_plating.dm
+++ b/code/game/turfs/open/floor/plating/misc_plating.dm
@@ -20,14 +20,14 @@
base_icon_state = "alienpod1"
tiled_turf = FALSE
rust_resistance = RUST_RESISTANCE_ORGANIC
- // Not actually broken, just should never break...yeah.
- broken = TRUE
- damaged_dmi = null
/turf/open/floor/plating/abductor/Initialize(mapload)
. = ..()
icon_state = "alienpod[rand(1,9)]"
+/turf/open/floor/plating/abductor/break_tile()
+ return FALSE
+
/turf/open/floor/plating/abductor2
name = "alien plating"
icon_state = "alienplating"
diff --git a/code/game/turfs/open/sand.dm b/code/game/turfs/open/sand.dm
index 71ffa6be613..c57d9b047bd 100644
--- a/code/game/turfs/open/sand.dm
+++ b/code/game/turfs/open/sand.dm
@@ -9,6 +9,7 @@
clawfootstep = FOOTSTEP_SAND
heavyfootstep = FOOTSTEP_GENERIC_HEAVY
rust_resistance = RUST_RESISTANCE_REINFORCED
+ leave_footprints = TRUE
/turf/open/misc/beach/Initialize(mapload)
. = ..()
@@ -19,6 +20,33 @@
GLOB.preset_fish_sources[fish_source].spawn_reward_from_explosion(src, severity)
return FALSE
+/turf/open/misc/beach/add_footprint(mob/living/carbon/human/walker, movement_direction)
+ if(!SSmapping.level_trait(z, ZTRAIT_SANDSTORM))
+ return ..()
+
+ // if an active sand storm affecting this turf is currently in its main or wind down stage, skip footprint creation
+ for(var/datum/weather/sand_storm/active_weather in SSweather.processing)
+ if(active_weather.stage != MAIN_STAGE && active_weather.stage != WIND_DOWN_STAGE)
+ continue
+ if(!(loc in active_weather.impacted_areas))
+ continue
+ return
+
+ . = ..()
+ // when a sand storm enters its main stage, clear all of our footprints
+ for(var/sand_type in typesof(/datum/weather/sand_storm))
+ RegisterSignal(SSdcs, COMSIG_WEATHER_START(sand_type), PROC_REF(sand_clear_footprints), override = TRUE)
+
+/turf/open/misc/beach/proc/sand_clear_footprints(datum/source, datum/weather/storm)
+ SIGNAL_HANDLER
+
+ if(!(loc in storm.impacted_areas))
+ return
+
+ clear_footprints()
+ for(var/sand_type in typesof(/datum/weather/sand_storm))
+ UnregisterSignal(SSdcs, COMSIG_WEATHER_START(sand_type))
+
/turf/open/misc/beach/sand
gender = PLURAL
name = "sand"
diff --git a/code/game/turfs/open/snow.dm b/code/game/turfs/open/snow.dm
index 5c34a91038e..58d2d0912eb 100644
--- a/code/game/turfs/open/snow.dm
+++ b/code/game/turfs/open/snow.dm
@@ -13,6 +13,7 @@
barefootstep = FOOTSTEP_SAND
clawfootstep = FOOTSTEP_SAND
heavyfootstep = FOOTSTEP_GENERIC_HEAVY
+ leave_footprints = TRUE
/turf/open/misc/snow/Initialize(mapload)
. = ..()
@@ -21,6 +22,35 @@
/turf/open/misc/snow/broken_states()
return list("snow_dug")
+/turf/open/misc/snow/add_footprint(mob/living/carbon/human/walker, movement_direction)
+ if(HAS_TRAIT(walker, TRAIT_NO_SNOWPRINTS))
+ return
+ // skip the special logic if the level doesn't naturally have snowstorms
+ if(!SSmapping.level_trait(z, ZTRAIT_SNOWSTORM))
+ return ..()
+
+ // if an active snow storm affecting this turf is currently in its main or wind down stage, skip footprint creation
+ for(var/datum/weather/snow_storm/active_weather in SSweather.processing)
+ if(active_weather.stage != MAIN_STAGE && active_weather.stage != WIND_DOWN_STAGE)
+ continue
+ if(!(loc in active_weather.impacted_areas))
+ continue
+ return
+ . = ..()
+ // when a snow storm enters its main stage, clear all of our footprints
+ for(var/snow_type in typesof(/datum/weather/snow_storm))
+ RegisterSignal(SSdcs, COMSIG_WEATHER_START(snow_type), PROC_REF(snow_clear_footprints), override = TRUE)
+
+/turf/open/misc/snow/proc/snow_clear_footprints(datum/source, datum/weather/storm)
+ SIGNAL_HANDLER
+
+ if(!(loc in storm.impacted_areas))
+ return
+
+ clear_footprints()
+ for(var/snow_type in typesof(/datum/weather/snow_storm))
+ UnregisterSignal(SSdcs, COMSIG_WEATHER_START(snow_type))
+
/turf/open/misc/snow/actually_safe
slowdown = 0
planetary_atmos = FALSE
diff --git a/code/modules/antagonists/heretic/magic/shadow_cloak.dm b/code/modules/antagonists/heretic/magic/shadow_cloak.dm
index 825e1874c78..0ecd355ae98 100644
--- a/code/modules/antagonists/heretic/magic/shadow_cloak.dm
+++ b/code/modules/antagonists/heretic/magic/shadow_cloak.dm
@@ -141,7 +141,7 @@
animate(cloak_image, alpha = 255, 0.2 SECONDS)
owner.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/everyone, id, cloak_image)
// Add the relevant traits and modifiers
- owner.add_traits(list(TRAIT_UNKNOWN_APPEARANCE, TRAIT_UNKNOWN_VOICE, TRAIT_SILENT_FOOTSTEPS), TRAIT_STATUS_EFFECT(id))
+ owner.add_traits(list(TRAIT_UNKNOWN_APPEARANCE, TRAIT_UNKNOWN_VOICE, TRAIT_SILENT_FOOTSTEPS, TRAIT_NO_SNOWPRINTS), TRAIT_STATUS_EFFECT(id))
owner.add_movespeed_modifier(/datum/movespeed_modifier/shadow_cloak)
owner.add_actionspeed_modifier(/datum/actionspeed_modifier/shadow_cloak)
// Register signals to cause effects
@@ -157,7 +157,7 @@
owner.remove_alt_appearance(id)
QDEL_NULL(cloak_image)
// Remove traits and modifiers
- owner.remove_traits(list(TRAIT_UNKNOWN_APPEARANCE, TRAIT_UNKNOWN_VOICE, TRAIT_SILENT_FOOTSTEPS), TRAIT_STATUS_EFFECT(id))
+ owner.remove_traits(list(TRAIT_UNKNOWN_APPEARANCE, TRAIT_UNKNOWN_VOICE, TRAIT_SILENT_FOOTSTEPS, TRAIT_NO_SNOWPRINTS), TRAIT_STATUS_EFFECT(id))
owner.remove_movespeed_modifier(/datum/movespeed_modifier/shadow_cloak)
owner.remove_actionspeed_modifier(/datum/actionspeed_modifier/shadow_cloak)
// Clear signals
diff --git a/code/modules/clothing/shoes/boots.dm b/code/modules/clothing/shoes/boots.dm
index 17957869608..492a9800572 100644
--- a/code/modules/clothing/shoes/boots.dm
+++ b/code/modules/clothing/shoes/boots.dm
@@ -105,7 +105,7 @@
desc = "A pair of winter boots with special grips on the bottom, designed to prevent slipping on frozen surfaces."
icon_state = "iceboots"
inhand_icon_state = null
- clothing_traits = list(TRAIT_NO_SLIP_ICE, TRAIT_NO_SLIP_SLIDE)
+ clothing_traits = list(TRAIT_NO_SLIP_ICE, TRAIT_NO_SLIP_SLIDE, TRAIT_NO_SNOWPRINTS)
// A pair of ice boots intended for general crew EVA use - see EVA winter coat for comparison.
/obj/item/clothing/shoes/winterboots/ice_boots/eva
diff --git a/code/modules/clothing/shoes/wheelys.dm b/code/modules/clothing/shoes/wheelys.dm
index 63c72ad71ff..2282de5bc29 100644
--- a/code/modules/clothing/shoes/wheelys.dm
+++ b/code/modules/clothing/shoes/wheelys.dm
@@ -89,3 +89,4 @@
wheels = /obj/vehicle/ridden/scooter/skateboard/wheelys/skishoes
custom_premium_price = PAYCHECK_CREW * 1.6
custom_price = PAYCHECK_CREW * 1.6
+ clothing_traits = list(TRAIT_NO_SNOWPRINTS)
diff --git a/code/modules/vehicles/mecha/equipment/tools/mining_tools.dm b/code/modules/vehicles/mecha/equipment/tools/mining_tools.dm
index f8e49cf0949..a693fe33ab1 100644
--- a/code/modules/vehicles/mecha/equipment/tools/mining_tools.dm
+++ b/code/modules/vehicles/mecha/equipment/tools/mining_tools.dm
@@ -178,7 +178,7 @@
/turf/open/misc/asteroid/drill_act(obj/item/mecha_parts/mecha_equipment/drill/drill)
for(var/turf/open/misc/asteroid/floor in range(1, drill.chassis))
- if((get_dir(drill.chassis, floor) & drill.chassis.dir) && !floor.dug)
+ if((get_dir(drill.chassis, floor) & drill.chassis.dir) && floor.can_dig())
floor.getDug()
drill.log_message("Drilled through [src]", LOG_MECHA)
drill.move_ores()
diff --git a/icons/effects/footprints.dmi b/icons/effects/footprints.dmi
index 175b0c17f66024e3aeef29ceb438f37a3ef8c190..9c17931691853ef83bfe8cfc842cc18974dd3d27 100644
GIT binary patch
literal 5087
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
literal 4204
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