diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm
index 7d0a4ab1504..9da2c725a43 100644
--- a/code/__DEFINES/layers.dm
+++ b/code/__DEFINES/layers.dm
@@ -18,6 +18,7 @@
#define CLOSED_TURF_LAYER 2.05
#define BULLET_HOLE_LAYER 2.06
#define ABOVE_NORMAL_TURF_LAYER 2.08
+#define ABOVE_ICYOVERLAY_LAYER 2.11
#define LATTICE_LAYER 2.2
#define DISPOSAL_PIPE_LAYER 2.3
#define GAS_PIPE_HIDDEN_LAYER 2.35
diff --git a/code/datums/supplypacks.dm b/code/datums/supplypacks.dm
index a485e447f89..614cd5bfa7c 100644
--- a/code/datums/supplypacks.dm
+++ b/code/datums/supplypacks.dm
@@ -1720,6 +1720,14 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine
)
containername = "hygiene station crate"
+/datum/supply_packs/misc/snow_machine
+ name = "Snow Machine Crate"
+ cost = 20
+ contains = list(
+ /obj/machinery/snow_machine
+ )
+ special = TRUE
+
//////////////////////////////////////////////////////////////////////////////
//////////////////////////// Vending /////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
diff --git a/code/game/machinery/constructable_frame.dm b/code/game/machinery/constructable_frame.dm
index fe9b20e0e69..1935869fb53 100644
--- a/code/game/machinery/constructable_frame.dm
+++ b/code/game/machinery/constructable_frame.dm
@@ -330,6 +330,16 @@ to destroy them and players will be able to make replacements.
name = "circuit board (Freezer)"
to_chat(user, "You set the board to cooling.")
+/obj/item/circuitboard/snow_machine
+ name = "circuit board (snow machine)"
+ build_path = /obj/machinery/snow_machine
+ board_type = "machine"
+ origin_tech = "programming=2;materials=2"
+ frame_desc = "Requires 1 Matter Bin and 1 Micro Laser."
+ req_components = list(
+ /obj/item/stock_parts/matter_bin = 1,
+ /obj/item/stock_parts/micro_laser = 1)
+
/obj/item/circuitboard/biogenerator
name = "circuit board (Biogenerator)"
build_path = /obj/machinery/biogenerator
diff --git a/code/game/machinery/snow_machine.dm b/code/game/machinery/snow_machine.dm
new file mode 100644
index 00000000000..2ae3bf22977
--- /dev/null
+++ b/code/game/machinery/snow_machine.dm
@@ -0,0 +1,133 @@
+/obj/machinery/snow_machine
+ name = "snow machine"
+ desc = "Just add water and you too can have your own winter wonderland! Carol singers not included."
+ icon_state = "snow_machine_off"
+ density = TRUE
+ anchored = FALSE
+ layer = OBJ_LAYER
+ var/active = FALSE
+ var/power_used_this_cycle = 0
+ var/cooling_speed = 1
+ var/power_efficiency = 1
+ var/lower_temperature_limit = T0C - 10 //Set lower for a bigger freeze
+ var/infinite_snow = FALSE //Set this to have it not use water
+
+/obj/machinery/snow_machine/New()
+ ..()
+ create_reagents(300) //Makes 100 snow tiles!
+ reagents.add_reagent("water", 300) //But any reagent will do
+ reagents.flags |= REAGENT_NOREACT //Because a) this doesn't need to process and b) this way we can use any reagents without needing to worry about explosions and shit
+ container_type = REFILLABLE
+ component_parts = list()
+ component_parts += new /obj/item/circuitboard/snow_machine(null)
+ component_parts += new /obj/item/stock_parts/matter_bin(null)
+ component_parts += new /obj/item/stock_parts/micro_laser(null)
+ RefreshParts()
+
+/obj/machinery/snow_machine/examine(mob/user)
+ ..()
+ to_chat(user, "The internal reservoir indicates it is [infinite_snow ? "100" : round(reagents.total_volume / reagents.maximum_volume * 100)]% full.")
+
+/obj/machinery/snow_machine/RefreshParts()
+ power_efficiency = 0
+ cooling_speed = 0
+ for(var/obj/item/stock_parts/matter_bin/B in component_parts)
+ cooling_speed += B.rating
+ for(var/obj/item/stock_parts/micro_laser/L in component_parts)
+ power_efficiency += L.rating
+
+/obj/machinery/snow_machine/attack_hand(mob/user)
+ if(!powered() || !anchored)
+ return
+ if(turn_on_or_off(!active))
+ to_chat(user, "You [active ? "turn on" : "turn off"] [src].")
+ return ..()
+
+/obj/machinery/snow_machine/attackby(obj/item/I, mob/user)
+ if(isscrewdriver(I))
+ if(default_deconstruction_screwdriver(user, "snow_machine_openpanel", "snow_machine_off", I))
+ turn_on_or_off(FALSE)
+ else if(iscrowbar(I))
+ default_deconstruction_crowbar(I)
+ else if(iswrench(I))
+ var/obj/item/wrench/W = I
+ anchored = !anchored
+ to_chat(user, "You [anchored ? "tighten" : "loosen"] [src]'s wheels.")
+ playsound(loc, W.usesound, 50, TRUE)
+ turn_on_or_off(FALSE)
+ else
+ return ..()
+
+/obj/machinery/snow_machine/process()
+ if(power_used_this_cycle)
+ power_used_this_cycle /= power_efficiency
+ use_power(power_used_this_cycle)
+ power_used_this_cycle = 0
+ if(!active || !anchored)
+ return
+ if(!powered())
+ return
+ if(!reagents.has_reagent(reagents.get_master_reagent_id(), 3))
+ return //This means you don't need to top it up constantly to keep the nice snowclouds going
+ var/turf/T = get_turf(src)
+ if(isspaceturf(T) || T.density) //If the snowmachine is on a dense tile or in space, then it shouldn't be able to produce any snow and so will turn off
+ turn_on_or_off(FALSE, TRUE)
+ return
+ for(var/turf/TF in range(1, src))
+ if(issimulatedturf(TF))
+ var/turf/simulated/S = TF
+ affect_turf_temperature(S, cooling_speed)
+ if(prob(50))
+ continue
+ make_snowcloud(TF)
+
+/obj/machinery/snow_machine/power_change()
+ ..()
+ if(!powered())
+ turn_on_or_off(FALSE, TRUE)
+ update_icon()
+
+/obj/machinery/snow_machine/update_icon()
+ ..()
+ if(panel_open)
+ icon_state = "snow_machine_openpanel"
+ else
+ icon_state = "snow_machine_[active ? "on" : "off"]"
+
+/obj/machinery/snow_machine/proc/affect_turf_temperature(turf/T, modifier)
+ if(!issimulatedturf(T) || T.density)
+ return
+ var/turf/simulated/S = T
+ var/initial_temperature = S.air.temperature
+ if(initial_temperature <= lower_temperature_limit) //Can we actually cool this?
+ return
+ var/old_thermal_energy = S.air.thermal_energy()
+ var/amount_cooled = initial_temperature - modifier * 8000 / S.air.heat_capacity()
+ S.air.temperature = max(amount_cooled, lower_temperature_limit)
+ air_update_turf()
+ var/new_thermal_energy = S.air.thermal_energy()
+ power_used_this_cycle += (old_thermal_energy - new_thermal_energy) / 100
+
+/obj/machinery/snow_machine/proc/make_snowcloud(turf/T)
+ if(isspaceturf(T))
+ return
+ if(T.density)
+ return
+ if(issimulatedturf(T))
+ var/turf/simulated/S = T
+ if(S.air.temperature > T0C + 1)
+ return
+ if(locate(/obj/effect/snowcloud, T)) //Ice to see you
+ return
+ if(infinite_snow || !reagents.remove_reagent(reagents.get_master_reagent_id(), 3))
+ new /obj/effect/snowcloud(T, src)
+ power_used_this_cycle += 1000
+ return TRUE
+
+/obj/machinery/snow_machine/proc/turn_on_or_off(activate, give_message = FALSE)
+ active = activate ? TRUE : FALSE
+ if(!active && give_message)
+ visible_message("[src] switches off!")
+ playsound(loc, 'sound/machines/buzz-sigh.ogg', 50, FALSE)
+ update_icon()
+ return TRUE
diff --git a/code/game/objects/effects/decals/misc.dm b/code/game/objects/effects/decals/misc.dm
index e2f1399a922..52ad55c48a4 100644
--- a/code/game/objects/effects/decals/misc.dm
+++ b/code/game/objects/effects/decals/misc.dm
@@ -16,6 +16,7 @@
anchored = TRUE
layer = TURF_DECAL_LAYER
icon = 'icons/turf/snow.dmi'
+ icon_state = "snow"
/obj/effect/decal/snow/clean/edge
icon_state = "snow_corner"
diff --git a/code/game/objects/effects/snowcloud.dm b/code/game/objects/effects/snowcloud.dm
new file mode 100644
index 00000000000..a67bbd3c5f7
--- /dev/null
+++ b/code/game/objects/effects/snowcloud.dm
@@ -0,0 +1,140 @@
+/obj/effect/snowcloud
+ name = "snow cloud"
+ desc = "Let it snow, let it snow, let it snow!"
+ icon_state = "snowcloud"
+ layer = FLY_LAYER
+ anchored = TRUE
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ var/obj/machinery/snow_machine/parent_machine
+
+/obj/effect/snowcloud/New(turf, obj/machinery/snow_machine/SM)
+ ..()
+ processing_objects.Add(src)
+ if(SM && istype(SM))
+ parent_machine = SM
+
+/obj/effect/snowcloud/Destroy()
+ processing_objects.Remove(src)
+ return ..()
+
+/obj/effect/snowcloud/process()
+ if(QDELETED(parent_machine))
+ parent_machine = null
+ var/turf/T = get_turf(src)
+ if(isspaceturf(T))
+ qdel(src)
+ return
+ var/turf_hotness
+ if(issimulatedturf(T))
+ var/turf/simulated/S = T
+ turf_hotness = S.air.temperature
+ if(turf_hotness > T0C && prob(10 * (turf_hotness - T0C))) //Cloud disappears if it's too warm
+ qdel(src)
+ return
+ if(!parent_machine || !parent_machine.active || parent_machine.stat & NOPOWER) //All reasons a cloud could dissipate
+ if(prob(10))
+ qdel(src)
+ return
+ try_to_snow()
+ try_to_spread_cloud()
+ parent_machine.affect_turf_temperature(T, 0.25 * parent_machine.cooling_speed)
+
+/obj/effect/snowcloud/proc/try_to_snow()
+ var/turf/T = get_turf(src)
+ if(locate(/obj/effect/snow, T))
+ return
+ if(issimulatedturf(T))
+ var/turf/simulated/S = T
+ if(prob(75 + S.air.temperature - T0C)) //Colder turf = more chance of snow
+ return
+ new /obj/effect/snow(T)
+
+/obj/effect/snowcloud/proc/try_to_spread_cloud()
+ if(prob(95 - parent_machine.cooling_speed * 5)) //10 / 15 / 20 / 25% chance to spawn a new cloud
+ return
+ var/list/random_dirs = shuffle(cardinal)
+ for(var/potential in random_dirs)
+ var/turf/T = get_turf(get_step(src, potential))
+ if(isspaceturf(T) || T.density)
+ continue
+ if(!T.CanAtmosPass(T))
+ continue
+ if(parent_machine.make_snowcloud(T))
+ return
+
+
+//Snow stuff below
+
+/obj/effect/snow
+ desc = "Perfect for making snow angels, or throwing at other people!"
+ icon = 'icons/effects/effects.dmi'
+ icon_state = "snow"
+ layer = ABOVE_ICYOVERLAY_LAYER
+ anchored = TRUE
+
+/obj/effect/snow/New()
+ processing_objects.Add(src)
+ icon_state = "snow[rand(1,6)]"
+ ..()
+
+/obj/effect/snow/Destroy()
+ processing_objects.Remove(src)
+ return ..()
+
+/obj/effect/snow/process()
+ var/turf/T = get_turf(src)
+ if(isspaceturf(T))
+ qdel(src)
+ return
+ else if(issimulatedturf(T))
+ var/turf/simulated/S = T
+ if(S.air.temperature <= T0C)
+ return
+ if(prob(10 + S.air.temperature - T0C))
+ qdel(src)
+
+/obj/effect/snow/attack_hand(mob/living/carbon/human/user)
+ if(!istype(user)) //Nonhumans don't have the balls to fight in the snow
+ return
+ user.changeNext_move(CLICK_CD_MELEE)
+ var/obj/item/snowball/SB = new(get_turf(user))
+ user.put_in_hands(SB)
+ to_chat(user, "You scoop up some snow and make \a [SB]!")
+
+/obj/effect/snow/attackby(obj/item/I, mob/user)
+ if(istype(I, /obj/item/shovel))
+ var/obj/item/shovel/S = I
+ user.visible_message("[user] is clearing away [src]...", "You begin clearing away [src]...", "You hear a wettish digging sound.")
+ playsound(loc, S.usesound, 50, TRUE)
+ if(!do_after(user, 50 * S.toolspeed, target = src))
+ return
+ user.visible_message("[user] clears away [src]!", "You clear away [src]!")
+ qdel(src)
+ else
+ return ..()
+
+/obj/effect/snow/fire_act()
+ qdel(src)
+
+/obj/effect/snow/ex_act(severity)
+ if(severity == 3 && prob(50))
+ return
+ qdel(src)
+
+/obj/item/snowball
+ name = "snowball"
+ desc = "Get ready for a snowball fight!"
+ force = 0
+ throwforce = 10
+ icon_state = "snowball"
+ damtype = STAMINA
+
+/obj/item/snowball/throw_impact(atom/target)
+ ..()
+ qdel(src)
+
+/obj/item/snowball/fire_act()
+ qdel(src)
+
+/obj/item/snowball/ex_act(severity)
+ qdel(src)
diff --git a/code/modules/holiday/christmas.dm b/code/modules/holiday/christmas.dm
index 7b00171b348..5a9a8605442 100644
--- a/code/modules/holiday/christmas.dm
+++ b/code/modules/holiday/christmas.dm
@@ -6,6 +6,8 @@
new /obj/item/a_gift(T)
for(var/mob/living/simple_animal/pet/corgi/Ian/Ian in GLOB.mob_list)
Ian.place_on_head(new /obj/item/clothing/head/helmet/space/santahat(Ian))
+ var/datum/supply_packs/xmas = SSshuttle.supply_packs["[/datum/supply_packs/misc/snow_machine]"] //Snow!
+ xmas.special_enabled = TRUE
/datum/holiday/xmas/handle_event()
spawnTree()
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index 060d8b77a27..95099bb00f3 100644
Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ
diff --git a/icons/obj/items.dmi b/icons/obj/items.dmi
index 9284e790cf8..07680638b6b 100644
Binary files a/icons/obj/items.dmi and b/icons/obj/items.dmi differ
diff --git a/icons/obj/stationobjs.dmi b/icons/obj/stationobjs.dmi
index 0b7ae5b5600..c8a9abd40f3 100755
Binary files a/icons/obj/stationobjs.dmi and b/icons/obj/stationobjs.dmi differ
diff --git a/paradise.dme b/paradise.dme
index 711a157e457..fcc4aa9c054 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -611,6 +611,7 @@
#include "code\game\machinery\shieldgen.dm"
#include "code\game\machinery\Sleeper.dm"
#include "code\game\machinery\slotmachine.dm"
+#include "code\game\machinery\snow_machine.dm"
#include "code\game\machinery\spaceheater.dm"
#include "code\game\machinery\status_display.dm"
#include "code\game\machinery\suit_storage_unit.dm"
@@ -746,6 +747,7 @@
#include "code\game\objects\effects\misc.dm"
#include "code\game\objects\effects\overlays.dm"
#include "code\game\objects\effects\portals.dm"
+#include "code\game\objects\effects\snowcloud.dm"
#include "code\game\objects\effects\spiders.dm"
#include "code\game\objects\effects\step_triggers.dm"
#include "code\game\objects\effects\decals\cleanable.dm"