diff --git a/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_plasma_facility.dmm b/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_plasma_facility.dmm
index 865695e5b3327..356bed29f9fe1 100644
--- a/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_plasma_facility.dmm
+++ b/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_plasma_facility.dmm
@@ -1016,7 +1016,7 @@
/area/ruin/plasma_facility/commons)
"pE" = (
/obj/structure/bed/maint,
-/obj/item/bedsheet/dorms,
+/obj/effect/spawner/random/bedsheet,
/obj/item/flashlight/flare/candle{
pixel_x = 12;
pixel_y = 9
diff --git a/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_village.dmm b/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_village.dmm
index c2b11194dfb4c..97b4e3d53c99f 100644
--- a/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_village.dmm
+++ b/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_village.dmm
@@ -40,7 +40,7 @@
"fD" = (
/obj/structure/bed,
/obj/effect/decal/cleanable/blood/bubblegum,
-/obj/item/bedsheet/random,
+/obj/effect/spawner/random/bedsheet/any,
/turf/open/floor/wood,
/area/ruin/powered)
"gG" = (
@@ -416,7 +416,7 @@
/obj/structure/bed{
dir = 4
},
-/obj/item/bedsheet/random{
+/obj/effect/spawner/random/bedsheet/any{
dir = 4
},
/turf/open/floor/wood,
diff --git a/_maps/RandomRuins/IceRuins/icemoon_underground_frozen_comms.dmm b/_maps/RandomRuins/IceRuins/icemoon_underground_frozen_comms.dmm
index 25f29890f45db..eb537cd26f6c6 100644
--- a/_maps/RandomRuins/IceRuins/icemoon_underground_frozen_comms.dmm
+++ b/_maps/RandomRuins/IceRuins/icemoon_underground_frozen_comms.dmm
@@ -63,7 +63,7 @@
"oj" = (
/obj/effect/decal/cleanable/dirt,
/obj/structure/bed,
-/obj/item/bedsheet/dorms,
+/obj/effect/spawner/random/bedsheet,
/turf/open/floor/iron/grimy{
initial_gas_mix = "ICEMOON_ATMOS"
},
diff --git a/_maps/RandomRuins/LavaRuins/lavaland_biodome_beach.dmm b/_maps/RandomRuins/LavaRuins/lavaland_biodome_beach.dmm
index c97bd25796016..7de6e7d990082 100644
--- a/_maps/RandomRuins/LavaRuins/lavaland_biodome_beach.dmm
+++ b/_maps/RandomRuins/LavaRuins/lavaland_biodome_beach.dmm
@@ -286,7 +286,7 @@
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4{
dir = 4
},
-/obj/item/bedsheet/dorms{
+/obj/effect/spawner/random/bedsheet{
dir = 4
},
/obj/structure/bed{
diff --git a/_maps/RandomRuins/SpaceRuins/DJstation/quarters_1.dmm b/_maps/RandomRuins/SpaceRuins/DJstation/quarters_1.dmm
index c389af77150b2..dc6cea464f7ef 100644
--- a/_maps/RandomRuins/SpaceRuins/DJstation/quarters_1.dmm
+++ b/_maps/RandomRuins/SpaceRuins/DJstation/quarters_1.dmm
@@ -16,7 +16,7 @@
/area/ruin/space/djstation)
"k" = (
/obj/structure/bed,
-/obj/item/bedsheet/random,
+/obj/effect/spawner/random/bedsheet/any,
/turf/open/floor/iron/grimy,
/area/ruin/space/djstation)
"p" = (
@@ -41,7 +41,7 @@
/obj/structure/bed{
dir = 4
},
-/obj/item/bedsheet/random{
+/obj/effect/spawner/random/bedsheet/any{
dir = 4
},
/turf/open/floor/iron/grimy,
diff --git a/_maps/RandomRuins/SpaceRuins/DJstation/quarters_4.dmm b/_maps/RandomRuins/SpaceRuins/DJstation/quarters_4.dmm
index 8e632403b3669..52ec466ae8623 100644
--- a/_maps/RandomRuins/SpaceRuins/DJstation/quarters_4.dmm
+++ b/_maps/RandomRuins/SpaceRuins/DJstation/quarters_4.dmm
@@ -39,7 +39,7 @@
/turf/open/floor/iron/freezer/airless,
/area/ruin/space/djstation)
"x" = (
-/obj/item/bedsheet/random,
+/obj/effect/spawner/random/bedsheet/any,
/turf/open/floor/plating/airless,
/area/ruin/space/djstation)
"A" = (
diff --git a/_maps/RandomRuins/SpaceRuins/deepstorage.dmm b/_maps/RandomRuins/SpaceRuins/deepstorage.dmm
index 5645ac3ef152c..2c23219b7d382 100644
--- a/_maps/RandomRuins/SpaceRuins/deepstorage.dmm
+++ b/_maps/RandomRuins/SpaceRuins/deepstorage.dmm
@@ -1958,7 +1958,7 @@
/area/ruin/space/has_grav/deepstorage/hydroponics)
"zl" = (
/obj/structure/bed/double,
-/obj/item/bedsheet/dorms_double,
+/obj/effect/spawner/random/bedsheet/double,
/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2,
/turf/open/floor/wood,
/area/ruin/space/has_grav/deepstorage/dorm)
@@ -2128,7 +2128,7 @@
"Ev" = (
/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2,
/obj/structure/bed/double,
-/obj/item/bedsheet/dorms_double,
+/obj/effect/spawner/random/bedsheet/double,
/turf/open/floor/wood,
/area/ruin/space/has_grav/deepstorage/dorm)
"EA" = (
diff --git a/_maps/RandomRuins/SpaceRuins/hellfactory.dmm b/_maps/RandomRuins/SpaceRuins/hellfactory.dmm
index 9660b317c66f1..524e81ebb69cc 100644
--- a/_maps/RandomRuins/SpaceRuins/hellfactory.dmm
+++ b/_maps/RandomRuins/SpaceRuins/hellfactory.dmm
@@ -570,7 +570,7 @@
/area/ruin/space/has_grav/hellfactory)
"cc" = (
/obj/structure/bed,
-/obj/item/bedsheet/dorms,
+/obj/effect/spawner/random/bedsheet,
/turf/open/floor/holofloor/wood,
/area/ruin/space/has_grav/hellfactory)
"cd" = (
@@ -899,7 +899,7 @@
/area/ruin/space/has_grav/hellfactoryoffice)
"xK" = (
/obj/structure/bed,
-/obj/item/bedsheet/dorms,
+/obj/effect/spawner/random/bedsheet,
/obj/effect/decal/cleanable/cobweb/cobweb2,
/turf/open/floor/holofloor/wood,
/area/ruin/space/has_grav/hellfactory)
diff --git a/_maps/RandomRuins/SpaceRuins/hilbertresearchfacility.dmm b/_maps/RandomRuins/SpaceRuins/hilbertresearchfacility.dmm
index f829696000951..b964addf1f6e6 100644
--- a/_maps/RandomRuins/SpaceRuins/hilbertresearchfacility.dmm
+++ b/_maps/RandomRuins/SpaceRuins/hilbertresearchfacility.dmm
@@ -349,7 +349,7 @@
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"jt" = (
/obj/structure/bed,
-/obj/item/bedsheet/dorms,
+/obj/effect/spawner/random/bedsheet,
/turf/open/floor/carpet/black,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"jC" = (
diff --git a/_maps/RandomRuins/SpaceRuins/waystation.dmm b/_maps/RandomRuins/SpaceRuins/waystation.dmm
index 9399ee029474d..def97102238e5 100644
--- a/_maps/RandomRuins/SpaceRuins/waystation.dmm
+++ b/_maps/RandomRuins/SpaceRuins/waystation.dmm
@@ -1143,7 +1143,7 @@
/area/ruin/space/has_grav/waystation/dorms)
"rQ" = (
/obj/structure/bed,
-/obj/item/bedsheet/random,
+/obj/effect/spawner/random/bedsheet/any,
/turf/open/floor/wood,
/area/ruin/space/has_grav/waystation/dorms)
"rV" = (
@@ -1889,7 +1889,7 @@
/area/ruin/space/has_grav/waystation/cargobay)
"IS" = (
/obj/structure/bed,
-/obj/item/bedsheet/random,
+/obj/effect/spawner/random/bedsheet/any,
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/wood,
/area/ruin/space/has_grav/waystation/dorms)
diff --git a/_maps/RandomZLevels/SnowCabin.dmm b/_maps/RandomZLevels/SnowCabin.dmm
index c6366d62e541d..7ad0b42218f51 100644
--- a/_maps/RandomZLevels/SnowCabin.dmm
+++ b/_maps/RandomZLevels/SnowCabin.dmm
@@ -2142,7 +2142,7 @@
/area/awaymission/cabin/caves)
"km" = (
/obj/structure/bed,
-/obj/item/bedsheet/dorms,
+/obj/effect/spawner/random/bedsheet,
/turf/open/floor/wood,
/area/awaymission/cabin/caves)
"ko" = (
diff --git a/_maps/map_files/Basketball/beach_bums.dmm b/_maps/map_files/Basketball/beach_bums.dmm
index aa5948a34e6d2..517e70f2b630a 100644
--- a/_maps/map_files/Basketball/beach_bums.dmm
+++ b/_maps/map_files/Basketball/beach_bums.dmm
@@ -1,6 +1,6 @@
//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE
"af" = (
-/obj/item/bedsheet/dorms{
+/obj/effect/spawner/random/bedsheet{
pixel_x = 6;
pixel_y = 11
},
@@ -344,7 +344,7 @@
/turf/open/misc/beach/sand,
/area/centcom/basketball)
"Jb" = (
-/obj/item/bedsheet/dorms{
+/obj/effect/spawner/random/bedsheet{
pixel_x = 6;
pixel_y = 11
},
@@ -360,7 +360,7 @@
/turf/open/misc/beach/sand,
/area/centcom/basketball)
"Lu" = (
-/obj/item/bedsheet/dorms{
+/obj/effect/spawner/random/bedsheet{
pixel_x = 6;
pixel_y = 11
},
diff --git a/_maps/map_files/Birdshot/birdshot.dmm b/_maps/map_files/Birdshot/birdshot.dmm
index 72516d9c396fa..09cd9472764a6 100644
--- a/_maps/map_files/Birdshot/birdshot.dmm
+++ b/_maps/map_files/Birdshot/birdshot.dmm
@@ -9299,7 +9299,7 @@
/area/station/maintenance/department/medical/central)
"dBA" = (
/obj/structure/bed,
-/obj/item/bedsheet/dorms,
+/obj/effect/spawner/random/bedsheet,
/obj/machinery/light/small/directional/east,
/turf/open/floor/carpet/royalblack,
/area/station/commons/dorms)
@@ -11067,7 +11067,7 @@
/obj/structure/bed{
dir = 4
},
-/obj/item/bedsheet/dorms{
+/obj/effect/spawner/random/bedsheet{
dir = 4
},
/obj/machinery/light/small/directional/west,
@@ -39807,7 +39807,7 @@
/area/station/security/checkpoint/customs)
"ohO" = (
/obj/structure/bed,
-/obj/item/bedsheet/dorms,
+/obj/effect/spawner/random/bedsheet,
/obj/machinery/light/small/directional/east,
/turf/open/floor/carpet/red,
/area/station/commons/dorms)
@@ -70063,7 +70063,7 @@
/obj/structure/bed{
dir = 4
},
-/obj/item/bedsheet/dorms{
+/obj/effect/spawner/random/bedsheet{
dir = 4
},
/obj/machinery/light/small/directional/west,
diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm
index bd942b1bd8787..28143ce53ea8b 100644
--- a/_maps/map_files/Deltastation/DeltaStation2.dmm
+++ b/_maps/map_files/Deltastation/DeltaStation2.dmm
@@ -1408,7 +1408,7 @@
/obj/structure/bed{
dir = 4
},
-/obj/item/bedsheet/dorms{
+/obj/effect/spawner/random/bedsheet{
dir = 1
},
/obj/item/pillow/random,
@@ -55689,7 +55689,7 @@
/area/station/maintenance/department/science)
"nTU" = (
/obj/structure/bed,
-/obj/item/bedsheet/dorms,
+/obj/effect/spawner/random/bedsheet,
/obj/effect/decal/cleanable/dirt,
/obj/effect/landmark/start/hangover,
/obj/item/pillow/random,
@@ -58792,7 +58792,7 @@
/area/station/maintenance/department/science)
"oLO" = (
/obj/structure/bed,
-/obj/item/bedsheet/dorms,
+/obj/effect/spawner/random/bedsheet,
/obj/machinery/status_display/evac/directional/east,
/obj/item/pillow/random,
/turf/open/floor/wood,
@@ -63735,7 +63735,7 @@
/obj/structure/bed{
dir = 4
},
-/obj/item/bedsheet/dorms{
+/obj/effect/spawner/random/bedsheet{
dir = 1
},
/obj/machinery/status_display/evac/directional/north,
@@ -82687,7 +82687,7 @@
/area/station/tcommsat/server)
"uEo" = (
/obj/structure/bed/double,
-/obj/item/bedsheet/dorms_double,
+/obj/effect/spawner/random/bedsheet/double,
/obj/machinery/status_display/evac/directional/east,
/obj/effect/landmark/start/hangover,
/obj/item/pillow/random,
@@ -93649,7 +93649,7 @@
/area/station/science/genetics)
"xso" = (
/obj/structure/bed/double,
-/obj/item/bedsheet/random/double,
+/obj/effect/spawner/random/bedsheet/any/double,
/turf/open/floor/wood,
/area/station/maintenance/port/aft)
"xsp" = (
diff --git a/_maps/map_files/IceBoxStation/IceBoxStation.dmm b/_maps/map_files/IceBoxStation/IceBoxStation.dmm
index efeb6bc25c74e..3b78dacec0643 100644
--- a/_maps/map_files/IceBoxStation/IceBoxStation.dmm
+++ b/_maps/map_files/IceBoxStation/IceBoxStation.dmm
@@ -6937,7 +6937,7 @@
/area/station/engineering/atmos/project)
"ccp" = (
/obj/structure/bed,
-/obj/item/bedsheet/dorms,
+/obj/effect/spawner/random/bedsheet,
/obj/effect/landmark/start/hangover,
/obj/machinery/button/door/directional/south{
id = "Dorm5";
@@ -10057,7 +10057,7 @@
/area/station/cargo/office)
"cXp" = (
/obj/structure/bed/pod,
-/obj/item/bedsheet/random,
+/obj/effect/spawner/random/bedsheet/any,
/obj/effect/mapping_helpers/broken_floor,
/turf/open/floor/wood,
/area/station/maintenance/port/fore)
@@ -10420,7 +10420,7 @@
"dcq" = (
/obj/structure/bed,
/obj/machinery/airalarm/directional/north,
-/obj/item/bedsheet/dorms,
+/obj/effect/spawner/random/bedsheet,
/obj/machinery/button/door/directional/east{
id = "Dorm3";
name = "Dorm Bolt Control";
@@ -34264,7 +34264,7 @@
/area/station/hallway/secondary/service)
"kzv" = (
/obj/structure/bed,
-/obj/item/bedsheet/random,
+/obj/effect/spawner/random/bedsheet/any,
/turf/open/floor/plating,
/area/station/maintenance/department/medical/morgue)
"kzw" = (
@@ -41367,7 +41367,7 @@
"mJO" = (
/obj/structure/bed,
/obj/machinery/airalarm/directional/north,
-/obj/item/bedsheet/dorms,
+/obj/effect/spawner/random/bedsheet,
/obj/machinery/button/door/directional/east{
id = "Dorm2";
name = "Dorm Bolt Control";
@@ -45232,7 +45232,7 @@
"nOl" = (
/obj/structure/bed,
/obj/machinery/airalarm/directional/north,
-/obj/item/bedsheet/dorms,
+/obj/effect/spawner/random/bedsheet,
/obj/effect/landmark/start/hangover,
/obj/machinery/button/door/directional/east{
id = "Dorm4";
@@ -49799,7 +49799,7 @@
/area/station/hallway/primary/central)
"piC" = (
/obj/structure/bed,
-/obj/item/bedsheet/dorms,
+/obj/effect/spawner/random/bedsheet,
/obj/machinery/button/door/directional/south{
id = "Dorm6";
name = "Cabin Bolt Control";
@@ -63483,7 +63483,7 @@
/obj/structure/bed{
dir = 1
},
-/obj/item/bedsheet/dorms{
+/obj/effect/spawner/random/bedsheet{
dir = 1
},
/obj/effect/spawner/random/contraband/permabrig_gear,
@@ -66635,7 +66635,7 @@
/obj/structure/bed{
dir = 1
},
-/obj/item/bedsheet/dorms{
+/obj/effect/spawner/random/bedsheet{
dir = 1
},
/obj/effect/turf_decal/trimline/red/filled/line{
diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm
index b2a9971f12f91..03dfa234b14be 100644
--- a/_maps/map_files/MetaStation/MetaStation.dmm
+++ b/_maps/map_files/MetaStation/MetaStation.dmm
@@ -1124,7 +1124,7 @@
/area/station/maintenance/fore/lesser)
"avU" = (
/obj/structure/bed,
-/obj/item/bedsheet/dorms,
+/obj/effect/spawner/random/bedsheet,
/obj/machinery/atmospherics/components/unary/vent_pump/on/layer4,
/obj/machinery/button/door/directional/east{
id = "Cabin2";
@@ -31986,7 +31986,7 @@
/area/station/medical/virology)
"lxf" = (
/obj/structure/bed,
-/obj/item/bedsheet/dorms,
+/obj/effect/spawner/random/bedsheet,
/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2,
/obj/machinery/button/door/directional/west{
id = "Cabin5";
@@ -41784,7 +41784,7 @@
"oYv" = (
/obj/effect/decal/cleanable/cobweb,
/obj/structure/bed,
-/obj/item/bedsheet/dorms,
+/obj/effect/spawner/random/bedsheet,
/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2,
/obj/machinery/button/door/directional/west{
id = "Cabin4";
@@ -50133,7 +50133,7 @@
/area/station/commons/locker)
"rUo" = (
/obj/structure/bed,
-/obj/item/bedsheet/dorms,
+/obj/effect/spawner/random/bedsheet,
/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2{
dir = 1
},
@@ -51804,7 +51804,7 @@
"sBa" = (
/obj/structure/bed,
/obj/effect/decal/cleanable/cobweb/cobweb2,
-/obj/item/bedsheet/dorms,
+/obj/effect/spawner/random/bedsheet,
/obj/machinery/atmospherics/components/unary/vent_pump/on/layer4,
/obj/machinery/button/door/directional/east{
id = "Cabin3";
@@ -64199,7 +64199,7 @@
"wNp" = (
/obj/structure/bed,
/obj/effect/decal/cleanable/cobweb,
-/obj/item/bedsheet/dorms,
+/obj/effect/spawner/random/bedsheet,
/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2,
/obj/effect/landmark/start/hangover,
/obj/machinery/button/door/directional/west{
diff --git a/_maps/map_files/NorthStar/north_star.dmm b/_maps/map_files/NorthStar/north_star.dmm
index e01fb546e4c3a..9a1372065a6d9 100644
--- a/_maps/map_files/NorthStar/north_star.dmm
+++ b/_maps/map_files/NorthStar/north_star.dmm
@@ -26977,7 +26977,7 @@
/area/station/medical/pharmacy)
"heS" = (
/obj/structure/bed/double,
-/obj/item/bedsheet/dorms_double,
+/obj/effect/spawner/random/bedsheet/double,
/obj/machinery/atmospherics/components/unary/vent_pump/on/layer4{
dir = 8
},
@@ -27019,7 +27019,7 @@
/area/station/maintenance/floor1/starboard/aft)
"hfy" = (
/obj/structure/bed/double,
-/obj/item/bedsheet/dorms_double,
+/obj/effect/spawner/random/bedsheet/double,
/obj/machinery/atmospherics/components/unary/vent_pump/on/layer4{
dir = 8
},
@@ -33741,7 +33741,7 @@
/area/station/maintenance/floor1/starboard/fore)
"iTn" = (
/obj/structure/bed,
-/obj/item/bedsheet/dorms,
+/obj/effect/spawner/random/bedsheet,
/turf/open/floor/carpet/black,
/area/station/hallway/secondary/service)
"iTu" = (
@@ -63497,7 +63497,7 @@
/area/station/science/genetics)
"qBh" = (
/obj/structure/bed/double,
-/obj/item/bedsheet/dorms_double,
+/obj/effect/spawner/random/bedsheet/double,
/obj/machinery/atmospherics/components/unary/vent_pump/on/layer4{
dir = 8
},
@@ -63553,7 +63553,7 @@
/area/station/commons/fitness)
"qCn" = (
/obj/structure/bed,
-/obj/item/bedsheet/random,
+/obj/effect/spawner/random/bedsheet/any,
/turf/open/floor/wood,
/area/station/medical/psychology)
"qCo" = (
diff --git a/_maps/map_files/tramstation/tramstation.dmm b/_maps/map_files/tramstation/tramstation.dmm
index bd915e60abb13..57379c2353eed 100644
--- a/_maps/map_files/tramstation/tramstation.dmm
+++ b/_maps/map_files/tramstation/tramstation.dmm
@@ -7420,7 +7420,7 @@
/obj/structure/bed{
dir = 4
},
-/obj/item/bedsheet/dorms{
+/obj/effect/spawner/random/bedsheet{
dir = 4
},
/obj/structure/sign/clock/directional/west,
@@ -10176,7 +10176,7 @@
/area/station/command/heads_quarters/captain)
"cvg" = (
/obj/structure/bed/double,
-/obj/item/bedsheet/dorms_double,
+/obj/effect/spawner/random/bedsheet/double,
/obj/effect/landmark/start/assistant,
/obj/structure/sign/clock/directional/north,
/obj/item/pillow/random,
@@ -11408,7 +11408,7 @@
/obj/structure/bed{
dir = 4
},
-/obj/item/bedsheet/dorms{
+/obj/effect/spawner/random/bedsheet{
dir = 4
},
/obj/effect/landmark/start/assistant,
@@ -14385,7 +14385,7 @@
/obj/structure/bed{
dir = 4
},
-/obj/item/bedsheet/dorms{
+/obj/effect/spawner/random/bedsheet{
dir = 4
},
/obj/structure/sign/clock/directional/south,
@@ -22999,7 +22999,7 @@
/obj/structure/bed{
dir = 8
},
-/obj/item/bedsheet/dorms,
+/obj/effect/spawner/random/bedsheet,
/obj/structure/sign/clock/directional/east,
/obj/item/pillow/random,
/turf/open/floor/wood,
@@ -24884,7 +24884,7 @@
/area/station/security/brig)
"hRK" = (
/obj/structure/bed,
-/obj/item/bedsheet/dorms,
+/obj/effect/spawner/random/bedsheet,
/obj/effect/landmark/start/hangover,
/obj/structure/sign/clock/directional/south,
/obj/item/pillow/random,
@@ -26311,7 +26311,7 @@
/area/station/hallway/secondary/entry)
"iuJ" = (
/obj/structure/bed/double,
-/obj/item/bedsheet/dorms_double,
+/obj/effect/spawner/random/bedsheet/double,
/obj/structure/sign/clock/directional/north,
/obj/item/pillow/random,
/turf/open/floor/carpet,
@@ -28841,7 +28841,7 @@
/obj/structure/bed{
dir = 8
},
-/obj/item/bedsheet/dorms,
+/obj/effect/spawner/random/bedsheet,
/obj/effect/landmark/start/assistant,
/obj/structure/sign/clock/directional/east,
/obj/item/pillow/random,
@@ -36227,7 +36227,7 @@
/obj/structure/bed{
dir = 8
},
-/obj/item/bedsheet/dorms,
+/obj/effect/spawner/random/bedsheet,
/obj/effect/landmark/start/assistant,
/obj/structure/sign/clock/directional/east,
/obj/item/pillow/random,
@@ -45402,7 +45402,7 @@
/turf/open/floor/iron/showroomfloor,
/area/station/security/warden)
"pfm" = (
-/obj/item/bedsheet/dorms_double{
+/obj/effect/spawner/random/bedsheet/double{
dir = 4
},
/obj/structure/bed/double{
@@ -59280,7 +59280,7 @@
/obj/structure/bed{
dir = 4
},
-/obj/item/bedsheet/dorms{
+/obj/effect/spawner/random/bedsheet{
dir = 4
},
/obj/structure/sign/clock/directional/west,
@@ -59892,7 +59892,7 @@
/obj/structure/bed{
dir = 8
},
-/obj/item/bedsheet/dorms,
+/obj/effect/spawner/random/bedsheet,
/obj/structure/sign/clock/directional/east,
/obj/item/pillow/random,
/turf/open/floor/carpet,
@@ -60494,7 +60494,7 @@
/area/station/hallway/secondary/command)
"upb" = (
/obj/structure/bed/double,
-/obj/item/bedsheet/dorms_double,
+/obj/effect/spawner/random/bedsheet/double,
/obj/effect/landmark/start/hangover,
/obj/structure/sign/clock/directional/north,
/obj/item/pillow/random,
@@ -67525,7 +67525,7 @@
/obj/structure/bed/double{
dir = 4
},
-/obj/item/bedsheet/dorms_double{
+/obj/effect/spawner/random/bedsheet/double{
dir = 4
},
/obj/structure/sign/clock/directional/north,
diff --git a/_maps/shuttles/emergency_clown.dmm b/_maps/shuttles/emergency_clown.dmm
index f5c0ca4ea61eb..fc943e5287bb8 100644
--- a/_maps/shuttles/emergency_clown.dmm
+++ b/_maps/shuttles/emergency_clown.dmm
@@ -158,7 +158,7 @@
/area/shuttle/escape)
"aM" = (
/obj/structure/bed,
-/obj/item/bedsheet/random,
+/obj/effect/spawner/random/bedsheet/any,
/turf/open/floor/mineral/bananium,
/area/shuttle/escape)
"aQ" = (
@@ -246,7 +246,7 @@
/area/shuttle/escape)
"iU" = (
/obj/structure/bed,
-/obj/item/bedsheet/random,
+/obj/effect/spawner/random/bedsheet/any,
/obj/machinery/light/small/directional/east,
/turf/open/floor/mineral/bananium,
/area/shuttle/escape)
@@ -254,7 +254,7 @@
/obj/structure/bed{
dir = 4
},
-/obj/item/bedsheet/random{
+/obj/effect/spawner/random/bedsheet/any{
dir = 4
},
/turf/open/floor/mineral/bananium,
@@ -289,7 +289,7 @@
/obj/structure/bed{
dir = 4
},
-/obj/item/bedsheet/random{
+/obj/effect/spawner/random/bedsheet/any{
dir = 4
},
/obj/machinery/light/small/directional/west,
@@ -300,7 +300,7 @@
/obj/structure/bed{
dir = 4
},
-/obj/item/bedsheet/random{
+/obj/effect/spawner/random/bedsheet/any{
dir = 4
},
/turf/open/floor/mineral/bananium,
@@ -315,7 +315,7 @@
"XT" = (
/obj/structure/window/reinforced/spawner/directional/east,
/obj/structure/bed,
-/obj/item/bedsheet/random,
+/obj/effect/spawner/random/bedsheet/any,
/turf/open/floor/mineral/bananium,
/area/shuttle/escape)
"YC" = (
diff --git a/_maps/shuttles/emergency_hugcage.dmm b/_maps/shuttles/emergency_hugcage.dmm
index 6e68506c4332e..4b500f7bdbd45 100644
--- a/_maps/shuttles/emergency_hugcage.dmm
+++ b/_maps/shuttles/emergency_hugcage.dmm
@@ -3,7 +3,7 @@
/turf/closed/wall/mineral/titanium/nodiagonal,
/area/shuttle/escape/brig)
"aR" = (
-/obj/item/bedsheet/random{
+/obj/effect/spawner/random/bedsheet/any{
dir = 8
},
/obj/structure/bed,
@@ -83,7 +83,7 @@
/turf/closed/wall/mineral/titanium,
/area/shuttle/escape)
"gg" = (
-/obj/item/bedsheet/random{
+/obj/effect/spawner/random/bedsheet/any{
dir = 4
},
/obj/structure/bed{
@@ -116,7 +116,7 @@
/area/shuttle/escape)
"iI" = (
/obj/structure/bed,
-/obj/item/bedsheet/random,
+/obj/effect/spawner/random/bedsheet/any,
/obj/item/pillow/random,
/obj/effect/spawner/random/entertainment/plushie_delux,
/turf/open/floor/mineral/titanium/yellow,
@@ -148,7 +148,7 @@
/obj/structure/bed{
dir = 1
},
-/obj/item/bedsheet/random{
+/obj/effect/spawner/random/bedsheet/any{
dir = 4
},
/obj/item/pillow/random,
@@ -211,7 +211,7 @@
/turf/open/floor/mineral/titanium/blue,
/area/shuttle/escape)
"ys" = (
-/obj/item/bedsheet/random{
+/obj/effect/spawner/random/bedsheet/any{
dir = 4
},
/obj/structure/bed{
@@ -327,7 +327,7 @@
/turf/open/floor/mineral/plastitanium/red,
/area/shuttle/escape/brig)
"KW" = (
-/obj/item/bedsheet/random{
+/obj/effect/spawner/random/bedsheet/any{
dir = 8
},
/obj/structure/bed,
diff --git a/_maps/shuttles/pirate_grey.dmm b/_maps/shuttles/pirate_grey.dmm
index 0726d8d1ea196..7ba8fdd5a5ced 100644
--- a/_maps/shuttles/pirate_grey.dmm
+++ b/_maps/shuttles/pirate_grey.dmm
@@ -955,7 +955,7 @@
/area/shuttle/pirate)
"DP" = (
/obj/structure/bed,
-/obj/item/bedsheet/random,
+/obj/effect/spawner/random/bedsheet/any,
/turf/open/floor/plating,
/area/shuttle/pirate)
"DX" = (
diff --git a/_maps/shuttles/pirate_silverscale.dmm b/_maps/shuttles/pirate_silverscale.dmm
index e4838e040e472..d4bd9d0c16b3e 100644
--- a/_maps/shuttles/pirate_silverscale.dmm
+++ b/_maps/shuttles/pirate_silverscale.dmm
@@ -91,7 +91,7 @@
/obj/structure/bed/pod{
dir = 4
},
-/obj/item/bedsheet/black{
+/obj/item/bedsheet/pirate{
dir = 4
},
/turf/open/floor/carpet/royalblack,
@@ -288,7 +288,7 @@
/area/shuttle/pirate)
"uP" = (
/obj/structure/bed/pod,
-/obj/item/bedsheet/black,
+/obj/item/bedsheet/pirate,
/turf/open/floor/carpet/royalblack,
/area/shuttle/pirate)
"vw" = (
@@ -381,7 +381,7 @@
/obj/machinery/airalarm/directional/east,
/obj/effect/mapping_helpers/airalarm/all_access,
/obj/structure/bed/pod,
-/obj/item/bedsheet/black,
+/obj/item/bedsheet/pirate,
/turf/open/floor/carpet/royalblack,
/area/shuttle/pirate)
"zB" = (
diff --git a/_maps/shuttles/ruin_pirate_cutter.dmm b/_maps/shuttles/ruin_pirate_cutter.dmm
index 9fa1943c5f8f1..e99c5097664a6 100644
--- a/_maps/shuttles/ruin_pirate_cutter.dmm
+++ b/_maps/shuttles/ruin_pirate_cutter.dmm
@@ -344,7 +344,7 @@
"wV" = (
/obj/machinery/light/small/directional/north,
/obj/structure/bed,
-/obj/item/bedsheet/brown,
+/obj/item/bedsheet/pirate,
/turf/open/floor/iron/dark,
/area/shuttle/ruin/caravan/pirate)
"xb" = (
@@ -501,7 +501,7 @@
/obj/structure/bed{
dir = 4
},
-/obj/item/bedsheet/brown{
+/obj/item/bedsheet/pirate{
dir = 4
},
/obj/machinery/airalarm/directional/west,
@@ -514,7 +514,7 @@
"Ha" = (
/obj/machinery/light/small/directional/south,
/obj/structure/bed,
-/obj/item/bedsheet/brown,
+/obj/item/bedsheet/pirate,
/turf/open/floor/iron/dark,
/area/shuttle/ruin/caravan/pirate)
"Hb" = (
@@ -838,7 +838,7 @@
/obj/structure/bed{
dir = 4
},
-/obj/item/bedsheet/brown{
+/obj/item/bedsheet/pirate{
dir = 4
},
/obj/machinery/firealarm/directional/west,
diff --git a/_maps/shuttles/whiteship_cere.dmm b/_maps/shuttles/whiteship_cere.dmm
index e6a677d57c3a5..1f0308690e77d 100644
--- a/_maps/shuttles/whiteship_cere.dmm
+++ b/_maps/shuttles/whiteship_cere.dmm
@@ -388,7 +388,7 @@
/area/shuttle/abandoned/cargo)
"oB" = (
/obj/structure/bed,
-/obj/item/bedsheet/random,
+/obj/effect/spawner/random/bedsheet/any,
/obj/machinery/light/small/directional/east,
/obj/effect/decal/cleanable/dirt,
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden,
diff --git a/_maps/shuttles/whiteship_kilo.dmm b/_maps/shuttles/whiteship_kilo.dmm
index 2dc2e1c54d5bc..cbc214f21d828 100644
--- a/_maps/shuttles/whiteship_kilo.dmm
+++ b/_maps/shuttles/whiteship_kilo.dmm
@@ -1176,7 +1176,7 @@
/obj/structure/bed/pod{
dir = 1
},
-/obj/item/bedsheet/dorms{
+/obj/effect/spawner/random/bedsheet{
dir = 4
},
/obj/effect/decal/cleanable/dirt,
diff --git a/_maps/shuttles/whiteship_personalshuttle.dmm b/_maps/shuttles/whiteship_personalshuttle.dmm
index e3f432f350b57..8e041082bc6d9 100644
--- a/_maps/shuttles/whiteship_personalshuttle.dmm
+++ b/_maps/shuttles/whiteship_personalshuttle.dmm
@@ -273,7 +273,7 @@
/area/shuttle/abandoned/bridge)
"pS" = (
/obj/machinery/light/small/directional/south,
-/obj/item/bedsheet/dorms{
+/obj/effect/spawner/random/bedsheet{
dir = 4
},
/obj/structure/bed/pod{
diff --git a/_maps/templates/hilbertshotel.dmm b/_maps/templates/hilbertshotel.dmm
index 2895d10406907..8c425aa9c13db 100644
--- a/_maps/templates/hilbertshotel.dmm
+++ b/_maps/templates/hilbertshotel.dmm
@@ -76,7 +76,7 @@
/area/misc/hilbertshotel)
"q" = (
/obj/structure/bed,
-/obj/item/bedsheet/random,
+/obj/effect/spawner/random/bedsheet/any,
/turf/open/indestructible/hotelwood,
/area/misc/hilbertshotel)
"r" = (
diff --git a/_maps/templates/hilbertshotellore.dmm b/_maps/templates/hilbertshotellore.dmm
index 4774f6788e373..fe77ef9cc3d54 100644
--- a/_maps/templates/hilbertshotellore.dmm
+++ b/_maps/templates/hilbertshotellore.dmm
@@ -177,7 +177,7 @@
/area/misc/hilbertshotel)
"aU" = (
/obj/structure/bed,
-/obj/item/bedsheet/random,
+/obj/effect/spawner/random/bedsheet/any,
/obj/effect/decal/cleanable/dirt,
/turf/open/indestructible/hotelwood,
/area/misc/hilbertshotel)
diff --git a/_maps/virtual_domains/beach_bar.dmm b/_maps/virtual_domains/beach_bar.dmm
index 7da71e943d172..6368168416193 100644
--- a/_maps/virtual_domains/beach_bar.dmm
+++ b/_maps/virtual_domains/beach_bar.dmm
@@ -859,7 +859,7 @@
/turf/open/misc/beach/sand,
/area/virtual_domain/fullbright)
"Nw" = (
-/obj/item/bedsheet/dorms{
+/obj/effect/spawner/random/bedsheet{
dir = 4
},
/obj/structure/bed{
diff --git a/code/__DEFINES/dcs/signals/signals_camera.dm b/code/__DEFINES/dcs/signals/signals_camera.dm
index 6ec142f54fabe..92e9b94f35bba 100644
--- a/code/__DEFINES/dcs/signals/signals_camera.dm
+++ b/code/__DEFINES/dcs/signals/signals_camera.dm
@@ -1,2 +1,4 @@
-///Signal sent when a /datum/trackable found a target: (datum/trackable/source, mob/living/target)
+///Signal sent when a /datum/trackable found a target: (mob/living/target)
#define COMSIG_TRACKABLE_TRACKING_TARGET "comsig_trackable_tracking_target"
+///Signal sent when the mob a /datum/trackable is actively following changes glide size: mob/living/target, new_glide_size)
+#define COMSIG_TRACKABLE_GLIDE_CHANGED "comsig_trackable_glide_changed"
diff --git a/code/__DEFINES/random_spawner.dm b/code/__DEFINES/random_spawner.dm
new file mode 100644
index 0000000000000..2a012e7904860
--- /dev/null
+++ b/code/__DEFINES/random_spawner.dm
@@ -0,0 +1,3 @@
+///Used by bedsheets spawners to tell if it's a single or double bedsheet.
+#define BEDSHEET_SINGLE "single"
+#define BEDSHEET_DOUBLE "double"
diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm
index 9e901b0665faa..5946f76b7a109 100644
--- a/code/__DEFINES/traits/declarations.dm
+++ b/code/__DEFINES/traits/declarations.dm
@@ -425,6 +425,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_FOV_APPLIED "fov_applied"
/// Mob is using the scope component
#define TRAIT_USER_SCOPED "user_scoped"
+/// Mob is unable to feel pain
+#define TRAIT_ANALGESIA "analgesia"
/// Trait added when a revenant is visible.
#define TRAIT_REVENANT_REVEALED "revenant_revealed"
diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm
index d4a4192614e6c..f7889f0b5af80 100644
--- a/code/__HELPERS/global_lists.dm
+++ b/code/__HELPERS/global_lists.dm
@@ -25,7 +25,6 @@
init_sprite_accessory_subtypes(/datum/sprite_accessory/wings_open, GLOB.wings_open_list)
init_sprite_accessory_subtypes(/datum/sprite_accessory/frills, GLOB.frills_list)
init_sprite_accessory_subtypes(/datum/sprite_accessory/spines, GLOB.spines_list)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/spines_animated, GLOB.animated_spines_list)
init_sprite_accessory_subtypes(/datum/sprite_accessory/tail_spines, GLOB.tail_spines_list)
init_sprite_accessory_subtypes(/datum/sprite_accessory/legs, GLOB.legs_list)
init_sprite_accessory_subtypes(/datum/sprite_accessory/caps, GLOB.caps_list)
diff --git a/code/_globalvars/lists/flavor_misc.dm b/code/_globalvars/lists/flavor_misc.dm
index 38072aaec01e7..ce4d847928988 100644
--- a/code/_globalvars/lists/flavor_misc.dm
+++ b/code/_globalvars/lists/flavor_misc.dm
@@ -26,7 +26,6 @@ GLOBAL_LIST_EMPTY(frills_list)
GLOBAL_LIST_EMPTY(spines_list)
GLOBAL_LIST_EMPTY(tail_spines_list)
GLOBAL_LIST_EMPTY(legs_list)
-GLOBAL_LIST_EMPTY(animated_spines_list)
//Mutant Human bits
GLOBAL_LIST_EMPTY(tails_list_human)
diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm
index dd074b8d0649b..4bc72354a8625 100644
--- a/code/_globalvars/traits/_traits.dm
+++ b/code/_globalvars/traits/_traits.dm
@@ -118,6 +118,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_ALLOW_HERETIC_CASTING" = TRAIT_ALLOW_HERETIC_CASTING,
"TRAIT_ALWAYS_NO_ACCESS" = TRAIT_ALWAYS_NO_ACCESS,
"TRAIT_ALWAYS_WANTED" = TRAIT_ALWAYS_WANTED,
+ "TRAIT_ANALGESIA" = TRAIT_ANALGESIA,
"TRAIT_ANGELIC" = TRAIT_ANGELIC,
"TRAIT_ANTENNAE" = TRAIT_ANTENNAE,
"TRAIT_ANTICONVULSANT" = TRAIT_ANTICONVULSANT,
@@ -270,13 +271,11 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_LIMBATTACHMENT" = TRAIT_LIMBATTACHMENT,
"TRAIT_LITERATE" = TRAIT_LITERATE,
"TRAIT_LIVERLESS_METABOLISM" = TRAIT_LIVERLESS_METABOLISM,
- "TRAIT_LIVERLESS_METABOLISM" = TRAIT_LIVERLESS_METABOLISM,
"TRAIT_MADNESS_IMMUNE" = TRAIT_MADNESS_IMMUNE,
"TRAIT_MAGICALLY_GIFTED" = TRAIT_MAGICALLY_GIFTED,
"TRAIT_MAGICALLY_PHASED" = TRAIT_MAGICALLY_PHASED,
"TRAIT_MARTIAL_ARTS_IMMUNE" = TRAIT_MARTIAL_ARTS_IMMUNE,
"TRAIT_MEDIBOTCOMINGTHROUGH" = TRAIT_MEDIBOTCOMINGTHROUGH,
- "TRAIT_MEDIBOTCOMINGTHROUGH" = TRAIT_MEDIBOTCOMINGTHROUGH,
"TRAIT_MEDICAL_HUD" = TRAIT_MEDICAL_HUD,
"TRAIT_MESON_VISION" = TRAIT_MESON_VISION,
"TRAIT_MIME_FAN" = TRAIT_MIME_FAN,
diff --git a/code/_globalvars/traits/admin_tooling.dm b/code/_globalvars/traits/admin_tooling.dm
index 01269087d3660..b81719bc808c3 100644
--- a/code/_globalvars/traits/admin_tooling.dm
+++ b/code/_globalvars/traits/admin_tooling.dm
@@ -113,7 +113,7 @@ GLOBAL_LIST_INIT(admin_visible_traits, list(
"TRAIT_LIMBATTACHMENT" = TRAIT_LIMBATTACHMENT,
"TRAIT_LITERATE" = TRAIT_LITERATE,
"TRAIT_LIVERLESS_METABOLISM" = TRAIT_LIVERLESS_METABOLISM,
- "TRAIT_LIVERLESS_METABOLISM" = TRAIT_LIVERLESS_METABOLISM,
+ "TRAIT_MAGICALLY_GIFTED" = TRAIT_MAGICALLY_GIFTED,
"TRAIT_MEDICAL_HUD" = TRAIT_MEDICAL_HUD,
"TRAIT_MIME_FAN" = TRAIT_MIME_FAN,
"TRAIT_MIMING" = TRAIT_MIMING,
diff --git a/code/_onclick/ai.dm b/code/_onclick/ai.dm
index 614258388cc1b..8f95bdadda934 100644
--- a/code/_onclick/ai.dm
+++ b/code/_onclick/ai.dm
@@ -11,7 +11,7 @@
return
if(ismob(A))
- ai_tracking_tool.set_tracked_mob(src, A.name)
+ ai_tracking_tool.track_mob(src, A)
else
A.move_camera_by_click()
diff --git a/code/controllers/master.dm b/code/controllers/master.dm
index 6e4c21fba208b..ac12add1ae2d5 100644
--- a/code/controllers/master.dm
+++ b/code/controllers/master.dm
@@ -684,10 +684,16 @@ GLOBAL_REAL(Master, /datum/controller/master)
queue_node.state = SS_RUNNING
+ if(queue_node.profiler_focused)
+ world.Profile(PROFILE_START)
+
tick_usage = TICK_USAGE
var/state = queue_node.ignite(queue_node_paused)
tick_usage = TICK_USAGE - tick_usage
+ if(queue_node.profiler_focused)
+ world.Profile(PROFILE_STOP)
+
if (state == SS_RUNNING)
state = SS_IDLE
current_tick_budget -= queue_node_priority
diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm
index ddeb9368f500b..b01f4b17b9de4 100644
--- a/code/controllers/subsystem.dm
+++ b/code/controllers/subsystem.dm
@@ -38,6 +38,12 @@
///Bitmap of what game states can this subsystem fire at. See [RUNLEVELS_DEFAULT] for more details.
var/runlevels = RUNLEVELS_DEFAULT //points of the game at which the SS can fire
+ /**
+ * boolean set by admins. if TRUE then this subsystem will stop the world profiler after ignite() returns and start it again when called.
+ * used so that you can audit a specific subsystem or group of subsystems' synchronous call chain.
+ */
+ var/profiler_focused = FALSE
+
/*
* The following variables are managed by the MC and should not be modified directly.
*/
@@ -65,7 +71,7 @@
/// Tracks the current execution state of the subsystem. Used to handle subsystems that sleep in fire so the mc doesn't run them again while they are sleeping
var/state = SS_IDLE
-
+
/// Tracks how many times a subsystem has ever slept in fire().
var/slept_count = 0
diff --git a/code/controllers/subsystem/polling.dm b/code/controllers/subsystem/polling.dm
index 7787d36b2bf4b..8a041eb36b96c 100644
--- a/code/controllers/subsystem/polling.dm
+++ b/code/controllers/subsystem/polling.dm
@@ -49,7 +49,6 @@ SUBSYSTEM_DEF(polling)
if(role && !is_eligible(candidate_mob, role, check_jobban, ignore_category))
continue
- SEND_SOUND(candidate_mob, 'sound/misc/notice2.ogg')
if(flash_window)
window_flash(candidate_mob.client)
@@ -116,7 +115,10 @@ SUBSYSTEM_DEF(polling)
var/act_never = ""
if(ignore_category)
act_never = "\[Never For This Round]"
- to_chat(candidate_mob, span_boldnotice(examine_block("Now looking for candidates [role_name_text ? "to play as \an [role_name_text]." : "\"[question]\""] [act_jump] [act_signup] [act_never]")))
+
+ if(!duplicate_message_check(alert_poll)) //Only notify people once. They'll notice if there are multiple and we don't want to spam people.
+ SEND_SOUND(candidate_mob, 'sound/misc/notice2.ogg')
+ to_chat(candidate_mob, span_boldnotice(examine_block("Now looking for candidates [role_name_text ? "to play as \an [role_name_text]." : "\"[question]\""] [act_jump] [act_signup] [act_never]")))
// Start processing it so it updates visually the timer
START_PROCESSING(SSprocessing, poll_alert_button)
@@ -192,13 +194,8 @@ SUBSYSTEM_DEF(polling)
// Take care of updating the remaining screen alerts if a similar poll is found, or deleting them.
if(length(finishing_poll.alert_buttons))
- var/polls_of_same_type_left = FALSE
- for(var/datum/candidate_poll/running_poll as anything in currently_polling)
- if(running_poll.poll_key == finishing_poll.poll_key && running_poll.time_left() > 0)
- polls_of_same_type_left = TRUE
- break
for(var/atom/movable/screen/alert/poll_alert/alert as anything in finishing_poll.alert_buttons)
- if(polls_of_same_type_left)
+ if(duplicate_message_check(finishing_poll))
alert.update_stacks_overlay()
else
alert.owner.clear_alert("[finishing_poll.poll_key]_poll_alert")
@@ -213,6 +210,13 @@ SUBSYSTEM_DEF(polling)
msg += " | Next: [DisplayTimeText(soonest_to_complete.time_left())] ([length(soonest_to_complete.signed_up)] candidates)"
return ..()
+///Is there a multiple of the given event type running right now?
+/datum/controller/subsystem/polling/proc/duplicate_message_check(datum/candidate_poll/poll_to_check)
+ for(var/datum/candidate_poll/running_poll as anything in currently_polling)
+ if((running_poll.poll_key == poll_to_check.poll_key && running_poll != poll_to_check) && running_poll.time_left() > 0)
+ return TRUE
+ return FALSE
+
/datum/controller/subsystem/polling/proc/get_next_poll_to_finish()
var/lowest_time_left = INFINITY
var/next_poll_to_finish
diff --git a/code/datums/achievements/_achievement_data.dm b/code/datums/achievements/_achievement_data.dm
index 295770b89b5b7..8b78cf7c8f387 100644
--- a/code/datums/achievements/_achievement_data.dm
+++ b/code/datums/achievements/_achievement_data.dm
@@ -113,7 +113,7 @@
"name" = award.name,
"desc" = award.desc,
"category" = award.category,
- "icon_class" = assets.icon_class_name(award.icon),
+ "icon_class" = assets.icon_class_name("achievement-[award.icon_state]"),
"value" = data[achievement_type],
)
award_data += award.get_ui_data(user.ckey)
diff --git a/code/datums/achievements/_awards.dm b/code/datums/achievements/_awards.dm
index 23ab5e81001c9..d99659ea780f9 100644
--- a/code/datums/achievements/_awards.dm
+++ b/code/datums/achievements/_awards.dm
@@ -2,8 +2,10 @@
///Name of the achievement, If null it won't show up in the achievement browser. (Handy for inheritance trees)
var/name
var/desc = "You did it."
- ///The icon state for this award. The icon file is found in ui_icons/achievements.
- var/icon = "default"
+ ///The dmi icon file that holds the award's icon state.
+ var/icon = ACHIEVEMENTS_SET
+ ///The icon state for this award.
+ var/icon_state = "default"
var/category = "Normal"
@@ -80,7 +82,7 @@
///Achievements are one-off awards for usually doing cool things.
/datum/award/achievement
desc = "Achievement for epic people"
- icon = "" // This should warn contributors that do not declare an icon when contributing new achievements.
+ icon_state = "" // This should warn contributors that do not declare an icon when contributing new achievements.
///How many players have earned this achievement
var/times_achieved = 0
@@ -171,7 +173,7 @@
/datum/award/score/achievements_score
name = "Achievements Unlocked"
desc = "Don't worry, metagaming is all that matters."
- icon = "elephant" //Obey the reference
+ icon_state = "elephant" //Obey the reference
database_id = ACHIEVEMENTS_SCORE
/datum/award/score/achievements_score/get_ui_data(key)
diff --git a/code/datums/achievements/boss_achievements.dm b/code/datums/achievements/boss_achievements.dm
index a378b703e46ed..c02286b8f7a58 100644
--- a/code/datums/achievements/boss_achievements.dm
+++ b/code/datums/achievements/boss_achievements.dm
@@ -5,119 +5,119 @@
name = "Tendril Exterminator"
desc = "Watch your step"
database_id = BOSS_MEDAL_TENDRIL
- icon = "tendril"
+ icon_state = "tendril"
/datum/award/achievement/boss/boss_killer
name = "Boss Killer"
desc = "You've come a long ways from asking how to switch hands."
database_id = "Boss Killer"
- icon = "firstboss"
+ icon_state = "firstboss"
/datum/award/achievement/boss/blood_miner_kill
name = "Blood-Drunk Miner Killer"
desc = "I guess he couldn't handle his drink that well."
database_id = BOSS_MEDAL_MINER
- icon = "miner"
+ icon_state = "miner"
/datum/award/achievement/boss/demonic_miner_kill
name = "Demonic-Frost Miner Killer"
desc = "Definitely harder than the Blood-Drunk Miner."
database_id = BOSS_MEDAL_FROSTMINER
- icon = "frostminer"
+ icon_state = "frostminer"
/datum/award/achievement/boss/bubblegum_kill
name = "Bubblegum Killer"
desc = "I guess he wasn't made of candy after all"
database_id = BOSS_MEDAL_BUBBLEGUM
- icon = "bbgum"
+ icon_state = "bbgum"
/datum/award/achievement/boss/colossus_kill
name = "Colossus Killer"
desc = "The bigger they are... the better the loot"
database_id = BOSS_MEDAL_COLOSSUS
- icon = "colossus"
+ icon_state = "colossus"
/datum/award/achievement/boss/drake_kill
name = "Drake Killer"
desc = "Now I can wear Rune Platebodies!"
database_id = BOSS_MEDAL_DRAKE
- icon = "drake"
+ icon_state = "drake"
/datum/award/achievement/boss/hierophant_kill
name = "Hierophant Killer"
desc = "Hierophant, but not triumphant."
database_id = BOSS_MEDAL_HIEROPHANT
- icon = "hierophant"
+ icon_state = "hierophant"
/datum/award/achievement/boss/legion_kill
name = "Legion Killer"
desc = "We were many..now we are none."
database_id = BOSS_MEDAL_LEGION
- icon = "legion"
+ icon_state = "legion"
/datum/award/achievement/boss/wendigo_kill
name = "Wendigo Killer"
desc = "You've now ruined years of mythical storytelling."
database_id = BOSS_MEDAL_WENDIGO
- icon = "wendigo"
+ icon_state = "wendigo"
/datum/award/achievement/boss/blood_miner_crusher
name = "Blood-Drunk Miner Crusher"
desc = "I guess he couldn't handle his drink that well."
database_id = BOSS_MEDAL_MINER_CRUSHER
- icon = "miner"
+ icon_state = "miner"
/datum/award/achievement/boss/demonic_miner_crusher
name = "Demonic-Frost Miner Crusher"
desc = "Definitely harder than the Blood-Drunk Miner."
database_id = BOSS_MEDAL_FROSTMINER_CRUSHER
- icon = "frostminer"
+ icon_state = "frostminer"
/datum/award/achievement/boss/bubblegum_crusher
name = "Bubblegum Crusher"
desc = "I guess he wasn't made of candy after all"
database_id = BOSS_MEDAL_BUBBLEGUM_CRUSHER
- icon = "bbgum"
+ icon_state = "bbgum"
/datum/award/achievement/boss/colossus_crusher
name = "Colossus Crusher"
desc = "The bigger they are... the better the loot"
database_id = BOSS_MEDAL_COLOSSUS_CRUSHER
- icon = "colossus"
+ icon_state = "colossus"
/datum/award/achievement/boss/drake_crusher
name = "Drake Crusher"
desc = "Now I can wear Rune Platebodies!"
database_id = BOSS_MEDAL_DRAKE_CRUSHER
- icon = "drake"
+ icon_state = "drake"
/datum/award/achievement/boss/hierophant_crusher
name = "Hierophant Crusher"
desc = "Hierophant, but not triumphant."
database_id = BOSS_MEDAL_HIEROPHANT_CRUSHER
- icon = "hierophant"
+ icon_state = "hierophant"
/datum/award/achievement/boss/legion_crusher
name = "Legion Crusher"
desc = "We were many... now we are none."
database_id = BOSS_MEDAL_LEGION_CRUSHER
- icon = "legion"
+ icon_state = "legion"
/datum/award/achievement/boss/wendigo_crusher
name = "Wendigo Crusher"
desc = "You've now ruined years of mythical storytelling."
database_id = BOSS_MEDAL_WENDIGO_CRUSHER
- icon = "wendigo"
+ icon_state = "wendigo"
//should be removed soon
/datum/award/achievement/boss/king_goat_kill
name = "King Goat Killer"
desc = "The king is dead, long live the king!"
database_id = BOSS_MEDAL_KINGGOAT
- icon = "goatboss"
+ icon_state = "goatboss"
/datum/award/achievement/boss/king_goat_crusher
name = "King Goat Crusher"
desc = "The king is dead, long live the king!"
database_id = BOSS_MEDAL_KINGGOAT_CRUSHER
- icon = "goatboss"
+ icon_state = "goatboss"
diff --git a/code/datums/achievements/job_achievements.dm b/code/datums/achievements/job_achievements.dm
index 6aafbee8e68d1..bd37de7c0e112 100644
--- a/code/datums/achievements/job_achievements.dm
+++ b/code/datums/achievements/job_achievements.dm
@@ -8,7 +8,7 @@
name = "All Within Theoretical Limits"
desc = "I never thought I'd see a resonance cascade, let alone prevent one..."
database_id = MEDAL_THEORETICAL_LIMITS
- icon = "theoreticallimits"
+ icon_state = "theoreticallimits"
//medical
@@ -16,13 +16,13 @@
name = "Mister Sandman"
desc = "Mechanically speaking, there's no real benefit to being unconscious during surgery. Weird how insistent this doctor is about using the N2O anyway though, huh?"
database_id = MEDAL_SANDMAN
- icon = "basemisc"
+ icon_state = "basemisc"
/datum/award/achievement/jobs/helbitaljanken
name = "Helbitaljanken"
desc = "You janked hard"
database_id = MEDAL_HELBITALJANKEN
- icon = "helbital"
+ icon_state = "helbital"
//mining
@@ -30,7 +30,7 @@
name = "Frenching"
desc = "Just a taste, for science!"
database_id = MEDAL_FRENCHING
- icon = "frenchingthebubble"
+ icon_state = "frenchingthebubble"
//science
@@ -38,13 +38,13 @@
name = "Feat of Strength"
desc = "If the rod is immovable, is it passing you or are you passing it?"
database_id = MEDAL_RODSUPLEX
- icon = "featofstrength"
+ icon_state = "featofstrength"
/datum/award/achievement/jobs/snail
name = "KKKiiilll mmmeee"
desc = "You were a little too ambitious, but hey, I guess you're still alive?"
database_id = MEDAL_SNAIL
- icon = "snail"
+ icon_state = "snail"
//all of service! hip hip!
@@ -52,18 +52,18 @@
name = "Centcom Grade: Shitty Service"
desc = "Well, you at least tried. How about trying harder?"
database_id = MEDAL_BAD_SERVICE
- icon = "service_bad"
+ icon_state = "service_bad"
/datum/award/achievement/jobs/service_okay
name = "Centcom Grade: Acceptable Service"
desc = "Well, it'll do! You and your department did just fine."
database_id = MEDAL_OKAY_SERVICE
- icon = "service_okay"
+ icon_state = "service_okay"
/datum/award/achievement/jobs/service_good
name = "Centcom Grade: Exemplary Service"
desc = "Centcom is very impressed with your department!"
database_id = MEDAL_GOOD_SERVICE
- icon = "service_good"
+ icon_state = "service_good"
//civilian achievies! while not recognized by the code, it is recognized by our hearts
diff --git a/code/datums/achievements/mafia_achievements.dm b/code/datums/achievements/mafia_achievements.dm
index da70fb11e871f..31462f7a0ce2f 100644
--- a/code/datums/achievements/mafia_achievements.dm
+++ b/code/datums/achievements/mafia_achievements.dm
@@ -7,103 +7,103 @@
name = "Assistant Victory"
desc = "If you got killed instead of someone more important, you just flexed the true strength of your \"\"\"\"role\"\"\"\"."
database_id = MAFIA_MEDAL_ASSISTANT
- icon = "town"
+ icon_state = "town"
/datum/award/achievement/mafia/detective
name = "Detective Victory"
desc = "If you did this with a Medical Doctor in the game, i'm not really that impressed."
database_id = MAFIA_MEDAL_DETECTIVE
- icon = "town"
+ icon_state = "town"
/datum/award/achievement/mafia/psychologist
name = "Psychologist Victory"
desc = "You learned how to not reveal someone random night one! Or... maybe you're just a lucky bastard."
database_id = MAFIA_MEDAL_PSYCHOLOGIST
- icon = "town"
+ icon_state = "town"
/datum/award/achievement/mafia/chaplain
name = "Chaplain Victory"
desc = "Useless... until the one night the thoughtfeeder confidently claims themselves as detective. Mafia's true bullshit detector."
database_id = MAFIA_MEDAL_CHAPLAIN
- icon = "town"
+ icon_state = "town"
/datum/award/achievement/mafia/md
name = "Medical Doctor Victory"
desc = "Congratulations on learning how to not talk!"
database_id = MAFIA_MEDAL_MD
- icon = "town"
+ icon_state = "town"
/datum/award/achievement/mafia/officer
name = "Security Officer Victory"
desc = "Don't worry, you can win this if you're dead! You... did use your ability to become dead, right?"
database_id = MAFIA_MEDAL_OFFICER
- icon = "town"
+ icon_state = "town"
/datum/award/achievement/mafia/lawyer
name = "Lawyer Victory"
desc = "Oh don't mind me, i'm just the worst rol- Oops, I just instantly ended the game."
database_id = MAFIA_MEDAL_LAWYER
- icon = "town"
+ icon_state = "town"
/datum/award/achievement/mafia/hop
name = "Head of Personnel Victory"
desc = "King of Assistants, waster of a single mafia's night, thrower of games."
database_id = MAFIA_MEDAL_HOP
- icon = "town"
+ icon_state = "town"
/datum/award/achievement/mafia/warden
name = "Warden Victory"
desc = "Make changelings think you're detective, go on lockdown, actual detective investigates you and dies. Cha cha real smooth!"
database_id = MAFIA_MEDAL_WARDEN
- icon = "town"
+ icon_state = "town"
/datum/award/achievement/mafia/hos
name = "Head of Security Victory"
desc = "Certified not shitcurity."
database_id = MAFIA_MEDAL_HOS
- icon = "town"
+ icon_state = "town"
/datum/award/achievement/mafia/changeling
name = "Changeling Victory"
desc = "I think the changelings are metacomming."
database_id = MAFIA_MEDAL_CHANGELING
- icon = "mafia"
+ icon_state = "mafia"
/datum/award/achievement/mafia/thoughtfeeder
name = "Thoughtfeeder Victory"
desc = "Clown's best friend. And Obsessed. And fugitive? Whose side are you on?!"
database_id = MAFIA_MEDAL_THOUGHTFEEDER
- icon = "mafia"
+ icon_state = "mafia"
/datum/award/achievement/mafia/traitor
name = "Traitor Victory"
desc = "Guys, we still have two more changelings to ki-!! TRAITOR VICTORY !!"
database_id = MAFIA_MEDAL_TRAITOR
- icon = "neutral"
+ icon_state = "neutral"
/datum/award/achievement/mafia/nightmare
name = "Nightmare Victory"
desc = "DID YOUR LIGHT FLICKER?!"
database_id = MAFIA_MEDAL_NIGHTMARE
- icon = "neutral"
+ icon_state = "neutral"
/datum/award/achievement/mafia/fugitive
name = "Fugitive Victory"
desc = "I'm just the description on an achievement, but if you end up having to choose between town and changelings, go changelings."
database_id = MAFIA_MEDAL_FUGITIVE
- icon = "neutral"
+ icon_state = "neutral"
/datum/award/achievement/mafia/obsessed
name = "Obsessed Victory"
desc = "You got your target lynched, so instead of being spiteful and annoying, you're just smug and annoying."
database_id = MAFIA_MEDAL_OBSESSED
- icon = "neutral"
+ icon_state = "neutral"
/datum/award/achievement/mafia/clown
name = "Clown Victory"
desc = "Did you know this works on traitors, despite their immunity? If you hit the jackpot and manage to kill one, they'll salt into the next dimension. Clown tips!"
database_id = MAFIA_MEDAL_CLOWN
- icon = "neutral"
+ icon_state = "neutral"
///ALL THE ACHIEVEMENTS FOR MISC MAFIA ODDITIES///
@@ -111,4 +111,4 @@
name = "Universally Hated"
desc = "Managed to get more than 12 votes when put up on trial, jesus christ."
database_id = MAFIA_MEDAL_HATED
- icon = "hated"
+ icon_state = "hated"
diff --git a/code/datums/achievements/misc_achievements.dm b/code/datums/achievements/misc_achievements.dm
index e452b860f0072..e92fc3bc56b91 100644
--- a/code/datums/achievements/misc_achievements.dm
+++ b/code/datums/achievements/misc_achievements.dm
@@ -1,54 +1,54 @@
/datum/award/achievement/misc
category = "Misc"
- icon = "basemisc" //for those achievements that still need an actual icon, later.
+ icon_state = "basemisc" //for those achievements that still need an actual icon, later.
/datum/award/achievement/misc/meteor_examine
name = "Your Life Before Your Eyes"
desc = "Take a close look at hurtling space debris"
database_id = MEDAL_METEOR
- icon = "meteors"
+ icon_state = "meteors"
/datum/award/achievement/misc/pulse
name = "Jackpot"
desc = "Win a pulse rifle from an arcade machine"
database_id = MEDAL_PULSE
- icon = "jackpot"
+ icon_state = "jackpot"
/datum/award/achievement/misc/time_waste
name = "Time waster"
desc = "Speak no evil, hear no evil, see just errors"
database_id = MEDAL_TIMEWASTE
- icon = "timewaste"
+ icon_state = "timewaste"
/datum/award/achievement/misc/round_and_full
name = "Round and Full"
desc = "Well at least you aren't down the river, I hear they eat people there."
database_id = MEDAL_CLOWNCARKING
- icon = "clownking"
+ icon_state = "clownking"
/datum/award/achievement/misc/the_best_driver
name = "The Best Driver"
desc = "100 honks later"
database_id = MEDAL_THANKSALOT
- icon = "clownthanks"
+ icon_state = "clownthanks"
/datum/award/achievement/misc/getting_an_upgrade
name = "Getting an upgrade"
desc = "Make your first unique material item!"
database_id = MEDAL_MATERIALCRAFT
- icon = "upgrade"
+ icon_state = "upgrade"
/datum/award/achievement/misc/rocket_holdup
name = "Disk, Please!"
desc = "Is the man currently pointing a loaded rocket launcher at your head point blank really dumb enough to pull the trigger? Do you really want to find out?"
database_id = MEDAL_DISKPLEASE
- icon = "rocket_holdup"
+ icon_state = "rocket_holdup"
/datum/award/achievement/misc/gamer
name = "My Watchlist Status is Not Important"
desc = "You may be under the impression that violent video games are a harmless pastime, but the security and medical personnel swarming your location with batons and knockout gas look like they disagree."
database_id = MEDAL_GAMER
- icon = "live_sec_reaction"
+ icon_state = "live_sec_reaction"
/datum/award/achievement/misc/vendor_squish
name = "I Was a Teenage Anarchist"
@@ -69,161 +69,161 @@
name = "One Lean, Mean, Cleaning Machine"
desc = "How does it feel to know that your workplace values a mop bucket on wheels more than you?" // i can do better than this give me time
database_id = MEDAL_CLEANBOSS
- icon = "cleanboss"
+ icon_state = "cleanboss"
/datum/award/achievement/misc/rule8
name = "Rule 8"
desc = "Call an admin this is ILLEGAL!!"
database_id = MEDAL_RULE8
- icon = "rule8"
+ icon_state = "rule8"
/datum/award/achievement/misc/speed_round
name = "Long shift"
desc = "Well, that didn't take long."
database_id = MEDAL_LONGSHIFT
- icon = "longshift"
+ icon_state = "longshift"
/datum/award/achievement/misc/lookoutsir
name = "Look Out, Sir!"
desc = "Either awarded for making the ultimate sacrifice for your comrades, or a really dumb attempt at grenade jumping."
database_id = MEDAL_LOOKOUTSIR
- icon = "martyr" // purple heart on an explosive danger warning sign (well, sort of)
+ icon_state = "martyr" // purple heart on an explosive danger warning sign (well, sort of)
/datum/award/achievement/misc/gottem
name = "HA, GOTTEM"
desc = "Made you look!"
database_id = MEDAL_GOTTEM
- icon = "gottem"
+ icon_state = "gottem"
/datum/award/achievement/misc/ascension
name = "Ascension"
desc = "Caedite eos. Novit enim Dominus qui sunt eius."
database_id = MEDAL_ASCENSION
- icon = "ascension"
+ icon_state = "ascension"
/datum/award/achievement/misc/ash_ascension
name = "Nightwatcher's Eyes"
desc = "You've risen above the flames, became one with the ashes. You've been reborn as one with the Nightwatcher."
database_id = MEDAL_ASH_ASCENSION
- icon = "ashascend"
+ icon_state = "ashascend"
/datum/award/achievement/misc/flesh_ascension
name = "Vortex of Arms"
desc = "You've became something more, something greater. A piece of the emperor resides within you, and you within him."
database_id = MEDAL_FLESH_ASCENSION
- icon = "fleshascend"
+ icon_state = "fleshascend"
/datum/award/achievement/misc/rust_ascension
name = "Hills of Rust"
desc = "You've summoned a piece of the Hill of rust, and so the Hills welcome you."
database_id = MEDAL_RUST_ASCENSION
- icon = "rustascend"
+ icon_state = "rustascend"
/datum/award/achievement/misc/void_ascension
name = "All that perish"
desc = "Place of a different being, different time. Everything ends there... but maybe it is just the beginning?"
database_id = MEDAL_VOID_ASCENSION
- icon = "voidascend"
+ icon_state = "voidascend"
/datum/award/achievement/misc/blade_ascension
name = "Silver and Steel"
desc = "You've become the master of all duellists - the paragon of blades."
database_id = MEDAL_BLADE_ASCENSION
- icon = "bladeascend"
+ icon_state = "bladeascend"
/datum/award/achievement/misc/cosmic_ascension
name = "It arrived"
desc = "You managed to teleport an entity on the station that really shouldn't be there."
database_id = MEDAL_COSMOS_ASCENSION
- icon = "cosmicascend"
+ icon_state = "cosmicascend"
/datum/award/achievement/misc/lock_ascension
name = "Secrets of the Locked Labyrinth"
desc = "You managed to open a gate into the mansus."
database_id = MEDAL_LOCK_ASCENSION
- icon = "lockascend"
+ icon_state = "lockascend"
/datum/award/achievement/misc/moon_ascension
name = "The Last Act"
desc = "You managed to become the ringleader and slay the lie."
database_id = MEDAL_MOON_ASCENSION
- icon = "moonascend"
+ icon_state = "moonascend"
/datum/award/achievement/misc/grand_ritual_finale
name = "Archmage"
desc = "Made a big impression on the station with your phenomenal cosmic power."
database_id = MEDAL_ARCHMAGE
- icon = "archmage"
+ icon_state = "archmage"
/datum/award/achievement/misc/toolbox_soul
name = "SOUL'd Out"
desc = "My eternal soul was destroyed to make a toolbox look funny and all I got was this achievement..."
database_id = MEDAL_TOOLBOX_SOUL
- icon = "toolbox_soul"
+ icon_state = "toolbox_soul"
/datum/award/achievement/misc/hot_damn
name = "Hot Damn!"
desc = "Sometimes you need to make some noise to make a point."
database_id = MEDAL_HOT_DAMN
- icon = "hotdamn"
+ icon_state = "hotdamn"
/datum/award/achievement/misc/cayenne_disk
name = "Very Important Piscis"
desc = "You can rest well now."
database_id = MEDAL_CAYENNE_DISK
- icon = "cayenne_disk"
+ icon_state = "cayenne_disk"
/datum/award/achievement/misc/tram_surfer
name = "Tram Surfer"
desc = "Lights out, guerilla radio!"
database_id = MEDAL_TRAM_SURFER
- icon = "tram_surfer"
+ icon_state = "tram_surfer"
/datum/award/achievement/misc/cult_shuttle_omfg
name = "WHAT JUST HAPPENED"
desc = "As a blood cultist, be part of a team that summons 3 shuttle curses within 10 seconds. Imagine cleaning up after them, g r o s s!"
database_id = MEDAL_CULT_SHUTTLE_OMFG
- icon = "cult_shuttle_omfg"
+ icon_state = "cult_shuttle_omfg"
/datum/award/achievement/misc/clickbait
name = "Clickbait"
desc = "Where's my free smartphone?!?"
database_id = MEDAL_CLICKBAIT
- icon = "bait"
+ icon_state = "bait"
/datum/award/achievement/misc/narsupreme
name = "If Nar'Sie is so good, why isn't there a..."
desc = "Even interdimensional space deitys need a friend."
database_id = MEDAL_NARSUPREME
- icon = "narsupreme"
+ icon_state = "narsupreme"
/datum/award/achievement/misc/springlock
name = "The Man Inside the MODsuit"
desc = "Ignore the warning label on a springlock MODsuit."
database_id = MEDAL_SPRINGLOCK
- icon = "springlock"
+ icon_state = "springlock"
/datum/award/achievement/misc/healthy
name = "The Picture of Health"
desc = "Don't be such a baby, it's just a heart attack. You've bounced back from worse!"
database_id = MEDAL_HEALTHY
- icon = "picofhealth"
+ icon_state = "picofhealth"
/datum/award/achievement/misc/gods_wrath
name = "God's Wrath"
desc = "Did you think you could get away with defiling the word of God?"
database_id = MEDAL_GODS_WRATH
- icon = "godswrath"
+ icon_state = "godswrath"
/datum/award/achievement/misc/earthquake_victim
name = "A Nasty Fall"
desc = "...And the earth opened its mouth and swallowed them and their station- all the HOP's men and all their possessions."
database_id = MEDAL_EARTHQUAKE_VICTIM
- icon = "earthquake"
+ icon_state = "earthquake"
/datum/award/achievement/misc/debt_extinguished
name = "Outdebted"
desc = "I've paid my dues, shift after shift... I've done my sentence but commited no griff..."
database_id = MEDAL_DEBT_EXTINGUISHED
- icon = "outdebted"
+ icon_state = "outdebted"
diff --git a/code/datums/achievements/skill_achievements.dm b/code/datums/achievements/skill_achievements.dm
index 6384b1b3db4ad..7e2f3f1a24742 100644
--- a/code/datums/achievements/skill_achievements.dm
+++ b/code/datums/achievements/skill_achievements.dm
@@ -5,10 +5,10 @@
name = "Legendary miner"
desc = "No mere rock can stop me!"
database_id = MEDAL_LEGENDARY_MINER
- icon = "mining"
+ icon_state = "mining"
/datum/award/achievement/skill/legendary_fisher
name = "Legendary fisher"
desc = "Give a spaceman a fish and you feed him for a while; teach a spaceman to fish and you feed him until the shuttle arrives."
database_id = MEDAL_LEGENDARY_FISHER
- icon = "fishing_hat"
+ icon_state = "fishing_hat"
diff --git a/code/datums/brain_damage/special.dm b/code/datums/brain_damage/special.dm
index 651881292e9c7..e24ecd99c6177 100644
--- a/code/datums/brain_damage/special.dm
+++ b/code/datums/brain_damage/special.dm
@@ -267,11 +267,11 @@
lose_text = span_warning("You realize you can feel pain again.")
/datum/brain_trauma/special/tenacity/on_gain()
- owner.add_traits(list(TRAIT_NOSOFTCRIT, TRAIT_NOHARDCRIT), TRAUMA_TRAIT)
+ owner.add_traits(list(TRAIT_NOSOFTCRIT, TRAIT_NOHARDCRIT, TRAIT_ANALGESIA), TRAUMA_TRAIT)
..()
/datum/brain_trauma/special/tenacity/on_lose()
- owner.remove_traits(list(TRAIT_NOSOFTCRIT, TRAIT_NOHARDCRIT), TRAUMA_TRAIT)
+ owner.remove_traits(list(TRAIT_NOSOFTCRIT, TRAIT_NOHARDCRIT, TRAIT_ANALGESIA), TRAUMA_TRAIT)
..()
/datum/brain_trauma/special/death_whispers
diff --git a/code/datums/components/jetpack.dm b/code/datums/components/jetpack.dm
index 3e0fd28ed8013..437660abc82e0 100644
--- a/code/datums/components/jetpack.dm
+++ b/code/datums/components/jetpack.dm
@@ -116,7 +116,7 @@
return
if(user.throwing)//You don't must use jet if you thrown
return
- if(length(user.client.keys_held & user.client.movement_keys))//You use jet when press keys. yes.
+ if(user.client.intended_direction)//You use jet when press keys. yes.
thrust()
/datum/component/jetpack/proc/pre_move_react(mob/user)
diff --git a/code/datums/components/material/material_container.dm b/code/datums/components/material/material_container.dm
index b69ae91825ea8..69f67d46df3a8 100644
--- a/code/datums/components/material/material_container.dm
+++ b/code/datums/components/material/material_container.dm
@@ -275,10 +275,9 @@
var/inserted = 0
//All messages to be displayed to chat
var/list/chat_msgs = list()
-
//differs from held_item when using TK
var/active_held = user.get_active_held_item()
-
+ //storage items to retrive items from
var/static/list/storage_items
if(isnull(storage_items))
storage_items = list(
@@ -288,7 +287,7 @@
)
//1st iteration consumes all items that do not have contents inside
- //2nd iteration consumes items who do have contents inside(but they were consumed in the 1st iteration si its empty now)
+ //2nd iteration consumes items who do have contents inside(but they were consumed in the 1st iteration so its empty now)
for(var/i in 1 to 2)
//no point inserting more items
if(inserted == MATERIAL_INSERT_ITEM_NO_SPACE)
@@ -311,15 +310,11 @@
//can't allow abstract, hologram items
if((target_item.item_flags & ABSTRACT) || (target_item.flags_1 & HOLOGRAM_1))
continue
- //untouchable, move it out the way, code copied from recycler
- if(target_item.resistance_flags & INDESTRUCTIBLE)
- target_item.forceMove(get_turf(parent))
- continue
//user defined conditions
if(SEND_SIGNAL(src, COMSIG_MATCONTAINER_PRE_USER_INSERT, target_item, user) & MATCONTAINER_BLOCK_INSERT)
continue
- //item is either not allowed for redemption, not in the allowed types
- if((target_item.item_flags & NO_MAT_REDEMPTION) || (allowed_item_typecache && !is_type_in_typecache(target_item, allowed_item_typecache)))
+ //item is either indestructible, not allowed for redemption or not in the allowed types
+ if((target_item.resistance_flags & INDESTRUCTIBLE) || (target_item.item_flags & NO_MAT_REDEMPTION) || (allowed_item_typecache && !is_type_in_typecache(target_item, allowed_item_typecache)))
if(!(mat_container_flags & MATCONTAINER_SILENT) && i == 1) //count only child items the 1st time around
var/list/status_data = chat_msgs["[MATERIAL_INSERT_ITEM_FAILURE]"] || list()
var/list/item_data = status_data[target_item.name] || list()
@@ -327,6 +322,10 @@
status_data[target_item.name] = item_data
chat_msgs["[MATERIAL_INSERT_ITEM_FAILURE]"] = status_data
+ if(target_item.resistance_flags & INDESTRUCTIBLE)
+ if(i == 1 && target_item != active_held) //move it out of any storage medium its in so it doesn't get consumed with its parent, but only if that storage medium is not our hand
+ target_item.forceMove(get_turf(context))
+ continue
//storage items usually come here but we make the exception only on the 1st iteration
//this is so players can insert items from their bags into machines for convinience
if(!is_type_in_list(target_item, storage_items))
diff --git a/code/datums/components/scope.dm b/code/datums/components/scope.dm
index b413b6f8e51ac..531ff9e9962df 100644
--- a/code/datums/components/scope.dm
+++ b/code/datums/components/scope.dm
@@ -60,7 +60,7 @@
stop_zooming(user_mob)
return
tracker.calculate_params()
- if(!length(user_client.keys_held & user_client.movement_keys))
+ if(!user_client.intended_direction)
user_mob.face_atom(tracker.given_turf)
animate(user_client, world.tick_lag, pixel_x = tracker.given_x, pixel_y = tracker.given_y)
diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm
index bee48a2bb4936..b979c9cda0f5c 100644
--- a/code/datums/helper_datums/teleport.dm
+++ b/code/datums/helper_datums/teleport.dm
@@ -70,6 +70,9 @@
teleatom.balloon_alert(teleatom, "something holds you back!")
return FALSE
+ SEND_SIGNAL(teleatom, COMSIG_MOVABLE_TELEPORTED, destination, channel)
+ SEND_SIGNAL(destturf, COMSIG_ATOM_INTERCEPT_TELEPORTED, channel, curturf, destturf)
+
if(isobserver(teleatom))
teleatom.abstract_move(destturf)
return TRUE
@@ -85,7 +88,7 @@
teleatom.log_message("teleported from [loc_name(curturf)] to [loc_name(destturf)].", LOG_GAME, log_globally = FALSE)
M.cancel_camera()
- SEND_SIGNAL(teleatom, COMSIG_MOVABLE_POST_TELEPORT)
+ SEND_SIGNAL(teleatom, COMSIG_MOVABLE_POST_TELEPORT, destination, channel)
return TRUE
@@ -209,7 +212,4 @@
if(SEND_SIGNAL(destination_turf, COMSIG_ATOM_INTERCEPT_TELEPORTING, channel, origin_turf, destination_turf) & COMPONENT_BLOCK_TELEPORT)
return FALSE
- SEND_SIGNAL(teleported_atom, COMSIG_MOVABLE_TELEPORTED, destination, channel)
- SEND_SIGNAL(destination_turf, COMSIG_ATOM_INTERCEPT_TELEPORTED, channel, origin_turf, destination_turf)
-
return TRUE
diff --git a/code/datums/martial/krav_maga.dm b/code/datums/martial/krav_maga.dm
index cda53bbe6475e..1710009de3c6e 100644
--- a/code/datums/martial/krav_maga.dm
+++ b/code/datums/martial/krav_maga.dm
@@ -102,6 +102,8 @@
/datum/martial_art/krav_maga/proc/leg_sweep(mob/living/attacker, mob/living/defender)
if(defender.stat != CONSCIOUS || defender.IsParalyzed())
return MARTIAL_ATTACK_INVALID
+ if(HAS_TRAIT(attacker, TRAIT_PACIFISM))
+ return MARTIAL_ATTACK_INVALID // Does 5 damage, so we can't let pacifists leg sweep.
defender.visible_message(
span_warning("[attacker] leg sweeps [defender]!"),
span_userdanger("Your legs are sweeped by [attacker]!"),
@@ -134,6 +136,8 @@
return MARTIAL_ATTACK_SUCCESS
/datum/martial_art/krav_maga/proc/neck_chop(mob/living/attacker, mob/living/defender)
+ if(HAS_TRAIT(attacker, TRAIT_PACIFISM))
+ return MARTIAL_ATTACK_INVALID // Does 10 damage, so we can't let pacifists neck chop.
attacker.do_attack_animation(defender)
defender.visible_message(
span_warning("[attacker] karate chops [defender]'s neck!"),
diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm
index afcb5d688ed60..32b9772dc0709 100644
--- a/code/datums/mood_events/generic_negative_events.dm
+++ b/code/datums/mood_events/generic_negative_events.dm
@@ -92,6 +92,12 @@
mood_change = -3
timeout = 2 MINUTES
+/datum/mood_event/reattachment/New(mob/M, ...)
+ if(HAS_TRAIT(M, TRAIT_ANALGESIA))
+ qdel(src)
+ return
+ return ..()
+
/datum/mood_event/reattachment/add_effects(obj/item/bodypart/limb)
if(limb)
description = "Ouch! My [limb.plaintext_zone] feels like I fell asleep on it."
@@ -122,6 +128,12 @@
mood_change = -3
timeout = 3 MINUTES
+/datum/mood_event/table_limbsmash/New(mob/M, ...)
+ if(HAS_TRAIT(M, TRAIT_ANALGESIA))
+ qdel(src)
+ return
+ return ..()
+
/datum/mood_event/table_limbsmash/add_effects(obj/item/bodypart/banged_limb)
if(banged_limb)
description = "My fucking [banged_limb.plaintext_zone], man that hurts..."
@@ -194,6 +206,12 @@
mood_change = -5
timeout = 60 SECONDS
+/datum/mood_event/painful_medicine/New(mob/M, ...)
+ if(HAS_TRAIT(M, TRAIT_ANALGESIA))
+ qdel(src)
+ return
+ return ..()
+
/datum/mood_event/spooked
description = "The rattling of those bones... It still haunts me."
mood_change = -4
@@ -231,6 +249,12 @@
description = "Bags never sit right on my back, this hurts like hell!"
mood_change = -15
+/datum/mood_event/back_pain/New(mob/M, ...)
+ if(HAS_TRAIT(M, TRAIT_ANALGESIA))
+ qdel(src)
+ return
+ return ..()
+
/datum/mood_event/sad_empath
description = "Someone seems upset..."
mood_change = -1
diff --git a/code/datums/quirks/negative_quirks/all_nighter.dm b/code/datums/quirks/negative_quirks/all_nighter.dm
index 798add0539f24..253ce12b41f32 100644
--- a/code/datums/quirks/negative_quirks/all_nighter.dm
+++ b/code/datums/quirks/negative_quirks/all_nighter.dm
@@ -14,7 +14,7 @@
mail_goodies = list(
/obj/item/clothing/glasses/blindfold,
- /obj/item/bedsheet/random,
+ /obj/effect/spawner/random/bedsheet/any,
/obj/item/clothing/under/misc/pj/red,
/obj/item/clothing/head/costume/nightcap/red,
/obj/item/clothing/under/misc/pj/blue,
diff --git a/code/datums/sprite_accessories.dm b/code/datums/sprite_accessories.dm
index bf4da0d98ea8f..2f433b8e340fb 100644
--- a/code/datums/sprite_accessories.dm
+++ b/code/datums/sprite_accessories.dm
@@ -2076,10 +2076,6 @@
icon = 'icons/mob/human/species/lizard/lizard_spines.dmi'
em_block = TRUE
-/datum/sprite_accessory/spines_animated
- icon = 'icons/mob/human/species/lizard/lizard_spines.dmi'
- em_block = TRUE
-
/datum/sprite_accessory/tail_spines
icon = 'icons/mob/human/species/lizard/lizard_spines.dmi'
em_block = TRUE
@@ -2088,10 +2084,6 @@
name = "None"
icon_state = "none"
-/datum/sprite_accessory/spines_animated/none
- name = "None"
- icon_state = "none"
-
/datum/sprite_accessory/tail_spines/none
name = "None"
icon_state = "none"
@@ -2100,10 +2092,6 @@
name = "Short"
icon_state = "short"
-/datum/sprite_accessory/spines_animated/short
- name = "Short"
- icon_state = "short"
-
/datum/sprite_accessory/tail_spines/short
name = "Short"
icon_state = "short"
@@ -2112,10 +2100,6 @@
name = "Short + Membrane"
icon_state = "shortmeme"
-/datum/sprite_accessory/spines_animated/shortmeme
- name = "Short + Membrane"
- icon_state = "shortmeme"
-
/datum/sprite_accessory/tail_spines/shortmeme
name = "Short + Membrane"
icon_state = "shortmeme"
@@ -2124,10 +2108,6 @@
name = "Long"
icon_state = "long"
-/datum/sprite_accessory/spines_animated/long
- name = "Long"
- icon_state = "long"
-
/datum/sprite_accessory/tail_spines/long
name = "Long"
icon_state = "long"
@@ -2136,10 +2116,6 @@
name = "Long + Membrane"
icon_state = "longmeme"
-/datum/sprite_accessory/spines_animated/longmeme
- name = "Long + Membrane"
- icon_state = "longmeme"
-
/datum/sprite_accessory/tail_spines/longmeme
name = "Long + Membrane"
icon_state = "longmeme"
@@ -2148,10 +2124,6 @@
name = "Aquatic"
icon_state = "aqua"
-/datum/sprite_accessory/spines_animated/aquatic
- name = "Aquatic"
- icon_state = "aqua"
-
/datum/sprite_accessory/tail_spines/aquatic
name = "Aquatic"
icon_state = "aqua"
diff --git a/code/datums/wounds/bones.dm b/code/datums/wounds/bones.dm
index 80c1c0a755713..9911a7cdd5de0 100644
--- a/code/datums/wounds/bones.dm
+++ b/code/datums/wounds/bones.dm
@@ -413,22 +413,11 @@
user.visible_message(span_notice("[user] finishes applying [I] to [victim]'s [limb.plaintext_zone], emitting a fizzing noise!"), span_notice("You finish applying [I] to [victim]'s [limb.plaintext_zone]!"), ignored_mobs=victim)
to_chat(victim, span_userdanger("[user] finishes applying [I] to your [limb.plaintext_zone], and you can feel the bones exploding with pain as they begin melting and reforming!"))
else
- var/painkiller_bonus = 0
- if(victim.get_drunk_amount() > 10)
- painkiller_bonus += 10
- if(victim.reagents.has_reagent(/datum/reagent/medicine/morphine))
- painkiller_bonus += 20
- if(victim.reagents.has_reagent(/datum/reagent/determination))
- painkiller_bonus += 10
- if(victim.reagents.has_reagent(/datum/reagent/consumable/ethanol/painkiller))
- painkiller_bonus += 15
- if(victim.reagents.has_reagent(/datum/reagent/medicine/mine_salve))
- painkiller_bonus += 20
-
- if(prob(25 + (20 * (severity - 2)) - painkiller_bonus)) // 25%/45% chance to fail self-applying with severe and critical wounds, modded by painkillers
- victim.visible_message(span_danger("[victim] fails to finish applying [I] to [victim.p_their()] [limb.plaintext_zone], passing out from the pain!"), span_notice("You pass out from the pain of applying [I] to your [limb.plaintext_zone] before you can finish!"))
- victim.AdjustUnconscious(5 SECONDS)
- return TRUE
+ if(!HAS_TRAIT(victim, TRAIT_ANALGESIA))
+ if(prob(25 + (20 * (severity - 2)) - min(victim.get_drunk_amount(), 10))) // 25%/45% chance to fail self-applying with severe and critical wounds, modded by drunkenness
+ victim.visible_message(span_danger("[victim] fails to finish applying [I] to [victim.p_their()] [limb.plaintext_zone], passing out from the pain!"), span_notice("You pass out from the pain of applying [I] to your [limb.plaintext_zone] before you can finish!"))
+ victim.AdjustUnconscious(5 SECONDS)
+ return TRUE
victim.visible_message(span_notice("[victim] finishes applying [I] to [victim.p_their()] [limb.plaintext_zone], grimacing from the pain!"), span_notice("You finish applying [I] to your [limb.plaintext_zone], and your bones explode in pain!"))
limb.receive_damage(25, wound_bonus=CANT_WOUND)
diff --git a/code/game/atom/alternate_appearance.dm b/code/game/atom/alternate_appearance.dm
index e22e5c8951995..228462f7936a4 100644
--- a/code/game/atom/alternate_appearance.dm
+++ b/code/game/atom/alternate_appearance.dm
@@ -166,7 +166,7 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances)
return TRUE
return FALSE
-/datum/atom_hud/alternate_appearance/basic/one_person/New(key, image/I, options, mob/living/seer)
+/datum/atom_hud/alternate_appearance/basic/one_person/New(key, image/I, options = NONE, mob/living/seer)
src.seer = seer
return ..()
diff --git a/code/game/machinery/camera/trackable.dm b/code/game/machinery/camera/trackable.dm
index 884f38f750bc5..b1c4b46da4b19 100644
--- a/code/game/machinery/camera/trackable.dm
+++ b/code/game/machinery/camera/trackable.dm
@@ -2,13 +2,13 @@
#define CAMERA_TICK_LIMIT 10
/datum/trackable
- ///Boolean on whether or not we are currently trying to track something.
- var/tracking = FALSE
///Reference to the atom that owns us, used for tracking.
var/atom/tracking_holder
- ///If there is a mob currently being tracked, this will be the weakref to it.
- var/datum/weakref/tracked_mob
+ ///What mob are we currently tracking, if any
+ var/mob/living/tracked_mob
+ ///If we're currently rechecking our target's trackability in hopes of something changing
+ var/rechecking = FALSE
///How many times we've failed to locate our target.
var/cameraticks = 0
@@ -24,7 +24,7 @@
/datum/trackable/New(atom/source)
. = ..()
tracking_holder = source
- RegisterSignal(tracking_holder, COMSIG_MOB_RESET_PERSPECTIVE, PROC_REF(cancel_target_tracking))
+ RegisterSignal(tracking_holder, COMSIG_MOB_RESET_PERSPECTIVE, PROC_REF(perspective_reset))
/datum/trackable/Destroy(force)
tracking_holder = null
@@ -32,27 +32,6 @@
STOP_PROCESSING(SSprocessing, src)
return ..()
-/datum/trackable/process()
- var/mob/living/tracked_target = tracked_mob?.resolve()
- if(!tracked_target || !tracking)
- set_tracking(FALSE)
- return
-
- if(tracked_target.can_track(tracking_holder))
- cameraticks = initial(cameraticks)
- SEND_SIGNAL(tracking_holder, COMSIG_TRACKABLE_TRACKING_TARGET, tracked_target)
- return
-
- if(cameraticks < CAMERA_TICK_LIMIT)
- if(!cameraticks)
- to_chat(tracking_holder, span_warning("Target is not near any active cameras. Attempting to reacquire..."))
- cameraticks++
- return
-
- to_chat(tracking_holder, span_warning("Unable to reacquire, cancelling track..."))
- cameraticks = initial(cameraticks)
- set_tracking(FALSE)
-
///Generates a list of trackable people by name, returning a list of Humans + Non-Humans that can be tracked.
/datum/trackable/proc/find_trackable_mobs()
RETURN_TYPE(/list)
@@ -82,47 +61,140 @@
var/list/targets = sort_list(humans) + sort_list(others)
return targets
-///Toggles whether or not we're tracking something. Arg is whether it's on or off.
-/datum/trackable/proc/set_tracking(on = FALSE)
- if(on)
+/// Takes a mob to track, resets our state and begins trying to follow it
+/// Best we can at least
+/datum/trackable/proc/set_tracked_mob(mob/living/track)
+ set_rechecking(FALSE)
+ if(tracked_mob)
+ UnregisterSignal(tracked_mob, list(COMSIG_QDELETING, COMSIG_MOVABLE_MOVED, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE))
+ if(track && !isliving(track))
+ tracked_mob = null
+ return
+ tracked_mob = track
+ if(tracked_mob)
+ RegisterSignal(tracked_mob, COMSIG_QDELETING, PROC_REF(target_deleted))
+ RegisterSignal(tracked_mob, COMSIG_MOVABLE_MOVED, PROC_REF(target_moved))
+ RegisterSignal(tracked_mob, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, PROC_REF(glide_size_changed))
+ attempt_track()
+
+/datum/trackable/proc/target_deleted(datum/source)
+ SIGNAL_HANDLER
+ reset_tracking()
+
+/datum/trackable/proc/perspective_reset(atom/source)
+ SIGNAL_HANDLER
+ reset_tracking()
+
+/datum/trackable/proc/target_moved(datum/source)
+ SIGNAL_HANDLER
+ if(attempt_track())
+ return
+ set_rechecking(TRUE)
+
+/// Controls if we're processing to recheck the conditions that prevent tracking or not
+/datum/trackable/proc/set_rechecking(should_check)
+ if(should_check)
START_PROCESSING(SSprocessing, src)
- tracking = TRUE
+ cameraticks = initial(cameraticks)
+ rechecking = TRUE
else
STOP_PROCESSING(SSprocessing, src)
- tracking = FALSE
- tracked_mob = null
+ rechecking = FALSE
+
+/datum/trackable/process()
+ if(!rechecking)
+ return PROCESS_KILL
+
+ if(attempt_track())
+ set_rechecking(FALSE)
+ return
+
+ if(cameraticks < CAMERA_TICK_LIMIT)
+ if(!cameraticks)
+ to_chat(tracking_holder, span_warning("Target is not near any active cameras. Attempting to reacquire..."))
+ cameraticks++
+ return
-///Called by Signals, used to cancel tracking of a target.
-/datum/trackable/proc/cancel_target_tracking(atom/source)
+ to_chat(tracking_holder, span_warning("Unable to reacquire, cancelling track..."))
+ reset_tracking()
+
+/// Tries to track onto our target mob. Returns true if it succeeds, false otherwise
+/datum/trackable/proc/attempt_track()
+ if(!tracked_mob)
+ reset_tracking()
+ return FALSE
+
+ if(!tracked_mob.can_track(tracking_holder))
+ return FALSE
+ // In case we've been checking
+ set_rechecking(FALSE)
+ SEND_SIGNAL(src, COMSIG_TRACKABLE_TRACKING_TARGET, tracked_mob)
+ return TRUE
+
+/datum/trackable/proc/glide_size_changed(datum/source, new_glide_size)
SIGNAL_HANDLER
- set_tracking(FALSE)
+ SEND_SIGNAL(src, COMSIG_TRACKABLE_GLIDE_CHANGED, tracked_mob, new_glide_size)
/**
- * set_tracked_mob
+ * reset_tracking
*
- * Sets a mob as being tracked, if a target is already provided then it will track that directly,
- * otherwise it will give a tgui input list to find targets to track.
+ * Resets our tracking
+ */
+/datum/trackable/proc/reset_tracking()
+ set_tracked_mob(null)
+
+/**
+ * track_input
+ *
+ * Sets a mob as being tracked, will give a tgui input list to find targets to track.
* Args:
* tracker - The person trying to track, used for feedback messages. This is not the same as tracking_holder
- * tracked_mob_name - (Optional) The person being tracked, to skip the input list.
*/
-/datum/trackable/proc/set_tracked_mob(mob/living/tracker, tracked_mob_name)
+/datum/trackable/proc/track_input(mob/living/tracker)
if(!tracker || tracker.stat == DEAD)
return
- if(tracked_mob_name)
- find_trackable_mobs() //this is in case the tracked mob is newly/no-longer in camera field of view.
- tracked_mob = isnull(humans[tracked_mob_name]) ? others[tracked_mob_name] : humans[tracked_mob_name]
- if(isnull(tracked_mob))
- to_chat(tracker, span_notice("Target is not on or near any active cameras. Tracking failed."))
- return
- to_chat(tracker, span_notice("Now tracking [tracked_mob_name] on camera."))
- else
- var/target_name = tgui_input_list(tracker, "Select a target", "Tracking", find_trackable_mobs())
- if(!target_name || isnull(target_name))
- return
- tracked_mob = isnull(humans[target_name]) ? others[target_name] : humans[target_name]
+ var/target_name = tgui_input_list(tracker, "Select a target", "Tracking", find_trackable_mobs())
+ if(!target_name || isnull(target_name))
+ return
+ var/datum/weakref/mob_ref = isnull(humans[target_name]) ? others[target_name] : humans[target_name]
+ if(isnull(mob_ref))
+ to_chat(tracker, span_notice("Target is not on or near any active cameras. Tracking failed."))
+ return
+ set_tracked_mob(mob_ref.resolve())
+
+/**
+ * track_name
+ *
+ * Sets a mob as being tracked, will track the passed in target name's target
+ * Args:
+ * tracker - The person trying to track, used for feedback messages. This is not the same as tracking_holder
+ * tracked_mob_name - The person being tracked.
+ */
+/datum/trackable/proc/track_name(mob/living/tracker, tracked_mob_name)
+ if(!tracker || tracker.stat == DEAD)
+ return
+
+ find_trackable_mobs() //this is in case the tracked mob is newly/no-longer in camera field of view.
+ var/datum/weakref/mob_ref = isnull(humans[tracked_mob_name]) ? others[tracked_mob_name] : humans[tracked_mob_name]
+ if(isnull(mob_ref))
+ to_chat(tracker, span_notice("Target is not on or near any active cameras. Tracking failed."))
+ return
+ to_chat(tracker, span_notice("Now tracking [tracked_mob_name] on camera."))
+ set_tracked_mob(mob_ref.resolve())
- set_tracking(TRUE)
+/**
+ * track_mob
+ *
+ * Sets a mob as being tracked, will track the passed in target
+ * Args:
+ * tracker - The person trying to track, used for feedback messages. This is not the same as tracking_holder
+ * tracked - The person being tracked.
+ */
+/datum/trackable/proc/track_mob(mob/living/tracker, mob/living/tracked)
+ if(!tracker || tracker.stat == DEAD)
+ return
+ // Need to make sure the tracked mob is in our list
+ track_name(tracked.name)
#undef CAMERA_TICK_LIMIT
diff --git a/code/game/machinery/computer/crew.dm b/code/game/machinery/computer/crew.dm
index a00ed045be7af..d286f8ee6043d 100644
--- a/code/game/machinery/computer/crew.dm
+++ b/code/game/machinery/computer/crew.dm
@@ -282,7 +282,7 @@ GLOBAL_DATUM_INIT(crewmonitor, /datum/crewmonitor, new)
var/mob/living/silicon/ai/AI = usr
if(!istype(AI))
return
- AI.ai_tracking_tool.set_tracked_mob(AI, params["name"])
+ AI.ai_tracking_tool.track_name(AI, params["name"])
#undef SENSORS_UPDATE_PERIOD
#undef UNKNOWN_JOB_ID
diff --git a/code/game/objects/effects/spawners/random/bedsheet.dm b/code/game/objects/effects/spawners/random/bedsheet.dm
new file mode 100644
index 0000000000000..3fb39c5de4a71
--- /dev/null
+++ b/code/game/objects/effects/spawners/random/bedsheet.dm
@@ -0,0 +1,64 @@
+/obj/effect/spawner/random/bedsheet
+ name = "random dorm bedsheet"
+ icon_state = "random_bedsheet"
+ loot = list(/obj/item/bedsheet = 8,
+ /obj/item/bedsheet/blue = 8,
+ /obj/item/bedsheet/green = 8,
+ /obj/item/bedsheet/grey = 8,
+ /obj/item/bedsheet/orange = 8,
+ /obj/item/bedsheet/purple = 8,
+ /obj/item/bedsheet/red = 8,
+ /obj/item/bedsheet/yellow = 8,
+ /obj/item/bedsheet/brown = 8,
+ /obj/item/bedsheet/black = 8,
+ /obj/item/bedsheet/patriot = 2,
+ /obj/item/bedsheet/rainbow = 2,
+ /obj/item/bedsheet/ian = 2,
+ /obj/item/bedsheet/runtime = 2,
+ /obj/item/bedsheet/cosmos = 2,
+ /obj/item/bedsheet/nanotrasen = 2,
+ /obj/item/bedsheet/pirate = 2,
+ /obj/item/bedsheet/gondola = 1,
+ )
+
+/obj/effect/spawner/random/bedsheet/double
+ name = "random dorm double bedsheet"
+ icon_state = "random_doublesheet"
+ loot = list(
+ /obj/item/bedsheet/double = 4,
+ /obj/item/bedsheet/blue/double = 4,
+ /obj/item/bedsheet/green/double = 4,
+ /obj/item/bedsheet/grey/double = 4,
+ /obj/item/bedsheet/orange/double = 4,
+ /obj/item/bedsheet/purple/double = 4,
+ /obj/item/bedsheet/red/double = 4,
+ /obj/item/bedsheet/yellow/double = 4,
+ /obj/item/bedsheet/brown/double = 4,
+ /obj/item/bedsheet/black/double = 4,
+ /obj/item/bedsheet/patriot/double = 1,
+ /obj/item/bedsheet/rainbow/double = 1,
+ /obj/item/bedsheet/ian/double = 1,
+ /obj/item/bedsheet/runtime/double = 1,
+ /obj/item/bedsheet/cosmos/double = 1,
+ /obj/item/bedsheet/nanotrasen/double = 1,
+ )
+
+/obj/effect/spawner/random/bedsheet/any
+ name = "random single bedsheet"
+ loot = null
+ var/static/list/bedsheet_list = list()
+ var/spawn_type = BEDSHEET_SINGLE
+
+/obj/effect/spawner/random/bedsheet/any/Initialize(mapload)
+ if(isnull(bedsheet_list[spawn_type]))
+ var/list/spawn_list = list()
+ for(var/obj/item/bedsheet/sheet as anything in typesof(/obj/item/bedsheet))
+ if(initial(sheet.bedsheet_type) == spawn_type)
+ spawn_list += sheet
+ bedsheet_list[spawn_type] = spawn_list
+ loot = bedsheet_list[spawn_type]
+ return ..()
+
+/obj/effect/spawner/random/bedsheet/any/double
+ icon_state = "random_doublesheet"
+ spawn_type = BEDSHEET_DOUBLE
diff --git a/code/game/objects/items/knives.dm b/code/game/objects/items/knives.dm
index b7273ed0a3264..315862d2d79ff 100644
--- a/code/game/objects/items/knives.dm
+++ b/code/game/objects/items/knives.dm
@@ -124,6 +124,7 @@
name = "combat knife"
icon = 'icons/obj/weapons/stabby.dmi'
icon_state = "buckknife"
+ worn_icon_state = "buckknife"
desc = "A military combat utility survival knife."
embedding = list("pain_mult" = 4, "embed_chance" = 65, "fall_chance" = 10, "ignore_throwspeed_threshold" = TRUE)
force = 20
@@ -131,11 +132,33 @@
attack_verb_continuous = list("slashes", "stabs", "slices", "tears", "lacerates", "rips", "cuts")
attack_verb_simple = list("slash", "stab", "slice", "tear", "lacerate", "rip", "cut")
bayonet = TRUE
+ slot_flags = ITEM_SLOT_MASK
+
+/obj/item/knife/combat/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/knockoff, 90, list(BODY_ZONE_PRECISE_MOUTH), slot_flags) //90% to knock off when wearing a mask
+
+/obj/item/knife/combat/dropped(mob/living/user, slot)
+ . = ..()
+ if(user.get_item_by_slot(ITEM_SLOT_MASK) == src && !user.has_status_effect(/datum/status_effect/choke) && prob(20))
+ user.apply_damage(5, BRUTE, BODY_ZONE_HEAD)
+ playsound(user, 'sound/weapons/slice.ogg', 50, TRUE)
+ user.visible_message(span_danger("[user] accidentally cuts [user.p_them()]self while pulling [src] out of [user.p_them()] teeth! What a doofus!"), span_userdanger("You accidentally cut your mouth with [src]!"))
+ . = ..()
+
+/obj/item/knife/combat/equipped(mob/living/user, slot, initial = FALSE)
+ . = ..()
+ if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(20))
+ if(user.get_item_by_slot(ITEM_SLOT_MASK) == src)
+ user.apply_status_effect(/datum/status_effect/choke, src)
+ user.visible_message(span_danger("[user] accidentally swallows [src]!"))
+ playsound(user, 'sound/items/eatfood.ogg', 100, TRUE)
/obj/item/knife/combat/survival
name = "survival knife"
icon = 'icons/obj/weapons/stabby.dmi'
icon_state = "survivalknife"
+ worn_icon_state = "survivalknife"
embedding = list("pain_mult" = 4, "embed_chance" = 35, "fall_chance" = 10)
desc = "A hunting grade survival knife."
force = 15
@@ -153,6 +176,7 @@
desc = "A sharpened bone. The bare minimum in survival."
embedding = list("pain_mult" = 4, "embed_chance" = 35, "fall_chance" = 10)
obj_flags = parent_type::obj_flags & ~CONDUCTS_ELECTRICITY
+ slot_flags = NONE
force = 15
throwforce = 15
custom_materials = null
diff --git a/code/game/objects/items/melee/baton.dm b/code/game/objects/items/melee/baton.dm
index da0bf1dbc64fb..b692b58bfcf2d 100644
--- a/code/game/objects/items/melee/baton.dm
+++ b/code/game/objects/items/melee/baton.dm
@@ -427,6 +427,12 @@
on_stun_volume = 50
active = FALSE
context_living_rmb_active = "Harmful Stun"
+ light_range = 1.5
+ light_system = OVERLAY_LIGHT
+ light_on = FALSE
+ light_color = LIGHT_COLOR_ORANGE
+ light_power = 0.5
+
var/throw_stun_chance = 35
var/obj/item/stock_parts/cell/cell
@@ -541,6 +547,8 @@
active = !active
balloon_alert(user, "turned [active ? "on" : "off"]")
playsound(src, SFX_SPARKS, 75, TRUE, -1)
+ toggle_light(user)
+ do_sparks(1, TRUE, src)
else
active = FALSE
if(!cell)
@@ -550,6 +558,11 @@
update_appearance()
add_fingerprint(user)
+/// Toggles the stun baton's light
+/obj/item/melee/baton/security/proc/toggle_light(mob/user)
+ set_light_on(!light_on)
+ return
+
/obj/item/melee/baton/security/proc/deductcharge(deducted_charge)
if(!cell)
return
@@ -559,6 +572,7 @@
if(active && cell.charge < cell_hit_cost)
//we're below minimum, turn off
active = FALSE
+ set_light_on(FALSE)
update_appearance()
playsound(src, SFX_SPARKS, 75, TRUE, -1)
@@ -649,6 +663,8 @@
if (!cell || cell.charge < cell_hit_cost)
return
active = !active
+ toggle_light()
+ do_sparks(1, TRUE, src)
playsound(src, SFX_SPARKS, 75, TRUE, -1)
update_appearance()
diff --git a/code/game/objects/items/stacks/sheets/leather.dm b/code/game/objects/items/stacks/sheets/leather.dm
index 500b2500c8ad5..e9426a913ec70 100644
--- a/code/game/objects/items/stacks/sheets/leather.dm
+++ b/code/game/objects/items/stacks/sheets/leather.dm
@@ -40,11 +40,6 @@ GLOBAL_LIST_INIT(human_recipes, list( \
inhand_icon_state = null
merge_type = /obj/item/stack/sheet/animalhide/corgi
-GLOBAL_LIST_INIT(gondola_recipes, list ( \
- new/datum/stack_recipe("gondola mask", /obj/item/clothing/mask/gondola, 1, check_density = FALSE, category = CAT_CLOTHING), \
- new/datum/stack_recipe("gondola suit", /obj/item/clothing/under/costume/gondola, 2, check_density = FALSE, category = CAT_CLOTHING), \
- ))
-
/obj/item/stack/sheet/animalhide/corgi/five
amount = 5
@@ -59,6 +54,12 @@ GLOBAL_LIST_INIT(gondola_recipes, list ( \
/obj/item/stack/sheet/animalhide/mothroach/five
amount = 5
+GLOBAL_LIST_INIT(gondola_recipes, list ( \
+ new/datum/stack_recipe("gondola mask", /obj/item/clothing/mask/gondola, 1, check_density = FALSE, category = CAT_CLOTHING), \
+ new/datum/stack_recipe("gondola suit", /obj/item/clothing/under/costume/gondola, 2, check_density = FALSE, category = CAT_CLOTHING), \
+ new/datum/stack_recipe("gondola bedsheet", /obj/item/bedsheet/gondola, 1, check_density = FALSE, category = CAT_FURNITURE), \
+ ))
+
/obj/item/stack/sheet/animalhide/gondola
name = "gondola hide"
desc = "The extremely valuable product of gondola hunting."
diff --git a/code/game/objects/items/storage/boxes/job_boxes.dm b/code/game/objects/items/storage/boxes/job_boxes.dm
index bb656061c1c55..220fdc2f79522 100644
--- a/code/game/objects/items/storage/boxes/job_boxes.dm
+++ b/code/game/objects/items/storage/boxes/job_boxes.dm
@@ -30,10 +30,8 @@
if(!isnull(mask_type))
new mask_type(src)
- if(!isplasmaman(loc))
+ if(!isnull(internal_type))
new internal_type(src)
- else
- new /obj/item/tank/internals/plasmaman/belt(src)
if(!isnull(medipen_type))
new medipen_type(src)
diff --git a/code/game/objects/items/storage/lockbox.dm b/code/game/objects/items/storage/lockbox.dm
index 902af39bbea0e..3e7b73c93f332 100644
--- a/code/game/objects/items/storage/lockbox.dm
+++ b/code/game/objects/items/storage/lockbox.dm
@@ -237,6 +237,8 @@
name = "order lockbox"
desc = "A box used to secure small cargo orders from being looted by those who didn't order it. Yeah, cargo tech, that means you."
icon_state = "secure"
+ icon_closed = "secure"
+ icon_locked = "secure_locked"
icon_broken = "secure+b"
inhand_icon_state = "sec-case"
lefthand_file = 'icons/mob/inhands/equipment/briefcase_lefthand.dmi'
@@ -265,8 +267,10 @@
if(privacy_lock)
atom_storage.locked = STORAGE_NOT_LOCKED
+ icon_state = icon_locked
else
atom_storage.locked = STORAGE_FULLY_LOCKED
+ icon_state = icon_closed
privacy_lock = atom_storage.locked
user.visible_message(span_notice("[user] [privacy_lock ? "" : "un"]locks [src]'s privacy lock."),
span_notice("You [privacy_lock ? "" : "un"]lock [src]'s privacy lock."))
diff --git a/code/game/objects/structures/bedsheet_bin.dm b/code/game/objects/structures/bedsheet_bin.dm
index 66de98bd50ab4..f80042f5679a7 100644
--- a/code/game/objects/structures/bedsheet_bin.dm
+++ b/code/game/objects/structures/bedsheet_bin.dm
@@ -4,10 +4,6 @@ BEDSHEETS
LINEN BINS
*/
-#define BEDSHEET_ABSTRACT "abstract"
-#define BEDSHEET_SINGLE "single"
-#define BEDSHEET_DOUBLE "double"
-
/obj/item/bedsheet
name = "bedsheet"
desc = "A surprisingly soft linen bedsheet."
@@ -28,7 +24,9 @@ LINEN BINS
dog_fashion = /datum/dog_fashion/head/ghost
/// Custom nouns to act as the subject of dreams
var/list/dream_messages = list("white")
- /// The number of cloth sheets to be dropped by this bedsheet when cut
+ /// Cutting it up will yield this.
+ var/stack_type = /obj/item/stack/sheet/cloth
+ /// The number of sheets dropped by this bedsheet when cut
var/stack_amount = 3
/// Denotes if the bedsheet is a single, double, or other kind of bedsheet
var/bedsheet_type = BEDSHEET_SINGLE
@@ -126,7 +124,7 @@ LINEN BINS
/obj/item/bedsheet/attackby(obj/item/I, mob/user, params)
if(I.tool_behaviour == TOOL_WIRECUTTER || I.get_sharpness())
if (!(flags_1 & HOLOGRAM_1))
- var/obj/item/stack/sheet/cloth/shreds = new (get_turf(src), stack_amount)
+ var/obj/item/stack/shreds = new stack_type(get_turf(src), stack_amount)
if(!QDELETED(shreds)) //stacks merged
transfer_fingerprints_to(shreds)
shreds.add_fingerprint(user)
@@ -338,6 +336,60 @@ LINEN BINS
inhand_icon_state = "sheetian"
dream_messages = list("a dog", "a corgi", "woof", "bark", "arf")
+/obj/item/bedsheet/runtime
+ icon_state = "sheetruntime"
+ inhand_icon_state = "sheetruntime"
+ dream_messages = list("a kitty", "a cat", "meow", "purr", "nya~")
+
+/obj/item/bedsheet/pirate
+ name = "pirate's bedsheet"
+ desc = "It has a Jolly Roger emblem on it and has a faint scent of grog."
+ icon_state = "sheetpirate"
+ inhand_icon_state = "sheetpirate"
+ dream_messages = list(
+ "a buried treasure",
+ "an island",
+ "a monkey",
+ "a parrot",
+ "a swashbuckler",
+ "a talking skull",
+ "avast",
+ "being a pirate",
+ "'cause a pirate is free",
+ "doing whatever you want",
+ "gold",
+ "landlubbers",
+ "stealing",
+ "sailing the Seven Seas",
+ "yarr",
+ )
+
+/obj/item/bedsheet/gondola
+ name = "gondola bedsheet"
+ desc = "A precious bedsheet made from the hide of a endangered and peculiar critter."
+ icon_state = "sheetgondola"
+ inhand_icon_state = "sheetgondola"
+ dream_messages = list("peace", "comfiness", "a rare critter", "a harmless creature")
+ stack_type = /obj/item/stack/sheet/animalhide/gondola
+ stack_amount = 1
+ ///one of four icon states that represent its mouth
+ var/gondola_mouth
+ ///one of four icon states that represent its eyes
+ var/gondola_eyes
+
+/obj/item/bedsheet/gondola/Initialize(mapload)
+ . = ..()
+ gondola_mouth = "sheetgondola_mouth[rand(1, 4)]"
+ gondola_eyes = "sheetgondola_eyes[rand(1, 4)]"
+ add_overlay(gondola_mouth)
+ add_overlay(gondola_eyes)
+
+/obj/item/bedsheet/gondola/worn_overlays(mutable_appearance/standing, isinhands, icon_file)
+ . = ..()
+ if(!isinhands)
+ . += mutable_appearance(icon_file, gondola_mouth)
+ . += mutable_appearance(icon_file, gondola_eyes)
+
/obj/item/bedsheet/cosmos
name = "cosmic space bedsheet"
desc = "Made from the dreams of those who wonder at the stars."
@@ -347,66 +399,6 @@ LINEN BINS
light_power = 2
light_range = 1.4
-/obj/item/bedsheet/random
- icon_state = "random_bedsheet"
- name = "random bedsheet"
- desc = "If you're reading this description ingame, something has gone wrong! Honk!"
- bedsheet_type = BEDSHEET_ABSTRACT
- item_flags = ABSTRACT
- var/static/list/bedsheet_list
- var/spawn_type = BEDSHEET_SINGLE
-
-/obj/item/bedsheet/random/Initialize(mapload)
- ..()
- if(!LAZYACCESS(bedsheet_list, spawn_type))
- var/list/spawn_list = list()
- var/list/possible_types = typesof(/obj/item/bedsheet)
- for(var/obj/item/bedsheet/sheet as anything in possible_types)
- if(initial(sheet.bedsheet_type) == spawn_type)
- spawn_list += sheet
- LAZYSET(bedsheet_list, spawn_type, spawn_list)
- var/chosen_type = pick(bedsheet_list[spawn_type])
- var/obj/item/bedsheet = new chosen_type(loc)
- bedsheet.dir = dir
- return INITIALIZE_HINT_QDEL
-
-/obj/item/bedsheet/random/double
- icon_state = "random_bedsheet"
- spawn_type = BEDSHEET_DOUBLE
-
-/obj/item/bedsheet/dorms
- icon_state = "random_bedsheet"
- name = "random dorms bedsheet"
- desc = "If you're reading this description ingame, something has gone wrong! Honk!"
- bedsheet_type = BEDSHEET_DOUBLE
- item_flags = ABSTRACT
- slot_flags = null
-
-/obj/item/bedsheet/dorms/Initialize(mapload)
- ..()
- var/type = pick_weight(list("Colors" = 80, "Special" = 20))
- switch(type)
- if("Colors")
- type = pick(list(/obj/item/bedsheet,
- /obj/item/bedsheet/blue,
- /obj/item/bedsheet/green,
- /obj/item/bedsheet/grey,
- /obj/item/bedsheet/orange,
- /obj/item/bedsheet/purple,
- /obj/item/bedsheet/red,
- /obj/item/bedsheet/yellow,
- /obj/item/bedsheet/brown,
- /obj/item/bedsheet/black))
- if("Special")
- type = pick(list(/obj/item/bedsheet/patriot,
- /obj/item/bedsheet/rainbow,
- /obj/item/bedsheet/ian,
- /obj/item/bedsheet/cosmos,
- /obj/item/bedsheet/nanotrasen))
- var/obj/item/bedsheet = new type(loc)
- bedsheet.dir = dir
- return INITIALIZE_HINT_QDEL
-
/obj/item/bedsheet/double
icon_state = "double_sheetwhite"
worn_icon_state = "sheetwhite"
@@ -559,45 +551,16 @@ LINEN BINS
worn_icon_state = "sheetian"
bedsheet_type = BEDSHEET_DOUBLE
+/obj/item/bedsheet/runtime/double
+ icon_state = "double_sheetruntime"
+ worn_icon_state = "sheetruntime"
+ bedsheet_type = BEDSHEET_DOUBLE
+
/obj/item/bedsheet/cosmos/double
icon_state = "double_sheetcosmos"
worn_icon_state = "sheetcosmos"
bedsheet_type = BEDSHEET_DOUBLE
-/obj/item/bedsheet/dorms_double
- icon_state = "random_bedsheet"
- item_flags = ABSTRACT
- bedsheet_type = BEDSHEET_ABSTRACT
-
-/obj/item/bedsheet/dorms_double/Initialize(mapload)
- ..()
- var/type = pick_weight(list("Colors" = 80, "Special" = 20))
- switch(type)
- if("Colors")
- type = pick(list(
- /obj/item/bedsheet/double,
- /obj/item/bedsheet/blue/double,
- /obj/item/bedsheet/green/double,
- /obj/item/bedsheet/grey/double,
- /obj/item/bedsheet/orange/double,
- /obj/item/bedsheet/purple/double,
- /obj/item/bedsheet/red/double,
- /obj/item/bedsheet/yellow/double,
- /obj/item/bedsheet/brown/double,
- /obj/item/bedsheet/black/double,
- ))
- if("Special")
- type = pick(list(
- /obj/item/bedsheet/patriot/double,
- /obj/item/bedsheet/rainbow/double,
- /obj/item/bedsheet/ian/double,
- /obj/item/bedsheet/cosmos/double,
- /obj/item/bedsheet/nanotrasen/double,
- ))
- var/obj/item/bedsheet = new type(loc)
- bedsheet.dir = dir
- return INITIALIZE_HINT_QDEL
-
/obj/structure/bedsheetbin
name = "linen bin"
desc = "It looks rather cosy."
@@ -736,7 +699,3 @@ LINEN BINS
add_fingerprint(user)
return COMPONENT_CANCEL_ATTACK_CHAIN
-
-#undef BEDSHEET_ABSTRACT
-#undef BEDSHEET_SINGLE
-#undef BEDSHEET_DOUBLE
diff --git a/code/game/sound.dm b/code/game/sound.dm
index 3c6a62eb781d3..e575534bdaeed 100644
--- a/code/game/sound.dm
+++ b/code/game/sound.dm
@@ -50,7 +50,7 @@
//allocate a channel if necessary now so its the same for everyone
channel = channel || SSsounds.random_available_channel()
- var/sound/S = sound(get_sfx(soundin))
+ var/sound/S = isdatum(soundin) ? soundin : sound(get_sfx(soundin))
var/maxdistance = SOUND_RANGE + extrarange
var/source_z = turf_source.z
var/list/listeners = SSmobs.clients_by_zlevel[source_z].Copy()
diff --git a/code/modules/antagonists/heretic/heretic_knowledge.dm b/code/modules/antagonists/heretic/heretic_knowledge.dm
index cb9f3b75ccccf..f2cf7b0004771 100644
--- a/code/modules/antagonists/heretic/heretic_knowledge.dm
+++ b/code/modules/antagonists/heretic/heretic_knowledge.dm
@@ -211,7 +211,8 @@
/datum/heretic_knowledge/spell/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
var/datum/action/cooldown/spell/created_spell = created_spell_ref?.resolve()
- created_spell?.Remove(user)
+ if(created_spell?.owner == user)
+ created_spell.Remove(user)
/**
* A knowledge subtype for knowledge that can only
diff --git a/code/modules/antagonists/nightmare/nightmare_equipment.dm b/code/modules/antagonists/nightmare/nightmare_equipment.dm
index 965b95ca6e892..fd80b3ea3ef56 100644
--- a/code/modules/antagonists/nightmare/nightmare_equipment.dm
+++ b/code/modules/antagonists/nightmare/nightmare_equipment.dm
@@ -64,7 +64,7 @@
remove_crit()
/obj/item/light_eater/proc/prepare_crit_timer()
- crit_timer = addtimer(CALLBACK(src, PROC_REF(add_crit)), 15 SECONDS, TIMER_DELETE_ME | TIMER_STOPPABLE)
+ crit_timer = addtimer(CALLBACK(src, PROC_REF(add_crit)), 7 SECONDS, TIMER_DELETE_ME | TIMER_STOPPABLE)
/obj/item/light_eater/proc/stop_crit_timer()
deltimer(crit_timer)
diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/mobility.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/mobility.dm
index d5112a59611ba..6a8f322a3a5f4 100644
--- a/code/modules/antagonists/wizard/equipment/spellbook_entries/mobility.dm
+++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/mobility.dm
@@ -50,3 +50,10 @@
item_path = /obj/item/gun/magic/staff/door
cost = 1
category = "Mobility"
+
+/datum/spellbook_entry/item/teleport_rod
+ name = /obj/item/teleport_rod::name
+ desc = /obj/item/teleport_rod::desc
+ item_path = /obj/item/teleport_rod
+ cost = 2 // Puts it at 3 cost if you go for safety instant summons, but teleporting anywhere on screen is pretty good.
+ category = "Mobility"
diff --git a/code/modules/antagonists/wizard/equipment/teleport_rod.dm b/code/modules/antagonists/wizard/equipment/teleport_rod.dm
new file mode 100644
index 0000000000000..3c41cae525783
--- /dev/null
+++ b/code/modules/antagonists/wizard/equipment/teleport_rod.dm
@@ -0,0 +1,246 @@
+/// Totally NOT a Rod of Discord
+/// Teleports you to where you click!
+/obj/item/teleport_rod
+ name = "Telegram Scepter"
+ desc = "A magical rod that teleports you to the location you point it. \
+ Using it puts you in a state of flux, removing some of your reagents and \
+ causing you to take damage from further uses until you stabilize once more."
+ icon_state = "tele_wand_er"
+ inhand_icon_state = "tele_wand_er"
+ icon = 'icons/obj/weapons/guns/magic.dmi'
+ lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi'
+ resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF | UNACIDABLE
+ item_flags = NOBLUDGEON
+ light_system = OVERLAY_LIGHT
+ light_color = COLOR_FADED_PINK
+ light_power = 1
+ light_range = 2
+ light_on = TRUE
+ /// Whether we apply the teleport flux debuff, damaging people who teleport
+ var/apply_debuffs = TRUE
+ /// Max range at which we can teleport, because it operates in view TECHNICALLY can click very very far
+ var/max_tp_range = 8
+
+/obj/item/teleport_rod/Initialize(mapload)
+ . = ..()
+ particles = new /particles/teleport_flux/small()
+
+// Admin only version which just teleports you, so spam it all you want
+/obj/item/teleport_rod/admin
+ name = "Harmonious " + parent_type::name
+ desc = "A magical rod that teleports you anywhere, no questions asked."
+ apply_debuffs = FALSE
+ max_tp_range = INFINITY
+
+/obj/item/teleport_rod/equipped(mob/living/user, slot, initial)
+ . = ..()
+ if(!isliving(user))
+ return
+ if(HAS_MIND_TRAIT(user, TRAIT_MAGICALLY_GIFTED))
+ return
+ if(!(slot & ITEM_SLOT_HANDS))
+ return
+ if(!apply_debuffs)
+ return
+ user.apply_status_effect(/datum/status_effect/teleport_flux/perma)
+
+/obj/item/teleport_rod/dropped(mob/living/user, silent)
+ . = ..()
+ if(!isliving(user))
+ return
+ if(HAS_MIND_TRAIT(user, TRAIT_MAGICALLY_GIFTED))
+ return
+
+ var/datum/status_effect/teleport_flux/perma/permaflux = user.has_status_effect(/datum/status_effect/teleport_flux/perma)
+ permaflux?.delayed_remove(src)
+
+/obj/item/teleport_rod/afterattack(atom/target, mob/living/user, proximity_flag, click_parameters)
+ . = ..()
+ if(!isliving(user))
+ return
+ if(proximity_flag) // assuming you don't want to teleport 1 tile away
+ return
+
+ . |= AFTERATTACK_PROCESSED_ITEM
+
+ var/turf/start_turf = get_turf(user)
+ var/turf/target_turf = get_turf(target)
+ if(get_dist(start_turf, target_turf) > max_tp_range)
+ user.balloon_alert(user, "too far!")
+ return
+
+ if(!(target_turf in view(user, user.client?.view || world.view)))
+ user.balloon_alert(user, "out of view!")
+ return
+
+ if(target_turf.is_blocked_turf(exclude_mobs = TRUE, source_atom = user))
+ user.balloon_alert(user, "obstructed!")
+ return
+
+ var/tp_result = do_teleport(
+ teleatom = user,
+ destination = target_turf,
+ precision = (HAS_MIND_TRAIT(user, TRAIT_MAGICALLY_GIFTED) || !apply_debuffs) ? 0 : 2,
+ no_effects = TRUE,
+ channel = TELEPORT_CHANNEL_MAGIC,
+ )
+
+ if(!tp_result)
+ user.balloon_alert(user, "teleport failed!")
+ return
+
+ var/sound/teleport_sound = sound('sound/magic/summonitems_generic.ogg')
+ teleport_sound.pitch = 0.5
+ // Handle our own pizzaz rather than doing it in do_teleport
+ new /obj/effect/temp_visual/teleport_flux(start_turf, user.dir)
+ new /obj/effect/temp_visual/teleport_flux(get_turf(user), user.dir)
+ playsound(start_turf, teleport_sound, 90, extrarange = MEDIUM_RANGE_SOUND_EXTRARANGE)
+ playsound(user, teleport_sound, 90, extrarange = MEDIUM_RANGE_SOUND_EXTRARANGE)
+ // Some extra delay to prevent accidental double clicks
+ user.changeNext_move(CLICK_CD_SLOW * 1.2)
+
+ if(!apply_debuffs)
+ return
+
+ // Teleporting leaves some of your reagents behind!
+ // (Primarily a way to prevent cheese with damage healing chem mixes,
+ // but also serves as a counter-counter to stuff like mute toxin.)
+ var/obj/item/organ/user_stomach = user.get_organ_slot(ORGAN_SLOT_STOMACH)
+ user.reagents?.remove_all_direct(0.33)
+ user_stomach?.reagents?.remove_all_direct(0.33)
+ if(user.has_status_effect(/datum/status_effect/teleport_flux/perma))
+ return
+
+ if(user.has_status_effect(/datum/status_effect/teleport_flux))
+ // The status effect handles the damage, but we'll add a special pop up for rod usage specifically
+ user.balloon_alert(user, "too soon!")
+
+ user.apply_status_effect(/datum/status_effect/teleport_flux)
+
+/// Temp visual displayed on both sides of a teleport rod teleport
+/obj/effect/temp_visual/teleport_flux
+ icon_state = "blank_white"
+ color = COLOR_MAGENTA
+ alpha = 255
+ duration = 2 SECONDS
+ light_color = COLOR_MAGENTA
+ light_power = 2
+ light_range = 1
+ light_on = TRUE
+ randomdir = FALSE
+
+/obj/effect/temp_visual/teleport_flux/Initialize(mapload, copy_dir = SOUTH)
+ . = ..()
+ setDir(copy_dir)
+ particles = new /particles/teleport_flux()
+ apply_wibbly_filters(src)
+ animate(src, alpha = 0, time = duration, flags = ANIMATION_PARALLEL)
+
+/// Status effect applied to users of a Teleport Rod, damages them when they teleport
+/datum/status_effect/teleport_flux
+ id = "teleport_flux"
+ status_type = STATUS_EFFECT_REFRESH
+ duration = 6 SECONDS
+ alert_type = /atom/movable/screen/alert/status_effect/teleport_flux
+ remove_on_fullheal = TRUE // staff of healing ~synergy~
+
+ /// Amount of damage to deal when teleporting in flux
+ var/tp_damage = 15
+ /// Damage type to deal when teleporting in flux
+ var/tp_damage_type = BRUTE
+
+/datum/status_effect/teleport_flux/on_apply()
+ RegisterSignal(owner, COMSIG_MOVABLE_POST_TELEPORT, PROC_REF(teleported))
+ return TRUE
+
+/datum/status_effect/teleport_flux/on_remove()
+ UnregisterSignal(owner, COMSIG_MOVABLE_POST_TELEPORT)
+
+/datum/status_effect/teleport_flux/proc/teleported(mob/living/source, turf/destination, channel)
+ SIGNAL_HANDLER
+
+ if(channel != TELEPORT_CHANNEL_MAGIC)
+ return
+
+ owner.apply_damage(
+ damage = tp_damage,
+ damagetype = tp_damage_type,
+ spread_damage = TRUE,
+ forced = TRUE,
+ )
+ log_combat(owner, owner, "teleported too soon")
+
+/datum/status_effect/teleport_flux/update_particles()
+ if(isnull(particle_effect))
+ particle_effect = new(owner, /particles/teleport_flux)
+
+ particle_effect.alpha = 200
+ var/original_duration = initial(duration)
+ if(original_duration == -1)
+ return
+ animate(particle_effect, alpha = 50, time = original_duration)
+
+/datum/status_effect/teleport_flux/refresh(effect, ...)
+ . = ..()
+ update_particles()
+
+/datum/status_effect/teleport_flux/perma
+ id = "perma_teleport_flux"
+ status_type = STATUS_EFFECT_REPLACE
+ duration = -1
+ alert_type = /atom/movable/screen/alert/status_effect/teleport_flux/perma
+ remove_on_fullheal = FALSE
+
+/datum/status_effect/teleport_flux/perma/on_apply()
+ . = ..()
+ RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_MAGICALLY_GIFTED), PROC_REF(gained_gift))
+
+/datum/status_effect/teleport_flux/perma/on_remove()
+ . = ..()
+ UnregisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_MAGICALLY_GIFTED))
+
+/datum/status_effect/teleport_flux/perma/proc/gained_gift(mob/living/source, trait)
+ SIGNAL_HANDLER
+
+ delayed_remove()
+
+/// Used to fade out the effect and remove it after a delay
+/// This cannot be interrupted, but if a new permaflux effect is applied,
+/// this one will be deleted instantly anyways, making it moot
+/datum/status_effect/teleport_flux/perma/proc/delayed_remove()
+ var/del_duration = /datum/status_effect/teleport_flux::duration
+ QDEL_IN(src, del_duration)
+ animate(particle_effect, alpha = 50, del_duration)
+
+/// Alert for the Teleport Flux status effect
+/atom/movable/screen/alert/status_effect/teleport_flux
+ name = "Teleport Flux"
+ desc = "Your body exists in a state of flux, making further teleportation dangerous."
+ icon_state = "flux"
+
+/atom/movable/screen/alert/status_effect/teleport_flux/perma
+ name = "Permanent " + parent_type::name
+ desc = "Your lack of magical talent has left you in a state of flux, making further teleportation dangerous."
+
+/// Particles for Teleport Flux and other similar effects
+/particles/teleport_flux
+ icon = 'icons/effects/particles/echo.dmi'
+ icon_state = list("echo1" = 3, "echo2" = 1, "echo3" = 1)
+ width = 40
+ height = 80
+ count = 1000
+ spawning = 3
+ lifespan = 1 SECONDS
+ fade = 1 SECONDS
+ friction = 0.5
+ position = generator(GEN_SPHERE, 12, 12, NORMAL_RAND)
+ drift = generator(GEN_VECTOR, list(-1, 1), list(1, 1), NORMAL_RAND)
+ color = COLOR_MAGENTA
+
+/particles/teleport_flux/small
+ spawning = 1.5
+ scale = 0.75
+ lifespan = 0.5 SECONDS
+ position = generator(GEN_SPHERE, 4, 12, NORMAL_RAND)
+ drift = generator(GEN_VECTOR, list(-1, 1), list(1, 2), NORMAL_RAND)
diff --git a/code/modules/asset_cache/assets/achievements.dm b/code/modules/asset_cache/assets/achievements.dm
index 1ba7b91af92b9..91f2d75b6d581 100644
--- a/code/modules/asset_cache/assets/achievements.dm
+++ b/code/modules/asset_cache/assets/achievements.dm
@@ -1,5 +1,11 @@
/datum/asset/spritesheet/simple/achievements
- name ="achievements"
+ name = "achievements"
/datum/asset/spritesheet/simple/achievements/create_spritesheets()
- InsertAll("", ACHIEVEMENTS_SET)
+ InsertAll("achievement", ACHIEVEMENTS_SET)
+ // catch achievements which are pulling icons from another file
+ for(var/datum/award/other_award as anything in subtypesof(/datum/award))
+ var/icon = initial(other_award.icon)
+ if (icon != ACHIEVEMENTS_SET)
+ var/icon_state = initial(other_award.icon_state)
+ Insert("achievement-[icon_state]", icon, icon_state=icon_state)
diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm
index 0429b92377e0d..ce70c93a558bb 100644
--- a/code/modules/atmospherics/machinery/atmosmachinery.dm
+++ b/code/modules/atmospherics/machinery/atmosmachinery.dm
@@ -280,8 +280,8 @@
* * given_layer - the piping_layer we are checking
*/
/obj/machinery/atmospherics/proc/connection_check(obj/machinery/atmospherics/target, given_layer)
- //if target is not multiz then we have to check if the target & src connect in the same direction
- if(!istype(target, /obj/machinery/atmospherics/pipe/multiz) && !((initialize_directions & get_dir(src, target)) && (target.initialize_directions & get_dir(target, src))))
+ //check if the target & src connect in the same direction
+ if(!((initialize_directions & get_dir(src, target)) && (target.initialize_directions & get_dir(target, src))))
return FALSE
//both target & src can't be connected either way
diff --git a/code/modules/atmospherics/machinery/components/components_base.dm b/code/modules/atmospherics/machinery/components/components_base.dm
index 931a952658c40..130c144d42439 100644
--- a/code/modules/atmospherics/machinery/components/components_base.dm
+++ b/code/modules/atmospherics/machinery/components/components_base.dm
@@ -268,6 +268,10 @@
if(!.)
return FALSE
set_init_directions()
+ reconnect_nodes()
+ return TRUE
+
+/obj/machinery/atmospherics/components/proc/reconnect_nodes()
for(var/i in 1 to device_type)
var/obj/machinery/atmospherics/node = nodes[i]
if(node)
@@ -285,7 +289,6 @@
node.add_member(src)
update_parents()
SSair.add_to_rebuild_queue(src)
- return TRUE
/**
* Disconnects all nodes from ourselves, remove us from the node's nodes.
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm b/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm
index 5067dcddae2bf..5c894274619ad 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm
@@ -129,7 +129,7 @@
if(!initial(icon))
return
var/mutable_appearance/thermo_overlay = new(initial(icon))
- . += get_pipe_image(thermo_overlay, "pipe", dir, COLOR_LIME, piping_layer)
+ . += get_pipe_image(thermo_overlay, "pipe", dir, pipe_color, piping_layer)
/obj/machinery/atmospherics/components/unary/thermomachine/examine(mob/user)
. = ..()
@@ -222,6 +222,8 @@
return ITEM_INTERACT_SUCCESS
piping_layer = (piping_layer >= PIPING_LAYER_MAX) ? PIPING_LAYER_MIN : (piping_layer + 1)
to_chat(user, span_notice("You change the circuitboard to layer [piping_layer]."))
+ if(anchored)
+ reconnect_nodes()
update_appearance()
return ITEM_INTERACT_SUCCESS
@@ -233,6 +235,8 @@
set_pipe_color(GLOB.pipe_paint_colors[GLOB.pipe_paint_colors[color_index]])
visible_message(span_notice("[user] set [src]'s pipe color to [GLOB.pipe_color_name[pipe_color]]."), ignored_mobs = user)
to_chat(user, span_notice("You set [src]'s pipe color to [GLOB.pipe_color_name[pipe_color]]."))
+ if(anchored)
+ reconnect_nodes()
update_appearance()
return ITEM_INTERACT_SUCCESS
diff --git a/code/modules/atmospherics/machinery/pipes/multiz.dm b/code/modules/atmospherics/machinery/pipes/multiz.dm
index 7e14b8a98063e..ebe295313161e 100644
--- a/code/modules/atmospherics/machinery/pipes/multiz.dm
+++ b/code/modules/atmospherics/machinery/pipes/multiz.dm
@@ -9,7 +9,7 @@
initialize_directions = SOUTH
layer = HIGH_OBJ_LAYER
- device_type = UNARY
+ device_type = TRINARY
paintable = FALSE
construction_type = /obj/item/pipe/directional
@@ -54,8 +54,8 @@
for(var/obj/machinery/atmospherics/pipe/multiz/above in GET_TURF_ABOVE(local_turf))
if(!is_connectable(above, piping_layer))
continue
- nodes += above
- above.nodes += src //Two way travel :)
+ nodes[2] = above
+ above.nodes[3] = src //Two way travel :)
for(var/obj/machinery/atmospherics/pipe/multiz/below in GET_TURF_BELOW(local_turf))
if(!is_connectable(below, piping_layer))
continue
diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm
index 15a8e4454fc1e..45ccda8b92bd1 100644
--- a/code/modules/client/client_defines.dm
+++ b/code/modules/client/client_defines.dm
@@ -240,6 +240,9 @@
var/list/keys_held = list()
/// A buffer for combinations such of modifiers + keys (ex: CtrlD, AltE, ShiftT). Format: `"key"` -> `"combo"` (ex: `"D"` -> `"CtrlD"`)
var/list/key_combos_held = list()
+ /// The direction we WANT to move, based off our keybinds
+ /// Will be udpated to be the actual direction later on
+ var/intended_direction = NONE
/*
** These next two vars are to apply movement for keypresses and releases made while move delayed.
** Because discarding that input makes the game less responsive.
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index 3c0b5aa1db3b0..5de1341358230 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -1012,6 +1012,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
winset(src, "default-[REF(key)]", "parent=default;name=[key];command=[asay]")
else
winset(src, "default-[REF(key)]", "parent=default;name=[key];command=")
+ calculate_move_dir()
/client/proc/change_view(new_size)
if (isnull(new_size))
diff --git a/code/modules/keybindings/bindings_atom.dm b/code/modules/keybindings/bindings_atom.dm
index 6dadcd5768ee3..e9e38489a4df3 100644
--- a/code/modules/keybindings/bindings_atom.dm
+++ b/code/modules/keybindings/bindings_atom.dm
@@ -2,12 +2,18 @@
// Only way to do that is to tie the behavior into the focus's keyLoop().
/atom/movable/keyLoop(client/user)
- var/movement_dir = NONE
- for(var/_key in user?.keys_held)
- movement_dir = movement_dir | user.movement_keys[_key]
- if(user?.next_move_dir_add)
- movement_dir |= user.next_move_dir_add
- if(user?.next_move_dir_sub)
+ // Clients don't go null randomly. They do go null unexpectedly though, when they're poked in particular ways
+ // keyLoop is called by a for loop over mobs. We're guarenteed that all the mobs have clients at the START
+ // But the move of one mob might poke the client of another, so we do this
+ if(!user)
+ return FALSE
+ var/movement_dir = user.intended_direction | user.next_move_dir_add
+ // If we're not movin anywhere, we aren't movin anywhere
+ // Safe because nothing adds to movement_dir after this moment
+ if(!movement_dir)
+ return FALSE
+
+ if(user.next_move_dir_sub)
movement_dir &= ~user.next_move_dir_sub
// Sanity checks in case you hold left and right and up to make sure you only go up
if((movement_dir & NORTH) && (movement_dir & SOUTH))
@@ -15,14 +21,21 @@
if((movement_dir & EAST) && (movement_dir & WEST))
movement_dir &= ~(EAST|WEST)
- if(user && movement_dir) //If we're not moving, don't compensate, as byond will auto-fill dir otherwise
+ if(user.dir != NORTH && movement_dir) //If we're not moving, don't compensate, as byond will auto-fill dir otherwise
movement_dir = turn(movement_dir, -dir2angle(user.dir)) //By doing this we ensure that our input direction is offset by the client (camera) direction
//turn without moving while using the movement lock key, unless something wants to ignore it and move anyway
- if(user?.movement_locked && !(SEND_SIGNAL(src, COMSIG_MOVABLE_KEYBIND_FACE_DIR, movement_dir) & COMSIG_IGNORE_MOVEMENT_LOCK))
+ if(user.movement_locked && !(SEND_SIGNAL(src, COMSIG_MOVABLE_KEYBIND_FACE_DIR, movement_dir) & COMSIG_IGNORE_MOVEMENT_LOCK))
keybind_face_direction(movement_dir)
- else
- user?.Move(get_step(src, movement_dir), movement_dir)
+ // Null check cause of the signal above
+ else if(user)
+ user.Move(get_step(src, movement_dir), movement_dir)
return !!movement_dir //true if there was actually any player input
return FALSE
+
+/client/proc/calculate_move_dir()
+ var/movement_dir = NONE
+ for(var/_key in keys_held)
+ movement_dir |= movement_keys[_key]
+ intended_direction = movement_dir
diff --git a/code/modules/keybindings/bindings_client.dm b/code/modules/keybindings/bindings_client.dm
index 0aa0fd6952ed2..0a8cc20b38fe5 100644
--- a/code/modules/keybindings/bindings_client.dm
+++ b/code/modules/keybindings/bindings_client.dm
@@ -47,9 +47,10 @@
//the time a key was pressed isn't actually used anywhere (as of 2019-9-10) but this allows easier access usage/checking
keys_held[_key] = world.time
- if(!movement_locked)
- var/movement = movement_keys[_key]
- if(!(next_move_dir_sub & movement))
+ var/movement = movement_keys[_key]
+ if(movement)
+ calculate_move_dir()
+ if(!movement_locked && !(next_move_dir_sub & movement))
next_move_dir_add |= movement
// Client-level keybindings are ones anyone should be able to do at any time
@@ -93,9 +94,10 @@
keys_held -= _key
- if(!movement_locked)
- var/movement = movement_keys[_key]
- if(!(next_move_dir_add & movement))
+ var/movement = movement_keys[_key]
+ if(movement)
+ calculate_move_dir()
+ if(!movement_locked && !(next_move_dir_add & movement))
next_move_dir_sub |= movement
// We don't do full key for release, because for mod keys you
diff --git a/code/modules/mining/satchel_ore_box.dm b/code/modules/mining/satchel_ore_box.dm
index 7a82b9483a042..3b2a5ce054c1d 100644
--- a/code/modules/mining/satchel_ore_box.dm
+++ b/code/modules/mining/satchel_ore_box.dm
@@ -5,58 +5,68 @@
icon = 'icons/obj/mining.dmi'
icon_state = "orebox"
name = "ore box"
- desc = "A heavy wooden box, which can be filled with a lot of ores."
+ desc = "A heavy wooden box, which can be filled with a lot of ores or boulders"
density = TRUE
- pressure_resistance = 5*ONE_ATMOSPHERE
-
-/obj/structure/ore_box/attackby(obj/item/W, mob/user, params)
- if (istype(W, /obj/item/stack/ore) || istype(W, /obj/item/boulder))
- if(!user.transferItemToLoc(W, src))
- return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
- else if(W.atom_storage)
- W.atom_storage.remove_type(/obj/item/stack/ore, src, INFINITY, TRUE, FALSE, user, null)
- to_chat(user, span_notice("You empty the ore in [W] into \the [src]."))
- else
- return ..()
+ pressure_resistance = 5 * ONE_ATMOSPHERE
-/obj/structure/ore_box/crowbar_act(mob/living/user, obj/item/I)
- if(I.use_tool(src, user, 50, volume=50))
- user.visible_message(span_notice("[user] pries \the [src] apart."),
- span_notice("You pry apart \the [src]."),
- span_hear("You hear splitting wood."))
- deconstruct(TRUE, user)
- return TRUE
-
-/obj/structure/ore_box/examine(mob/living/user)
- if(Adjacent(user) && istype(user))
- ui_interact(user)
+/obj/structure/ore_box/Initialize(mapload)
. = ..()
+ register_context()
-/obj/structure/ore_box/attack_hand(mob/user, list/modifiers)
- . = ..()
- if(.)
+///Dumps all contents of this ore box on the turf
+/obj/structure/ore_box/proc/dump_box_contents()
+ var/drop = drop_location()
+ for(var/obj/item/weapon in src)
+ weapon.forceMove(drop)
+
+/obj/structure/ore_box/deconstruct(disassembled = TRUE)
+ new /obj/item/stack/sheet/mineral/wood(loc, 4)
+
+ dump_box_contents()
+
+ return ..()
+
+/obj/structure/ore_box/add_context(atom/source, list/context, obj/item/held_item, mob/user)
+ . = NONE
+ if(isnull(held_item))
return
- if(Adjacent(user))
- ui_interact(user)
-/obj/structure/ore_box/attack_robot(mob/user)
- if(Adjacent(user))
+ if(held_item.tool_behaviour == TOOL_CROWBAR)
+ context[SCREENTIP_CONTEXT_LMB] = "Deconstruct"
+ return CONTEXTUAL_SCREENTIP_SET
+ else if(istype(held_item, /obj/item/stack/ore) || istype(held_item, /obj/item/boulder))
+ context[SCREENTIP_CONTEXT_LMB] = "Insert Item"
+ return CONTEXTUAL_SCREENTIP_SET
+ else if(held_item.atom_storage)
+ context[SCREENTIP_CONTEXT_LMB] = "Transfer Contents"
+ return CONTEXTUAL_SCREENTIP_SET
+
+
+/obj/structure/ore_box/examine(mob/living/user)
+ . = ..()
+ if(in_range(src, user) || isobserver(user))
+ . += span_notice("Can be [EXAMINE_HINT("pried")] apart.")
ui_interact(user)
-/obj/structure/ore_box/proc/dump_box_contents()
- var/drop = drop_location()
- var/turf/our_turf = get_turf(src)
- for(var/obj/item/O in src)
- if(QDELETED(O))
- continue
- if(QDELETED(src))
- break
- O.forceMove(drop)
- SET_PLANE(O, PLANE_TO_TRUE(O.plane), our_turf)
- if(TICK_CHECK)
- stoplag()
- our_turf = get_turf(src)
- drop = drop_location()
+/obj/structure/ore_box/crowbar_act(mob/living/user, obj/item/I)
+ . = ITEM_INTERACT_BLOCKING
+ if(I.use_tool(src, user, 50, volume = 50))
+ user.visible_message(span_notice("[user] pries \the [src] apart."),
+ span_notice("You pry apart \the [src]."),
+ span_hear("You hear splitting wood."))
+ deconstruct(TRUE)
+ return ITEM_INTERACT_SUCCESS
+
+/obj/structure/ore_box/attackby(obj/item/weapon, mob/user, params)
+ if(istype(weapon, /obj/item/stack/ore) || istype(weapon, /obj/item/boulder))
+ user.transferItemToLoc(weapon, src)
+ return TRUE
+ else if(weapon.atom_storage)
+ weapon.atom_storage.remove_type(/obj/item/stack/ore, src, INFINITY, TRUE, FALSE, user, null)
+ to_chat(user, span_notice("You empty the ore in [weapon] into \the [src]."))
+ return TRUE
+ else
+ return ..()
/obj/structure/ore_box/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
@@ -65,43 +75,36 @@
ui.open()
/obj/structure/ore_box/ui_data()
- var/item_contents = list()
- var/boulder_count = 0
+ var/list/materials = list()
+ var/name
+ var/amount
for(var/obj/item/stack/ore/potental_ore as anything in contents)
if(istype(potental_ore, /obj/item/stack/ore))
- item_contents[potental_ore.type] += potental_ore.amount
+ name = potental_ore.name
+ amount = potental_ore.amount
else
- boulder_count++
+ name = "Boulders"
+ amount = 1
- var/data = list()
+ var/item_found = FALSE
+ for(var/list/item as anything in materials)
+ if(item["name"] == name)
+ item_found = TRUE
+ item["amount"] += amount
+ break
+ if(!item_found)
+ materials += list(list("name" = name, "amount" = amount))
- data["materials"] = list()
-
- for(var/obj/item/stone as anything in item_contents)
- if(ispath(stone, /obj/item/stack/ore))
- var/obj/item/stack/ore/found_ore = stone
- var/name = initial(found_ore.name)
- data["materials"] += list(list("name" = name, "amount" = item_contents[stone], "id" = type))
- data["boulders"] = boulder_count
- return data
+ return list("materials" = materials)
/obj/structure/ore_box/ui_act(action, params)
. = ..()
if(.)
return
- if(!Adjacent(usr))
- return
- switch(action)
- if("removeall")
- dump_box_contents()
- to_chat(usr, span_notice("You open the release hatch on the box.."))
-
-/obj/structure/ore_box/deconstruct(disassembled = TRUE, mob/user)
- var/obj/item/stack/sheet/mineral/wood/WD = new (loc, 4)
- if(user && !QDELETED(WD))
- WD.add_fingerprint(user)
- dump_box_contents()
- qdel(src)
+
+ if(action == "removeall")
+ dump_box_contents()
+ return TRUE
/// Special override for notify_contents = FALSE.
/obj/structure/ore_box/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents = FALSE)
diff --git a/code/modules/mob/living/basic/blob_minions/blob_zombie.dm b/code/modules/mob/living/basic/blob_minions/blob_zombie.dm
index c9bf3b7346a98..3cbce54cf29db 100644
--- a/code/modules/mob/living/basic/blob_minions/blob_zombie.dm
+++ b/code/modules/mob/living/basic/blob_minions/blob_zombie.dm
@@ -51,13 +51,13 @@
. = ..()
death()
-/mob/living/basic/blob_minion/zombie/update_overlays()
- . = ..()
+//Sets up our appearance
+/mob/living/basic/blob_minion/zombie/proc/set_up_zombie_appearance()
copy_overlays(corpse, TRUE)
var/mutable_appearance/blob_head_overlay = mutable_appearance('icons/mob/nonhuman-player/blob.dmi', "blob_head")
blob_head_overlay.color = LAZYACCESS(atom_colours, FIXED_COLOUR_PRIORITY) || COLOR_WHITE
color = initial(color) // reversing what our component did lol, but we needed the value for the overlay
- . += blob_head_overlay
+ overlays += blob_head_overlay
/// Create an explosion of spores on death
/mob/living/basic/blob_minion/zombie/proc/death_burst()
@@ -73,6 +73,7 @@
new_corpse.forceMove(src)
corpse = new_corpse
update_appearance(UPDATE_ICON)
+ set_up_zombie_appearance()
RegisterSignal(corpse, COMSIG_LIVING_REVIVE, PROC_REF(on_corpse_revived))
/// Dynamic changeling reentry
diff --git a/code/modules/mob/living/basic/drone/_drone.dm b/code/modules/mob/living/basic/drone/_drone.dm
index 76f09318b7acb..9298083c67c21 100644
--- a/code/modules/mob/living/basic/drone/_drone.dm
+++ b/code/modules/mob/living/basic/drone/_drone.dm
@@ -161,6 +161,12 @@
/obj/item/clothing/mask,
/obj/item/storage/box/lights,
/obj/item/lightreplacer,
+ /obj/item/construction/rcd,
+ /obj/item/rcd_ammo,
+ /obj/item/rcd_upgrade,
+ /obj/item/storage/part_replacer,
+ /obj/item/soap,
+ /obj/item/holosign_creator,
)
/// machines whitelisted from being shy with
var/list/shy_machine_whitelist = list(
diff --git a/code/modules/mob/living/basic/pets/cat/cat.dm b/code/modules/mob/living/basic/pets/cat/cat.dm
index 207599a1d164a..dd8a588e91502 100644
--- a/code/modules/mob/living/basic/pets/cat/cat.dm
+++ b/code/modules/mob/living/basic/pets/cat/cat.dm
@@ -134,6 +134,7 @@
icon_state = "spacecat"
icon_living = "spacecat"
icon_dead = "spacecat_dead"
+ unsuitable_atmos_damage = 0
minimum_survivable_temperature = TCMB
maximum_survivable_temperature = T0C + 40
held_state = "spacecat"
diff --git a/code/modules/mob/living/carbon/human/emote.dm b/code/modules/mob/living/carbon/human/emote.dm
index a7be681a22e2f..b205eb2e2e217 100644
--- a/code/modules/mob/living/carbon/human/emote.dm
+++ b/code/modules/mob/living/carbon/human/emote.dm
@@ -81,10 +81,14 @@
only_forced_audio = TRUE
vary = TRUE
+/datum/emote/carbon/human/scream/run_emote(mob/user, params, type_override, intentional = FALSE)
+ if(!intentional && HAS_TRAIT(user, TRAIT_ANALGESIA))
+ return
+ return ..()
+
/datum/emote/living/carbon/human/scream/get_sound(mob/living/carbon/human/user)
if(!istype(user))
return
-
return user.dna.species.get_scream_sound(user)
/datum/emote/living/carbon/human/scream/screech //If a human tries to screech it'll just scream.
diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm
index db8302b49dfa9..5c13e489395b1 100644
--- a/code/modules/mob/living/emote.dm
+++ b/code/modules/mob/living/emote.dm
@@ -342,6 +342,11 @@
emote_type = EMOTE_VISIBLE | EMOTE_AUDIBLE
mob_type_blacklist_typecache = list(/mob/living/carbon/human) //Humans get specialized scream.
+/datum/emote/living/scream/run_emote(mob/user, params, type_override, intentional = FALSE)
+ if(!intentional && HAS_TRAIT(user, TRAIT_ANALGESIA))
+ return
+ return ..()
+
/datum/emote/living/scream/select_message_type(mob/user, message, intentional)
. = ..()
if(!intentional && isanimal_or_basicmob(user))
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index 23de5d9417f5d..041d964f5dc50 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -77,7 +77,7 @@
var/mob/camera/ai_eye/eyeobj
var/sprint = 10
- var/cooldown = 0
+ var/last_moved = 0
var/acceleration = TRUE
var/obj/structure/ai_core/deactivated/linked_core //For exosuit control
@@ -191,7 +191,8 @@
builtInCamera.network = list("ss13")
ai_tracking_tool = new(src)
- RegisterSignal(src, COMSIG_TRACKABLE_TRACKING_TARGET, PROC_REF(on_track_target))
+ RegisterSignal(ai_tracking_tool, COMSIG_TRACKABLE_TRACKING_TARGET, PROC_REF(on_track_target))
+ RegisterSignal(ai_tracking_tool, COMSIG_TRACKABLE_GLIDE_CHANGED, PROC_REF(tracked_glidesize_changed))
add_traits(list(TRAIT_PULL_BLOCKED, TRAIT_HANDS_BLOCKED), ROUNDSTART_TRAIT)
@@ -211,8 +212,7 @@
switch(_key)
if("`", "0")
if(cam_prev)
- if(ai_tracking_tool.tracking)
- ai_tracking_tool.set_tracking(FALSE)
+ ai_tracking_tool.reset_tracking()
eyeobj.setLoc(cam_prev)
return
if("1", "2", "3", "4", "5", "6", "7", "8", "9")
@@ -223,8 +223,7 @@
return
if(cam_hotkeys[_key]) //if this is false, no hotkey for this slot exists.
cam_prev = eyeobj.loc
- if(ai_tracking_tool.tracking)
- ai_tracking_tool.set_tracking(FALSE)
+ ai_tracking_tool.reset_tracking()
eyeobj.setLoc(cam_hotkeys[_key])
return
return ..()
@@ -250,7 +249,6 @@
if(ai_voicechanger)
ai_voicechanger.owner = null
ai_voicechanger = null
- UnregisterSignal(src, COMSIG_TRACKABLE_TRACKING_TARGET)
return ..()
/// Removes all malfunction-related abilities from the AI
@@ -401,7 +399,7 @@
set name = "track"
set hidden = TRUE //Don't display it on the verb lists. This verb exists purely so you can type "track Oldman Robustin" and follow his ass
- ai_tracking_tool.set_tracked_mob(src)
+ ai_tracking_tool.track_input(src)
///Called when an AI finds their tracking target.
/mob/living/silicon/ai/proc/on_track_target(datum/trackable/source, mob/living/target)
@@ -411,6 +409,12 @@
else
view_core()
+/// Keeps our rate of gliding in step with the mob we're following
+/mob/living/silicon/ai/proc/tracked_glidesize_changed(datum/trackable/source, mob/living/target, new_glide_size)
+ SIGNAL_HANDLER
+ if(eyeobj)
+ eyeobj.glide_size = new_glide_size
+
/mob/living/silicon/ai/verb/toggle_anchor()
set category = "AI Commands"
set name = "Toggle Floor Bolts"
@@ -524,7 +528,7 @@
else
to_chat(src, span_notice("Unable to project to the holopad."))
if(href_list["track"])
- ai_tracking_tool.set_tracked_mob(src, href_list["track"])
+ ai_tracking_tool.track_name(src, href_list["track"])
return
if (href_list["ai_take_control"]) //Mech domination
var/obj/vehicle/sealed/mecha/M = locate(href_list["ai_take_control"]) in GLOB.mechas_list
@@ -567,8 +571,7 @@
view_core()
return
- if(ai_tracking_tool.tracking)
- ai_tracking_tool.set_tracking(FALSE)
+ ai_tracking_tool.reset_tracking()
// ok, we're alive, camera is good and in our network...
eyeobj.setLoc(get_turf(C))
@@ -642,8 +645,7 @@
set category = "AI Commands"
set name = "Jump To Network"
unset_machine()
- if(ai_tracking_tool.tracking)
- ai_tracking_tool.set_tracking(FALSE)
+ ai_tracking_tool.reset_tracking()
var/cameralist[0]
if(incapacitated())
diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm
index 416bbb19912e8..e8c1919b020f9 100644
--- a/code/modules/mob/living/silicon/ai/freelook/eye.dm
+++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm
@@ -154,35 +154,44 @@
return
var/mob/living/silicon/ai/AI = usr
if(AI.eyeobj && (AI.multicam_on || (AI.client.eye == AI.eyeobj)) && (AI.eyeobj.z == z))
- if(AI.ai_tracking_tool.tracking)
- AI.ai_tracking_tool.set_tracking(FALSE)
+ AI.ai_tracking_tool.reset_tracking()
if (isturf(loc) || isturf(src))
AI.eyeobj.setLoc(src)
// This will move the AIEye. It will also cause lights near the eye to light up, if toggled.
// This is handled in the proc below this one.
-
-/client/proc/AIMove(n, direct, mob/living/silicon/ai/user)
-
- var/initial = initial(user.sprint)
- var/max_sprint = 50
-
- if(user.cooldown && user.cooldown < world.timeofday) // 3 seconds
- user.sprint = initial
-
- for(var/i = 0; i < max(user.sprint, initial); i += 20)
- var/turf/step = get_turf(get_step(user.eyeobj, direct))
+#define SPRINT_PER_TICK 0.5
+#define MAX_SPRINT 50
+#define SPRINT_PER_STEP 20
+/mob/living/silicon/ai/proc/AIMove(direction)
+ if(last_moved && last_moved + 1 < world.timeofday)
+ // Decay sprint based off how long it took us to input this next move
+ var/missed_sprint = max((world.timeofday + 1) - last_moved, 0) * SPRINT_PER_TICK
+ sprint = max(sprint - missed_sprint * 7, initial(sprint))
+
+ // We move a full step, at least. Can't glide more with our current movement mode, so this is how I have to live
+ var/step_count = 0
+ for(var/i = 0; i < max(sprint, initial(sprint)); i += SPRINT_PER_STEP)
+ step_count += 1
+ var/turf/step = get_turf(get_step(eyeobj, direction))
if(step)
- user.eyeobj.setLoc(step)
+ eyeobj.setLoc(step)
+
+ // I'd like to make this scale with the steps we take, but it like, just can't
+ // So we're doin this instead
+ eyeobj.glide_size = world.icon_size
- user.cooldown = world.timeofday + 5
- if(user.acceleration)
- user.sprint = min(user.sprint + 0.5, max_sprint)
+ last_moved = world.timeofday
+ if(acceleration)
+ sprint = min(sprint + SPRINT_PER_TICK, MAX_SPRINT)
else
- user.sprint = initial
+ sprint = initial(sprint)
+
+ ai_tracking_tool.reset_tracking()
- if(user.ai_tracking_tool.tracking)
- user.ai_tracking_tool.set_tracking(FALSE)
+#undef SPRINT_PER_STEP
+#undef MAX_SPRINT
+#undef SPRINT_PER_TICK
// Return to the Core.
/mob/living/silicon/ai/proc/view_core()
@@ -191,8 +200,8 @@
H.clear_holo(src)
else
current = null
- if(ai_tracking_tool && ai_tracking_tool.tracking)
- ai_tracking_tool.set_tracking(FALSE)
+ if(ai_tracking_tool)
+ ai_tracking_tool.reset_tracking()
unset_machine()
if(isturf(loc) && (QDELETED(eyeobj) || !eyeobj.loc))
diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm
index 9b62bd0774331..c54d0fd7c33d0 100644
--- a/code/modules/mob/mob_movement.dm
+++ b/code/modules/mob/mob_movement.dm
@@ -76,7 +76,8 @@
return mob.remote_control.relaymove(mob, direct)
if(isAI(mob))
- return AIMove(new_loc,direct,mob)
+ var/mob/living/silicon/ai/smoovin_ai = mob
+ return smoovin_ai.AIMove(direct)
if(Process_Grab()) //are we restrained by someone's grip?
return
diff --git a/code/modules/modular_computers/file_system/programs/secureye.dm b/code/modules/modular_computers/file_system/programs/secureye.dm
index c9e4fc087f364..2b3b8f6ea8c95 100644
--- a/code/modules/modular_computers/file_system/programs/secureye.dm
+++ b/code/modules/modular_computers/file_system/programs/secureye.dm
@@ -61,7 +61,6 @@
cam_background = new
cam_background.assigned_map = map_name
cam_background.del_on_map_removal = FALSE
- RegisterSignal(src, COMSIG_TRACKABLE_TRACKING_TARGET, PROC_REF(on_track_target))
/datum/computer_file/program/secureye/Destroy()
QDEL_NULL(cam_screen)
@@ -138,8 +137,8 @@
playsound(computer, get_sfx(SFX_TERMINAL_TYPE), 25, FALSE)
if(isnull(camera_ref))
return TRUE
- if(internal_tracker && internal_tracker.tracking)
- internal_tracker.set_tracking(FALSE)
+ if(internal_tracker)
+ internal_tracker.reset_tracking()
update_active_camera_screen()
return TRUE
@@ -147,7 +146,8 @@
if("start_tracking")
if(!internal_tracker)
internal_tracker = new(src)
- internal_tracker.set_tracked_mob(usr)
+ RegisterSignal(internal_tracker, COMSIG_TRACKABLE_TRACKING_TARGET, PROC_REF(on_track_target))
+ internal_tracker.track_input(usr)
return TRUE
/datum/computer_file/program/secureye/proc/on_track_target(datum/trackable/source, mob/living/target)
@@ -169,8 +169,8 @@
/datum/computer_file/program/secureye/ui_close(mob/user)
. = ..()
//don't track anyone while we're shutting off.
- if(internal_tracker && internal_tracker.tracking)
- internal_tracker.set_tracking(FALSE)
+ if(internal_tracker)
+ internal_tracker.reset_tracking()
var/user_ref = REF(user)
var/is_living = isliving(user)
// Living creature or not, we remove you anyway.
diff --git a/code/modules/reagents/chemistry/holder/holder.dm b/code/modules/reagents/chemistry/holder/holder.dm
index a4e149a2bc862..e6a861b851d33 100644
--- a/code/modules/reagents/chemistry/holder/holder.dm
+++ b/code/modules/reagents/chemistry/holder/holder.dm
@@ -333,6 +333,25 @@
handle_reactions()
return round(total_removed_amount, CHEMICAL_VOLUME_ROUNDING)
+/**
+ * Like remove_all but removes a percentage of every reagent directly rather than by volume
+ *
+ * Arguments
+ * * percentage - the percentage of each reagent to remove
+ */
+/datum/reagents/proc/remove_all_direct(percentage = 0.5)
+ if(!total_volume)
+ return 0
+ if(percentage <= 0)
+ stack_trace("non positive percentage passed to remove all reagents direct [percentage]")
+ return 0
+ var/total_removed_amount = 0
+ for(var/datum/reagent/reagent as anything in reagent_list)
+ total_removed_amount += remove_reagent(reagent.type, reagent.volume * percentage)
+
+ handle_reactions()
+ return round(total_removed_amount, CHEMICAL_VOLUME_ROUNDING)
+
/**
* Removes an specific reagent from this holder
* Arguments
diff --git a/code/modules/reagents/chemistry/reagents.dm b/code/modules/reagents/chemistry/reagents.dm
index 714b2ac21979f..f51532b28b19d 100644
--- a/code/modules/reagents/chemistry/reagents.dm
+++ b/code/modules/reagents/chemistry/reagents.dm
@@ -74,6 +74,10 @@
/// The affected respiration type, if the reagent damages/heals oxygen damage of an affected mob.
/// See "Mob bio-types flags" in /code/_DEFINES/mobs.dm
var/affected_respiration_type = ALL
+ /// A list of traits to apply while the reagent is being metabolized.
+ var/list/metabolized_traits
+ /// A list of traits to apply while the reagent is in a mob.
+ var/list/added_traits
///The default reagent container for the reagent, used for icon generation
var/obj/item/reagent_containers/default_container = /obj/item/reagent_containers/cup/bottle
@@ -191,20 +195,24 @@ Primarily used in reagents/reaction_agents
/// Called when this reagent is first added to a mob
/datum/reagent/proc/on_mob_add(mob/living/affected_mob, amount)
overdose_threshold /= max(normalise_creation_purity(), 1) //Maybe??? Seems like it would help pure chems be even better but, if I normalised this to 1, then everything would take a 25% reduction
- return
+ if(added_traits)
+ affected_mob.add_traits(added_traits, "base:[type]")
/// Called when this reagent is removed while inside a mob
/datum/reagent/proc/on_mob_delete(mob/living/affected_mob)
affected_mob.clear_mood_event("[type]_overdose")
- return
+ REMOVE_TRAITS_IN(affected_mob, "base:[type]")
/// Called when this reagent first starts being metabolized by a liver
/datum/reagent/proc/on_mob_metabolize(mob/living/affected_mob)
- return
+ SHOULD_CALL_PARENT(TRUE)
+ if(metabolized_traits)
+ affected_mob.add_traits(metabolized_traits, "metabolize:[type]")
/// Called when this reagent stops being metabolized by a liver
/datum/reagent/proc/on_mob_end_metabolize(mob/living/affected_mob)
- return
+ SHOULD_CALL_PARENT(TRUE)
+ REMOVE_TRAITS_IN(affected_mob, "metabolize:[type]")
/**
* Called when a reagent is inside of a mob when they are dead if the reagent has the REAGENT_DEAD_PROCESS flag
diff --git a/code/modules/reagents/chemistry/reagents/atmos_gas_reagents.dm b/code/modules/reagents/chemistry/reagents/atmos_gas_reagents.dm
index 7b13b2d28b1d2..1406e37369359 100644
--- a/code/modules/reagents/chemistry/reagents/atmos_gas_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/atmos_gas_reagents.dm
@@ -23,16 +23,15 @@
color = "90560B"
taste_description = "minty"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE
+ metabolized_traits = list(TRAIT_RESISTHEAT)
/datum/reagent/halon/on_mob_metabolize(mob/living/breather)
. = ..()
breather.add_movespeed_modifier(/datum/movespeed_modifier/reagent/halon)
- ADD_TRAIT(breather, TRAIT_RESISTHEAT, type)
/datum/reagent/halon/on_mob_end_metabolize(mob/living/breather)
. = ..()
breather.remove_movespeed_modifier(/datum/movespeed_modifier/reagent/halon)
- REMOVE_TRAIT(breather, TRAIT_RESISTHEAT, type)
/datum/reagent/healium
name = "Healium"
@@ -81,14 +80,7 @@
ph = 1.8
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE
addiction_types = list(/datum/addiction/stimulants = 14)
-
-/datum/reagent/nitrium_high_metabolization/on_mob_metabolize(mob/living/breather)
- . = ..()
- ADD_TRAIT(breather, TRAIT_SLEEPIMMUNE, type)
-
-/datum/reagent/nitrium_high_metabolization/on_mob_end_metabolize(mob/living/breather)
- . = ..()
- REMOVE_TRAIT(breather, TRAIT_SLEEPIMMUNE, type)
+ metabolized_traits = list(TRAIT_SLEEPIMMUNE)
/datum/reagent/nitrium_high_metabolization/on_mob_life(mob/living/carbon/breather, seconds_per_tick, times_fired)
. = ..()
diff --git a/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm
index 8fb16f6333ac6..039bce7eaa8eb 100644
--- a/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm
@@ -584,8 +584,10 @@
if(affected_mob.health <= (affected_mob.crit_threshold + HEALTH_THRESHOLD_FULLCRIT*(2*normalise_creation_purity()))) //certain death below this threshold
REMOVE_TRAIT(affected_mob, TRAIT_STABLEHEART, type) //we have to remove the stable heart trait before we give them a heart attack
- to_chat(affected_mob,span_danger("You feel something rupturing inside your chest!"))
- affected_mob.emote("scream")
+ affected_mob.remove_traits(subject_traits, type)
+ to_chat(affected_mob, span_danger("You feel something rupturing inside your chest!"))
+ if(!HAS_TRAIT(affected_mob, TRAIT_ANALGESIA))
+ affected_mob.emote("scream")
affected_mob.set_heartattack(TRUE)
volume = 0
if(need_mob_update)
diff --git a/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm
index 2cc88da41ade8..87033589812cb 100644
--- a/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm
@@ -158,6 +158,7 @@
drinker.add_atom_colour(color, TEMPORARY_COLOUR_PRIORITY)
/datum/reagent/consumable/ethanol/beer/green/on_mob_end_metabolize(mob/living/drinker)
+ . = ..()
drinker.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, color)
/datum/reagent/consumable/ethanol/beer/green/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired)
@@ -684,6 +685,7 @@
taste_description = "alcoholic bravery"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
glass_price = DRINK_PRICE_EASY
+ metabolized_traits = list(TRAIT_FEARLESS, TRAIT_ANALGESIA)
var/tough_text
/datum/reagent/consumable/ethanol/brave_bull/on_mob_metabolize(mob/living/drinker)
@@ -692,14 +694,12 @@
to_chat(drinker, span_notice("You feel [tough_text]!"))
drinker.maxHealth += 10 //Brave Bull makes you sturdier, and thus capable of withstanding a tiny bit more punishment.
drinker.health += 10
- ADD_TRAIT(drinker, TRAIT_FEARLESS, type)
/datum/reagent/consumable/ethanol/brave_bull/on_mob_end_metabolize(mob/living/drinker)
. = ..()
to_chat(drinker, span_notice("You no longer feel [tough_text]."))
drinker.maxHealth -= 10
drinker.health = min(drinker.health - 10, drinker.maxHealth) //This can indeed crit you if you're alive solely based on alchol ingestion
- REMOVE_TRAIT(drinker, TRAIT_FEARLESS, type)
/datum/reagent/consumable/ethanol/tequila_sunrise
name = "Tequila Sunrise"
@@ -1073,15 +1073,11 @@
quality = DRINK_VERYGOOD
taste_description = "concentrated matter"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
+ metabolized_traits = list(TRAIT_MADNESS_IMMUNE)
var/static/list/ray_filter = list(type = "rays", size = 40, density = 15, color = SUPERMATTER_SINGULARITY_RAYS_COLOUR, factor = 15)
-/datum/reagent/consumable/ethanol/singulo/on_mob_metabolize(mob/living/drinker)
- . = ..()
- ADD_TRAIT(drinker, TRAIT_MADNESS_IMMUNE, type)
-
/datum/reagent/consumable/ethanol/singulo/on_mob_end_metabolize(mob/living/drinker)
. = ..()
- REMOVE_TRAIT(drinker, TRAIT_MADNESS_IMMUNE, type)
drinker.remove_filter("singulo_rays")
/datum/reagent/consumable/ethanol/singulo/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired)
@@ -2173,14 +2169,7 @@
quality = DRINK_GOOD
taste_description = "artifical fruityness"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
-
-/datum/reagent/consumable/ethanol/rubberneck/on_mob_metabolize(mob/living/drinker)
- . = ..()
- ADD_TRAIT(drinker, TRAIT_SHOCKIMMUNE, type)
-
-/datum/reagent/consumable/ethanol/rubberneck/on_mob_end_metabolize(mob/living/drinker)
- REMOVE_TRAIT(drinker, TRAIT_SHOCKIMMUNE, type)
- return ..()
+ metabolized_traits = list(TRAIT_SHOCKIMMUNE)
/datum/reagent/consumable/ethanol/duplex
name = "Duplex"
@@ -2260,6 +2249,7 @@
quality = DRINK_NICE
taste_description = "sugary tartness"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
+ metabolized_traits = list(TRAIT_ANALGESIA)
/datum/reagent/consumable/ethanol/pina_colada
name = "Pina Colada"
@@ -2642,20 +2632,13 @@
/datum/reagent/consumable/ethanol/telepole
name = "Telepole"
- description = "A grounding rod in the form of a drink. Recharges ethereals, and gives temporary shock resistance."
+ description = "A grounding rod in the form of a drink. Recharges ethereals, and gives temporary shock resistance."
boozepwr = 50
color = "#b300ff"
quality = DRINK_NICE
taste_description = "the howling storm"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
-
-/datum/reagent/consumable/ethanol/telepole/on_mob_metabolize(mob/living/affected_mob)
- . = ..()
- ADD_TRAIT(affected_mob, TRAIT_SHOCKIMMUNE, type)
-
-/datum/reagent/consumable/ethanol/telepole/on_mob_end_metabolize(mob/living/affected_mob)
- . = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_SHOCKIMMUNE, type)
+ metabolized_traits = list(TRAIT_SHOCKIMMUNE)
/datum/reagent/consumable/ethanol/telepole/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume) //can't be on life because of the way blood works.
. = ..()
diff --git a/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm b/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm
index 5365cea34841f..43430d0946916 100644
--- a/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm
@@ -454,8 +454,10 @@
affected_mob.set_drugginess(1 MINUTES * REM * seconds_per_tick)
affected_mob.adjust_dizzy(3 SECONDS * REM * seconds_per_tick)
affected_mob.remove_status_effect(/datum/status_effect/drowsiness)
- affected_mob.AdjustSleeping(-40 * REM * seconds_per_tick)
+ affected_mob.AdjustSleeping(-4 SECONDS * REM * seconds_per_tick)
affected_mob.adjust_bodytemperature(-5 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, affected_mob.get_body_temp_normal())
+ if (SSradiation.can_irradiate_basic(affected_mob))
+ affected_mob.AddComponent(/datum/component/irradiated)
/datum/reagent/consumable/rootbeer
name = "root beer"
@@ -497,19 +499,15 @@
quality = DRINK_VERYGOOD
taste_description = "carbonated oil"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
+ metabolized_traits = list(TRAIT_SHOCKIMMUNE)
/datum/reagent/consumable/grey_bull/on_mob_metabolize(mob/living/carbon/affected_atom)
. = ..()
- ADD_TRAIT(affected_atom, TRAIT_SHOCKIMMUNE, type)
var/obj/item/organ/internal/liver/liver = affected_atom.get_organ_slot(ORGAN_SLOT_LIVER)
if(HAS_TRAIT(liver, TRAIT_MAINTENANCE_METABOLISM))
affected_atom.add_mood_event("maintenance_fun", /datum/mood_event/maintenance_high)
metabolization_rate *= 0.8
-/datum/reagent/consumable/grey_bull/on_mob_end_metabolize(mob/living/affected_mob)
- . = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_SHOCKIMMUNE, type)
-
/datum/reagent/consumable/grey_bull/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
affected_mob.set_jitter_if_lower(40 SECONDS * REM * seconds_per_tick)
diff --git a/code/modules/reagents/chemistry/reagents/drug_reagents.dm b/code/modules/reagents/chemistry/reagents/drug_reagents.dm
index 837a34daf7f15..6363a9766a35a 100644
--- a/code/modules/reagents/chemistry/reagents/drug_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/drug_reagents.dm
@@ -5,6 +5,7 @@
var/trippy = TRUE //Does this drug make you trip?
/datum/reagent/drug/on_mob_end_metabolize(mob/living/affected_mob)
+ . = ..()
if(trippy)
affected_mob.clear_mood_event("[type]_high")
@@ -214,13 +215,13 @@
overdose_threshold = 20
taste_description = "salt" // because they're bathsalts?
addiction_types = list(/datum/addiction/stimulants = 25) //8 per 2 seconds
- var/datum/brain_trauma/special/psychotic_brawling/bath_salts/rage
ph = 8.2
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
+ metabolized_traits = list(TRAIT_STUNIMMUNE, TRAIT_SLEEPIMMUNE, TRAIT_ANALGESIA)
+ var/datum/brain_trauma/special/psychotic_brawling/bath_salts/rage
/datum/reagent/drug/bath_salts/on_mob_metabolize(mob/living/affected_mob)
. = ..()
- affected_mob.add_traits(list(TRAIT_STUNIMMUNE, TRAIT_SLEEPIMMUNE), type)
if(iscarbon(affected_mob))
var/mob/living/carbon/carbon_mob = affected_mob
rage = new()
@@ -228,7 +229,6 @@
/datum/reagent/drug/bath_salts/on_mob_end_metabolize(mob/living/affected_mob)
. = ..()
- affected_mob.remove_traits(list(TRAIT_STUNIMMUNE, TRAIT_SLEEPIMMUNE), type)
if(rage)
QDEL_NULL(rage)
@@ -290,15 +290,14 @@
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
taste_description = "paint thinner"
addiction_types = list(/datum/addiction/hallucinogens = 18)
+ metabolized_traits = list(TRAIT_FEARLESS, TRAIT_ANALGESIA)
/datum/reagent/drug/happiness/on_mob_metabolize(mob/living/affected_mob)
. = ..()
- ADD_TRAIT(affected_mob, TRAIT_FEARLESS, type)
affected_mob.add_mood_event("happiness_drug", /datum/mood_event/happiness_drug)
/datum/reagent/drug/happiness/on_mob_delete(mob/living/affected_mob)
. = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_FEARLESS, type)
affected_mob.clear_mood_event("happiness_drug")
/datum/reagent/drug/happiness/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
@@ -335,19 +334,15 @@
overdose_threshold = 30
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
addiction_types = list(/datum/addiction/stimulants = 6) //2.6 per 2 seconds
+ metabolized_traits = list(TRAIT_BATON_RESISTANCE, TRAIT_ANALGESIA)
/datum/reagent/drug/pumpup/on_mob_metabolize(mob/living/carbon/affected_mob)
. = ..()
- ADD_TRAIT(affected_mob, TRAIT_BATON_RESISTANCE, type)
var/obj/item/organ/internal/liver/liver = affected_mob.get_organ_slot(ORGAN_SLOT_LIVER)
if(liver && HAS_TRAIT(liver, TRAIT_MAINTENANCE_METABOLISM))
affected_mob.add_mood_event("maintenance_fun", /datum/mood_event/maintenance_high)
metabolization_rate *= 0.8
-/datum/reagent/drug/pumpup/on_mob_end_metabolize(mob/living/affected_mob)
- . = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_BATON_RESISTANCE, type)
-
/datum/reagent/drug/pumpup/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
affected_mob.set_jitter_if_lower(10 SECONDS * REM * seconds_per_tick)
@@ -432,20 +427,13 @@
overdose_threshold = 25
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
addiction_types = list(/datum/addiction/maintenance_drugs = 8)
-
-/datum/reagent/drug/maint/sludge/on_mob_metabolize(mob/living/affected_mob)
- . = ..()
- ADD_TRAIT(affected_mob,TRAIT_HARDLY_WOUNDED,type)
+ metabolized_traits = list(TRAIT_HARDLY_WOUNDED, TRAIT_ANALGESIA)
/datum/reagent/drug/maint/sludge/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
if(affected_mob.adjustToxLoss(0.5 * REM * seconds_per_tick, required_biotype = affected_biotype))
return UPDATE_MOB_HEALTH
-/datum/reagent/drug/maint/sludge/on_mob_end_metabolize(mob/living/affected_mob)
- . = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_HARDLY_WOUNDED,type)
-
/datum/reagent/drug/maint/sludge/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired)
. = ..()
if(!iscarbon(affected_mob))
diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm
index f7d070ee33dab..a9fad04ff7f1f 100644
--- a/code/modules/reagents/chemistry/reagents/food_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm
@@ -525,14 +525,7 @@
taste_description = "garlic"
metabolization_rate = 0.15 * REAGENTS_METABOLISM
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
-
-/datum/reagent/consumable/garlic/on_mob_add(mob/living/affected_mob, amount)
- . = ..()
- ADD_TRAIT(affected_mob, TRAIT_GARLIC_BREATH, type)
-
-/datum/reagent/consumable/garlic/on_mob_delete(mob/living/affected_mob)
- . = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_GARLIC_BREATH, type)
+ added_traits = list(TRAIT_GARLIC_BREATH)
/datum/reagent/consumable/garlic/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
diff --git a/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm
index a54f117265da1..3c677e43d1c07 100644
--- a/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm
@@ -847,14 +847,7 @@ Basically, we fill the time between now and 2s from now with hands based off the
ph = 4.5
metabolization_rate = 0.08 * REM
tox_damage = 0
-
-/datum/reagent/inverse/salbutamol/on_mob_metabolize(mob/living/affected_mob)
- . = ..()
- ADD_TRAIT(affected_mob, TRAIT_EASYBLEED, type)
-
-/datum/reagent/inverse/salbutamol/on_mob_end_metabolize(mob/living/affected_mob)
- . = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_EASYBLEED, type)
+ metabolized_traits = list(TRAIT_EASYBLEED)
/datum/reagent/inverse/pen_acid
name = "Pendetide"
diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
index 303a85c6b4b6a..aface2dce34b5 100644
--- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
@@ -41,6 +41,7 @@
color = "#E0BB00" //golden for the gods
taste_description = "badmins"
chemical_flags = REAGENT_DEAD_PROCESS
+ metabolized_traits = list(TRAIT_ANALGESIA)
/// Flags to fullheal every metabolism tick
var/full_heal_flags = ~(HEAL_BRUTE|HEAL_BURN|HEAL_TOX|HEAL_RESTRAINTS|HEAL_REFRESH_ORGANS)
@@ -252,14 +253,7 @@
metabolization_rate = 0.1 * REAGENTS_METABOLISM
ph = 8.1
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
-
-/datum/reagent/medicine/spaceacillin/on_mob_add(mob/living/affected_mob)
- . = ..()
- ADD_TRAIT(affected_mob, TRAIT_VIRUS_RESISTANCE, type)
-
-/datum/reagent/medicine/spaceacillin/on_mob_delete(mob/living/affected_mob)
- . = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_VIRUS_RESISTANCE, type)
+ added_traits = list(TRAIT_VIRUS_RESISTANCE)
//Goon Chems. Ported mainly from Goonstation. Easily mixable (or not so easily) and provide a variety of effects.
@@ -349,6 +343,7 @@
metabolization_rate = 0.4 * REAGENTS_METABOLISM
ph = 2.6
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_AFFECTS_WOUNDS
+ metabolized_traits = list(TRAIT_ANALGESIA)
/datum/reagent/medicine/mine_salve/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
@@ -496,14 +491,7 @@
metabolization_rate = 2 * REAGENTS_METABOLISM
ph = 12 //It's a reducing agent
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
-
-/datum/reagent/medicine/potass_iodide/on_mob_metabolize(mob/living/affected_mob)
- . = ..()
- ADD_TRAIT(affected_mob, TRAIT_HALT_RADIATION_EFFECTS, "[type]")
-
-/datum/reagent/medicine/potass_iodide/on_mob_end_metabolize(mob/living/affected_mob)
- . = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_HALT_RADIATION_EFFECTS, "[type]")
+ metabolized_traits = list(TRAIT_HALT_RADIATION_EFFECTS)
/datum/reagent/medicine/potass_iodide/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
@@ -521,14 +509,7 @@
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
inverse_chem_val = 0.4
inverse_chem = /datum/reagent/inverse/pen_acid
-
-/datum/reagent/medicine/pen_acid/on_mob_metabolize(mob/living/affected_mob)
- . = ..()
- ADD_TRAIT(affected_mob, TRAIT_HALT_RADIATION_EFFECTS, "[type]")
-
-/datum/reagent/medicine/pen_acid/on_mob_end_metabolize(mob/living/affected_mob)
- . = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_HALT_RADIATION_EFFECTS, "[type]")
+ metabolized_traits = list(TRAIT_HALT_RADIATION_EFFECTS)
/datum/reagent/medicine/pen_acid/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
@@ -603,16 +584,15 @@
addiction_types = list(/datum/addiction/stimulants = 4) //1.6 per 2 seconds
inverse_chem = /datum/reagent/inverse/corazargh
inverse_chem_val = 0.4
+ metabolized_traits = list(TRAIT_BATON_RESISTANCE)
/datum/reagent/medicine/ephedrine/on_mob_metabolize(mob/living/affected_mob)
. = ..()
affected_mob.add_movespeed_modifier(/datum/movespeed_modifier/reagent/ephedrine)
- ADD_TRAIT(affected_mob, TRAIT_BATON_RESISTANCE, type)
/datum/reagent/medicine/ephedrine/on_mob_end_metabolize(mob/living/affected_mob)
. = ..()
affected_mob.remove_movespeed_modifier(/datum/movespeed_modifier/reagent/ephedrine)
- REMOVE_TRAIT(affected_mob, TRAIT_BATON_RESISTANCE, type)
/datum/reagent/medicine/ephedrine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
@@ -668,6 +648,7 @@
ph = 8.96
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
addiction_types = list(/datum/addiction/opioids = 10)
+ metabolized_traits = list(TRAIT_ANALGESIA)
/datum/reagent/medicine/morphine/on_mob_metabolize(mob/living/affected_mob)
. = ..()
@@ -820,14 +801,7 @@
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
inverse_chem_val = 0.35
inverse_chem = /datum/reagent/inverse/atropine
-
-/datum/reagent/medicine/atropine/on_mob_add(mob/living/affected_mob)
- . = ..()
- ADD_TRAIT(affected_mob, TRAIT_PREVENT_IMPLANT_AUTO_EXPLOSION, "[type]")
-
-/datum/reagent/medicine/atropine/on_mob_delete(mob/living/affected_mob)
- . = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_PREVENT_IMPLANT_AUTO_EXPLOSION, "[type]")
+ added_traits = list(TRAIT_PREVENT_IMPLANT_AUTO_EXPLOSION)
/datum/reagent/medicine/atropine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
@@ -863,14 +837,7 @@
overdose_threshold = 30
ph = 10.2
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
-
-/datum/reagent/medicine/epinephrine/on_mob_metabolize(mob/living/affected_mob)
- . = ..()
- ADD_TRAIT(affected_mob, TRAIT_NOCRITDAMAGE, type)
-
-/datum/reagent/medicine/epinephrine/on_mob_end_metabolize(mob/living/affected_mob)
- . = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_NOCRITDAMAGE, type)
+ metabolized_traits = list(TRAIT_NOCRITDAMAGE)
/datum/reagent/medicine/epinephrine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
@@ -1039,21 +1006,13 @@
purity = REAGENT_STANDARD_PURITY
inverse_chem = /datum/reagent/inverse
inverse_chem_val = 0.45
+ metabolized_traits = list(TRAIT_TUMOR_SUPPRESSED) //Having mannitol in you will pause the brain damage from brain tumor (so it heals an even 2 brain damage instead of 1.8)
/datum/reagent/medicine/mannitol/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
if(affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, -2 * REM * seconds_per_tick * normalise_creation_purity(), required_organ_flag = affected_organ_flags))
return UPDATE_MOB_HEALTH
-//Having mannitol in you will pause the brain damage from brain tumor (so it heals an even 2 brain damage instead of 1.8)
-/datum/reagent/medicine/mannitol/on_mob_metabolize(mob/living/carbon/affected_mob)
- . = ..()
- ADD_TRAIT(affected_mob, TRAIT_TUMOR_SUPPRESSED, TRAIT_GENERIC)
-
-/datum/reagent/medicine/mannitol/on_mob_end_metabolize(mob/living/carbon/affected_mob)
- . = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_TUMOR_SUPPRESSED, TRAIT_GENERIC)
-
/datum/reagent/medicine/mannitol/overdose_start(mob/living/affected_mob)
. = ..()
to_chat(affected_mob, span_notice("You suddenly feel E N L I G H T E N E D!"))
@@ -1080,12 +1039,12 @@
purity = REAGENT_STANDARD_PURITY
inverse_chem_val = 0.5
inverse_chem = /datum/reagent/inverse/neurine
+ added_traits = list(TRAIT_ANTICONVULSANT)
///brain damage level when we first started taking the chem
var/initial_bdamage = 200
/datum/reagent/medicine/neurine/on_mob_add(mob/living/affected_mob, amount)
. = ..()
- ADD_TRAIT(affected_mob, TRAIT_ANTICONVULSANT, name)
if(!iscarbon(affected_mob))
return
var/mob/living/carbon/affected_carbon = affected_mob
@@ -1094,7 +1053,6 @@
/datum/reagent/medicine/neurine/on_mob_delete(mob/living/affected_mob)
. = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_ANTICONVULSANT, name)
if(!iscarbon(affected_mob))
return
var/mob/living/carbon/affected_carbon = affected_mob
@@ -1172,16 +1130,15 @@
ph = 8.7
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE
addiction_types = list(/datum/addiction/stimulants = 4) //0.8 per 2 seconds
+ metabolized_traits = list(TRAIT_BATON_RESISTANCE, TRAIT_ANALGESIA)
/datum/reagent/medicine/stimulants/on_mob_metabolize(mob/living/affected_mob)
. = ..()
affected_mob.add_movespeed_modifier(/datum/movespeed_modifier/reagent/stimulants)
- ADD_TRAIT(affected_mob, TRAIT_BATON_RESISTANCE, type)
/datum/reagent/medicine/stimulants/on_mob_end_metabolize(mob/living/affected_mob)
. = ..()
affected_mob.remove_movespeed_modifier(/datum/movespeed_modifier/reagent/stimulants)
- REMOVE_TRAIT(affected_mob, TRAIT_BATON_RESISTANCE, type)
/datum/reagent/medicine/stimulants/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
@@ -1298,6 +1255,7 @@
ph = 11
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
addiction_types = list(/datum/addiction/hallucinogens = 14)
+ metabolized_traits = list(TRAIT_PACIFISM)
/datum/reagent/medicine/earthsblood/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
@@ -1323,14 +1281,6 @@
if(need_mob_update)
return UPDATE_MOB_HEALTH
-/datum/reagent/medicine/earthsblood/on_mob_metabolize(mob/living/affected_mob)
- . = ..()
- ADD_TRAIT(affected_mob, TRAIT_PACIFISM, type)
-
-/datum/reagent/medicine/earthsblood/on_mob_end_metabolize(mob/living/affected_mob)
- . = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_PACIFISM, type)
-
/datum/reagent/medicine/earthsblood/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired)
. = ..()
affected_mob.adjust_hallucinations_up_to(10 SECONDS * REM * seconds_per_tick, 120 SECONDS)
@@ -1446,14 +1396,7 @@
color = "#FF3542"
self_consuming = TRUE
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
-
-/datum/reagent/medicine/higadrite/on_mob_metabolize(mob/living/affected_mob)
- . = ..()
- ADD_TRAIT(affected_mob, TRAIT_STABLELIVER, type)
-
-/datum/reagent/medicine/higadrite/on_mob_end_metabolize(mob/living/affected_mob)
- . = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_STABLELIVER, type)
+ metabolized_traits = list(TRAIT_STABLELIVER)
/datum/reagent/medicine/cordiolis_hepatico
name = "Cordiolis Hepatico"
@@ -1474,6 +1417,7 @@
name = "Muscle Stimulant"
description = "A potent chemical that allows someone under its influence to be at full physical ability even when under massive amounts of pain."
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE
+ metabolized_traits = list(TRAIT_ANALGESIA)
/datum/reagent/medicine/muscle_stimulant/on_mob_metabolize(mob/living/affected_mob)
. = ..()
@@ -1494,14 +1438,7 @@
var/overdose_progress = 0 // to track overdose progress
ph = 7.89
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
-
-/datum/reagent/medicine/modafinil/on_mob_metabolize(mob/living/affected_mob)
- . = ..()
- ADD_TRAIT(affected_mob, TRAIT_SLEEPIMMUNE, type)
-
-/datum/reagent/medicine/modafinil/on_mob_end_metabolize(mob/living/affected_mob)
- . = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_SLEEPIMMUNE, type)
+ metabolized_traits = list(TRAIT_SLEEPIMMUNE)
/datum/reagent/medicine/modafinil/on_mob_life(mob/living/carbon/metabolizer, seconds_per_tick, times_fired)
. = ..()
@@ -1565,14 +1502,7 @@
overdose_threshold = 30
ph = 9.12
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
-
-/datum/reagent/medicine/psicodine/on_mob_metabolize(mob/living/affected_mob)
- . = ..()
- ADD_TRAIT(affected_mob, TRAIT_FEARLESS, type)
-
-/datum/reagent/medicine/psicodine/on_mob_end_metabolize(mob/living/affected_mob)
- . = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_FEARLESS, type)
+ metabolized_traits = list(TRAIT_FEARLESS)
/datum/reagent/medicine/psicodine/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
@@ -1694,19 +1624,16 @@
/// For tracking when we tell the person we're no longer bleeding
var/was_working
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
+ metabolized_traits = list(TRAIT_COAGULATING)
/datum/reagent/medicine/coagulant/on_mob_metabolize(mob/living/affected_mob)
. = ..()
- ADD_TRAIT(affected_mob, TRAIT_COAGULATING, /datum/reagent/medicine/coagulant)
-
if(ishuman(affected_mob))
var/mob/living/carbon/human/blood_boy = affected_mob
blood_boy.physiology?.bleed_mod *= passive_bleed_modifier
/datum/reagent/medicine/coagulant/on_mob_end_metabolize(mob/living/affected_mob)
. = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_COAGULATING, /datum/reagent/medicine/coagulant)
-
if(was_working)
to_chat(affected_mob, span_warning("The medicine thickening your blood loses its effect!"))
if(ishuman(affected_mob))
diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm
index 48c30271158f9..25751c5903319 100644
--- a/code/modules/reagents/chemistry/reagents/other_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm
@@ -347,6 +347,7 @@
ph = 7.5 //God is alkaline
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_CLEANS|REAGENT_UNAFFECTED_BY_METABOLISM // Operates at fixed metabolism for balancing memes.
default_container = /obj/item/reagent_containers/cup/glass/bottle/holywater
+ metabolized_traits = list(TRAIT_HOLY)
/datum/glass_style/drinking_glass/holywater
required_drink_type = /datum/reagent/water/holywater
@@ -369,14 +370,6 @@
mytray.adjust_plant_health(round(volume * 0.1))
mytray.myseed?.adjust_instability(round(volume * 0.15))
-/datum/reagent/water/holywater/on_mob_metabolize(mob/living/affected_mob)
- . = ..()
- ADD_TRAIT(affected_mob, TRAIT_HOLY, type)
-
-/datum/reagent/water/holywater/on_mob_end_metabolize(mob/living/affected_mob)
- . = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_HOLY, type)
-
/datum/reagent/water/holywater/on_mob_add(mob/living/affected_mob, amount)
. = ..()
if(IS_CULTIST(affected_mob))
@@ -2507,14 +2500,7 @@
metabolization_rate = 0.25 * REAGENTS_METABOLISM
ph = 15
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
-
-/datum/reagent/pax/on_mob_metabolize(mob/living/affected_mob)
- . = ..()
- ADD_TRAIT(affected_mob, TRAIT_PACIFISM, type)
-
-/datum/reagent/pax/on_mob_end_metabolize(mob/living/affected_mob)
- . = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_PACIFISM, type)
+ metabolized_traits = list(TRAIT_PACIFISM)
/datum/reagent/bz_metabolites
name = "BZ Metabolites"
@@ -2523,14 +2509,7 @@
taste_description = "acrid cinnamon"
metabolization_rate = 0.2 * REAGENTS_METABOLISM
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE
-
-/datum/reagent/bz_metabolites/on_mob_metabolize(mob/living/ling)
- . = ..()
- ADD_TRAIT(ling, TRAIT_CHANGELING_HIVEMIND_MUTE, type)
-
-/datum/reagent/bz_metabolites/on_mob_end_metabolize(mob/living/ling)
- . = ..()
- REMOVE_TRAIT(ling, TRAIT_CHANGELING_HIVEMIND_MUTE, type)
+ metabolized_traits = list(TRAIT_CHANGELING_HIVEMIND_MUTE)
/datum/reagent/bz_metabolites/on_mob_life(mob/living/carbon/target, seconds_per_tick, times_fired)
. = ..()
@@ -2767,6 +2746,7 @@
metabolization_rate = 0.75 * REAGENTS_METABOLISM // 5u (WOUND_DETERMINATION_CRITICAL) will last for ~34 seconds
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
self_consuming = TRUE
+ metabolized_traits = list(TRAIT_ANALGESIA)
/// Whether we've had at least WOUND_DETERMINATION_SEVERE (2.5u) of determination at any given time. No damage slowdown immunity or indication we're having a second wind if it's just a single moderate wound
var/significant = FALSE
@@ -3055,6 +3035,7 @@
addtimer(CALLBACK(exposed_obj, TYPE_PROC_REF(/atom/movable/, remove_haunted), HAUNTIUM_REAGENT_TRAIT), volume * 20 SECONDS)
/datum/reagent/hauntium/on_mob_metabolize(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
+ . = ..()
to_chat(affected_mob, span_userdanger("You feel an evil presence inside you!"))
if(affected_mob.mob_biotypes & MOB_UNDEAD || HAS_MIND_TRAIT(affected_mob, TRAIT_MORBID))
affected_mob.add_mood_event("morbid_hauntium", /datum/mood_event/morbid_hauntium, name) //8 minutes of slight mood buff if undead or morbid
diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
index 7457acd0687b9..e9bea91fbed64 100644
--- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
@@ -295,14 +295,7 @@
taste_description = "death"
ph = 14.5
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
-
-/datum/reagent/toxin/ghoulpowder/on_mob_metabolize(mob/living/affected_mob)
- . = ..()
- ADD_TRAIT(affected_mob, TRAIT_FAKEDEATH, type)
-
-/datum/reagent/toxin/ghoulpowder/on_mob_end_metabolize(mob/living/affected_mob)
- . = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_FAKEDEATH, type)
+ metabolized_traits = list(TRAIT_FAKEDEATH)
/datum/reagent/toxin/ghoulpowder/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
@@ -321,14 +314,7 @@
inverse_chem = /datum/reagent/impurity/rosenol
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
addiction_types = list(/datum/addiction/hallucinogens = 18) //7.2 per 2 seconds
-
-/datum/reagent/toxin/mindbreaker/on_mob_metabolize(mob/living/affected_mob)
- . = ..()
- ADD_TRAIT(affected_mob, TRAIT_RDS_SUPPRESSED, type)
-
-/datum/reagent/toxin/mindbreaker/on_mob_end_metabolize(mob/living/affected_mob)
- . = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_RDS_SUPPRESSED, type)
+ metabolized_traits = list(TRAIT_RDS_SUPPRESSED)
/datum/reagent/toxin/mindbreaker/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
@@ -848,14 +834,7 @@
metabolization_rate = 0.75 * REAGENTS_METABOLISM
toxpwr = 0
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE
-
-/datum/reagent/toxin/sodium_thiopental/on_mob_add(mob/living/affected_mob, amount)
- . = ..()
- ADD_TRAIT(affected_mob, TRAIT_ANTICONVULSANT, name)
-
-/datum/reagent/toxin/sodium_thiopental/on_mob_delete(mob/living/affected_mob)
- . = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_ANTICONVULSANT, name)
+ added_traits = list(TRAIT_ANTICONVULSANT)
/datum/reagent/toxin/sodium_thiopental/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
@@ -1000,20 +979,13 @@
toxpwr = 0
ph = 11.6
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
+ metabolized_traits = list(TRAIT_BLOODY_MESS)
/datum/reagent/toxin/heparin/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
if(holder.has_reagent(/datum/reagent/medicine/coagulant)) //Directly purges coagulants from the system. Get rid of the heparin BEFORE attempting to use coagulants.
holder.remove_reagent(/datum/reagent/medicine/coagulant, 2 * REM * seconds_per_tick)
return ..()
-/datum/reagent/toxin/heparin/on_mob_metabolize(mob/living/affected_mob)
- . = ..()
- ADD_TRAIT(affected_mob, TRAIT_BLOODY_MESS, /datum/reagent/toxin/heparin)
-
-/datum/reagent/toxin/heparin/on_mob_end_metabolize(mob/living/affected_mob)
- . = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_BLOODY_MESS, /datum/reagent/toxin/heparin)
-
/datum/reagent/toxin/rotatium //Rotatium. Fucks up your rotation and is hilarious
name = "Rotatium"
description = "A constantly swirling, oddly colourful fluid. Causes the consumer's sense of direction and hand-eye coordination to become wild."
@@ -1184,14 +1156,7 @@
ph = 1.7
taste_description = "stillness"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
-
-/datum/reagent/toxin/mimesbane/on_mob_metabolize(mob/living/affected_mob)
- . = ..()
- ADD_TRAIT(affected_mob, TRAIT_EMOTEMUTE, type)
-
-/datum/reagent/toxin/mimesbane/on_mob_end_metabolize(mob/living/affected_mob)
- . = ..()
- REMOVE_TRAIT(affected_mob, TRAIT_EMOTEMUTE, type)
+ metabolized_traits = list(TRAIT_EMOTEMUTE)
/datum/reagent/toxin/bonehurtingjuice //oof ouch
name = "Bone Hurting Juice"
diff --git a/code/modules/surgery/organs/external/spines.dm b/code/modules/surgery/organs/external/spines.dm
index 524810a2a67c3..743b7fa8d47f2 100644
--- a/code/modules/surgery/organs/external/spines.dm
+++ b/code/modules/surgery/organs/external/spines.dm
@@ -7,6 +7,8 @@
zone = BODY_ZONE_CHEST
slot = ORGAN_SLOT_EXTERNAL_SPINES
+ preference = "feature_lizard_spines"
+
dna_block = DNA_SPINES_BLOCK
restyle_flags = EXTERNAL_RESTYLE_FLESH
diff --git a/code/modules/surgery/surgery_step.dm b/code/modules/surgery/surgery_step.dm
index 6d20a58b9e426..342f984042fec 100644
--- a/code/modules/surgery/surgery_step.dm
+++ b/code/modules/surgery/surgery_step.dm
@@ -70,6 +70,8 @@
#define SURGERY_SPEED_DISSECTION_MODIFIER 0.8
///Modifier given to users with TRAIT_MORBID on certain surgeries
#define SURGERY_SPEED_MORBID_CURIOSITY 0.7
+///Modifier given to patients with TRAIT_ANALGESIA
+#define SURGERY_SPEED_TRAIT_ANALGESIA 0.8
/datum/surgery_step/proc/initiate(mob/living/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, try_to_fail = FALSE)
// Only followers of Asclepius have the ability to use Healing Touch and perform miracle feats of surgery.
@@ -95,6 +97,9 @@
if(check_morbid_curiosity(user, tool, surgery))
speed_mod *= SURGERY_SPEED_MORBID_CURIOSITY
+ if(HAS_TRAIT(target, TRAIT_ANALGESIA))
+ speed_mod *= SURGERY_SPEED_TRAIT_ANALGESIA
+
var/implement_speed_mod = 1
if(implement_type) //this means it isn't a require hand or any item step.
implement_speed_mod = implements[implement_type] / 100.0
@@ -260,10 +265,14 @@
*/
/datum/surgery_step/proc/display_pain(mob/living/target, pain_message, mechanical_surgery = FALSE)
if(target.stat < UNCONSCIOUS)
- to_chat(target, span_userdanger(pain_message))
- if(prob(30) && !mechanical_surgery)
- target.emote("scream")
+ if(HAS_TRAIT(target, TRAIT_ANALGESIA))
+ to_chat(target, span_notice("You feel a dull, numb sensation as your body is surgically operated on."))
+ else
+ to_chat(target, span_userdanger(pain_message))
+ if(prob(30) && !mechanical_surgery)
+ target.emote("scream")
+#undef SURGERY_SPEED_TRAIT_ANALGESIA
#undef SURGERY_SPEED_DISSECTION_MODIFIER
#undef SURGERY_SPEED_MORBID_CURIOSITY
#undef SURGERY_SLOWDOWN_CAP_MULTIPLIER
diff --git a/code/modules/unit_tests/achievements.dm b/code/modules/unit_tests/achievements.dm
index 44e1384c2e01a..decda52a2f5d7 100644
--- a/code/modules/unit_tests/achievements.dm
+++ b/code/modules/unit_tests/achievements.dm
@@ -2,13 +2,13 @@
/datum/unit_test/achievements
/datum/unit_test/achievements/Run()
- var/award_icons = icon_states(ACHIEVEMENTS_SET)
for(var/datum/award/award as anything in subtypesof(/datum/award))
if(!initial(award.name)) //Skip abstract achievements types
continue
var/init_icon = initial(award.icon)
- if(!init_icon || !(init_icon in award_icons))
- TEST_FAIL("Award [initial(award.name)] has an unexistent icon: \"[init_icon || "null"]\"")
+ var/init_icon_state = initial(award.icon_state)
+ if(!init_icon_state || !icon_exists(init_icon, init_icon_state))
+ TEST_FAIL("Award [initial(award.name)] has a non-existent icon in [init_icon]: \"[init_icon_state || "null"]\"")
if(length(initial(award.database_id)) > 32) //sql schema limit
TEST_FAIL("Award [initial(award.name)] database id is too long")
var/init_category = initial(award.category)
diff --git a/html/changelogs/AutoChangeLog-pr-81354.yml b/html/changelogs/AutoChangeLog-pr-81354.yml
deleted file mode 100644
index db9fbbabb28e6..0000000000000
--- a/html/changelogs/AutoChangeLog-pr-81354.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-author: "Aylong"
-delete-after: True
-changes:
- - rscadd: "Added `Mute` button into `Chat Tabs` settings, it disables tab unread counter"
- - rscadd: "Added `Clear chat` button into `General` settings, you can clear your dirty chat like you did it before TGchat"
- - bugfix: "Case-sensitive highlighting now works properly"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-81385.yml b/html/changelogs/AutoChangeLog-pr-81385.yml
deleted file mode 100644
index 23f3aab42b1af..0000000000000
--- a/html/changelogs/AutoChangeLog-pr-81385.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-author: "ViktorKoL"
-delete-after: True
-changes:
- - bugfix: "knit flesh now heals organs as intended, and does not cause its victims to be red forever if interrupted"
- - spellcheck: "knit flesh chat messages are no longer gramatically incorrect"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-81409.yml b/html/changelogs/AutoChangeLog-pr-81409.yml
new file mode 100644
index 0000000000000..f3f1949b1ad00
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-81409.yml
@@ -0,0 +1,4 @@
+author: "Thunder12345"
+delete-after: True
+changes:
+ - qol: "The bitrunning quantum console UI now lists domains in tabs by difficulty."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-81413.yml b/html/changelogs/AutoChangeLog-pr-81413.yml
new file mode 100644
index 0000000000000..b64eee4e917c7
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-81413.yml
@@ -0,0 +1,4 @@
+author: "Melbert"
+delete-after: True
+changes:
+ - rscadd: "Wizards have a new mobility option available, the Telegram Scepter. The ability to travel anywhere you can see at the point of a wand... but at a price?"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-81420.yml b/html/changelogs/AutoChangeLog-pr-81420.yml
deleted file mode 100644
index c83fff17957ef..0000000000000
--- a/html/changelogs/AutoChangeLog-pr-81420.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "JohnFulpWillard"
-delete-after: True
-changes:
- - bugfix: "Revenants (and other flying mobs) will not make noise when walking into pools of gibs,"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-81432.yml b/html/changelogs/AutoChangeLog-pr-81432.yml
deleted file mode 100644
index 0b74f0ee4c90e..0000000000000
--- a/html/changelogs/AutoChangeLog-pr-81432.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Rhials"
-delete-after: True
-changes:
- - spellcheck: "Some space ruin area names have been made more distinct."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-81435.yml b/html/changelogs/AutoChangeLog-pr-81435.yml
new file mode 100644
index 0000000000000..0ef4705974e09
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-81435.yml
@@ -0,0 +1,4 @@
+author: "Ghommie"
+delete-after: True
+changes:
+ - rscadd: "Added three new 'special' bedsheets. One of them is quite rare and made from gondola hide."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-81437.yml b/html/changelogs/AutoChangeLog-pr-81437.yml
deleted file mode 100644
index c27dd8a0fa994..0000000000000
--- a/html/changelogs/AutoChangeLog-pr-81437.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "vinylspiders"
-delete-after: True
-changes:
- - bugfix: "*wag emote is now functional again"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-81452.yml b/html/changelogs/AutoChangeLog-pr-81452.yml
new file mode 100644
index 0000000000000..d501307c8ba40
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-81452.yml
@@ -0,0 +1,5 @@
+author: "mogeoko"
+delete-after: True
+changes:
+ - bugfix: "Thermomachines now reconnect to pipes on multitool's act."
+ - bugfix: "Multi-deck connectors won't connect pipes not located in front/top/bottom of it."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-81459.yml b/html/changelogs/AutoChangeLog-pr-81459.yml
new file mode 100644
index 0000000000000..39addd9cbe676
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-81459.yml
@@ -0,0 +1,6 @@
+author: "SyncIt21"
+delete-after: True
+changes:
+ - qol: "adds examines & screentips for ore box"
+ - code_imp: "cleans up some procs for ore box"
+ - spellcheck: "corrected description & ui notice of ore box to specify it can hold boulders too"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-81472.yml b/html/changelogs/AutoChangeLog-pr-81472.yml
new file mode 100644
index 0000000000000..0ca1b4ccb0f35
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-81472.yml
@@ -0,0 +1,4 @@
+author: "kawaiinick"
+delete-after: True
+changes:
+ - rscadd: "Your mouth now fits combat or survival knives(it's totally safe)"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-81481.yml b/html/changelogs/AutoChangeLog-pr-81481.yml
new file mode 100644
index 0000000000000..d076280f163ec
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-81481.yml
@@ -0,0 +1,4 @@
+author: "Sylphet"
+delete-after: True
+changes:
+ - bugfix: "cargo lockboxes update iconstates correctly now"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-81482.yml b/html/changelogs/AutoChangeLog-pr-81482.yml
new file mode 100644
index 0000000000000..7306b7c28b4de
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-81482.yml
@@ -0,0 +1,4 @@
+author: "00-Steven"
+delete-after: True
+changes:
+ - bugfix: "Space cats CAN into space. (They're back to surviving being in unsuitable atmos.)"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-81483.yml b/html/changelogs/AutoChangeLog-pr-81483.yml
new file mode 100644
index 0000000000000..8c82b8396d588
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-81483.yml
@@ -0,0 +1,4 @@
+author: "IndieanaJones"
+delete-after: True
+changes:
+ - bugfix: "Tank spider corpses should no longer be conditionally invisible"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-81485.yml b/html/changelogs/AutoChangeLog-pr-81485.yml
new file mode 100644
index 0000000000000..bac62bcf734ae
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-81485.yml
@@ -0,0 +1,4 @@
+author: "IndieanaJones"
+delete-after: True
+changes:
+ - bugfix: "Blob Zombies now render their blob heads correctly again."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-81487.yml b/html/changelogs/AutoChangeLog-pr-81487.yml
new file mode 100644
index 0000000000000..db13b1e0f97da
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-81487.yml
@@ -0,0 +1,4 @@
+author: "SyncIt21"
+delete-after: True
+changes:
+ - bugfix: "Indestructible items like the pai card don't teleport to the ore silo when you insert them into silo linked machine & also displays a message saying it was rejected."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-81492.yml b/html/changelogs/AutoChangeLog-pr-81492.yml
new file mode 100644
index 0000000000000..d2aeff28762c2
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-81492.yml
@@ -0,0 +1,4 @@
+author: "13spacemen"
+delete-after: True
+changes:
+ - code_imp: "Removed unused global lists and sprite accessories related to tail spines"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-81493.yml b/html/changelogs/AutoChangeLog-pr-81493.yml
new file mode 100644
index 0000000000000..4f7d4b4b89c23
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-81493.yml
@@ -0,0 +1,4 @@
+author: "13spacemen"
+delete-after: True
+changes:
+ - code_imp: "removed redundant check for plasmamen in survival box code"
\ No newline at end of file
diff --git a/html/changelogs/archive/2024-02.yml b/html/changelogs/archive/2024-02.yml
index 7c8d89aa9282a..6afd8b08c3696 100644
--- a/html/changelogs/archive/2024-02.yml
+++ b/html/changelogs/archive/2024-02.yml
@@ -387,3 +387,62 @@
- bugfix: Holding bloodied gloves no longer makes your hands look bloody, bloodied
gloves now look bloody when worn, and damaged gloves now look damaged when worn
- bugfix: Gaining or losing an arm now correctly updates your hand overlays
+2024-02-14:
+ Absolucy:
+ - rscadd: Painkillers (i.e morphine, miner's salve) now actually induce analgesic
+ effects, preventing various pain-related effects, such as screaming due to pain,
+ and also provides a speed bonus during surgery.
+ - rscadd: The tenacity trauma (traumatic neuropathy) also applies analgesic effects.
+ - refactor: Simplified code related to reagents adding traits.
+ Aylong:
+ - rscadd: Added `Mute` button into `Chat Tabs` settings, it disables tab unread
+ counter
+ - rscadd: Added `Clear chat` button into `General` settings, you can clear your
+ dirty chat like you did it before TGchat
+ - bugfix: Case-sensitive highlighting now works properly
+ JohnFulpWillard:
+ - bugfix: Revenants (and other flying mobs) will not make noise when walking into
+ pools of gibs,
+ Rhials:
+ - spellcheck: Some space ruin area names have been made more distinct.
+ - bugfix: Ghost role polls should spam you less when multiple of the same roll occur
+ in succession.
+ ViktorKoL:
+ - bugfix: heretics no longer lose their spells when returning from a shapeshift
+ - bugfix: knit flesh now heals organs as intended, and does not cause its victims
+ to be red forever if interrupted
+ - spellcheck: knit flesh chat messages are no longer gramatically incorrect
+ vinylspiders:
+ - bugfix: '*wag emote is now functional again'
+2024-02-15:
+ CandleJaxx and Iamgoofball:
+ - bugfix: Fixes Krav Maga allowing pacifism bypasses.
+ IndieanaJones:
+ - balance: Nightmare's Light Eater takes less time in jaunt to gain a critical strike,
+ being reduced to 7 seconds from 15 seconds.
+ K4rlox:
+ - balance: Maintenance drones now can use RPED, RCD, Holosign, and Spray bottles
+ LemonInTheDark:
+ - rscadd: AI's acceleration now smoothly decays, instead of just falling back down
+ to 0 after 0.5 seconds
+ - bugfix: AI's standard movement (non accelerated) is smooth now, instead of constantly
+ jumping around
+ - bugfix: AIs will now follow their targets more closely, shouldn't have any issues
+ with them lagging behind anymore
+ Melbert:
+ - bugfix: Valentines no longer see themselves covered in hearts. They only see their
+ Valentine covered in hearts.
+ - balance: Scientists have discovered Nuka Cola is not good for short term health.
+2024-02-16:
+ 00-Steven:
+ - bugfix: Fixed the lizardperson spine preference dropdown not showing up in the
+ character menu.
+ Kylerace:
+ - admin: admins/maintainers can now make the profiler focus on specific subsystems
+ by setting the subsystem var profile_focused to TRUE
+ LemonInTheDark:
+ - refactor: Fucks with how movement keys are handled. Please report any bugs
+ WinterDarkraven:
+ - rscadd: In an attempt to stop the greytide, NanoTrasen has increased security's
+ baton energy output. This has, through testing, done nothing but make the device
+ spark more than it used to.
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index 0b01e4dd6a8cb..fcb4e262d7c89 100644
Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ
diff --git a/icons/effects/random_spawners.dmi b/icons/effects/random_spawners.dmi
index c03b196a13798..08df14c0ffc99 100644
Binary files a/icons/effects/random_spawners.dmi and b/icons/effects/random_spawners.dmi differ
diff --git a/icons/hud/screen_alert.dmi b/icons/hud/screen_alert.dmi
index 289d02da46ada..7aa32c94b8e2a 100644
Binary files a/icons/hud/screen_alert.dmi and b/icons/hud/screen_alert.dmi differ
diff --git a/icons/mob/clothing/mask.dmi b/icons/mob/clothing/mask.dmi
index 4c05979ff2bad..24e8622344e4e 100644
Binary files a/icons/mob/clothing/mask.dmi and b/icons/mob/clothing/mask.dmi differ
diff --git a/icons/mob/clothing/neck.dmi b/icons/mob/clothing/neck.dmi
index fcfdb55f52250..5440bf9d99dae 100644
Binary files a/icons/mob/clothing/neck.dmi and b/icons/mob/clothing/neck.dmi differ
diff --git a/icons/mob/inhands/items/bedsheet_lefthand.dmi b/icons/mob/inhands/items/bedsheet_lefthand.dmi
index 1f2d7df00753d..2795277a183ca 100644
Binary files a/icons/mob/inhands/items/bedsheet_lefthand.dmi and b/icons/mob/inhands/items/bedsheet_lefthand.dmi differ
diff --git a/icons/mob/inhands/items/bedsheet_righthand.dmi b/icons/mob/inhands/items/bedsheet_righthand.dmi
index 5c831140c9eab..4fe73af823a4c 100644
Binary files a/icons/mob/inhands/items/bedsheet_righthand.dmi and b/icons/mob/inhands/items/bedsheet_righthand.dmi differ
diff --git a/icons/mob/inhands/weapons/staves_lefthand.dmi b/icons/mob/inhands/weapons/staves_lefthand.dmi
index 83504696b61a3..5e4eb552f2a5c 100644
Binary files a/icons/mob/inhands/weapons/staves_lefthand.dmi and b/icons/mob/inhands/weapons/staves_lefthand.dmi differ
diff --git a/icons/mob/inhands/weapons/staves_righthand.dmi b/icons/mob/inhands/weapons/staves_righthand.dmi
index c83e0eed3abee..9de32d8cd9533 100644
Binary files a/icons/mob/inhands/weapons/staves_righthand.dmi and b/icons/mob/inhands/weapons/staves_righthand.dmi differ
diff --git a/icons/mob/simple/arachnoid.dmi b/icons/mob/simple/arachnoid.dmi
index d17297f2ccf57..7e15fde685240 100644
Binary files a/icons/mob/simple/arachnoid.dmi and b/icons/mob/simple/arachnoid.dmi differ
diff --git a/icons/obj/bedsheets.dmi b/icons/obj/bedsheets.dmi
index 8db48b45fc684..daa0c3cdd7904 100644
Binary files a/icons/obj/bedsheets.dmi and b/icons/obj/bedsheets.dmi differ
diff --git a/icons/obj/weapons/guns/magic.dmi b/icons/obj/weapons/guns/magic.dmi
index 90eb4bdc669a5..3d7238a72bb0b 100644
Binary files a/icons/obj/weapons/guns/magic.dmi and b/icons/obj/weapons/guns/magic.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index aa58962102c40..0a32dc4496121 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -184,6 +184,7 @@
#include "code\__DEFINES\radiation.dm"
#include "code\__DEFINES\radio.dm"
#include "code\__DEFINES\radioactive_nebula.dm"
+#include "code\__DEFINES\random_spawner.dm"
#include "code\__DEFINES\reactions.dm"
#include "code\__DEFINES\reagents.dm"
#include "code\__DEFINES\reagents_specific_heat.dm"
@@ -2176,6 +2177,7 @@
#include "code\game\objects\effects\spawners\random\ai_module.dm"
#include "code\game\objects\effects\spawners\random\animalhide.dm"
#include "code\game\objects\effects\spawners\random\armory.dm"
+#include "code\game\objects\effects\spawners\random\bedsheet.dm"
#include "code\game\objects\effects\spawners\random\bureaucracy.dm"
#include "code\game\objects\effects\spawners\random\clothing.dm"
#include "code\game\objects\effects\spawners\random\contraband.dm"
@@ -3195,6 +3197,7 @@
#include "code\modules\antagonists\wizard\equipment\chuunibyou_spell.dm"
#include "code\modules\antagonists\wizard\equipment\enchanted_clown_suit.dm"
#include "code\modules\antagonists\wizard\equipment\soulstone.dm"
+#include "code\modules\antagonists\wizard\equipment\teleport_rod.dm"
#include "code\modules\antagonists\wizard\equipment\wizard_spellbook.dm"
#include "code\modules\antagonists\wizard\equipment\spellbook_entries\_entry.dm"
#include "code\modules\antagonists\wizard\equipment\spellbook_entries\assistance.dm"
diff --git a/tgui/packages/tgui/interfaces/AntagInfoNightmare.tsx b/tgui/packages/tgui/interfaces/AntagInfoNightmare.tsx
index 2552bf4b86e41..f0b897e764545 100644
--- a/tgui/packages/tgui/interfaces/AntagInfoNightmare.tsx
+++ b/tgui/packages/tgui/interfaces/AntagInfoNightmare.tsx
@@ -63,7 +63,7 @@ export const AntagInfoNightmare = (props) => {
Your twisted appendage. It will consume the light of what it
- touches, be it victim or object. After 15 seconds of being in
+ touches, be it victim or object. After 7 seconds of being in
jaunt, stabbing a foe will stun them or do extra damage.
diff --git a/tgui/packages/tgui/interfaces/OreBox.tsx b/tgui/packages/tgui/interfaces/OreBox.tsx
index bcafaa7245459..63716b021db40 100644
--- a/tgui/packages/tgui/interfaces/OreBox.tsx
+++ b/tgui/packages/tgui/interfaces/OreBox.tsx
@@ -4,40 +4,42 @@ import { useBackend } from '../backend';
import { Box, Button, Section, Table } from '../components';
import { Window } from '../layouts';
-type Data = {
- materials: Material[];
- boulders: number;
-};
-
type Material = {
- type: string;
name: string;
amount: number;
};
-const OREBOX_INFO = `All ores will be placed in here when you are wearing a
-mining stachel on your belt or in a pocket while dragging the ore box.`;
+type Data = {
+ materials: Material[];
+};
export const OreBox = (props) => {
const { act, data } = useBackend();
- const { materials, boulders } = data;
+ const { materials } = data;
return (
act('removeall')} />}
+ title="Ores & Boulders"
+ buttons={
+
+ }
>
- Ore
+ Item
Amount
- {materials.map((material) => (
-
+ {materials.map((material, id) => (
+
{toTitleCase(material.name)}
@@ -46,21 +48,12 @@ export const OreBox = (props) => {
))}
- {boulders > 0 && (
-
- Boulders
-
-
- {boulders}
-
-
-
- )}
- {OREBOX_INFO}
+ Ores can be loaded here via a mining satchel or by hand. Boulders
+ can also be stored here
Gibtonite is not accepted.
diff --git a/tgui/packages/tgui/interfaces/QuantumConsole.tsx b/tgui/packages/tgui/interfaces/QuantumConsole.tsx
index 8730220dff14b..dca8f2dbf1554 100644
--- a/tgui/packages/tgui/interfaces/QuantumConsole.tsx
+++ b/tgui/packages/tgui/interfaces/QuantumConsole.tsx
@@ -1,6 +1,6 @@
import { BooleanLike } from 'common/react';
-import { useBackend } from '../backend';
+import { useBackend, useSharedState } from '../backend';
import {
Button,
Collapsible,
@@ -10,6 +10,7 @@ import {
Section,
Stack,
Table,
+ Tabs,
Tooltip,
} from '../components';
import { TableCell, TableRow } from '../components/Table';
@@ -82,7 +83,7 @@ const getColor = (difficulty: number) => {
case Difficulty.High:
return 'bad';
default:
- return '';
+ return 'green';
}
};
@@ -101,6 +102,7 @@ export const QuantumConsole = (props) => {
const AccessView = (props) => {
const { act, data } = useBackend();
+ const [tab, setTab] = useSharedState('tab', 0);
if (!isConnected(data)) {
return No server connected!;
@@ -117,6 +119,10 @@ const AccessView = (props) => {
const sorted = available_domains.sort((a, b) => a.cost - b.cost);
+ const filtered = sorted.filter((domain) => {
+ return domain.difficulty === tab;
+ });
+
let selected;
if (generated_domain) {
selected = randomized
@@ -153,7 +159,45 @@ const AccessView = (props) => {
scrollable
title="Virtual Domains"
>
- {sorted.map((domain) => (
+
+ setTab(0)}
+ icon="chevron-down"
+ >
+ Peaceful
+
+ setTab(1)}
+ icon="chevron-down"
+ >
+ Easy
+
+ setTab(2)}
+ icon="chevron-down"
+ >
+ Medium
+
+ setTab(3)}
+ icon="chevron-down"
+ >
+ Hard {' '}
+
+
+ {filtered.map((domain) => (
))}
@@ -223,7 +267,6 @@ const DomainEntry = (props: DomainEntryProps) => {
title={
<>
{name}
- {difficulty === Difficulty.High && }
{!!is_modular && name !== '???' && }
>
}
@@ -232,19 +275,19 @@ const DomainEntry = (props: DomainEntryProps) => {
{desc}
{!!is_modular && ' (Modular)'}
- {difficulty === Difficulty.High && ' (Hard)'}
diff --git a/tools/UpdatePaths/Scripts/81435_bedsheet_spawners.txt b/tools/UpdatePaths/Scripts/81435_bedsheet_spawners.txt
new file mode 100644
index 0000000000000..85520034921d3
--- /dev/null
+++ b/tools/UpdatePaths/Scripts/81435_bedsheet_spawners.txt
@@ -0,0 +1,3 @@
+/obj/item/bedsheet/dorms : /obj/effect/spawner/random/bedsheet{@OLD}
+/obj/item/bedsheet/dorms_double : /obj/effect/spawner/random/bedsheet/double{@OLD}
+/obj/item/bedsheet/random/@SUBTYPES: /obj/effect/spawner/random/bedsheet/any/@SUBTYPES{@OLD}