diff --git a/_maps/map_files/cyberiad/cyberiad.dmm b/_maps/map_files/cyberiad/cyberiad.dmm
index 998ae80eada..1108d55aa5d 100644
--- a/_maps/map_files/cyberiad/cyberiad.dmm
+++ b/_maps/map_files/cyberiad/cyberiad.dmm
@@ -5221,7 +5221,7 @@
"bWu" = (/obj/effect/spawner/window/reinforced,/obj/structure/sign/examroom,/turf/simulated/floor/plating,/area/medical/sleeper)
"bWv" = (/turf/simulated/wall/r_wall,/area/crew_quarters/fitness{name = "\improper Arcade"})
"bWw" = (/obj/machinery/door/firedoor,/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/machinery/atmospherics/pipe/simple/hidden/supply,/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/structure/disposalpipe/segment,/turf/simulated/floor/plasteel{icon_state = "white"},/area/medical/sleeper)
-"bWx" = (/obj/machinery/door/firedoor,/turf/simulated/floor/plasteel{icon_state = "white"},/area/medical/sleeper)
+"bWx" = (/obj/machinery/door/firedoor,/turf/simulated/floor/plasteel{tag = "icon-whiteblue (WEST)"; icon_state = "whiteblue"; dir = 8},/area/medical/sleeper)
"bWy" = (/obj/effect/spawner/window/reinforced,/turf/simulated/floor/plating,/area/medical/cryo)
"bWz" = (/obj/structure/disposalpipe/segment,/turf/simulated/floor/plasteel{dir = 1; icon_state = "whitebluecorner"; tag = "icon-whitebluecorner"},/area/medical/medbay2)
"bWA" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply,/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"},/turf/simulated/floor/plasteel{icon_state = "white"},/area/medical/medbay2)
@@ -5282,8 +5282,8 @@
"bXD" = (/obj/effect/spawner/window/reinforced,/turf/simulated/floor/plating,/area/medical/sleeper)
"bXE" = (/obj/item/roller,/turf/simulated/floor/plasteel{tag = "icon-whiteblue (NORTH)"; icon_state = "whiteblue"; dir = 1},/area/medical/sleeper)
"bXF" = (/obj/machinery/light_switch{pixel_x = -23; pixel_y = 0},/obj/machinery/sleeper{dir = 4},/turf/simulated/floor/plasteel{tag = "icon-whiteblue (NORTHWEST)"; icon_state = "whiteblue"; dir = 9},/area/medical/sleeper)
-"bXG" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/machinery/atmospherics/pipe/simple/hidden/supply,/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/structure/disposalpipe/segment,/turf/simulated/floor/plasteel{dir = 2; icon_state = "cmo"},/area/medical/sleeper)
-"bXH" = (/turf/simulated/floor/plasteel{dir = 2; icon_state = "cmo"},/area/medical/sleeper)
+"bXG" = (/obj/machinery/door/firedoor,/turf/simulated/floor/plasteel{dir = 4; icon_state = "whiteblue"; tag = "icon-whitehall (WEST)"},/area/medical/sleeper)
+"bXH" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/machinery/atmospherics/pipe/simple/hidden/supply,/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/structure/disposalpipe/segment,/turf/simulated/floor/plasteel{icon_state = "white"},/area/medical/sleeper)
"bXI" = (/obj/machinery/vending/medical,/turf/simulated/floor/plasteel{dir = 5; icon_state = "whiteblue"; tag = "icon-whitehall (WEST)"},/area/medical/sleeper)
"bXJ" = (/obj/structure/table,/obj/item/reagent_containers/syringe/antiviral,/obj/item/reagent_containers/syringe/charcoal,/obj/item/reagent_containers/syringe/insulin,/obj/item/reagent_containers/glass/bottle/morphine,/obj/item/reagent_containers/glass/bottle/epinephrine,/obj/item/reagent_containers/syringe,/obj/item/stack/medical/bruise_pack/advanced{pixel_x = 6; pixel_y = 6},/obj/item/stack/medical/ointment/advanced{pixel_x = 6; pixel_y = 8},/obj/item/reagent_containers/food/pill/patch/styptic{pixel_y = 6},/obj/item/reagent_containers/food/pill/patch/styptic{pixel_x = 2; pixel_y = 8},/obj/item/reagent_containers/food/pill/patch/silver_sulf{pixel_x = -8; pixel_y = 6},/obj/item/reagent_containers/food/pill/patch/silver_sulf{pixel_x = -8; pixel_y = 8},/obj/item/storage/pill_bottle/painkillers{pixel_x = -6; pixel_y = -3},/turf/simulated/floor/plasteel{tag = "icon-whiteblue (NORTH)"; icon_state = "whiteblue"; dir = 1},/area/medical/sleeper)
"bXK" = (/obj/item/twohanded/required/kirbyplants,/obj/machinery/firealarm{dir = 8; pixel_x = -24},/turf/simulated/floor/plasteel{icon_state = "freezerfloor"},/area/medical/cryo)
@@ -5361,13 +5361,13 @@
"bZe" = (/obj/machinery/camera{c_tag = "Gravity Generator Foyer"; dir = 1; network = list("SS13")},/obj/machinery/alarm{dir = 4; icon_state = "alarm0"; pixel_x = -22},/obj/structure/closet/radiation,/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/machinery/light,/turf/simulated/floor/plasteel,/area/engine/gravitygenerator)
"bZf" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/obj/structure/disposalpipe/sortjunction{dir = 8; icon_state = "pipe-j2s"; name = "Genetics"; sortType = 23},/obj/machinery/atmospherics/pipe/simple/hidden{dir = 4},/obj/machinery/hologram/holopad,/turf/simulated/floor/plasteel{icon_state = "freezerfloor"},/area/medical/genetics_cloning)
"bZg" = (/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"; tag = ""},/obj/structure/cable{d1 = 2; d2 = 4; icon_state = "2-4"; tag = ""},/obj/structure/disposalpipe/segment{dir = 4; icon_state = "pipe-c"},/obj/machinery/atmospherics/pipe/simple/hidden/supply,/obj/machinery/status_display{density = 0; layer = 4; pixel_x = -32; pixel_y = 0},/turf/simulated/floor/plasteel,/area/hallway/primary/central/se)
-"bZh" = (/obj/structure/table,/obj/item/roller,/obj/machinery/firealarm{dir = 8; pixel_x = -24},/obj/item/soap/nanotrasen,/turf/simulated/floor/plasteel{tag = "icon-whiteblue (NORTHWEST)"; icon_state = "whiteblue"; dir = 9},/area/medical/sleeper)
-"bZi" = (/obj/structure/closet/crate/freezer,/obj/item/reagent_containers/blood/empty,/obj/item/reagent_containers/blood/empty,/obj/item/reagent_containers/blood/AMinus,/obj/item/reagent_containers/blood/APlus,/obj/item/reagent_containers/blood/BMinus,/obj/item/reagent_containers/blood/BPlus,/obj/item/reagent_containers/blood/OMinus,/obj/item/reagent_containers/blood/OPlus,/obj/item/reagent_containers/blood/random,/obj/item/reagent_containers/blood/random,/obj/item/reagent_containers/blood/random,/obj/structure/window/reinforced{dir = 4},/turf/simulated/floor/plasteel{tag = "icon-whiteblue (NORTHEAST)"; icon_state = "whiteblue"; dir = 5},/area/medical/sleeper)
-"bZj" = (/obj/structure/sink{icon_state = "sink"; dir = 8; pixel_x = -12; pixel_y = 2},/turf/simulated/floor/plasteel{tag = "icon-whiteblue (WEST)"; icon_state = "whiteblue"; dir = 8},/area/medical/sleeper)
-"bZk" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/machinery/atmospherics/pipe/manifold/hidden/supply{dir = 4; initialize_directions = 11; level = 1},/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/machinery/hologram/holopad,/obj/structure/disposalpipe/segment{dir = 1; icon_state = "pipe-c"},/turf/simulated/floor/plasteel{dir = 2; icon_state = "cmo"},/area/medical/sleeper)
-"bZl" = (/obj/machinery/atmospherics/unary/vent_pump{dir = 4; on = 1},/turf/simulated/floor/plasteel{dir = 2; icon_state = "cmo"},/area/medical/sleeper)
+"bZh" = (/turf/simulated/floor/plasteel{dir = 1; icon_state = "whitebluecorner"; tag = "icon-whitebluecorner"},/area/medical/sleeper)
+"bZi" = (/turf/simulated/floor/plasteel{dir = 4; icon_state = "whitebluecorner"; tag = "icon-whitebluecorner"},/area/medical/sleeper)
+"bZj" = (/obj/structure/sink{dir = 8; icon_state = "sink"; pixel_x = -12; pixel_y = 0},/obj/machinery/firealarm{dir = 8; pixel_x = -24},/turf/simulated/floor/plasteel{tag = "icon-whiteblue (NORTHWEST)"; icon_state = "whiteblue"; dir = 9},/area/medical/sleeper)
+"bZk" = (/turf/simulated/floor/plasteel{icon_state = "white"},/area/medical/sleeper)
+"bZl" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/machinery/atmospherics/pipe/manifold/hidden/supply{dir = 4; initialize_directions = 11; level = 1},/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/machinery/hologram/holopad,/obj/structure/disposalpipe/segment{dir = 1; icon_state = "pipe-c"},/turf/simulated/floor/plasteel{icon_state = "white"},/area/medical/sleeper)
"bZm" = (/obj/structure/disposalpipe/segment{dir = 4},/turf/simulated/floor/plasteel{dir = 4; icon_state = "whiteblue"; tag = "icon-whitehall (WEST)"},/area/medical/sleeper)
-"bZn" = (/obj/machinery/atmospherics/unary/vent_scrubber{on = 1; scrub_N2O = 1; scrub_Toxins = 1},/obj/structure/disposalpipe/junction{dir = 8; icon_state = "pipe-j2"; tag = "icon-pipe-j1 (WEST)"},/turf/simulated/floor/plasteel{dir = 2; icon_state = "cmo"},/area/medical/sleeper)
+"bZn" = (/obj/machinery/atmospherics/unary/vent_pump{dir = 4; on = 1},/turf/simulated/floor/plasteel{icon_state = "white"},/area/medical/sleeper)
"bZo" = (/obj/structure/disposalpipe/segment{dir = 2; icon_state = "pipe-c"},/turf/simulated/floor/plasteel{icon_state = "freezerfloor"},/area/medical/cryo)
"bZp" = (/obj/machinery/door/firedoor,/obj/structure/disposalpipe/segment{dir = 4},/turf/simulated/floor/plasteel{icon_state = "freezerfloor"},/area/medical/cryo)
"bZq" = (/obj/machinery/atmospherics/pipe/manifold/hidden{dir = 1},/obj/machinery/hologram/holopad,/turf/simulated/floor/plasteel{icon_state = "freezerfloor"},/area/medical/cryo)
@@ -5433,14 +5433,14 @@
"cay" = (/obj/machinery/light{dir = 4; icon_state = "tube1"},/obj/machinery/atmospherics/pipe/simple/hidden/supply{level = 1},/obj/structure/table,/turf/simulated/floor/plasteel,/area/engine/break_room)
"caz" = (/obj/structure/sign/poster/random{pixel_x = -32; pixel_y = 0},/obj/machinery/light{icon_state = "tube1"; dir = 8},/turf/simulated/floor/plasteel{tag = "icon-whiteblue (WEST)"; icon_state = "whiteblue"; dir = 8},/area/medical/sleeper)
"caA" = (/turf/simulated/wall,/area/medical/sleeper)
-"caB" = (/obj/machinery/door/window/eastright{base_state = "left"; dir = 4; icon_state = "left"; name = "Blood Transfusion Ward"; req_access_txt = "5"},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/turf/simulated/floor/plasteel{icon_state = "white"},/area/medical/sleeper)
+"caB" = (/obj/machinery/atmospherics/unary/vent_scrubber{on = 1; scrub_N2O = 1; scrub_Toxins = 1},/obj/structure/disposalpipe/junction{dir = 8; icon_state = "pipe-j2"; tag = "icon-pipe-j1 (WEST)"},/turf/simulated/floor/plasteel{icon_state = "white"},/area/medical/sleeper)
"caC" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/obj/machinery/atmospherics/unary/vent_pump{dir = 4; on = 1},/turf/simulated/floor/plasteel{icon_state = "white"},/area/medical/sleeper)
-"caD" = (/obj/structure/cable{d1 = 2; d2 = 4; icon_state = "2-4"},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/turf/simulated/floor/plasteel{dir = 2; icon_state = "cmo"},/area/medical/sleeper)
+"caD" = (/obj/structure/cable{d1 = 2; d2 = 4; icon_state = "2-4"},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/turf/simulated/floor/plasteel{icon_state = "white"},/area/medical/sleeper)
"caE" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/turf/simulated/floor/plasteel{icon_state = "white"},/area/medical/sleeper)
-"caF" = (/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/structure/cable{d1 = 1; d2 = 4; icon_state = "1-4"; tag = "90Curve"},/obj/structure/cable{d1 = 1; d2 = 8; icon_state = "1-8"; tag = ""},/obj/structure/disposalpipe/segment{dir = 4; icon_state = "pipe-c"},/obj/machinery/atmospherics/pipe/manifold4w/hidden/supply,/obj/machinery/atmospherics/pipe/manifold4w/hidden/scrubbers,/turf/simulated/floor/plasteel{dir = 2; icon_state = "cmo"},/area/medical/sleeper)
-"caG" = (/obj/effect/landmark/start{name = "Medical Doctor"},/obj/structure/cable{d1 = 4; d2 = 8; icon_state = "4-8"; tag = ""},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/turf/simulated/floor/plasteel{dir = 2; icon_state = "cmo"},/area/medical/sleeper)
+"caF" = (/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/structure/cable{d1 = 1; d2 = 4; icon_state = "1-4"; tag = "90Curve"},/obj/structure/cable{d1 = 1; d2 = 8; icon_state = "1-8"; tag = ""},/obj/structure/disposalpipe/segment{dir = 4; icon_state = "pipe-c"},/obj/machinery/atmospherics/pipe/manifold4w/hidden/supply,/obj/machinery/atmospherics/pipe/manifold4w/hidden/scrubbers,/turf/simulated/floor/plasteel{icon_state = "white"},/area/medical/sleeper)
+"caG" = (/obj/effect/landmark/start{name = "Medical Doctor"},/obj/structure/cable{d1 = 4; d2 = 8; icon_state = "4-8"; tag = ""},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/turf/simulated/floor/plasteel{icon_state = "white"},/area/medical/sleeper)
"caH" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/obj/structure/cable{d1 = 4; d2 = 8; icon_state = "4-8"; pixel_x = 0; tag = ""},/turf/simulated/floor/plasteel{dir = 4; icon_state = "whiteblue"; tag = "icon-whitehall (WEST)"},/area/medical/sleeper)
-"caI" = (/obj/machinery/atmospherics/pipe/manifold/hidden/scrubbers,/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/obj/structure/cable{d1 = 4; d2 = 8; icon_state = "4-8"; pixel_x = 0; tag = ""},/obj/structure/disposalpipe/segment{dir = 8; icon_state = "pipe-c"},/turf/simulated/floor/plasteel{dir = 2; icon_state = "cmo"},/area/medical/sleeper)
+"caI" = (/obj/machinery/atmospherics/pipe/manifold/hidden/scrubbers,/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/obj/structure/cable{d1 = 4; d2 = 8; icon_state = "4-8"; pixel_x = 0; tag = ""},/obj/structure/disposalpipe/segment{dir = 8; icon_state = "pipe-c"},/turf/simulated/floor/plasteel{icon_state = "white"},/area/medical/sleeper)
"caJ" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/obj/structure/cable{d1 = 4; d2 = 8; icon_state = "4-8"; pixel_x = 0; tag = ""},/obj/structure/disposalpipe/segment,/turf/simulated/floor/plasteel{icon_state = "freezerfloor"},/area/medical/cryo)
"caK" = (/obj/machinery/door/firedoor,/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/obj/structure/cable{d1 = 4; d2 = 8; icon_state = "4-8"; pixel_x = 0; tag = ""},/obj/structure/extinguisher_cabinet{pixel_x = 0; pixel_y = -30},/turf/simulated/floor/plasteel{icon_state = "freezerfloor"},/area/medical/cryo)
"caL" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 4},/obj/structure/cable{d1 = 4; d2 = 8; icon_state = "4-8"; pixel_x = 0; tag = ""},/obj/machinery/atmospherics/pipe/manifold4w/hidden,/turf/simulated/floor/plasteel{icon_state = "freezerfloor"},/area/medical/cryo)
@@ -5480,7 +5480,7 @@
"cbt" = (/obj/machinery/navbeacon{codes_txt = "delivery"; dir = 4; location = "Medbay"},/obj/structure/plasticflaps{opacity = 1},/turf/simulated/floor/plating,/area/medical/sleeper)
"cbu" = (/obj/structure/lamarr,/turf/simulated/floor/plasteel{dir = 4; icon_state = "warnwhite"; tag = "icon-warnwhite (NORTH)"},/area/crew_quarters/hor)
"cbv" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply,/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"},/turf/simulated/floor/plating,/area/maintenance/asmaint)
-"cbw" = (/obj/machinery/iv_drip,/turf/simulated/floor/plasteel{tag = "icon-whiteblue"; icon_state = "whiteblue"},/area/medical/sleeper)
+"cbw" = (/obj/structure/table,/obj/item/roller,/obj/item/soap/nanotrasen,/turf/simulated/floor/plasteel{dir = 10; icon_state = "whiteblue"; tag = "icon-whitehall (WEST)"},/area/medical/sleeper)
"cbx" = (/obj/structure/stool/bed/chair/comfy/black{dir = 4},/turf/simulated/shuttle/floor{icon_state = "floor4"},/area/shuttle/administration)
"cby" = (/obj/machinery/computer/shuttle/admin,/turf/simulated/shuttle/floor{icon_state = "floor4"},/area/shuttle/administration)
"cbz" = (/obj/machinery/door/airlock/centcom{id_tag = "adminshuttle"; name = "Bridge"; opacity = 1; req_access_txt = "106"},/turf/simulated/shuttle/floor{icon_state = "floor4"},/area/shuttle/administration)
@@ -5510,7 +5510,7 @@
"cbX" = (/obj/machinery/atmospherics/pipe/manifold/hidden/scrubbers{level = 1},/obj/structure/disposalpipe/junction{tag = "icon-pipe-j1 (EAST)"; icon_state = "pipe-j1"; dir = 4},/turf/simulated/floor/plasteel{dir = 1; icon_state = "blue"},/area/hallway/primary/central/south)
"cbY" = (/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"; tag = ""},/obj/structure/disposalpipe/segment{dir = 1; icon_state = "pipe-c"},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/turf/simulated/floor/plasteel{dir = 1; icon_state = "blue"},/area/hallway/primary/central/south)
"cbZ" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{req_access_txt = "0"},/obj/structure/disposalpipe/segment{dir = 4},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/turf/simulated/floor/plasteel,/area/hallway/primary/central/south)
-"cca" = (/obj/structure/closet/crate/freezer,/obj/item/reagent_containers/blood/empty,/obj/item/reagent_containers/blood/empty,/obj/item/reagent_containers/blood/AMinus,/obj/item/reagent_containers/blood/APlus,/obj/item/reagent_containers/blood/BMinus,/obj/item/reagent_containers/blood/BPlus,/obj/item/reagent_containers/blood/OMinus,/obj/item/reagent_containers/blood/OPlus,/obj/item/reagent_containers/blood/random,/obj/item/reagent_containers/blood/random,/obj/item/reagent_containers/blood/random,/obj/structure/window/reinforced{dir = 4},/turf/simulated/floor/plasteel{tag = "icon-whiteblue (SOUTHEAST)"; icon_state = "whiteblue"; dir = 6},/area/medical/sleeper)
+"cca" = (/obj/structure/closet/crate/freezer,/obj/item/reagent_containers/blood/empty,/obj/item/reagent_containers/blood/empty,/obj/item/reagent_containers/blood/AMinus,/obj/item/reagent_containers/blood/APlus,/obj/item/reagent_containers/blood/BMinus,/obj/item/reagent_containers/blood/BPlus,/obj/item/reagent_containers/blood/OMinus,/obj/item/reagent_containers/blood/OPlus,/obj/item/reagent_containers/blood/random,/obj/item/reagent_containers/blood/random,/obj/item/reagent_containers/blood/random,/obj/machinery/iv_drip,/turf/simulated/floor/plasteel{tag = "icon-whiteblue"; icon_state = "whiteblue"},/area/medical/sleeper)
"ccb" = (/obj/machinery/ai_status_display{pixel_y = 32},/obj/structure/disposalpipe/segment{dir = 4},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/turf/simulated/floor/plasteel,/area/hallway/primary/central/south)
"ccc" = (/obj/structure/disposalpipe/segment{dir = 4},/obj/machinery/atmospherics/pipe/manifold/hidden/scrubbers{level = 1},/obj/item/radio/intercom{broadcasting = 0; name = "station intercom (General)"; pixel_y = 25},/turf/simulated/floor/plasteel,/area/hallway/primary/central/south)
"ccd" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/obj/effect/decal/warning_stripes/north,/turf/simulated/floor/plasteel,/area/hallway/primary/central/south)
@@ -5520,15 +5520,15 @@
"cch" = (/obj/machinery/atmospherics/pipe/manifold/hidden/scrubbers{dir = 1; initialize_directions = 11; level = 1},/turf/simulated/floor/plasteel,/area/hallway/primary/central/south)
"cci" = (/obj/machinery/light{dir = 1},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/turf/simulated/floor/plasteel,/area/hallway/primary/central/south)
"ccj" = (/obj/machinery/camera{c_tag = "Central Primary Hallway South"; dir = 2; network = list("SS13")},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/turf/simulated/floor/plasteel,/area/hallway/primary/central/south)
-"cck" = (/obj/machinery/iv_drip,/obj/machinery/ai_status_display{pixel_y = -32},/turf/simulated/floor/plasteel{tag = "icon-whiteblue"; icon_state = "whiteblue"},/area/medical/sleeper)
+"cck" = (/obj/structure/closet/crate/freezer,/obj/item/reagent_containers/blood/empty,/obj/item/reagent_containers/blood/empty,/obj/item/reagent_containers/blood/AMinus,/obj/item/reagent_containers/blood/APlus,/obj/item/reagent_containers/blood/BMinus,/obj/item/reagent_containers/blood/BPlus,/obj/item/reagent_containers/blood/OMinus,/obj/item/reagent_containers/blood/OPlus,/obj/item/reagent_containers/blood/random,/obj/item/reagent_containers/blood/random,/obj/item/reagent_containers/blood/random,/obj/machinery/iv_drip,/obj/machinery/ai_status_display{pixel_y = -32},/turf/simulated/floor/plasteel{tag = "icon-whiteblue"; icon_state = "whiteblue"},/area/medical/sleeper)
"ccl" = (/obj/machinery/door/firedoor,/turf/simulated/floor/plasteel,/area/hallway/primary/central/south)
"ccm" = (/obj/machinery/door/airlock/public/glass{name = "Central Access"},/turf/simulated/floor/plasteel,/area/hallway/primary/central/se)
"ccn" = (/obj/machinery/atm{pixel_y = 32},/turf/simulated/floor/plasteel,/area/hallway/primary/central/se)
"cco" = (/obj/machinery/newscaster{pixel_y = 32},/turf/simulated/floor/plasteel,/area/hallway/primary/central/se)
"ccp" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 6},/turf/simulated/floor/plasteel,/area/hallway/primary/central/se)
-"ccq" = (/obj/machinery/camera{c_tag = "Medbay Treatment Center"; dir = 1; network = list("SS13"); pixel_x = 0},/obj/machinery/light,/obj/machinery/power/apc{dir = 2; name = "south bump"; pixel_y = -24},/obj/structure/cable,/turf/simulated/floor/plasteel{tag = "icon-whiteblue"; icon_state = "whiteblue"},/area/medical/sleeper)
+"ccq" = (/obj/machinery/camera{c_tag = "Medbay Treatment Center"; dir = 1; network = list("SS13"); pixel_x = 0},/obj/machinery/light,/obj/machinery/power/apc{dir = 2; name = "south bump"; pixel_y = -24},/obj/structure/cable,/obj/item/roller,/turf/simulated/floor/plasteel{tag = "icon-whiteblue"; icon_state = "whiteblue"},/area/medical/sleeper)
"ccr" = (/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"; tag = ""},/obj/structure/disposalpipe/segment,/obj/machinery/atmospherics/pipe/simple/hidden/supply{dir = 9; pixel_y = 0},/turf/simulated/floor/plasteel,/area/hallway/primary/central/se)
-"ccs" = (/obj/machinery/sleeper{dir = 4},/obj/item/radio/intercom{name = "station intercom (General)"; pixel_y = -28},/turf/simulated/floor/plasteel{tag = "icon-whiteblue (SOUTHWEST)"; icon_state = "whiteblue"; dir = 10},/area/medical/sleeper)
+"ccs" = (/obj/machinery/sleeper{dir = 4},/obj/item/radio/intercom{name = "station intercom (General)"; pixel_y = -28},/turf/simulated/floor/plasteel{tag = "icon-whiteblue"; icon_state = "whiteblue"},/area/medical/sleeper)
"cct" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/machinery/atmospherics/pipe/simple/hidden/supply,/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/structure/disposalpipe/segment,/turf/simulated/floor/plasteel{tag = "icon-whiteblue"; icon_state = "whiteblue"},/area/medical/sleeper)
"ccu" = (/turf/simulated/floor/plasteel{tag = "icon-whiteblue"; icon_state = "whiteblue"},/area/medical/sleeper)
"ccv" = (/obj/machinery/light{dir = 4},/obj/machinery/alarm{dir = 1; pixel_y = -22},/obj/machinery/body_scanconsole,/turf/simulated/floor/plasteel{dir = 6; icon_state = "whiteblue"; tag = "icon-whitehall (WEST)"},/area/medical/sleeper)
@@ -5978,8 +5978,8 @@
"ckX" = (/turf/simulated/floor/plasteel{tag = "icon-whiteblue (NORTH)"; icon_state = "whiteblue"; dir = 1},/area/medical/surgery1)
"ckY" = (/obj/machinery/firealarm{dir = 8; pixel_x = -24},/obj/machinery/power/apc{dir = 2; name = "south bump"; pixel_y = -24},/obj/structure/cable,/turf/simulated/floor/plasteel{dir = 10; icon_state = "darkblue"},/area/medical/ward)
"ckZ" = (/obj/structure/disposalpipe/segment,/obj/machinery/atmospherics/pipe/manifold/hidden/supply{dir = 4; initialize_directions = 11; level = 1},/obj/machinery/atmospherics/pipe/manifold/hidden/scrubbers{dir = 8; initialize_directions = 11; level = 1},/turf/simulated/floor/plasteel{icon_state = "dark"},/area/medical/ward)
-"cla" = (/obj/machinery/atmospherics/unary/vent_pump{dir = 4; on = 1},/obj/machinery/alarm{dir = 1; pixel_y = -22},/turf/simulated/floor/plasteel{dir = 2; icon_state = "darkblue"},/area/medical/ward)
-"clb" = (/obj/machinery/atmospherics/unary/vent_scrubber{dir = 8; on = 1},/obj/machinery/light_switch{pixel_x = 27},/turf/simulated/floor/plasteel{dir = 6; icon_state = "darkblue"},/area/medical/ward)
+"cla" = (/obj/machinery/atmospherics/unary/vent_pump{dir = 4; on = 1},/turf/simulated/floor/plasteel{icon_state = "dark"},/area/medical/ward)
+"clb" = (/obj/machinery/atmospherics/unary/vent_scrubber{dir = 8; on = 1},/obj/machinery/light_switch{pixel_x = 27},/obj/machinery/alarm{dir = 1; pixel_y = -22},/turf/simulated/floor/plasteel{dir = 6; icon_state = "darkblue"},/area/medical/ward)
"clc" = (/obj/machinery/bodyscanner,/obj/machinery/firealarm{dir = 1; pixel_y = -24},/turf/simulated/floor/plasteel{tag = "icon-whiteblue (NORTH)"; icon_state = "whiteblue"; dir = 1},/area/medical/surgery2)
"cld" = (/obj/structure/closet/secure_closet/medical2,/turf/simulated/floor/plasteel{dir = 4; icon_state = "whitebluecorner"; tag = "icon-whitebluecorner"},/area/medical/surgery2)
"cle" = (/obj/machinery/disposal,/obj/structure/disposalpipe/trunk{dir = 1},/obj/machinery/light_switch{pixel_x = 23; pixel_y = -5},/obj/machinery/holosign_switch{id = "surgery2"; pixel_x = 23; pixel_y = 4},/turf/simulated/floor/plasteel{tag = "icon-whiteblue (NORTH)"; icon_state = "whiteblue"; dir = 1},/area/medical/surgery2)
@@ -6042,7 +6042,7 @@
"cmj" = (/obj/machinery/computer/security/telescreen{desc = "Used for watching the singularity chamber."; dir = 8; layer = 4; name = "Singularity Engine Telescreen"; network = list("Singularity"); pixel_x = 0; pixel_y = 30},/obj/structure/stool/bed/chair/office/dark{dir = 4},/turf/simulated/floor/plasteel,/area/engine/controlroom)
"cmk" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply,/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"; tag = ""},/obj/machinery/light/small{dir = 8},/turf/simulated/floor/plating,/area/maintenance/asmaint)
"cml" = (/obj/machinery/door/firedoor,/obj/machinery/door/airlock/medical{name = "Operating Theatre Storage"; req_access_txt = "45"},/turf/simulated/floor/plasteel{icon_state = "showroomfloor"},/area/medical/surgery1)
-"cmm" = (/obj/machinery/door/firedoor,/obj/structure/disposalpipe/segment,/obj/machinery/door/airlock/medical/glass{id_tag = ""; name = "Medbay Virology Lobby"; req_access_txt = "5"},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/machinery/atmospherics/pipe/simple/hidden/supply,/turf/simulated/floor/plasteel{icon_state = "white"},/area/medical/virology/lab{name = "\improper Virology Lobby"})
+"cmm" = (/obj/machinery/door/firedoor,/obj/structure/disposalpipe/segment,/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/machinery/atmospherics/pipe/simple/hidden/supply,/obj/machinery/door/airlock/medical/glass{id_tag = null; name = "Virology Lobby Hallway"; req_access_txt = "0"},/turf/simulated/floor/plasteel{icon_state = "dark"},/area/medical/virology/lab{name = "\improper Virology Lobby"})
"cmn" = (/obj/machinery/vending/coffee,/turf/simulated/floor/plasteel{tag = "icon-whitegreen (NORTHWEST)"; icon_state = "whitegreen"; dir = 9},/area/medical/virology/lab{name = "\improper Virology Lobby"})
"cmo" = (/obj/structure/sign/biohazard,/turf/simulated/wall,/area/medical/virology/lab{name = "\improper Virology Lobby"})
"cmp" = (/obj/machinery/vending/medical,/turf/simulated/floor/plasteel{tag = "icon-whitegreen (NORTHEAST)"; icon_state = "whitegreen"; dir = 5},/area/medical/virology/lab{name = "\improper Virology Lobby"})
@@ -6104,8 +6104,8 @@
"cnt" = (/obj/structure/disposalpipe/segment{dir = 8; icon_state = "pipe-c"},/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"; tag = ""},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/turf/simulated/floor/plating,/area/maintenance/apmaint)
"cnu" = (/obj/structure/stool/bed/chair/office/dark,/turf/simulated/floor/carpet,/area/ntrep)
"cnv" = (/obj/machinery/alarm{dir = 8; icon_state = "alarm0"; pixel_x = 24},/turf/simulated/floor/wood,/area/blueshield)
-"cnw" = (/obj/structure/disposalpipe/segment,/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/machinery/atmospherics/pipe/simple/hidden/supply,/turf/simulated/floor/plasteel{icon_state = "white"},/area/medical/virology/lab{name = "\improper Virology Lobby"})
-"cnx" = (/turf/simulated/floor/plasteel{tag = "icon-whitegreen (NORTHWEST)"; icon_state = "whitegreen"; dir = 9},/area/medical/virology/lab{name = "\improper Virology Lobby"})
+"cnw" = (/obj/machinery/door/firedoor,/obj/machinery/door/airlock/medical/glass{id_tag = null; name = "Virology Lobby Hallway"; req_access_txt = "0"},/turf/simulated/floor/plasteel{icon_state = "dark"},/area/medical/ward)
+"cnx" = (/obj/structure/disposalpipe/segment,/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/machinery/atmospherics/pipe/simple/hidden/supply,/turf/simulated/floor/plasteel{dir = 4; icon_state = "whitegreencorner"},/area/medical/virology/lab{name = "\improper Virology Lobby"})
"cny" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply,/turf/simulated/floor/plasteel,/area/hallway/primary/aft)
"cnz" = (/obj/structure/sign/biohazard{pixel_y = 32},/turf/simulated/floor/plasteel{dir = 1; icon_state = "whitegreen"; tag = "icon-whitegreen (WEST)"},/area/medical/virology/lab{name = "\improper Virology Lobby"})
"cnA" = (/turf/simulated/floor/plasteel{dir = 2; icon_state = "yellowcorner"},/area/hallway/primary/aft)
@@ -7170,6 +7170,7 @@
"cHT" = (/obj/structure/disposalpipe/segment{dir = 2; icon_state = "pipe-c"},/turf/simulated/floor/plasteel,/area/engine/break_room)
"cHU" = (/obj/structure/sign/securearea,/obj/structure/disposalpipe/segment{dir = 4},/obj/machinery/atmospherics/pipe/simple/hidden/yellow{tag = "icon-intact (NORTHWEST)"; icon_state = "intact"; dir = 9},/turf/simulated/wall,/area/engine/break_room)
"cHV" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{level = 1},/turf/simulated/wall/r_wall,/area/atmos/control)
+"cHW" = (/turf/simulated/floor/plasteel{tag = "icon-whitegreen (WEST)"; icon_state = "whitegreen"; dir = 8},/area/medical/virology/lab{name = "\improper Virology Lobby"})
"cHY" = (/obj/machinery/computer/general_air_control{frequency = 1441; name = "Tank Monitor"; sensors = list("n2_sensor" = "Nitrogen", "o2_sensor" = "Oxygen", "co2_sensor" = "Carbon Dioxide", "tox_sensor" = "Toxins", "n2o_sensor" = "Nitrous Oxide", "waste_sensor" = "Gas Mix Tank")},/obj/machinery/alarm{frequency = 1439; pixel_y = 23},/turf/simulated/floor/plasteel,/area/atmos/control)
"cHZ" = (/obj/machinery/kitchen_machine/grill,/turf/simulated/floor/plating,/area/maintenance/asmaint)
"cIa" = (/obj/machinery/cooker/deepfryer,/obj/machinery/light/small{dir = 1},/turf/simulated/floor/plating,/area/maintenance/asmaint)
@@ -8972,10 +8973,10 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabPLbPIbVebVfbVgbVhbVibRGbVkbRAbVlbRAbRAbRAbRAbRAbTlbRGaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabOebVmbOcbOcbOcbVnbOebPTbKBbKBbVobKzbPPbKzbKzbKzbKzbKzbJfbKAbTsbVpbxbbVrbBnbVqbVsbBnbVwbzEbBnbBnbVtbDXbOwbTybTzbRVbRWbMGbVubVvbVybVxbVzbMHbVBbVAbVDbVCbHsbVEbVFbHsbHtbHtbVGbSibVIbVHbVKbVJbVLbVLbVNbVMbVObFObSMbSLbSObSNbSPcrAcrAcrAcrAcrAbSRbSQbSUbSSbSVbXAbSXbRtbXAbThbXAbJRcuobJSbTZbTYbUabUabUcbUbbWgbUdbUfbUdbWibWibWjbWjbWjbWjbIabIabIabIabIabWlbWnbLIbIabNVbNZbNXbWqbWqbPibOabLRctvbPkbPjcacbGGbZZaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabWObWGbRAbRAbWHbWHbWHbRGbRAbRAbRGbWIbWJbWKbRAbWMbWNbRGaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabOebOcbOcbOcbOcbOcbWPbTrbTqbTrbPUbWQbWRbKzbKzbKzbWQbKzbJfbKAbTsbWSbxbbWWbBnbzFbQdbBnbzJbLJbBnbBnbWUbDXbOwbTybWVbRVbWXbMGbXbbWYbWZbXabXdbMHbXcbXebXgbXfbXibXhbXjbFCaaaaaaaaabSibXpbXkbXlbXlbXmbXnbXobSibXrbXqbUhbUgbUjbUibUrbUkbUnbUmbUmbUmbUpbUobUmbUmbUqbUmbUmbUsbUnbUmbUmbUGbNubNtbUwbUvbUBbUybZfbUDbXNbUIbUIbUIbUJbWgbLRbUPbWqbWqbXYbXXbWqbXZbYbbYabYdbYcbWqbPlbYgbYfbYibYhbYlbYjbYjbYjbYjbYjcacbGjbGHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabYubWGbRAbRAbRAbRAbRAbYtbRAbRAbRGbPIbPIbPIbPIbPIbPIbYvaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabOebOcbOcbOcbOcbOcbYxbYwbYybYwbYAbYzbYBbYBbYBbYBbYBbYCbYEbYDbTsbYFbxbbYGbYIbYHbYNbYJbYKbYLbYLbYLbYMbYMbYObTybYRbYQbRWbMGbYTbYSbYVbVvbYWbMHbYUbYXbZdbTJbZebQmbYYbFCaaaaaaaaabSibYZbZabZbbZcbXmbXnbXobSibZgbZJbURbUQbUTbUScyTcyTbUVbUUbUXbUWcyTbUYbUZbUZbVabUZbUZbUSbUVbUZbVcbVbbVdcyTbVPbVjbVRbVQbVTbVSbVVbVUbVXbVWbVZbVYbWcbWbbWcbWcbWebUNbZMbUNbZNbUNbZPbZObZObPCbPDbZRbZUbZTbZXbZVbGkbZWaYpbYjcacbGGbGHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaebPIcafcagcahcaibRAbRGbRAbRAbRGbRAcancakcaraaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabOebOcbOcbOcbOcbOcbOebPTbKBbKBbxbbxbbKBbKBbKBbKBbKBbxbbxbbKBbKBbKBbxbcamcamcamcascambYMcaocapcaqcatbYMbOwbTycawbMEbMEbMGbMGbMGbMGbMGbMNbMHbTJbTIcaubTJcavcaxbFCbFCbFCbFCbFCbSibSjbSibSibSibSibSibSibSibNvbFObWfbSLbWkbWhbWobWmbWscaAbWubWtbWxbWwbWtbWubWabWybWabWybWabWybWabWzbWAcrAbTZbWBbWDbWCbWFbWEbXNbXsbXubXtbXvcbdcbdcbecbdcbfcbicbfcbfcbfcbhcblcbjcbjcbkcbmcbocbnbZUcbpcbrcbqbGlcbscbubYjcacbGGbZZaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabPIbPIbPIbPIbPIbPIbRGbTobRAcbzbRAcbxcbycbAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacbDcbBbOccbCbOccbEcbFaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacbGaabaaaaaaaaaaaacamcbIcbHcbKcbJbYMcbNcbLcbMcbRbYMbOwbTycbOcbPcbQbXwcbVcbTcbUcbWcbYcbXbXxcbZcccccbccfccdcceccgccjcchccibXybXzcclccmbEnccnbFOccoccpccrbFObXBbQVbXCcaAcaAbXDbXDcaAbXFbXEbXHbXGbXJbXIbWabXKbXMbXLbXMbXObWabXPbWAbXRbWdbXSbXUbXTbXSbXSbWibXVbXWbXQbYkcbdccTccSccVccUccXccWcdbcbfccYccZcdacbjcbjcdccbocddcdfbPEcdhcdgbGmcdicdkbYjcdndfcbZZaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabPLbPIcdlcdmcdpcdlcdobRGbRAbRAbRGbRAcdrcdqcdsaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacdvcdtcducducducdtcdwaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacamcdycdxcdAcdzbYLcdBcdDcdCcdEbYMbOwbNwcdFbTybTybYmbYncdIcdJcclcdObYobYpcdMcdNcdNcdQcdPcdRcdNcdNcdTcdNcdNcdScclccmbEncdVcdUcdXbYqcdZcdYbFOchfbYrcaAbZhbYsbYsbZibZjbXHbZlbZkbZnbZmbZpbZobZrbZqbZtbZsbZvbZubWAcuvchrbZwbZybZxbZAbZzbZBccQbXWbXQbZBcbdcezceyceBceAceDceCceGcbfceEccZceFceFcbjceHceJceIceLceKceNceMbGnceOceSbYjcaccLhbGpbGobGsaaaaabaabaabaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabWObWGceRceRceRceRceRceTbRAbRAbRGbPIbPIbPIbPIbPIbPIbPRaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaceVceUceUceUceWaaaaaaaaaaaaaaaaaaaaaceXceZceYceZcfaaaaaaaaaacamcamcamcffcfbcdAcfcbYLcfdcbMcfecfgbYMbOwbNxbTicficflcfkbZDbZCbZFbZEbZHbZGbZIcfscftcftcfxcfvcfvcfvcfvcfvcfvcfvcJfcfycfzcfBbZLbZKbZYbZScabcaacadchfcajcaAcazcalcaCcaBcaEcaDcaGcaFcaIcaHcaKcaJcaMcaLcaOcaNbZvcaPcaQcuvbZzcaRcaTcaScaVcaUcaXcaWcaZcaYcaXcbdcgccgbcgecgdcghcgfcggcbfcgiccZceFceFcbjcgjcgmcgkcglcgncgpcgocgrcgqcgvbYjcaccgscgtcgscgucgycgucgucguaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaabaabaabaabaaaaaaaaaaaaaabaabaabaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaebPIcafcagcahcaibRAbRGbRAbRAbRGbRAcancakcaraaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabOebOcbOcbOcbOcbOcbOebPTbKBbKBbxbbxbbKBbKBbKBbKBbKBbxbbxbbKBbKBbKBbxbcamcamcamcascambYMcaocapcaqcatbYMbOwbTycawbMEbMEbMGbMGbMGbMGbMGbMNbMHbTJbTIcaubTJcavcaxbFCbFCbFCbFCbFCbSibSjbSibSibSibSibSibSibSibNvbFObWfbSLbWkbWhbWobWmbWscaAbWubWtbWxbWwbXGbWubWabWybWabWybWabWybWabWzbWAcrAbTZbWBbWDbWCbWFbWEbXNbXsbXubXtbXvcbdcbdcbecbdcbfcbicbfcbfcbfcbhcblcbjcbjcbkcbmcbocbnbZUcbpcbrcbqbGlcbscbubYjcacbGGbZZaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabPIbPIbPIbPIbPIbPIbRGbTobRAcbzbRAcbxcbycbAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacbDcbBbOccbCbOccbEcbFaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacbGaabaaaaaaaaaaaacamcbIcbHcbKcbJbYMcbNcbLcbMcbRbYMbOwbTycbOcbPcbQbXwcbVcbTcbUcbWcbYcbXbXxcbZcccccbccfccdcceccgccjcchccibXybXzcclccmbEnccnbFOccoccpccrbFObXBbQVbXCcaAcaAbXDbXDcaAbXFbXEbZhbXHbZibXIbWabXKbXMbXLbXMbXObWabXPbWAbXRbWdbXSbXUbXTbXSbXSbWibXVbXWbXQbYkcbdccTccSccVccUccXccWcdbcbfccYccZcdacbjcbjcdccbocddcdfbPEcdhcdgbGmcdicdkbYjcdndfcbZZaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabPLbPIcdlcdmcdpcdlcdobRGbRAbRAbRGbRAcdrcdqcdsaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacdvcdtcducducducdtcdwaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacamcdycdxcdAcdzbYLcdBcdDcdCcdEbYMbOwbNwcdFbTybTybYmbYncdIcdJcclcdObYobYpcdMcdNcdNcdQcdPcdRcdNcdNcdTcdNcdNcdScclccmbEncdVcdUcdXbYqcdZcdYbFOchfbYrcaAbZjbYsbYsbXJbZhbZkbZnbZlcaBbZmbZpbZobZrbZqbZtbZsbZvbZubWAcuvchrbZwbZybZxbZAbZzbZBccQbXWbXQbZBcbdcezceyceBceAceDceCceGcbfceEccZceFceFcbjceHceJceIceLceKceNceMbGnceOceSbYjcaccLhbGpbGobGsaaaaabaabaabaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabWObWGceRceRceRceRceRceTbRAbRAbRGbPIbPIbPIbPIbPIbPIbPRaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaceVceUceUceUceWaaaaaaaaaaaaaaaaaaaaaceXceZceYceZcfaaaaaaaaaacamcamcamcffcfbcdAcfcbYLcfdcbMcfecfgbYMbOwbNxbTicficflcfkbZDbZCbZFbZEbZHbZGbZIcfscftcftcfxcfvcfvcfvcfvcfvcfvcfvcJfcfycfzcfBbZLbZKbZYbZScabcaacadchfcajcaAcazcalcaCcaEcaEcaDcaGcaFcaIcaHcaKcaJcaMcaLcaOcaNbZvcaPcaQcuvbZzcaRcaTcaScaVcaUcaXcaWcaZcaYcaXcbdcgccgbcgecgdcghcgfcggcbfcgiccZceFceFcbjcgjcgmcgkcglcgncgpcgocgrcgqcgvbYjcaccgscgtcgscgucgycgucgucguaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaabaabaabaabaaaaaaaaaaaaaabaabaabaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabYubWGceRceRceRcgwcgxbRGbRAbRAbRGbRAbRAbRAcgzbRAcgAbRGaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacgFcgBcgCcgDcgFbYPbYPbYPcamcgHcgGcgJcgIcgLcgKcgNcgMcgOcgOcgPbYMbTkcgQcgTcgScgScgUcbacgVcgScgWcgWcgYcbbcgZcgWcgWchacclcclchechcchcchcchccbcchcchcchfcbgchfchfchfchfchfchfchfcbvcbtcbScbwcckccaccsccqccucctccwccvbWaccxcczccyccBccAccDccCccEcuvccGccFccIccHccJchrbWibWgbWgbWgbWicbdcbdchMchQchOchPchOchRcbfchUchSchTchTcbjchVchXbTwbYjbYjbYjbYjbYjbYjbYjbYjcaccgscifchYchZciaciacibcguaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabcicciccicciccidcidcieciecieciecijaabaabaaaaaaaaaabpaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaebPIcigceRceRcihciibRGbRAbRAcikbRAbRAbRAciobRAcilbRGaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaceYcimcincimceYbYPcipciqcamcitcfbcircisciyciubYMcivciwcixcizbYMcgQcgQciCcgSciAciBccKciDciHcgWccLciGccMciIciKcgWciMciLciNchcccOccNccRccPcdjcdecdKcdHcdLchfceacdWceccebchfcedcbvcaAcaAcaAcaAcaAcaAcaAcefceecegcaAcDfcDfcDfcDfcDfcDfcDfcDfceicehccGcejcelcekcencemcepceocerceqclpcjFcjHcjGcjIcjGcjGcjLcjJcjKcjPcjMcjNcjOcbjcjQbUtcbnbPtbGGcjTcjScjUcjUcjScjUcjYcgscjVcjWchZciackecjXcguaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaciccjZckacjZcidciecieckbckcckbciecieaabaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaebPIckdceRceRckfbRGbRAbRAbRGckgbRAbRAckjckhbPIbYvaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacgFckicimcimckkcklcfbcfbckmckncfbcfbcisckqckobYMbYMbYMbYMbYMbYMckpcgQciCcgSclRcesceucetckucgWckvcevcexcewcePcgWcfmceQcfnchccfpcfockHckGcfqckIchcchfdgCchfcebcebcfrcebchfcsLcbvchfcfwbUucfCcfAcfEcfDcfGcfFcfIcfHcfKcfJcfMcfLcDfcfNcfPcfOcfRcfQcfTcfScfVcfUcfXcfWcepcfYcgacfZclpclncjGcjGclmclocjGcltclpcbjclqccZclrclrcbjcbmclscbnbPtcgsclvcgscgscgscgscgscgscgscluclwcguclxclzclycguafOafOafOafOafOafOafOafOafOafOafOafOafOafOafOafOafOafOafOafOafOclBcjZcjZclAciecieckbckbclHckbckbciecieaabaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
@@ -8983,8 +8984,8 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacaebPIbPIbPIbPIbPIbPIbPIbPIbPIbPIbYvaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaabaabafOcgQcngcngcgQcngcngcgQaaaaaacgFcnhcnicnjcgFbYPbYPbYPcamcnmcnlcnocnocnocamcamcgQcnncnpcnrcnqcgQcnscntcgSclRcktcktcktcnvcgWckvckwckwcnuchHcgWcpicHDcnAchcchcchcchcchcchIchcchccnCdgCcsLcsLcsLchfcABchJcsLcbvchfchLchKciEchNcfEciFcfGcfFcfIcfHciOciJciQciPcDfciRciScDfciUciTchrciVciXciWchrchrcepciYcjaciZclpcjbcodcoicoicoicodcogcodcodcohcojclrclrcbjcokclscbncomcolcooconcorcopcoqcoqcotcoscovcoucoxcowcozcoycoCcoAcoBcoDcoEcoFcoHcoGcoIaabaabaabaabaabaabaabaabaaaaaaaaaaaaaaacidciecoMcoJcoJckbckbckbckbckbckbckbckbciecieaabaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaacgQcoKcoLcgQcoKcoLcgQaaaaaacoOceZcoNceZcoQaaaaabaaacamcamcamcamcamcamcamcoPcgQcoScoRcoUcoTclPcoVcoWcgScoYcoXcpfcktcjccgWcjdckwcjfcjecjgcgWcjicjhcjkcjjcpqcpqcpqcpqcjlcpmcppcpocjncjmcjmcjocjqcjpcjscjrcjtchfcjwcjucjycjxcjAcjzcjCcjBcjCcjDckrcjEckxcksckzckyckAcDfckBcuvckCcjvckDcmDckFckEciYckJckLcgacpRcpTcpRcpUcpWcpVcpRcpXcpYcodcqacpZcdacbjcbjcqccqbcqfcqdcqecqicqgcqhcqlcqjcqkcqncqmcqpcqocqtcqqcqrcqqcqscqrcqrcqucqwcqvcqycqxcqCaabaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaacieciecqDckbckbckbckbckbckbckbckbckbckbckbciecieaabaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaafOaaaafOcgQcqzcoLcgQcqzcoLcgQaaaaaaaabaabaabaabaabaabaabaabaabcgQcoLcqAcqBcgQcqEcqGcgQcqHcgQcgQcqFcgQcgQcqPcgScqRcqIcqJcqKcqLcgWckMcqOcqScqQcqTcgWckOckNckPcJZcsDcsDcsDcsDcsDcrdcqZcrackQchfcucckRcsLckSckTcsLcsKchfckVckUckXckWcCWckYclackZclbcDfcldclcclfclecDfcDfcDfcDfclhclgckCchCcljclicllclkciYcmdcgacmfcpRcrNcrQcrLcrMcrLcrTcrOcrPcodcbjcrUcbjcbjcrRcrSclsbLRbPtcgscrVcgscgscrWcrYcrXcsacrZcsdcsbcsfcsecsgcsecsgcshcsgcsicgEcsncvdcsqcsraabaabaabaabaabaabaabaabaaaaaaaaaaaaaaacieckbckbckbckbckbckbcssckbcstckbckbckbckbckbciecieaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabcsjafOaabaMraMrafOcgQcgQcqFcgQcgQcqFcgQcgQcgQcngcngcngcgQcgQcgQcgQcgQcgQcgQcoLcskcslcgQcsmcgQcgQcsucgQcsocspcoLcgQcsvcgScsycsxcszclRcmgcgWcswckvcsEcsCcmhcgWcsIcsHcmicsDcsDcsNcsOcsGcmjcsQcsJcsKcsLcsUchfcmkcsLckSckTchfchfchfcCWcCWcmlcCWcCWcxhcxhcmmcxhcDfcDfcDfcDfcDfcDfcmncmpcmocmrcmqcmtcmscmvcmucmxcmwciYcgacgaaqEcpRcttctxctwctyctwctActzcrLctuctvctBbLRctCbLRcrSclsbLRbIictFctHctGcgscgscgscgscgsctIcqkctKctDcsccqkctEcqkclucqkctLcgEctOctQctPcgEaabaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaciectJctJctJctJctJctJctSckbctTctJcstckbckbckbckbcieaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabafOafOcngcoLcoLcoLcoLctMctNcoLcoLcoSctUctUctVctUctUctUctUctUctUctUctUctUctUctUctUcoRcgQcskcqzctWcgQcsvctRctRctRctRctRctRcgWcgWcgWcgWcgWcgWcgWcuactXcmBcmzcmEcmCcmFcmFcmGcuicsJcmHcsLcubcuccmIcsLckScmJchicsLcsLcsKcCWcmLcmKcmNcCWcnxcnwcnBcnzcnEcnDcnGcnFcnIcnHcnKcnJcnMcnLckCcnNcnPcnOcnRcnQciYbQXcgacgacpRcuGcnTcnScnUcrLcuKcuJcuOcuLcuScuRcuVbLRcuXcrSclscuMbIibPAcuNcuYcuPcuQbGGbGGcgscvccvecuTcuUcvfcuWcvgcvicuZcvacvbcgEcvjdoKdoFcvraaaaaaaaaaaaabpaaaaaaaaaaaaaaaaaaaaaaaacvscjZcjZcjZcjZcjZcjZcjZcvhcvtckbckbckbckbcvvckccieaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabcsjafOaabaMraMrafOcgQcgQcqFcgQcgQcqFcgQcgQcgQcngcngcngcgQcgQcgQcgQcgQcgQcgQcoLcskcslcgQcsmcgQcgQcsucgQcsocspcoLcgQcsvcgScsycsxcszclRcmgcgWcswckvcsEcsCcmhcgWcsIcsHcmicsDcsDcsNcsOcsGcmjcsQcsJcsKcsLcsUchfcmkcsLckSckTchfchfchfcCWcCWcmlcCWcCWcxhcnwcmmcxhcDfcDfcDfcDfcDfcDfcmncmpcmocmrcmqcmtcmscmvcmucmxcmwciYcgacgaaqEcpRcttctxctwctyctwctActzcrLctuctvctBbLRctCbLRcrSclsbLRbIictFctHctGcgscgscgscgscgsctIcqkctKctDcsccqkctEcqkclucqkctLcgEctOctQctPcgEaabaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaciectJctJctJctJctJctJctSckbctTctJcstckbckbckbckbcieaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabafOafOcngcoLcoLcoLcoLctMctNcoLcoLcoSctUctUctVctUctUctUctUctUctUctUctUctUctUctUctUcoRcgQcskcqzctWcgQcsvctRctRctRctRctRctRcgWcgWcgWcgWcgWcgWcgWcuactXcmBcmzcmEcmCcmFcmFcmGcuicsJcmHcsLcubcuccmIcsLckScmJchicsLcsLcsKcCWcmLcmKcmNcCWcHWcnxcnBcnzcnEcnDcnGcnFcnIcnHcnKcnJcnMcnLckCcnNcnPcnOcnRcnQciYbQXcgacgacpRcuGcnTcnScnUcrLcuKcuJcuOcuLcuScuRcuVbLRcuXcrSclscuMbIibPAcuNcuYcuPcuQbGGbGGcgscvccvecuTcuUcvfcuWcvgcvicuZcvacvbcgEcvjdoKdoFcvraaaaaaaaaaaaabpaaaaaaaaaaaaaaaaaaaaaaaacvscjZcjZcjZcjZcjZcjZcjZcvhcvtckbckbckbckbcvvckccieaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabafOaabaabafOcngcvkcvlcvmcoLcoLcvncoLcoLcsucgQcgQcgQcgQcgQcgQcqFcgQcgQcgQcgQcvocvocvocvocvocvocvocvocvocgQcsvcvpaaaaaaaaaaaacvqcvwcvzcvycvucvAcvDcvxcvJcvGcvKcvBcvCcvLcvEcvEcvFcvMcvHcvIcvIcvIcvIcvNcvIckScmJchfcnVcKOdercCWcnXcnWcnZcnYcobcoacoccoccofcoecpacoZcpccpbcpecpdcphcpgciYciYciYciYciYciYciYciYcpkcpjcpRcwjcplcwqcptcpncpvcpucpRcpRbIicwpbIibIibIicwvcwJcwwbIibGGcuNcwKcwucuQcuQcuQcgscgscgscgscgscgscgscgscgscgscgscgscgEcgEcgEcgEcgEaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaciecoJcoJcoJcoJcoJcoJcwLckbcoMcoJcwMckbckbckbckbcieaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabcgQcwxcwxcwxcwxcwxcwxcwycwxcsucwzcgQcoLcwAcwBcwCcoLcwCcwDcwEcwBcvocwFcwFcvocwGcvocwHcwIcvocoLcqPcngaaacwPcwUcwQcwVcwNcwOcwWcwXcwRcwScwTcwZcwYcxbcxacxdcxccxfcxecxjcxgcvHcxkcxmcxmcxmcxocvIckScmJchfdgmcQkcQkcQkcQkcpwcpxcQkcpycpycpycpycQkcpzcpBcpAcQkcQncQncpCcpDcuocukbQXbQXcpEbQXbQXbQXbQXbQXcpFcpRcpRcpRcpRcpGcpRcpRcpRcxIcpHcpIcxKcxUcxMcxNcxOcxWcxQcxNbGGcuNcylbGGbGGcxScxZcyactFbGGbGGbGGbGGbGGbGGbGGcxVcymbZZaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacieckbckbckbckbckbckbcssckbcwMckbckbckbckbckbciecieaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacxXcxYcxYcxYcxYcxYcxYcyocyncybcyccydcyecyfcwycsucygcgQcoLcoLcwBcoLcoLcoLcwDcqBcwBcvocwFcyicwIcwIcyjcwIcykcvocypcqPcngaaacyrcytcyscyvcyqcwOcywcyAcyycyucvxcyCcsHcyEcvBcyxctYcyFctYcyzcyGcyBcyHcyDcyDcyDcyIcvIckScmJchfcQkcQkcpKcpJcQkcQkcQkcQkcpMcpLcpOcpNcQkcQkcpPcQkcQkcTBdgmbQVcpScuvcqMcqMcqMcqMcqMcqMcqNbQXcqWcqUcqXcqXcqXcqYcrccrbcrbcrfcrbcrbcrbcrgczmczmcricrhcrjczwczzczyczBczAczCczCczDczCczCczCczCczGczIczCczCczCczOczLbGGbZZaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacieciecqDckbckbckbckbckbckbckbckbckbckbckbciecieaabaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
diff --git a/code/__DEFINES/math.dm b/code/__DEFINES/math.dm
index c6ffc61495e..3f360d5bad7 100644
--- a/code/__DEFINES/math.dm
+++ b/code/__DEFINES/math.dm
@@ -32,6 +32,8 @@
#define ToDegrees(radians) ((radians) * 57.2957795) // 180 / Pi
#define ToRadians(degrees) ((degrees) * 0.0174532925) // Pi / 180
+#define SHORT_REAL_LIMIT 16777216
+
//"fancy" math for calculating time in ms from tick_usage percentage and the length of ticks
//percent_of_tick_used * (ticklag * 100(to convert to ms)) / 100(percent ratio)
//collapsed to percent_of_tick_used * tick_lag
diff --git a/code/__HELPERS/cmp.dm b/code/__HELPERS/cmp.dm
index 4b97b67e27e..0db57b27eae 100644
--- a/code/__HELPERS/cmp.dm
+++ b/code/__HELPERS/cmp.dm
@@ -31,6 +31,9 @@
/proc/cmp_subsystem_priority(datum/controller/subsystem/a, datum/controller/subsystem/b)
return a.priority - b.priority
+/proc/cmp_timer(datum/timedevent/a, datum/timedevent/b)
+ return a.timeToRun - b.timeToRun
+
/proc/cmp_qdel_item_time(datum/qdel_item/A, datum/qdel_item/B)
. = B.hard_delete_time - A.hard_delete_time
if(!.)
diff --git a/code/__HELPERS/qdel.dm b/code/__HELPERS/qdel.dm
index 8171dd66427..136e573a25e 100644
--- a/code/__HELPERS/qdel.dm
+++ b/code/__HELPERS/qdel.dm
@@ -1,5 +1,10 @@
-#define QDEL_IN(item, time) addtimer(GLOBAL_PROC, "qdel", time, FALSE, item)
+#define QDEL_IN(item, time) addtimer(CALLBACK(GLOBAL_PROC, .proc/qdel, item), time, TIMER_STOPPABLE)
+#define QDEL_IN_CLIENT_TIME(item, time) addtimer(CALLBACK(GLOBAL_PROC, .proc/qdel, item), time, TIMER_STOPPABLE | TIMER_CLIENT_TIME)
#define QDEL_NULL(item) if(item) { qdel(item); item = null }
#define QDEL_LIST(L) if(L) { for(var/I in L) qdel(I); L.Cut(); }
+#define QDEL_LIST_IN(L, time) addtimer(CALLBACK(GLOBAL_PROC, .proc/______qdel_list_wrapper, L), time, TIMER_STOPPABLE)
#define QDEL_LIST_ASSOC(L) if(L) { for(var/I in L) { qdel(L[I]); qdel(I); } L.Cut(); }
#define QDEL_LIST_ASSOC_VAL(L) if(L) { for(var/I in L) qdel(L[I]); L.Cut(); }
+
+/proc/______qdel_list_wrapper(list/L) //the underscores are to encourage people not to use this directly.
+ QDEL_LIST(L)
\ No newline at end of file
diff --git a/code/controllers/Processes/nano_mob_hunter.dm b/code/controllers/Processes/nano_mob_hunter.dm
index fb57b6e0bbf..dc5dcba805b 100644
--- a/code/controllers/Processes/nano_mob_hunter.dm
+++ b/code/controllers/Processes/nano_mob_hunter.dm
@@ -44,7 +44,7 @@ DECLARE_GLOBAL_CONTROLLER(mob_hunt, mob_hunt_server)
recover_time = 3000
if(recover_time > 0) //when provided with a negative or zero valued recover_time argument, the server won't auto-restart but can be manually rebooted still
//set a timer to automatically recover after recover_time has passed (can be manually restarted if you get impatient too)
- addtimer(src, "auto_recover", recover_time, TRUE)
+ addtimer(CALLBACK(src, .proc/auto_recover), recover_time, TIMER_UNIQUE)
/datum/controller/process/mob_hunt/proc/client_mob_update()
var/list/ex_players = list()
diff --git a/code/controllers/Processes/timer.dm b/code/controllers/Processes/timer.dm
deleted file mode 100644
index 4b42d69221d..00000000000
--- a/code/controllers/Processes/timer.dm
+++ /dev/null
@@ -1,100 +0,0 @@
-var/global/datum/controller/process/timer/timer_master
-
-/datum/controller/process/timer
- var/list/processing_timers = list()
- var/list/hashes = list()
-
-/datum/controller/process/timer/setup()
- name = "timer"
- schedule_interval = 1 //every 0.1 seconds--2 server ticks
- log_startup_progress("Timer process starting up.")
-
-/datum/controller/process/timer/statProcess()
- ..()
- stat(null, "[processing_timers.len] timers")
-
-/datum/controller/process/timer/doWork()
- if(!processing_timers.len)
- disabled = 1 //nothing to do, lets stop firing.
- return
- for(last_object in processing_timers)
- var/datum/timedevent/event = last_object
- if(!event.thingToCall || check_datum_qdeleted(event.thingToCall))
- qdel(event)
- if(event.timeToRun <= world.time)
- runevent(event)
- qdel(event)
- SCHECK
-
-DECLARE_GLOBAL_CONTROLLER(timer, timer_master)
-
-/datum/controller/process/timer/proc/runevent(datum/timedevent/event)
- set waitfor = 0
- if(event.thingToCall)
- if(event.thingToCall == GLOBAL_PROC && istext(event.procToCall))
- call("/proc/[event.procToCall]")(arglist(event.argList))
- else
- call(event.thingToCall, event.procToCall)(arglist(event.argList))
-
-/datum/timedevent
- var/thingToCall
- var/procToCall
- var/timeToRun
- var/argList
- var/id
- var/hash
- var/static/nextid = 1
-
-/datum/timedevent/New()
- id = nextid++
-
-/datum/timedevent/Destroy()
- timer_master.processing_timers -= src
- timer_master.hashes -= hash
- return QDEL_HINT_IWILLGC
-
-/proc/addtimer(thingToCall, procToCall, wait, unique = FALSE, ...)
- if(!timer_master) //can't run timers before the mc has been created
- return
- if(!thingToCall || !procToCall)
- return
- if(timer_master.disabled)
- timer_master.disabled = 0
-
- var/datum/timedevent/event = new()
- event.thingToCall = thingToCall
- event.procToCall = procToCall
- event.timeToRun = world.time + wait
- var/hashlist = args.Copy()
-
- hashlist[1] = "[thingToCall](\ref[thingToCall])"
- event.hash = jointext(hashlist, null)
- if(args.len > 4)
- event.argList = args.Copy(5)
-
- // Check for dupes if unique = 1.
- if(unique)
- var/datum/timedevent/hash_event = timer_master.hashes[event.hash]
- if(hash_event)
- return hash_event.id
- timer_master.hashes[event.hash] = event
- if(wait <= 0)
- timer_master.runevent(event)
- timer_master.hashes -= event.hash
- return
- // If we are unique (or we're not checking that), add the timer and return the id.
- timer_master.processing_timers += event
-
- return event.id
-
-/proc/deltimer(id)
- if(id == 0)
- // No event will correspond to an id of 0 - the timer does not exist
- // Save us a possibly expensive iteration through the timer list
- // This would probably be more efficient in general if we used an associative list instead
- return 0
- for(var/datum/timedevent/event in timer_master.processing_timers)
- if(event.id == id)
- qdel(event)
- return 1
- return 0
diff --git a/code/controllers/Processes/weather.dm b/code/controllers/Processes/weather.dm
index 1706b2232c0..919352313c6 100644
--- a/code/controllers/Processes/weather.dm
+++ b/code/controllers/Processes/weather.dm
@@ -37,7 +37,7 @@ var/global/datum/controller/process/weather/weather_master
var/datum/weather/W = pickweight(possible_weather_for_this_z)
run_weather(W.name)
eligible_zlevels -= Z
- addtimer(src, "make_z_eligible", rand(3000, 6000) + W.weather_duration_upper, TRUE, Z) //Around 5-10 minutes between weathers
+ addtimer(CALLBACK(src, .proc/make_z_eligible, Z), rand(3000, 6000) + W.weather_duration_upper, TIMER_UNIQUE) //Around 5-10 minutes between weathers
DECLARE_GLOBAL_CONTROLLER(weather, weather_master)
diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm
index b71531ee20f..b35bc300434 100644
--- a/code/controllers/configuration.dm
+++ b/code/controllers/configuration.dm
@@ -202,6 +202,9 @@
var/randomize_shift_time = FALSE
var/enable_night_shifts = FALSE
+ // Developer
+ var/developer_express_start = 0
+
/datum/configuration/New()
for(var/T in subtypesof(/datum/game_mode))
var/datum/game_mode/M = T
@@ -612,6 +615,8 @@
config.high_pop_mc_mode_amount = text2num(value)
if("disable_high_pop_mc_mode_amount")
config.disable_high_pop_mc_mode_amount = text2num(value)
+ if("developer_express_start")
+ config.developer_express_start = 1
else
log_config("Unknown setting in configuration: '[name]'")
diff --git a/code/controllers/master.dm b/code/controllers/master.dm
index 518d27d3c0a..9cb84f26aa5 100644
--- a/code/controllers/master.dm
+++ b/code/controllers/master.dm
@@ -54,7 +54,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/static/restart_clear = 0
var/static/restart_timeout = 0
var/static/restart_count = 0
-
+
var/static/random_seed
//current tick limit, assigned before running a subsystem.
@@ -70,7 +70,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
if(!random_seed)
random_seed = rand(1, 1e9)
rand_seed(random_seed)
-
+
var/list/_subsystems = list()
subsystems = _subsystems
if(Master != src)
@@ -194,6 +194,9 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
to_chat(world, "[msg]")
log_world(msg)
+ if(config.developer_express_start & ticker.current_state == GAME_STATE_PREGAME)
+ ticker.current_state = GAME_STATE_SETTING_UP
+
if(!current_runlevel)
SetRunLevel(1)
diff --git a/code/controllers/subsystem/timer.dm b/code/controllers/subsystem/timer.dm
new file mode 100644
index 00000000000..21fea2ee419
--- /dev/null
+++ b/code/controllers/subsystem/timer.dm
@@ -0,0 +1,518 @@
+#define BUCKET_LEN (world.fps*1*60) //how many ticks should we keep in the bucket. (1 minutes worth)
+#define BUCKET_POS(timer) ((round((timer.timeToRun - SStimer.head_offset) / world.tick_lag) % BUCKET_LEN) + 1)
+#define TIMER_MAX (world.time + TICKS2DS(min(BUCKET_LEN-(SStimer.practical_offset-DS2TICKS(world.time - SStimer.head_offset))-1, BUCKET_LEN-1)))
+#define TIMER_ID_MAX (2**24) //max float with integer precision
+
+SUBSYSTEM_DEF(timer)
+ name = "Timer"
+ wait = 1 //SS_TICKER subsystem, so wait is in ticks
+ init_order = INIT_ORDER_TIMER
+
+ flags = SS_TICKER|SS_NO_INIT
+
+ var/list/second_queue = list() //awe, yes, you've had first queue, but what about second queue? Contains: /datum/timedevent
+ var/list/hashes = list()
+
+ var/head_offset = 0 //world.time of the first entry in the the bucket.
+ var/practical_offset = 1 //index of the first non-empty item in the bucket.
+ var/bucket_resolution = 0 //world.tick_lag the bucket was designed for
+ var/bucket_count = 0 //how many timers are in the buckets
+
+ var/list/bucket_list = list() //list of buckets, each bucket holds every timer that has to run that byond tick.
+
+ var/list/timer_id_dict = list() //list of all active timers assoicated to their timer id (for easy lookup)
+
+ var/list/clienttime_timers = list() //special snowflake timers that run on fancy pansy "client time"
+
+ var/last_invoke_tick = 0
+ var/static/last_invoke_warning = 0
+ var/static/bucket_auto_reset = TRUE
+
+/datum/controller/subsystem/timer/PreInit()
+ bucket_list.len = BUCKET_LEN
+ head_offset = world.time
+ bucket_resolution = world.tick_lag
+
+/datum/controller/subsystem/timer/stat_entry(msg)
+ ..("B:[bucket_count] P:[length(second_queue)] H:[length(hashes)] C:[length(clienttime_timers)] S:[length(timer_id_dict)]")
+
+/datum/controller/subsystem/timer/fire(resumed = FALSE)
+ var/lit = last_invoke_tick
+ var/last_check = world.time - TIMER_NO_INVOKE_WARNING
+ var/list/bucket_list = src.bucket_list
+
+ if(!bucket_count)
+ last_invoke_tick = world.time
+
+ if(lit && lit < last_check && last_invoke_warning < last_check)
+ last_invoke_warning = world.time
+ var/msg = "No regular timers processed in the last [TIMER_NO_INVOKE_WARNING] ticks[bucket_auto_reset ? ", resetting buckets" : ""]!"
+ message_admins(msg)
+ WARNING(msg)
+ if(bucket_auto_reset)
+ bucket_resolution = 0
+
+ log_world("Timer bucket reset. world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]")
+ for(var/i in 1 to length(bucket_list))
+ var/datum/timedevent/bucket_head = bucket_list[i]
+ if(!bucket_head)
+ continue
+
+ log_world("Active timers at index [i]:")
+
+ var/datum/timedevent/bucket_node = bucket_head
+ var/anti_loop_check = 1000
+ do
+ log_world(get_timer_debug_string(bucket_node))
+ bucket_node = bucket_node.next
+ anti_loop_check--
+ while(bucket_node && bucket_node != bucket_head && anti_loop_check)
+ log_world("Active timers in the second_queue queue:")
+ for(var/I in second_queue)
+ log_world(get_timer_debug_string(I))
+
+ var/next_clienttime_timer_index = 0
+ var/len = length(clienttime_timers)
+
+ for(next_clienttime_timer_index in 1 to len)
+ if(MC_TICK_CHECK)
+ next_clienttime_timer_index--
+ break
+ var/datum/timedevent/ctime_timer = clienttime_timers[next_clienttime_timer_index]
+ if(ctime_timer.timeToRun > REALTIMEOFDAY)
+ next_clienttime_timer_index--
+ break
+
+ var/datum/callback/callBack = ctime_timer.callBack
+ if(!callBack)
+ clienttime_timers.Cut(next_clienttime_timer_index,next_clienttime_timer_index+1)
+ CRASH("Invalid timer: [get_timer_debug_string(ctime_timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset], REALTIMEOFDAY: [REALTIMEOFDAY]")
+
+ ctime_timer.spent = REALTIMEOFDAY
+ callBack.InvokeAsync()
+ qdel(ctime_timer)
+
+
+ if(next_clienttime_timer_index)
+ clienttime_timers.Cut(1,next_clienttime_timer_index+1)
+
+ if(MC_TICK_CHECK)
+ return
+
+ var/static/list/spent = list()
+ var/static/datum/timedevent/timer
+ if(practical_offset > BUCKET_LEN)
+ head_offset += TICKS2DS(BUCKET_LEN)
+ practical_offset = 1
+ resumed = FALSE
+
+ if((length(bucket_list) != BUCKET_LEN) || (world.tick_lag != bucket_resolution))
+ reset_buckets()
+ bucket_list = src.bucket_list
+ resumed = FALSE
+
+
+ if(!resumed)
+ timer = null
+
+ while(practical_offset <= BUCKET_LEN && head_offset + (practical_offset*world.tick_lag) <= world.time)
+ var/datum/timedevent/head = bucket_list[practical_offset]
+ if(!timer || !head || timer == head)
+ head = bucket_list[practical_offset]
+ timer = head
+ while(timer)
+ var/datum/callback/callBack = timer.callBack
+ if(!callBack)
+ bucket_resolution = null //force bucket recreation
+ CRASH("Invalid timer: [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]")
+
+ if(!timer.spent)
+ spent += timer
+ timer.spent = world.time
+ callBack.InvokeAsync()
+ last_invoke_tick = world.time
+
+ if(MC_TICK_CHECK)
+ return
+
+ timer = timer.next
+ if(timer == head)
+ break
+
+
+ bucket_list[practical_offset++] = null
+
+ //we freed up a bucket, lets see if anything in second_queue needs to be shifted to that bucket.
+ var/i = 0
+ var/L = length(second_queue)
+ for(i in 1 to L)
+ timer = second_queue[i]
+ if(timer.timeToRun >= TIMER_MAX)
+ i--
+ break
+
+ if(timer.timeToRun < head_offset)
+ bucket_resolution = null //force bucket recreation
+ CRASH("[i] Invalid timer state: Timer in long run queue with a time to run less then head_offset. [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]")
+
+ if(timer.callBack && !timer.spent)
+ timer.callBack.InvokeAsync()
+ spent += timer
+ bucket_count++
+ else if(!QDELETED(timer))
+ qdel(timer)
+ continue
+
+ if(timer.timeToRun < head_offset + TICKS2DS(practical_offset))
+ bucket_resolution = null //force bucket recreation
+ CRASH("[i] Invalid timer state: Timer in long run queue that would require a backtrack to transfer to short run queue. [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]")
+ if(timer.callBack && !timer.spent)
+ timer.callBack.InvokeAsync()
+ spent += timer
+ bucket_count++
+ else if(!QDELETED(timer))
+ qdel(timer)
+ continue
+
+ bucket_count++
+ var/bucket_pos = max(1, BUCKET_POS(timer))
+
+ var/datum/timedevent/bucket_head = bucket_list[bucket_pos]
+ if(!bucket_head)
+ bucket_list[bucket_pos] = timer
+ timer.next = null
+ timer.prev = null
+ continue
+
+ if(!bucket_head.prev)
+ bucket_head.prev = bucket_head
+ timer.next = bucket_head
+ timer.prev = bucket_head.prev
+ timer.next.prev = timer
+ timer.prev.next = timer
+ if(i)
+ second_queue.Cut(1, i+1)
+
+ timer = null
+
+ bucket_count -= length(spent)
+
+ for(var/spent_timer in spent)
+ qdel(spent_timer)
+
+ spent.len = 0
+
+//formated this way to be runtime resistant
+/datum/controller/subsystem/timer/proc/get_timer_debug_string(datum/timedevent/TE)
+ . = "Timer: [TE]"
+ . += "Prev: [TE.prev ? TE.prev : "NULL"], Next: [TE.next ? TE.next : "NULL"]"
+ if(TE.spent)
+ . += ", SPENT([TE.spent])"
+ if(QDELETED(TE))
+ . += ", QDELETED"
+ if(!TE.callBack)
+ . += ", NO CALLBACK"
+
+/datum/controller/subsystem/timer/proc/reset_buckets()
+ var/list/bucket_list = src.bucket_list
+ var/list/alltimers = list()
+ //collect the timers currently in the bucket
+ for(var/bucket_head in bucket_list)
+ if(!bucket_head)
+ continue
+ var/datum/timedevent/bucket_node = bucket_head
+ do
+ alltimers += bucket_node
+ bucket_node = bucket_node.next
+ while(bucket_node && bucket_node != bucket_head)
+
+ bucket_list.len = 0
+ bucket_list.len = BUCKET_LEN
+
+ practical_offset = 1
+ bucket_count = 0
+ head_offset = world.time
+ bucket_resolution = world.tick_lag
+
+ alltimers += second_queue
+ if(!length(alltimers))
+ return
+
+ sortTim(alltimers, .proc/cmp_timer)
+
+ var/datum/timedevent/head = alltimers[1]
+
+ if(head.timeToRun < head_offset)
+ head_offset = head.timeToRun
+
+ var/new_bucket_count
+ var/i = 1
+ for(i in 1 to length(alltimers))
+ var/datum/timedevent/timer = alltimers[1]
+ if(!timer)
+ continue
+
+ var/bucket_pos = BUCKET_POS(timer)
+ if(timer.timeToRun >= TIMER_MAX)
+ i--
+ break
+
+
+ if(!timer.callBack || timer.spent)
+ WARNING("Invalid timer: [get_timer_debug_string(timer)] world.time: [world.time], head_offset: [head_offset], practical_offset: [practical_offset]")
+ if(timer.callBack)
+ qdel(timer)
+ continue
+
+ new_bucket_count++
+ var/datum/timedevent/bucket_head = bucket_list[bucket_pos]
+ if(!bucket_head)
+ bucket_list[bucket_pos] = timer
+ timer.next = null
+ timer.prev = null
+ continue
+
+ if(!bucket_head.prev)
+ bucket_head.prev = bucket_head
+ timer.next = bucket_head
+ timer.prev = bucket_head.prev
+ timer.next.prev = timer
+ timer.prev.next = timer
+ if(i)
+ alltimers.Cut(1, i+1)
+ second_queue = alltimers
+ bucket_count = new_bucket_count
+
+
+/datum/controller/subsystem/timer/Recover()
+ second_queue |= SStimer.second_queue
+ hashes |= SStimer.hashes
+ timer_id_dict |= SStimer.timer_id_dict
+ bucket_list |= SStimer.bucket_list
+
+/datum/timedevent
+ var/id
+ var/datum/callback/callBack
+ var/timeToRun
+ var/hash
+ var/list/flags
+ var/spent = 0 //time we ran the timer.
+ var/name //for easy debugging.
+ //cicular doublely linked list
+ var/datum/timedevent/next
+ var/datum/timedevent/prev
+
+/datum/timedevent/New(datum/callback/callBack, timeToRun, flags, hash)
+ var/static/nextid = 1
+ id = TIMER_ID_NULL
+ src.callBack = callBack
+ src.timeToRun = timeToRun
+ src.flags = flags
+ src.hash = hash
+
+ if(flags & TIMER_UNIQUE)
+ SStimer.hashes[hash] = src
+
+ if(flags & TIMER_STOPPABLE)
+ id = num2text(nextid, 100)
+ if(nextid >= SHORT_REAL_LIMIT)
+ nextid += min(1, 2**round(nextid/SHORT_REAL_LIMIT))
+ else
+ nextid++
+ SStimer.timer_id_dict[id] = src
+
+ name = "Timer: [id] (\ref[src]), TTR: [timeToRun], Flags: [jointext(bitfield2list(flags, list("TIMER_UNIQUE", "TIMER_OVERRIDE", "TIMER_CLIENT_TIME", "TIMER_STOPPABLE", "TIMER_NO_HASH_WAIT")), ", ")], callBack: \ref[callBack], callBack.object: [callBack.object]\ref[callBack.object]([getcallingtype()]), callBack.delegate:[callBack.delegate]([callBack.arguments ? callBack.arguments.Join(", ") : ""])"
+
+ if((timeToRun < world.time || timeToRun < SStimer.head_offset) && !(flags & TIMER_CLIENT_TIME))
+ CRASH("Invalid timer state: Timer created that would require a backtrack to run (addtimer would never let this happen): [SStimer.get_timer_debug_string(src)]")
+
+ if(callBack.object != GLOBAL_PROC)
+ LAZYADD(callBack.object.active_timers, src)
+
+
+ var/list/L
+
+ if(flags & TIMER_CLIENT_TIME)
+ L = SStimer.clienttime_timers
+ else if(timeToRun >= TIMER_MAX)
+ L = SStimer.second_queue
+
+
+ if(L)
+ //binary search sorted insert
+ var/cttl = length(L)
+ if(cttl)
+ var/left = 1
+ var/right = cttl
+ var/mid = (left+right) >> 1 //rounded divide by two for hedgehogs
+
+ var/datum/timedevent/item
+ while(left < right)
+ item = L[mid]
+ if(item.timeToRun <= timeToRun)
+ left = mid+1
+ else
+ right = mid
+ mid = (left+right) >> 1
+
+ item = L[mid]
+ mid = item.timeToRun > timeToRun ? mid : mid+1
+ L.Insert(mid, src)
+
+ else
+ L += src
+ return
+
+ //get the list of buckets
+ var/list/bucket_list = SStimer.bucket_list
+
+ //calculate our place in the bucket list
+ var/bucket_pos = BUCKET_POS(src)
+
+ //get the bucket for our tick
+ var/datum/timedevent/bucket_head = bucket_list[bucket_pos]
+ SStimer.bucket_count++
+ //empty bucket, we will just add ourselves
+ if(!bucket_head)
+ bucket_list[bucket_pos] = src
+ return
+ //other wise, lets do a simplified linked list add.
+ if(!bucket_head.prev)
+ bucket_head.prev = bucket_head
+ next = bucket_head
+ prev = bucket_head.prev
+ next.prev = src
+ prev.next = src
+
+/datum/timedevent/Destroy()
+ ..()
+ if(flags & TIMER_UNIQUE && hash)
+ SStimer.hashes -= hash
+
+ if(callBack && callBack.object && callBack.object != GLOBAL_PROC && callBack.object.active_timers)
+ callBack.object.active_timers -= src
+ UNSETEMPTY(callBack.object.active_timers)
+
+ callBack = null
+
+ if(flags & TIMER_STOPPABLE)
+ SStimer.timer_id_dict -= id
+
+ if(flags & TIMER_CLIENT_TIME)
+ if(!spent)
+ spent = world.time
+ SStimer.clienttime_timers -= src
+ return QDEL_HINT_IWILLGC
+
+ if(!spent)
+ spent = world.time
+ var/bucketpos = BUCKET_POS(src)
+ var/datum/timedevent/buckethead
+ var/list/bucket_list = SStimer.bucket_list
+ if(bucketpos > 0)
+ buckethead = bucket_list[bucketpos]
+
+ if(buckethead == src)
+ bucket_list[bucketpos] = next
+ SStimer.bucket_count--
+ else if(timeToRun < TIMER_MAX || next || prev)
+ SStimer.bucket_count--
+ else
+ var/l = length(SStimer.second_queue)
+ SStimer.second_queue -= src
+ if(l == length(SStimer.second_queue))
+ SStimer.bucket_count--
+
+ if(prev == next && next)
+ next.prev = null
+ prev.next = null
+ else
+ if(prev)
+ prev.next = next
+ if(next)
+ next.prev = prev
+ else
+ if(prev && prev.next == src)
+ prev.next = next
+ if(next && next.prev == src)
+ next.prev = prev
+ next = null
+ prev = null
+ return QDEL_HINT_IWILLGC
+
+/datum/timedevent/proc/getcallingtype()
+ . = "ERROR"
+ if(callBack.object == GLOBAL_PROC)
+ . = "GLOBAL_PROC"
+ else
+ . = "[callBack.object.type]"
+
+/proc/addtimer(datum/callback/callback, wait = 0, flags = 0)
+ if(!callback)
+ CRASH("addtimer called without a callback")
+
+ if(wait < 0)
+ stack_trace("addtimer called with a negative wait. Converting to 0")
+
+ //alot of things add short timers on themselves in their destroy, we ignore those cases
+ if(wait >= 1 && callback && callback.object && callback.object != GLOBAL_PROC && QDELETED(callback.object))
+ stack_trace("addtimer called with a callback assigned to a qdeleted object")
+
+ wait = max(wait, 0)
+
+ if(wait >= INFINITY)
+ CRASH("Attempted to create timer with INFINITY delay")
+
+ var/hash
+
+ if(flags & TIMER_UNIQUE)
+ var/list/hashlist
+ if(flags & TIMER_NO_HASH_WAIT)
+ hashlist = list(callback.object, "([callback.object.UID()])", callback.delegate, flags & TIMER_CLIENT_TIME)
+ else
+ hashlist = list(callback.object, "([callback.object.UID()])", callback.delegate, wait, flags & TIMER_CLIENT_TIME)
+ hashlist += callback.arguments
+ hash = hashlist.Join("|||||||")
+
+ var/datum/timedevent/hash_timer = SStimer.hashes[hash]
+ if(hash_timer)
+ if(hash_timer.spent) //it's pending deletion, pretend it doesn't exist.
+ hash_timer.hash = null //but keep it from accidentally deleting us
+ else
+ if(flags & TIMER_OVERRIDE)
+ hash_timer.hash = null //no need having it delete it's hash if we are going to replace it
+ qdel(hash_timer)
+ else
+ if(hash_timer.flags & TIMER_STOPPABLE)
+ . = hash_timer.id
+ return
+
+
+ var/timeToRun = world.time + wait
+ if(flags & TIMER_CLIENT_TIME)
+ timeToRun = REALTIMEOFDAY + wait
+
+ var/datum/timedevent/timer = new(callback, timeToRun, flags, hash)
+ return timer.id
+
+/proc/deltimer(id)
+ if(!id)
+ return FALSE
+ if(id == TIMER_ID_NULL)
+ CRASH("Tried to delete a null timerid. Use TIMER_STOPPABLE flag")
+ if(!istext(id))
+ if(istype(id, /datum/timedevent))
+ qdel(id)
+ return TRUE
+ //id is string
+ var/datum/timedevent/timer = SStimer.timer_id_dict[id]
+ if(timer && !timer.spent)
+ qdel(timer)
+ return TRUE
+ return FALSE
+
+
+#undef BUCKET_LEN
+#undef BUCKET_POS
+#undef TIMER_MAX
+#undef TIMER_ID_MAX
diff --git a/code/controllers/verbs.dm b/code/controllers/verbs.dm
index 7fcce1238a5..3315cafb399 100644
--- a/code/controllers/verbs.dm
+++ b/code/controllers/verbs.dm
@@ -91,7 +91,7 @@
debug_variables(shuttle_master)
feedback_add_details("admin_verb","DShuttle")
if("Timer")
- debug_variables(timer_master)
+ debug_variables(SStimer)
feedback_add_details("admin_verb","DTimer")
if("Weather")
debug_variables(weather_master)
diff --git a/code/datums/datum.dm b/code/datums/datum.dm
index 3e8c11cc1e8..ae9a6df5b4f 100644
--- a/code/datums/datum.dm
+++ b/code/datums/datum.dm
@@ -1,5 +1,6 @@
/datum
var/gc_destroyed //Time when this object was destroyed.
+ var/list/active_timers //for SStimer
var/list/datum_components //for /datum/components
var/var_edited = FALSE //Warranty void if seal is broken
@@ -16,6 +17,14 @@
/datum/proc/Destroy(force = FALSE, ...)
tag = null
+ var/list/timers = active_timers
+ active_timers = null
+ for(var/thing in timers)
+ var/datum/timedevent/timer = thing
+ if(timer.spent)
+ continue
+ qdel(timer)
+
var/list/dc = datum_components
if(dc)
var/all_components = dc[/datum/component]
diff --git a/code/datums/looping_sounds/looping_sound.dm b/code/datums/looping_sounds/looping_sound.dm
new file mode 100644
index 00000000000..2cf79ad318b
--- /dev/null
+++ b/code/datums/looping_sounds/looping_sound.dm
@@ -0,0 +1,100 @@
+/*
+ output_atoms (list of atoms) The destination(s) for the sounds
+
+ mid_sounds (list or soundfile) Since this can be either a list or a single soundfile you can have random sounds. May contain further lists but must contain a soundfile at the end.
+ mid_length (num) The length to wait between playing mid_sounds
+
+ start_sound (soundfile) Played before starting the mid_sounds loop
+ start_length (num) How long to wait before starting the main loop after playing start_sound
+
+ end_sound (soundfile) The sound played after the main loop has concluded
+
+ chance (num) Chance per loop to play a mid_sound
+ volume (num) Sound output volume
+ muted (bool) Private. Used to stop the sound loop.
+ max_loops (num) The max amount of loops to run for.
+ direct (bool) If true plays directly to provided atoms instead of from them
+*/
+/datum/looping_sound
+ var/list/atom/output_atoms
+ var/mid_sounds
+ var/mid_length
+ var/start_sound
+ var/start_length
+ var/end_sound
+ var/chance
+ var/volume = 100
+ var/muted = TRUE
+ var/max_loops
+ var/direct
+
+/datum/looping_sound/New(list/_output_atoms = list(), start_immediately = FALSE, _direct = FALSE)
+ if(!mid_sounds)
+ WARNING("A looping sound datum was created without sounds to play.")
+ return
+
+ output_atoms = _output_atoms
+ direct = _direct
+
+ if(start_immediately)
+ start()
+
+/datum/looping_sound/Destroy()
+ stop()
+ output_atoms = null
+ return ..()
+
+/datum/looping_sound/proc/start(atom/add_thing)
+ if(add_thing)
+ output_atoms |= add_thing
+ if(!muted)
+ return
+ muted = FALSE
+ on_start()
+
+/datum/looping_sound/proc/stop(atom/remove_thing)
+ if(remove_thing)
+ output_atoms -= remove_thing
+ if(muted)
+ return
+ muted = TRUE
+
+/datum/looping_sound/proc/sound_loop(looped = 0)
+ if(muted || (max_loops && looped > max_loops))
+ on_stop(looped)
+ return
+ if(!chance || prob(chance))
+ play(get_sound(looped))
+ addtimer(CALLBACK(src, .proc/sound_loop, ++looped), mid_length)
+
+/datum/looping_sound/proc/play(soundfile)
+ var/list/atoms_cache = output_atoms
+ var/sound/S = sound(soundfile)
+ if(direct)
+ S.channel = open_sound_channel()
+ S.volume = volume
+ for(var/i in 1 to atoms_cache.len)
+ var/atom/thing = atoms_cache[i]
+ if(direct)
+ SEND_SOUND(thing, S)
+ else
+ playsound(thing, S, volume)
+
+/datum/looping_sound/proc/get_sound(looped, _mid_sounds)
+ if(!_mid_sounds)
+ . = mid_sounds
+ else
+ . = _mid_sounds
+ while(!isfile(.) && !isnull(.))
+ . = pickweight(.)
+
+/datum/looping_sound/proc/on_start()
+ var/start_wait = 0
+ if(start_sound)
+ play(start_sound)
+ start_wait = start_length
+ addtimer(CALLBACK(src, .proc/sound_loop), start_wait)
+
+/datum/looping_sound/proc/on_stop(looped)
+ if(end_sound)
+ play(end_sound)
\ No newline at end of file
diff --git a/code/datums/looping_sounds/machinery_sounds.dm b/code/datums/looping_sounds/machinery_sounds.dm
new file mode 100644
index 00000000000..b1f5bdc6908
--- /dev/null
+++ b/code/datums/looping_sounds/machinery_sounds.dm
@@ -0,0 +1,7 @@
+/datum/looping_sound/showering
+ start_sound = 'sound/machines/shower/shower_start.ogg'
+ start_length = 2
+ mid_sounds = list('sound/machines/shower/shower_mid1.ogg' = 1,'sound/machines/shower/shower_mid2.ogg' = 1,'sound/machines/shower/shower_mid3.ogg' = 1)
+ mid_length = 10
+ end_sound = 'sound/machines/shower/shower_end.ogg'
+ volume = 20
\ No newline at end of file
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index 5fee7994a83..be35cf8cca8 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -1538,8 +1538,7 @@
H.update_inv_w_uniform(0,0)
add_attack_logs(missionary, current, "Converted to a zealot for [convert_duration/600] minutes")
- addtimer(src, "remove_zealot", convert_duration, FALSE, jumpsuit) //deconverts after the timer expires
-
+ addtimer(CALLBACK(src, .proc/remove_zealot, jumpsuit), convert_duration) //deconverts after the timer expires
return 1
/datum/mind/proc/remove_zealot(obj/item/clothing/under/jumpsuit = null)
diff --git a/code/datums/outfits/outfit_admin.dm b/code/datums/outfits/outfit_admin.dm
index 525880ee314..74cc6235bb6 100644
--- a/code/datums/outfits/outfit_admin.dm
+++ b/code/datums/outfits/outfit_admin.dm
@@ -145,6 +145,7 @@
belt = /obj/item/gun/projectile/automatic/pistol/deagle/camo
l_ear = /obj/item/radio/headset/syndicate/alt
l_pocket = /obj/item/pinpointer/advpinpointer
+ r_pocket = null // stop them getting a radio uplink, they get an implant instead
backpack_contents = list(
/obj/item/storage/box/engineer = 1,
@@ -158,7 +159,6 @@
id_icon = "commander"
id_access = "Syndicate Operative Leader"
- uplink_uses = 500
/datum/outfit/admin/syndicate/officer/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
. = ..()
diff --git a/code/datums/spells/ethereal_jaunt.dm b/code/datums/spells/ethereal_jaunt.dm
index 43c3d3a61cd..6e3ead3849e 100644
--- a/code/datums/spells/ethereal_jaunt.dm
+++ b/code/datums/spells/ethereal_jaunt.dm
@@ -26,7 +26,7 @@
if(!target.can_safely_leave_loc()) // No more brainmobs hopping out of their brains
to_chat(target, "You are somehow too bound to your current location to abandon it.")
continue
- addtimer(src, "do_jaunt", 0, FALSE, target)
+ INVOKE_ASYNC(src, .proc/do_jaunt, target)
/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/proc/do_jaunt(mob/living/target)
target.notransform = 1
diff --git a/code/datums/weather/weather.dm b/code/datums/weather/weather.dm
index c87b2b455e7..320cc137806 100644
--- a/code/datums/weather/weather.dm
+++ b/code/datums/weather/weather.dm
@@ -31,7 +31,7 @@
var/list/impacted_areas = list() //Areas to be affected by the weather, calculated when the weather begins
var/target_z = MAIN_STATION //The z-level to affect
var/list/protected_areas = list()//Areas that are protected and excluded from the affected areas.
-
+
var/overlay_layer = 10 //Since it's above everything else, this is the layer used by default. 2 is below mobs and walls if you need to use that.
var/aesthetic = FALSE //If the weather has no purpose other than looks
var/immunity_type = "storm" //Used by mobs to prevent them from being affected by the weather
@@ -70,7 +70,7 @@
to_chat(M, telegraph_message)
if(telegraph_sound)
M << sound(telegraph_sound)
- addtimer(src, "start", telegraph_duration)
+ addtimer(CALLBACK(src, .proc/start), telegraph_duration)
/datum/weather/proc/start()
if(stage >= MAIN_STAGE)
@@ -85,7 +85,7 @@
if(weather_sound)
M << sound(weather_sound)
weather_master.processing_weather |= src
- addtimer(src, "wind_down", weather_duration)
+ addtimer(CALLBACK(src, .proc/wind_down), weather_duration)
/datum/weather/proc/wind_down()
if(stage >= WIND_DOWN_STAGE)
@@ -100,7 +100,7 @@
if(end_sound)
M << sound(end_sound)
weather_master.processing_weather -= src
- addtimer(src, "end", end_duration)
+ addtimer(CALLBACK(src, .proc/end), end_duration)
/datum/weather/proc/end()
if(stage == END_STAGE)
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 5f09e2d5349..19af319aa20 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -298,7 +298,7 @@
/atom/proc/hitby(atom/movable/AM, skipcatch, hitpush, blocked)
if(density && !has_gravity(AM)) //thrown stuff bounces off dense stuff in no grav, unless the thrown stuff ends up inside what it hit(embedding, bola, etc...).
- addtimer(src, "hitby_react", 2, TRUE, AM)
+ addtimer(CALLBACK(src, .proc/hitby_react, AM), 2)
/atom/proc/hitby_react(atom/movable/AM)
if(AM && isturf(AM.loc))
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 714edc0fbfc..c7e6401e204 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -166,11 +166,11 @@
/mob/living/forceMove(atom/destination)
if(buckled)
- addtimer(src, "check_buckled", 1, TRUE)
+ addtimer(CALLBACK(src, .proc/check_buckled), 1, TIMER_UNIQUE)
if(buckled_mob)
- addtimer(buckled_mob, "check_buckled", 1, TRUE)
+ addtimer(CALLBACK(buckled_mob, .proc/check_buckled), 1, TIMER_UNIQUE)
if(pulling)
- addtimer(src, "check_pull", 1, TRUE)
+ addtimer(CALLBACK(src, .proc/check_pull), 1, TIMER_UNIQUE)
. = ..()
if(client)
reset_perspective(destination)
diff --git a/code/game/gamemodes/changeling/powers/biodegrade.dm b/code/game/gamemodes/changeling/powers/biodegrade.dm
index 5e4b0e7fa12..3103416b9e4 100644
--- a/code/game/gamemodes/changeling/powers/biodegrade.dm
+++ b/code/game/gamemodes/changeling/powers/biodegrade.dm
@@ -18,7 +18,7 @@
return FALSE
user.visible_message("[user] vomits a glob of acid on \his [O]!", \
"We vomit acidic ooze onto our restraints!")
- addtimer(src, "dissolve_handcuffs", 30, FALSE, user, O)
+ addtimer(CALLBACK(src, .proc/dissolve_handcuffs, user, O), 30)
used = TRUE
if(user.wear_suit && user.wear_suit.breakouttime && !used)
@@ -27,7 +27,7 @@
return FALSE
user.visible_message("[user] vomits a glob of acid across the front of \his [S]!", \
"We vomit acidic ooze onto our straight jacket!")
- addtimer(src, "dissolve_straightjacket", 30, FALSE, user, S)
+ addtimer(CALLBACK(src, .proc/dissolve_straightjacket, user, S), 30)
used = TRUE
@@ -37,7 +37,7 @@
return FALSE
C.visible_message("[C]'s hinges suddenly begin to melt and run!")
to_chat(user, "We vomit acidic goop onto the interior of [C]!")
- addtimer(src, "open_closet", 70, FALSE, user, C)
+ addtimer(CALLBACK(src, .proc/open_closet, user, C), 70)
used = TRUE
if(istype(user.loc, /obj/structure/spider/cocoon) && !used)
@@ -46,7 +46,7 @@
return FALSE
C.visible_message("[src] shifts and starts to fall apart!")
to_chat(user, "We secrete acidic enzymes from our skin and begin melting our cocoon...")
- addtimer(src, "dissolve_cocoon", 25, FALSE, user, C) //Very short because it's just webs
+ addtimer(CALLBACK(src, .proc/dissolve_cocoon, user, C), 25) //Very short because it's just webs
used = TRUE
if(used)
diff --git a/code/game/gamemodes/changeling/powers/fakedeath.dm b/code/game/gamemodes/changeling/powers/fakedeath.dm
index 156e8ffb896..a166fd74502 100644
--- a/code/game/gamemodes/changeling/powers/fakedeath.dm
+++ b/code/game/gamemodes/changeling/powers/fakedeath.dm
@@ -17,7 +17,7 @@
user.status_flags |= FAKEDEATH //play dead
user.update_canmove()
- addtimer(src, "ready_to_regenerate", LING_FAKEDEATH_TIME, FALSE, user)
+ addtimer(CALLBACK(src, .proc/ready_to_regenerate, user), LING_FAKEDEATH_TIME)
feedback_add_details("changeling_powers","FD")
return 1
diff --git a/code/game/gamemodes/changeling/powers/fleshmend.dm b/code/game/gamemodes/changeling/powers/fleshmend.dm
index 08786b6d6db..56417ac4335 100644
--- a/code/game/gamemodes/changeling/powers/fleshmend.dm
+++ b/code/game/gamemodes/changeling/powers/fleshmend.dm
@@ -31,7 +31,7 @@
by quick repeated use!")
recent_uses++
- addtimer(src, "fleshmend", 0, FALSE, user)
+ INVOKE_ASYNC(src, .proc/fleshmend, user)
feedback_add_details("changeling_powers","RR")
return TRUE
diff --git a/code/game/gamemodes/malfunction/Malf_Modules.dm b/code/game/gamemodes/malfunction/Malf_Modules.dm
index 57ce00c92af..8f4b5dab95e 100644
--- a/code/game/gamemodes/malfunction/Malf_Modules.dm
+++ b/code/game/gamemodes/malfunction/Malf_Modules.dm
@@ -159,9 +159,8 @@
for(var/obj/machinery/door/D in airlocks)
if(!is_station_level(D.z))
continue
- spawn(0)
- D.hostile_lockdown(src)
- addtimer(D, "disable_lockdown", 900)
+ INVOKE_ASYNC(D, /obj/machinery/door.proc/hostile_lockdown, src)
+ addtimer(CALLBACK(D, /obj/machinery/door.proc/disable_lockdown), 900)
post_status("alert", "lockdown")
diff --git a/code/game/gamemodes/miniantags/borer/borer.dm b/code/game/gamemodes/miniantags/borer/borer.dm
index 96b83c9a8da..00bf335c576 100644
--- a/code/game/gamemodes/miniantags/borer/borer.dm
+++ b/code/game/gamemodes/miniantags/borer/borer.dm
@@ -43,7 +43,8 @@
to_chat(B.host, "You feel the captive mind of [src] begin to resist your control.")
var/delay = (rand(350,450) + B.host.brainloss)
- addtimer(src, "return_control", delay, FALSE, B)
+ addtimer(CALLBACK(src, .proc/return_control, B), delay)
+
/mob/living/captive_brain/proc/return_control(mob/living/simple_animal/borer/B)
if(!B || !B.controlling)
@@ -534,7 +535,7 @@
leaving = TRUE
- addtimer(src, "let_go", 200)
+ addtimer(CALLBACK(src, .proc/let_go), 200)
/mob/living/simple_animal/borer/proc/let_go()
@@ -611,7 +612,7 @@
bonding = TRUE
var/delay = 300+(host.getBrainLoss()*5)
- addtimer(src, "assume_control", delay)
+ addtimer(CALLBACK(src, .proc/assume_control), delay)
/mob/living/simple_animal/borer/proc/assume_control()
if(!host || !src || controlling)
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index 907e037f72c..501f510348b 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -118,7 +118,7 @@ About the new airlock wires panel:
/obj/machinery/door/airlock/Initialize()
. = ..()
if(closeOtherId != null)
- addtimer(src, "update_other_id", 5)
+ addtimer(CALLBACK(src, .proc/update_other_id), 5)
if(glass)
airlock_material = "glass"
if(security_level > AIRLOCK_SECURITY_METAL)
@@ -226,13 +226,11 @@ About the new airlock wires panel:
/obj/machinery/door/airlock/proc/loseMainPower()
main_power_lost_until = mainPowerCablesCut() ? -1 : world.time + SecondsToTicks(60)
if(main_power_lost_until > 0)
- main_power_timer = addtimer(src, "regainMainPower", SecondsToTicks(60), 1)
-
+ main_power_timer = addtimer(CALLBACK(src, .proc/regainMainPower), SecondsToTicks(60), TIMER_UNIQUE | TIMER_STOPPABLE)
// If backup power is permanently disabled then activate in 10 seconds if possible, otherwise it's already enabled or a timer is already running
if(backup_power_lost_until == -1 && !backupPowerCablesCut())
backup_power_lost_until = world.time + SecondsToTicks(10)
- backup_power_timer = addtimer(src, "regainBackupPower", SecondsToTicks(10), 1)
-
+ backup_power_timer = addtimer(CALLBACK(src, .proc/regainBackupPower), SecondsToTicks(10), TIMER_UNIQUE | TIMER_STOPPABLE)
// Disable electricity if required
if(electrified_until && isAllPowerLoss())
electrify(0)
@@ -240,7 +238,7 @@ About the new airlock wires panel:
/obj/machinery/door/airlock/proc/loseBackupPower()
backup_power_lost_until = backupPowerCablesCut() ? -1 : world.time + SecondsToTicks(60)
if(backup_power_lost_until > 0)
- backup_power_timer = addtimer(src, "regainBackupPower", SecondsToTicks(60), 1)
+ backup_power_timer = addtimer(CALLBACK(src, .proc/regainBackupPower), SecondsToTicks(60), TIMER_UNIQUE | TIMER_STOPPABLE)
// Disable electricity if required
if(electrified_until && isAllPowerLoss())
@@ -288,8 +286,7 @@ About the new airlock wires panel:
message = "The door is now electrified [duration == -1 ? "permanently" : "for [duration] second\s"]."
electrified_until = duration == -1 ? -1 : world.time + SecondsToTicks(duration)
if(duration != -1)
- electrified_timer = addtimer(src, "electrify", SecondsToTicks(duration), 1, 0)
-
+ electrified_timer = addtimer(CALLBACK(src, .proc/electrify, 0), SecondsToTicks(duration), TIMER_UNIQUE | TIMER_STOPPABLE)
if(feedback && message)
to_chat(usr, message)
@@ -1080,8 +1077,7 @@ About the new airlock wires panel:
// The `addtimer` system has the advantage of being cancelable
if(autoclose)
- autoclose_timer = addtimer(src, "autoclose", normalspeed ? auto_close_time : auto_close_time_dangerous, unique = 1)
-
+ autoclose_timer = addtimer(CALLBACK(src, .proc/autoclose), normalspeed ? auto_close_time : auto_close_time_dangerous, TIMER_UNIQUE | TIMER_STOPPABLE)
return TRUE
/obj/machinery/door/airlock/close(forced=0, override = 0)
@@ -1098,7 +1094,7 @@ About the new airlock wires panel:
for(var/turf/turf in locs)
for(var/atom/movable/M in turf)
if(M.density && M != src) //something is blocking the door
- addtimer(src, "autoclose", 60)
+ addtimer(CALLBACK(src, .proc/autoclose), 60)
use_power(360) //360 W seems much more appropriate for an actuator moving an industrial door capable of crushing people
if(forced)
diff --git a/code/game/machinery/doors/airlock_types.dm b/code/game/machinery/doors/airlock_types.dm
index bff4bda660d..db78e8c82ef 100644
--- a/code/game/machinery/doors/airlock_types.dm
+++ b/code/game/machinery/doors/airlock_types.dm
@@ -150,13 +150,15 @@
/obj/machinery/door/airlock/uranium/New()
..()
- addtimer(src, "radiate", event_step)
+ addtimer(CALLBACK(src, .proc/radiate), event_step)
+
/obj/machinery/door/airlock/uranium/proc/radiate()
if(prob(50))
for(var/mob/living/L in range (3,src))
L.apply_effect(15,IRRADIATE,0)
- addtimer(src, "radiate", event_step)
+ addtimer(CALLBACK(src, .proc/radiate), event_step)
+
/obj/machinery/door/airlock/uranium/glass
opacity = 0
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index 4652cde6224..78949fa0e45 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -279,7 +279,7 @@
// The `addtimer` system has the advantage of being cancelable
if(autoclose)
- autoclose_timer = addtimer(src, "autoclose", normalspeed ? auto_close_time : auto_close_time_dangerous, unique = 1)
+ autoclose_timer = addtimer(CALLBACK(src, .proc/autoclose), normalspeed ? auto_close_time : auto_close_time_dangerous, TIMER_UNIQUE | TIMER_STOPPABLE)
return TRUE
@@ -292,7 +292,7 @@
for(var/atom/movable/M in get_turf(src))
if(M.density && M != src) //something is blocking the door
if(autoclose)
- addtimer(src, "autoclose", 60)
+ addtimer(CALLBACK(src, .proc/autoclose), 60)
return
operating = TRUE
diff --git a/code/game/machinery/recycler.dm b/code/game/machinery/recycler.dm
index 13a2ba49ddd..d80a628927b 100644
--- a/code/game/machinery/recycler.dm
+++ b/code/game/machinery/recycler.dm
@@ -143,7 +143,7 @@ var/const/SAFETY_COOLDOWN = 100
safety_mode = 1
update_icon()
L.loc = loc
- addtimer(src, "reboot", SAFETY_COOLDOWN)
+ addtimer(CALLBACK(src, .proc/reboot), SAFETY_COOLDOWN)
/obj/machinery/recycler/proc/reboot()
playsound(loc, 'sound/machines/ping.ogg', 50, 0)
diff --git a/code/game/machinery/syndicatebomb.dm b/code/game/machinery/syndicatebomb.dm
index 48637f9e67f..a4c73cc8fac 100644
--- a/code/game/machinery/syndicatebomb.dm
+++ b/code/game/machinery/syndicatebomb.dm
@@ -438,7 +438,7 @@
chem_splash(get_turf(src), spread_range, list(reactants), temp_boost)
// Detonate it again in one second, until it's out of juice.
- addtimer(src, "detonate", 10)
+ addtimer(CALLBACK(src, .proc/detonate), 10)
// If it's not a time release bomb, do normal explosion
diff --git a/code/game/mecha/equipment/mecha_equipment.dm b/code/game/mecha/equipment/mecha_equipment.dm
index 98e7b6f342d..b990029423c 100644
--- a/code/game/mecha/equipment/mecha_equipment.dm
+++ b/code/game/mecha/equipment/mecha_equipment.dm
@@ -90,7 +90,7 @@
/obj/item/mecha_parts/mecha_equipment/proc/start_cooldown()
set_ready_state(0)
chassis.use_power(energy_drain)
- addtimer(src, "set_ready_state", equip_cooldown, FALSE, 1)
+ addtimer(CALLBACK(src, .proc/set_ready_state, 1), equip_cooldown)
/obj/item/mecha_parts/mecha_equipment/proc/do_after_cooldown(atom/target)
if(!chassis)
diff --git a/code/game/objects/effects/decals/Cleanable/humans.dm b/code/game/objects/effects/decals/Cleanable/humans.dm
index 02f89f6f0f6..f832b446cd5 100644
--- a/code/game/objects/effects/decals/Cleanable/humans.dm
+++ b/code/game/objects/effects/decals/Cleanable/humans.dm
@@ -44,7 +44,7 @@ var/global/list/image/splatter_cache=list()
if(B.blood_DNA)
blood_DNA |= B.blood_DNA.Copy()
qdel(B)
- dry_timer = addtimer(src, "dry", DRYING_TIME * (amount+1))
+ dry_timer = addtimer(CALLBACK(src, .proc/dry), DRYING_TIME * (amount+1), TIMER_STOPPABLE)
/obj/effect/decal/cleanable/blood/Destroy()
if(GAMEMODE_IS_CULT)
diff --git a/code/game/objects/effects/effect_system/effect_system.dm b/code/game/objects/effects/effect_system/effect_system.dm
index fa65008d307..7fb397297d7 100644
--- a/code/game/objects/effects/effect_system/effect_system.dm
+++ b/code/game/objects/effects/effect_system/effect_system.dm
@@ -51,7 +51,7 @@ would spawn and follow the beaker, even if it is carried or thrown.
for(var/i in 1 to number)
if(total_effects > 20)
return
- addtimer(src, "generate_effect", 0)
+ INVOKE_ASYNC(src, .proc/generate_effect)
/datum/effect_system/proc/generate_effect()
if(holder)
@@ -67,7 +67,7 @@ would spawn and follow the beaker, even if it is carried or thrown.
for(var/j in 1 to steps_amt)
sleep(5)
step(E,direction)
- addtimer(src, "decrement_total_effect", 20)
+ addtimer(CALLBACK(src, .proc/decrement_total_effect), 20)
/datum/effect_system/proc/decrement_total_effect()
total_effects--
diff --git a/code/game/objects/effects/effect_system/effects_explosion.dm b/code/game/objects/effects/effect_system/effects_explosion.dm
index aa3ecd8376b..5ad00157a02 100644
--- a/code/game/objects/effects/effect_system/effects_explosion.dm
+++ b/code/game/objects/effects/effect_system/effects_explosion.dm
@@ -58,4 +58,4 @@
/datum/effect_system/explosion/smoke/start()
..()
- addtimer(src, "create_smoke", 5)
+ addtimer(CALLBACK(src, .proc/create_smoke), 5)
diff --git a/code/game/objects/effects/effect_system/effects_smoke.dm b/code/game/objects/effects/effect_system/effects_smoke.dm
index 64421b35fe9..385067e0611 100644
--- a/code/game/objects/effects/effect_system/effects_smoke.dm
+++ b/code/game/objects/effects/effect_system/effects_smoke.dm
@@ -39,7 +39,7 @@
/obj/effect/particle_effect/smoke/proc/kill_smoke()
processing_objects.Remove(src)
- addtimer(src, "fade_out", 0)
+ INVOKE_ASYNC(src, .proc/fade_out)
QDEL_IN(src, 10)
/obj/effect/particle_effect/smoke/process()
@@ -67,7 +67,7 @@
if(C.smoke_delay)
return FALSE
C.smoke_delay++
- addtimer(src, "remove_smoke_delay", 10, FALSE, C)
+ addtimer(CALLBACK(src, .proc/remove_smoke_delay, C), 10)
return TRUE
/obj/effect/particle_effect/smoke/proc/remove_smoke_delay(mob/living/carbon/C)
diff --git a/code/game/objects/effects/glowshroom.dm b/code/game/objects/effects/glowshroom.dm
index 9ecafee37f0..4cf5e79cc77 100644
--- a/code/game/objects/effects/glowshroom.dm
+++ b/code/game/objects/effects/glowshroom.dm
@@ -72,7 +72,7 @@
else //if on the floor, glowshroom on-floor sprite
icon_state = "[base_icon_state]f"
- addtimer(src, "Spread", delay, FALSE)
+ addtimer(CALLBACK(src, .proc/Spread), delay)
/obj/structure/glowshroom/proc/Spread()
var/turf/ownturf = get_turf(src)
@@ -117,7 +117,7 @@
shrooms_planted++ //if we failed due to generation, don't try to plant one later
if(shrooms_planted < myseed.yield) //if we didn't get all possible shrooms planted, try again later
myseed.yield -= shrooms_planted
- addtimer(src, "Spread", delay, FALSE)
+ addtimer(CALLBACK(src, .proc/Spread), delay)
/obj/structure/glowshroom/proc/CalcDir(turf/location = loc)
var/direction = 16
diff --git a/code/game/objects/items/devices/flash.dm b/code/game/objects/items/devices/flash.dm
index 3f56932371d..0e2e3cc3785 100644
--- a/code/game/objects/items/devices/flash.dm
+++ b/code/game/objects/items/devices/flash.dm
@@ -208,7 +208,7 @@
to_chat(I.owner, "Your photon projector implant overheats and deactivates!")
I.Retract()
overheat = FALSE
- addtimer(src, "cooldown", flashcd * 2)
+ addtimer(CALLBACK(src, .proc/cooldown), flashcd * 2)
/obj/item/flash/armimplant/try_use_flash(mob/user = null)
if(overheat)
@@ -216,7 +216,7 @@
to_chat(I.owner, "Your photon projector is running too hot to be used again so quickly!")
return FALSE
overheat = TRUE
- addtimer(src, "cooldown", flashcd)
+ addtimer(CALLBACK(src, .proc/cooldown), flashcd)
playsound(src.loc, 'sound/weapons/flash.ogg', 100, 1)
update_icon(1)
return TRUE
diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm
index 003248cdfbc..530156fbd99 100644
--- a/code/game/objects/items/devices/radio/radio.dm
+++ b/code/game/objects/items/devices/radio/radio.dm
@@ -271,7 +271,7 @@ var/global/list/default_medbay_channels = list(
universal_speak = 1
/mob/living/automatedannouncer/New()
- lifetime_timer = addtimer(src, "autocleanup", SecondsToTicks(10))
+ lifetime_timer = addtimer(CALLBACK(src, .proc/autocleanup), SecondsToTicks(10), TIMER_STOPPABLE)
..()
/mob/living/automatedannouncer/Destroy()
@@ -620,7 +620,7 @@ var/global/list/default_medbay_channels = list(
/obj/item/radio/emp_act(severity)
on = 0
disable_timer++
- addtimer(src, "enable_radio", rand(100, 200))
+ addtimer(CALLBACK(src, .proc/enable_radio), rand(100, 200))
if(listening)
visible_message("[src] buzzes violently!")
diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm
index 3382292bc3a..b321721b639 100644
--- a/code/game/objects/items/toys.dm
+++ b/code/game/objects/items/toys.dm
@@ -303,7 +303,7 @@
/obj/effect/decal/cleanable/ash/snappop_phoenix/New()
. = ..()
- addtimer(src, "respawn", respawn_time)
+ addtimer(CALLBACK(src, .proc/respawn), respawn_time)
/obj/effect/decal/cleanable/ash/snappop_phoenix/proc/respawn()
new /obj/item/toy/snappop/phoenix(get_turf(src))
diff --git a/code/game/objects/items/weapons/RCD.dm b/code/game/objects/items/weapons/RCD.dm
index b52a92112eb..fe132c107b0 100644
--- a/code/game/objects/items/weapons/RCD.dm
+++ b/code/game/objects/items/weapons/RCD.dm
@@ -385,7 +385,7 @@ RCD
buzz loudly!","[src] begins \
vibrating violently!")
// 5 seconds to get rid of it
- addtimer(src, "detonate_pulse_explode", 50)
+ addtimer(CALLBACK(src, .proc/detonate_pulse_explode), 50)
/obj/item/rcd/proc/detonate_pulse_explode()
explosion(src, 0, 0, 3, 1, flame_range = 1)
diff --git a/code/game/objects/items/weapons/explosives.dm b/code/game/objects/items/weapons/explosives.dm
index 381e6177bb2..6164ed01047 100644
--- a/code/game/objects/items/weapons/explosives.dm
+++ b/code/game/objects/items/weapons/explosives.dm
@@ -85,7 +85,7 @@
target.overlays += image_overlay
if(!nadeassembly)
to_chat(user, "You plant the bomb. Timer counting down from [det_time].")
- addtimer(src, "prime", det_time*10)
+ addtimer(CALLBACK(src, .proc/prime), det_time*10)
/obj/item/grenade/plastic/suicide_act(mob/user)
message_admins("[key_name_admin(user)](?) (FLW) suicided with [src.name] at ([user.x],[user.y],[user.z] - JMP)",0,1)
diff --git a/code/game/objects/items/weapons/grenades/chem_grenade.dm b/code/game/objects/items/weapons/grenades/chem_grenade.dm
index c6899d71c22..de85ec64a58 100644
--- a/code/game/objects/items/weapons/grenades/chem_grenade.dm
+++ b/code/game/objects/items/weapons/grenades/chem_grenade.dm
@@ -422,7 +422,7 @@
message_admins("grenade primed by an assembly, attached by [key_name_admin(M)](?) (FLW) and last touched by [key_name_admin(last)](?) (FLW) ([nadeassembly.a_left.name] and [nadeassembly.a_right.name]) at [A.name] (JMP).")
log_game("grenade primed by an assembly, attached by [key_name(M)] and last touched by [key_name(last)] ([nadeassembly.a_left.name] and [nadeassembly.a_right.name]) at [A.name] ([T.x], [T.y], [T.z])")
else
- addtimer(src, "prime", det_time)
+ addtimer(CALLBACK(src, .proc/prime), det_time)
var/turf/DT = get_turf(src)
var/area/DA = get_area(DT)
log_game("A grenade detonated at [DA.name] ([DT.x], [DT.y], [DT.z])")
diff --git a/code/game/objects/items/weapons/grenades/ghettobomb.dm b/code/game/objects/items/weapons/grenades/ghettobomb.dm
index d24f93736e5..d0dc2c8fe06 100644
--- a/code/game/objects/items/weapons/grenades/ghettobomb.dm
+++ b/code/game/objects/items/weapons/grenades/ghettobomb.dm
@@ -53,7 +53,7 @@
if(iscarbon(user))
var/mob/living/carbon/C = user
C.throw_mode_on()
- addtimer(src, "prime", det_time)
+ addtimer(CALLBACK(src, .proc/prime), det_time)
/obj/item/grenade/iedcasing/prime() //Blowing that can up
update_mob()
diff --git a/code/game/objects/items/weapons/legcuffs.dm b/code/game/objects/items/weapons/legcuffs.dm
index b238899676b..e6ba57ff8da 100644
--- a/code/game/objects/items/weapons/legcuffs.dm
+++ b/code/game/objects/items/weapons/legcuffs.dm
@@ -132,7 +132,7 @@
/obj/item/restraints/legcuffs/beartrap/energy/New()
..()
- addtimer(src, "dissipate", 100)
+ addtimer(CALLBACK(src, .proc/dissipate), 100)
/obj/item/restraints/legcuffs/beartrap/energy/proc/dissipate()
if(!ismob(loc))
diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm
index 0738d57e8fb..fcd233318d5 100644
--- a/code/game/objects/obj_defense.dm
+++ b/code/game/objects/obj_defense.dm
@@ -162,7 +162,7 @@
being_shocked = TRUE
var/power_bounced = power * 0.5
tesla_zap(src, 3, power_bounced)
- addtimer(src, "reset_shocked", 10)
+ addtimer(CALLBACK(src, .proc/reset_shocked), 10)
/obj/proc/reset_shocked()
being_shocked = FALSE
diff --git a/code/game/objects/structures/barsign.dm b/code/game/objects/structures/barsign.dm
index 6c627bb40ce..d392deeec81 100644
--- a/code/game/objects/structures/barsign.dm
+++ b/code/game/objects/structures/barsign.dm
@@ -105,7 +105,7 @@
to_chat(user, "Nothing interesting happens!")
return
to_chat(user, "You emag the barsign. Takeover in progress...")
- addtimer(src, "post_emag", 100)
+ addtimer(CALLBACK(src, .proc/post_emag), 100)
/obj/structure/sign/barsign/proc/post_emag()
if(broken || emagged)
diff --git a/code/game/objects/structures/grille.dm b/code/game/objects/structures/grille.dm
index 284001de2bf..d5dccaf0d03 100644
--- a/code/game/objects/structures/grille.dm
+++ b/code/game/objects/structures/grille.dm
@@ -146,6 +146,7 @@
return ..()
/obj/structure/grille/proc/build_window(obj/item/stack/sheet/S, mob/user)
+ var/dir_to_set = NORTH
if(!istype(S) || !user)
return
if(broken)
@@ -160,59 +161,40 @@
if(!getRelativeDirection(src, user) && (user.loc != loc)) //essentially a cardinal direction adjacent or sharing same loc check
to_chat(user, "You can't reach.")
return
- if(/obj/structure/window/full in loc) //check for a full window already present (blocks the whole tile)
- to_chat(user, "There is already a full window there.")
- return
- var/selection = alert(user, "What type of window would you like to place?", "Window Construction", "One Direction", "Full", "Cancel")
- if(selection == "Cancel")
- return
- if(selection == "Full")
- if(S.get_amount() < 2)
- to_chat(user, "You need at least two sheets of glass for that!")
+ if(loc == user.loc)
+ dir_to_set = user.dir
+ else
+ if(x == user.x)
+ if(y > user.y)
+ dir_to_set = SOUTH
+ else
+ dir_to_set = NORTH
+ else if(y == user.y)
+ if(x > user.x)
+ dir_to_set = WEST
+ else
+ dir_to_set = EAST
+ for(var/obj/structure/window/WINDOW in loc)
+ if(WINDOW.dir == dir_to_set)
+ to_chat(user, "There is already a window facing this way there.")
return
- if(do_after(user, 20, target = src)) //glass doesn't have a toolspeed, so no multiplier
- if(broken || !anchored || !src) //make sure the grille is still intact, anchored, and exists!
- return
- if(S.get_amount() < 2) //make sure we still have enough for this!
- return
- if(!getRelativeDirection(src, user) && (user.loc != loc)) //make sure we can still do this from our location
- return
- var/obj/structure/window/W = new S.full_window(get_turf(src))
- S.use(2)
- W.anchored = 0
- W.state = 0
- to_chat(user, "You place [W] on [src].")
- W.update_icon()
- return
- if(selection == "One Direction")
- var/dir_selection = input("Which direction will this window face?", "Direction") as null|anything in list("north", "east", "south", "west")
- if(!dir_selection)
+ to_chat(user, "You start placing the window...")
+ if(do_after(user, 20, target = src))
+ if(!loc || !anchored) //Grille destroyed or unanchored while waiting
return
- var/temp_dir = text2dir(dir_selection)
- for(var/obj/structure/window/W in loc)
- if(istype(W, /obj/structure/window/full)) //double checking in case a full window was created while selecting direction
- to_chat(user, "There is already a full window there.")
+ for(var/obj/structure/window/WINDOW in loc)
+ if(WINDOW.dir == dir_to_set)//checking this for a 2nd time to check if a window was made while we were waiting.
+ to_chat(user, "There is already a window facing this way there.")
return
- if(W.dir == temp_dir) //to avoid building a window on top of an existing window
- to_chat(user, "There is already a window facing this direction there.")
- return
- if(do_after(user, 20, target = src))
- if(broken || !anchored || !src) //make sure the grille is still intact, anchored, and exists!
- return
- if(S.get_amount() < 1) //make sure we still have enough fir this!
- to_chat(user, "You need at least one sheet of glass for that!")
- return
- if(!getRelativeDirection(src, user) && (user.loc != loc)) //make sure we can still do this from our location
- return
- var/obj/structure/window/W = new S.created_window(get_turf(src))
- S.use(1)
- W.setDir(temp_dir)
- W.ini_dir = temp_dir
- W.anchored = 0
- W.state = 0
- to_chat(user, "You place [W] on [src].")
- W.update_icon()
- return
+ var/obj/structure/window/W = new S.created_window(get_turf(src))
+ S.use(1)
+ W.setDir(dir_to_set)
+ W.ini_dir = dir_to_set
+ W.anchored = 0
+ W.state = 0
+ to_chat(user, "You place the [W] on [src].")
+ W.update_icon()
+ return
/obj/structure/grille/attacked_by(obj/item/I, mob/living/user)
user.changeNext_move(CLICK_CD_MELEE)
diff --git a/code/game/objects/structures/guillotine.dm b/code/game/objects/structures/guillotine.dm
new file mode 100644
index 00000000000..0de1a09a74c
--- /dev/null
+++ b/code/game/objects/structures/guillotine.dm
@@ -0,0 +1,235 @@
+#define GUILLOTINE_BLADE_MAX_SHARP 10 // This is maxiumum sharpness that will decapitate without failure
+#define GUILLOTINE_DECAP_MIN_SHARP 7 // Minimum amount of sharpness for decapitation. Any less and it will just deal brute damage
+#define GUILLOTINE_ANIMATION_LENGTH 9 // How many deciseconds the animation is
+#define GUILLOTINE_BLADE_RAISED 1
+#define GUILLOTINE_BLADE_MOVING 2
+#define GUILLOTINE_BLADE_DROPPED 3
+#define GUILLOTINE_BLADE_SHARPENING 4
+#define GUILLOTINE_HEAD_OFFSET 16 // How much we need to move the player to center their head
+#define GUILLOTINE_LAYER_DIFF 1.2 // How much to increase/decrease a head when it's buckled/unbuckled
+#define GUILLOTINE_ACTIVATE_DELAY 30 // Delay for executing someone
+#define GUILLOTINE_WRENCH_DELAY 10
+#define GUILLOTINE_ACTION_INUSE 5
+#define GUILLOTINE_ACTION_WRENCH 6
+
+/obj/structure/guillotine
+ name = "guillotine"
+ desc = "A large structure used to remove the heads of traitors and treasonists."
+ icon = 'icons/obj/guillotine.dmi'
+ icon_state = "guillotine_raised"
+ can_buckle = TRUE
+ anchored = TRUE
+ density = FALSE
+ buckle_lying = FALSE
+ layer = ABOVE_MOB_LAYER
+ var/blade_status = GUILLOTINE_BLADE_RAISED
+ var/blade_sharpness = GUILLOTINE_BLADE_MAX_SHARP // How sharp the blade is
+ var/kill_count = 0
+ var/force_clap = FALSE //You WILL clap if I want you to
+ var/current_action = 0 // What's currently happening to the guillotine
+
+/obj/structure/guillotine/examine(mob/user)
+ ..()
+
+ var/msg = ""
+
+ msg += "It is [anchored ? "wrenched to the floor." : "unsecured. A wrench should fix that."]
"
+
+ if(blade_status == GUILLOTINE_BLADE_RAISED)
+ msg += "The blade is raised, ready to fall, and"
+
+ if(blade_sharpness >= GUILLOTINE_DECAP_MIN_SHARP)
+ msg += " looks sharp enough to decapitate without any resistance."
+ else
+ msg += " doesn't look particularly sharp. Perhaps a whetstone can be used to sharpen it."
+ else
+ msg += "The blade is hidden inside the stocks."
+
+ if(has_buckled_mobs())
+ msg += "
"
+ msg += "Someone appears to be strapped in. You can help them out, or you can harm them by activating the guillotine."
+
+ to_chat(user, msg)
+
+ return msg
+
+/obj/structure/guillotine/attack_hand(mob/user)
+ add_fingerprint(user)
+
+ // Currently being used by something
+ if(current_action)
+ return
+
+ switch(blade_status)
+ if(GUILLOTINE_BLADE_MOVING)
+ return
+ if(GUILLOTINE_BLADE_DROPPED)
+ blade_status = GUILLOTINE_BLADE_MOVING
+ icon_state = "guillotine_raise"
+ addtimer(CALLBACK(src, .proc/raise_blade), GUILLOTINE_ANIMATION_LENGTH)
+ return
+ if(GUILLOTINE_BLADE_RAISED)
+ if(has_buckled_mobs())
+ if(user.a_intent == INTENT_HARM)
+ user.visible_message("[user] begins to pull the lever!",
+ "You begin to the pull the lever.")
+ current_action = GUILLOTINE_ACTION_INUSE
+
+ if(do_after(user, GUILLOTINE_ACTIVATE_DELAY, target = src) && blade_status == GUILLOTINE_BLADE_RAISED)
+ current_action = 0
+ blade_status = GUILLOTINE_BLADE_MOVING
+ icon_state = "guillotine_drop"
+ playsound(src, 'sound/items/unsheath.ogg', 100, 1)
+ addtimer(CALLBACK(src, .proc/drop_blade, user), GUILLOTINE_ANIMATION_LENGTH - 2) // Minus two so we play the sound and decap faster
+ else
+ current_action = 0
+ else
+ unbuckle_mob()
+ else
+ blade_status = GUILLOTINE_BLADE_MOVING
+ icon_state = "guillotine_drop"
+ playsound(src, 'sound/items/unsheath.ogg', 100, 1)
+ addtimer(CALLBACK(src, .proc/drop_blade), GUILLOTINE_ANIMATION_LENGTH)
+
+/obj/structure/guillotine/proc/raise_blade()
+ blade_status = GUILLOTINE_BLADE_RAISED
+ icon_state = "guillotine_raised"
+
+/obj/structure/guillotine/proc/drop_blade(mob/user)
+ if(has_buckled_mobs() && blade_sharpness)
+ var/mob/living/carbon/human/H = buckled_mob
+
+ if(!H)
+ blade_status = GUILLOTINE_BLADE_DROPPED
+ icon_state = "guillotine"
+ return
+
+ var/obj/item/organ/external/head/head = H.get_organ("head")
+
+ if(QDELETED(head))
+ blade_status = GUILLOTINE_BLADE_DROPPED
+ icon_state = "guillotine"
+ return
+
+ playsound(src, 'sound/weapons/bladeslice.ogg', 100, 1)
+ if(blade_sharpness >= GUILLOTINE_DECAP_MIN_SHARP || head.brute_dam >= 100)
+ head.droplimb()
+ add_attack_logs(user, H, "beheaded with [src]")
+ H.regenerate_icons()
+ unbuckle_mob()
+ kill_count += 1
+
+ var/blood_overlay = "bloody"
+
+ if(kill_count == 2)
+ blood_overlay = "bloodier"
+ else if(kill_count > 2)
+ blood_overlay = "bloodiest"
+
+ blood_overlay = "guillotine_" + blood_overlay + "_overlay"
+ overlays.Cut()
+ overlays += mutable_appearance(icon, blood_overlay)
+
+ if(force_clap)
+ // The crowd is pleased
+ // The delay is to make large crowds have a longer lasting applause
+ var/delay_offset = 0
+ for(var/mob/living/carbon/human/HM in viewers(src, 7))
+ addtimer(CALLBACK(HM, /mob/.proc/emote, "clap"), delay_offset * 0.3)
+ delay_offset++
+ else
+ H.apply_damage(15 * blade_sharpness, BRUTE, head)
+ add_attack_logs(user, H, "non-fatally dropped the blade on with [src]")
+ H.emote("scream")
+
+ if(blade_sharpness > 1)
+ blade_sharpness -= 1
+
+ blade_status = GUILLOTINE_BLADE_DROPPED
+ icon_state = "guillotine"
+
+/obj/structure/guillotine/attackby(obj/item/W, mob/user, params)
+ if(istype(W, /obj/item/whetstone))
+ add_fingerprint(user)
+ if(blade_status == GUILLOTINE_BLADE_SHARPENING)
+ return
+
+ if(blade_status == GUILLOTINE_BLADE_RAISED)
+ if(blade_sharpness < GUILLOTINE_BLADE_MAX_SHARP)
+ blade_status = GUILLOTINE_BLADE_SHARPENING
+ if(do_after(user, 7, target = src))
+ blade_status = GUILLOTINE_BLADE_RAISED
+ user.visible_message("[user] sharpens the large blade of the guillotine.",
+ "You sharpen the large blade of the guillotine.")
+ blade_sharpness += 1
+ playsound(src, 'sound/items/Screwdriver.ogg', 100, 1)
+ return
+ else
+ blade_status = GUILLOTINE_BLADE_RAISED
+ return
+ else
+ to_chat(user, "The blade is sharp enough!")
+ return
+ else
+ to_chat(user, "You need to raise the blade in order to sharpen it!")
+ return
+ if(iswrench(W))
+ if(current_action)
+ return
+
+ current_action = GUILLOTINE_ACTION_WRENCH
+
+ if(do_after(user, GUILLOTINE_WRENCH_DELAY, target = src))
+ current_action = 0
+ if(has_buckled_mobs())
+ to_chat(user, "Can't unfasten, someone's strapped in!")
+ return
+
+ if(current_action)
+ return
+ to_chat(user, "You [anchored ? "un" : ""]secure [src].")
+ anchored = !anchored
+ playsound(src, 'sound/items/deconstruct.ogg', 50, 1)
+ dir = SOUTH
+ return TRUE
+ else
+ current_action = 0
+ else
+ return ..()
+
+/obj/structure/guillotine/buckle_mob(mob/living/M, force = 0)
+ if(!anchored)
+ to_chat(usr, "The [src] needs to be wrenched to the floor!")
+ return FALSE
+
+ if(!ishuman(M))
+ to_chat(usr, "It doesn't look like they can fit into this properly!")
+ return FALSE // Can't decapitate non-humans
+
+ if(blade_status != GUILLOTINE_BLADE_RAISED)
+ to_chat(usr, "You need to raise the blade before buckling someone in!")
+ return FALSE
+
+ if(..())
+ M.pixel_y -= GUILLOTINE_HEAD_OFFSET // Offset their body so it looks like they're in the guillotine
+ M.layer += GUILLOTINE_LAYER_DIFF
+
+/obj/structure/guillotine/unbuckle_mob(force = 0)
+ if(buckled_mob)
+ buckled_mob.pixel_y += GUILLOTINE_HEAD_OFFSET // Move their body back
+ buckled_mob.layer -= GUILLOTINE_LAYER_DIFF
+ . = ..()
+
+#undef GUILLOTINE_BLADE_MAX_SHARP
+#undef GUILLOTINE_DECAP_MIN_SHARP
+#undef GUILLOTINE_ANIMATION_LENGTH
+#undef GUILLOTINE_BLADE_RAISED
+#undef GUILLOTINE_BLADE_MOVING
+#undef GUILLOTINE_BLADE_DROPPED
+#undef GUILLOTINE_BLADE_SHARPENING
+#undef GUILLOTINE_HEAD_OFFSET
+#undef GUILLOTINE_LAYER_DIFF
+#undef GUILLOTINE_ACTIVATE_DELAY
+#undef GUILLOTINE_WRENCH_DELAY
+#undef GUILLOTINE_ACTION_INUSE
+#undef GUILLOTINE_ACTION_WRENCH
\ No newline at end of file
diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm
index 002bbb566d6..367be27b6dc 100644
--- a/code/game/objects/structures/tables_racks.dm
+++ b/code/game/objects/structures/tables_racks.dm
@@ -376,7 +376,7 @@
return
// Don't break if they're just flying past
if(AM.throwing)
- addtimer(src, "throw_check", 5, FALSE, AM)
+ addtimer(CALLBACK(src, .proc/throw_check, AM), 5)
else
check_break(AM)
diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm
index 7cc9c97dfdd..3ea23108662 100644
--- a/code/game/objects/structures/watercloset.dm
+++ b/code/game/objects/structures/watercloset.dm
@@ -249,9 +249,11 @@
var/ismist = 0 //needs a var so we can make it linger~
var/watertemp = "normal" //freezing, normal, or boiling
var/mobpresent = 0 //true if there is a mob on the shower's loc, this is to ease process()
+ var/datum/looping_sound/showering/soundloop
/obj/machinery/shower/New(turf/T, newdir = SOUTH, building = FALSE)
..()
+ soundloop = new(list(src), FALSE)
if(building)
dir = newdir
pixel_x = 0
@@ -264,8 +266,8 @@
layer = FLY_LAYER
/obj/machinery/shower/Destroy()
- if(mymist)
- QDEL_NULL(mymist)
+ QDEL_NULL(mymist)
+ QDEL_NULL(soundloop)
return ..()
//add heat controls? when emagged, you can freeze to death in it?
@@ -282,6 +284,7 @@
on = !on
update_icon()
if(on)
+ soundloop.start()
if(M.loc == loc)
wash(M)
check_heat(M)
@@ -289,6 +292,8 @@
for(var/atom/movable/G in src.loc)
G.clean_blood()
G.water_act(100, convertHeat(), src)
+ else
+ soundloop.stop()
/obj/machinery/shower/attackby(obj/item/I as obj, mob/user as mob, params)
if(I.type == /obj/item/analyzer)
@@ -338,9 +343,9 @@
mist_time = 70 //7 seconds on freezing temperature to disperse existing mist
if(watertemp == "boiling")
mist_time = 20 //2 seconds on boiling temperature to build up mist
- addtimer(src, "update_mist", mist_time)
+ addtimer(CALLBACK(src, .proc/update_mist), mist_time)
else
- addtimer(src, "update_mist", 250) //25 seconds for mist to disperse after being turned off
+ addtimer(CALLBACK(src, .proc/update_mist), 250) //25 seconds for mist to disperse after being turned off
/obj/machinery/shower/proc/update_mist()
if(on)
diff --git a/code/game/sound.dm b/code/game/sound.dm
index 39bf16bc697..66746ece0ae 100644
--- a/code/game/sound.dm
+++ b/code/game/sound.dm
@@ -1,4 +1,4 @@
-/proc/playsound(atom/source, soundin, vol as num, vary, extrarange as num, falloff, frequency = null, channel = 0, pressure_affected = TRUE)
+/proc/playsound(atom/source, soundin, vol as num, vary, extrarange as num, falloff, frequency = null, channel = 0, pressure_affected = TRUE, ignore_walls = TRUE)
if(isarea(source))
error("[source] is an area and is trying to make the sound: [soundin]")
return
@@ -11,7 +11,10 @@
// Looping through the player list has the added bonus of working for mobs inside containers
var/sound/S = sound(get_sfx(soundin))
var/maxdistance = (world.view + extrarange) * 3
- for(var/P in player_list)
+ var/list/listeners = player_list
+ if(!ignore_walls) //these sounds don't carry through walls
+ listeners = listeners & hearers(maxdistance, turf_source)
+ for(var/P in listeners)
var/mob/M = P
if(!M || !M.client)
continue
@@ -78,13 +81,15 @@
S.y = 1
S.falloff = (falloff ? falloff : FALLOFF_SOUNDS)
- src << S
+ SEND_SOUND(src, S)
-/client/proc/playtitlemusic()
- if(!ticker || !ticker.login_music || config.disable_lobby_music)
- return
- if(prefs.sound & SOUND_LOBBY)
- src << sound(ticker.login_music, repeat = 0, wait = 0, volume = 85, channel = CHANNEL_LOBBYMUSIC) // MAD JAMS
+/proc/sound_to_playing_players(soundin, volume = 100, vary = FALSE, frequency = 0, falloff = FALSE, channel = 0, pressure_affected = FALSE, sound/S)
+ if(!S)
+ S = sound(get_sfx(soundin))
+ for(var/m in player_list)
+ if(ismob(m) && !isnewplayer(m))
+ var/mob/M = m
+ M.playsound_local(M, null, volume, vary, frequency, falloff, channel, pressure_affected, S)
/proc/open_sound_channel()
var/static/next_channel = 1 //loop through the available 1024 - (the ones we reserve) channels and pray that its not still being used
@@ -93,7 +98,13 @@
next_channel = 1
/mob/proc/stop_sound_channel(chan)
- src << sound(null, repeat = 0, wait = 0, channel = chan)
+ SEND_SOUND(src, sound(null, repeat = 0, wait = 0, channel = chan))
+
+/client/proc/playtitlemusic()
+ if(!ticker || !ticker.login_music || config.disable_lobby_music)
+ return
+ if(prefs.sound & SOUND_LOBBY)
+ SEND_SOUND(src, sound(ticker.login_music, repeat = 0, wait = 0, volume = 85, channel = CHANNEL_LOBBYMUSIC)) // MAD JAMS
/proc/get_rand_frequency()
return rand(32000, 55000) //Frequency stuff only works with 45kbps oggs.
diff --git a/code/modules/arcade/mob_hunt/mob_avatar.dm b/code/modules/arcade/mob_hunt/mob_avatar.dm
index 42342e0693b..e4ea98a44a6 100644
--- a/code/modules/arcade/mob_hunt/mob_avatar.dm
+++ b/code/modules/arcade/mob_hunt/mob_avatar.dm
@@ -21,7 +21,7 @@
update_self()
forceMove(mob_info.spawn_point)
if(!mob_info.is_trap)
- addtimer(src, "despawn", mob_info.lifetime)
+ addtimer(CALLBACK(src, .proc/despawn), mob_info.lifetime)
/obj/effect/nanomob/proc/update_self()
if(!mob_info)
diff --git a/code/modules/awaymissions/mission_code/beach.dm b/code/modules/awaymissions/mission_code/beach.dm
index f285d4434da..4d2384ceb67 100644
--- a/code/modules/awaymissions/mission_code/beach.dm
+++ b/code/modules/awaymissions/mission_code/beach.dm
@@ -12,7 +12,7 @@
var/water_timer = 0
/obj/effect/waterfall/New()
- water_timer = addtimer(src, "drip", water_frequency)
+ water_timer = addtimer(CALLBACK(src, .proc/drip), water_frequency, TIMER_STOPPABLE)
/obj/effect/waterfall/Destroy()
if(water_timer)
@@ -25,4 +25,4 @@
W.dir = dir
spawn(1)
W.loc = get_step(W, dir)
- water_timer = addtimer(src, "drip", water_frequency)
\ No newline at end of file
+ water_timer = addtimer(CALLBACK(src, .proc/drip), water_frequency, TIMER_STOPPABLE)
\ No newline at end of file
diff --git a/code/modules/awaymissions/mission_code/spacehotel.dm b/code/modules/awaymissions/mission_code/spacehotel.dm
index 2810320aaf7..4e4f2628736 100644
--- a/code/modules/awaymissions/mission_code/spacehotel.dm
+++ b/code/modules/awaymissions/mission_code/spacehotel.dm
@@ -238,7 +238,7 @@
return null
D.occupant = occupant
- D.roomtimer = addtimer(src, "process_room", PAY_INTERVAL, 0, roomid)
+ D.roomtimer = addtimer(CALLBACK(src, .proc/process_room, roomid), PAY_INTERVAL, TIMER_STOPPABLE)
vacant_rooms -= D
guests[occupant] = roomid
@@ -252,7 +252,7 @@
return
if(D.account.charge(100, transaction_purpose = "10 minutes", dest_name = name))
- D.roomtimer = addtimer(src, "process_room", PAY_INTERVAL, 0, roomid)
+ D.roomtimer = addtimer(CALLBACK(src, .proc/process_room, roomid), PAY_INTERVAL, TIMER_STOPPABLE)
else
force_checkout(roomid)
diff --git a/code/modules/client/preference/loadout/loadout_hat.dm b/code/modules/client/preference/loadout/loadout_hat.dm
index d81f1f7e02c..7d6275e09e4 100644
--- a/code/modules/client/preference/loadout/loadout_hat.dm
+++ b/code/modules/client/preference/loadout/loadout_hat.dm
@@ -152,3 +152,7 @@
/datum/gear/hat/flowerpin
display_name = "hair flower"
path = /obj/item/clothing/head/hairflower
+
+/datum/gear/hat/kitty
+ display_name = "kitty headband"
+ path = /obj/item/clothing/head/kitty
diff --git a/code/modules/crafting/recipes.dm b/code/modules/crafting/recipes.dm
index 84226bd1f3b..f1739750a0a 100644
--- a/code/modules/crafting/recipes.dm
+++ b/code/modules/crafting/recipes.dm
@@ -354,3 +354,13 @@
reqs = list(/obj/item/grown/log = 5)
result = /obj/structure/bonfire
category = CAT_PRIMAL
+
+/datum/crafting_recipe/guillotine
+ name = "Guillotine"
+ result = /obj/structure/guillotine
+ time = 150 // Building a functioning guillotine takes time
+ reqs = list(/obj/item/stack/sheet/plasteel = 3,
+ /obj/item/stack/sheet/wood = 20,
+ /obj/item/stack/cable_coil = 10)
+ tools = list(/obj/item/screwdriver, /obj/item/wrench, /obj/item/weldingtool)
+ category = CAT_MISC
\ No newline at end of file
diff --git a/code/modules/customitems/item_defines.dm b/code/modules/customitems/item_defines.dm
index f7135acd270..a075dbdfba3 100644
--- a/code/modules/customitems/item_defines.dm
+++ b/code/modules/customitems/item_defines.dm
@@ -696,6 +696,12 @@
user.update_inv_head()
return 1
+/obj/item/clothing/head/beret/fluff/elo //V-Force_Bomber: E.L.O.
+ name = "E.L.O.'s medical beret"
+ desc = "E.L.O.s personal medical beret, issued by Nanotrassen and awarded along with her medal."
+ icon = 'icons/obj/custom_items.dmi'
+ icon_state = "elo-beret"
+
//////////// Suits ////////////
/obj/item/clothing/suit/fluff
icon = 'icons/obj/custom_items.dmi'
diff --git a/code/modules/flufftext/Dreaming.dm b/code/modules/flufftext/Dreaming.dm
index d01b5595d92..6a1d28eaf87 100644
--- a/code/modules/flufftext/Dreaming.dm
+++ b/code/modules/flufftext/Dreaming.dm
@@ -11,7 +11,7 @@
dream_images += pick_n_take(dreams)
dreaming++
for(var/i in 1 to dream_images.len)
- addtimer(src, "experience_dream", ((i - 1) * rand(30,60)), FALSE, dream_images[i], FALSE)
+ addtimer(CALLBACK(src, .proc/experience_dream, dream_images[i], FALSE), ((i - 1) * rand(30,60)))
return TRUE
@@ -33,7 +33,7 @@
dream_images += pick_n_take(nightmares)
nightmare++
for(var/i in 1 to dream_images.len)
- addtimer(src, "experience_dream", ((i - 1) * rand(30,60)), FALSE, nightmares[i], TRUE)
+ addtimer(CALLBACK(src, .proc/experience_dream, nightmares[i], TRUE), ((i - 1) * rand(30,60)))
return TRUE
/mob/living/carbon/proc/handle_dreams()
diff --git a/code/modules/flufftext/Hallucination.dm b/code/modules/flufftext/Hallucination.dm
index 621b8ec37ea..021db59f8a4 100644
--- a/code/modules/flufftext/Hallucination.dm
+++ b/code/modules/flufftext/Hallucination.dm
@@ -339,7 +339,7 @@ Gunshots/explosions/opening doors/less rare audio (done)
s.loc = get_step(get_turf(s), get_dir(s, target))
s.Show()
s.Eat()
- addtimer(src, "wake_and_restore", rand(50, 100))
+ addtimer(CALLBACK(src, .proc/wake_and_restore), rand(50, 100))
qdel(s)
/obj/effect/hallucination/simple/singularity
@@ -365,9 +365,9 @@ Gunshots/explosions/opening doors/less rare audio (done)
for(var/i=0,iERROR: APC access disabled, hack attempt canceled.")
- malfhacking = 0
- malfhack = null
+ deltimer(malfhacking)
+ // This proc handles cleanup of screen notifications and
+ // messenging the client
+ malfhacked(malfhack)
if(aiRestorePowerRoutine)
adjustOxyLoss(1)
diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm
index c040fbe1ed2..986795ccd1e 100644
--- a/code/modules/mob/living/simple_animal/bot/bot.dm
+++ b/code/modules/mob/living/simple_animal/bot/bot.dm
@@ -193,6 +193,9 @@
bots_list -= src
QDEL_NULL(Radio)
QDEL_NULL(access_card)
+ if(reset_access_timer_id)
+ deltimer(reset_access_timer_id)
+ reset_access_timer_id = null
if(radio_controller && bot_filter)
radio_controller.remove_object(bot_core, control_freq)
QDEL_NULL(bot_core)
@@ -527,7 +530,7 @@ Pass a positive integer as an argument to override a bot's default speed.
turn_on() //Saves the AI the hassle of having to activate a bot manually.
access_card = all_access //Give the bot all-access while under the AI's command.
if(client)
- reset_access_timer_id = addtimer(src, "bot_reset", 600) //if the bot is player controlled, they get the extra access for a limited time
+ reset_access_timer_id = addtimer(CALLBACK (src, .proc/bot_reset), 600, TIMER_OVERRIDE|TIMER_STOPPABLE) //if the bot is player controlled, they get the extra access for a limited time
to_chat(src, "Priority waypoint set by [calling_ai] [caller]. Proceed to [end_area.name].
[path.len-1] meters to destination. You have been granted additional door access for 60 seconds.")
if(message)
to_chat(calling_ai, "[bicon(src)] [name] called to [end_area.name]. [path.len-1] meters to destination.")
diff --git a/code/modules/mob/living/simple_animal/hostile/mining/hivelord.dm b/code/modules/mob/living/simple_animal/hostile/mining/hivelord.dm
index 3f39ce233bf..3063c5a8ce1 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining/hivelord.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining/hivelord.dm
@@ -62,7 +62,7 @@
/obj/item/organ/internal/hivelord_core/New()
..()
- addtimer(src, "inert_check", 2400)
+ addtimer(CALLBACK(src, .proc/inert_check), 2400)
/obj/item/organ/internal/hivelord_core/proc/inert_check()
if(!owner && !preserved)
@@ -151,7 +151,7 @@
/mob/living/simple_animal/hostile/asteroid/hivelordbrood/New()
..()
- addtimer(src, "death", 100)
+ addtimer(CALLBACK(src, .proc/death), 100)
/mob/living/simple_animal/hostile/asteroid/hivelordbrood/blood
name = "blood brood"
diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_spiders.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_spiders.dm
index 0b174ed5fee..ef456ddcb0e 100644
--- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_spiders.dm
+++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/terror_spiders.dm
@@ -270,8 +270,8 @@ var/global/list/ts_spiderling_list = list()
else
ts_count_alive_station++
// after 30 seconds, assuming nobody took control of it yet, offer it to ghosts.
- addtimer(src, "CheckFaction", 150)
- addtimer(src, "announcetoghosts", 300)
+ addtimer(CALLBACK(src, .proc/CheckFaction), 150)
+ addtimer(CALLBACK(src, .proc/announcetoghosts), 300)
var/datum/atom_hud/U = huds[DATA_HUD_MEDICAL_ADVANCED]
U.add_hud_to(src)
diff --git a/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm b/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm
index 9e6396dbde0..57412eb5a0d 100644
--- a/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm
+++ b/code/modules/mob/living/simple_animal/hostile/venus_human_trap.dm
@@ -22,7 +22,7 @@
for(var/turf/T in anchors)
var/datum/beam/B = Beam(T, "vine", time=INFINITY, maxdistance=5, beam_type=/obj/effect/ebeam/vine)
B.sleep_time = 10 //these shouldn't move, so let's slow down updates to 1 second (any slower and the deletion of the vines would be too slow)
- addtimer(src, "bear_fruit", growth_time)
+ addtimer(CALLBACK(src, .proc/bear_fruit), growth_time)
/obj/structure/alien/resin/flower_bud_enemy/proc/bear_fruit()
visible_message("the plant has borne fruit!")
diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm
index 23c6e2a1f15..9c3d64ef4fc 100644
--- a/code/modules/power/apc.dm
+++ b/code/modules/power/apc.dm
@@ -1004,8 +1004,7 @@
return
to_chat(malf, "Beginning override of APC systems. This takes some time, and you cannot perform other actions during the process.")
malf.malfhack = src
- malf.malfhacking = addtimer(malf, "malfhacked", 600, FALSE, src)
-
+ malf.malfhacking = addtimer(CALLBACK(malf, /mob/living/silicon/ai/.proc/malfhacked, src), 600, TIMER_STOPPABLE)
var/obj/screen/alert/hackingapc/A
A = malf.throw_alert("hackingapc", /obj/screen/alert/hackingapc)
A.target = src
diff --git a/code/modules/power/tesla/coil.dm b/code/modules/power/tesla/coil.dm
index 8bca07ccd45..aa25105cba6 100644
--- a/code/modules/power/tesla/coil.dm
+++ b/code/modules/power/tesla/coil.dm
@@ -66,7 +66,7 @@
flick("coilhit", src)
playsound(src.loc, 'sound/magic/LightningShock.ogg', 100, 1, extrarange = 5)
tesla_zap(src, 5, power_produced)
- addtimer(src, "reset_shocked", 10)
+ addtimer(CALLBACK(src, .proc/reset_shocked), 10)
else
..()
diff --git a/code/modules/power/tesla/energy_ball.dm b/code/modules/power/tesla/energy_ball.dm
index f2ffbb9b857..9cfd526585e 100644
--- a/code/modules/power/tesla/energy_ball.dm
+++ b/code/modules/power/tesla/energy_ball.dm
@@ -83,7 +83,7 @@
energy_to_raise = energy_to_raise * 1.25
playsound(src.loc, 'sound/magic/lightning_chargeup.ogg', 100, 1, extrarange = 30)
- addtimer(src, "new_mini_ball", 100)
+ addtimer(CALLBACK(src, .proc/new_mini_ball), 100)
else if(energy < energy_to_lower && orbiting_balls.len)
energy_to_raise = energy_to_raise / 1.25
diff --git a/code/modules/projectiles/guns/misc/blastcannon.dm b/code/modules/projectiles/guns/misc/blastcannon.dm
new file mode 100644
index 00000000000..f6881208a32
--- /dev/null
+++ b/code/modules/projectiles/guns/misc/blastcannon.dm
@@ -0,0 +1,130 @@
+/obj/item/gun/blastcannon
+ name = "pipe gun"
+ desc = "A pipe welded onto a gun stock, with a mechanical trigger. The pipe has an opening near the top, and there seems to be a spring loaded wheel in the hole."
+ icon_state = "empty_blastcannon"
+ var/icon_state_loaded = "loaded_blastcannon"
+ item_state = "blastcannon_empty"
+ w_class = WEIGHT_CLASS_NORMAL
+ force = 10
+ fire_sound = 'sound/weapons/blastcannon.ogg'
+ needs_permit = FALSE
+ clumsy_check = FALSE
+ randomspread = FALSE
+
+ var/obj/item/transfer_valve/bomb
+
+/obj/item/gun/blastcannon/Destroy()
+ QDEL_NULL(bomb)
+ return ..()
+
+/obj/item/gun/blastcannon/attack_self(mob/user)
+ if(bomb)
+ bomb.forceMove(user.loc)
+ user.put_in_hands(bomb)
+ user.visible_message("[user] detaches [bomb] from [src].")
+ bomb = null
+ update_icon()
+ return ..()
+
+/obj/item/gun/blastcannon/update_icon()
+ if(bomb)
+ icon_state = icon_state_loaded
+ name = "blast cannon"
+ desc = "A makeshift device used to concentrate a bomb's blast energy to a narrow wave."
+ else
+ icon_state = initial(icon_state)
+ name = initial(name)
+ desc = initial(desc)
+
+/obj/item/gun/blastcannon/attackby(obj/O, mob/user)
+ if(istype(O, /obj/item/transfer_valve))
+ var/obj/item/transfer_valve/T = O
+ if(!T.tank_one || !T.tank_two)
+ to_chat(user, "What good would an incomplete bomb do?")
+ return FALSE
+ if(!user.drop_item())
+ to_chat(user, "[T] seems to be stuck to your hand!")
+ return FALSE
+ user.visible_message("[user] attaches [T] to [src]!")
+ T.forceMove(src)
+ bomb = T
+ update_icon()
+ return TRUE
+ return ..()
+
+/obj/item/gun/blastcannon/proc/calculate_bomb()
+ if(!istype(bomb)||!istype(bomb.tank_one)||!istype(bomb.tank_two))
+ return 0
+ var/datum/gas_mixture/temp = new() //directional buff.
+ temp.volume = 60
+ temp.merge(bomb.tank_one.air_contents.remove_ratio(1))
+ temp.merge(bomb.tank_two.air_contents.remove_ratio(2))
+ for(var/i in 1 to 6)
+ temp.react()
+ var/pressure = temp.return_pressure()
+ qdel(temp)
+ if(pressure < TANK_FRAGMENT_PRESSURE)
+ return 0
+ return (pressure / TANK_FRAGMENT_SCALE)
+
+/obj/item/gun/blastcannon/afterattack(atom/target, mob/user, flag, params)
+ if((!bomb) || (!target) || (get_dist(get_turf(target), get_turf(user)) <= 2))
+ return ..()
+ var/power = calculate_bomb()
+ QDEL_NULL(bomb)
+ update_icon()
+ var/heavy = power * 0.2
+ var/medium = power * 0.5
+ var/light = power
+ user.visible_message("[user] opens [bomb] on \his [name] and fires a blast wave at [target]!","You open [bomb] on your [name] and fire a blast wave at [target]!")
+ playsound(user, "explosion", 100, 1)
+ var/turf/starting = get_turf(user)
+ var/turf/targturf = get_turf(target)
+ message_admins("Blast wave fired from [ADMIN_COORDJMP(starting)] ([get_area_name(user, TRUE)]) at [ADMIN_COORDJMP(targturf)] ([target.name]) by [key_name_admin(user)] with power [heavy]/[medium]/[light].")
+ log_game("Blast wave fired from ([starting.x], [starting.y], [starting.z]) ([get_area_name(user, TRUE)]) at ([target.x], [target.y], [target.z]) ([target]) by [key_name(user)] with power [heavy]/[medium]/[light].")
+ var/obj/item/projectile/blastwave/BW = new(loc, heavy, medium, light)
+ BW.preparePixelProjectile(target, get_turf(target), user, params, 0)
+ BW.fire()
+
+/obj/item/projectile/blastwave
+ name = "blast wave"
+ icon_state = "blastwave"
+ damage = 0
+ nodamage = FALSE
+ forcedodge = TRUE
+ range = 150
+ var/heavyr = 0
+ var/mediumr = 0
+ var/lightr = 0
+
+/obj/item/projectile/blastwave/New(loc, _h, _m, _l)
+ ..()
+ heavyr = _h
+ mediumr = _m
+ lightr = _l
+
+/obj/item/projectile/blastwave/Range()
+ ..()
+ var/amount_destruction = 0
+ if(heavyr)
+ amount_destruction = EXPLODE_DEVASTATE
+ else if(mediumr)
+ amount_destruction = EXPLODE_HEAVY
+ else if(lightr)
+ amount_destruction = EXPLODE_LIGHT
+ if(amount_destruction && isturf(loc))
+ var/turf/T = loc
+ for(var/thing in T.contents)
+ var/atom/AM = thing
+ if(AM && AM.simulated)
+ AM.ex_act(amount_destruction)
+ CHECK_TICK
+ T.ex_act(amount_destruction)
+ else
+ qdel(src)
+ heavyr = max(heavyr - 1, 0)
+ mediumr = max(mediumr - 1, 0)
+ lightr = max(lightr - 1, 0)
+
+/obj/item/projectile/blastwave/ex_act()
+ return
\ No newline at end of file
diff --git a/code/modules/reagents/chemistry/recipes/others.dm b/code/modules/reagents/chemistry/recipes/others.dm
index 22fd75b5e21..39c2e8c78fb 100644
--- a/code/modules/reagents/chemistry/recipes/others.dm
+++ b/code/modules/reagents/chemistry/recipes/others.dm
@@ -101,7 +101,7 @@
min_temp = 374
result_amount = 1
-/datum/chemical_reaction/plastication/on_reaction(datum/reagents/holder)
+/datum/chemical_reaction/plastic_polymers/on_reaction(datum/reagents/holder, created_volume)
var/obj/item/stack/sheet/plastic/P = new /obj/item/stack/sheet/plastic
P.amount = 10
P.forceMove(get_turf(holder.my_atom))
diff --git a/code/modules/research/rdconsole.dm b/code/modules/research/rdconsole.dm
index b39229ebc29..8d18df1ee07 100644
--- a/code/modules/research/rdconsole.dm
+++ b/code/modules/research/rdconsole.dm
@@ -887,7 +887,7 @@ won't update every console in existence) but it's more of a hassle to do. Also,
//helper proc that guarantees the wait message will not freeze the UI
/obj/machinery/computer/rdconsole/proc/add_wait_message(message, delay)
wait_message = message
- wait_message_timer = addtimer(src, "clear_wait_message", delay, TRUE)
+ wait_message_timer = addtimer(CALLBACK(src, .proc/clear_wait_message), delay, TIMER_UNIQUE | TIMER_STOPPABLE)
// This is here to guarantee that we never lock the console, so long as the timer
// process is running
diff --git a/code/modules/shuttle/on_move.dm b/code/modules/shuttle/on_move.dm
index 342931a065c..88551f49096 100644
--- a/code/modules/shuttle/on_move.dm
+++ b/code/modules/shuttle/on_move.dm
@@ -17,18 +17,18 @@
return 0
/obj/machinery/door/airlock/onShuttleMove()
- . = ..()
- if(!.)
- return
- addtimer(src, "close", 0, TRUE, 0, 1)
- // Close any nearby airlocks as well
- for(var/obj/machinery/door/airlock/D in orange(1, src))
- addtimer(D, "close", 0, TRUE, 0, 1)
+ . = ..()
+ if(!.)
+ return
+ INVOKE_ASYNC(src, .proc/close, 0, 1)
+ // Close any nearby airlocks as well
+ for(var/obj/machinery/door/airlock/D in orange(1, src))
+ INVOKE_ASYNC(D, .proc/close, 0, 1)
/obj/machinery/door/airlock/onShuttleMove()
- . = ..()
- if(id_tag == "s_docking_airlock")
- addtimer(src, "lock", 0, TRUE)
+ . = ..()
+ if(id_tag == "s_docking_airlock")
+ INVOKE_ASYNC(src, .proc/lock)
/mob/onShuttleMove()
if(!move_on_shuttle)
@@ -57,7 +57,7 @@
/obj/machinery/door/airlock/postDock(obj/docking_port/stationary/S1)
. = ..()
if(!S1.lock_shuttle_doors && id_tag == "s_docking_airlock")
- addtimer(src, "unlock", 0, TRUE)
+ INVOKE_ASYNC(src, .proc/unlock)
// Shuttle Rotation //
/atom/proc/shuttleRotate(rotation)
diff --git a/code/modules/surgery/organs/augments_arms.dm b/code/modules/surgery/organs/augments_arms.dm
index 1290212a249..d98e40700df 100644
--- a/code/modules/surgery/organs/augments_arms.dm
+++ b/code/modules/surgery/organs/augments_arms.dm
@@ -290,7 +290,7 @@
if(H.nutrition >= NUTRITION_LEVEL_WELL_FED)
to_chat(user, "You are already fully charged!")
else
- addtimer(src, "powerdraw_loop", 0, TRUE, A, H)
+ INVOKE_ASYNC(src, .proc/powerdraw_loop, A, H)
else
to_chat(user, "There is no charge to draw from that APC.")
else
diff --git a/code/modules/surgery/organs/blood.dm b/code/modules/surgery/organs/blood.dm
index 88e8341d37c..9f732fdef4d 100644
--- a/code/modules/surgery/organs/blood.dm
+++ b/code/modules/surgery/organs/blood.dm
@@ -7,7 +7,7 @@
return
else
bleedsuppress = TRUE
- addtimer(src, "resume_bleeding", amount)
+ addtimer(CALLBACK(src, .proc/resume_bleeding), amount)
/mob/living/carbon/human/proc/resume_bleeding()
bleedsuppress = FALSE
diff --git a/code/modules/telesci/gps.dm b/code/modules/telesci/gps.dm
index c8ed52ec787..467f92d095e 100644
--- a/code/modules/telesci/gps.dm
+++ b/code/modules/telesci/gps.dm
@@ -26,7 +26,7 @@ var/list/GPS_list = list()
emped = 1
overlays -= "working"
overlays += "emp"
- addtimer(src, "reboot", 300)
+ addtimer(CALLBACK(src, .proc/reboot), 300)
/obj/item/gps/proc/reboot()
emped = FALSE
diff --git a/config/example/config.txt b/config/example/config.txt
index d0eb704ea07..46d03c48aaa 100644
--- a/config/example/config.txt
+++ b/config/example/config.txt
@@ -368,3 +368,6 @@ HIGH_POP_MC_MODE_AMOUNT 65
##Disengage high pop mode if player count drops below this
DISABLE_HIGH_POP_MC_MODE_AMOUNT 60
+
+##Uncomment to enable developer start. Auto starts the server after initialization
+##DEVELOPER_EXPRESS_START
\ No newline at end of file
diff --git a/html/changelog.html b/html/changelog.html
index 054cee07cbc..7f3af71489e 100644
--- a/html/changelog.html
+++ b/html/changelog.html
@@ -56,6 +56,25 @@
-->
+
30 April 2018
+
Fox McCloud updated:
+
+ - Adds Guillotines: make them with plasteel, wood, and cable coil. Execute your comdom head of staff TODAY
+ - Clapping now actually plays a clapping sound
+ - Adds Blast cannon
+
+
FreeStylaLT updated:
+
+ - Medbay treatment center now has a wider entrance
+ - Virology hallway is now publically accessible from Surgery waiting room, entrance is now double-doors.
+
+
+
28 April 2018
+
MINIMAN10000 updated:
+
+ - Developer express start
+
+
26 April 2018
Fox McCloud updated:
diff --git a/html/changelogs/.all_changelog.yml b/html/changelogs/.all_changelog.yml
index bd6ac9e988b..c1ed06b54a7 100644
--- a/html/changelogs/.all_changelog.yml
+++ b/html/changelogs/.all_changelog.yml
@@ -6352,3 +6352,16 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
- rscadd: Slimes can steal nutrition from other slimes when attacking them
- rscadd: Slime docility potion no longer makes a simple animal slime, instead makes
the target docile and never hungry
+2018-04-28:
+ MINIMAN10000:
+ - rscadd: Developer express start
+2018-04-30:
+ Fox McCloud:
+ - rscadd: 'Adds Guillotines: make them with plasteel, wood, and cable coil. Execute
+ your comdom head of staff TODAY'
+ - rscadd: Clapping now actually plays a clapping sound
+ - rscadd: Adds Blast cannon
+ FreeStylaLT:
+ - tweak: Medbay treatment center now has a wider entrance
+ - tweak: Virology hallway is now publically accessible from Surgery waiting room,
+ entrance is now double-doors.
diff --git a/icons/mob/head.dmi b/icons/mob/head.dmi
index e465cdd6901..16eeff1107f 100644
Binary files a/icons/mob/head.dmi and b/icons/mob/head.dmi differ
diff --git a/icons/mob/inhands/guns_lefthand.dmi b/icons/mob/inhands/guns_lefthand.dmi
index b258ecdfb50..2066f60075a 100644
Binary files a/icons/mob/inhands/guns_lefthand.dmi and b/icons/mob/inhands/guns_lefthand.dmi differ
diff --git a/icons/mob/inhands/guns_righthand.dmi b/icons/mob/inhands/guns_righthand.dmi
index fb256e34937..9ed3a2ca4bf 100644
Binary files a/icons/mob/inhands/guns_righthand.dmi and b/icons/mob/inhands/guns_righthand.dmi differ
diff --git a/icons/obj/custom_items.dmi b/icons/obj/custom_items.dmi
index 384b608a5c2..8c3b77c337d 100644
Binary files a/icons/obj/custom_items.dmi and b/icons/obj/custom_items.dmi differ
diff --git a/icons/obj/guillotine.dmi b/icons/obj/guillotine.dmi
new file mode 100644
index 00000000000..e985dbaa825
Binary files /dev/null and b/icons/obj/guillotine.dmi differ
diff --git a/icons/obj/guns/projectile.dmi b/icons/obj/guns/projectile.dmi
index a630f68652d..22dc1384904 100644
Binary files a/icons/obj/guns/projectile.dmi and b/icons/obj/guns/projectile.dmi differ
diff --git a/icons/obj/projectiles.dmi b/icons/obj/projectiles.dmi
index 8c3d0f2c81f..2e1e5d45006 100644
Binary files a/icons/obj/projectiles.dmi and b/icons/obj/projectiles.dmi differ
diff --git a/paradise.dme b/paradise.dme
index 430730a17de..957b385ff9e 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -198,7 +198,6 @@
#include "code\controllers\Processes\obj.dm"
#include "code\controllers\Processes\shuttles.dm"
#include "code\controllers\Processes\ticker.dm"
-#include "code\controllers\Processes\timer.dm"
#include "code\controllers\Processes\weather.dm"
#include "code\controllers\ProcessScheduler\core\process.dm"
#include "code\controllers\ProcessScheduler\core\processScheduler.dm"
@@ -213,6 +212,7 @@
#include "code\controllers\subsystem\spacedrift.dm"
#include "code\controllers\subsystem\sun.dm"
#include "code\controllers\subsystem\throwing.dm"
+#include "code\controllers\subsystem\timer.dm"
#include "code\controllers\subsystem\processing\processing.dm"
#include "code\datums\action.dm"
#include "code\datums\ai_law_sets.dm"
@@ -313,6 +313,8 @@
#include "code\datums\helper_datums\map_template.dm"
#include "code\datums\helper_datums\teleport.dm"
#include "code\datums\helper_datums\topic_input.dm"
+#include "code\datums\looping_sounds\looping_sound.dm"
+#include "code\datums\looping_sounds\machinery_sounds.dm"
#include "code\datums\outfits\outfit.dm"
#include "code\datums\outfits\outfit_admin.dm"
#include "code\datums\ruins\space.dm"
@@ -967,6 +969,7 @@
#include "code\game\objects\structures\fullwindow.dm"
#include "code\game\objects\structures\girders.dm"
#include "code\game\objects\structures\grille.dm"
+#include "code\game\objects\structures\guillotine.dm"
#include "code\game\objects\structures\inflatable.dm"
#include "code\game\objects\structures\janicart.dm"
#include "code\game\objects\structures\kitchen_spike.dm"
@@ -2004,6 +2007,7 @@
#include "code\modules\projectiles\guns\energy\telegun.dm"
#include "code\modules\projectiles\guns\magic\staff.dm"
#include "code\modules\projectiles\guns\magic\wand.dm"
+#include "code\modules\projectiles\guns\misc\blastcannon.dm"
#include "code\modules\projectiles\guns\projectile\automatic.dm"
#include "code\modules\projectiles\guns\projectile\bow.dm"
#include "code\modules\projectiles\guns\projectile\launchers.dm"
diff --git a/sound/items/unsheath.ogg b/sound/items/unsheath.ogg
new file mode 100644
index 00000000000..388ff1fc189
Binary files /dev/null and b/sound/items/unsheath.ogg differ
diff --git a/sound/machines/shower/shower_end.ogg b/sound/machines/shower/shower_end.ogg
new file mode 100644
index 00000000000..80b93af39eb
Binary files /dev/null and b/sound/machines/shower/shower_end.ogg differ
diff --git a/sound/machines/shower/shower_mid1.ogg b/sound/machines/shower/shower_mid1.ogg
new file mode 100644
index 00000000000..e1ae5a0c456
Binary files /dev/null and b/sound/machines/shower/shower_mid1.ogg differ
diff --git a/sound/machines/shower/shower_mid2.ogg b/sound/machines/shower/shower_mid2.ogg
new file mode 100644
index 00000000000..4a54acd3524
Binary files /dev/null and b/sound/machines/shower/shower_mid2.ogg differ
diff --git a/sound/machines/shower/shower_mid3.ogg b/sound/machines/shower/shower_mid3.ogg
new file mode 100644
index 00000000000..8b4776a9b97
Binary files /dev/null and b/sound/machines/shower/shower_mid3.ogg differ
diff --git a/sound/machines/shower/shower_start.ogg b/sound/machines/shower/shower_start.ogg
new file mode 100644
index 00000000000..e5529f401bb
Binary files /dev/null and b/sound/machines/shower/shower_start.ogg differ
diff --git a/sound/misc/clap1.ogg b/sound/misc/clap1.ogg
new file mode 100644
index 00000000000..a9a6cc71f83
Binary files /dev/null and b/sound/misc/clap1.ogg differ
diff --git a/sound/misc/clap2.ogg b/sound/misc/clap2.ogg
new file mode 100644
index 00000000000..72e26d4a241
Binary files /dev/null and b/sound/misc/clap2.ogg differ
diff --git a/sound/misc/clap3.ogg b/sound/misc/clap3.ogg
new file mode 100644
index 00000000000..7a72ab9bf3c
Binary files /dev/null and b/sound/misc/clap3.ogg differ
diff --git a/sound/misc/clap4.ogg b/sound/misc/clap4.ogg
new file mode 100644
index 00000000000..cdc533ca771
Binary files /dev/null and b/sound/misc/clap4.ogg differ
diff --git a/sound/weapons/blastcannon.ogg b/sound/weapons/blastcannon.ogg
new file mode 100644
index 00000000000..9b88f3ecf96
Binary files /dev/null and b/sound/weapons/blastcannon.ogg differ