diff --git a/_maps/RandomRuins/BeachRuins/beach_ancient_ruin.dmm b/_maps/RandomRuins/BeachRuins/beach_ancient_ruin.dmm
index be52f6a67f4..77df46175bc 100644
--- a/_maps/RandomRuins/BeachRuins/beach_ancient_ruin.dmm
+++ b/_maps/RandomRuins/BeachRuins/beach_ancient_ruin.dmm
@@ -606,7 +606,7 @@
/area/ruin/beach/complex)
"kK" = (
/obj/structure/closet/crate/bin,
-/obj/item/reagent_containers/food/snacks/breadslice/moldy,
+/obj/item/food/breadslice/moldy,
/obj/effect/decal/cleanable/dirt/dust,
/turf/open/floor/concrete/slab_4,
/area/ruin/beach/complex)
diff --git a/_maps/RandomRuins/BeachRuins/beach_float_resort.dmm b/_maps/RandomRuins/BeachRuins/beach_float_resort.dmm
index 29ad64707d8..d1a169d9b49 100644
--- a/_maps/RandomRuins/BeachRuins/beach_float_resort.dmm
+++ b/_maps/RandomRuins/BeachRuins/beach_float_resort.dmm
@@ -1314,10 +1314,10 @@
/area/ruin/beach/float_resort)
"Ja" = (
/obj/structure/table/wood,
-/obj/item/reagent_containers/food/condiment/ketchup{
+/obj/item/reagent_containers/condiment/ketchup{
pixel_y = 18
},
-/obj/item/reagent_containers/food/condiment/mayonnaise{
+/obj/item/reagent_containers/condiment/mayonnaise{
pixel_x = -8;
pixel_y = 16
},
diff --git a/_maps/RandomRuins/BeachRuins/beach_treasure_cove.dmm b/_maps/RandomRuins/BeachRuins/beach_treasure_cove.dmm
index a0b0e06bcd4..2edc53e9b1e 100644
--- a/_maps/RandomRuins/BeachRuins/beach_treasure_cove.dmm
+++ b/_maps/RandomRuins/BeachRuins/beach_treasure_cove.dmm
@@ -248,7 +248,7 @@
},
/obj/structure/table/wood/reinforced,
/obj/effect/decal/cleanable/dirt/dust,
-/obj/item/reagent_containers/food/snacks/breadslice/moldy{
+/obj/item/food/breadslice/moldy{
pixel_x = 3;
pixel_y = 6
},
diff --git a/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_village.dmm b/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_village.dmm
index a5ea6e81762..b48d659a13f 100644
--- a/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_village.dmm
+++ b/_maps/RandomRuins/IceRuins/icemoon_underground_abandoned_village.dmm
@@ -242,9 +242,9 @@
/obj/item/reagent_containers/food/snacks/fishmeat/carp,
/obj/item/reagent_containers/food/snacks/fishmeat/carp,
/obj/item/reagent_containers/food/snacks/fishmeat/carp,
-/obj/item/reagent_containers/food/condiment/pack/ketchup,
-/obj/item/reagent_containers/food/condiment/pack/ketchup,
-/obj/item/reagent_containers/food/condiment/pack/ketchup,
+/obj/item/reagent_containers/condiment/pack/ketchup,
+/obj/item/reagent_containers/condiment/pack/ketchup,
+/obj/item/reagent_containers/condiment/pack/ketchup,
/obj/effect/turf_decal/corner/opaque/black{
dir = 1
},
diff --git a/_maps/RandomRuins/JungleRuins/jungle_cavecrew.dmm b/_maps/RandomRuins/JungleRuins/jungle_cavecrew.dmm
index 5c4e6c34e7a..3fb56a9ae0c 100644
--- a/_maps/RandomRuins/JungleRuins/jungle_cavecrew.dmm
+++ b/_maps/RandomRuins/JungleRuins/jungle_cavecrew.dmm
@@ -1034,7 +1034,7 @@
/obj/item/reagent_containers/food/snacks/rationpack,
/obj/item/reagent_containers/food/snacks/rationpack,
/obj/item/reagent_containers/food/snacks/rationpack,
-/obj/item/reagent_containers/food/condiment/enzyme{
+/obj/item/reagent_containers/condiment/enzyme{
pixel_x = -8;
pixel_y = 5
},
diff --git a/_maps/RandomRuins/JungleRuins/jungle_interceptor.dmm b/_maps/RandomRuins/JungleRuins/jungle_interceptor.dmm
index 057b0cbdbe8..aab9b566f78 100644
--- a/_maps/RandomRuins/JungleRuins/jungle_interceptor.dmm
+++ b/_maps/RandomRuins/JungleRuins/jungle_interceptor.dmm
@@ -41,7 +41,7 @@
dir = 5
},
/obj/item/stack/cable_coil/cut/green,
-/obj/item/reagent_containers/food/condiment/peppermill,
+/obj/item/reagent_containers/condiment/peppermill,
/turf/open/floor/plating/rust,
/area/ruin/jungle/interceptor/starhall)
"au" = (
@@ -992,9 +992,9 @@
/obj/structure/closet/wall/directional/east,
/obj/item/reagent_containers/food/snacks/meat/slab/monkey,
/obj/item/reagent_containers/food/snacks/meat/slab/monkey,
-/obj/item/reagent_containers/food/condiment/sugar,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/rice,
+/obj/item/reagent_containers/condiment/sugar,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/rice,
/turf/open/floor/plasteel/tech/techmaint,
/area/ruin/jungle/interceptor/starhall)
"hO" = (
@@ -2887,7 +2887,7 @@
icon_state = "1-2"
},
/obj/effect/decal/cleanable/dirt/dust,
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
pixel_x = 5;
pixel_y = 5
},
diff --git a/_maps/RandomRuins/JungleRuins/jungle_paradise.dmm b/_maps/RandomRuins/JungleRuins/jungle_paradise.dmm
index 5d4f979255f..9e2881d763e 100644
--- a/_maps/RandomRuins/JungleRuins/jungle_paradise.dmm
+++ b/_maps/RandomRuins/JungleRuins/jungle_paradise.dmm
@@ -1466,19 +1466,19 @@
pixel_x = 9
},
/obj/structure/table/reinforced,
-/obj/item/reagent_containers/food/condiment/enzyme{
+/obj/item/reagent_containers/condiment/enzyme{
pixel_x = -3;
pixel_y = 6
},
/obj/item/reagent_containers/glass/beaker{
pixel_x = -2
},
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
desc = "Often used to flavor food or make people sneeze. Fashionably moved to the left side of the table.";
pixel_x = -8;
pixel_y = 2
},
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
desc = "Salt. From space oceans, presumably. A staple of modern medicine.";
pixel_x = -8;
pixel_y = 12
@@ -5382,7 +5382,7 @@
"Ih" = (
/obj/structure/table/wood,
/obj/machinery/light/directional/south,
-/obj/item/reagent_containers/food/snacks/breadslice/moldy{
+/obj/item/food/breadslice/moldy{
pixel_x = -8
},
/obj/item/reagent_containers/food/snacks/grown/berries/poison{
@@ -7510,7 +7510,7 @@
},
/obj/effect/mob_spawn/human/corpse/nanotrasensoldier,
/obj/effect/decal/cleanable/vomit/old,
-/obj/item/reagent_containers/food/snacks/breadslice/moldy{
+/obj/item/food/breadslice/moldy{
pixel_x = -4;
pixel_y = 16
},
diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_biodome_winter.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_biodome_winter.dmm
index d4025ce2b21..dace0cea74c 100644
--- a/_maps/RandomRuins/LavaRuins/lavaland_surface_biodome_winter.dmm
+++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_biodome_winter.dmm
@@ -829,7 +829,7 @@
/area/ruin/unpowered/winter_biodome)
"kb" = (
/obj/structure/table/wood,
-/obj/item/reagent_containers/food/snacks/breadslice/plain,
+/obj/item/food/breadslice/plain,
/obj/item/reagent_containers/food/snacks/grown/cabbage,
/turf/open/floor/wood,
/area/ruin/unpowered/winter_biodome/cabin)
@@ -1006,8 +1006,8 @@
"oR" = (
/obj/effect/turf_decal/corner/opaque/terragovblue/diagonal,
/obj/structure/closet/secure_closet/freezer,
-/obj/item/reagent_containers/food/snacks/store/bread/plain,
-/obj/item/reagent_containers/food/snacks/store/bread/plain,
+/obj/item/food/bread/plain,
+/obj/item/food/bread/plain,
/obj/item/reagent_containers/food/snacks/grown/cabbage,
/obj/item/reagent_containers/food/snacks/grown/cabbage,
/obj/effect/decal/cleanable/dirt/dust,
diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_pizzaparty.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_pizzaparty.dmm
index d035e25f5cb..330311a5269 100644
--- a/_maps/RandomRuins/LavaRuins/lavaland_surface_pizzaparty.dmm
+++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_pizzaparty.dmm
@@ -205,7 +205,7 @@
/area/ruin/unpowered)
"F" = (
/obj/structure/table/wood,
-/obj/item/reagent_containers/food/snacks/store/cake/birthday,
+/obj/item/food/cake/birthday,
/turf/open/floor/wood{
initial_gas_mix = "o2=14;n2=5;co2=13;TEMP=300"
},
diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_wrecked_factory.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_wrecked_factory.dmm
index f4f938f0b7b..92571390c95 100644
--- a/_maps/RandomRuins/LavaRuins/lavaland_surface_wrecked_factory.dmm
+++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_wrecked_factory.dmm
@@ -705,11 +705,11 @@
/obj/item/reagent_containers/food/snacks/meat/slab,
/obj/item/reagent_containers/food/snacks/meat/slab,
/obj/item/reagent_containers/food/snacks/meat/slab,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/rice,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/rice,
/obj/item/storage/fancy/egg_box,
-/obj/item/reagent_containers/food/condiment/soymilk,
-/obj/item/reagent_containers/food/condiment/milk,
+/obj/item/reagent_containers/condiment/soymilk,
+/obj/item/reagent_containers/condiment/milk,
/obj/item/storage/box/ingredients/vegetarian,
/turf/open/floor/wood,
/area/ruin/lavaland/factory/adminstrative)
@@ -1578,11 +1578,11 @@
/obj/effect/turf_decal/corner/opaque/bar,
/obj/machinery/light/directional/east,
/obj/item/storage/box/ingredients/vegetarian,
-/obj/item/reagent_containers/food/condiment/milk,
-/obj/item/reagent_containers/food/condiment/soymilk,
+/obj/item/reagent_containers/condiment/milk,
+/obj/item/reagent_containers/condiment/soymilk,
/obj/item/storage/fancy/egg_box,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/rice,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/rice,
/obj/item/reagent_containers/food/snacks/meat/slab,
/obj/item/reagent_containers/food/snacks/meat/slab,
/obj/item/reagent_containers/food/snacks/meat/slab,
diff --git a/_maps/RandomRuins/RockRuins/rockplanet_budgetcuts.dmm b/_maps/RandomRuins/RockRuins/rockplanet_budgetcuts.dmm
index b837eff979f..ef9240170e4 100644
--- a/_maps/RandomRuins/RockRuins/rockplanet_budgetcuts.dmm
+++ b/_maps/RandomRuins/RockRuins/rockplanet_budgetcuts.dmm
@@ -47,7 +47,7 @@
/obj/machinery/reagentgrinder{
pixel_y = 5
},
-/obj/item/reagent_containers/food/condiment/enzyme{
+/obj/item/reagent_containers/condiment/enzyme{
pixel_x = -2;
pixel_y = 6
},
@@ -517,7 +517,7 @@
pixel_x = -1;
pixel_y = 3
},
-/obj/item/reagent_containers/food/condiment/sugar{
+/obj/item/reagent_containers/condiment/sugar{
pixel_x = 5;
pixel_y = 5
},
@@ -769,12 +769,12 @@
"mO" = (
/obj/structure/table/reinforced,
/obj/machinery/door/firedoor,
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
desc = "Often used to flavor food or make people sneeze. Fashionably moved to the left side of the table.";
pixel_x = -8;
pixel_y = 2
},
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
desc = "Salt. From space oceans, presumably. A staple of modern medicine.";
pixel_x = -8;
pixel_y = 12
diff --git a/_maps/RandomRuins/RockRuins/rockplanet_crash.dmm b/_maps/RandomRuins/RockRuins/rockplanet_crash.dmm
index 05feaf75086..aa45410eee0 100644
--- a/_maps/RandomRuins/RockRuins/rockplanet_crash.dmm
+++ b/_maps/RandomRuins/RockRuins/rockplanet_crash.dmm
@@ -395,11 +395,11 @@
/area/ruin/unpowered)
"mq" = (
/obj/structure/table,
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
pixel_x = -10;
pixel_y = 10
},
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
pixel_x = 6;
pixel_y = -2
},
@@ -1214,11 +1214,11 @@
pixel_x = -3;
pixel_y = 3
},
-/obj/item/reagent_containers/food/condiment/flour{
+/obj/item/reagent_containers/condiment/flour{
pixel_x = -3;
pixel_y = 3
},
-/obj/item/reagent_containers/food/condiment/flour{
+/obj/item/reagent_containers/condiment/flour{
pixel_x = -3;
pixel_y = 3
},
diff --git a/_maps/RandomRuins/SandRuins/whitesands_surface_camp_drugstore.dmm b/_maps/RandomRuins/SandRuins/whitesands_surface_camp_drugstore.dmm
index 7f69ca337c0..6377f663c00 100644
--- a/_maps/RandomRuins/SandRuins/whitesands_surface_camp_drugstore.dmm
+++ b/_maps/RandomRuins/SandRuins/whitesands_surface_camp_drugstore.dmm
@@ -33,7 +33,7 @@
"f" = (
/obj/structure/rack,
/obj/item/reagent_containers/food/snacks/cornchips,
-/obj/item/reagent_containers/food/snacks/butterdog,
+/obj/item/food/butterdog,
/obj/item/reagent_containers/food/snacks/candy,
/obj/item/reagent_containers/food/snacks/canned/peaches,
/obj/item/reagent_containers/food/drinks/bottle/sarsaparilla,
diff --git a/_maps/RandomRuins/SandRuins/whitesands_surface_waterplant.dmm b/_maps/RandomRuins/SandRuins/whitesands_surface_waterplant.dmm
index ae51575ef49..4c9744c6787 100644
--- a/_maps/RandomRuins/SandRuins/whitesands_surface_waterplant.dmm
+++ b/_maps/RandomRuins/SandRuins/whitesands_surface_waterplant.dmm
@@ -425,7 +425,7 @@
/area/ruin/powered)
"lN" = (
/obj/structure/table/wood/fancy,
-/obj/item/reagent_containers/food/snacks/cakeslice/lime,
+/obj/item/food/cakeslice/lime,
/turf/open/floor/carpet,
/area/ruin/powered)
"lQ" = (
@@ -663,7 +663,7 @@
/area/ruin/powered)
"qv" = (
/obj/structure/table/wood/fancy,
-/obj/item/reagent_containers/food/snacks/cakeslice/chocolate,
+/obj/item/food/cakeslice/chocolate,
/turf/open/floor/carpet,
/area/ruin/powered)
"qC" = (
@@ -1064,7 +1064,7 @@
/area/ruin/powered)
"Aj" = (
/obj/structure/table/wood/fancy,
-/obj/item/reagent_containers/food/snacks/cakeslice/cheese,
+/obj/item/food/cakeslice/cheese,
/turf/open/floor/carpet,
/area/ruin/powered)
"AC" = (
diff --git a/_maps/RandomRuins/SpaceRuins/onehalf.dmm b/_maps/RandomRuins/SpaceRuins/onehalf.dmm
index 85f087ec38a..0ee697760b2 100644
--- a/_maps/RandomRuins/SpaceRuins/onehalf.dmm
+++ b/_maps/RandomRuins/SpaceRuins/onehalf.dmm
@@ -2034,12 +2034,6 @@
},
/turf/open/space,
/area/space/nearstation)
-"Ra" = (
-/obj/structure/frame/computer{
- dir = 8
- },
-/turf/open/floor/plasteel,
-/area/ruin/space/has_grav/onehalf)
"Rv" = (
/obj/effect/turf_decal/siding/wood,
/turf/open/floor/wood/airless{
@@ -3328,7 +3322,7 @@ UM
cp
ZX
Gv
-Ra
+Gv
cU
YE
bU
diff --git a/_maps/RandomRuins/SpaceRuins/power_puzzle.dmm b/_maps/RandomRuins/SpaceRuins/power_puzzle.dmm
index 586c37670a5..3de92c538ab 100644
--- a/_maps/RandomRuins/SpaceRuins/power_puzzle.dmm
+++ b/_maps/RandomRuins/SpaceRuins/power_puzzle.dmm
@@ -1739,7 +1739,7 @@
/area/ruin/space/has_grav/powerpuzzle/secure)
"sc" = (
/obj/effect/mob_spawn/human/corpse/cargo_tech,
-/obj/item/reagent_containers/food/snacks/cakeslice/birthday,
+/obj/item/food/cakeslice/birthday,
/obj/effect/decal/cleanable/confetti,
/obj/machinery/light/small/broken/directional/east,
/obj/structure/toilet,
diff --git a/_maps/RandomRuins/SpaceRuins/spacemall.dmm b/_maps/RandomRuins/SpaceRuins/spacemall.dmm
index b225765a1a3..50c0e6c185a 100644
--- a/_maps/RandomRuins/SpaceRuins/spacemall.dmm
+++ b/_maps/RandomRuins/SpaceRuins/spacemall.dmm
@@ -1430,10 +1430,10 @@
name = "Kiosk Shutters";
dir = 4
},
-/obj/item/reagent_containers/food/condiment/sugar{
+/obj/item/reagent_containers/condiment/sugar{
pixel_y = 5
},
-/obj/item/reagent_containers/food/condiment/sugar,
+/obj/item/reagent_containers/condiment/sugar,
/obj/structure/closet/wall/directional/north,
/obj/item/spacecash/bundle/c100,
/turf/open/floor/plasteel/dark,
@@ -2212,10 +2212,10 @@
"iE" = (
/obj/effect/turf_decal/corner/transparent/black/diagonal,
/obj/structure/table/reinforced,
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
pixel_x = 4
},
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
pixel_x = -4
},
/obj/machinery/door/poddoor/shutters{
@@ -3479,10 +3479,10 @@
/area/ruin/space/has_grav/spacemall)
"ns" = (
/obj/effect/turf_decal/corner/transparent/black/diagonal,
-/obj/item/reagent_containers/food/condiment/sugar{
+/obj/item/reagent_containers/condiment/sugar{
pixel_y = -5
},
-/obj/item/reagent_containers/food/condiment/flour,
+/obj/item/reagent_containers/condiment/flour,
/obj/structure/table,
/turf/open/floor/plasteel/white,
/area/ruin/space/has_grav/spacemall/dorms)
@@ -3607,7 +3607,7 @@
/area/ruin/space/has_grav/spacemall/shop)
"nS" = (
/obj/effect/turf_decal/corner/transparent/black/diagonal,
-/obj/item/reagent_containers/food/snacks/store/bread/spidermeat,
+/obj/item/food/bread/spidermeat,
/obj/structure/table,
/turf/open/floor/plasteel/white,
/area/ruin/space/has_grav/spacemall/dorms)
@@ -5967,15 +5967,15 @@
pixel_x = -30;
dir = 4
},
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/sugar,
-/obj/item/reagent_containers/food/condiment/sugar,
-/obj/item/reagent_containers/food/condiment/sugar,
-/obj/item/reagent_containers/food/condiment/rice,
-/obj/item/reagent_containers/food/condiment/rice,
-/obj/item/reagent_containers/food/condiment/rice,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/sugar,
+/obj/item/reagent_containers/condiment/sugar,
+/obj/item/reagent_containers/condiment/sugar,
+/obj/item/reagent_containers/condiment/rice,
+/obj/item/reagent_containers/condiment/rice,
+/obj/item/reagent_containers/condiment/rice,
/obj/structure/table,
/turf/open/floor/plasteel/white,
/area/ruin/space/has_grav/spacemall/dorms)
@@ -6777,7 +6777,7 @@
dir = 8
},
/obj/item/trash/plate,
-/obj/item/reagent_containers/food/snacks/breadslice/moldy{
+/obj/item/food/breadslice/moldy{
pixel_y = 6
},
/obj/item/reagent_containers/food/snacks/spiderling{
diff --git a/_maps/RandomRuins/WasteRuins/wasteplanet_crash_kitchen.dmm b/_maps/RandomRuins/WasteRuins/wasteplanet_crash_kitchen.dmm
index 0d3857067cf..b9b2e4fb429 100644
--- a/_maps/RandomRuins/WasteRuins/wasteplanet_crash_kitchen.dmm
+++ b/_maps/RandomRuins/WasteRuins/wasteplanet_crash_kitchen.dmm
@@ -82,11 +82,11 @@
/area/ruin/unpowered)
"lN" = (
/obj/structure/table/reinforced,
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
pixel_x = -6;
pixel_y = 2
},
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
pixel_y = 2
},
/obj/machinery/door/firedoor,
@@ -216,7 +216,7 @@
/obj/item/storage/bag/tray,
/obj/item/reagent_containers/food/drinks/shaker,
/obj/item/reagent_containers/glass/rag,
-/obj/item/reagent_containers/food/condiment/enzyme,
+/obj/item/reagent_containers/condiment/enzyme,
/obj/item/kitchen/knife,
/obj/effect/turf_decal/corner/opaque/white{
dir = 1
diff --git a/_maps/RandomRuins/WasteRuins/wasteplanet_fortress_of_solitide.dmm b/_maps/RandomRuins/WasteRuins/wasteplanet_fortress_of_solitide.dmm
index a2c683e4ec6..cef1ce41ee0 100644
--- a/_maps/RandomRuins/WasteRuins/wasteplanet_fortress_of_solitide.dmm
+++ b/_maps/RandomRuins/WasteRuins/wasteplanet_fortress_of_solitide.dmm
@@ -330,7 +330,7 @@
/area/ruin/powered)
"cS" = (
/obj/structure/table/wood/fancy/orange,
-/obj/item/reagent_containers/food/snacks/store/cake/clown_cake,
+/obj/item/food/cake/clown_cake,
/turf/open/floor/plating,
/area/ruin/powered)
"cU" = (
@@ -462,7 +462,7 @@
/area/ruin/powered)
"ep" = (
/obj/effect/spawner/lootdrop/maintenance/two,
-/obj/item/reagent_containers/food/snacks/butterdog,
+/obj/item/food/butterdog,
/obj/structure/table,
/obj/item/coin/plastic,
/turf/open/floor/plating,
@@ -757,7 +757,7 @@
/turf/open/floor/plating,
/area/ruin/powered)
"hd" = (
-/obj/item/reagent_containers/food/snacks/cakeslice/clown_slice,
+/obj/item/food/cakeslice/clown_slice,
/turf/open/floor/mineral/gold,
/area/ruin/powered)
"he" = (
@@ -1390,7 +1390,7 @@
/area/ruin/powered)
"nc" = (
/obj/structure/table/reinforced,
-/obj/item/reagent_containers/food/snacks/cakeslice/brain,
+/obj/item/food/cakeslice/brain,
/turf/open/floor/plating,
/area/ruin/powered)
"nd" = (
diff --git a/_maps/map_files/generic/CentCom.dmm b/_maps/map_files/generic/CentCom.dmm
index 3fb9c52f2af..34e50da5ee1 100644
--- a/_maps/map_files/generic/CentCom.dmm
+++ b/_maps/map_files/generic/CentCom.dmm
@@ -3043,10 +3043,6 @@
/obj/structure/table/wood,
/turf/open/floor/plasteel,
/area/wizard_station)
-"aEX" = (
-/obj/structure/table/wood,
-/turf/open/floor/plasteel,
-/area/wizard_station)
"aEY" = (
/obj/structure/table/wood,
/obj/item/bikehorn/golden{
@@ -4412,7 +4408,7 @@
/turf/open/floor/plasteel/grimy,
/area/centcom/ferry)
"aOO" = (
-/obj/item/reagent_containers/food/condiment/enzyme,
+/obj/item/reagent_containers/condiment/enzyme,
/obj/item/reagent_containers/food/drinks/shaker,
/obj/item/book/manual/wiki/drinks,
/obj/structure/closet/crate,
@@ -4520,11 +4516,11 @@
"aPq" = (
/obj/structure/table/reinforced,
/obj/machinery/door/firedoor,
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
pixel_x = -8;
pixel_y = 5
},
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
pixel_x = -8
},
/obj/item/reagent_containers/food/drinks/britcup,
@@ -10979,11 +10975,11 @@
/area/centcom/control)
"lii" = (
/obj/structure/table/reinforced,
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
pixel_x = -8;
pixel_y = 5
},
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
pixel_x = -8
},
/obj/item/kitchen/knife,
@@ -11574,8 +11570,8 @@
/obj/item/reagent_containers/food/snacks/meat/slab/xeno,
/obj/item/reagent_containers/food/snacks/meat/slab/xeno,
/obj/item/reagent_containers/food/snacks/meat/slab/xeno,
-/obj/item/reagent_containers/food/snacks/spaghetti,
-/obj/item/reagent_containers/food/snacks/spaghetti,
+/obj/item/food/spaghetti/raw,
+/obj/item/food/spaghetti/raw,
/obj/item/reagent_containers/food/snacks/meat/rawcutlet,
/obj/item/reagent_containers/food/snacks/meat/rawcutlet,
/obj/item/reagent_containers/food/snacks/meat/rawcutlet,
@@ -14505,7 +14501,7 @@
"rUH" = (
/obj/structure/table/reinforced,
/obj/item/reagent_containers/food/snacks/mint,
-/obj/item/reagent_containers/food/condiment/enzyme{
+/obj/item/reagent_containers/condiment/enzyme{
pixel_y = 5
},
/obj/effect/turf_decal/corner/transparent/neutral{
@@ -23766,7 +23762,7 @@ auE
aEc
auE
aAx
-aEX
+aEW
aqZ
aqZ
aGr
diff --git a/_maps/outpost/nanotrasen_asteroid.dmm b/_maps/outpost/nanotrasen_asteroid.dmm
index 7621ebb412f..633747e6703 100644
--- a/_maps/outpost/nanotrasen_asteroid.dmm
+++ b/_maps/outpost/nanotrasen_asteroid.dmm
@@ -125,7 +125,7 @@
/area/outpost/maintenance/fore)
"aE" = (
/obj/structure/table/wood,
-/obj/item/reagent_containers/food/snacks/cakeslice/birthday{
+/obj/item/food/cakeslice/birthday{
pixel_x = -3;
pixel_y = -5
},
@@ -4360,7 +4360,7 @@
/area/outpost/maintenance/fore)
"qg" = (
/obj/structure/table/reinforced,
-/obj/item/reagent_containers/food/condiment/enzyme{
+/obj/item/reagent_containers/condiment/enzyme{
pixel_x = -2;
pixel_y = 6
},
@@ -4716,12 +4716,12 @@
pixel_x = 6;
pixel_y = 6
},
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
desc = "Often used to flavor food or make people sneeze. Fashionably moved to the left side of the table.";
pixel_x = -8;
pixel_y = 2
},
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
desc = "Salt. From space oceans, presumably. A staple of modern medicine.";
pixel_x = -8;
pixel_y = 12
@@ -5022,12 +5022,12 @@
/area/outpost/cargo)
"rZ" = (
/obj/structure/table/reinforced,
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
desc = "Often used to flavor food or make people sneeze. Fashionably moved to the left side of the table.";
pixel_x = -8;
pixel_y = 2
},
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
desc = "Salt. From space oceans, presumably. A staple of modern medicine.";
pixel_x = -8;
pixel_y = 12
@@ -10124,12 +10124,12 @@
/area/outpost/operations)
"Ju" = (
/obj/structure/table,
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
desc = "Often used to flavor food or make people sneeze. Fashionably moved to the left side of the table.";
pixel_x = 9;
pixel_y = 2
},
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
desc = "Salt. From space oceans, presumably. A staple of modern medicine.";
pixel_x = 9;
pixel_y = 12
@@ -11245,7 +11245,7 @@
/area/outpost/maintenance/fore)
"NA" = (
/obj/structure/table,
-/obj/item/reagent_containers/food/condiment/enzyme{
+/obj/item/reagent_containers/condiment/enzyme{
pixel_x = -2;
pixel_y = 6
},
@@ -11761,7 +11761,7 @@
pixel_x = -5;
pixel_y = 5
},
-/obj/item/reagent_containers/food/condiment/soysauce{
+/obj/item/reagent_containers/condiment/soysauce{
pixel_x = -6;
pixel_y = 7
},
@@ -12995,7 +12995,7 @@
/obj/structure/table/reinforced,
/obj/machinery/light/directional/south,
/obj/item/reagent_containers/glass/beaker,
-/obj/item/reagent_containers/food/condiment/enzyme{
+/obj/item/reagent_containers/condiment/enzyme{
pixel_x = -2;
pixel_y = 6
},
@@ -14099,12 +14099,12 @@
pixel_x = 6;
pixel_y = 6
},
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
desc = "Often used to flavor food or make people sneeze. Fashionably moved to the left side of the table.";
pixel_x = -8;
pixel_y = 2
},
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
desc = "Salt. From space oceans, presumably. A staple of modern medicine.";
pixel_x = -8;
pixel_y = 12
diff --git a/_maps/shuttles/independent/independent_beluga.dmm b/_maps/shuttles/independent/independent_beluga.dmm
index 81e3f3048f6..9ae7355d443 100644
--- a/_maps/shuttles/independent/independent_beluga.dmm
+++ b/_maps/shuttles/independent/independent_beluga.dmm
@@ -1458,29 +1458,29 @@
/obj/item/storage/bag/tray{
pixel_y = 6
},
-/obj/item/reagent_containers/food/condiment/soysauce{
+/obj/item/reagent_containers/condiment/soysauce{
pixel_x = 6;
pixel_y = 11
},
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
desc = "Salt. From space oceans, presumably. A staple of modern medicine.";
pixel_x = -5;
pixel_y = 12
},
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
desc = "Often used to flavor food or make people sneeze. Fashionably moved to the left side of the table.";
pixel_x = -3;
pixel_y = 8
},
-/obj/item/reagent_containers/food/condiment/pack/ketchup{
+/obj/item/reagent_containers/condiment/pack/ketchup{
pixel_x = -8;
pixel_y = -4
},
-/obj/item/reagent_containers/food/condiment/pack/ketchup{
+/obj/item/reagent_containers/condiment/pack/ketchup{
pixel_x = -8;
pixel_y = -4
},
-/obj/item/reagent_containers/food/condiment/pack/ketchup{
+/obj/item/reagent_containers/condiment/pack/ketchup{
pixel_x = -8;
pixel_y = -4
},
@@ -3203,42 +3203,42 @@
/area/ship/crew/dorm)
"EP" = (
/obj/structure/closet/secure_closet/freezer/wall/directional/north,
-/obj/item/reagent_containers/food/condiment/rice{
+/obj/item/reagent_containers/condiment/rice{
pixel_y = 17
},
-/obj/item/reagent_containers/food/condiment/flour{
+/obj/item/reagent_containers/condiment/flour{
pixel_x = 6;
pixel_y = 10
},
-/obj/item/reagent_containers/food/condiment/flour{
+/obj/item/reagent_containers/condiment/flour{
pixel_x = 6;
pixel_y = 4
},
-/obj/item/reagent_containers/food/condiment/sugar{
+/obj/item/reagent_containers/condiment/sugar{
pixel_x = -7;
pixel_y = 12
},
-/obj/item/reagent_containers/food/condiment/sugar{
+/obj/item/reagent_containers/condiment/sugar{
pixel_x = -7;
pixel_y = 6
},
-/obj/item/reagent_containers/food/condiment/milk{
+/obj/item/reagent_containers/condiment/milk{
pixel_x = -10;
pixel_y = -3
},
-/obj/item/reagent_containers/food/condiment/milk{
+/obj/item/reagent_containers/condiment/milk{
pixel_x = -10;
pixel_y = -3
},
-/obj/item/reagent_containers/food/condiment/milk{
+/obj/item/reagent_containers/condiment/milk{
pixel_x = -10;
pixel_y = -3
},
-/obj/item/reagent_containers/food/condiment/soymilk{
+/obj/item/reagent_containers/condiment/soymilk{
pixel_y = -5;
pixel_x = -4
},
-/obj/item/reagent_containers/food/condiment/soymilk{
+/obj/item/reagent_containers/condiment/soymilk{
pixel_y = -5;
pixel_x = -4
},
@@ -4150,7 +4150,7 @@
pixel_y = 17;
pixel_x = -7
},
-/obj/item/reagent_containers/food/condiment/enzyme{
+/obj/item/reagent_containers/condiment/enzyme{
pixel_x = -6;
pixel_y = 10
},
diff --git a/_maps/shuttles/independent/independent_daniel.dmm b/_maps/shuttles/independent/independent_daniel.dmm
index 6caad653863..b9a6abb0009 100644
--- a/_maps/shuttles/independent/independent_daniel.dmm
+++ b/_maps/shuttles/independent/independent_daniel.dmm
@@ -441,18 +441,18 @@
/obj/structure/closet/secure_closet/freezer/kitchen,
/obj/item/storage/fancy/egg_box,
/obj/item/storage/fancy/egg_box,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/rice,
-/obj/item/reagent_containers/food/condiment/rice,
-/obj/item/reagent_containers/food/condiment/sugar,
-/obj/item/reagent_containers/food/condiment/sugar,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/rice,
+/obj/item/reagent_containers/condiment/rice,
+/obj/item/reagent_containers/condiment/sugar,
+/obj/item/reagent_containers/condiment/sugar,
/obj/effect/turf_decal/corner/opaque/white/diagonal,
/obj/machinery/door/window/brigdoor/southleft,
-/obj/item/reagent_containers/food/condiment/milk,
-/obj/item/reagent_containers/food/condiment/milk,
-/obj/item/reagent_containers/food/condiment/milk,
-/obj/item/reagent_containers/food/condiment/milk,
+/obj/item/reagent_containers/condiment/milk,
+/obj/item/reagent_containers/condiment/milk,
+/obj/item/reagent_containers/condiment/milk,
+/obj/item/reagent_containers/condiment/milk,
/turf/open/floor/plasteel,
/area/ship/crew/library)
"iM" = (
diff --git a/_maps/shuttles/independent/independent_kilo.dmm b/_maps/shuttles/independent/independent_kilo.dmm
index f923fd26a23..7e441191e24 100644
--- a/_maps/shuttles/independent/independent_kilo.dmm
+++ b/_maps/shuttles/independent/independent_kilo.dmm
@@ -1423,7 +1423,7 @@
pixel_x = -6;
pixel_y = 8
},
-/obj/item/reagent_containers/food/condiment/enzyme{
+/obj/item/reagent_containers/condiment/enzyme{
layer = 5;
pixel_x = 12;
pixel_y = 6
diff --git a/_maps/shuttles/independent/independent_lagoon.dmm b/_maps/shuttles/independent/independent_lagoon.dmm
index 07057b9d0a5..b5565c86a54 100644
--- a/_maps/shuttles/independent/independent_lagoon.dmm
+++ b/_maps/shuttles/independent/independent_lagoon.dmm
@@ -1375,7 +1375,7 @@
icon_state = "2-4"
},
/obj/item/kitchen/rollingpin,
-/obj/item/reagent_containers/food/condiment/enzyme,
+/obj/item/reagent_containers/condiment/enzyme,
/obj/effect/turf_decal/corner/opaque/white/diagonal,
/obj/item/radio/intercom/directional/west,
/turf/open/floor/plasteel,
@@ -1578,12 +1578,12 @@
/obj/structure/closet/secure_closet/freezer/kitchen,
/obj/item/storage/fancy/egg_box,
/obj/item/storage/fancy/egg_box,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/rice,
-/obj/item/reagent_containers/food/condiment/rice,
-/obj/item/reagent_containers/food/condiment/sugar,
-/obj/item/reagent_containers/food/condiment/sugar,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/rice,
+/obj/item/reagent_containers/condiment/rice,
+/obj/item/reagent_containers/condiment/sugar,
+/obj/item/reagent_containers/condiment/sugar,
/obj/effect/turf_decal/corner/opaque/white/diagonal,
/obj/machinery/firealarm/directional/north,
/turf/open/floor/plasteel,
@@ -2078,7 +2078,7 @@
pixel_y = 1
},
/obj/item/toy/cards/deck,
-/obj/item/reagent_containers/food/snacks/butterbiscuit{
+/obj/item/food/butterbiscuit{
pixel_x = 6;
pixel_y = 6
},
@@ -3948,11 +3948,11 @@
/area/ship/hallway/aft)
"An" = (
/obj/structure/table/reinforced,
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
pixel_x = 5;
pixel_y = 5
},
-/obj/item/reagent_containers/food/condiment/peppermill,
+/obj/item/reagent_containers/condiment/peppermill,
/obj/machinery/door/firedoor/border_only,
/obj/effect/turf_decal/corner/opaque/white/diagonal,
/turf/open/floor/plasteel,
diff --git a/_maps/shuttles/independent/independent_mudskipper.dmm b/_maps/shuttles/independent/independent_mudskipper.dmm
index 7780e20406f..4984a9563a3 100644
--- a/_maps/shuttles/independent/independent_mudskipper.dmm
+++ b/_maps/shuttles/independent/independent_mudskipper.dmm
@@ -1971,11 +1971,11 @@
color = "#543C30";
dir = 8
},
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
pixel_x = 10;
pixel_y = 5
},
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
pixel_x = 18;
pixel_y = 2
},
diff --git a/_maps/shuttles/independent/independent_rigger.dmm b/_maps/shuttles/independent/independent_rigger.dmm
index a15470127ac..e8bc6b37f91 100644
--- a/_maps/shuttles/independent/independent_rigger.dmm
+++ b/_maps/shuttles/independent/independent_rigger.dmm
@@ -241,8 +241,8 @@
/obj/item/storage/cans/sixbeer,
/obj/effect/spawner/lootdrop/ration,
/obj/effect/spawner/lootdrop/ration,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/sugar,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/sugar,
/obj/item/radio/intercom/directional/north,
/turf/open/floor/plasteel/white,
/area/ship/crew/canteen)
@@ -4400,10 +4400,10 @@
},
/obj/effect/turf_decal/corner/opaque/yellow/diagonal,
/obj/structure/table,
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
pixel_x = -7
},
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
pixel_x = -14
},
/turf/open/floor/plasteel/white,
diff --git a/_maps/shuttles/independent/independent_schmiedeberg.dmm b/_maps/shuttles/independent/independent_schmiedeberg.dmm
index ca0794db7f5..48729d87bd7 100644
--- a/_maps/shuttles/independent/independent_schmiedeberg.dmm
+++ b/_maps/shuttles/independent/independent_schmiedeberg.dmm
@@ -68,13 +68,13 @@
"aO" = (
/obj/structure/table,
/obj/item/storage/bag/tray,
-/obj/item/reagent_containers/food/condiment/enzyme,
+/obj/item/reagent_containers/condiment/enzyme,
/obj/item/kitchen/knife,
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
pixel_x = -5;
pixel_y = 10
},
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
pixel_x = 3;
pixel_y = 11
},
@@ -2517,13 +2517,13 @@
/obj/structure/closet/secure_closet/freezer/wall/directional/north,
/obj/item/storage/fancy/egg_box,
/obj/item/storage/fancy/egg_box,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/sugar,
-/obj/item/reagent_containers/food/condiment/milk,
-/obj/item/reagent_containers/food/condiment/milk,
-/obj/item/reagent_containers/food/condiment/rice,
-/obj/item/reagent_containers/food/condiment/peppermill,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/sugar,
+/obj/item/reagent_containers/condiment/milk,
+/obj/item/reagent_containers/condiment/milk,
+/obj/item/reagent_containers/condiment/rice,
+/obj/item/reagent_containers/condiment/peppermill,
/obj/effect/decal/cleanable/food/flour,
/turf/open/floor/concrete/slab_3,
/area/ship/crew/canteen)
diff --git a/_maps/shuttles/independent/independent_shetland.dmm b/_maps/shuttles/independent/independent_shetland.dmm
index 5f5aa5abd48..09b1f631276 100644
--- a/_maps/shuttles/independent/independent_shetland.dmm
+++ b/_maps/shuttles/independent/independent_shetland.dmm
@@ -2782,11 +2782,11 @@
"xz" = (
/obj/effect/turf_decal/corner/opaque/neutral/half,
/obj/structure/table/reinforced,
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
pixel_x = -4;
pixel_y = 10
},
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
pixel_x = 4;
pixel_y = 10
},
@@ -3371,8 +3371,8 @@
populate = 0
},
/obj/effect/turf_decal/corner/opaque/neutral/half,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/sugar,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/sugar,
/turf/open/floor/plasteel/dark,
/area/ship/crew/canteen)
"CK" = (
diff --git a/_maps/shuttles/independent/independent_sunskipper.dmm b/_maps/shuttles/independent/independent_sunskipper.dmm
index 8c3421462bf..c0d751d8547 100644
--- a/_maps/shuttles/independent/independent_sunskipper.dmm
+++ b/_maps/shuttles/independent/independent_sunskipper.dmm
@@ -196,11 +196,11 @@
dir = 8
},
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4,
-/obj/item/reagent_containers/food/condiment/ketchup{
+/obj/item/reagent_containers/condiment/ketchup{
pixel_x = -6;
pixel_y = 8
},
-/obj/item/reagent_containers/food/condiment/mayonnaise{
+/obj/item/reagent_containers/condiment/mayonnaise{
pixel_x = 6;
pixel_y = 8
},
@@ -349,11 +349,11 @@
pixel_x = -7;
pixel_y = 9
},
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
pixel_y = 13;
pixel_x = 4
},
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
pixel_y = 13;
pixel_x = 11
},
@@ -3171,11 +3171,11 @@
/area/template_noop)
"Pe" = (
/obj/structure/table/reinforced,
-/obj/item/reagent_containers/food/condiment/enzyme{
+/obj/item/reagent_containers/condiment/enzyme{
pixel_x = -6;
pixel_y = 8
},
-/obj/item/reagent_containers/food/condiment/oliveoil{
+/obj/item/reagent_containers/condiment/oliveoil{
pixel_x = 6;
pixel_y = 8
},
@@ -3272,7 +3272,7 @@
/obj/structure/closet/secure_closet/freezer/kitchen,
/obj/effect/turf_decal/borderfloorblack,
/obj/effect/turf_decal/box,
-/obj/item/reagent_containers/food/condiment/rice,
+/obj/item/reagent_containers/condiment/rice,
/turf/open/floor/plasteel/dark,
/area/ship/crew/canteen/kitchen)
"QB" = (
@@ -3553,10 +3553,10 @@
/obj/structure/closet/crate{
name = "supplies crate"
},
-/obj/item/reagent_containers/food/condiment/saltshaker,
-/obj/item/reagent_containers/food/condiment/saltshaker,
-/obj/item/reagent_containers/food/condiment/peppermill,
-/obj/item/reagent_containers/food/condiment/peppermill,
+/obj/item/reagent_containers/condiment/saltshaker,
+/obj/item/reagent_containers/condiment/saltshaker,
+/obj/item/reagent_containers/condiment/peppermill,
+/obj/item/reagent_containers/condiment/peppermill,
/obj/item/storage/box/lights/mixed,
/obj/item/storage/box/drinkingglasses,
/obj/item/storage/fancy/candle_box,
@@ -3655,11 +3655,11 @@
"VG" = (
/obj/machinery/light/directional/west,
/obj/structure/table/reinforced,
-/obj/item/reagent_containers/food/condiment/soysauce{
+/obj/item/reagent_containers/condiment/soysauce{
pixel_x = -6;
pixel_y = 8
},
-/obj/item/reagent_containers/food/condiment/bbqsauce{
+/obj/item/reagent_containers/condiment/bbqsauce{
pixel_x = 6;
pixel_y = 8
},
diff --git a/_maps/shuttles/independent/independent_tranquility.dmm b/_maps/shuttles/independent/independent_tranquility.dmm
index f2327b1e228..e0f41306f99 100644
--- a/_maps/shuttles/independent/independent_tranquility.dmm
+++ b/_maps/shuttles/independent/independent_tranquility.dmm
@@ -544,20 +544,20 @@
dir = 1
},
/obj/effect/turf_decal/corner/transparent/bar,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/sugar,
-/obj/item/reagent_containers/food/condiment/sugar,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/sugar,
+/obj/item/reagent_containers/condiment/sugar,
/obj/item/storage/box/ingredients/carnivore,
/obj/item/storage/box/ingredients/fruity,
/obj/item/storage/box/ingredients/grains,
/obj/item/storage/box/ingredients/vegetarian,
/obj/item/storage/box/ingredients/wildcard,
/obj/item/storage/box/ingredients/wildcard,
-/obj/item/reagent_containers/food/condiment/enzyme,
-/obj/item/reagent_containers/food/condiment/mayonnaise,
-/obj/item/reagent_containers/food/condiment/rice,
-/obj/item/reagent_containers/food/condiment/rice,
+/obj/item/reagent_containers/condiment/enzyme,
+/obj/item/reagent_containers/condiment/mayonnaise,
+/obj/item/reagent_containers/condiment/rice,
+/obj/item/reagent_containers/condiment/rice,
/obj/item/storage/box/condimentbottles,
/obj/structure/closet/secure_closet/wall/directional/west{
name = "Kitchen Cabinet"
@@ -1160,7 +1160,7 @@
/area/ship/crew/dorm)
"iq" = (
/obj/structure/table,
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
pixel_x = -11;
pixel_y = 6
},
@@ -2736,7 +2736,7 @@
/area/ship/crew/crewtwo)
"wA" = (
/obj/structure/table,
-/obj/item/reagent_containers/food/snacks/cakeslice/lime,
+/obj/item/food/cakeslice/lime,
/obj/item/kitchen/fork/plastic{
pixel_x = -11
},
@@ -3003,7 +3003,7 @@
/area/ship/hallway/port)
"yL" = (
/obj/structure/table,
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
pixel_x = 13;
pixel_y = 2
},
@@ -3516,7 +3516,7 @@
/area/ship/crew/hydroponics)
"CH" = (
/obj/structure/table/wood,
-/obj/item/reagent_containers/food/snacks/garlicbread,
+/obj/item/food/garlicbread,
/obj/item/reagent_containers/food/snacks/grown/citrus/orange{
pixel_x = -8;
pixel_y = 7
@@ -5416,10 +5416,10 @@
/obj/item/reagent_containers/food/snacks/meat/slab/synthmeat,
/obj/item/reagent_containers/food/snacks/meat/slab/synthmeat,
/obj/item/reagent_containers/food/snacks/meat/slab/synthmeat,
-/obj/item/reagent_containers/food/condiment/milk,
-/obj/item/reagent_containers/food/condiment/milk,
-/obj/item/reagent_containers/food/condiment/soymilk,
-/obj/item/reagent_containers/food/condiment/soymilk,
+/obj/item/reagent_containers/condiment/milk,
+/obj/item/reagent_containers/condiment/milk,
+/obj/item/reagent_containers/condiment/soymilk,
+/obj/item/reagent_containers/condiment/soymilk,
/obj/item/storage/fancy/egg_box,
/obj/item/storage/fancy/egg_box,
/obj/structure/closet/secure_closet/wall/directional/east{
@@ -6033,11 +6033,11 @@
pixel_x = -11;
pixel_y = 11
},
-/obj/item/reagent_containers/food/snacks/store/bread/banana{
+/obj/item/food/bread/banana{
pixel_x = 14;
pixel_y = 1
},
-/obj/item/reagent_containers/food/snacks/breadslice/banana,
+/obj/item/food/breadslice/banana,
/turf/open/floor/plasteel,
/area/ship/crew/cryo)
"Vg" = (
diff --git a/_maps/shuttles/independent/independent_wyvern.dmm b/_maps/shuttles/independent/independent_wyvern.dmm
index 3e1843514f0..d57a99e2735 100644
--- a/_maps/shuttles/independent/independent_wyvern.dmm
+++ b/_maps/shuttles/independent/independent_wyvern.dmm
@@ -3533,13 +3533,13 @@
/obj/structure/closet/secure_closet/freezer/fridge{
populate = 0
},
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/sugar,
-/obj/item/reagent_containers/food/condiment/sugar,
-/obj/item/reagent_containers/food/condiment/enzyme,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/sugar,
+/obj/item/reagent_containers/condiment/sugar,
+/obj/item/reagent_containers/condiment/enzyme,
/obj/item/reagent_containers/food/snacks/meat/slab,
/obj/item/reagent_containers/food/snacks/meat/slab,
/obj/item/reagent_containers/food/snacks/meat/slab,
@@ -4048,10 +4048,10 @@
/obj/item/cutting_board,
/obj/item/kitchen/knife,
/obj/item/kitchen/rollingpin,
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
pixel_x = -7
},
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
pixel_x = -14
},
/turf/open/floor/plasteel/white,
diff --git a/_maps/shuttles/nanotrasen/nanotrasen_cheyenne.dmm b/_maps/shuttles/nanotrasen/nanotrasen_cheyenne.dmm
index 41af7439c48..554e1174ad8 100644
--- a/_maps/shuttles/nanotrasen/nanotrasen_cheyenne.dmm
+++ b/_maps/shuttles/nanotrasen/nanotrasen_cheyenne.dmm
@@ -2268,16 +2268,16 @@
/obj/structure/closet/secure_closet/freezer/kitchen,
/obj/item/storage/fancy/egg_box,
/obj/item/storage/fancy/egg_box,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/rice,
-/obj/item/reagent_containers/food/condiment/rice,
-/obj/item/reagent_containers/food/condiment/sugar,
-/obj/item/reagent_containers/food/condiment/sugar,
-/obj/item/reagent_containers/food/condiment/soymilk,
-/obj/item/reagent_containers/food/condiment/soymilk,
-/obj/item/reagent_containers/food/condiment/milk,
-/obj/item/reagent_containers/food/condiment/milk,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/rice,
+/obj/item/reagent_containers/condiment/rice,
+/obj/item/reagent_containers/condiment/sugar,
+/obj/item/reagent_containers/condiment/sugar,
+/obj/item/reagent_containers/condiment/soymilk,
+/obj/item/reagent_containers/condiment/soymilk,
+/obj/item/reagent_containers/condiment/milk,
+/obj/item/reagent_containers/condiment/milk,
/turf/open/floor/wood/ebony,
/area/ship/crew/canteen)
"ld" = (
@@ -4582,24 +4582,24 @@
/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2{
dir = 8
},
-/obj/item/reagent_containers/food/condiment/bbqsauce{
+/obj/item/reagent_containers/condiment/bbqsauce{
pixel_x = 0;
pixel_y = 17
},
-/obj/item/reagent_containers/food/condiment/coldsauce{
+/obj/item/reagent_containers/condiment/coldsauce{
pixel_x = -8;
pixel_y = 18
},
-/obj/item/reagent_containers/food/condiment/hotsauce{
+/obj/item/reagent_containers/condiment/hotsauce{
pixel_x = 10;
pixel_y = 17
},
-/obj/item/reagent_containers/food/condiment/soysauce{
+/obj/item/reagent_containers/condiment/soysauce{
pixel_x = 8;
pixel_y = 3
},
-/obj/item/reagent_containers/food/condiment/saltshaker,
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/saltshaker,
+/obj/item/reagent_containers/condiment/peppermill{
pixel_x = -8;
pixel_y = 2
},
diff --git a/_maps/shuttles/nanotrasen/nanotrasen_heron.dmm b/_maps/shuttles/nanotrasen/nanotrasen_heron.dmm
index fed40e16503..370321ca295 100644
--- a/_maps/shuttles/nanotrasen/nanotrasen_heron.dmm
+++ b/_maps/shuttles/nanotrasen/nanotrasen_heron.dmm
@@ -7053,11 +7053,11 @@
/area/ship/hallway/fore)
"zN" = (
/obj/structure/closet/secure_closet/freezer/fridge,
-/obj/item/reagent_containers/food/condiment/soysauce{
+/obj/item/reagent_containers/condiment/soysauce{
pixel_x = 3;
pixel_y = 3
},
-/obj/item/reagent_containers/food/condiment/mayonnaise,
+/obj/item/reagent_containers/condiment/mayonnaise,
/obj/effect/turf_decal/box/corners{
dir = 4
},
@@ -9208,12 +9208,12 @@
"HT" = (
/obj/structure/table/reinforced,
/obj/machinery/door/firedoor,
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
desc = "Often used to flavor food or make people sneeze. Fashionably moved to the left side of the table.";
pixel_x = -8;
pixel_y = 2
},
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
desc = "Salt. From space oceans, presumably. A staple of modern medicine.";
pixel_x = -8;
pixel_y = 12
@@ -9318,11 +9318,11 @@
"Ip" = (
/obj/structure/table,
/obj/item/reagent_containers/food/snacks/mint,
-/obj/item/reagent_containers/food/condiment/enzyme{
+/obj/item/reagent_containers/condiment/enzyme{
pixel_x = -2;
pixel_y = 6
},
-/obj/item/reagent_containers/food/condiment/sugar{
+/obj/item/reagent_containers/condiment/sugar{
pixel_x = 5;
pixel_y = 5
},
@@ -12150,12 +12150,12 @@
pixel_x = 6;
pixel_y = 6
},
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
desc = "Often used to flavor food or make people sneeze. Fashionably moved to the left side of the table.";
pixel_x = -8;
pixel_y = 2
},
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
desc = "Salt. From space oceans, presumably. A staple of modern medicine.";
pixel_x = -8;
pixel_y = 12
diff --git a/_maps/shuttles/nanotrasen/nanotrasen_meta.dmm b/_maps/shuttles/nanotrasen/nanotrasen_meta.dmm
index f128a1049d2..233dd2995ce 100644
--- a/_maps/shuttles/nanotrasen/nanotrasen_meta.dmm
+++ b/_maps/shuttles/nanotrasen/nanotrasen_meta.dmm
@@ -543,11 +543,11 @@
pixel_y = 3
},
/obj/effect/turf_decal/corner/transparent/bar/diagonal,
-/obj/item/reagent_containers/food/condiment/ketchup{
+/obj/item/reagent_containers/condiment/ketchup{
pixel_y = 14;
pixel_x = 9
},
-/obj/item/reagent_containers/food/condiment/mayonnaise{
+/obj/item/reagent_containers/condiment/mayonnaise{
pixel_y = 14
},
/obj/item/reagent_containers/food/drinks/soda_cans/cola{
@@ -1803,10 +1803,10 @@
/area/ship/engineering)
"lH" = (
/obj/item/storage/fancy/egg_box,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/rice,
-/obj/item/reagent_containers/food/condiment/sugar,
-/obj/item/reagent_containers/food/condiment/enzyme,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/rice,
+/obj/item/reagent_containers/condiment/sugar,
+/obj/item/reagent_containers/condiment/enzyme,
/obj/item/reagent_containers/food/snacks/meat/slab/synthmeat{
pixel_x = -3;
pixel_y = 3
@@ -1822,10 +1822,10 @@
name = "fridge";
anchored = 1
},
-/obj/item/reagent_containers/food/condiment/soymilk,
-/obj/item/reagent_containers/food/condiment/milk,
-/obj/item/reagent_containers/food/condiment/milk,
-/obj/item/reagent_containers/food/condiment/soymilk,
+/obj/item/reagent_containers/condiment/soymilk,
+/obj/item/reagent_containers/condiment/milk,
+/obj/item/reagent_containers/condiment/milk,
+/obj/item/reagent_containers/condiment/soymilk,
/turf/open/floor/plasteel,
/area/ship/crew/canteen/kitchen)
"lK" = (
@@ -2739,11 +2739,11 @@
/area/ship/cargo)
"EX" = (
/obj/structure/table,
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
pixel_x = -8;
pixel_y = 10
},
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
pixel_x = -8;
pixel_y = 4
},
@@ -2850,11 +2850,11 @@
pixel_y = 6
},
/obj/effect/turf_decal/corner/opaque/white/diagonal,
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
pixel_x = -8;
pixel_y = 10
},
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
pixel_x = -8;
pixel_y = 4
},
diff --git a/_maps/shuttles/nanotrasen/nanotrasen_mimir.dmm b/_maps/shuttles/nanotrasen/nanotrasen_mimir.dmm
index 0e3034a7930..5b0c66cd0f5 100644
--- a/_maps/shuttles/nanotrasen/nanotrasen_mimir.dmm
+++ b/_maps/shuttles/nanotrasen/nanotrasen_mimir.dmm
@@ -2814,18 +2814,18 @@
},
/obj/item/storage/fancy/egg_box,
/obj/item/storage/fancy/egg_box,
-/obj/item/reagent_containers/food/condiment/milk,
-/obj/item/reagent_containers/food/condiment/milk,
-/obj/item/reagent_containers/food/condiment/milk,
-/obj/item/reagent_containers/food/condiment/soymilk,
-/obj/item/reagent_containers/food/condiment/soymilk,
-/obj/item/reagent_containers/food/condiment/soymilk,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/sugar,
-/obj/item/reagent_containers/food/condiment/sugar,
-/obj/item/reagent_containers/food/condiment/rice,
+/obj/item/reagent_containers/condiment/milk,
+/obj/item/reagent_containers/condiment/milk,
+/obj/item/reagent_containers/condiment/milk,
+/obj/item/reagent_containers/condiment/soymilk,
+/obj/item/reagent_containers/condiment/soymilk,
+/obj/item/reagent_containers/condiment/soymilk,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/sugar,
+/obj/item/reagent_containers/condiment/sugar,
+/obj/item/reagent_containers/condiment/rice,
/obj/item/reagent_containers/food/snacks/tofu,
/obj/item/reagent_containers/food/snacks/tofu,
/turf/open/floor/plasteel/mono/white,
@@ -3878,14 +3878,14 @@
/area/ship/crew/canteen/kitchen)
"xb" = (
/obj/structure/table,
-/obj/item/reagent_containers/food/condiment/sugar{
+/obj/item/reagent_containers/condiment/sugar{
pixel_y = 4
},
-/obj/item/reagent_containers/food/condiment/rice{
+/obj/item/reagent_containers/condiment/rice{
pixel_y = 10;
pixel_x = 3
},
-/obj/item/reagent_containers/food/condiment/enzyme{
+/obj/item/reagent_containers/condiment/enzyme{
pixel_x = -5;
pixel_y = 5
},
@@ -4466,11 +4466,11 @@
/obj/item/toy/figure/chef,
/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2,
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4,
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
pixel_y = 10;
pixel_x = -7
},
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
pixel_x = 8;
pixel_y = 9
},
diff --git a/_maps/shuttles/nanotrasen/nanotrasen_osprey.dmm b/_maps/shuttles/nanotrasen/nanotrasen_osprey.dmm
index 7951fcfa521..a301ddd8a64 100644
--- a/_maps/shuttles/nanotrasen/nanotrasen_osprey.dmm
+++ b/_maps/shuttles/nanotrasen/nanotrasen_osprey.dmm
@@ -2797,11 +2797,11 @@
/area/ship/hallway/central)
"sl" = (
/obj/structure/table/reinforced,
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
pixel_x = -4;
pixel_y = 10
},
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
pixel_x = 4;
pixel_y = 10
},
@@ -5577,7 +5577,7 @@
"JL" = (
/obj/structure/table/reinforced,
/obj/item/reagent_containers/food/drinks/shaker,
-/obj/item/reagent_containers/food/condiment/enzyme,
+/obj/item/reagent_containers/condiment/enzyme,
/obj/effect/turf_decal/corner/opaque/white{
dir = 4
},
@@ -7645,8 +7645,8 @@
/obj/structure/table/reinforced,
/obj/item/kitchen/knife,
/obj/item/kitchen/rollingpin,
-/obj/item/reagent_containers/food/condiment/sugar,
-/obj/item/reagent_containers/food/condiment/rice,
+/obj/item/reagent_containers/condiment/sugar,
+/obj/item/reagent_containers/condiment/rice,
/obj/effect/turf_decal/corner/opaque/white{
dir = 4
},
diff --git a/_maps/shuttles/nanotrasen/nanotrasen_skipper.dmm b/_maps/shuttles/nanotrasen/nanotrasen_skipper.dmm
index d37de0ec2d1..62da3b1fc75 100644
--- a/_maps/shuttles/nanotrasen/nanotrasen_skipper.dmm
+++ b/_maps/shuttles/nanotrasen/nanotrasen_skipper.dmm
@@ -2620,11 +2620,11 @@
/obj/effect/turf_decal/corner/opaque/white/mono,
/obj/structure/table/reinforced,
/obj/item/kitchen/rollingpin,
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
pixel_x = -2;
pixel_y = 11
},
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
pixel_y = 6;
pixel_x = -8
},
@@ -4975,11 +4975,11 @@
/obj/effect/turf_decal/corner/opaque/neutral/half{
dir = 1
},
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
pixel_y = 6;
pixel_x = -8
},
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
pixel_x = -2;
pixel_y = 11
},
@@ -5146,12 +5146,12 @@
/obj/structure/closet/secure_closet/freezer{
anchored = 1
},
-/obj/item/reagent_containers/food/condiment/enzyme,
-/obj/item/reagent_containers/food/condiment/sugar,
-/obj/item/reagent_containers/food/condiment/rice,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/milk,
-/obj/item/reagent_containers/food/condiment/soymilk,
+/obj/item/reagent_containers/condiment/enzyme,
+/obj/item/reagent_containers/condiment/sugar,
+/obj/item/reagent_containers/condiment/rice,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/milk,
+/obj/item/reagent_containers/condiment/soymilk,
/obj/effect/turf_decal/corner/opaque/green/mono,
/turf/open/floor/plasteel,
/area/ship/crew/canteen/kitchen)
diff --git a/_maps/shuttles/pgf/pgf_crying_sun.dmm b/_maps/shuttles/pgf/pgf_crying_sun.dmm
index fdef1a017e4..5da85bc4552 100644
--- a/_maps/shuttles/pgf/pgf_crying_sun.dmm
+++ b/_maps/shuttles/pgf/pgf_crying_sun.dmm
@@ -2499,11 +2499,11 @@
pixel_x = 9;
pixel_y = -5
},
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
pixel_y = 10;
pixel_x = -6
},
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
pixel_y = 10
},
/turf/open/floor/plasteel/dark,
diff --git a/_maps/shuttles/syndicate/syndicate_aegis.dmm b/_maps/shuttles/syndicate/syndicate_aegis.dmm
index 1e60e03a6ff..465bd853a88 100644
--- a/_maps/shuttles/syndicate/syndicate_aegis.dmm
+++ b/_maps/shuttles/syndicate/syndicate_aegis.dmm
@@ -597,8 +597,8 @@
},
/obj/item/storage/fancy/egg_box,
/obj/item/storage/fancy/egg_box,
-/obj/item/reagent_containers/food/condiment/milk,
-/obj/item/reagent_containers/food/condiment/milk,
+/obj/item/reagent_containers/condiment/milk,
+/obj/item/reagent_containers/condiment/milk,
/obj/item/reagent_containers/food/snacks/meat/slab,
/obj/item/reagent_containers/food/snacks/meat/slab,
/obj/item/reagent_containers/food/snacks/meat/slab,
@@ -2627,7 +2627,7 @@
dir = 1
},
/obj/machinery/door/firedoor/border_only,
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
pixel_x = 6;
pixel_y = 4
},
@@ -4582,10 +4582,10 @@
},
/obj/item/storage/bag/tray,
/obj/item/kitchen/rollingpin,
-/obj/item/reagent_containers/food/condiment/sugar,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/flour,
+/obj/item/reagent_containers/condiment/sugar,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/flour,
/obj/structure/table/reinforced,
/turf/open/floor/plasteel/mono/dark,
/area/ship/crew/canteen)
@@ -4611,7 +4611,7 @@
dir = 1
},
/obj/machinery/door/firedoor/border_only,
-/obj/item/reagent_containers/food/condiment/saltshaker,
+/obj/item/reagent_containers/condiment/saltshaker,
/obj/structure/table/wood/reinforced,
/obj/effect/turf_decal/siding/wood{
dir = 1
diff --git a/_maps/shuttles/syndicate/syndicate_twinkleshine.dmm b/_maps/shuttles/syndicate/syndicate_twinkleshine.dmm
index c2eabd813b8..cc72f6b5949 100644
--- a/_maps/shuttles/syndicate/syndicate_twinkleshine.dmm
+++ b/_maps/shuttles/syndicate/syndicate_twinkleshine.dmm
@@ -2978,18 +2978,18 @@
"qP" = (
/obj/machinery/light/directional/north,
/obj/item/kitchen/rollingpin,
-/obj/item/reagent_containers/food/condiment/sugar,
-/obj/item/reagent_containers/food/condiment/sugar,
+/obj/item/reagent_containers/condiment/sugar,
+/obj/item/reagent_containers/condiment/sugar,
/obj/structure/closet/secure_closet/freezer/kitchen,
/obj/item/storage/fancy/egg_box,
/obj/item/storage/fancy/egg_box,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/flour,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/flour,
/obj/item/storage/box/ingredients/carnivore,
/obj/item/storage/box/ingredients/vegetarian,
-/obj/item/reagent_containers/food/condiment/milk,
-/obj/item/reagent_containers/food/condiment/milk,
+/obj/item/reagent_containers/condiment/milk,
+/obj/item/reagent_containers/condiment/milk,
/obj/effect/spawner/lootdrop/donkpockets,
/obj/effect/spawner/lootdrop/donkpockets,
/turf/open/floor/carpet/red,
diff --git a/_maps/shuttles/terragov/terragov_chronicle.dmm b/_maps/shuttles/terragov/terragov_chronicle.dmm
index 035dc5cd2b7..6ca9bcc7c0e 100644
--- a/_maps/shuttles/terragov/terragov_chronicle.dmm
+++ b/_maps/shuttles/terragov/terragov_chronicle.dmm
@@ -2477,8 +2477,8 @@
dir = 4;
color = "#543C30"
},
-/obj/item/reagent_containers/food/condiment/milk,
-/obj/item/reagent_containers/food/condiment/milk,
+/obj/item/reagent_containers/condiment/milk,
+/obj/item/reagent_containers/condiment/milk,
/turf/open/floor/wood/mahogany,
/area/ship/crew)
"zh" = (
@@ -2529,10 +2529,10 @@
/obj/item/reagent_containers/food/snacks/grown/cabbage,
/obj/item/reagent_containers/food/snacks/grown/cabbage,
/obj/item/reagent_containers/food/snacks/grown/cabbage,
-/obj/item/reagent_containers/food/condiment/saltshaker,
-/obj/item/reagent_containers/food/condiment/saltshaker,
-/obj/item/reagent_containers/food/condiment/saltshaker,
-/obj/item/reagent_containers/food/condiment/saltshaker,
+/obj/item/reagent_containers/condiment/saltshaker,
+/obj/item/reagent_containers/condiment/saltshaker,
+/obj/item/reagent_containers/condiment/saltshaker,
+/obj/item/reagent_containers/condiment/saltshaker,
/obj/structure/closet/crate/secure/gear{
populate = 0;
name = "emergency sauerkraut supplies";
diff --git a/_maps/shuttles/terragov/terragov_inkwell.dmm b/_maps/shuttles/terragov/terragov_inkwell.dmm
index 704668a314a..9a865244f56 100644
--- a/_maps/shuttles/terragov/terragov_inkwell.dmm
+++ b/_maps/shuttles/terragov/terragov_inkwell.dmm
@@ -1848,10 +1848,10 @@
/area/ship/crew/canteen/kitchen)
"mz" = (
/obj/structure/closet/crate,
-/obj/item/reagent_containers/food/snacks/store/bread/plain,
-/obj/item/reagent_containers/food/snacks/store/bread/plain,
-/obj/item/reagent_containers/food/snacks/store/bread/plain,
-/obj/item/reagent_containers/food/snacks/store/bread/plain,
+/obj/item/food/bread/plain,
+/obj/item/food/bread/plain,
+/obj/item/food/bread/plain,
+/obj/item/food/bread/plain,
/obj/item/reagent_containers/food/drinks/waterbottle/large,
/obj/item/reagent_containers/food/drinks/waterbottle/large,
/obj/item/reagent_containers/food/drinks/waterbottle/large,
@@ -3297,10 +3297,10 @@
/obj/structure/table/wood,
/obj/item/cutting_board,
/obj/item/kitchen/knife,
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
pixel_x = -17
},
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
pixel_x = -10;
pixel_y = 6
},
@@ -3897,17 +3897,17 @@
/area/ship/crew/dorm/dormtwo)
"yE" = (
/obj/structure/closet/secure_closet/freezer/fridge,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/rice,
-/obj/item/reagent_containers/food/condiment/sugar,
-/obj/item/reagent_containers/food/condiment/milk,
-/obj/item/reagent_containers/food/condiment/milk,
-/obj/item/reagent_containers/food/condiment/soymilk,
-/obj/item/reagent_containers/food/condiment/soymilk,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/rice,
+/obj/item/reagent_containers/condiment/sugar,
+/obj/item/reagent_containers/condiment/milk,
+/obj/item/reagent_containers/condiment/milk,
+/obj/item/reagent_containers/condiment/soymilk,
+/obj/item/reagent_containers/condiment/soymilk,
/obj/item/storage/fancy/egg_box,
-/obj/item/reagent_containers/food/condiment/enzyme,
+/obj/item/reagent_containers/condiment/enzyme,
/turf/open/floor/wood/walnut,
/area/ship/crew/canteen/kitchen)
"yG" = (
@@ -7295,10 +7295,10 @@
/obj/item/reagent_containers/food/snacks/grown/cabbage,
/obj/item/reagent_containers/food/snacks/grown/cabbage,
/obj/item/reagent_containers/food/snacks/grown/cabbage,
-/obj/item/reagent_containers/food/condiment/saltshaker,
-/obj/item/reagent_containers/food/condiment/saltshaker,
-/obj/item/reagent_containers/food/condiment/saltshaker,
-/obj/item/reagent_containers/food/condiment/saltshaker,
+/obj/item/reagent_containers/condiment/saltshaker,
+/obj/item/reagent_containers/condiment/saltshaker,
+/obj/item/reagent_containers/condiment/saltshaker,
+/obj/item/reagent_containers/condiment/saltshaker,
/obj/structure/closet/crate/secure/gear{
populate = 0;
name = "emergency sauerkraut supplies";
diff --git a/_maps/shuttles/terragov/terragov_paracelsus.dmm b/_maps/shuttles/terragov/terragov_paracelsus.dmm
index a52194b7442..a34643dcb30 100644
--- a/_maps/shuttles/terragov/terragov_paracelsus.dmm
+++ b/_maps/shuttles/terragov/terragov_paracelsus.dmm
@@ -731,10 +731,10 @@
/area/ship/hallway/starboard)
"hh" = (
/obj/structure/closet/crate,
-/obj/item/reagent_containers/food/snacks/store/bread/plain,
-/obj/item/reagent_containers/food/snacks/store/bread/plain,
-/obj/item/reagent_containers/food/snacks/store/bread/plain,
-/obj/item/reagent_containers/food/snacks/store/bread/plain,
+/obj/item/food/bread/plain,
+/obj/item/food/bread/plain,
+/obj/item/food/bread/plain,
+/obj/item/food/bread/plain,
/obj/item/reagent_containers/food/drinks/waterbottle/large,
/obj/item/reagent_containers/food/drinks/waterbottle/large,
/obj/item/reagent_containers/food/drinks/waterbottle/large,
@@ -3540,11 +3540,11 @@
pixel_y = 8;
pixel_x = -7
},
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
pixel_x = 4;
pixel_y = 10
},
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
pixel_x = 10;
pixel_y = 10
},
@@ -4007,17 +4007,17 @@
/turf/open/floor/carpet/royalblue,
/area/ship/crew/office)
"ML" = (
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/flour,
-/obj/item/reagent_containers/food/condiment/rice,
-/obj/item/reagent_containers/food/condiment/sugar,
-/obj/item/reagent_containers/food/condiment/milk,
-/obj/item/reagent_containers/food/condiment/milk,
-/obj/item/reagent_containers/food/condiment/soymilk,
-/obj/item/reagent_containers/food/condiment/soymilk,
-/obj/item/reagent_containers/food/condiment/enzyme,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/flour,
+/obj/item/reagent_containers/condiment/rice,
+/obj/item/reagent_containers/condiment/sugar,
+/obj/item/reagent_containers/condiment/milk,
+/obj/item/reagent_containers/condiment/milk,
+/obj/item/reagent_containers/condiment/soymilk,
+/obj/item/reagent_containers/condiment/soymilk,
+/obj/item/reagent_containers/condiment/enzyme,
/obj/structure/closet/secure_closet/freezer{
anchored = 1;
name = "refrigerator"
diff --git a/_maps/templates/shelter_3.dmm b/_maps/templates/shelter_3.dmm
index dd4f6645a51..570483a06a1 100644
--- a/_maps/templates/shelter_3.dmm
+++ b/_maps/templates/shelter_3.dmm
@@ -187,11 +187,11 @@
/area/survivalpod)
"F" = (
/obj/structure/table/wood/fancy,
-/obj/item/reagent_containers/food/condiment/peppermill{
+/obj/item/reagent_containers/condiment/peppermill{
pixel_x = -4;
pixel_y = 12
},
-/obj/item/reagent_containers/food/condiment/saltshaker{
+/obj/item/reagent_containers/condiment/saltshaker{
pixel_x = 4;
pixel_y = 4
},
diff --git a/code/__DEFINES/achievements.dm b/code/__DEFINES/achievements.dm
index 31dd5523fef..f36aac75544 100644
--- a/code/__DEFINES/achievements.dm
+++ b/code/__DEFINES/achievements.dm
@@ -24,6 +24,7 @@
#define MEDAL_SNAIL "KKKiiilll mmmeee"
#define MEDAL_LOOKOUTSIR "Look Out, Sir!"
#define MEDAL_GOTTEM "GOTTEM"
+#define MEDAL_SPRINGLOCK "The Man Inside the Modsuit"
//Skill medal hub IDs
#define MEDAL_LEGENDARY_MINER "Legendary Miner"
diff --git a/code/__DEFINES/actions.dm b/code/__DEFINES/actions.dm
new file mode 100644
index 00000000000..ca206810699
--- /dev/null
+++ b/code/__DEFINES/actions.dm
@@ -0,0 +1,20 @@
+///Action button checks if hands are unusable
+#define AB_CHECK_HANDS_BLOCKED (1<<0)
+///Action button checks if user is immobile
+#define AB_CHECK_IMMOBILE (1<<1)
+///Action button checks if user is resting
+#define AB_CHECK_LYING (1<<2)
+///Action button checks if user is conscious
+#define AB_CHECK_CONSCIOUS (1<<3)
+
+///Action button triggered with right click
+#define TRIGGER_SECONDARY_ACTION (1<<0)
+
+// Defines for formatting cooldown actions for the stat panel.
+/// The stat panel the action is displayed in.
+#define PANEL_DISPLAY_PANEL "panel"
+/// The status shown in the stat panel.
+/// Can be stuff like "ready", "on cooldown", "active", "charges", "charge cost", etc.
+#define PANEL_DISPLAY_STATUS "status"
+/// The name shown in the stat panel.
+#define PANEL_DISPLAY_NAME "name"
diff --git a/code/__DEFINES/atmospherics.dm b/code/__DEFINES/atmospherics.dm
index 6e99bc182e6..6c2bd5b2c67 100644
--- a/code/__DEFINES/atmospherics.dm
+++ b/code/__DEFINES/atmospherics.dm
@@ -33,6 +33,10 @@
/// Amount of air to take a from a tile
#define BREATH_PERCENTAGE (BREATH_VOLUME/CELL_VOLUME)
+/// This is the divisor which handles how much of the temperature difference between the current body temperature and 310.15K (optimal temperature) humans auto-regenerate each tick. The higher the number, the slower the recovery. This is applied each tick, so long as the mob is alive.
+#define BODYTEMP_AUTORECOVERY_DIVISOR 28
+/// The natural temperature for a body
+#define BODYTEMP_NORMAL 310.15
//EXCITED GROUPS
/// number of FULL air controller ticks before an excited group breaks down (averages gas contents across turfs)
diff --git a/code/__DEFINES/botany.dm b/code/__DEFINES/botany.dm
index 4780f819f55..4abffb067dd 100644
--- a/code/__DEFINES/botany.dm
+++ b/code/__DEFINES/botany.dm
@@ -15,3 +15,24 @@
//Floral Somoray
#define REVOLUTION_CHARGE 10000 // Default flora cell
+
+/// -- Trait IDs. Plants that match IDs cannot be added to the same plant. --
+/// Plants that glow.
+#define GLOW_ID (1<<0)
+/// Plant types.
+#define PLANT_TYPE_ID (1<<1)
+/// Plants that affect the reagent's temperature.
+#define TEMP_CHANGE_ID (1<<2)
+/// Plants that affect the reagent contents.
+#define CONTENTS_CHANGE_ID (1<<3)
+/// Plants that do something special when they impact.
+#define THROW_IMPACT_ID (1<<4)
+/// Plants that transfer reagents on impact.
+#define REAGENT_TRANSFER_ID (1<<5)
+/// Plants that have a unique effect on attack_self.
+#define ATTACK_SELF_ID (1<<6)
+
+#define HYDROTRAY_NO_PLANT "missing"
+#define HYDROTRAY_PLANT_DEAD "dead"
+#define HYDROTRAY_PLANT_GROWING "growing"
+#define HYDROTRAY_PLANT_HARVESTABLE "harvestable"
diff --git a/code/__DEFINES/clothing.dm b/code/__DEFINES/clothing.dm
new file mode 100644
index 00000000000..4906c6bdd4f
--- /dev/null
+++ b/code/__DEFINES/clothing.dm
@@ -0,0 +1,39 @@
+/*
+//stages of shoe tying-ness
+/// Shoes are untied
+#define SHOES_UNTIED 0
+/// Shoes are tied normally
+#define SHOES_TIED 1
+/// Shoes have been tied in knots
+#define SHOES_KNOTTED 2
+
+//suit sensors: sensor_mode defines
+/// Suit sensor is turned off
+#define SENSOR_OFF 0
+/// Suit sensor displays the mob as alive or dead
+#define SENSOR_LIVING 1
+/// Suit sensor displays the mob damage values
+#define SENSOR_VITALS 2
+/// Suit sensor displays the mob damage values and exact location
+#define SENSOR_COORDS 3
+
+//suit sensors: has_sensor defines
+/// Suit sensor has been EMP'd and cannot display any information (can be fixed)
+#define BROKEN_SENSORS -1
+/// Suit sensor is not present and cannot display any information
+#define NO_SENSORS 0
+/// Suit sensor is present and can display information
+#define HAS_SENSORS 1
+/// Suit sensor is present and is forced to display information (used on prisoner jumpsuits)
+#define LOCKED_SENSORS 2
+*/
+
+/// Wrapper for adding clothing based traits
+#define ADD_CLOTHING_TRAIT(mob, trait) ADD_TRAIT(mob, trait, "[CLOTHING_TRAIT]_[REF(src)]")
+/// Wrapper for removing clothing based traits
+#define REMOVE_CLOTHING_TRAIT(mob, trait) REMOVE_TRAIT(mob, trait, "[CLOTHING_TRAIT]_[REF(src)]")
+
+/*
+/// How much integrity does a shirt lose every time we bite it?
+#define MOTH_EATING_CLOTHING_DAMAGE 15
+*/
diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm
index ad0754c85b2..0802fb2005e 100644
--- a/code/__DEFINES/combat.dm
+++ b/code/__DEFINES/combat.dm
@@ -168,3 +168,14 @@ GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list(/obj/item/gun)))
//We will round to this value in damage calculations.
#define DAMAGE_PRECISION 0.1
+
+/// Alternate attack defines. Return these at the end of procs like afterattack_secondary.
+/// Calls the normal attack proc. For example, if returned in afterattack_secondary, will call afterattack.
+/// Will continue the chain depending on the return value of the non-alternate proc, like with normal attacks.
+#define SECONDARY_ATTACK_CALL_NORMAL 1
+
+/// Cancels the attack chain entirely.
+#define SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN 2
+
+/// Proceed with the attack chain, but don't call the normal methods.
+#define SECONDARY_ATTACK_CONTINUE_CHAIN 3
diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals/signals.dm
similarity index 83%
rename from code/__DEFINES/dcs/signals.dm
rename to code/__DEFINES/dcs/signals/signals.dm
index d0038cf5277..ecb2f4666da 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals/signals.dm
@@ -59,6 +59,14 @@
#define COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE "atom_init_success"
///from base of atom/attackby(): (/obj/item, /mob/living, params)
#define COMSIG_PARENT_ATTACKBY "atom_attackby"
+/// From base of [/obj/item/proc/pre_attack_secondary()]: (atom/target, mob/user, params)
+#define COMSIG_ITEM_PRE_ATTACK_SECONDARY "item_pre_attack_secondary"
+ #define COMPONENT_SECONDARY_CANCEL_ATTACK_CHAIN (1<<0)
+ #define COMPONENT_SECONDARY_CONTINUE_ATTACK_CHAIN (1<<1)
+ #define COMPONENT_SECONDARY_CALL_NORMAL_ATTACK_CHAIN (1<<2)
+#define COMSIG_PARENT_ATTACKBY_SECONDARY "atom_attackby_secondary"
+/// From base of [/atom/proc/attack_hand_secondary]: (mob/user, list/modifiers) - Called when the atom receives a secondary unarmed attack.
+#define COMSIG_ATOM_ATTACK_HAND_SECONDARY "atom_attack_hand_secondary"
///Return this in response if you don't want afterattack to be called
#define COMPONENT_NO_AFTERATTACK (1<<0)
///from base of atom/attack_hulk(): (/mob/living/carbon/human)
@@ -158,6 +166,10 @@
///from internal loop in atom/movable/proc/CanReach(): (list/next)
#define COMSIG_ATOM_CANREACH "atom_can_reach"
#define COMPONENT_BLOCK_REACH 1
+ #define COMPONENT_ALLOW_REACH (1<<0)
+///for when an atom has been created through processing (atom/original_atom, list/chosen_processing_option)
+#define COMSIG_ATOM_CREATEDBY_PROCESSING "atom_createdby_processing"
+
///from base of atom/screwdriver_act(): (mob/living/user, obj/item/I)
#define COMSIG_ATOM_SCREWDRIVER_ACT "atom_screwdriver_act"
///from base of atom/wrench_act(): (mob/living/user, obj/item/I)
@@ -172,7 +184,11 @@
#define COMSIG_ATOM_CROWBAR_ACT "atom_crowbar_act"
///from base of atom/analyser_act(): (mob/living/user, obj/item/I)
#define COMSIG_ATOM_ANALYSER_ACT "atom_analyser_act"
+
+///for any tool behaviors: (mob/living/user, obj/item/I, list/recipes)
+#define COMSIG_ATOM_TOOL_ACT(tooltype) "tool_act_[tooltype]"
#define COMPONENT_BLOCK_TOOL_ATTACK (1<<0)
+
///called when teleporting into a protected turf: (channel, turf/origin)
#define COMSIG_ATOM_INTERCEPT_TELEPORT "intercept_teleport"
#define COMPONENT_BLOCK_TELEPORT (1<<0)
@@ -182,6 +198,12 @@
#define COMSIG_ATOM_ORBIT_BEGIN "atom_orbit_begin"
///called when an atom stops orbiting another atom: (atom)
#define COMSIG_ATOM_ORBIT_STOP "atom_orbit_stop"
+/* Attack signals. They should share the returned flags, to standardize the attack chain. */
+/// tool_act -> pre_attack -> target.attackby (item.attack) -> afterattack
+ ///Ends the attack chain. If sent early might cause posterior attacks not to happen.
+ #define COMPONENT_CANCEL_ATTACK_CHAIN (1<<0)
+ ///Skips the specific attack step, continuing for the next one to happen.
+ #define COMPONENT_SKIP_ATTACK (1<<1)
///from base of atom/attack_ghost(): (mob/dead/observer/ghost)
#define COMSIG_ATOM_ATTACK_GHOST "atom_attack_ghost"
///from base of atom/attack_hand(): (mob/user)
@@ -324,6 +346,37 @@
#define COMSIG_MOVABLE_LIGHT_OVERLAY_TOGGLE_ON "movable_light_overlay_toggle_on"
///called when the movable's glide size is updated: (new_glide_size)
#define COMSIG_MOVABLE_UPDATE_GLIDE_SIZE "movable_glide_size"
+/// from base of atom/movable/Process_Spacemove(): (movement_dir, continuous_move)
+#define COMSIG_MOVABLE_SPACEMOVE "spacemove"
+ #define COMSIG_MOVABLE_STOP_SPACEMOVE (1<<0)
+ ///from datum/component/drift/apply_initial_visuals(): ()
+#define COMSIG_MOVABLE_DRIFT_VISUAL_ATTEMPT "movable_drift_visual_attempt"
+ #define DRIFT_VISUAL_FAILED (1<<0)
+ ///from datum/component/drift/allow_final_movement(): ()
+#define COMSIG_MOVABLE_DRIFT_BLOCK_INPUT "movable_drift_block_input"
+ #define DRIFT_ALLOW_INPUT (1<<0)
+
+///signal sent out by an atom when it checks if it can be pulled, for additional checks
+#define COMSIG_ATOM_CAN_BE_PULLED "movable_can_be_pulled"
+ #define COMSIG_ATOM_CANT_PULL (1 << 0)
+///signal sent out by an atom when it is no longer being pulled by something else
+#define COMSIG_ATOM_NO_LONGER_PULLED "movable_no_longer_pulled"
+///signal sent out by an atom when it is no longer pulling something : (atom/pulling)
+#define COMSIG_ATOM_NO_LONGER_PULLING "movable_no_longer_pulling"
+///called on /living, when pull is attempted, but before it completes, from base of [/mob/living/start_pulling]: (atom/movable/thing, force)
+#define COMSIG_LIVING_TRY_PULL "living_try_pull"
+ #define COMSIG_LIVING_CANCEL_PULL (1 << 0)
+/// Called from /mob/living/update_pull_movespeed
+#define COMSIG_LIVING_UPDATING_PULL_MOVESPEED "living_updating_pull_movespeed"
+/// Called from /mob/living/PushAM -- Called when this mob is about to push a movable, but before it moves
+/// (aotm/movable/being_pushed)
+#define COMSIG_LIVING_PUSHING_MOVABLE "living_pushing_movable"
+///from base of [/atom/proc/interact]: (mob/user)
+#define COMSIG_ATOM_UI_INTERACT "atom_ui_interact"
+///called on /living when attempting to pick up an item, from base of /mob/living/put_in_hand_check(): (obj/item/I)
+#define COMSIG_LIVING_TRY_PUT_IN_HAND "living_try_put_in_hand"
+ /// Can't pick up
+ #define COMPONENT_LIVING_CANT_PUT_IN_HAND (1<<0)
// /mob signals
@@ -345,6 +398,9 @@
#define COMSIG_MOB_ALTCLICKON "mob_altclickon"
#define COMSIG_MOB_CANCEL_CLICKON (1<<0)
+///From base of mob/living/MobBump() (mob/living)
+#define COMSIG_LIVING_MOB_BUMP "living_mob_bump"
+
///from base of obj/allowed(mob/M): (/obj) returns bool, if TRUE the mob has id access to the obj
#define COMSIG_MOB_ALLOWED "mob_allowed"
///from base of mob/anti_magic_check(): (mob/user, magic, holy, tinfoil, chargecost, self, protection_sources)
@@ -498,84 +554,6 @@
#define COMSIG_HOSTILE_ATTACKINGTARGET "hostile_attackingtarget"
#define COMPONENT_HOSTILE_NO_ATTACK 1
-// /obj signals
-
-///from base of obj/deconstruct(): (disassembled)
-#define COMSIG_OBJ_DECONSTRUCT "obj_deconstruct"
-///from base of code/game/machinery
-#define COMSIG_OBJ_DEFAULT_UNFASTEN_WRENCH "obj_default_unfasten_wrench"
-///from base of /turf/proc/levelupdate(). (intact) true to hide and false to unhide
-#define COMSIG_OBJ_HIDE "obj_hide"
-
-// /obj/machinery signals
-
-///from /obj/machinery/obj_break(damage_flag): (damage_flag)
-#define COMSIG_MACHINERY_BROKEN "machinery_broken"
-///from base power_change() when power is lost
-#define COMSIG_MACHINERY_POWER_LOST "machinery_power_lost"
-///from base power_change() when power is restored
-#define COMSIG_MACHINERY_POWER_RESTORED "machinery_power_restored"
-
-// /obj/machinery/power/supermatter_crystal signals
-/// from /obj/machinery/power/supermatter_crystal/process_atmos(); when the SM delam reaches the point of sounding alarms
-#define COMSIG_SUPERMATTER_DELAM_START_ALARM "sm_delam_start_alarm"
-/// from /obj/machinery/power/supermatter_crystal/process_atmos(); when the SM sounds an audible alarm
-#define COMSIG_SUPERMATTER_DELAM_ALARM "sm_delam_alarm"
-
-// /obj/item signals
-#define COMSIG_ITEM_ATTACK "item_attack" //from base of obj/item/attack(): (/mob/living/target, /mob/living/user)
-#define COMSIG_ITEM_ATTACK_SELF "item_attack_self" //from base of obj/item/attack_self(): (/mob)
- #define COMPONENT_NO_INTERACT 1
-#define COMSIG_ITEM_ATTACK_OBJ "item_attack_obj" //from base of obj/item/attack_obj(): (/obj, /mob)
- #define COMPONENT_NO_ATTACK_OBJ 1
-#define COMSIG_ITEM_PRE_ATTACK "item_pre_attack" //from base of obj/item/pre_attack(): (atom/target, mob/user, params)
- #define COMPONENT_NO_ATTACK 1
-#define COMSIG_ITEM_AFTERATTACK "item_afterattack" //from base of obj/item/afterattack(): (atom/target, mob/user, params)
-#define COMSIG_ITEM_ATTACK_QDELETED "item_attack_qdeleted" //from base of obj/item/attack_qdeleted(): (atom/target, mob/user, params)
-#define COMSIG_ITEM_EQUIPPED "item_equip" //from base of obj/item/equipped(): (/mob/equipper, slot)
-#define COMSIG_ITEM_DROPPED "item_drop" //from base of obj/item/dropped(): (mob/user)
-#define COMSIG_ITEM_PICKUP "item_pickup" //from base of obj/item/pickup(): (/mob/taker)
-#define COMSIG_ITEM_ATTACK_ZONE "item_attack_zone" //from base of mob/living/carbon/attacked_by(): (mob/living/carbon/target, mob/living/user, hit_zone)
-#define COMSIG_ITEM_IMBUE_SOUL "item_imbue_soul" //return a truthy value to prevent ensouling, checked in /obj/effect/proc_holder/spell/targeted/lichdom/cast(): (mob/user)
-#define COMSIG_ITEM_MARK_RETRIEVAL "item_mark_retrieval" //called before marking an object for retrieval, checked in /obj/effect/proc_holder/spell/targeted/summonitem/cast() : (mob/user)
- #define COMPONENT_BLOCK_MARK_RETRIEVAL 1
-#define COMSIG_ITEM_HIT_REACT "item_hit_react" //from base of obj/item/hit_reaction(): (list/args)
-#define COMSIG_ITEM_WEARERCROSSED "wearer_crossed" //called on item when crossed by something (): (/atom/movable, mob/living/crossed)
-#define COMSIG_ITEM_MICROWAVE_ACT "microwave_act" //called on item when microwaved (): (obj/machinery/microwave/M)
-#define COMSIG_ITEM_SHARPEN_ACT "sharpen_act" //from base of item/sharpener/attackby(): (amount, max)
- #define COMPONENT_BLOCK_SHARPEN_APPLIED 1
- #define COMPONENT_BLOCK_SHARPEN_BLOCKED 2
- #define COMPONENT_BLOCK_SHARPEN_ALREADY 4
- #define COMPONENT_BLOCK_SHARPEN_MAXED 8
-#define COMSIG_TOOL_IN_USE "tool_in_use" ///from base of [/obj/item/proc/tool_check_callback]: (mob/living/user)
-#define COMSIG_TOOL_START_USE "tool_start_use" ///from base of [/obj/item/proc/tool_start_check]: (mob/living/user)
-#define COMSIG_ITEM_DISABLE_EMBED "item_disable_embed" ///from [/obj/item/proc/disableEmbedding]:
-#define COMSIG_MINE_TRIGGERED "minegoboom" ///from [/obj/item/mine/proc/trigger_mine]:
-///from [/obj/structure/closet/supplypod/proc/endlaunch]:
-#define COMSIG_SUPPLYPOD_LANDED "supplypodgoboom"
-
-// Item mouse siganls
-#define COMSIG_ITEM_MOUSE_EXIT "item_mouse_exit" //from base of obj/item/MouseExited(): (location, control, params)
-#define COMSIG_ITEM_MOUSE_ENTER "item_mouse_enter" //from base of obj/item/MouseEntered(): (location, control, params)
-
-///Called when an item is being offered, from [/obj/item/proc/on_offered(mob/living/carbon/offerer)]
-#define COMSIG_ITEM_OFFERING "item_offering"
- ///Interrupts the offer proc
- #define COMPONENT_OFFER_INTERRUPT (1<<0)
-///Called when an someone tries accepting an offered item, from [/obj/item/proc/on_offer_taken(mob/living/carbon/offerer, mob/living/carbon/taker)]
-#define COMSIG_ITEM_OFFER_TAKEN "item_offer_taken"
- ///Interrupts the offer acceptance
- #define COMPONENT_OFFER_TAKE_INTERRUPT (1<<0)
-/// sent from obj/effect/attackby(): (/obj/effect/hit_effect, /mob/living/attacker, params)
-#define COMSIG_ITEM_ATTACK_EFFECT "item_effect_attacked"
-
-// /obj/item signals for economy
-#define COMSIG_ITEM_SOLD "item_sold" //called when an item is sold by the exports subsystem
-#define COMSIG_STRUCTURE_UNWRAPPED "structure_unwrapped" //called when a wrapped up structure is opened by hand
-#define COMSIG_ITEM_UNWRAPPED "item_unwrapped" //called when a wrapped up item is opened by hand
- #define COMSIG_ITEM_SPLIT_VALUE 1
-#define COMSIG_ITEM_SPLIT_PROFIT "item_split_profits" //Called when getting the item's exact ratio for cargo's profit.
-#define COMSIG_ITEM_SPLIT_PROFIT_DRY "item_split_profits_dry" //Called when getting the item's exact ratio for cargo's profit, without selling the item.
/// Admin helps
/// From /datum/admin_help/RemoveActive().
@@ -585,21 +563,6 @@
/// Called when the player replies. From /client/proc/cmd_admin_pm().
#define COMSIG_ADMIN_HELP_REPLIED "admin_help_replied"
-// /obj/item/clothing signals
-#define COMSIG_SHOES_STEP_ACTION "shoes_step_action" //from base of obj/item/clothing/shoes/proc/step_action(): ()
-#define COMSIG_SUIT_SPACE_TOGGLE "suit_space_toggle" //from base of /obj/item/clothing/suit/space/proc/toggle_spacesuit(): (obj/item/clothing/suit/space/suit)
-
-// /obj/item/implant signals
-#define COMSIG_IMPLANT_ACTIVATED "implant_activated" //from base of /obj/item/implant/proc/activate(): ()
-#define COMSIG_IMPLANT_IMPLANTING "implant_implanting" //from base of /obj/item/implant/proc/implant(): (list/args)
- #define COMPONENT_STOP_IMPLANTING 1
-#define COMSIG_IMPLANT_OTHER "implant_other" //called on already installed implants when a new one is being added in /obj/item/implant/proc/implant(): (list/args, obj/item/implant/new_implant)
- //#define COMPONENT_STOP_IMPLANTING 1 //The name makes sense for both
- #define COMPONENT_DELETE_NEW_IMPLANT 2
- #define COMPONENT_DELETE_OLD_IMPLANT 4
-#define COMSIG_IMPLANT_EXISTING_UPLINK "implant_uplink_exists" //called on implants being implanted into someone with an uplink implant: (datum/component/uplink)
- //This uses all return values of COMSIG_IMPLANT_OTHER
-
// /obj/item/pda signals
#define COMSIG_PDA_CHANGE_RINGTONE "pda_change_ringtone" //called on pda when the user changes the ringtone: (mob/living/user, new_ringtone)
#define COMPONENT_STOP_RINGTONE_CHANGE 1
@@ -615,10 +578,6 @@
// /obj/item/gun signals
#define COMSIG_MOB_FIRED_GUN "mob_fired_gun" //called in /obj/item/gun/process_fire (user, target, params, zone_override)
-// /obj/item/grenade signals
-#define COMSIG_GRENADE_PRIME "grenade_prime" //called in /obj/item/gun/process_fire (user, target, params, zone_override)
-#define COMSIG_GRENADE_ARMED "grenade_armed" //called in /obj/item/gun/process_fire (user, target, params, zone_override)
-
// /obj/projectile signals (sent to the firer)
#define COMSIG_PROJECTILE_SELF_ON_HIT "projectile_self_on_hit" // from base of /obj/projectile/proc/on_hit(): (atom/movable/firer, atom/target, Angle)
#define COMSIG_PROJECTILE_ON_HIT "projectile_on_hit" // from base of /obj/projectile/proc/on_hit(): (atom/movable/firer, atom/target, Angle)
@@ -633,13 +592,6 @@
// /obj/mecha signals
#define COMSIG_MECHA_ACTION_ACTIVATE "mecha_action_activate" //sent from mecha action buttons to the mecha they're linked to
-// /mob/living/carbon/human signals
-#define COMSIG_HUMAN_EARLY_UNARMED_ATTACK "human_early_unarmed_attack" //from mob/living/carbon/human/UnarmedAttack(): (atom/target, proximity)
-#define COMSIG_HUMAN_MELEE_UNARMED_ATTACK "human_melee_unarmed_attack" //from mob/living/carbon/human/UnarmedAttack(): (atom/target, proximity)
-#define COMSIG_HUMAN_MELEE_UNARMED_ATTACKBY "human_melee_unarmed_attackby" //from mob/living/carbon/human/UnarmedAttack(): (mob/living/carbon/human/attacker)
-#define COMSIG_HUMAN_DISARM_HIT "human_disarm_hit" //Hit by successful disarm attack (mob/living/carbon/human/attacker,zone_targeted)
-#define COMSIG_JOB_RECEIVED "job_received" //Whenever EquipRanked is called, called after job is set
-
// /datum/species signals
#define COMSIG_SPECIES_GAIN "species_gain" //from datum/species/on_species_gain(): (datum/species/new_species, datum/species/old_species)
#define COMSIG_SPECIES_LOSS "species_loss" //from datum/species/on_species_loss(): (datum/species/lost_species)
@@ -665,9 +617,6 @@
//Creamed
#define COMSIG_COMPONENT_CLEAN_FACE_ACT "clean_face_act" //called when you wash your face at a sink: (num/strength)
-//Food
-#define COMSIG_FOOD_EATEN "food_eaten" //from base of obj/item/reagent_containers/food/snacks/attack(): (mob/living/eater, mob/feeder)
-
//Gibs
#define COMSIG_GIBS_STREAK "gibs_streak" // from base of /obj/effect/decal/cleanable/blood/gibs/streak(): (list/directions, list/diseases)
@@ -753,10 +702,6 @@
#define COMSIG_BEAM_BEFORE_DRAW "beam_before_draw"
#define BEAM_CANCEL_DRAW (1 << 0)
-// Aquarium related signals
-#define COMSIG_AQUARIUM_SURFACE_CHANGED "aquarium_surface_changed"
-#define COMSIG_AQUARIUM_FLUID_CHANGED "aquarium_fluid_changed"
-
// Fish signals
#define COMSIG_FISH_STATUS_CHANGED "fish_status_changed"
#define COMSIG_FISH_STIRRED "fish_stirred"
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm
new file mode 100644
index 00000000000..2428eddf134
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm
@@ -0,0 +1,82 @@
+///Called from /mob/living/carbon/help_shake_act, before any hugs have ocurred. (mob/living/helper)
+#define COMSIG_CARBON_PRE_HELP_ACT "carbon_pre_help"
+ /// Stops the rest of help act (hugging, etc) from occuring
+ #define COMPONENT_BLOCK_HELP_ACT (1<<0)
+
+///Called from /mob/living/carbon/help_shake_act on the person being helped, after any hugs have ocurred. (mob/living/helper)
+#define COMSIG_CARBON_HELP_ACT "carbon_help"
+///Called from /mob/living/carbon/help_shake_act on the helper, after any hugs have ocurred. (mob/living/helped)
+#define COMSIG_CARBON_HELPED "carbon_helped_someone"
+
+///Before a carbon mob is shoved, sent to the turf we're trying to shove onto (mob/living/carbon/shover, mob/living/carbon/target)
+#define COMSIG_CARBON_DISARM_PRESHOVE "carbon_disarm_preshove"
+ #define COMSIG_CARBON_ACT_SOLID (1<<0) //Tells disarm code to act as if the mob was shoved into something solid, even we we're not
+///When a carbon mob is disarmed, this is sent to the turf we're trying to shove onto (mob/living/carbon/shover, mob/living/carbon/target, shove_blocked)
+#define COMSIG_CARBON_DISARM_COLLIDE "carbon_disarm_collision"
+ #define COMSIG_CARBON_SHOVE_HANDLED (1<<0)
+
+// /mob/living/carbon physiology signals
+#define COMSIG_CARBON_GAIN_WOUND "carbon_gain_wound" //from /datum/wound/proc/apply_wound() (/mob/living/carbon/C, /datum/wound/W, /obj/item/bodypart/L)
+#define COMSIG_CARBON_LOSE_WOUND "carbon_lose_wound" //from /datum/wound/proc/remove_wound() (/mob/living/carbon/C, /datum/wound/W, /obj/item/bodypart/L)
+///from base of /obj/item/bodypart/proc/attach_limb(): (new_limb, special) allows you to fail limb attachment
+#define COMSIG_CARBON_ATTACH_LIMB "carbon_attach_limb"
+#define COMSIG_CARBON_REMOVE_LIMB "carbon_remove_limb" //from base of /obj/item/bodypart/proc/drop_limb(lost_limb, dismembered)
+#define COMSIG_BODYPART_GAUZED "bodypart_gauzed" // from /obj/item/bodypart/proc/apply_gauze(/obj/item/stack/gauze)
+#define COMSIG_BODYPART_GAUZE_DESTROYED "bodypart_degauzed" // from [/obj/item/bodypart/proc/seep_gauze] when it runs out of absorption
+
+///Called when someone attempts to cuff a carbon
+#define COMSIG_CARBON_CUFF_ATTEMPTED "carbon_attempt_cuff"
+///Called when a carbon mutates (source = dna, mutation = mutation added)
+#define COMSIG_CARBON_GAIN_MUTATION "carbon_gain_mutation"
+///Called when a carbon loses a mutation (source = dna, mutation = mutation lose)
+#define COMSIG_CARBON_LOSE_MUTATION "carbon_lose_mutation"
+///Called when a carbon becomes addicted (source = what addiction datum, addicted_mind = mind of the addicted carbon)
+#define COMSIG_CARBON_GAIN_ADDICTION "carbon_gain_addiction"
+///Called when a carbon is no longer addicted (source = what addiction datum was lost, addicted_mind = mind of the freed carbon)
+#define COMSIG_CARBON_LOSE_ADDICTION "carbon_lose_addiction"
+///Called when a carbon gets a brain trauma (source = carbon, trauma = what trauma was added) - this is before on_gain()
+#define COMSIG_CARBON_GAIN_TRAUMA "carbon_gain_trauma"
+///Called when a carbon loses a brain trauma (source = carbon, trauma = what trauma was removed)
+#define COMSIG_CARBON_LOSE_TRAUMA "carbon_lose_trauma"
+///Called when a carbon updates their health (source = carbon)
+#define COMSIG_CARBON_HEALTH_UPDATE "carbon_health_update"
+///Called when a carbon updates their sanity (source = carbon)
+#define COMSIG_CARBON_SANITY_UPDATE "carbon_sanity_update"
+///Called when a carbon breathes, before the breath has actually occured
+#define COMSIG_CARBON_PRE_BREATHE "carbon_pre_breathe"
+///Called when a carbon updates their mood
+#define COMSIG_CARBON_MOOD_UPDATE "carbon_mood_update"
+
+// /mob/living/carbon/human signals
+
+///Hit by successful disarm attack (mob/living/carbon/human/attacker,zone_targeted)
+#define COMSIG_HUMAN_DISARM_HIT "human_disarm_hit"
+///Whenever EquipRanked is called, called after job is set
+#define COMSIG_JOB_RECEIVED "job_received"
+///from /mob/living/carbon/human/proc/set_coretemperature(): (oldvalue, newvalue)
+#define COMSIG_HUMAN_CORETEMP_CHANGE "human_coretemp_change"
+///from /datum/species/handle_fire. Called when the human is set on fire and burning clothes and stuff
+#define COMSIG_HUMAN_BURNING "human_burning"
+///from mob/living/carbon/human/UnarmedAttack(): (atom/target, proximity, modifiers)
+#define COMSIG_HUMAN_EARLY_UNARMED_ATTACK "human_early_unarmed_attack"
+///from mob/living/carbon/human/UnarmedAttack(): (atom/target, proximity, modifiers)
+#define COMSIG_HUMAN_MELEE_UNARMED_ATTACK "human_melee_unarmed_attack"
+//from mob/living/carbon/human/UnarmedAttack(): (mob/living/carbon/human/attacker)
+#define COMSIG_HUMAN_MELEE_UNARMED_ATTACKBY "human_melee_unarmed_attackby"
+//from /mob/living/carbon/human/proc/check_shields(): (atom/hit_by, damage, attack_text, attack_type, armour_penetration)
+#define COMSIG_HUMAN_CHECK_SHIELDS "human_check_shields"
+ #define SHIELD_BLOCK (1<<0)
+
+// Mob transformation signals
+///Called when a human turns into a monkey, from /mob/living/carbon/proc/finish_monkeyize()
+#define COMSIG_HUMAN_MONKEYIZE "human_monkeyize"
+///Called when a monkey turns into a human, from /mob/living/carbon/proc/finish_humanize(species)
+#define COMSIG_MONKEY_HUMANIZE "monkey_humanize"
+
+///From mob/living/carbon/human/suicide()
+#define COMSIG_HUMAN_SUICIDE_ACT "human_suicide_act"
+
+/// A mob has just equipped an item. Called on [/mob] from base of [/obj/item/equipped()]: (/obj/item/equipped_item, slot)
+#define COMSIG_MOB_EQUIPPED_ITEM "mob_equipped_item"
+/// A mob has just unequipped an item.
+#define COMSIG_MOB_UNEQUIPPED_ITEM "mob_unequipped_item"
diff --git a/code/__DEFINES/dcs/signals/signals_mod.dm b/code/__DEFINES/dcs/signals/signals_mod.dm
new file mode 100644
index 00000000000..e5c27a902a6
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_mod.dm
@@ -0,0 +1,25 @@
+//MODsuit signals
+/// Called when a module is selected to be the active one from on_select(obj/item/mod/module/module)
+#define COMSIG_MOD_MODULE_SELECTED "mod_module_selected"
+/// Called when a MOD activation is called from toggle_activate(mob/user)
+#define COMSIG_MOD_ACTIVATE "mod_activate"
+ /// Cancels the suit's activation
+ #define MOD_CANCEL_ACTIVATE (1 << 0)
+/// Called when a MOD is having modules removed from crowbar_act(mob/user, obj/crowbar)
+#define COMSIG_MOD_MODULE_REMOVAL "mod_module_removal"
+ /// Cancels the removal of modules
+ #define MOD_CANCEL_REMOVAL (1 << 0)
+/// Called when a module attempts to activate, however it does. At the end of checks so you can add some yourself, or work on trigger behavior (mob/user)
+#define COMSIG_MODULE_TRIGGERED "mod_module_triggered"
+ // Cancels activation, with no message. include feedback on your cancel.
+ #define MOD_ABORT_USE (1<<0)
+/// Called when a module activates, after all checks have passed and cooldown started.
+#define COMSIG_MODULE_ACTIVATED "mod_module_activated"
+/// Called when a module deactivates, after all checks have passed.
+#define COMSIG_MODULE_DEACTIVATED "mod_module_deactivated"
+/// Called when a module is used, after all checks have passed and cooldown started.
+#define COMSIG_MODULE_USED "mod_module_used"
+/// Called when the MODsuit wearer is set.
+#define COMSIG_MOD_WEARER_SET "mod_wearer_set"
+/// Called when the MODsuit wearer is unset.
+#define COMSIG_MOD_WEARER_UNSET "mod_wearer_unset"
diff --git a/code/__DEFINES/dcs/signals/signals_obj/signals_item/signals_clothing.dm b/code/__DEFINES/dcs/signals/signals_obj/signals_item/signals_clothing.dm
new file mode 100644
index 00000000000..6eb107d55fd
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_obj/signals_item/signals_clothing.dm
@@ -0,0 +1,10 @@
+// Format:
+// When the signal is called: (signal arguments)
+// All signals send the source datum of the signal as the first argument
+
+// /obj/item/clothing signals
+
+//from base of obj/item/clothing/shoes/proc/step_action(): ()
+#define COMSIG_SHOES_STEP_ACTION "shoes_step_action"
+//from base of /obj/item/clothing/suit/space/proc/toggle_spacesuit(): (obj/item/clothing/suit/space/suit)
+#define COMSIG_SUIT_SPACE_TOGGLE "suit_space_toggle"
diff --git a/code/__DEFINES/dcs/signals/signals_obj/signals_item/signals_food.dm b/code/__DEFINES/dcs/signals/signals_obj/signals_item/signals_food.dm
new file mode 100644
index 00000000000..831363f5a52
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_obj/signals_item/signals_food.dm
@@ -0,0 +1,22 @@
+// Eating stuff
+/// From datum/component/edible/proc/TakeBite: (mob/living/eater, mob/feeder, bitecount, bitesize)
+#define COMSIG_FOOD_EATEN "food_eaten"
+/// From base of Component/edible/On_Consume: (mob/living/eater, mob/living/feeder)
+#define COMSIG_FOOD_CONSUMED "food_consumed"
+
+// Deep frying foods
+/// An item becomes fried - From /datum/element/fried_item/Attach: (fry_time)
+#define COMSIG_ITEM_FRIED "item_fried"
+ #define COMSIG_FRYING_HANDLED (1<<0)
+
+// Microwaving foods
+///called on item when microwaved (): (obj/machinery/microwave/M)
+#define COMSIG_ITEM_MICROWAVE_ACT "microwave_act"
+ #define COMPONENT_SUCCESFUL_MICROWAVE (1<<0)
+///called on item when created through microwaving (): (obj/machinery/microwave/M, cooking_efficiency)
+#define COMSIG_ITEM_MICROWAVE_COOKED "microwave_cooked"
+
+///From /datum/component/edible/on_compost(source, /mob/living/user)
+#define COMSIG_EDIBLE_ON_COMPOST "on_compost"
+ // Used to stop food from being composted.
+ #define COMPONENT_EDIBLE_BLOCK_COMPOST 1
diff --git a/code/__DEFINES/dcs/signals/signals_obj/signals_item/signals_grenade.dm b/code/__DEFINES/dcs/signals/signals_obj/signals_item/signals_grenade.dm
new file mode 100644
index 00000000000..03767ecbc53
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_obj/signals_item/signals_grenade.dm
@@ -0,0 +1,11 @@
+// Format:
+// When the signal is called: (signal arguments)
+// All signals send the source datum of the signal as the first argument
+
+// /obj/item/grenade signals
+
+//called in /obj/item/gun/process_fire (user, target, params, zone_override)
+#define COMSIG_GRENADE_PRIME "grenade_prime"
+
+//called in /obj/item/gun/process_fire (user, target, params, zone_override)
+#define COMSIG_GRENADE_ARMED "grenade_armed"
diff --git a/code/__DEFINES/dcs/signals/signals_obj/signals_item/signals_hydroponic.dm b/code/__DEFINES/dcs/signals/signals_obj/signals_item/signals_hydroponic.dm
new file mode 100644
index 00000000000..32fbb4867b2
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_obj/signals_item/signals_hydroponic.dm
@@ -0,0 +1,34 @@
+//Plants / Plant Traits
+
+///called when a plant with slippery skin is slipped on (mob/victim)
+#define COMSIG_PLANT_ON_SLIP "plant_on_slip"
+///called when a plant with liquid contents is squashed on (atom/target)
+#define COMSIG_PLANT_ON_SQUASH "plant_on_squash"
+///called when a plant backfires via the backfire element (mob/victim)
+#define COMSIG_PLANT_ON_BACKFIRE "plant_on_backfire"
+///called when a seed grows in a tray (obj/machinery/hydroponics)
+#define COMSIG_SEED_ON_GROW "plant_on_grow"
+///called when a seed is planted in a tray (obj/machinery/hydroponics)
+#define COMSIG_SEED_ON_PLANTED "plant_on_plant"
+
+//Hydro tray
+///from base of /obj/machinery/hydroponics/set_seed() : (obj/item/new_seed)
+#define COMSIG_HYDROTRAY_SET_SEED "hydrotray_set_seed"
+///from base of /obj/machinery/hydroponics/set_self_sustaining() : (new_value)
+#define COMSIG_HYDROTRAY_SET_SELFSUSTAINING "hydrotray_set_selfsustaining"
+///from base of /obj/machinery/hydroponics/set_weedlevel() : (new_value)
+#define COMSIG_HYDROTRAY_SET_WEEDLEVEL "hydrotray_set_weedlevel"
+///from base of /obj/machinery/hydroponics/set_pestlevel() : (new_value)
+#define COMSIG_HYDROTRAY_SET_PESTLEVEL "hydrotray_set_pestlevel"
+///from base of /obj/machinery/hydroponics/set_waterlevel() : (new_value)
+#define COMSIG_HYDROTRAY_SET_WATERLEVEL "hydrotray_set_waterlevel"
+///from base of /obj/machinery/hydroponics/set_plant_health() : (new_value)
+#define COMSIG_HYDROTRAY_SET_PLANT_HEALTH "hydrotray_set_plant_health"
+///from base of /obj/machinery/hydroponics/set_toxic() : (new_value)
+#define COMSIG_HYDROTRAY_SET_TOXIC "hydrotray_set_toxic"
+///from base of /obj/machinery/hydroponics/set_plant_status() : (new_value)
+#define COMSIG_HYDROTRAY_SET_PLANT_STATUS "hydrotray_set_plant_status"
+///from base of /obj/machinery/hydroponics/update_tray() : (mob/user, product_count)
+#define COMSIG_HYDROTRAY_ON_HARVEST "hydrotray_on_harvest"
+///from base of /obj/machinery/hydroponics/plantdies()
+#define COMSIG_HYDROTRAY_PLANT_DEATH "hydrotray_plant_death"
diff --git a/code/__DEFINES/dcs/signals/signals_obj/signals_item/signals_implant.dm b/code/__DEFINES/dcs/signals/signals_obj/signals_item/signals_implant.dm
new file mode 100644
index 00000000000..95123ef8b30
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_obj/signals_item/signals_implant.dm
@@ -0,0 +1,14 @@
+// Format:
+// When the signal is called: (signal arguments)
+// All signals send the source datum of the signal as the first argument
+
+// /obj/item/implant signals
+#define COMSIG_IMPLANT_ACTIVATED "implant_activated" //from base of /obj/item/implant/proc/activate(): ()
+#define COMSIG_IMPLANT_IMPLANTING "implant_implanting" //from base of /obj/item/implant/proc/implant(): (list/args)
+ #define COMPONENT_STOP_IMPLANTING 1
+#define COMSIG_IMPLANT_OTHER "implant_other" //called on already installed implants when a new one is being added in /obj/item/implant/proc/implant(): (list/args, obj/item/implant/new_implant)
+ //#define COMPONENT_STOP_IMPLANTING 1 //The name makes sense for both
+ #define COMPONENT_DELETE_NEW_IMPLANT 2
+ #define COMPONENT_DELETE_OLD_IMPLANT 4
+#define COMSIG_IMPLANT_EXISTING_UPLINK "implant_uplink_exists" //called on implants being implanted into someone with an uplink implant: (datum/component/uplink)
+ //This uses all return values of COMSIG_IMPLANT_OTHER
diff --git a/code/__DEFINES/dcs/signals/signals_obj/signals_item/signals_item.dm b/code/__DEFINES/dcs/signals/signals_obj/signals_item/signals_item.dm
new file mode 100644
index 00000000000..1a562b84a4a
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_obj/signals_item/signals_item.dm
@@ -0,0 +1,68 @@
+// Format:
+// When the signal is called: (signal arguments)
+// All signals send the source datum of the signal as the first argument
+
+// /obj/item signals
+///from base of obj/item/equipped(): (/mob/equipper, slot)
+#define COMSIG_ITEM_EQUIPPED "item_equip"
+///from base of obj/item/dropped(): (mob/user)
+#define COMSIG_ITEM_DROPPED "item_drop"
+///from base of obj/item/pickup(): (/mob/taker)
+#define COMSIG_ITEM_PICKUP "item_pickup"
+
+///from base of mob/living/carbon/attacked_by(): (mob/living/carbon/target, mob/living/user, hit_zone)
+#define COMSIG_ITEM_ATTACK_ZONE "item_attack_zone"
+///from base of obj/item/hit_reaction(): (list/args)
+#define COMSIG_ITEM_HIT_REACT "item_hit_react" //from base of obj/item/hit_reaction(): (list/args)
+ #define COMPONENT_HIT_REACTION_BLOCK (1<<0)
+
+#define COMSIG_ITEM_ATTACK "item_attack" //from base of obj/item/attack(): (/mob/living/target, /mob/living/user)
+#define COMSIG_ITEM_ATTACK_SELF "item_attack_self" //from base of obj/item/attack_self(): (/mob)
+ #define COMPONENT_NO_INTERACT 1
+#define COMSIG_ITEM_ATTACK_OBJ "item_attack_obj" //from base of obj/item/attack_obj(): (/obj, /mob)
+ #define COMPONENT_NO_ATTACK_OBJ 1
+#define COMSIG_ITEM_PRE_ATTACK "item_pre_attack" //from base of obj/item/pre_attack(): (atom/target, mob/user, params)
+ #define COMPONENT_NO_ATTACK 1
+#define COMSIG_ITEM_AFTERATTACK "item_afterattack" //from base of obj/item/afterattack(): (atom/target, mob/user, params)
+#define COMSIG_ITEM_ATTACK_QDELETED "item_attack_qdeleted" //from base of obj/item/attack_qdeleted(): (atom/target, mob/user, params)
+#define COMSIG_ITEM_IMBUE_SOUL "item_imbue_soul" //return a truthy value to prevent ensouling, checked in /obj/effect/proc_holder/spell/targeted/lichdom/cast(): (mob/user)
+#define COMSIG_ITEM_MARK_RETRIEVAL "item_mark_retrieval" //called before marking an object for retrieval, checked in /obj/effect/proc_holder/spell/targeted/summonitem/cast() : (mob/user)
+ #define COMPONENT_BLOCK_MARK_RETRIEVAL 1
+#define COMSIG_ITEM_WEARERCROSSED "wearer_crossed" //called on item when crossed by something (): (/atom/movable, mob/living/crossed)
+
+///from base of item/sharpener/attackby(): (amount, max)
+#define COMSIG_ITEM_SHARPEN_ACT "sharpen_act"
+ #define COMPONENT_BLOCK_SHARPEN_APPLIED 1
+ #define COMPONENT_BLOCK_SHARPEN_BLOCKED 2
+ #define COMPONENT_BLOCK_SHARPEN_ALREADY 4
+ #define COMPONENT_BLOCK_SHARPEN_MAXED 8
+
+#define COMSIG_TOOL_IN_USE "tool_in_use" ///from base of [/obj/item/proc/tool_check_callback]: (mob/living/user)
+#define COMSIG_TOOL_START_USE "tool_start_use" ///from base of [/obj/item/proc/tool_start_check]: (mob/living/user)
+#define COMSIG_ITEM_DISABLE_EMBED "item_disable_embed" ///from [/obj/item/proc/disableEmbedding]:
+#define COMSIG_MINE_TRIGGERED "minegoboom" ///from [/obj/item/mine/proc/trigger_mine]:
+///from [/obj/structure/closet/supplypod/proc/endlaunch]:
+#define COMSIG_SUPPLYPOD_LANDED "supplypodgoboom"
+
+// Item mouse siganls
+#define COMSIG_ITEM_MOUSE_EXIT "item_mouse_exit" //from base of obj/item/MouseExited(): (location, control, params)
+#define COMSIG_ITEM_MOUSE_ENTER "item_mouse_enter" //from base of obj/item/MouseEntered(): (location, control, params)
+
+///Called when an item is being offered, from [/obj/item/proc/on_offered(mob/living/carbon/offerer)]
+#define COMSIG_ITEM_OFFERING "item_offering"
+ ///Interrupts the offer proc
+ #define COMPONENT_OFFER_INTERRUPT (1<<0)
+///Called when an someone tries accepting an offered item, from [/obj/item/proc/on_offer_taken(mob/living/carbon/offerer, mob/living/carbon/taker)]
+#define COMSIG_ITEM_OFFER_TAKEN "item_offer_taken"
+ ///Interrupts the offer acceptance
+ #define COMPONENT_OFFER_TAKE_INTERRUPT (1<<0)
+/// sent from obj/effect/attackby(): (/obj/effect/hit_effect, /mob/living/attacker, params)
+#define COMSIG_ITEM_ATTACK_EFFECT "item_effect_attacked"
+
+// /obj/item signals for economy
+#define COMSIG_ITEM_SOLD "item_sold" //called when an item is sold by the exports subsystem
+#define COMSIG_STRUCTURE_UNWRAPPED "structure_unwrapped" //called when a wrapped up structure is opened by hand
+#define COMSIG_ITEM_UNWRAPPED "item_unwrapped" //called when a wrapped up item is opened by hand
+ #define COMSIG_ITEM_SPLIT_VALUE 1
+#define COMSIG_ITEM_SPLIT_PROFIT "item_split_profits" //Called when getting the item's exact ratio for cargo's profit.
+#define COMSIG_ITEM_SPLIT_PROFIT_DRY "item_split_profits_dry" //Called when getting the item's exact ratio for cargo's profit, without selling the item.
diff --git a/code/__DEFINES/dcs/signals/signals_obj/signals_machine/signals_aquarium.dm b/code/__DEFINES/dcs/signals/signals_obj/signals_machine/signals_aquarium.dm
new file mode 100644
index 00000000000..d4c88d7c16b
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_obj/signals_machine/signals_aquarium.dm
@@ -0,0 +1,7 @@
+// Format:
+// When the signal is called: (signal arguments)
+// All signals send the source datum of the signal as the first argument
+
+// Aquarium related signals
+#define COMSIG_AQUARIUM_SURFACE_CHANGED "aquarium_surface_changed"
+#define COMSIG_AQUARIUM_FLUID_CHANGED "aquarium_fluid_changed"
diff --git a/code/__DEFINES/dcs/signals/signals_obj/signals_machine/signals_machinery.dm b/code/__DEFINES/dcs/signals/signals_obj/signals_machine/signals_machinery.dm
new file mode 100644
index 00000000000..929a2f84407
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_obj/signals_machine/signals_machinery.dm
@@ -0,0 +1,12 @@
+// Format:
+// When the signal is called: (signal arguments)
+// All signals send the source datum of the signal as the first argument
+
+// /obj/machinery signals
+
+///from /obj/machinery/obj_break(damage_flag): (damage_flag)
+#define COMSIG_MACHINERY_BROKEN "machinery_broken"
+///from base power_change() when power is lost
+#define COMSIG_MACHINERY_POWER_LOST "machinery_power_lost"
+///from base power_change() when power is restored
+#define COMSIG_MACHINERY_POWER_RESTORED "machinery_power_restored"
diff --git a/code/__DEFINES/dcs/signals/signals_obj/signals_machine/signals_supermatter.dm b/code/__DEFINES/dcs/signals/signals_obj/signals_machine/signals_supermatter.dm
new file mode 100644
index 00000000000..b4e8abe2b80
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_obj/signals_machine/signals_supermatter.dm
@@ -0,0 +1,9 @@
+// Format:
+// When the signal is called: (signal arguments)
+// All signals send the source datum of the signal as the first argument
+
+// /obj/machinery/power/supermatter_crystal signals
+/// from /obj/machinery/power/supermatter_crystal/process_atmos(); when the SM delam reaches the point of sounding alarms
+#define COMSIG_SUPERMATTER_DELAM_START_ALARM "sm_delam_start_alarm"
+/// from /obj/machinery/power/supermatter_crystal/process_atmos(); when the SM sounds an audible alarm
+#define COMSIG_SUPERMATTER_DELAM_ALARM "sm_delam_alarm"
diff --git a/code/__DEFINES/dcs/signals/signals_obj/signals_object.dm b/code/__DEFINES/dcs/signals/signals_obj/signals_object.dm
new file mode 100644
index 00000000000..136b73ffb3d
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_obj/signals_object.dm
@@ -0,0 +1,14 @@
+// Format:
+// When the signal is called: (signal arguments)
+// All signals send the source datum of the signal as the first argument
+
+// /obj signals
+
+///from base of obj/deconstruct(): (disassembled)
+#define COMSIG_OBJ_DECONSTRUCT "obj_deconstruct"
+///from base of code/game/machinery
+#define COMSIG_OBJ_DEFAULT_UNFASTEN_WRENCH "obj_default_unfasten_wrench"
+///from base of /turf/proc/levelupdate(). (intact) true to hide and false to unhide
+#define COMSIG_OBJ_HIDE "obj_hide"
+/// from base of [/atom/proc/obj_destruction]: (damage_flag)
+#define COMSIG_OBJ_DESTRUCTION "obj_destruction"
diff --git a/code/__DEFINES/dcs/signals/signals_reagent.dm b/code/__DEFINES/dcs/signals/signals_reagent.dm
new file mode 100644
index 00000000000..957bb608367
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_reagent.dm
@@ -0,0 +1,59 @@
+// Atom reagent signals. Format:
+// When the signal is called: (signal arguments)
+// All signals send the source datum of the signal as the first argument
+
+///from base of atom/expose_reagents(): (/list, /datum/reagents, methods, volume_modifier, show_message)
+//#define COMSIG_ATOM_EXPOSE_REAGENTS "atom_expose_reagents"
+ /// Prevents the atom from being exposed to reagents if returned on [COMSIG_ATOM_EXPOSE_REAGENTS]
+ //#define COMPONENT_NO_EXPOSE_REAGENTS (1<<0)
+///from base of atom/expose_reagents(): (/list, /datum/reagents, methods, volume_modifier, show_message)
+//#define COMSIG_ATOM_AFTER_EXPOSE_REAGENTS "atom_after_expose_reagents"
+///from base of [/datum/reagent/proc/expose_atom]: (/datum/reagent, reac_volume)
+//#define COMSIG_ATOM_EXPOSE_REAGENT "atom_expose_reagent"
+///from base of [/datum/reagent/proc/expose_atom]: (/atom, reac_volume)
+//#define COMSIG_REAGENT_EXPOSE_ATOM "reagent_expose_atom"
+///from base of [/datum/reagent/proc/expose_atom]: (/obj, reac_volume)
+//#define COMSIG_REAGENT_EXPOSE_OBJ "reagent_expose_obj"
+///from base of [/datum/reagent/proc/expose_atom]: (/mob/living, reac_volume, methods, show_message, touch_protection, /mob/camera/blob) // ovemind arg is only used by blob reagents.
+//#define COMSIG_REAGENT_EXPOSE_MOB "reagent_expose_mob"
+///from base of [/datum/reagent/proc/expose_atom]: (/turf, reac_volume)
+//#define COMSIG_REAGENT_EXPOSE_TURF "reagent_expose_turf"
+
+///from base of [/datum/materials_controller/proc/InitializeMaterial]: (/datum/material)
+//#define COMSIG_MATERIALS_INIT_MAT "SSmaterials_init_mat"
+
+///from base of [/datum/component/multiple_lives/proc/respawn]: (mob/respawned_mob, gibbed, lives_left)
+//#define COMSIG_ON_MULTIPLE_LIVES_RESPAWN "on_multiple_lives_respawn"
+
+///from base of [/datum/reagents/proc/add_reagent] - Sent before the reagent is added: (reagenttype, amount, reagtemp, data, no_react)
+//#define COMSIG_REAGENTS_PRE_ADD_REAGENT "reagents_pre_add_reagent"
+ /// Prevents the reagent from being added.
+ //#define COMPONENT_CANCEL_REAGENT_ADD (1<<0)
+///from base of [/datum/reagents/proc/add_reagent]: (/datum/reagent, amount, reagtemp, data, no_react)
+#define COMSIG_REAGENTS_NEW_REAGENT "reagents_new_reagent"
+///from base of [/datum/reagents/proc/add_reagent]: (/datum/reagent, amount, reagtemp, data, no_react)
+#define COMSIG_REAGENTS_ADD_REAGENT "reagents_add_reagent"
+///from base of [/datum/reagents/proc/del_reagent]: (/datum/reagent)
+#define COMSIG_REAGENTS_DEL_REAGENT "reagents_del_reagent"
+///from base of [/datum/reagents/proc/remove_reagent]: (/datum/reagent, amount)
+#define COMSIG_REAGENTS_REM_REAGENT "reagents_rem_reagent"
+///from base of [/datum/reagents/proc/clear_reagents]: ()
+#define COMSIG_REAGENTS_CLEAR_REAGENTS "reagents_clear_reagents"
+///from base of [/datum/reagents/proc/set_temperature]: (new_temp, old_temp)
+//#define COMSIG_REAGENTS_TEMP_CHANGE "reagents_temp_change"
+///from base of [/datum/reagents/proc/handle_reactions]: (num_reactions)
+//#define COMSIG_REAGENTS_REACTED "reagents_reacted"
+///from base of [/datum/reagents/proc/process]: (num_reactions)
+//#define COMSIG_REAGENTS_REACTION_STEP "reagents_time_step"
+///from base of [/atom/proc/expose_reagents]: (/atom, /list, methods, volume_modifier, show_message)
+//#define COMSIG_REAGENTS_EXPOSE_ATOM "reagents_expose_atom"
+///from base of [/obj/proc/expose_reagents]: (/obj, /list, methods, volume_modifier, show_message)
+//#define COMSIG_REAGENTS_EXPOSE_OBJ "reagents_expose_obj"
+///from base of [/mob/living/proc/expose_reagents]: (/mob/living, /list, methods, volume_modifier, show_message, touch_protection)
+//#define COMSIG_REAGENTS_EXPOSE_MOB "reagents_expose_mob"
+///from base of [/turf/proc/expose_reagents]: (/turf, /list, methods, volume_modifier, show_message)
+//#define COMSIG_REAGENTS_EXPOSE_TURF "reagents_expose_turf"
+/// sent when reagents are transfered from a cup, to something refillable (atom/transfer_to)
+//#define COMSIG_REAGENTS_CUP_TRANSFER_TO "reagents_cup_transfer_to"
+/// sent when reagents are transfered from some reagent container, to a cup (atom/transfer_from)
+//#define COMSIG_REAGENTS_CUP_TRANSFER_FROM "reagents_cup_transfer_from"
diff --git a/code/__DEFINES/dcs/signals/signals_storage.dm b/code/__DEFINES/dcs/signals/signals_storage.dm
new file mode 100644
index 00000000000..456ac3c0781
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_storage.dm
@@ -0,0 +1,4 @@
+/// Sent when /datum/storage/dump_content_at(): (obj/item/storage_source, mob/user)
+#define COMSIG_STORAGE_DUMP_CONTENT "storage_dump_contents"
+ /// Return to stop the standard dump behavior.
+ #define STORAGE_DUMP_HANDLED (1<<0)
diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm
index c27a78ffd2d..00093c8ecf7 100644
--- a/code/__DEFINES/flags.dm
+++ b/code/__DEFINES/flags.dm
@@ -46,6 +46,8 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204
#define SHOW_BEHIND_LARGE_ICONS_1 (1<<12)
/// Should we use the initial icon for display? Mostly used by overlay only objects
#define HTML_USE_INITAL_ICON_1 (1<<20)
+// Whether or not this atom is storing contents for a disassociated storage object
+#define HAS_DISASSOCIATED_STORAGE_1 (1<<24)
// Update flags for [/atom/proc/update_appearance]
/// Update the atom's name
diff --git a/code/__DEFINES/food.dm b/code/__DEFINES/food.dm
index f2b6a8fd196..a12ceca284d 100644
--- a/code/__DEFINES/food.dm
+++ b/code/__DEFINES/food.dm
@@ -49,6 +49,16 @@
#define DRINK_FANTASTIC 4
#define FOOD_AMAZING 5
+/// Food is "in a container", not in a code sense, but in a literal sense (canned foods)
#define FOOD_IN_CONTAINER (1<<0)
+/// Finger food can be eaten while walking / running around
+#define FOOD_FINGER_FOOD (1<<1)
#define STOP_SERVING_BREAKFAST (15 MINUTES)
+
+///Amount of reagents you start with on crafted food excluding the used parts
+#define CRAFTED_FOOD_BASE_REAGENT_MODIFIER 0.7
+///Modifier of reagents you get when crafting food from the parts used
+#define CRAFTED_FOOD_INGREDIENT_REAGENT_MODIFIER 0.5
+
+#define IS_EDIBLE(O) (O.GetComponent(/datum/component/edible))
diff --git a/code/__DEFINES/inventory.dm b/code/__DEFINES/inventory.dm
index 64aa6aa5262..6cad7078b6e 100644
--- a/code/__DEFINES/inventory.dm
+++ b/code/__DEFINES/inventory.dm
@@ -46,6 +46,7 @@
#define HIDEFACIALHAIR (1<<9)
#define HIDENECK (1<<10)
#define HIDEHORNS (1<<11) // Used for hiding Sarathi horns.
+#define HIDESNOUT (1<<11)
//bitflags for clothing coverage - also used for limbs
#define HEAD (1<<0)
diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index 598d16ee823..9659ac17c64 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -168,6 +168,8 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list(
#define isitem(A) (istype(A, /obj/item))
+#define isstack(A) (istype(A, /obj/item/stack))
+
#define isgrenade(A) (istype(A, /obj/item/grenade))
#define islandmine(A) (istype(A, /obj/item/mine))
diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm
index d021558901f..bd9b0f0063b 100644
--- a/code/__DEFINES/layers.dm
+++ b/code/__DEFINES/layers.dm
@@ -1,5 +1,6 @@
//Defines for atom layers and planes
//KEEP THESE IN A NICE ACSCENDING ORDER, PLEASE
+#define LOWEST_EVER_PLANE -100
#define CLICKCATCHER_PLANE -99
@@ -21,6 +22,20 @@
#define BLACKNESS_PLANE 0 //To keep from conflicts with SEE_BLACKNESS internals
#define BLACKNESS_PLANE_RENDER_TARGET "BLACKNESS_PLANE"
+#define ABOVE_GAME_PLANE 1
+
+//-------------------- Rendering ---------------------
+#define RENDER_PLANE_GAME 100
+#define RENDER_PLANE_NON_GAME 101
+#define RENDER_PLANE_MASTER 102
+
+// Lummox I swear to god I will find you
+// NOTE! You can only ever have planes greater then -10000, if you add too many with large offsets you will brick multiz
+// Same can be said for large multiz maps. Tread carefully mappers
+#define HIGHEST_EVER_PLANE RENDER_PLANE_MASTER
+/// The range unique planes can be in
+#define PLANE_RANGE (HIGHEST_EVER_PLANE - LOWEST_EVER_PLANE)
+
#define SPACE_LAYER 1.8
//#define TURF_LAYER 2 //For easy recordkeeping; this is a byond define
#define MID_TURF_LAYER 2.02
diff --git a/code/__DEFINES/machines.dm b/code/__DEFINES/machines.dm
index a89965da7be..02e6853338d 100644
--- a/code/__DEFINES/machines.dm
+++ b/code/__DEFINES/machines.dm
@@ -123,9 +123,16 @@
#define MACHINE_ELECTRIFIED_PERMANENT -1
#define MACHINE_DEFAULT_ELECTRIFY_TIME 30
-//these flags are used to tell the DNA modifier if a plant gene cannot be extracted or modified.
+/// -- Flags for genes --
+/// Plant genes that can be removed via gene shears.
#define PLANT_GENE_REMOVABLE (1<<0)
-#define PLANT_GENE_EXTRACTABLE (1<<1)
+/// Plant genes that can be mutated randomly in strange seeds / due to high instability.
+#define PLANT_GENE_MUTATABLE (1<<1)
+#define PLANT_GENE_EXTRACTABLE (1<<2)
+
+/// -- Flags for traits. --
+/// Caps the plant's yield at 5 instead of 10.
+#define TRAIT_HALVES_YIELD (1<<0)
//used to determine what rotation mode the ore redemption machine is in
#define ORM_BOTH 0
diff --git a/code/__DEFINES/maths.dm b/code/__DEFINES/maths.dm
index 719f06f2a81..a442ddb464b 100644
--- a/code/__DEFINES/maths.dm
+++ b/code/__DEFINES/maths.dm
@@ -290,3 +290,11 @@
/// Like SPT_PROB_RATE but easier to use, simply put `if(SPT_PROB(10, 5))`
#define SPT_PROB(prob_per_second_percent, seconds_per_tick) (prob(100*SPT_PROB_RATE((prob_per_second_percent)/100, (seconds_per_tick))))
+
+/// Converts a probability/second chance to probability/delta_time chance
+/// For example, if you want an event to happen with a 10% per second chance, but your proc only runs every 5 seconds, do `if(prob(100*DT_PROB_RATE(0.1, 5)))`
+#define DT_PROB_RATE(prob_per_second, delta_time) (1 - (1 - (prob_per_second)) ** (delta_time))
+
+/// Like DT_PROB_RATE but easier to use, simply put `if(DT_PROB(10, 5))`
+#define DT_PROB(prob_per_second_percent, delta_time) (prob(100*DT_PROB_RATE((prob_per_second_percent)/100, (delta_time))))
+// )
diff --git a/code/__DEFINES/mod.dm b/code/__DEFINES/mod.dm
new file mode 100644
index 00000000000..29a450eceb9
--- /dev/null
+++ b/code/__DEFINES/mod.dm
@@ -0,0 +1,40 @@
+/// Default value for the max_complexity var on MODsuits
+#define DEFAULT_MAX_COMPLEXITY 15
+
+/// Default cell drain per process on MODsuits
+#define DEFAULT_CHARGE_DRAIN 0.09
+
+/// Default time for a part to seal
+#define MOD_ACTIVATION_STEP_TIME (2 SECONDS)
+
+/// Passive module, just acts when put in naturally.
+#define MODULE_PASSIVE 0
+/// Usable module, does something when you press a button.
+#define MODULE_USABLE 1
+/// Toggle module, you turn it on/off and it does stuff.
+#define MODULE_TOGGLE 2
+/// Actively usable module, you may only have one selected at a time.
+#define MODULE_ACTIVE 3
+
+//Defines used by the theme for clothing flags and similar
+#define CONTROL_LAYER "control_layer"
+#define HELMET_FLAGS "helmet_flags"
+#define CHESTPLATE_FLAGS "chestplate_flags"
+#define GAUNTLETS_FLAGS "gauntlets_flags"
+#define BOOTS_FLAGS "boots_flags"
+
+#define UNSEALED_LAYER "unsealed_layer"
+#define UNSEALED_CLOTHING "unsealed_clothing"
+#define SEALED_CLOTHING "sealed_clothing"
+#define UNSEALED_INVISIBILITY "unsealed_invisibility"
+#define SEALED_INVISIBILITY "sealed_invisibility"
+#define UNSEALED_COVER "unsealed_cover"
+#define SEALED_COVER "sealed_cover"
+#define CAN_OVERSLOT "can_overslot"
+
+//Defines used to override MOD clothing's icon and worn icon files in the skin.
+#define MOD_ICON_OVERRIDE "mod_icon_override"
+#define MOD_WORN_ICON_OVERRIDE "mod_worn_icon_override"
+
+/// Global list of all /datum/mod_theme
+GLOBAL_LIST_INIT(mod_themes, setup_mod_themes())
diff --git a/code/__DEFINES/processing.dm b/code/__DEFINES/processing.dm
new file mode 100644
index 00000000000..905c03830d5
--- /dev/null
+++ b/code/__DEFINES/processing.dm
@@ -0,0 +1,3 @@
+#define TOOL_PROCESSING_RESULT "result"
+#define TOOL_PROCESSING_AMOUNT "amount"
+#define TOOL_PROCESSING_TIME "time"
diff --git a/code/__DEFINES/sound.dm b/code/__DEFINES/sound.dm
index d4d9807ec0a..285e7ce5ff8 100644
--- a/code/__DEFINES/sound.dm
+++ b/code/__DEFINES/sound.dm
@@ -173,3 +173,37 @@
#define SOUND_AREA_LAVALAND SOUND_ENVIRONMENT_MOUNTAINS
#define SOUND_AREA_ICEMOON SOUND_ENVIRONMENT_CAVE
#define SOUND_AREA_WOODFLOOR SOUND_ENVIRONMENT_CITY
+
+/// List of all of our sound keys.
+#define SFX_BODYFALL "bodyfall"
+#define SFX_BULLET_MISS "bullet_miss"
+#define SFX_CAN_OPEN "can_open"
+#define SFX_CLOWN_STEP "clown_step"
+#define SFX_DESECRATION "desecration"
+#define SFX_EXPLOSION "explosion"
+#define SFX_EXPLOSION_CREAKING "explosion_creaking"
+#define SFX_HISS "hiss"
+#define SFX_HONKBOT_E "honkbot_e"
+#define SFX_HULL_CREAKING "hull_creaking"
+#define SFX_HYPERTORUS_CALM "hypertorus_calm"
+#define SFX_HYPERTORUS_MELTING "hypertorus_melting"
+#define SFX_IM_HERE "im_here"
+#define SFX_LAW "law"
+#define SFX_PAGE_TURN "page_turn"
+#define SFX_PUNCH "punch"
+#define SFX_REVOLVER_SPIN "revolver_spin"
+#define SFX_RICOCHET "ricochet"
+#define SFX_RUSTLE "rustle"
+#define SFX_SHATTER "shatter"
+#define SFX_SM_CALM "sm_calm"
+#define SFX_SM_DELAM "sm_delam"
+#define SFX_SPARKS "sparks"
+#define SFX_SUIT_STEP "suit_step"
+#define SFX_SWING_HIT "swing_hit"
+#define SFX_TERMINAL_TYPE "terminal_type"
+#define SFX_WARPSPEED "warpspeed"
+#define SFX_CRUNCHY_BUSH_WHACK "crunchy_bush_whack"
+#define SFX_TREE_CHOP "tree_chop"
+#define SFX_ROCK_TAP "rock_tap"
+
+#define SOUND_EMPTY_MAG 'sound/weapons/empty.ogg'
diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm
index d1fbf26616d..68913e3925f 100644
--- a/code/__DEFINES/status_effects.dm
+++ b/code/__DEFINES/status_effects.dm
@@ -99,6 +99,14 @@
#define STATUS_EFFECT_METAB_FROZEN /datum/status_effect/metab_frozen // Affected cannot process chems
+//Incapacitated status effect flags
+/// If the incapacitated status effect will ignore a mob in restraints (handcuffs)
+#define IGNORE_RESTRAINTS (1<<0)
+/// If the incapacitated status effect will ignore a mob in stasis (stasis beds)
+#define IGNORE_STASIS (1<<1)
+/// If the incapacitated status effect will ignore a mob being agressively grabbed
+#define IGNORE_GRAB (1<<2)
+
/////////////
// NEUTRAL //
/////////////
diff --git a/code/__DEFINES/tools.dm b/code/__DEFINES/tools.dm
index 320648170b4..35860ac927f 100644
--- a/code/__DEFINES/tools.dm
+++ b/code/__DEFINES/tools.dm
@@ -14,6 +14,7 @@
#define TOOL_DRILL "drill"
#define TOOL_SCALPEL "scalpel"
#define TOOL_SAW "saw"
+#define TOOL_KNIFE "knife" //luv me kuh-nyfe
// If delay between the start and the end of tool operation is less than MIN_TOOL_SOUND_DELAY,
// tool sound is only played when op is started. If not, it's played twice.
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index f88f0c9d791..878320bb7cd 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -179,7 +179,13 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_VIRUSIMMUNE "virus_immunity"
#define TRAIT_PIERCEIMMUNE "pierce_immunity"
#define TRAIT_NODISMEMBER "dismember_immunity"
+#define TRAIT_LAVA_IMMUNE "lava_immunity"
+#define TRAIT_SNOWSTORM_IMMUNE "snow_immunity"
+#define TRAIT_ASHSTORM_IMMUNE "ash_immunity"
+#define TRAIT_SANDSTORM_IMMUNE "sand_immunity"
#define TRAIT_NOFIRE "nonflammable"
+/// Prevents plasmamen from self-igniting if only their helmet is missing
+#define TRAIT_NOSELFIGNITION_HEAD_ONLY "no_selfignition_head_only"
#define TRAIT_NOGUNS "no_guns"
#define TRAIT_NOHUNGER "no_hunger"
#define TRAIT_NOMETABOLISM "no_metabolism"
@@ -215,6 +221,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_NOMOBSWAP "no-mob-swap"
#define TRAIT_XRAY_VISION "xray_vision"
#define TRAIT_THERMAL_VISION "thermal_vision"
+/// Like antimagic, but doesn't block the user from casting
+#define TRAIT_ANTIMAGIC_NO_SELFBLOCK "anti_magic_no_selfblock"
/// We have some form of forced gravity acting on us
#define TRAIT_FORCED_GRAVITY "forced_gravity"
#define TRAIT_ABDUCTOR_TRAINING "abductor-training"
@@ -248,6 +256,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_GAMERGOD "gamer-god" //double arcade prizes
#define TRAIT_GIANT "giant"
#define TRAIT_DWARF "dwarf"
+#define TRAIT_FASTMED "fast_med_use"
#define TRAIT_SILENT_FOOTSTEPS "silent_footsteps" //makes your footsteps completely silent
#define TRAIT_NICE_SHOT "nice_shot" //hnnnnnnnggggg..... you're pretty good....
/// The holder of this trait has antennae or whatever that hurt a ton when noogied
@@ -264,6 +273,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_SCOOPABLE "scoopable"
//your smooches actually deal damage to their target
#define TRAIT_KISS_OF_DEATH "kiss_of_death"
+/// We can handle 'dangerous' plants in botany safely
+#define TRAIT_PLANT_SAFE "plant_safe"
/// This mob overrides certian SSlag_switch measures with this special trait
#define TRAIT_BYPASS_MEASURES "bypass_lagswitch_measures"
//non-mob traits
@@ -279,6 +290,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_AREA_SENSITIVE "area-sensitive"
///Used for managing KEEP_TOGETHER in [/atom/var/appearance_flags]
+///every object that is currently the active storage of some client mob has this trait
+#define TRAIT_ACTIVE_STORAGE "active_storage"
+
#define TRAIT_KEEP_TOGETHER "keep-together"
// item traits
@@ -362,6 +376,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define LYING_DOWN_TRAIT "lying-down"
/// Trait associated to lacking electrical power.
#define POWER_LACK_TRAIT "power-lack"
+/// Trait applied by MODsuits.
+#define MOD_TRAIT "mod"
// unique trait sources, still defines
#define CLONING_POD_TRAIT "cloning-pod"
@@ -435,6 +451,31 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define BEAUTY_ELEMENT_TRAIT "beauty_element"
#define MOOD_COMPONENT_TRAIT "mood_component"
+// mobility flag traits
+// IN THE FUTURE, IT WOULD BE NICE TO DO SOMETHING SIMILAR TO https://github.com/tgstation/tgstation/pull/48923/files (ofcourse not nearly the same because I have my.. thoughts on it)
+// BUT FOR NOW, THESE ARE HOOKED TO DO update_mobility() VIA COMSIG IN living_mobility.dm
+// SO IF YOU ADD MORE, BESURE TO UPDATE IT THERE.
+
+/// Disallow movement
+#define TRAIT_MOBILITY_NOMOVE "mobility_nomove"
+/// Disallow pickup
+#define TRAIT_MOBILITY_NOPICKUP "mobility_nopickup"
+/// Disallow item use
+#define TRAIT_MOBILITY_NOUSE "mobility_nouse"
+///Disallow resting/unresting
+#define TRAIT_MOBILITY_NOREST "mobility_norest"
+
+#define TRAIT_FORCED_STANDING "forcedstanding"
+
+///Movement type traits for movables. See elements/movetype_handler.dm
+#define TRAIT_MOVE_GROUND "move_ground"
+#define TRAIT_MOVE_FLYING "move_flying"
+#define TRAIT_MOVE_VENTCRAWLING "move_ventcrawling"
+#define TRAIT_MOVE_FLOATING "move_floating"
+#define TRAIT_MOVE_PHASING "move_phasing"
+/// Disables the floating animation. See above.
+#define TRAIT_NO_FLOATING_ANIM "no-floating-animation"
+
/// Trait granted by [mob/living/silicon/ai]
/// Applied when the ai anchors itself
#define AI_ANCHOR_TRAIT "ai_anchor"
diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm
index f603e85292a..435b83e2979 100644
--- a/code/__HELPERS/_lists.dm
+++ b/code/__HELPERS/_lists.dm
@@ -109,13 +109,25 @@
return "[output][and_text][input[index]]"
-//Checks for specific types in a list
-/proc/is_type_in_list(atom/A, list/L)
- if(!LAZYLEN(L) || !A)
+/**
+ * Checks for specific types in a list.
+ *
+ * If using zebra mode the list should be an assoc list with truthy/falsey values.
+ * The check short circuits so earlier entries in the input list will take priority.
+ * Ergo, subtypes should come before parent types.
+ * Notice that this is the opposite priority of [/proc/typecacheof].
+ *
+ * Arguments:
+ * - [type_to_check][/datum]: An instance to check.
+ * - [list_to_check][/list]: A list of typepaths to check the type_to_check against.
+ * - zebra: Whether to use the value of the matching type in the list instead of just returning true when a match is found.
+ */
+/proc/is_type_in_list(datum/type_to_check, list/list_to_check, zebra = FALSE)
+ if(!LAZYLEN(list_to_check) || !type_to_check)
return FALSE
- for(var/type in L)
- if(istype(A, type))
- return TRUE
+ for(var/type in list_to_check)
+ if(istype(type_to_check, type))
+ return !zebra || list_to_check[type] // Subtypes must come first in zebra lists.
return FALSE
//Checks for specific types in specifically structured (Assoc "type" = TRUE) lists ('typecaches')
diff --git a/code/__HELPERS/_planes.dm b/code/__HELPERS/_planes.dm
new file mode 100644
index 00000000000..d8306c356d4
--- /dev/null
+++ b/code/__HELPERS/_planes.dm
@@ -0,0 +1,80 @@
+// This file contains helper macros for plane operations
+// See the planes section of Visuals.md for more detail, but essentially
+// When we render multiz, we do it by placing all atoms on lower levels on well, lower planes
+// This is done with stacks of plane masters (things we use to apply effects to planes)
+// These macros exist to facilitate working with this system, and other associated small bits
+
+/// Takes an atom to change the plane of, a new plane value, and something that can be used as a reference to a z level as input
+/// Modifies the new value to match the plane we actually want. Note, if you pass in an already offset plane the offsets will add up
+/// Use PLANE_TO_TRUE() to avoid this
+#define SET_PLANE(thing, new_value, z_reference) (thing.plane = MUTATE_PLANE(new_value, z_reference))
+
+/// Takes a plane and a z reference, and offsets the plane by the mutation
+/// The SSmapping.max_plane_offset bit here is technically redundant, but saves a bit of work in the base case
+/// And the base case is important to me. Non multiz shouldn't get hit too bad by this code
+#define MUTATE_PLANE(new_value, z_reference) ((SSmapping.max_plane_offset) ? GET_NEW_PLANE(new_value, GET_TURF_PLANE_OFFSET(z_reference)) : (new_value))
+
+/// Takes a z reference that we are unsure of, sanity checks it
+/// Returns either its offset, or 0 if it's not a valid ref
+#define GET_TURF_PLANE_OFFSET(z_reference) ((SSmapping.max_plane_offset && isatom(z_reference)) ? GET_Z_PLANE_OFFSET(z_reference.z) : 0)
+/// Essentially just an unsafe version of GET_TURF_PLANE_OFFSET()
+/// Takes a z value we returns its offset with a list lookup
+/// Will runtime during parts of init. Be careful :)
+#define GET_Z_PLANE_OFFSET(z) (SSmapping.z_level_to_plane_offset[z])
+
+/// Takes a plane to offset, and the multiplier to use, and well, does the offsetting
+/// Respects a blacklist we use to remove redundant plane masters, such as hud objects
+#define GET_NEW_PLANE(new_value, multiplier) (SSmapping.plane_offset_blacklist?["[new_value]"] ? new_value : (new_value) - (PLANE_RANGE * (multiplier)))
+
+// Now for the more niche things
+
+/// Takes an object, new plane, and multipler, and offsets the plane
+/// This is for cases where you have a multipler precalculated, and just want to use it
+/// Often an optimization, sometimes a necessity
+#define SET_PLANE_W_SCALAR(thing, new_value, multiplier) (thing.plane = GET_NEW_PLANE(new_value, multiplier))
+
+
+/// Implicit plane set. We take the turf from the object we're changing the plane of, and use ITS z as a spokesperson for our plane value
+#define SET_PLANE_IMPLICIT(thing, new_value) SET_PLANE_EXPLICIT(thing, new_value, thing)
+
+// This is an unrolled and optimized version of SET_PLANE, for use anywhere where you are unsure of a source's "turfness"
+// The plane is cached to allow for fancy stuff to be eval'd once, rather then often
+#define SET_PLANE_EXPLICIT(thing, new_value, source) \
+ do {\
+ if(SSmapping.max_plane_offset) {\
+ var/_cached_plane = new_value;\
+ var/turf/_our_turf = get_turf(source);\
+ if(_our_turf){\
+ thing.plane = GET_NEW_PLANE(_cached_plane, GET_Z_PLANE_OFFSET(_our_turf.z));\
+ }\
+ }\
+ else {\
+ thing.plane = new_value;\
+ }\
+ }\
+ while (FALSE)
+
+// Now for macros that exist to get info from SSmapping
+// Mostly about details of planes, or z levels
+
+/// Takes a z level, gets the lowest plane offset in its "stack"
+#define GET_LOWEST_STACK_OFFSET(z) ((SSmapping.max_plane_offset) ? SSmapping.z_level_to_lowest_plane_offset[z] : 0)
+/// Takes a plane, returns the canonical, unoffset plane it represents
+#define PLANE_TO_TRUE(plane) ((SSmapping.plane_offset_to_true) ? SSmapping.plane_offset_to_true["[plane]"] : plane)
+/// Takes a plane, returns the offset it uses
+#define PLANE_TO_OFFSET(plane) ((SSmapping.plane_to_offset) ? SSmapping.plane_to_offset["[plane]"] : plane)
+/// Takes a plane, returns TRUE if it is of critical priority, FALSE otherwise
+#define PLANE_IS_CRITICAL(plane) ((SSmapping.plane_to_offset) ? !!SSmapping.critical_planes["[plane]"] : FALSE)
+/// Takes a true plane, returns the offset planes that would canonically represent it
+#define TRUE_PLANE_TO_OFFSETS(plane) ((SSmapping.true_to_offset_planes) ? SSmapping.true_to_offset_planes["[plane]"] : list(plane))
+/// Takes a render target and an offset, returns a canonical render target string for it
+#define OFFSET_RENDER_TARGET(render_target, offset) (_OFFSET_RENDER_TARGET(render_target, SSmapping.render_offset_blacklist?["[render_target]"] ? 0 : offset))
+/// Helper macro for the above
+/// Honestly just exists to make the pattern of render target strings more readable
+#define _OFFSET_RENDER_TARGET(render_target, offset) ("[(render_target)] #[(offset)]")
+
+// Known issues:
+// Potentially too much client load? Hard to tell due to not having a potato pc to hand.
+// This is solvable with lowspec preferences, which would not be hard to implement
+// Player popups will now render their effects, like overlay lights. this is fixable, but I've not gotten to it
+// I think overlay lights can render on the wrong z layer. s fucked
diff --git a/code/__HELPERS/atoms.dm b/code/__HELPERS/atoms.dm
new file mode 100644
index 00000000000..dbb42122ff4
--- /dev/null
+++ b/code/__HELPERS/atoms.dm
@@ -0,0 +1,9 @@
+///Returns the src and all recursive contents as a list.
+/atom/proc/get_all_contents(ignore_flag_1)
+ . = list(src)
+ var/i = 0
+ while(i < length(.))
+ var/atom/checked_atom = .[++i]
+ if(checked_atom.flags_1 & ignore_flag_1)
+ continue
+ . += checked_atom.contents
diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm
index e7af7f31884..34cacd872d4 100644
--- a/code/__HELPERS/game.dm
+++ b/code/__HELPERS/game.dm
@@ -596,3 +596,31 @@ block( \
continue
C.energy_fail(rand(duration_min,duration_max))
+
+///Returns a list of turfs around a center based on RANGE_TURFS()
+/proc/circle_range_turfs(center = usr, radius = 3)
+
+ var/turf/center_turf = get_turf(center)
+ var/list/turfs = new/list()
+ var/rsq = radius * (radius + 0.5)
+
+ for(var/turf/checked_turf as anything in RANGE_TURFS(radius, center_turf))
+ var/dx = checked_turf.x - center_turf.x
+ var/dy = checked_turf.y - center_turf.y
+ if(dx * dx + dy * dy <= rsq)
+ turfs += checked_turf
+ return turfs
+
+///Returns a list of turfs around a center based on view()
+/proc/circle_view_turfs(center=usr,radius=3) //Is there even a diffrence between this proc and circle_range_turfs()?
+
+ var/turf/center_turf = get_turf(center)
+ var/list/turfs = new/list()
+ var/rsq = radius * (radius + 0.5)
+
+ for(var/turf/checked_turf in view(radius, center_turf))
+ var/dx = checked_turf.x - center_turf.x
+ var/dy = checked_turf.y - center_turf.y
+ if(dx * dx + dy * dy <= rsq)
+ turfs += checked_turf
+ return turfs
diff --git a/code/__HELPERS/maths.dm b/code/__HELPERS/maths.dm
new file mode 100644
index 00000000000..983ecc80027
--- /dev/null
+++ b/code/__HELPERS/maths.dm
@@ -0,0 +1,13 @@
+///Calculate the angle between two movables and the west|east coordinate
+/proc/get_angle(atom/movable/start, atom/movable/end)//For beams.
+ if(!start || !end)
+ return 0
+ var/dy =(32 * end.y + end.pixel_y) - (32 * start.y + start.pixel_y)
+ var/dx =(32 * end.x + end.pixel_x) - (32 * start.x + start.pixel_x)
+ if(!dy)
+ return (dx >= 0) ? 90 : 270
+ . = arctan(dx/dy)
+ if(dy < 0)
+ . += 180
+ else if(dx < 0)
+ . += 360
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index 6a3028443dc..ae5a1c1ce92 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -257,7 +257,19 @@ GLOBAL_LIST_EMPTY(species_list)
return ..()
/**
- * Timed action involving one mob user. A target can also be specified, but it is optional.
+ * Used to get the amount of change between two body temperatures
+ *
+ * When passed the difference between two temperatures returns the amount of change to temperature to apply.
+ * The change rate should be kept at a low value tween 0.16 and 0.02 for optimal results.
+ * vars:
+ * * temp_diff (required) The differance between two temperatures
+ * * change_rate (optional)(Default: 0.06) The rate of range multiplyer
+ */
+/proc/get_temp_change_amount(temp_diff, change_rate = 0.06)
+ if(temp_diff < 0)
+ return -(BODYTEMP_AUTORECOVERY_DIVISOR / 2) * log(1 - (temp_diff * change_rate))
+
+/* Timed action involving one mob user. A target can also be specified, but it is optional.
*
* Checks that `user` does not move, change hands, get stunned, etc. for the
* given `delay`. Returns `TRUE` on success or `FALSE` on failure.
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index c8c7b63d0a0..36764c6bae9 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -1420,10 +1420,15 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
REMOVE_TRAIT(the_atom2,trait,source)
/proc/get_random_food()
- var/list/blocked = list(/obj/item/reagent_containers/food/snacks/store/bread,
- /obj/item/reagent_containers/food/snacks/breadslice,
- /obj/item/reagent_containers/food/snacks/store/cake,
- /obj/item/reagent_containers/food/snacks/cakeslice,
+ var/static/list/allowed_food = list()
+
+ if(!LAZYLEN(allowed_food)) //it's static so we only ever do this once
+ var/list/blocked = list(
+ /obj/item/food/spaghetti,
+ /obj/item/food/bread,
+ /obj/item/food/breadslice,
+ /obj/item/food/cake,
+ /obj/item/food/cakeslice,
/obj/item/reagent_containers/food/snacks/store,
/obj/item/reagent_containers/food/snacks/pie,
/obj/item/reagent_containers/food/snacks/kebab,
@@ -1435,15 +1440,21 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
/obj/item/reagent_containers/food/snacks/soup,
/obj/item/reagent_containers/food/snacks/grown,
/obj/item/reagent_containers/food/snacks/grown/mushroom,
- /obj/item/reagent_containers/food/snacks/deepfryholder,
+ /obj/item/food/deepfryholder,
/obj/item/reagent_containers/food/snacks/clothing,
/obj/item/reagent_containers/food/snacks/grown/shell, //base types
- /obj/item/reagent_containers/food/snacks/store/bread,
+ /obj/item/food/bread,
/obj/item/reagent_containers/food/snacks/grown/nettle
)
- blocked |= typesof(/obj/item/reagent_containers/food/snacks/customizable)
+ blocked |= typesof(/obj/item/reagent_containers/food/snacks/customizable)
- return pick(subtypesof(/obj/item/reagent_containers/food/snacks) - blocked)
+ var/list/unfiltered_allowed_food = subtypesof(/obj/item/food) - blocked
+ for(var/obj/item/food/food as anything in unfiltered_allowed_food)
+ if(!initial(food.icon_state)) //food with no icon_state should probably not be spawned
+ continue
+ allowed_food.Add(food)
+
+ return pick(allowed_food)
/proc/get_random_drink()
var/list/blocked = list(/obj/item/reagent_containers/food/drinks/soda_cans,
diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm
index 5f5c26731d1..8eda1e52faf 100644
--- a/code/_globalvars/traits.dm
+++ b/code/_globalvars/traits.dm
@@ -147,8 +147,8 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_HOLDABLE" = TRAIT_HOLDABLE,
"TRAIT_SCOOPABLE" = TRAIT_SCOOPABLE,
"TRAIT_ANXIOUS" = TRAIT_ANXIOUS,
- "TRAIT_KISS_OF_DEATH" = TRAIT_KISS_OF_DEATH
-
+ "TRAIT_KISS_OF_DEATH" = TRAIT_KISS_OF_DEATH,
+ "TRAIT_PLANT_SAFE" = TRAIT_PLANT_SAFE
),
/obj/item/bodypart = list(
"TRAIT_PARALYSIS" = TRAIT_PARALYSIS
diff --git a/code/_onclick/hud/action_button.dm b/code/_onclick/hud/action_button.dm
index fb2bac17503..6cd9aa8a084 100644
--- a/code/_onclick/hud/action_button.dm
+++ b/code/_onclick/hud/action_button.dm
@@ -56,10 +56,16 @@
if(id && usr.client) //try to (un)remember position
usr.client.prefs.action_buttons_screen_locs["[name]_[id]"] = locked ? moved : null
return TRUE
+ var/trigger_flags
+ if(LAZYACCESS(modifiers, ALT_CLICK))
+ if(locked)
+ to_chat(usr, "Action button \"[name]\" is locked, unlock it first.")
+ return TRUE
+ trigger_flags |= TRIGGER_SECONDARY_ACTION
if(usr.next_click > world.time)
return
usr.next_click = world.time + 1
- linked_action.Trigger()
+ linked_action.Trigger(trigger_flags)
return TRUE
//Hide/Show Action Buttons ... Button
diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm
index 43ae8ffbe24..1fa51487d6b 100644
--- a/code/_onclick/hud/alert.dm
+++ b/code/_onclick/hud/alert.dm
@@ -587,6 +587,29 @@ Recharging stations are available in robotics, the dormitory bathrooms, and the
desc = "Your blood's electric charge is becoming dangerously high, find an outlet for your energy. Use Grab Intent on an APC to channel your energy into it."
icon_state = "ethereal_overcharge"
+//MODsuit unique
+/atom/movable/screen/alert/nocore
+ name = "Missing Core"
+ desc = "Unit has no core. No modules available until a core is reinstalled. Robotics may provide assistance."
+ icon_state = "no_cell"
+
+/atom/movable/screen/alert/emptycell/plasma
+ name = "Out of Power"
+ desc = "Unit's plasma core has no charge remaining. No modules available until plasma core is recharged. \
+ Unit can be refilled through plasma fuel."
+
+/atom/movable/screen/alert/emptycell/plasma/update_desc()
+ . = ..()
+ desc = initial(desc)
+
+/atom/movable/screen/alert/lowcell/plasma
+ name = "Low Charge"
+ desc = "Unit's plasma core is running low. Unit can be refilled through plasma fuel."
+
+/atom/movable/screen/alert/lowcell/plasma/update_desc()
+ . = ..()
+ desc = initial(desc)
+
//Need to cover all use cases - emag, illegal upgrade module, malf AI hack, traitor cyborg
/atom/movable/screen/alert/hacked
name = "Hacked"
diff --git a/code/controllers/subsystem/mapping.dm b/code/controllers/subsystem/mapping.dm
index e7fc71a3e79..8e2da49f0fd 100644
--- a/code/controllers/subsystem/mapping.dm
+++ b/code/controllers/subsystem/mapping.dm
@@ -42,6 +42,28 @@ SUBSYSTEM_DEF(mapping)
/// Translation of virtual level ID to a virtual level reference
var/list/virtual_z_translation = list()
+ /// List of z level (as number) -> plane offset of that z level
+ /// Used to maintain the plane cube
+ var/list/z_level_to_plane_offset = list()
+ /// List of z level (as number) -> The lowest plane offset in that z stack
+ var/list/z_level_to_lowest_plane_offset = list()
+ // This pair allows for easy conversion between an offset plane, and its true representation
+ // Both are in the form "input plane" -> output plane(s)
+ /// Assoc list of string plane values to their true, non offset representation
+ var/list/plane_offset_to_true
+ /// Assoc list of true string plane values to a list of all potential offset planess
+ var/list/true_to_offset_planes
+ /// Assoc list of string plane to the plane's offset value
+ var/list/plane_to_offset
+ /// List of planes that do not allow for offsetting
+ var/list/plane_offset_blacklist
+ /// List of render targets that do not allow for offsetting
+ var/list/render_offset_blacklist
+ /// List of plane masters that are of critical priority
+ var/list/critical_planes
+ /// The largest plane offset we've generated so far
+ var/max_plane_offset = 0
+
/datum/controller/subsystem/mapping/Initialize(timeofday)
if(initialized)
return
diff --git a/code/datums/action.dm b/code/datums/action.dm
index de13fc002dd..9ceaf8bc9f6 100644
--- a/code/datums/action.dm
+++ b/code/datums/action.dm
@@ -1,8 +1,3 @@
-#define AB_CHECK_HANDS_BLOCKED (1<<0)
-#define AB_CHECK_IMMOBILE (1<<1)
-#define AB_CHECK_LYING (1<<2)
-#define AB_CHECK_CONSCIOUS (1<<3)
-
/datum/action
var/name = "Generic Action"
var/desc = null
@@ -90,7 +85,7 @@
button.locked = FALSE
button.id = null
-/datum/action/proc/Trigger()
+/datum/action/proc/Trigger(trigger_flags)
if(!IsAvailable())
return FALSE
if(SEND_SIGNAL(src, COMSIG_ACTION_TRIGGER, src) & COMPONENT_ACTION_BLOCK_TRIGGER)
diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm
index d76504787b0..6c15d00869f 100644
--- a/code/datums/components/_component.dm
+++ b/code/datums/components/_component.dm
@@ -204,6 +204,11 @@
else // Many other things have registered here
lookup[sig_type][src] = TRUE
+/// Registers multiple signals to the same proc.
+/datum/proc/RegisterSignals(datum/target, list/signal_types, proctype, override = FALSE)
+ for (var/signal_type in signal_types)
+ RegisterSignal(target, signal_type, proctype, override)
+
/**
* Stop listening to a given signal from target
*
diff --git a/code/datums/components/edible.dm b/code/datums/components/edible.dm
deleted file mode 100644
index b65a2d8b7e3..00000000000
--- a/code/datums/components/edible.dm
+++ /dev/null
@@ -1,259 +0,0 @@
-/*!
-
-This component makes it possible to make things edible. What this means is that you can take a bite or force someone to take a bite (in the case of items).
-These items take a specific time to eat, and can do most of the things our original food items could.
-
-Behavior that's still missing from this component that original food items had that should either be put into seperate components or somewhere else:
- Components:
- Drying component (jerky etc)
- Customizable component (custom pizzas etc)
- Processable component (Slicing and cooking behavior essentialy, making it go from item A to B when conditions are met.)
- Dunkable component (Dunking things into reagent containers to absorb a specific amount of reagents)
-
- Misc:
- Something for cakes (You can store things inside)
-
-*/
-/datum/component/edible
- ///Amount of reagents taken per bite
- var/bite_consumption = 2
- ///Amount of bites taken so far
- var/bitecount = 0
- ///Flags for food
- var/food_flags = NONE
- ///Bitfield of the types of this food
- var/foodtypes = NONE
- ///Amount of seconds it takes to eat this food
- var/eat_time = 30
- ///Defines how much it lowers someones satiety (Need to eat, essentialy)
- var/junkiness = 0
- ///Message to send when eating
- var/list/eatverbs
- ///Callback to be ran for when you take a bite of something
- var/datum/callback/after_eat
- ///Last time we checked for food likes
- var/last_check_time
- ///Color we use when stuffed in things
- var/filling_color = "#FFFFFF"
-
-/datum/component/edible/Initialize(list/initial_reagents, food_flags = NONE, foodtypes = NONE, volume = 50, eat_time = 30, list/tastes, list/eatverbs = list("bite","chew","nibble","gnaw","gobble","chomp"), bite_consumption = 2, filling_color = "#FFFFFF", datum/callback/after_eat)
- if(!isatom(parent))
- return COMPONENT_INCOMPATIBLE
-
- RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(examine))
- RegisterSignal(parent, COMSIG_ATOM_ATTACK_ANIMAL, PROC_REF(UseByAnimal))
- if(isitem(parent))
- RegisterSignal(parent, COMSIG_ITEM_ATTACK, PROC_REF(UseFromHand))
- else if(isturf(parent))
- RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, PROC_REF(TryToEatTurf))
-
- src.bite_consumption = bite_consumption
- src.food_flags = food_flags
- src.foodtypes = foodtypes
- src.eat_time = eat_time
- src.eatverbs = eatverbs
- src.junkiness = junkiness
- src.after_eat = after_eat
- src.filling_color = filling_color
-
- var/atom/owner = parent
-
- owner.create_reagents(volume, INJECTABLE)
-
- if(initial_reagents)
- for(var/rid in initial_reagents)
- var/amount = initial_reagents[rid]
- if(tastes && tastes.len && (rid == /datum/reagent/consumable/nutriment || rid == /datum/reagent/consumable/nutriment/vitamin))
- owner.reagents.add_reagent(rid, amount, tastes.Copy())
- else
- owner.reagents.add_reagent(rid, amount)
-
-/datum/component/edible/proc/examine(datum/source, mob/user, list/examine_list)
- SIGNAL_HANDLER
-
- if(!(food_flags & FOOD_IN_CONTAINER))
- switch (bitecount)
- if (0)
- return
- if(1)
- examine_list += "[parent] was bitten by someone!"
- if(2,3)
- examine_list += "[parent] was bitten [bitecount] times!"
- else
- examine_list += "[parent] was bitten multiple times!"
-
-/datum/component/edible/proc/UseFromHand(obj/item/source, mob/living/M, mob/living/user)
- SIGNAL_HANDLER
-
- return TryToEat(M, user)
-
-/datum/component/edible/proc/TryToEatTurf(datum/source, mob/user)
- SIGNAL_HANDLER
-
- return TryToEat(user, user)
-
-///All the checks for the act of eating itself and
-/datum/component/edible/proc/TryToEat(mob/living/eater, mob/living/feeder)
-
- set waitfor = FALSE
-
- var/atom/owner = parent
-
- if(feeder.a_intent == INTENT_HARM)
- return
- if(!owner.reagents.total_volume)//Shouldn't be needed but it checks to see if it has anything left in it.
- to_chat(feeder, "None of [owner] left, oh no!")
- if(isturf(parent))
- var/turf/T = parent
- T.ScrapeAway(1, CHANGETURF_INHERIT_AIR)
- else
- qdel(parent)
- return
- if(!CanConsume(eater, feeder))
- return
- var/fullness = eater.nutrition + 10 //The theoretical fullness of the person eating if they were to eat this
- for(var/datum/reagent/consumable/C in eater.reagents.reagent_list) //we add the nutrition value of what we're currently digesting
- fullness += C.nutriment_factor * C.volume / C.metabolization_rate
-
- . = COMPONENT_ITEM_NO_ATTACK //Point of no return I suppose
-
- if(eater == feeder)//If you're eating it yourself.
- if(!do_after(feeder, eat_time, eater)) //Gotta pass the minimal eat time
- return
- var/eatverb = pick(eatverbs)
- if(junkiness && eater.satiety < -150 && eater.nutrition > NUTRITION_LEVEL_STARVING + 50 && !HAS_TRAIT(eater, TRAIT_VORACIOUS))
- to_chat(eater, "You don't feel like eating any more junk food at the moment!")
- return
- else if(fullness <= 50)
- eater.visible_message("[eater] hungrily [eatverb]s \the [parent], gobbling it down!", "You hungrily [eatverb] \the [parent], gobbling it down!")
- else if(fullness > 50 && fullness < 150)
- eater.visible_message("[eater] hungrily [eatverb]s \the [parent].", "You hungrily [eatverb] \the [parent].")
- else if(fullness > 150 && fullness < 500)
- eater.visible_message("[eater] [eatverb]s \the [parent].", "You [eatverb] \the [parent].")
- else if(fullness > 500 && fullness < 600)
- eater.visible_message("[eater] unwillingly [eatverb]s a bit of \the [parent].", "You unwillingly [eatverb] a bit of \the [parent].")
- else if(fullness > (600 * (1 + eater.overeatduration / 2000))) // The more you eat - the more you can eat
- eater.visible_message("[eater] cannot force any more of \the [parent] to go down [eater.p_their()] throat!", "You cannot force any more of \the [parent] to go down your throat!")
- return
- else //If you're feeding it to someone else.
- if(isbrain(eater))
- to_chat(feeder, "[eater] doesn't seem to have a mouth!")
- return
- if(fullness <= (600 * (1 + eater.overeatduration / 1000)))
- eater.visible_message("[feeder] attempts to feed [eater] [parent].", \
- "[feeder] attempts to feed you [parent].")
- else
- eater.visible_message("[feeder] cannot force any more of [parent] down [eater]'s throat!", \
- "[feeder] cannot force any more of [parent] down your throat!")
- return
- if(!do_after(feeder, target = eater)) //Wait 3 seconds before you can feed
- return
-
- log_combat(feeder, eater, "fed", owner.reagents.log_list())
- eater.visible_message("[feeder] forces [eater] to eat [parent]!", \
- "[feeder] forces you to eat [parent]!")
-
- TakeBite(eater, feeder)
-
-///This function lets the eater take a bite and transfers the reagents to the eater.
-/datum/component/edible/proc/TakeBite(mob/living/eater, mob/living/feeder)
-
- var/atom/owner = parent
-
- if(!owner?.reagents)
- return FALSE
- if(eater.satiety > -200)
- eater.satiety -= junkiness
- playsound(eater.loc,'sound/items/eatfood.ogg', rand(10,50), TRUE)
- if(owner.reagents.total_volume)
- SEND_SIGNAL(parent, COMSIG_FOOD_EATEN, eater, feeder)
- var/fraction = min(bite_consumption / owner.reagents.total_volume, 1)
- owner.reagents.trans_to(eater, bite_consumption, transfered_by = feeder, method = INGEST)
- bitecount++
- On_Consume(eater)
- checkLiked(fraction, eater)
-
- //Invoke our after eat callback if it is valid
- if(after_eat)
- after_eat.Invoke(eater, feeder)
-
- return TRUE
-
-///Checks whether or not the eater can actually consume the food
-/datum/component/edible/proc/CanConsume(mob/living/eater, mob/living/feeder)
- if(!iscarbon(eater))
- return FALSE
- var/mob/living/carbon/C = eater
- var/covered = ""
- if(C.is_mouth_covered(head_only = 1))
- covered = "headgear"
- else if(C.is_mouth_covered(mask_only = 1))
- covered = "mask"
- if(covered)
- var/who = (isnull(feeder) || eater == feeder) ? "your" : "[eater.p_their()]"
- to_chat(feeder, "You have to remove [who] [covered] first!")
- return FALSE
- return TRUE
-
-///Check foodtypes to see if we should send a moodlet
-/datum/component/edible/proc/checkLiked(fraction, mob/M)
- if(last_check_time + 50 > world.time)
- return FALSE
- if(!ishuman(M))
- return FALSE
- var/mob/living/carbon/human/H = M
- if(HAS_TRAIT(H, TRAIT_AGEUSIA) && foodtypes & H.dna.species.toxic_food)
- to_chat(H, "You don't feel so good...")
- H.adjust_disgust(25 + 30 * fraction)
- else
- if(foodtypes & H.dna.species.toxic_food)
- to_chat(H,"What the hell was that thing?!")
- H.adjust_disgust(25 + 30 * fraction)
- SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "toxic_food", /datum/mood_event/disgusting_food)
- else if(foodtypes & H.dna.species.disliked_food)
- to_chat(H,"That didn't taste very good...")
- H.adjust_disgust(11 + 15 * fraction)
- SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "gross_food", /datum/mood_event/gross_food)
- else if(foodtypes & H.dna.species.liked_food)
- to_chat(H,"I love this taste!")
- H.adjust_disgust(-5 + -2.5 * fraction)
- SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "fav_food", /datum/mood_event/favorite_food)
- if((foodtypes & BREAKFAST) && world.time - SSticker.round_start_time < STOP_SERVING_BREAKFAST)
- SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "breakfast", /datum/mood_event/breakfast)
- last_check_time = world.time
-
-///Delete the item when it is fully eaten
-/datum/component/edible/proc/On_Consume(mob/living/eater)
-
- var/atom/owner = parent
-
- if(!eater)
- return
- if(!owner.reagents.total_volume)
- if(isturf(parent))
- var/turf/T = parent
- T.ScrapeAway(1, CHANGETURF_INHERIT_AIR)
- else
- qdel(parent)
-
-///Ability to feed food to puppers
-/datum/component/edible/proc/UseByAnimal(datum/source, mob/user)
-
- SIGNAL_HANDLER
-
-
- var/atom/owner = parent
-
- if(!isdog(user))
- return
- var/mob/living/L = user
- if(bitecount == 0 || prob(50))
- L.manual_emote("nibbles away at \the [parent]")
- bitecount++
- . = COMPONENT_ITEM_NO_ATTACK
- L.taste(owner.reagents) // why should carbons get all the fun?
- if(bitecount >= 5)
- var/sattisfaction_text = pick("burps from enjoyment", "yaps for more", "woofs twice", "looks at the area where \the [parent] was")
- if(sattisfaction_text)
- L.manual_emote(sattisfaction_text)
- qdel(parent)
diff --git a/code/datums/components/food/edible.dm b/code/datums/components/food/edible.dm
new file mode 100644
index 00000000000..cde77f96991
--- /dev/null
+++ b/code/datums/components/food/edible.dm
@@ -0,0 +1,494 @@
+/*!
+
+This component makes it possible to make things edible. What this means is that you can take a bite or force someone to take a bite (in the case of items).
+These items take a specific time to eat, and can do most of the things our original food items could.
+
+Behavior that's still missing from this component that original food items had that should either be put into seperate components or somewhere else:
+ Components:
+ Drying component (jerky etc)
+ Customizable component (custom pizzas etc)
+ Processable component (Slicing and cooking behavior essentialy, making it go from item A to B when conditions are met.)
+ Microwavability component
+ Frying component
+
+ Misc:
+ Something for cakes (You can store things inside)
+
+*/
+/datum/component/edible
+ dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
+ ///Amount of reagents taken per bite
+ var/bite_consumption = 2
+ ///Amount of bites taken so far
+ var/bitecount = 0
+ ///Flags for food
+ var/food_flags = NONE
+ ///Bitfield of the types of this food
+ var/foodtypes = NONE
+ ///Amount of seconds it takes to eat this food
+ var/eat_time = 30
+ ///Defines how much it lowers someones satiety (Need to eat, essentialy)
+ var/junkiness = 0
+ ///Message to send when eating
+ var/list/eatverbs
+ ///Callback to be ran before you eat something, so you can check if someone *can* eat it.
+ var/datum/callback/pre_eat
+ ///Callback to be ran before composting something, in case you don't want a piece of food to be compostable for some reason.
+ var/datum/callback/on_compost
+ ///Callback to be ran for when you take a bite of something
+ var/datum/callback/after_eat
+ ///Callback to be ran for when you finish eating something
+ var/datum/callback/on_consume
+ ///Last time we checked for food likes
+ var/last_check_time
+ ///The initial reagents of this food when it is made
+ var/list/initial_reagents
+ ///The initial volume of the foods reagents
+ var/volume
+ ///The flavortext for taste
+ var/list/tastes
+ ///The type of atom this creates when the object is microwaved.
+ var/microwaved_type
+
+ //TEMP VAR, filling is nonfunctional because newfood isnt customizable yet
+ var/filling_color
+
+/datum/component/edible/Initialize(list/initial_reagents,
+ food_flags = NONE,
+ foodtypes = NONE,
+ volume = 50,
+ eat_time = 10,
+ list/tastes,
+ list/eatverbs = list("bite","chew","nibble","gnaw","gobble","chomp"),
+ bite_consumption = 2,
+ microwaved_type,
+ junkiness,
+ filling_color = null, //Temp var
+ datum/callback/pre_eat,
+ datum/callback/on_compost,
+ datum/callback/after_eat,
+ datum/callback/on_consume
+)
+ if(!isatom(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(examine))
+ RegisterSignal(parent, COMSIG_ATOM_ATTACK_ANIMAL, PROC_REF(use_by_animal))
+ RegisterSignal(parent, COMSIG_ATOM_CHECKPARTS, PROC_REF(on_craft))
+ RegisterSignal(parent, COMSIG_ATOM_CREATEDBY_PROCESSING, PROC_REF(on_processed))
+ RegisterSignal(parent, COMSIG_ITEM_MICROWAVE_COOKED, PROC_REF(on_microwave_cooked))
+ RegisterSignal(parent, COMSIG_EDIBLE_ON_COMPOST, PROC_REF(compost))
+
+ if(isitem(parent))
+ RegisterSignal(parent, COMSIG_ITEM_ATTACK, PROC_REF(use_from_hand))
+ RegisterSignal(parent, COMSIG_ITEM_FRIED, PROC_REF(on_fried))
+ RegisterSignal(parent, COMSIG_ITEM_MICROWAVE_ACT, PROC_REF(on_microwaved))
+
+ var/obj/item/item = parent
+ if (!item.grind_results)
+ item.grind_results = list() //If this doesn't already exist, add it as an empty list. This is needed for the grinder to accept it.
+
+ src.bite_consumption = bite_consumption
+ src.food_flags = food_flags
+ src.foodtypes = foodtypes
+ src.eat_time = eat_time
+ src.eatverbs = string_list(eatverbs)
+ src.junkiness = junkiness
+ src.pre_eat = pre_eat
+ src.on_compost = on_compost
+ src.after_eat = after_eat
+ src.on_consume = on_consume
+ src.initial_reagents = string_assoc_list(initial_reagents)
+ src.tastes = string_assoc_list(tastes)
+ src.microwaved_type = microwaved_type
+
+ var/atom/owner = parent
+
+ owner.create_reagents(volume, INJECTABLE)
+
+ for(var/rid in initial_reagents)
+ var/amount = initial_reagents[rid]
+ if(length(tastes) && (rid == /datum/reagent/consumable/nutriment || rid == /datum/reagent/consumable/nutriment/vitamin))
+ owner.reagents.add_reagent(rid, amount, tastes.Copy())
+ else
+ owner.reagents.add_reagent(rid, amount)
+
+/datum/component/edible/InheritComponent(datum/component/C,
+ i_am_original,
+ list/initial_reagents,
+ food_flags = NONE,
+ foodtypes = NONE,
+ volume = 50,
+ eat_time = 30,
+ list/tastes,
+ list/eatverbs = list("bite","chew","nibble","gnaw","gobble","chomp"),
+ bite_consumption = 2,
+ filling_color = null, //Temp var
+ datum/callback/pre_eat,
+ datum/callback/on_compost,
+ datum/callback/after_eat,
+ datum/callback/on_consume
+ )
+
+ . = ..()
+ src.bite_consumption = bite_consumption
+ src.food_flags = food_flags
+ src.foodtypes = foodtypes
+ src.eat_time = eat_time
+ src.eatverbs = eatverbs
+ src.junkiness = junkiness
+ src.pre_eat = pre_eat
+ src.on_compost = on_compost
+ src.after_eat = after_eat
+ src.on_consume = on_consume
+
+/datum/component/edible/Destroy(force, silent)
+ QDEL_NULL(pre_eat)
+ QDEL_NULL(on_compost)
+ QDEL_NULL(after_eat)
+ QDEL_NULL(on_consume)
+ return ..()
+
+/datum/component/edible/proc/examine(datum/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+
+ if(!(food_flags & FOOD_IN_CONTAINER))
+ switch (bitecount)
+ if (0)
+ return
+ if(1)
+ examine_list += "[parent] was bitten by someone!"
+ if(2,3)
+ examine_list += "[parent] was bitten [bitecount] times!"
+ else
+ examine_list += "[parent] was bitten multiple times!"
+
+/datum/component/edible/proc/use_from_hand(obj/item/source, mob/living/M, mob/living/user)
+ SIGNAL_HANDLER
+
+ return TryToEat(M, user)
+
+/datum/component/edible/proc/on_fried(fry_object)
+ SIGNAL_HANDLER
+ var/atom/our_atom = parent
+ our_atom.reagents.trans_to(fry_object, our_atom.reagents.total_volume)
+ qdel(our_atom)
+ return COMSIG_FRYING_HANDLED
+
+///Called when food is created through processing (Usually this means it was sliced). We use this to pass the OG items reagents.
+/datum/component/edible/proc/on_processed(datum/source, atom/original_atom, list/chosen_processing_option)
+ SIGNAL_HANDLER
+
+ if(!original_atom.reagents)
+ return
+
+ var/atom/this_food = parent
+ var/reagents_for_slice = chosen_processing_option[TOOL_PROCESSING_AMOUNT]
+
+ this_food.create_reagents(volume) //Make sure we have a reagent container
+
+ original_atom.reagents.trans_to(this_food, reagents_for_slice)
+
+ if(original_atom.name != initial(original_atom.name))
+ this_food.name = "slice of [original_atom.name]"
+ if(original_atom.desc != initial(original_atom.desc))
+ this_food.desc = "[original_atom.desc]"
+
+///Called when food is crafted through a crafting recipe datum.
+/datum/component/edible/proc/on_craft(datum/source, list/parts_list, datum/crafting_recipe/food/recipe)
+ SIGNAL_HANDLER
+
+ var/atom/this_food = parent
+
+ this_food.reagents.clear_reagents()
+
+ for(var/obj/item/crafted_part in this_food.contents)
+ crafted_part.reagents?.trans_to(this_food.reagents, crafted_part.reagents.maximum_volume, CRAFTED_FOOD_INGREDIENT_REAGENT_MODIFIER)
+
+ var/list/objects_to_delete = list()
+
+ // Remove all non recipe objects from the contents
+ for(var/content_object in this_food.contents)
+ for(var/recipe_object in recipe.real_parts)
+ if(istype(content_object, recipe_object))
+ continue
+ objects_to_delete += content_object
+
+ QDEL_LIST(objects_to_delete)
+
+ for(var/r_id in initial_reagents)
+ var/amount = initial_reagents[r_id] * CRAFTED_FOOD_BASE_REAGENT_MODIFIER
+ if(r_id == /datum/reagent/consumable/nutriment || r_id == /datum/reagent/consumable/nutriment/vitamin)
+ this_food.reagents.add_reagent(r_id, amount, tastes)
+ else
+ this_food.reagents.add_reagent(r_id, amount)
+
+ SSblackbox.record_feedback("tally", "food_made", 1, type)
+
+/datum/component/edible/proc/on_microwaved(datum/source, obj/machinery/microwave/used_microwave)
+ SIGNAL_HANDLER
+
+ var/turf/parent_turf = get_turf(parent)
+
+ if(!microwaved_type)
+ new /obj/item/reagent_containers/food/snacks/badrecipe(parent_turf)
+ qdel(parent)
+ return
+
+ var/obj/item/result
+
+ result = new microwaved_type(parent_turf)
+
+ var/efficiency = istype(used_microwave) ? used_microwave.efficiency : 1
+
+ SEND_SIGNAL(result, COMSIG_ITEM_MICROWAVE_COOKED, parent, efficiency)
+
+ SSblackbox.record_feedback("tally", "food_made", 1, result.type)
+ qdel(parent)
+ return COMPONENT_SUCCESFUL_MICROWAVE
+
+///Corrects the reagents on the newly cooked food
+/datum/component/edible/proc/on_microwave_cooked(datum/source, obj/item/source_item, cooking_efficiency = 1)
+ SIGNAL_HANDLER
+
+ var/atom/this_food = parent
+
+ this_food.reagents.clear_reagents()
+
+ source_item.reagents?.trans_to(this_food, source_item.reagents.total_volume)
+
+ for(var/r_id in initial_reagents)
+ var/amount = initial_reagents[r_id] * cooking_efficiency * CRAFTED_FOOD_BASE_REAGENT_MODIFIER
+ if(r_id == /datum/reagent/consumable/nutriment || r_id == /datum/reagent/consumable/nutriment/vitamin)
+ this_food.reagents.add_reagent(r_id, amount, tastes)
+ else
+ this_food.reagents.add_reagent(r_id, amount)
+
+///Makes sure the thing hasn't been destroyed or fully eaten to prevent eating phantom edibles
+/datum/component/edible/proc/IsFoodGone(atom/owner, mob/living/feeder)
+ if(QDELETED(owner)|| !(IS_EDIBLE(owner)))
+ return TRUE
+ if(owner.reagents.total_volume)
+ return FALSE
+ return TRUE
+
+/// Normal time to forcefeed someone something
+#define EAT_TIME_FORCE_FEED (3 SECONDS)
+
+///All the checks for the act of eating itself and
+/datum/component/edible/proc/TryToEat(mob/living/eater, mob/living/feeder)
+
+ set waitfor = FALSE // We might end up sleeping here, so we don't want to hold up anything
+
+ var/atom/owner = parent
+
+ if(feeder.a_intent == INTENT_HARM)
+ return
+
+ . = COMPONENT_CANCEL_ATTACK_CHAIN //Point of no return I suppose
+
+ if(IsFoodGone(owner, feeder))
+ return
+
+ if(!CanConsume(eater, feeder))
+ return
+ var/fullness = eater.nutrition + 10 //The theoretical fullness of the person eating if they were to eat this
+
+ var/time_to_eat = (eater = feeder) ? eat_time : EAT_TIME_FORCE_FEED
+
+ if(eater == feeder)//If you're eating it yourself.
+ if(eat_time && !do_after(feeder, time_to_eat, eater, timed_action_flags = food_flags & FOOD_FINGER_FOOD ? IGNORE_USER_LOC_CHANGE | IGNORE_TARGET_LOC_CHANGE : NONE)) //Gotta pass the minimal eat time
+ return
+ if(IsFoodGone(owner, feeder))
+ return
+ var/eatverb = pick(eatverbs)
+
+ if(junkiness && eater.satiety < -150 && eater.nutrition > NUTRITION_LEVEL_STARVING + 50 && !HAS_TRAIT(eater, TRAIT_VORACIOUS))
+ to_chat(eater, "You don't feel like eating any more junk food at the moment!")
+ return
+ else if(fullness <= 50)
+ eater.visible_message("[eater] hungrily [eatverb]s \the [parent], gobbling it down!", "You hungrily [eatverb] \the [parent], gobbling it down!")
+ else if(fullness > 50 && fullness < 150)
+ eater.visible_message("[eater] hungrily [eatverb]s \the [parent].", "You hungrily [eatverb] \the [parent].")
+ else if(fullness > 150 && fullness < 500)
+ eater.visible_message("[eater] [eatverb]s \the [parent].", "You [eatverb] \the [parent].")
+ else if(fullness > 500 && fullness < 600)
+ eater.visible_message("[eater] unwillingly [eatverb]s a bit of \the [parent].", "You unwillingly [eatverb] a bit of \the [parent].")
+ else if(fullness > (600 * (1 + eater.overeatduration / 2000))) // The more you eat - the more you can eat
+ eater.visible_message("[eater] cannot force any more of \the [parent] to go down [eater.p_their()] throat!", "You cannot force any more of \the [parent] to go down your throat!")
+ return
+
+
+
+
+
+ else //If you're feeding it to someone else.
+ if(isbrain(eater))
+ to_chat(feeder, "[eater] doesn't seem to have a mouth!")
+ return
+ if(fullness <= (600 * (1 + eater.overeatduration / 1000)))
+ eater.visible_message(
+ "[feeder] attempts to feed [eater] [parent].", \
+ "[feeder] attempts to feed you [parent]."
+ )
+ if(eater.is_blind())
+ to_chat(eater, "You feel someone trying to feed you something!")
+ else
+ eater.visible_message(
+ "[feeder] cannot force any more of [parent] down [eater]'s throat!", \
+ "[feeder] cannot force any more of [parent] down your throat!"
+ )
+ if(eater.is_blind())
+ to_chat(eater, "You're too full to eat what's being fed to you!")
+ return
+ if(!do_after(feeder, delay = time_to_eat, target = eater)) //Wait 3 seconds before you can feed
+ return
+ if(IsFoodGone(owner, feeder))
+ return
+ log_combat(feeder, eater, "fed", owner.reagents.log_list())
+ eater.visible_message(
+ "[feeder] forces [eater] to eat [parent]!", \
+ "[feeder] forces you to eat [parent]!"
+ )
+ if(eater.is_blind())
+ to_chat(eater, "You're forced to eat something!")
+
+ TakeBite(eater, feeder)
+
+ //If we're not force-feeding, try take another bite
+ if(eater == feeder && eat_time)
+ INVOKE_ASYNC(src, PROC_REF(TryToEat), eater, feeder)
+
+#undef EAT_TIME_FORCE_FEED
+
+///This function lets the eater take a bite and transfers the reagents to the eater.
+/datum/component/edible/proc/TakeBite(mob/living/eater, mob/living/feeder)
+
+ var/atom/owner = parent
+
+ if(!owner?.reagents)
+ return FALSE
+ if(eater.satiety > -200)
+ eater.satiety -= junkiness
+ playsound(eater.loc,'sound/items/eatfood.ogg', rand(10,50), TRUE)
+ if(!owner.reagents.total_volume)
+ return
+ SEND_SIGNAL(parent, COMSIG_FOOD_EATEN, eater, feeder, bitecount, bite_consumption)
+ var/fraction = min(bite_consumption / owner.reagents.total_volume, 1)
+ owner.reagents.trans_to(eater, bite_consumption, transfered_by = feeder, method = INGEST)
+ bitecount++
+ check_liked(fraction, eater)
+ if(!owner.reagents.total_volume)
+ on_consume(eater, feeder)
+
+ //Invoke our after eat callback if it is valid
+ if(after_eat)
+ after_eat.Invoke(eater, feeder, bitecount)
+
+ return TRUE
+
+///Checks if we can compost something, and handles it
+/datum/component/edible/proc/compost(mob/living/user)
+ SIGNAL_HANDLER
+ if(on_compost && !on_compost.Invoke(user))
+ return COMPONENT_EDIBLE_BLOCK_COMPOST
+
+///Checks whether or not the eater can actually consume the food
+/datum/component/edible/proc/CanConsume(mob/living/eater, mob/living/feeder)
+ if(!iscarbon(eater))
+ return FALSE
+ if(pre_eat && !pre_eat.Invoke(eater, feeder))
+ return FALSE
+ var/mob/living/carbon/C = eater
+ var/covered = ""
+ if(C.is_mouth_covered(head_only = 1))
+ covered = "headgear"
+ else if(C.is_mouth_covered(mask_only = 1))
+ covered = "mask"
+ if(covered)
+ var/who = (isnull(feeder) || eater == feeder) ? "your" : "[eater.p_their()]"
+ to_chat(feeder, "You have to remove [who] [covered] first!")
+ return FALSE
+ return TRUE
+
+///Check foodtypes to see if we should send a moodlet
+/datum/component/edible/proc/check_liked(fraction, mob/eater)
+ if(last_check_time + 50 > world.time)
+ return FALSE
+ if(!ishuman(eater))
+ return FALSE
+ var/mob/living/carbon/human/human_eater = eater
+ if((foodtypes & BREAKFAST) && world.time - SSticker.round_start_time < STOP_SERVING_BREAKFAST)
+ SEND_SIGNAL(human_eater, COMSIG_ADD_MOOD_EVENT, "breakfast", /datum/mood_event/breakfast)
+ if(HAS_TRAIT(human_eater, TRAIT_AGEUSIA))
+ if(foodtypes & human_eater.dna.species.toxic_food)
+ to_chat(human_eater, "You don't feel so good...")
+ human_eater.adjust_disgust(25 + 30 * fraction)
+ else
+ if(foodtypes & human_eater.dna.species.toxic_food)
+ to_chat(human_eater,"What the hell was that thing?!")
+ human_eater.adjust_disgust(25 + 30 * fraction)
+ SEND_SIGNAL(human_eater, COMSIG_ADD_MOOD_EVENT, "toxic_food", /datum/mood_event/disgusting_food)
+ else if(foodtypes & human_eater.dna.species.disliked_food)
+ to_chat(human_eater,"That didn't taste very good...")
+ human_eater.adjust_disgust(11 + 15 * fraction)
+ SEND_SIGNAL(human_eater, COMSIG_ADD_MOOD_EVENT, "gross_food", /datum/mood_event/gross_food)
+ else if(foodtypes & human_eater.dna.species.liked_food)
+ to_chat(human_eater,"I love this taste!")
+ human_eater.adjust_disgust(-5 + -2.5 * fraction)
+ SEND_SIGNAL(human_eater, COMSIG_ADD_MOOD_EVENT, "fav_food", /datum/mood_event/favorite_food)
+ last_check_time = world.time
+
+ /* Should shiptest ever want to move taste to tongues as Beestation & later TGstation did, rather than on species
+ var/obj/item/organ/tongue/tongue = human_eater.getorganslot(ORGAN_SLOT_TONGUE)
+ if((foodtypes & BREAKFAST) && world.time - SSticker.round_start_time < STOP_SERVING_BREAKFAST)
+ SEND_SIGNAL(human_eater, COMSIG_ADD_MOOD_EVENT, "breakfast", /datum/mood_event/breakfast)
+ if(HAS_TRAIT(human_eater, TRAIT_AGEUSIA))
+ if(foodtypes & tongue.toxic_food)
+ to_chat(human_eater, "You don't feel so good...")
+ human_eater.adjust_disgust(25 + 30 * fraction)
+ else
+ if(foodtypes & tongue.toxic_food)
+ to_chat(human_eater,"What the hell was that thing?!")
+ human_eater.adjust_disgust(25 + 30 * fraction)
+ SEND_SIGNAL(human_eater, COMSIG_ADD_MOOD_EVENT, "toxic_food", /datum/mood_event/disgusting_food)
+ else if(foodtypes & tongue.disliked_food)
+ to_chat(human_eater,"That didn't taste very good...")
+ human_eater.adjust_disgust(11 + 15 * fraction)
+ SEND_SIGNAL(human_eater, COMSIG_ADD_MOOD_EVENT, "gross_food", /datum/mood_event/gross_food)
+ else if(foodtypes & tongue.liked_food)
+ to_chat(human_eater,"I love this taste!")
+ human_eater.adjust_disgust(-5 + -2.5 * fraction)
+ SEND_SIGNAL(human_eater, COMSIG_ADD_MOOD_EVENT, "fav_food", /datum/mood_event/favorite_food)
+ last_check_time = world.time
+ */
+
+///Delete the item when it is fully eaten
+/datum/component/edible/proc/on_consume(mob/living/eater, mob/living/feeder)
+ SEND_SIGNAL(parent, COMSIG_FOOD_CONSUMED, eater, feeder)
+
+ on_consume?.Invoke(eater, feeder)
+
+ if(isturf(parent))
+ var/turf/T = parent
+ T.ScrapeAway(1, CHANGETURF_INHERIT_AIR)
+ else
+ qdel(parent)
+
+///Ability to feed food to puppers
+/datum/component/edible/proc/use_by_animal(datum/source, mob/user)
+ SIGNAL_HANDLER
+ var/atom/owner = parent
+
+ if(!isdog(user))
+ return
+ var/mob/living/L = user
+ if(bitecount == 0 || prob(50))
+ L.manual_emote("nibbles away at \the [parent].")
+ bitecount++
+ . = COMPONENT_CANCEL_ATTACK_CHAIN
+ L.taste(owner.reagents) // why should carbons get all the fun?
+ if(bitecount >= 5)
+ var/satisfaction_text = pick("burps from enjoyment.", "yaps for more!", "woofs twice.", "looks at the area where \the [parent] was.")
+ L.manual_emote(satisfaction_text)
+ qdel(parent)
diff --git a/code/datums/components/food/food_storage.dm b/code/datums/components/food/food_storage.dm
new file mode 100644
index 00000000000..259ef4a8b6c
--- /dev/null
+++ b/code/datums/components/food/food_storage.dm
@@ -0,0 +1,204 @@
+/// --Food storage component--
+/// This component lets you slide one item into large foods, such as bread, cheese wheels, or cakes.
+/// Consuming food storages with an item inside can cause unique interactions, such as eating glass shards.
+
+/datum/component/food_storage
+ /// Reference to what we have in our food.
+ var/obj/item/stored_item
+ /// The amount of volume the food has on creation - Used for probabilities
+ var/initial_volume = 10
+ /// Minimum size items that can be inserted
+ var/minimum_weight_class = WEIGHT_CLASS_SMALL
+ /// What are the odds we bite into the stored item?
+ var/bad_chance_of_discovery = 0
+ /// What are the odds we see the stored item before we bite it?
+ var/good_chance_of_discovery = 100
+ /// The stored item was found out somehow.
+ var/discovered = FALSE
+
+/datum/component/food_storage/Initialize(_minimum_weight_class = WEIGHT_CLASS_SMALL, _bad_chance = 0, _good_chance = 100)
+
+ RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, PROC_REF(try_inserting_item))
+ RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, PROC_REF(try_removing_item))
+ RegisterSignal(parent, COMSIG_FOOD_EATEN, PROC_REF(consume_food_storage))
+
+ var/atom/food = parent
+ initial_volume = food.reagents.total_volume
+
+ minimum_weight_class = _minimum_weight_class
+ bad_chance_of_discovery = _bad_chance
+ good_chance_of_discovery = _good_chance
+
+/datum/component/food_storage/Destroy(force, silent)
+ if(stored_item)
+ stored_item.forceMove(stored_item.drop_location())
+ stored_item.dropped()
+ stored_item = null
+ . = ..()
+
+/** Begins the process of inserted an item.
+ *
+ * Clicking on the food storage with an item will begin a do_after, which if successful inserts the item.
+ *
+ * Arguments
+ * inserted_item - the item being placed into the food
+ * user - the person inserting the item
+ */
+/datum/component/food_storage/proc/try_inserting_item(datum/source, obj/item/inserted_item, mob/user, params)
+ SIGNAL_HANDLER
+
+ // No matryoshka-ing food storage
+ if(istype(inserted_item, /obj/item/storage) || IS_EDIBLE(inserted_item))
+ return
+
+ //Harm intent will bypass inserting for injecting food with syringes and such
+ if(user.a_intent == INTENT_HARM)
+ return
+
+ if(inserted_item.w_class > minimum_weight_class)
+ to_chat(user, "\The [inserted_item.name] won't fit in \the [parent].")
+ return
+
+ if(!QDELETED(stored_item))
+ to_chat(user, "There's something in \the [parent].")
+ return
+
+ if(HAS_TRAIT(inserted_item, TRAIT_NODROP))
+ to_chat(user, "\the [inserted_item] is stuck to your hand, you can't put into \the [parent]!")
+ return
+
+ user.visible_message("[user.name] begins inserting [inserted_item.name] into \the [parent].", \
+ "You start to insert the [inserted_item.name] into \the [parent].")
+
+ INVOKE_ASYNC(src, PROC_REF(insert_item), inserted_item, user)
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+
+/** Begins the process of attempting to remove the stored item.
+ *
+ * Clicking on food storage on grab intent will begin a do_after, which if successful removes the stored_item.
+ *
+ * Arguments
+ * user - the person removing the item.
+ */
+/datum/component/food_storage/proc/try_removing_item(datum/source, mob/user)
+ SIGNAL_HANDLER
+
+ var/atom/food = parent
+
+ if(user.a_intent != INTENT_GRAB)
+ return
+
+ if(QDELETED(stored_item))
+ return
+
+ if(!food.can_interact(user))
+ return
+
+ user.visible_message("[user.name] begins tearing at \the [parent].", \
+ "You start to rip into \the [parent].")
+
+ INVOKE_ASYNC(src, PROC_REF(begin_remove_item), user)
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+
+/** Inserts the item into the food, after a do_after.
+ *
+ * Arguments
+ * inserted_item - The item being inserted.
+ * user - the person inserting the item.
+ */
+/datum/component/food_storage/proc/insert_item(obj/item/inserted_item, mob/user)
+ if(do_after(user, 1.5 SECONDS, target = parent))
+ var/atom/food = parent
+ to_chat(user, "You slip [inserted_item.name] inside \the [parent].")
+ inserted_item.forceMove(food)
+ user.log_message("[key_name(user)] inserted [inserted_item] into [parent] at [AREACOORD(user)]", LOG_ATTACK)
+ food.add_fingerprint(user)
+ inserted_item.add_fingerprint(user)
+
+ stored_item = inserted_item
+
+/** Removes the item from the food, after a do_after.
+ *
+ * Arguments
+ * user - person removing the item.
+ */
+/datum/component/food_storage/proc/begin_remove_item(mob/user)
+ if(do_after(user, 10 SECONDS, target = parent))
+ remove_item(user)
+
+/**
+ * Removes the stored item, putting it in user's hands or on the ground, then updates the reference.
+ */
+/datum/component/food_storage/proc/remove_item(mob/user)
+ if(user.put_in_hands(stored_item))
+ user.visible_message("[user.name] slowly pulls [stored_item.name] out of \the [parent].", \
+ "You slowly pull [stored_item.name] out of \the [parent].")
+ else
+ stored_item.dropped()
+ stored_item.visible_message("[stored_item.name] falls out of \the [parent].")
+
+ update_stored_item()
+
+/** Checks for stored items when the food is eaten.
+ *
+ * If the food is eaten while an item is stored in it, calculates the odds that the item will be found.
+ * Then, if the item is found before being bitten, the item is removed.
+ * If the item is found by biting into it, calls on_accidental_consumption on the stored item.
+ * Afterwards, removes the item from the food if it was discovered.
+ *
+ * Arguments
+ * target - person doing the eating (can be the same as user)
+ * user - person causing the eating to happen
+ * bitecount - how many times the current food has been bitten
+ * bitesize - how large bties are for this food
+ */
+/datum/component/food_storage/proc/consume_food_storage(datum/source, mob/living/target, mob/living/user, bitecount, bitesize)
+ SIGNAL_HANDLER
+
+ if(QDELETED(stored_item)) //if the stored item was deleted/null...
+ if(!update_stored_item()) //check if there's a replacement item
+ return
+
+ /// Chance of biting the held item = amount of bites / (intitial reagents / reagents per bite) * 100
+ bad_chance_of_discovery = (bitecount / (initial_volume / bitesize))*100
+ /// Chance of finding the held item = bad chance - 50
+ good_chance_of_discovery = bad_chance_of_discovery - 50
+
+ if(prob(good_chance_of_discovery)) //finding the item, without biting it
+ discovered = TRUE
+ to_chat(target, "It feels like there's something in \the [parent]...!")
+
+ else if(prob(bad_chance_of_discovery)) //finding the item, BY biting it
+ user.log_message("[key_name(user)] just fed [key_name(target)] a/an [stored_item] which was hidden in [parent] at [AREACOORD(target)]", LOG_ATTACK)
+ discovered = stored_item.on_accidental_consumption(target, user, parent)
+ update_stored_item() //make sure if the item was changed, the reference changes as well
+
+ if(!QDELETED(stored_item) && discovered)
+ INVOKE_ASYNC(src, PROC_REF(remove_item), user)
+
+/** Updates the reference of the stored item.
+ *
+ * Checks the food's contents for if an alternate item was placed into the food.
+ * If there is an alternate item, updates the reference to the new item.
+ * If there isn't, updates the reference to null.
+ *
+ * Returns FALSE if the ref is nulled, or TRUE is another item replaced it.
+ */
+/datum/component/food_storage/proc/update_stored_item()
+ var/atom/food = parent
+ if(!food?.contents.len) //if there's no items in the food or food is deleted somehow
+ stored_item = null
+ return FALSE
+
+ for(var/obj/item/i in food.contents) //search the food's contents for a replacement item
+ if(IS_EDIBLE(i))
+ continue
+ if(QDELETED(i))
+ continue
+
+ stored_item = i //we found something to replace it
+ return TRUE
+
+ //if there's nothing else in the food, or we found nothing valid
+ stored_item = null
+ return FALSE
diff --git a/code/datums/components/jetpack.dm b/code/datums/components/jetpack.dm
new file mode 100644
index 00000000000..3451a75538a
--- /dev/null
+++ b/code/datums/components/jetpack.dm
@@ -0,0 +1,149 @@
+// Welcome to the jetpack component
+// Apply this to something when you want it to be "like a jetpack"
+// So propulsion through space on move, that sort of thing
+/datum/component/jetpack
+ dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
+ var/datum/callback/check_on_move
+ var/datum/callback/get_mover
+ /// If we should stabilize ourselves when not drifting
+ var/stabilize = FALSE
+ /// The signal we listen for as an activation
+ var/activation_signal
+ /// The signal we listen for as a de-activation
+ var/deactivation_signal
+ /// The return flag our parent expects for a failed activation
+ var/return_flag
+ var/datum/effect_system/trail_follow/trail
+ /// The typepath to instansiate our trail as, when we need it
+ var/effect_type
+
+/**
+ * Arguments:
+ * * stabilize - If we should drift when we finish moving, or sit stable in space]
+ * * activation_signal - Signal we activate on
+ * * deactivation_signal - Signal we deactivate on
+ * * return_flag - Flag to return if activation fails
+ * * get_mover - Callback we use to get the "moving" thing, for trail purposes, alongside signal registration
+ * * check_on_move - Callback we call each time we attempt a move, we expect it to retun true if the move is ok, false otherwise. It expects an arg, TRUE if fuel should be consumed, FALSE othewise
+ * * effect_type - Type of trail_follow to spawn
+ */
+/datum/component/jetpack/Initialize(stabilize, activation_signal, deactivation_signal, return_flag, datum/callback/get_mover, datum/callback/check_on_move, datum/effect_system/trail_follow/effect_type)
+ . = ..()
+ if(!isatom(parent))
+ return COMPONENT_INCOMPATIBLE
+ if(!activation_signal) // Can't activate? go away
+ return COMPONENT_INCOMPATIBLE
+
+ RegisterSignal(parent, activation_signal, PROC_REF(activate))
+ if(deactivation_signal)
+ RegisterSignal(parent, deactivation_signal, PROC_REF(deactivate))
+
+ src.check_on_move = check_on_move
+ src.get_mover = get_mover
+ src.stabilize = stabilize
+ src.return_flag = return_flag
+ src.activation_signal = activation_signal
+ src.deactivation_signal = deactivation_signal
+ src.effect_type = effect_type
+
+/datum/component/jetpack/InheritComponent(datum/component/component, original, stabilize, activation_signal, deactivation_signal, return_flag, datum/callback/get_mover, datum/callback/check_on_move, datum/effect_system/trail_follow/effect_type)
+ UnregisterSignal(parent, src.activation_signal)
+ if(src.deactivation_signal)
+ UnregisterSignal(parent, src.deactivation_signal)
+ RegisterSignal(parent, activation_signal, PROC_REF(activate))
+ if(deactivation_signal)
+ RegisterSignal(parent, deactivation_signal, PROC_REF(deactivate))
+
+ src.check_on_move = check_on_move
+ src.get_mover = get_mover
+ src.stabilize = stabilize
+ src.activation_signal = activation_signal
+ src.deactivation_signal = deactivation_signal
+ src.effect_type = effect_type
+
+ if(trail && effect_type != trail.type)
+ QDEL_NULL(trail)
+ setup_trail()
+
+/datum/component/jetpack/Destroy()
+ QDEL_NULL(trail)
+ QDEL_NULL(check_on_move)
+ return ..()
+
+/datum/component/jetpack/proc/setup_trail()
+ var/mob/moving = get_mover.Invoke()
+ if(!moving || trail)
+ return
+ trail = new effect_type
+ trail.auto_process = FALSE
+ trail.set_up(moving)
+
+/datum/component/jetpack/proc/activate(datum/source)
+ SIGNAL_HANDLER
+ var/mob/moving = get_mover.Invoke()
+ if(!thrust(moving))
+ return return_flag
+ trail.start()
+ RegisterSignal(moving, COMSIG_MOVABLE_MOVED, PROC_REF(move_react))
+ RegisterSignal(moving, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(pre_move_react))
+ RegisterSignal(moving, COMSIG_MOVABLE_SPACEMOVE, PROC_REF(spacemove_react))
+ RegisterSignal(moving, COMSIG_MOVABLE_DRIFT_VISUAL_ATTEMPT, PROC_REF(block_starting_visuals))
+ RegisterSignal(moving, COMSIG_MOVABLE_DRIFT_BLOCK_INPUT, PROC_REF(ignore_ending_block))
+
+/datum/component/jetpack/proc/deactivate(datum/source)
+ SIGNAL_HANDLER
+ QDEL_NULL(trail)
+ var/mob/moving = get_mover.Invoke()
+ if(moving)
+ UnregisterSignal(moving, COMSIG_MOVABLE_MOVED)
+ UnregisterSignal(moving, COMSIG_MOVABLE_PRE_MOVE)
+ UnregisterSignal(moving, COMSIG_MOVABLE_SPACEMOVE)
+ UnregisterSignal(moving, COMSIG_MOVABLE_DRIFT_VISUAL_ATTEMPT)
+ UnregisterSignal(moving, COMSIG_MOVABLE_DRIFT_BLOCK_INPUT)
+
+/datum/component/jetpack/proc/move_react(mob/user)
+ SIGNAL_HANDLER
+ if(!user || !user.client)//Don't allow jet self using
+ return
+ if(!isturf(user.loc))//You can't use jet in nowhere or from mecha/closet
+ return
+ if(!(user.movement_type & FLOATING) || user.buckled)//You don't want use jet in gravity or while buckled.
+ return
+ if(user.pulledby)//You don't must use jet if someone pull you
+ 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.
+ thrust()
+
+/datum/component/jetpack/proc/pre_move_react(mob/user)
+ SIGNAL_HANDLER
+ trail.oldposition = get_turf(user)
+
+/datum/component/jetpack/proc/spacemove_react(mob/user, movement_dir, continuous_move)
+ SIGNAL_HANDLER
+ if(!continuous_move && movement_dir)
+ return COMSIG_MOVABLE_STOP_SPACEMOVE
+ // Check if we have the fuel to stop this. Do NOT cosume any fuel, just check
+ // This is done because things other then us can use our fuel
+ if(stabilize && check_on_move.Invoke(FALSE))
+ return COMSIG_MOVABLE_STOP_SPACEMOVE
+
+/// Returns true if the thrust went well, false otherwise
+/datum/component/jetpack/proc/thrust()
+ if(!check_on_move.Invoke(TRUE))
+ return FALSE
+ if(!trail)
+ setup_trail()
+ trail.generate_effect()
+ return TRUE
+
+/// Basically, tell the drift component not to do its starting visuals, because they look dumb for us
+/datum/component/jetpack/proc/block_starting_visuals(datum/source)
+ SIGNAL_HANDLER
+ return DRIFT_VISUAL_FAILED
+
+/// If we're on, don't let the drift component block movements at the end since we can speed
+/datum/component/jetpack/proc/ignore_ending_block(datum/source)
+ SIGNAL_HANDLER
+ return DRIFT_ALLOW_INPUT
diff --git a/code/datums/components/shielded.dm b/code/datums/components/shielded.dm
new file mode 100644
index 00000000000..81cb0c2b4d4
--- /dev/null
+++ b/code/datums/components/shielded.dm
@@ -0,0 +1,186 @@
+/**
+ * The shielded component causes the parent item to nullify a certain number of attacks against the wearer, see: shielded vests.
+ */
+
+/datum/component/shielded
+ /// The person currently wearing us
+ var/mob/living/wearer
+ /// How many charges we can have max, and how many we start with
+ var/max_charges
+ /// How many charges we currently have
+ var/current_charges
+ /// How long we have to avoid being hit to replenish charges. If set to 0, we never recharge lost charges
+ var/recharge_start_delay = 20 SECONDS
+ /// Once we go unhit long enough to recharge, we replenish charges this often. The floor is effectively 1 second, AKA how often SSdcs processes
+ var/charge_increment_delay = 1 SECONDS
+ /// How many charges we recover on each charge increment
+ var/charge_recovery = 1
+ /// What .dmi we're pulling the shield icon from
+ var/shield_icon_file = 'icons/effects/effects.dmi'
+ /// What icon is used when someone has a functional shield up
+ var/shield_icon = "shield-old"
+ /// Do we still shield if we're being held in-hand? If FALSE, it needs to be equipped to a slot to work
+ var/shield_inhand = FALSE
+ /// Should the shield lose charges equal to the damage dealt by a hit?
+ var/lose_multiple_charges = FALSE
+ /// The cooldown tracking when we were last hit
+ COOLDOWN_DECLARE(recently_hit_cd)
+ /// The cooldown tracking when we last replenished a charge
+ COOLDOWN_DECLARE(charge_add_cd)
+ /// A callback for the sparks/message that play when a charge is used, see [/datum/component/shielded/proc/default_run_hit_callback]
+ var/datum/callback/on_hit_effects
+
+/datum/component/shielded/Initialize(max_charges = 3, recharge_start_delay = 20 SECONDS, charge_increment_delay = 1 SECONDS, charge_recovery = 1, lose_multiple_charges = FALSE, starting_charges = null, shield_icon_file = 'icons/effects/effects.dmi', shield_icon = "shield-old", shield_inhand = FALSE, run_hit_callback)
+ if(!isitem(parent) || max_charges <= 0)
+ return COMPONENT_INCOMPATIBLE
+
+ src.max_charges = max_charges
+ src.recharge_start_delay = recharge_start_delay
+ src.charge_increment_delay = charge_increment_delay
+ src.charge_recovery = charge_recovery
+ src.lose_multiple_charges = lose_multiple_charges
+ src.shield_icon_file = shield_icon_file
+ src.shield_icon = shield_icon
+ src.shield_inhand = shield_inhand
+ src.on_hit_effects = run_hit_callback || CALLBACK(src, PROC_REF(default_run_hit_callback))
+ if(isnull(starting_charges))
+ current_charges = max_charges
+ else
+ current_charges = starting_charges
+ if(recharge_start_delay)
+ START_PROCESSING(SSdcs, src)
+
+/datum/component/shielded/Destroy(force, silent)
+ if(wearer)
+ shield_icon = "broken"
+ UnregisterSignal(wearer, COMSIG_ATOM_UPDATE_OVERLAYS)
+ wearer.update_appearance(UPDATE_ICON)
+ wearer = null
+ QDEL_NULL(on_hit_effects)
+ return ..()
+
+/datum/component/shielded/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equipped))
+ RegisterSignal(parent, COMSIG_ITEM_DROPPED, PROC_REF(lost_wearer))
+ RegisterSignal(parent, COMSIG_ITEM_HIT_REACT, PROC_REF(on_hit_react))
+ RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, PROC_REF(check_recharge_rune))
+ var/atom/shield = parent
+ if(ismob(shield.loc))
+ var/mob/holder = shield.loc
+ if(holder.is_holding(parent) && !shield_inhand)
+ return
+ set_wearer(holder)
+
+/datum/component/shielded/UnregisterFromParent()
+ UnregisterSignal(parent, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED, COMSIG_ITEM_HIT_REACT, COMSIG_PARENT_ATTACKBY))
+ var/atom/shield = parent
+ if(shield.loc == wearer)
+ lost_wearer(src, wearer)
+
+// Handle recharging, if we want to
+/datum/component/shielded/process(delta_time)
+ if(current_charges >= max_charges)
+ STOP_PROCESSING(SSdcs, src)
+ return
+
+ if(!COOLDOWN_FINISHED(src, recently_hit_cd))
+ return
+ if(!COOLDOWN_FINISHED(src, charge_add_cd))
+ return
+
+ var/obj/item/item_parent = parent
+ COOLDOWN_START(src, charge_add_cd, charge_increment_delay)
+ adjust_charge(charge_recovery) // set the number of charges to current + recovery per increment, clamped from zero to max_charges
+ playsound(item_parent, 'sound/magic/charge.ogg', 50, TRUE)
+ if(current_charges == max_charges)
+ playsound(item_parent, 'sound/machines/ding.ogg', 50, TRUE)
+
+/datum/component/shielded/proc/adjust_charge(change)
+ current_charges = clamp(current_charges + change, 0, max_charges)
+ if(wearer)
+ wearer.update_appearance(UPDATE_ICON)
+
+/// Check if we've been equipped to a valid slot to shield
+/datum/component/shielded/proc/on_equipped(datum/source, mob/user, slot)
+ SIGNAL_HANDLER
+
+ if(slot == ITEM_SLOT_HANDS && !shield_inhand)
+ lost_wearer(source, user)
+ return
+ set_wearer(source, user)
+
+/// Either we've been dropped or our wearer has been QDEL'd. Either way, they're no longer our problem
+/datum/component/shielded/proc/lost_wearer(datum/source, mob/user)
+ SIGNAL_HANDLER
+
+ if(wearer)
+ UnregisterSignal(wearer, list(COMSIG_ATOM_UPDATE_OVERLAYS, COMSIG_PARENT_QDELETING))
+ wearer.update_appearance(UPDATE_ICON)
+ wearer = null
+
+/datum/component/shielded/proc/set_wearer(mob/user)
+ wearer = user
+ RegisterSignal(wearer, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_update_overlays))
+ RegisterSignal(wearer, COMSIG_PARENT_QDELETING, PROC_REF(lost_wearer))
+ if(current_charges)
+ wearer.update_appearance(UPDATE_ICON)
+
+/// Used to draw the shield overlay on the wearer
+/datum/component/shielded/proc/on_update_overlays(atom/parent_atom, list/overlays)
+ SIGNAL_HANDLER
+
+ overlays += mutable_appearance(shield_icon_file, (current_charges > 0 ? shield_icon : "broken"), ABOVE_MOB_LAYER)
+
+/**
+ * This proc fires when we're hit, and is responsible for checking if we're charged, then deducting one + returning that we're blocking if so.
+ * It then runs the callback in [/datum/component/shielded/var/on_hit_effects] which handles the messages/sparks (so the visuals)
+ */
+/datum/component/shielded/proc/on_hit_react(datum/source, mob/living/carbon/human/owner, atom/movable/hitby, attack_text, final_block_chance, damage, attack_type)
+ SIGNAL_HANDLER
+
+ COOLDOWN_START(src, recently_hit_cd, recharge_start_delay)
+
+ if(current_charges <= 0)
+ return
+ . = COMPONENT_HIT_REACTION_BLOCK
+
+ var/charge_loss = 1 // how many charges do we lose
+
+ if(lose_multiple_charges) // if the shield has health like damage we'll lose charges equal to the damage of the hit
+ charge_loss = damage
+
+ adjust_charge(-charge_loss)
+
+ INVOKE_ASYNC(src, PROC_REF(actually_run_hit_callback), owner, attack_text, current_charges)
+
+ if(!recharge_start_delay) // if recharge_start_delay is 0, we don't recharge
+ if(!current_charges) // obviously if someone ever adds a manual way to replenish charges, change this
+ qdel(src)
+ return
+
+ START_PROCESSING(SSdcs, src) // if we DO recharge, start processing so we can do that
+
+/// The wrapper to invoke the on_hit callback, so we don't have to worry about blocking in the signal handler
+/datum/component/shielded/proc/actually_run_hit_callback(mob/living/owner, attack_text, current_charges)
+ on_hit_effects.Invoke(owner, attack_text, current_charges)
+
+/// Default on_hit proc, since cult robes are stupid and have different descriptions/sparks
+/datum/component/shielded/proc/default_run_hit_callback(mob/living/owner, attack_text, current_charges)
+ do_sparks(2, TRUE, owner)
+ owner.visible_message(span_danger("Щит [owner] отражает [attack_text]!"))
+ if(current_charges <= 0)
+ owner.visible_message(span_warning("Щит [owner] перегружается!"))
+
+/datum/component/shielded/proc/check_recharge_rune(datum/source, obj/item/wizard_armour_charge/recharge_rune, mob/living/user)
+ /*SIGNAL_HANDLER
+
+ if(!istype(recharge_rune))
+ return
+ . = COMPONENT_NO_AFTERATTACK
+ if(!istype(parent, /obj/item/clothing/suit/space/hardsuit/shielded/wizard))
+ to_chat(user, span_warning("Руна может быть использована только на броне боевого мага!"))
+ return
+
+ current_charges += recharge_rune.restored_charges
+ to_chat(user, span_notice("Заряжаю [parent]. Теперь она сможет поглотить [current_charges] ударов."))
+ qdel(recharge_rune)*/
diff --git a/code/datums/components/storage/storage.dm b/code/datums/components/storage/storage.dm
index 89831dafab7..765e14e5db6 100644
--- a/code/datums/components/storage/storage.dm
+++ b/code/datums/components/storage/storage.dm
@@ -424,7 +424,7 @@
/datum/component/storage/proc/dump_content_at(atom/dest_object, mob/M)
var/atom/A = parent
var/atom/dump_destination = dest_object.get_dumping_location()
- if(A.Adjacent(M) && dump_destination && M.Adjacent(dump_destination))
+ if(M.CanReach(A) && dump_destination && M.CanReach(dump_destination))
if(locked)
to_chat(M, "[parent] seems to be [locked_flavor]!")
return FALSE
@@ -433,6 +433,12 @@
return TRUE
return FALSE
+/datum/component/storage/proc/get_dumping_location(atom/dest_object)
+ var/datum/component/storage/storage = dest_object.GetComponent(/datum/component/storage)
+ if(storage)
+ return storage.real_location()
+ return dest_object.get_dumping_location()
+
//This proc is called when you want to place an item into the storage item.
/datum/component/storage/proc/attackby(datum/source, obj/item/I, mob/M, params)
SIGNAL_HANDLER
diff --git a/code/datums/components/tackle.dm b/code/datums/components/tackle.dm
index 68c74921b8b..0d3d520c2b5 100644
--- a/code/datums/components/tackle.dm
+++ b/code/datums/components/tackle.dm
@@ -270,11 +270,10 @@
if(ishuman(target))
var/mob/living/carbon/human/T = target
- var/suit_slot = T.get_item_by_slot(ITEM_SLOT_OCLOTHING)
if(isnull(T.wear_suit) && isnull(T.w_uniform)) // who honestly puts all of their effort into tackling a naked guy?
defense_mod += 2
- if(suit_slot && (istype(suit_slot,/obj/item/clothing/suit/space/hardsuit)))
+ if(T.mob_negates_gravity())
defense_mod += 1
if(T.is_shove_knockdown_blocked()) // riot armor and such
defense_mod += 5
diff --git a/code/datums/elements/empprotection.dm b/code/datums/elements/empprotection.dm
new file mode 100644
index 00000000000..8d5d798c3cb
--- /dev/null
+++ b/code/datums/elements/empprotection.dm
@@ -0,0 +1,20 @@
+/datum/element/empprotection
+ element_flags = ELEMENT_DETACH | ELEMENT_BESPOKE
+ id_arg_index = 2
+ var/flags = NONE
+
+/datum/element/empprotection/Attach(datum/target, _flags)
+ . = ..()
+ if(. == ELEMENT_INCOMPATIBLE || !isatom(target))
+ return ELEMENT_INCOMPATIBLE
+ flags = _flags
+ RegisterSignal(target, COMSIG_ATOM_EMP_ACT, PROC_REF(getEmpFlags))
+
+/datum/element/empprotection/Detach(atom/target)
+ UnregisterSignal(target, COMSIG_ATOM_EMP_ACT)
+ return ..()
+
+/datum/element/empprotection/proc/getEmpFlags(datum/source, severity)
+ SIGNAL_HANDLER
+
+ return flags
diff --git a/code/datums/elements/dunkable.dm b/code/datums/elements/food/dunkable.dm
similarity index 99%
rename from code/datums/elements/dunkable.dm
rename to code/datums/elements/food/dunkable.dm
index 1eaee1d8cbb..80661d5c4ac 100644
--- a/code/datums/elements/dunkable.dm
+++ b/code/datums/elements/food/dunkable.dm
@@ -17,8 +17,6 @@
UnregisterSignal(target, COMSIG_ITEM_AFTERATTACK)
/datum/element/dunkable/proc/get_dunked(datum/source, atom/target, mob/user, proximity_flag)
- SIGNAL_HANDLER
-
if(!proximity_flag) // if the user is not adjacent to the container
return
var/obj/item/reagent_containers/container = target // the container we're trying to dunk into
diff --git a/code/datums/elements/food/edible.dm b/code/datums/elements/food/edible.dm
new file mode 100644
index 00000000000..a06a5ec28b7
--- /dev/null
+++ b/code/datums/elements/food/edible.dm
@@ -0,0 +1,471 @@
+/*!
+
+This component makes it possible to make things edible. What this means is that you can take a bite or force someone to take a bite (in the case of items).
+These items take a specific time to eat, and can do most of the things our original food items could.
+
+Behavior that's still missing from this component that original food items had that should either be put into seperate components or somewhere else:
+ Components:
+ Drying component (jerky etc)
+ Customizable component (custom pizzas etc)
+ Processable component (Slicing and cooking behavior essentialy, making it go from item A to B when conditions are met.)
+ Microwavability component
+ Frying component
+
+ Misc:
+ Something for cakes (You can store things inside)
+
+*/
+/datum/component/edible
+ dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
+ ///Amount of reagents taken per bite
+ var/bite_consumption = 2
+ ///Amount of bites taken so far
+ var/bitecount = 0
+ ///Flags for food
+ var/food_flags = NONE
+ ///Bitfield of the types of this food
+ var/foodtypes = NONE
+ ///Amount of seconds it takes to eat this food
+ var/eat_time = 30
+ ///Defines how much it lowers someones satiety (Need to eat, essentialy)
+ var/junkiness = 0
+ ///Message to send when eating
+ var/list/eatverbs
+ ///Callback to be ran before you eat something, so you can check if someone *can* eat it.
+ var/datum/callback/pre_eat
+ ///Callback to be ran before composting something, in case you don't want a piece of food to be compostable for some reason.
+ var/datum/callback/on_compost
+ ///Callback to be ran for when you take a bite of something
+ var/datum/callback/after_eat
+ ///Callback to be ran for when you finish eating something
+ var/datum/callback/on_consume
+ ///Last time we checked for food likes
+ var/last_check_time
+ ///The initial reagents of this food when it is made
+ var/list/initial_reagents
+ ///The initial volume of the foods reagents
+ var/volume
+ ///The flavortext for taste
+ var/list/tastes
+ ///The type of atom this creates when the object is microwaved.
+ var/microwaved_type
+
+ //TEMP VAR. To be phased out
+ var/filling_color = null
+
+
+/datum/component/edible/Initialize(list/initial_reagents,
+ food_flags = NONE,
+ foodtypes = NONE,
+ volume = 50,
+ eat_time = 10,
+ list/tastes,
+ list/eatverbs = list("bite","chew","nibble","gnaw","gobble","chomp"),
+ bite_consumption = 2,
+ microwaved_type,
+ junkiness,
+ datum/callback/pre_eat,
+ datum/callback/on_compost,
+ datum/callback/after_eat,
+ datum/callback/on_consume
+)
+ if(!isatom(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(examine))
+ RegisterSignal(parent, COMSIG_ATOM_ATTACK_ANIMAL, PROC_REF(use_by_animal))
+ RegisterSignal(parent, COMSIG_ATOM_CHECKPARTS, PROC_REF(on_craft))
+ RegisterSignal(parent, COMSIG_ATOM_CREATEDBY_PROCESSING, PROC_REF(on_processed))
+ RegisterSignal(parent, COMSIG_ITEM_MICROWAVE_COOKED, PROC_REF(on_microwave_cooked))
+ RegisterSignal(parent, COMSIG_EDIBLE_ON_COMPOST, PROC_REF(compost))
+
+ if(isitem(parent))
+ RegisterSignal(parent, COMSIG_ITEM_ATTACK, PROC_REF(use_from_hand))
+ RegisterSignal(parent, COMSIG_ITEM_FRIED, PROC_REF(on_fried))
+ RegisterSignal(parent, COMSIG_ITEM_MICROWAVE_ACT, PROC_REF(on_microwaved))
+
+ var/obj/item/item = parent
+ if (!item.grind_results)
+ item.grind_results = list() //If this doesn't already exist, add it as an empty list. This is needed for the grinder to accept it.
+
+ src.bite_consumption = bite_consumption
+ src.food_flags = food_flags
+ src.foodtypes = foodtypes
+ src.initial_reagents = initial_reagents
+ src.tastes = tastes
+ src.eat_time = eat_time
+ src.eatverbs = string_list(eatverbs)
+ src.junkiness = junkiness
+ src.pre_eat = pre_eat
+ src.on_compost = on_compost
+ src.after_eat = after_eat
+ src.on_consume = on_consume
+ src.initial_reagents = string_assoc_list(initial_reagents)
+ src.tastes = string_assoc_list(tastes)
+ src.microwaved_type = microwaved_type
+
+ var/atom/owner = parent
+
+ owner.create_reagents(volume, INJECTABLE)
+
+ for(var/rid in initial_reagents)
+ var/amount = initial_reagents[rid]
+ if(length(tastes) && (rid == /datum/reagent/consumable/nutriment || rid == /datum/reagent/consumable/nutriment/vitamin))
+ owner.reagents.add_reagent(rid, amount, tastes.Copy())
+ else
+ owner.reagents.add_reagent(rid, amount)
+
+/datum/component/edible/InheritComponent(datum/component/C,
+ i_am_original,
+ list/initial_reagents,
+ food_flags = NONE,
+ foodtypes = NONE,
+ volume = 50,
+ eat_time = 30,
+ list/tastes,
+ list/eatverbs = list("bite","chew","nibble","gnaw","gobble","chomp"),
+ bite_consumption = 2,
+ datum/callback/pre_eat,
+ datum/callback/on_compost,
+ datum/callback/after_eat,
+ datum/callback/on_consume
+ )
+
+ . = ..()
+ src.bite_consumption = bite_consumption
+ src.food_flags = food_flags
+ src.foodtypes = foodtypes
+ src.eat_time = eat_time
+ src.eatverbs = eatverbs
+ src.junkiness = junkiness
+ src.pre_eat = pre_eat
+ src.on_compost = on_compost
+ src.after_eat = after_eat
+ src.on_consume = on_consume
+
+/datum/component/edible/Destroy(force, silent)
+ QDEL_NULL(pre_eat)
+ QDEL_NULL(on_compost)
+ QDEL_NULL(after_eat)
+ QDEL_NULL(on_consume)
+ return ..()
+
+/datum/component/edible/proc/examine(datum/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+
+ if(!(food_flags & FOOD_IN_CONTAINER))
+ switch (bitecount)
+ if (0)
+ return
+ if(1)
+ examine_list += "[parent] was bitten by someone!"
+ if(2,3)
+ examine_list += "[parent] was bitten [bitecount] times!"
+ else
+ examine_list += "[parent] was bitten multiple times!"
+
+/datum/component/edible/proc/use_from_hand(obj/item/source, mob/living/M, mob/living/user)
+ SIGNAL_HANDLER
+
+ return TryToEat(M, user)
+
+/datum/component/edible/proc/on_fried(fry_object)
+ SIGNAL_HANDLER
+ var/atom/our_atom = parent
+ our_atom.reagents.trans_to(fry_object, our_atom.reagents.total_volume)
+ qdel(our_atom)
+ return COMSIG_FRYING_HANDLED
+
+///Called when food is created through processing (Usually this means it was sliced). We use this to pass the OG items reagents.
+/datum/component/edible/proc/on_processed(datum/source, atom/original_atom, list/chosen_processing_option)
+ SIGNAL_HANDLER
+
+ if(!original_atom.reagents)
+ return
+
+ var/atom/this_food = parent
+ var/reagents_for_slice = chosen_processing_option[TOOL_PROCESSING_AMOUNT]
+
+ this_food.create_reagents(volume) //Make sure we have a reagent container
+
+ original_atom.reagents.trans_to(this_food, reagents_for_slice)
+
+ if(original_atom.name != initial(original_atom.name))
+ this_food.name = "slice of [original_atom.name]"
+ if(original_atom.desc != initial(original_atom.desc))
+ this_food.desc = "[original_atom.desc]"
+
+///Called when food is crafted through a crafting recipe datum.
+/datum/component/edible/proc/on_craft(datum/source, list/parts_list, datum/crafting_recipe/food/recipe)
+ SIGNAL_HANDLER
+
+ var/atom/this_food = parent
+
+ this_food.reagents.clear_reagents()
+
+ for(var/obj/item/crafted_part in this_food.contents)
+ crafted_part.reagents?.trans_to(this_food.reagents, crafted_part.reagents.maximum_volume, CRAFTED_FOOD_INGREDIENT_REAGENT_MODIFIER)
+
+ var/list/objects_to_delete = list()
+
+ // Remove all non recipe objects from the contents
+ for(var/content_object in this_food.contents)
+ for(var/recipe_object in recipe.real_parts)
+ if(istype(content_object, recipe_object))
+ continue
+ objects_to_delete += content_object
+
+ QDEL_LIST(objects_to_delete)
+
+ for(var/r_id in initial_reagents)
+ var/amount = initial_reagents[r_id] * CRAFTED_FOOD_BASE_REAGENT_MODIFIER
+ if(r_id == /datum/reagent/consumable/nutriment || r_id == /datum/reagent/consumable/nutriment/vitamin)
+ this_food.reagents.add_reagent(r_id, amount, tastes)
+ else
+ this_food.reagents.add_reagent(r_id, amount)
+
+ SSblackbox.record_feedback("tally", "food_made", 1, type)
+
+/datum/component/edible/proc/on_microwaved(datum/source, obj/machinery/microwave/used_microwave)
+ SIGNAL_HANDLER
+
+ var/turf/parent_turf = get_turf(parent)
+
+ if(!microwaved_type)
+ new /obj/item/reagent_containers/food/snacks/badrecipe(parent_turf)
+ qdel(src)
+ return
+
+
+ var/obj/item/result
+
+ result = new microwaved_type(parent_turf)
+
+ var/efficiency = istype(used_microwave) ? used_microwave.efficiency : 1
+
+ SEND_SIGNAL(result, COMSIG_ITEM_MICROWAVE_COOKED, parent, efficiency)
+
+ SSblackbox.record_feedback("tally", "food_made", 1, result.type)
+
+///Corrects the reagents on the newly cooked food
+/datum/component/edible/proc/on_microwave_cooked(datum/source, obj/item/source_item, cooking_efficiency = 1)
+ SIGNAL_HANDLER
+
+ var/atom/this_food = parent
+
+ this_food.reagents.clear_reagents()
+
+ source_item.reagents?.trans_to(this_food, source_item.reagents.total_volume)
+
+ for(var/r_id in initial_reagents)
+ var/amount = initial_reagents[r_id] * cooking_efficiency * CRAFTED_FOOD_BASE_REAGENT_MODIFIER
+ if(r_id == /datum/reagent/consumable/nutriment || r_id == /datum/reagent/consumable/nutriment/vitamin)
+ this_food.reagents.add_reagent(r_id, amount, tastes)
+ else
+ this_food.reagents.add_reagent(r_id, amount)
+
+///Makes sure the thing hasn't been destroyed or fully eaten to prevent eating phantom edibles
+/datum/component/edible/proc/IsFoodGone(atom/owner, mob/living/feeder)
+ if(QDELETED(owner)|| !(IS_EDIBLE(owner)))
+ return TRUE
+ if(owner.reagents.total_volume)
+ return FALSE
+ return TRUE
+
+/// Normal time to forcefeed someone something
+#define EAT_TIME_FORCE_FEED (3 SECONDS)
+
+///All the checks for the act of eating itself and
+/datum/component/edible/proc/TryToEat(mob/living/eater, mob/living/feeder)
+
+ set waitfor = FALSE // We might end up sleeping here, so we don't want to hold up anything
+
+ var/atom/owner = parent
+
+ if(feeder.a_intent == INTENT_HARM)
+ return
+
+ . = COMPONENT_CANCEL_ATTACK_CHAIN //Point of no return I suppose
+
+ if(IsFoodGone(owner, feeder))
+ return
+
+ if(!CanConsume(eater, feeder))
+ return
+ var/fullness = eater.nutrition + 10 //The theoretical fullness of the person eating if they were to eat this
+
+ var/time_to_eat = (eater = feeder) ? eat_time : EAT_TIME_FORCE_FEED
+
+ if(eater == feeder)//If you're eating it yourself.
+ if(eat_time && !do_after(feeder, time_to_eat, eater, timed_action_flags = food_flags & FOOD_FINGER_FOOD ? IGNORE_USER_LOC_CHANGE | IGNORE_TARGET_LOC_CHANGE : NONE)) //Gotta pass the minimal eat time
+ return
+ if(IsFoodGone(owner, feeder))
+ return
+ var/eatverb = pick(eatverbs)
+
+ if(junkiness && eater.satiety < -150 && eater.nutrition > NUTRITION_LEVEL_STARVING + 50 && !HAS_TRAIT(eater, TRAIT_VORACIOUS))
+ to_chat(eater, "You don't feel like eating any more junk food at the moment!")
+ return
+ else if(fullness <= 50)
+ eater.visible_message("[eater] hungrily [eatverb]s \the [parent], gobbling it down!", "You hungrily [eatverb] \the [parent], gobbling it down!")
+ else if(fullness > 50 && fullness < 150)
+ eater.visible_message("[eater] hungrily [eatverb]s \the [parent].", "You hungrily [eatverb] \the [parent].")
+ else if(fullness > 150 && fullness < 500)
+ eater.visible_message("[eater] [eatverb]s \the [parent].", "You [eatverb] \the [parent].")
+ else if(fullness > 500 && fullness < 600)
+ eater.visible_message("[eater] unwillingly [eatverb]s a bit of \the [parent].", "You unwillingly [eatverb] a bit of \the [parent].")
+ else if(fullness > (600 * (1 + eater.overeatduration / 2000))) // The more you eat - the more you can eat
+ eater.visible_message("[eater] cannot force any more of \the [parent] to go down [eater.p_their()] throat!", "You cannot force any more of \the [parent] to go down your throat!")
+ return
+
+
+
+
+
+ else //If you're feeding it to someone else.
+ if(isbrain(eater))
+ to_chat(feeder, "[eater] doesn't seem to have a mouth!")
+ return
+ if(fullness <= (600 * (1 + eater.overeatduration / 1000)))
+ eater.visible_message(
+ "[feeder] attempts to feed [eater] [parent].", \
+ "[feeder] attempts to feed you [parent]."
+ )
+ if(eater.is_blind())
+ to_chat(eater, "You feel someone trying to feed you something!")
+ else
+ eater.visible_message(
+ "[feeder] cannot force any more of [parent] down [eater]'s throat!", \
+ "[feeder] cannot force any more of [parent] down your throat!"
+ )
+ if(eater.is_blind())
+ to_chat(eater, "You're too full to eat what's being fed to you!")
+ return
+ if(!do_after(feeder, delay = time_to_eat, target = eater)) //Wait 3 seconds before you can feed
+ return
+ if(IsFoodGone(owner, feeder))
+ return
+ log_combat(feeder, eater, "fed", owner.reagents.log_list())
+ eater.visible_message(
+ "[feeder] forces [eater] to eat [parent]!", \
+ "[feeder] forces you to eat [parent]!"
+ )
+ if(eater.is_blind())
+ to_chat(eater, "You're forced to eat something!")
+
+ TakeBite(eater, feeder)
+
+ //If we're not force-feeding, try take another bite
+ if(eater == feeder && eat_time)
+ INVOKE_ASYNC(src, PROC_REF(TryToEat), eater, feeder)
+
+#undef EAT_TIME_FORCE_FEED
+
+///This function lets the eater take a bite and transfers the reagents to the eater.
+/datum/component/edible/proc/TakeBite(mob/living/eater, mob/living/feeder)
+
+ var/atom/owner = parent
+
+ if(!owner?.reagents)
+ return FALSE
+ if(eater.satiety > -200)
+ eater.satiety -= junkiness
+ playsound(eater.loc,'sound/items/eatfood.ogg', rand(10,50), TRUE)
+ if(!owner.reagents.total_volume)
+ return
+ SEND_SIGNAL(parent, COMSIG_FOOD_EATEN, eater, feeder, bitecount, bite_consumption)
+ var/fraction = min(bite_consumption / owner.reagents.total_volume, 1)
+ owner.reagents.trans_to(eater, bite_consumption, transfered_by = feeder, method = INGEST)
+ bitecount++
+ check_liked(fraction, eater)
+ if(!owner.reagents.total_volume)
+ on_consume(eater, feeder)
+
+ //Invoke our after eat callback if it is valid
+ if(after_eat)
+ after_eat.Invoke(eater, feeder, bitecount)
+
+ return TRUE
+
+///Checks if we can compost something, and handles it
+/datum/component/edible/proc/compost(mob/living/user)
+ SIGNAL_HANDLER
+ if(on_compost && !on_compost.Invoke(user))
+ return COMPONENT_EDIBLE_BLOCK_COMPOST
+
+///Checks whether or not the eater can actually consume the food
+/datum/component/edible/proc/CanConsume(mob/living/eater, mob/living/feeder)
+ if(!iscarbon(eater))
+ return FALSE
+ if(pre_eat && !pre_eat.Invoke(eater, feeder))
+ return FALSE
+ var/mob/living/carbon/C = eater
+ var/covered = ""
+ if(C.is_mouth_covered(head_only = 1))
+ covered = "headgear"
+ else if(C.is_mouth_covered(mask_only = 1))
+ covered = "mask"
+ if(covered)
+ var/who = (isnull(feeder) || eater == feeder) ? "your" : "[eater.p_their()]"
+ to_chat(feeder, "You have to remove [who] [covered] first!")
+ return FALSE
+ return TRUE
+
+///Check foodtypes to see if we should send a moodlet
+/datum/component/edible/proc/check_liked(fraction, mob/eater)
+ if(last_check_time + 50 > world.time)
+ return FALSE
+ if(!ishuman(eater))
+ return FALSE
+ var/mob/living/carbon/human/human_eater = eater
+ var/obj/item/organ/tongue/tongue = human_eater.getorganslot(ORGAN_SLOT_TONGUE)
+ if((foodtypes & BREAKFAST) && world.time - SSticker.round_start_time < STOP_SERVING_BREAKFAST)
+ SEND_SIGNAL(human_eater, COMSIG_ADD_MOOD_EVENT, "breakfast", /datum/mood_event/breakfast)
+ if(HAS_TRAIT(human_eater, TRAIT_AGEUSIA))
+ if(foodtypes & tongue.toxic_food)
+ to_chat(human_eater, "You don't feel so good...")
+ human_eater.adjust_disgust(25 + 30 * fraction)
+ else
+ if(foodtypes & tongue.toxic_food)
+ to_chat(human_eater,"What the hell was that thing?!")
+ human_eater.adjust_disgust(25 + 30 * fraction)
+ SEND_SIGNAL(human_eater, COMSIG_ADD_MOOD_EVENT, "toxic_food", /datum/mood_event/disgusting_food)
+ else if(foodtypes & tongue.disliked_food)
+ to_chat(human_eater,"That didn't taste very good...")
+ human_eater.adjust_disgust(11 + 15 * fraction)
+ SEND_SIGNAL(human_eater, COMSIG_ADD_MOOD_EVENT, "gross_food", /datum/mood_event/gross_food)
+ else if(foodtypes & tongue.liked_food)
+ to_chat(human_eater,"I love this taste!")
+ human_eater.adjust_disgust(-5 + -2.5 * fraction)
+ SEND_SIGNAL(human_eater, COMSIG_ADD_MOOD_EVENT, "fav_food", /datum/mood_event/favorite_food)
+ last_check_time = world.time
+
+///Delete the item when it is fully eaten
+/datum/component/edible/proc/on_consume(mob/living/eater, mob/living/feeder)
+ SEND_SIGNAL(parent, COMSIG_FOOD_CONSUMED, eater, feeder)
+
+ on_consume?.Invoke(eater, feeder)
+
+ if(isturf(parent))
+ var/turf/T = parent
+ T.ScrapeAway(1, CHANGETURF_INHERIT_AIR)
+ else
+ qdel(parent)
+
+///Ability to feed food to puppers
+/datum/component/edible/proc/use_by_animal(datum/source, mob/user)
+ SIGNAL_HANDLER
+ var/atom/owner = parent
+
+ if(!isdog(user))
+ return
+ var/mob/living/L = user
+ if(bitecount == 0 || prob(50))
+ L.manual_emote("nibbles away at \the [parent].")
+ bitecount++
+ . = COMPONENT_CANCEL_ATTACK_CHAIN
+ L.taste(owner.reagents) // why should carbons get all the fun?
+ if(bitecount >= 5)
+ var/satisfaction_text = pick("burps from enjoyment.", "yaps for more!", "woofs twice.", "looks at the area where \the [parent] was.")
+ L.manual_emote(satisfaction_text)
+ qdel(parent)
diff --git a/code/datums/elements/food/food_trash.dm b/code/datums/elements/food/food_trash.dm
new file mode 100644
index 00000000000..6c484c414a0
--- /dev/null
+++ b/code/datums/elements/food/food_trash.dm
@@ -0,0 +1,40 @@
+// If an item has the food_trash element it will drop an item when it is consumed.
+/datum/element/food_trash
+ element_flags = ELEMENT_BESPOKE
+ id_arg_index = 2
+ /// The type of trash that is spawned by this element
+ var/trash
+ ///Flags of the trash element that change its behavior UNUSED UNTIL PART 2
+ //var/flags
+ ///Generate trash proc path
+ var/generate_trash_procpath
+
+/datum/element/food_trash/Attach(datum/target, atom/trash, flags, generate_trash_proc)
+ . = ..()
+ if(!isatom(target))
+ return ELEMENT_INCOMPATIBLE
+ src.trash = trash
+ //src.flags = flags
+ RegisterSignal(target, COMSIG_FOOD_CONSUMED, PROC_REF(generate_trash))
+ if(!generate_trash_procpath && generate_trash_proc)
+ generate_trash_procpath = generate_trash_proc
+
+/datum/element/food_trash/Detach(datum/target)
+ . = ..()
+ UnregisterSignal(target, COMSIG_FOOD_CONSUMED)
+
+/datum/element/food_trash/proc/generate_trash(datum/source, mob/living/eater, mob/living/feeder)
+ SIGNAL_HANDLER
+
+ ///cringy signal_handler shouldnt be needed if you dont want to return but oh well
+ INVOKE_ASYNC(src, PROC_REF(async_generate_trash), source)
+
+/datum/element/food_trash/proc/async_generate_trash(datum/source)
+ var/atom/edible_object = source
+
+ var/obj/item/trash_item = generate_trash_procpath ? call(source, generate_trash_procpath)() : new trash(edible_object.drop_location())
+
+ if(isliving(edible_object.loc))
+ var/mob/living/food_holding_mob = edible_object.loc
+ food_holding_mob.dropItemToGround(edible_object)
+ food_holding_mob.put_in_hands(trash_item)
diff --git a/code/datums/elements/food/processable.dm b/code/datums/elements/food/processable.dm
new file mode 100644
index 00000000000..503e5169c87
--- /dev/null
+++ b/code/datums/elements/food/processable.dm
@@ -0,0 +1,47 @@
+// If an item has the processable item, it can be processed into another item with a specific tool. This adds generic behavior for those actions to make it easier to set-up generically.
+/datum/element/processable
+ element_flags = ELEMENT_BESPOKE
+ id_arg_index = 2
+ ///The type of atom this creates when the processing recipe is used.
+ var/result_atom_type
+ ///The tool behaviour for this processing recipe
+ var/tool_behaviour
+ ///Time to process the atom
+ var/time_to_process
+ ///Amount of the resulting actor this will create
+ var/amount_created
+ ///Whether or not the atom being processed has to be on a table or tray to process it
+ var/table_required
+
+/datum/element/processable/Attach(datum/target, tool_behaviour, result_atom_type, amount_created = 3, time_to_process = 20, table_required = FALSE)
+ . = ..()
+ if(!isatom(target))
+ return ELEMENT_INCOMPATIBLE
+
+ src.tool_behaviour = tool_behaviour
+ src.amount_created = amount_created
+ src.time_to_process = time_to_process
+ src.result_atom_type = result_atom_type
+ src.table_required = table_required
+
+ RegisterSignal(target, COMSIG_ATOM_TOOL_ACT(tool_behaviour), PROC_REF(try_process))
+
+/datum/element/processable/Detach(datum/target)
+ . = ..()
+ UnregisterSignal(target, COMSIG_ATOM_TOOL_ACT(tool_behaviour))
+
+/datum/element/processable/proc/try_process(datum/source, mob/living/user, obj/item/I, list/mutable_recipes)
+ SIGNAL_HANDLER
+
+ if(table_required)
+ var/obj/item/found_item = source
+ var/found_location = found_item.loc
+ var/found_turf = isturf(found_location)
+ var/found_table = locate(/obj/structure/table) in found_location
+ var/found_tray = locate(/obj/item/storage/bag/tray) in found_location
+ if(!found_turf && !istype(found_location, /obj/item/storage/bag/tray) || found_turf && !(found_table || found_tray))
+ to_chat(user, "You cannot make that here! You need a table or at least a tray.")
+ return
+
+ mutable_recipes += list(list(TOOL_PROCESSING_RESULT = result_atom_type, TOOL_PROCESSING_AMOUNT = amount_created, TOOL_PROCESSING_TIME = time_to_process))
+ return COMPONENT_NO_AFTERATTACK
diff --git a/code/datums/elements/plant_backfire.dm b/code/datums/elements/plant_backfire.dm
new file mode 100644
index 00000000000..47cd1de51fe
--- /dev/null
+++ b/code/datums/elements/plant_backfire.dm
@@ -0,0 +1,125 @@
+/// -- Plant backfire element --
+/// Certain high-danger plants, like death-nettles, will backfire and harm the holder if they're not properly protected.
+/// If a user is protected with something like leather gloves, they can handle them normally.
+/// If they're not protected properly, we invoke a callback on the user, harming or inconveniencing them.
+/datum/element/plant_backfire
+ element_flags = ELEMENT_BESPOKE
+ id_arg_index = 2
+ /// Whether we stop the current action if backfire is triggered (EX: returning CANCEL_ATTACK_CHAIN)
+ var/cancel_action = FALSE
+ /// Any extra traits we want to check in addition to TRAIT_PLANT_SAFE. Mobs with a trait in this list will be considered safe. List of traits.
+ var/extra_traits
+ /// Any plant genes we want to check that are required for our plant to be dangerous. Plants without a gene in this list will be considered safe. List of typepaths.
+ var/extra_genes
+
+/datum/element/plant_backfire/Attach(datum/target, cancel_action = FALSE, extra_traits, extra_genes)
+ . = ..()
+ if(!isitem(target))
+ return ELEMENT_INCOMPATIBLE
+
+ src.cancel_action = cancel_action
+ src.extra_traits = extra_traits
+ src.extra_genes = extra_genes
+
+ RegisterSignal(target, COMSIG_ITEM_PRE_ATTACK, PROC_REF(attack_safety_check))
+ RegisterSignal(target, COMSIG_ITEM_PICKUP, PROC_REF(pickup_safety_check))
+ RegisterSignal(target, COMSIG_MOVABLE_PRE_THROW, PROC_REF(throw_safety_check))
+
+/datum/element/plant_backfire/Detach(datum/target)
+ . = ..()
+ UnregisterSignal(target, list(COMSIG_ITEM_PRE_ATTACK, COMSIG_ITEM_PICKUP, COMSIG_MOVABLE_PRE_THROW))
+
+/**
+ * Checks before we attack if we're okay to continue.
+ *
+ * source - our plant
+ * user - the mob wielding our [source]
+ */
+/datum/element/plant_backfire/proc/attack_safety_check(obj/item/source, atom/target, mob/user)
+ SIGNAL_HANDLER
+
+ // Covers stuff like tk, since we aren't actually touching the plant.
+ if(!user.is_holding(source))
+ return
+ if(!backfire(source, user))
+ return
+
+ return //cancel_action ? COMPONENT_CANCEL_ATTACK_CHAIN : NONE
+
+/**
+ * Checks before we pick up the plant if we're okay to continue.
+ *
+ * source - our plant
+ * user - the mob picking our [source]
+ */
+/datum/element/plant_backfire/proc/pickup_safety_check(obj/item/source, mob/user)
+ SIGNAL_HANDLER
+
+ backfire(source, user)
+
+/**
+ * Checks before we throw the plant if we're okay to continue.
+ *
+ * source - our plant
+ * thrower - the mob throwing our [source]
+ */
+/datum/element/plant_backfire/proc/throw_safety_check(obj/item/source, list/arguments)
+ SIGNAL_HANDLER
+
+ var/mob/living/thrower = arguments[4] // the 4th arg = the mob throwing our item
+ if(!istype(thrower) || !thrower.is_holding(source))
+ return
+ if(!backfire(source, thrower))
+ return
+
+ return //cancel_action ? COMPONENT_CANCEL_ATTACK_CHAIN : NONE
+
+/**
+ * The actual backfire occurs here.
+ * Checks if the user is able to safely handle the plant.
+ * If not, sends the backfire signal (meaning backfire will occur and be handled by one or multiple genes).
+ *
+ * Returns FALSE if the user was safe and no backfire occured.
+ * Returns TRUE if the user was not safe and a backfire actually happened.
+ */
+/datum/element/plant_backfire/proc/backfire(obj/item/plant, mob/user)
+ if(plant_safety_check(plant, user))
+ return FALSE
+
+ SEND_SIGNAL(plant, COMSIG_PLANT_ON_BACKFIRE, user)
+ return TRUE
+
+/**
+ * Actually checks if our user is safely handling our plant.
+ *
+ * Checks for TRAIT_PLANT_SAFE, and returns TRUE if we have it.
+ * Then, any extra traits we need to check (Like TRAIT_PIERCEIMMUNE for nettles) and returns TRUE if we have one of them.
+ * Then, any extra genes we need to check (Like liquid contents for bluespace tomatos) and returns TRUE if we don't have the gene.
+ *
+ * source - our plant
+ * user - the carbon handling our [source]
+ *
+ * returns FALSE if none of the checks are successful.
+ */
+/datum/element/plant_backfire/proc/plant_safety_check(obj/item/plant, mob/living/carbon/user)
+ if(!istype(user))
+ return TRUE
+
+ if(HAS_TRAIT(user, TRAIT_PLANT_SAFE))
+ return TRUE
+
+ for(var/checked_trait in extra_traits)
+ if(HAS_TRAIT(user, checked_trait))
+ return TRUE
+
+ var/obj/item/seeds/our_seed = plant.get_plant_seed()
+ if(our_seed)
+ for(var/checked_gene in extra_genes)
+ if(!our_seed.get_gene(checked_gene))
+ return TRUE
+
+ for(var/obj/item/clothing/worn_item in user.get_equipped_items())
+ if((worn_item.body_parts_covered & HANDS) && (worn_item.clothing_flags & THICKMATERIAL))
+ return TRUE
+
+ return FALSE
diff --git a/code/datums/materials/_material.dm b/code/datums/materials/_material.dm
index 4f505cb2a3e..1b05f7d7ef9 100644
--- a/code/datums/materials/_material.dm
+++ b/code/datums/materials/_material.dm
@@ -79,6 +79,16 @@ Simple datum which is instanced once per type and is used for every object of sa
/atom/proc/mat_update_desc(/datum/material/mat)
return
+
+/**
+ * This proc is called when the mat is found in an item that's consumed by accident. see /obj/item/proc/on_accidental_consumption.
+ * Arguments
+ * * M - person consuming the mat
+ * * S - (optional) item the mat is contained in (NOT the item with the mat itself)
+ */
+/datum/material/proc/on_accidental_mat_consumption(mob/living/carbon/M, obj/item/S)
+ return FALSE
+
///This proc is called when the material is added to an object specifically.
/datum/material/proc/on_applied_obj(obj/o, amount, material_flags)
if(material_flags & MATERIAL_AFFECT_STATISTICS)
diff --git a/code/datums/traits/negative.dm b/code/datums/traits/negative.dm
index dcbce187b3c..e96aa459d69 100644
--- a/code/datums/traits/negative.dm
+++ b/code/datums/traits/negative.dm
@@ -107,11 +107,11 @@
if("Clown")
heirloom_type = /obj/item/bikehorn/golden
if("Mime")
- heirloom_type = /obj/item/reagent_containers/food/snacks/baguette
+ heirloom_type = /obj/item/food/baguette
if("Janitor")
heirloom_type = pick(/obj/item/mop, /obj/item/clothing/suit/caution, /obj/item/reagent_containers/glass/bucket, /obj/item/paper/fluff/stations/soap)
if("Cook")
- heirloom_type = pick(/obj/item/reagent_containers/food/condiment/saltshaker, /obj/item/kitchen/rollingpin, /obj/item/clothing/head/chefhat)
+ heirloom_type = pick(/obj/item/reagent_containers/condiment/saltshaker, /obj/item/kitchen/rollingpin, /obj/item/clothing/head/chefhat)
if("Botanist")
heirloom_type = pick(/obj/item/cultivator, /obj/item/reagent_containers/glass/bucket, /obj/item/toy/plush/beeplushie)
if("Bartender")
@@ -465,7 +465,7 @@
to_chat(H, "You think of a dumb thing you said a long time ago and scream internally.")
dumb_thing = FALSE //only once per life
if(prob(1))
- new/obj/item/reagent_containers/food/snacks/spaghetti/pastatomato(get_turf(H)) //now that's what I call spaghetti code
+ new/obj/item/food/spaghetti/pastatomato(get_turf(H)) //now that's what I call spaghetti code
// small chance to make eye contact with inanimate objects/mindless mobs because of nerves
/datum/quirk/social_anxiety/proc/looks_at_floor(datum/source, atom/A)
diff --git a/code/datums/wires/mod.dm b/code/datums/wires/mod.dm
new file mode 100644
index 00000000000..b5805557eaf
--- /dev/null
+++ b/code/datums/wires/mod.dm
@@ -0,0 +1,57 @@
+/datum/wires/mod
+ holder_type = /obj/item/mod/control
+ proper_name = "MOD control unit"
+
+/datum/wires/mod/New(atom/holder)
+ wires = list(WIRE_HACK, WIRE_DISABLE, WIRE_SHOCK, WIRE_INTERFACE)
+ add_duds(2)
+ ..()
+
+/datum/wires/mod/interactable(mob/user)
+ if(!..())
+ return FALSE
+ var/obj/item/mod/control/mod = holder
+ return mod.open
+
+/datum/wires/mod/get_status()
+ var/obj/item/mod/control/mod = holder
+ var/list/status = list()
+ status += "The orange light is [mod.seconds_electrified ? "on" : "off"]."
+ status += "The red light is [mod.malfunctioning ? "off" : "blinking"]."
+ status += "The green light is [mod.locked ? "on" : "off"]."
+ status += "The yellow light is [mod.interface_break ? "off" : "on"]."
+ return status
+
+/datum/wires/mod/on_pulse(wire)
+ var/obj/item/mod/control/mod = holder
+ switch(wire)
+ if(WIRE_HACK)
+ mod.locked = !mod.locked
+ if(WIRE_DISABLE)
+ mod.malfunctioning = TRUE
+ if(WIRE_SHOCK)
+ mod.seconds_electrified = MACHINE_DEFAULT_ELECTRIFY_TIME
+ if(WIRE_INTERFACE)
+ mod.interface_break = !mod.interface_break
+
+/datum/wires/mod/on_cut(wire, mend)
+ var/obj/item/mod/control/mod = holder
+ switch(wire)
+ if(WIRE_HACK)
+ if(!mend)
+ mod.req_access = list()
+ if(WIRE_DISABLE)
+ mod.malfunctioning = !mend
+ if(WIRE_SHOCK)
+ if(mend)
+ mod.seconds_electrified = MACHINE_NOT_ELECTRIFIED
+ else
+ mod.seconds_electrified = MACHINE_ELECTRIFIED_PERMANENT
+ if(WIRE_INTERFACE)
+ mod.interface_break = !mend
+
+/datum/wires/mod/ui_act(action, params)
+ var/obj/item/mod/control/mod = holder
+ if(!issilicon(usr) && mod.seconds_electrified && mod.shock(usr))
+ return FALSE
+ return ..()
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 6d9f0df9f2e..f059ae47564 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -44,6 +44,14 @@
*/
var/list/atom_colours
+ /// Lazylist of all images (hopefully attached to us) to update when we change z levels
+ /// You will need to manage adding/removing from this yourself, but I'll do the updating for you
+ var/list/image/update_on_z
+
+ /// Lazylist of all overlays attached to us to update when we change z levels
+ /// You will need to manage adding/removing from this yourself, but I'll do the updating for you
+ /// Oh and note, if order of addition is important this WILL break that. so mind yourself
+ var/list/image/update_overlays_on_z
/// a very temporary list of overlays to remove
var/list/remove_overlays
@@ -977,7 +985,7 @@
return TRUE
///Get the best place to dump the items contained in the source storage item?
-/atom/proc/get_dumping_location(obj/item/storage/source,mob/user)
+/atom/proc/get_dumping_location()
return null
/**
@@ -1265,25 +1273,71 @@
* Must return parent proc ..() in the end if overridden
*/
/atom/proc/tool_act(mob/living/user, obj/item/I, tool_type)
+ var/signal_result
+
+ var/list/processing_recipes = list() //List of recipes that can be mutated by sending the signal
+ signal_result = SEND_SIGNAL(src, COMSIG_ATOM_TOOL_ACT(tool_type), user, I, processing_recipes)
+ if(processing_recipes.len)
+ process_recipes(user, I, processing_recipes)
+ if(QDELETED(I))
+ return TRUE
switch(tool_type)
if(TOOL_CROWBAR)
- . |= crowbar_act(user, I)
+ . = crowbar_act(user, I)
if(TOOL_MULTITOOL)
- . |= multitool_act(user, I)
+ . = multitool_act(user, I)
if(TOOL_SCREWDRIVER)
- . |= screwdriver_act(user, I)
+ . = screwdriver_act(user, I)
if(TOOL_WRENCH)
- . |= wrench_act(user, I)
+ . = wrench_act(user, I)
if(TOOL_WIRECUTTER)
- . |= wirecutter_act(user, I)
+ . = wirecutter_act(user, I)
if(TOOL_WELDER)
- . |= welder_act(user, I)
+ . = welder_act(user, I)
if(TOOL_ANALYZER)
- . |= analyzer_act(user, I)
- if(. & COMPONENT_BLOCK_TOOL_ATTACK)
+ . = analyzer_act(user, I)
+ if(. || signal_result & COMPONENT_BLOCK_TOOL_ATTACK) //Either the proc or the signal handled the tool's events in some way.
return TRUE
-//! Tool-specific behavior procs. They send signals, so try to call ..()
+/atom/proc/process_recipes(mob/living/user, obj/item/I, list/processing_recipes)
+ //Only one recipe? use the first
+ if(processing_recipes.len == 1)
+ StartProcessingAtom(user, I, processing_recipes[1])
+ return
+ //Otherwise, select one with a radial
+ ShowProcessingGui(user, I, processing_recipes)
+
+///Creates the radial and processes the selected option
+/atom/proc/ShowProcessingGui(mob/living/user, obj/item/I, list/possible_options)
+ var/list/choices_to_options = list() //Dict of object name | dict of object processing settings
+ var/list/choices = list()
+
+ for(var/i in possible_options)
+ var/list/current_option = i
+ var/atom/current_option_type = current_option[TOOL_PROCESSING_RESULT]
+ choices_to_options[initial(current_option_type.name)] = current_option
+ var/image/option_image = image(icon = initial(current_option_type.icon), icon_state = initial(current_option_type.icon_state))
+ choices += list("[initial(current_option_type.name)]" = option_image)
+
+ var/pick = show_radial_menu(user, src, choices, radius = 36, require_near = TRUE)
+
+ StartProcessingAtom(user, I, choices_to_options[pick])
+
+
+/atom/proc/StartProcessingAtom(mob/living/user, obj/item/I, list/chosen_option)
+ to_chat(user, "You start working on [src]")
+ if(I.use_tool(src, user, chosen_option[TOOL_PROCESSING_TIME], volume=50))
+ var/atom/atom_to_create = chosen_option[TOOL_PROCESSING_RESULT]
+ for(var/i = 1 to chosen_option[TOOL_PROCESSING_AMOUNT])
+ new atom_to_create(loc)
+ to_chat(user, "You manage to create [chosen_option[TOOL_PROCESSING_AMOUNT]] [initial(atom_to_create.name)] from [src]")
+ qdel(src)
+ return
+
+/atom/proc/OnCreatedFromProcessing(mob/living/user, obj/item/I, list/chosen_option, atom/original_atom)
+ return
+
+//! Tool-specific behavior procs.
///
///Crowbar act
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index beb7cef2718..79326ab9e2a 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -772,6 +772,44 @@
. = movement_type
movement_type = newval
+/**
+ * Called when a movable changes z-levels.
+ *
+ * Arguments:
+ * * old_turf - The previous turf they were on before.
+ * * new_turf - The turf they have now entered.
+ * * same_z_layer - If their old and new z levels are on the same level of plane offsets or not
+ * * notify_contents - Whether or not to notify the movable's contents that their z-level has changed. NOTE, IF YOU SET THIS, YOU NEED TO MANUALLY SET PLANE OF THE CONTENTS LATER
+ */
+/atom/movable/proc/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents = TRUE)
+ SHOULD_CALL_PARENT(TRUE)
+ SEND_SIGNAL(src, COMSIG_MOVABLE_Z_CHANGED, old_turf, new_turf, same_z_layer)
+
+ // If our turfs are on different z "layers", recalc our planes
+ if(!same_z_layer && !QDELETED(src))
+ SET_PLANE(src, PLANE_TO_TRUE(src.plane), new_turf)
+ // a TON of overlays use planes, and thus require offsets
+ // so we do this. sucks to suck
+ update_appearance()
+
+ if(update_on_z)
+ // I so much wish this could be somewhere else. alas, no.
+ for(var/image/update in update_on_z)
+ SET_PLANE(update, PLANE_TO_TRUE(update.plane), new_turf)
+ if(update_overlays_on_z)
+ // This EVEN more so
+ cut_overlay(update_overlays_on_z)
+ // This even more so
+ for(var/mutable_appearance/update in update_overlays_on_z)
+ SET_PLANE(update, PLANE_TO_TRUE(update.plane), new_turf)
+ add_overlay(update_overlays_on_z)
+
+ if(!notify_contents)
+ return
+
+ for (var/atom/movable/content as anything in src) // Notify contents of Z-transition.
+ content.on_changed_z_level(old_turf, new_turf, same_z_layer)
+
/**
* Called whenever an object moves and by mobs when they attempt to move themselves through space
* And when an object or action applies a force on src, see [newtonian_move][/atom/movable/proc/newtonian_move]
@@ -784,6 +822,9 @@
* * movement_dir - 0 when stopping or any dir when trying to move
*/
/atom/movable/proc/Process_Spacemove(movement_dir = 0)
+ if(SEND_SIGNAL(src, COMSIG_MOVABLE_SPACEMOVE, movement_dir) & COMSIG_MOVABLE_STOP_SPACEMOVE)
+ return TRUE
+
if(has_gravity(src))
return 1
diff --git a/code/game/machinery/suit_storage_unit.dm b/code/game/machinery/suit_storage_unit.dm
index 163f16a707f..04e6ac70f42 100644
--- a/code/game/machinery/suit_storage_unit.dm
+++ b/code/game/machinery/suit_storage_unit.dm
@@ -16,6 +16,7 @@
var/obj/item/clothing/suit/space/suit = null
var/obj/item/clothing/head/helmet/space/helmet = null
var/obj/item/clothing/mask/mask = null
+ var/obj/item/mod/control/mod = null
var/obj/item/storage = null
// if you add more storage slots, update cook() to clear their radiation too.
@@ -27,6 +28,8 @@
var/mask_type = null
/// What type of additional item the unit starts with when spawned.
var/storage_type = null
+ /// What type of MOD the unit starts with when spawned.
+ var/mod_type = null
state_open = FALSE
/// If the SSU's doors are locked closed. Can be toggled manually via the UI, but is also locked automatically when the UV decontamination sequence is running.
@@ -194,6 +197,8 @@
helmet = new helmet_type(src)
if(mask_type)
mask = new mask_type(src)
+ if(mod_type)
+ mod = new mod_type(src)
if(storage_type)
storage = new storage_type(src)
update_appearance()
@@ -215,6 +220,7 @@
QDEL_NULL(suit)
QDEL_NULL(helmet)
QDEL_NULL(mask)
+ QDEL_NULL(mod)
QDEL_NULL(storage)
return ..()
@@ -225,7 +231,7 @@
. += "[base_icon_state]_panel"
if(state_open)
. += "[base_icon_state]_open"
- if(suit)
+ if(suit || mod)
. += "[base_icon_state]_suit"
if(helmet)
. += "[base_icon_state]_helm"
@@ -265,6 +271,7 @@
helmet = null
suit = null
mask = null
+ mod = null
storage = null
occupant = null
@@ -291,6 +298,7 @@
"suit" = create_silhouette_of(/obj/item/clothing/suit/space/eva),
"helmet" = create_silhouette_of(/obj/item/clothing/head/helmet/space/eva),
"mask" = create_silhouette_of(/obj/item/clothing/mask/breath),
+ "mod" = create_silhouette_of(/obj/item/mod/control),
"storage" = create_silhouette_of(/obj/item/tank/internals/oxygen),
)
@@ -402,7 +410,7 @@
if(!is_operational)
to_chat(user, span_warning("The unit is not operational!"))
return
- if(occupant || helmet || suit || storage)
+ if(occupant || helmet || suit || mod || storage)
to_chat(user, span_warning("It's too cluttered inside to fit in!"))
return
@@ -412,7 +420,7 @@
target.visible_message(span_warning("[user] starts shoving [target] into [src]!"), span_userdanger("[user] starts shoving you into [src]!"))
if(do_after(user, 30, target))
- if(occupant || helmet || suit || storage)
+ if(occupant || helmet || suit || mod || storage)
return
if(target == user)
user.visible_message(span_warning("[user] slips into [src] and closes the door behind [user.p_them()]!"), span_notice("You slip into [src]'s cramped space and shut its door."))
@@ -457,6 +465,8 @@
qdel(suit) // Delete everything but the occupant.
mask = null
qdel(mask)
+ mod = null
+ qdel(mod)
storage = null
qdel(storage)
// The wires get damaged too.
@@ -484,6 +494,9 @@
if(mask)
things_to_clear += mask
things_to_clear += mask.GetAllContents()
+ if(mod)
+ things_to_clear += mod
+ things_to_clear += mod.GetAllContents()
if(storage)
things_to_clear += storage
things_to_clear += storage.GetAllContents()
@@ -570,6 +583,13 @@
if(!user.transferItemToLoc(I, src))
return
mask = I
+ else if(istype(I, /obj/item/mod/control))
+ if(mod)
+ to_chat(user, span_warning("The unit already contains a MOD!"))
+ return
+ if(!user.transferItemToLoc(I, src))
+ return
+ mod = I
else
if(storage)
to_chat(user, span_warning("The auxiliary storage compartment is full!"))
@@ -636,6 +656,9 @@
else if(istype(AM, /obj/item/clothing/mask) && !mask)
AM.forceMove(src)
mask = AM
+ else if(istype(AM, /obj/item/mod/control) && !storage)
+ AM.forceMove(src)
+ mod = AM
else if(istype(AM, /obj/item) && !storage)
AM.forceMove(src)
storage = AM
diff --git a/code/game/objects/effects/contraband.dm b/code/game/objects/effects/contraband.dm
index 3a11ab44414..458d4556592 100644
--- a/code/game/objects/effects/contraband.dm
+++ b/code/game/objects/effects/contraband.dm
@@ -877,8 +877,8 @@
/obj/structure/sign/poster/terragov/mars
name = "Mars"
- desc = "Mars, the fourth planet in the Sol system. While evidence suggests that Venus and Mars may have once had life, Terra was the only one that kept it. This poster in particular is trying to attract tourists to Mars, listing attractions like skiing resorts and ancient robot exhibits."
- icon_state = "poster-solgov-ares"
+ desc = "Mars, fourth planet in the Sol system. While evidence suggests that Venus and Mars may have once had life, Terra was the only one that kept it. This poster in particular is trying to attract tourists to Mars, listing attractions like skiing resorts and ancient robot exhibits."
+ icon_state = "poster-solgov-mars"
/obj/structure/sign/poster/terragov/luna
name = "Luna"
diff --git a/code/game/objects/effects/effect_system/effects_other.dm b/code/game/objects/effects/effect_system/effects_other.dm
index 3f2b6ecaf94..efa1de11103 100644
--- a/code/game/objects/effects/effect_system/effects_other.dm
+++ b/code/game/objects/effects/effect_system/effects_other.dm
@@ -81,6 +81,9 @@
/datum/effect_system/trail_follow/proc/set_dir(obj/effect/particle_effect/ion_trails/I)
I.setDir(holder.dir)
+/datum/effect_system/trail_follow/ion/grav_allowed
+ nograv_required = FALSE
+
//Reagent-based explosion effect
/datum/effect_system/reagents_explosion
diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
index 4913f9b835c..d36c427d5b2 100644
--- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm
+++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
@@ -469,6 +469,11 @@
duration = 12
shrink = FALSE
+/obj/effect/temp_visual/light_ash
+ icon_state = "light_ash"
+ icon = 'icons/effects/weather_effects.dmi'
+ duration = 3.2 SECONDS
+
/obj/effect/temp_visual/warp_cube
duration = 5
var/outgoing = TRUE
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 6fefb2e631f..99f5084363d 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -240,6 +240,51 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb
qdel(X)
return ..()
+/// Called when an action associated with our item is deleted
+/obj/item/proc/on_action_deleted(datum/source)
+ SIGNAL_HANDLER
+
+ if(!(source in actions))
+ CRASH("An action ([source.type]) was deleted that was associated with an item ([src]), but was not found in the item's actions list.")
+
+ LAZYREMOVE(actions, source)
+
+
+/// Adds an item action to our list of item actions.
+/// Item actions are actions linked to our item, that are granted to mobs who equip us.
+/// This also ensures that the actions are properly tracked in the actions list and removed if they're deleted.
+/// Can be be passed a typepath of an action or an instance of an action.
+/obj/item/proc/add_item_action(action_or_action_type)
+
+ var/datum/action/action
+ if(ispath(action_or_action_type, /datum/action))
+ action = new action_or_action_type(src)
+ else if(istype(action_or_action_type, /datum/action))
+ action = action_or_action_type
+ else
+ CRASH("item add_item_action got a type or instance of something that wasn't an action.")
+
+ LAZYADD(actions, action)
+ RegisterSignal(action, COMSIG_PARENT_QDELETING, PROC_REF(on_action_deleted))
+ grant_action_to_bearer(action)
+ return action
+
+/// Grant the action to anyone who has this item equipped to an appropriate slot
+/obj/item/proc/grant_action_to_bearer(datum/action/action)
+ if(!ismob(loc))
+ return
+ var/mob/holder = loc
+ give_item_action(action, holder, holder.get_slot_by_item(src))
+
+/// Removes an instance of an action from our list of item actions.
+/obj/item/proc/remove_item_action(datum/action/action)
+ if(!action)
+ return
+
+ UnregisterSignal(action, COMSIG_PARENT_QDELETING)
+ LAZYREMOVE(actions, action)
+ qdel(action)
+
/obj/item/proc/check_allowed_items(atom/target, not_inside, target_self)
if(((src in target) && !target_self) || (!isturf(target.loc) && !isturf(target) && not_inside))
return 0
@@ -535,9 +580,21 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb
playsound(src, pickup_sound, PICKUP_SOUND_VOLUME, ignore_walls = FALSE)
user.update_equipment_speed_mods()
-///sometimes we only want to grant the item's action if it's equipped in a specific slot.
-/obj/item/proc/item_action_slot_check(slot, mob/user)
- if(slot == ITEM_SLOT_BACKPACK || slot == ITEM_SLOT_LEGCUFFED) //these aren't true slots, so avoid granting actions there
+/// Gives one of our item actions to a mob, when equipped to a certain slot
+/obj/item/proc/give_item_action(datum/action/action, mob/to_who, slot)
+ // Some items only give their actions buttons when in a specific slot.
+ if(!item_action_slot_check(slot, to_who))
+ // There is a chance we still have our item action currently,
+ // and are moving it from a "valid slot" to an "invalid slot".
+ // So call Remove() here regardless, even if excessive.
+ action.Remove(to_who)
+ return
+
+ action.Grant(to_who)
+
+/// Sometimes we only want to grant the item's action if it's equipped in a specific slot.
+/obj/item/proc/item_action_slot_check(slot, mob/user, datum/action/action)
+ if(slot & (ITEM_SLOT_BACKPACK|ITEM_SLOT_LEGCUFFED)) //these aren't true slots, so avoid granting actions there
return FALSE
return TRUE
@@ -817,7 +874,8 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb
..()
/obj/item/proc/microwave_act(obj/machinery/microwave/M)
- SEND_SIGNAL(src, COMSIG_ITEM_MICROWAVE_ACT, M)
+ if(SEND_SIGNAL(src, COMSIG_ITEM_MICROWAVE_ACT, M) & COMPONENT_SUCCESFUL_MICROWAVE)
+ return
if(istype(M) && M.dirty < 100)
M.dirty++
@@ -1126,3 +1184,98 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb
*/
/obj/item/proc/get_writing_implement_details()
return null
+
+/// Whether or not this item can be put into a storage item through attackby
+/obj/item/proc/attackby_storage_insert(datum/storage, atom/storage_holder, mob/user)
+ return TRUE
+/// How many different types of mats will be counted in a bite?
+#define MAX_MATS_PER_BITE 2
+
+/*
+ * On accidental consumption: when you somehow end up eating an item accidentally (currently, this is used for when items are hidden in food like bread or cake)
+ *
+ * The base proc will check if the item is sharp and has a decent force.
+ * Then, it checks the item's mat datums for the effects it applies afterwards.
+ * Then, it checks tiny items.
+ * After all that, it returns TRUE if the item is set to be discovered. Otherwise, it returns FALSE.
+ *
+ * This works similarily to /suicide_act: if you want an item to have a unique interaction, go to that item
+ * and give it an /on_accidental_consumption proc override. For a simple example of this, check out the nuke disk.
+ *
+ * Arguments
+ * * M - the mob accidentally consuming the item
+ * * user - the mob feeding M the item - usually, it's the same as M
+ * * source_item - the item that held the item being consumed - bread, cake, etc
+ * * discover_after - if the item will be discovered after being chomped (FALSE will usually mean it was swallowed, TRUE will usually mean it was bitten into and discovered)
+ */
+/obj/item/proc/on_accidental_consumption(mob/living/carbon/victim, mob/living/carbon/user, obj/item/source_item, discover_after = TRUE)
+ if(get_sharpness() && force >= 5) //if we've got something sharp with a decent force (ie, not plastic)
+ INVOKE_ASYNC(victim, TYPE_PROC_REF(/mob, emote), "scream")
+ victim.visible_message("[victim] looks like [victim.p_theyve()] just bit something they shouldn't have!", \
+ "OH GOD! Was that a crunch? That didn't feel good at all!!")
+
+ victim.apply_damage(max(15, force), BRUTE, BODY_ZONE_HEAD)
+ victim.losebreath += 2
+ if(tryEmbed(victim.get_bodypart(BODY_ZONE_CHEST), forced = TRUE)) //and if it embeds successfully in their chest, cause a lot of pain
+ victim.apply_damage(max(25, force*1.5), BRUTE, BODY_ZONE_CHEST)
+ victim.losebreath += 6
+ discover_after = FALSE
+ if(QDELETED(src)) // in case trying to embed it caused its deletion (say, if it's DROPDEL)
+ return
+ source_item?.reagents?.add_reagent(/datum/reagent/blood, 2)
+
+ else if(custom_materials?.len) //if we've got materials, lets see whats in it
+ /// How many mats have we found? You can only be affected by two material datums by default
+ var/found_mats = 0
+ /// How much of each material is in it? Used to determine if the glass should break
+ var/total_material_amount = 0
+
+ for(var/mats in custom_materials)
+ total_material_amount += custom_materials[mats]
+ if(found_mats >= MAX_MATS_PER_BITE)
+ continue //continue instead of break so we can finish adding up all the mats to the total
+
+ var/datum/material/discovered_mat = mats
+ if(discovered_mat.on_accidental_mat_consumption(victim, source_item))
+ found_mats++
+
+ //if there's glass in it and the glass is more than 60% of the item, then we can shatter it
+ if(custom_materials[SSmaterials.GetMaterialRef(/datum/material/glass)] >= total_material_amount * 0.60)
+ if(prob(66)) //66% chance to break it
+ /// The glass shard that is spawned into the source item
+ var/obj/item/shard/broken_glass = new /obj/item/shard(loc)
+ broken_glass.name = "broken [name]"
+ broken_glass.desc = "This used to be \a [name], but it sure isn't anymore."
+ playsound(victim, "shatter", 25, TRUE)
+ qdel(src)
+ if(QDELETED(source_item))
+ broken_glass.on_accidental_consumption(victim, user)
+ else //33% chance to just "crack" it (play a sound) and leave it in the bread
+ playsound(victim, "shatter", 15, TRUE)
+ discover_after = FALSE
+
+ victim.adjust_disgust(33)
+ victim.visible_message(
+ "[victim] looks like [victim.p_theyve()] just bitten into something hard.", \
+ "Eugh! Did I just bite into something?")
+
+ else if(w_class == WEIGHT_CLASS_TINY) //small items like soap or toys that don't have mat datums
+ /// victim's chest (for cavity implanting the item)
+ var/obj/item/bodypart/chest/victim_cavity = victim.get_bodypart(BODY_ZONE_CHEST)
+ if(victim_cavity.cavity_item)
+ victim.vomit(5, FALSE, FALSE, distance = 0)
+ forceMove(drop_location())
+ to_chat(victim, "You vomit up a [name]! [source_item? "Was that in \the [source_item]?" : ""]")
+ else
+ victim.transferItemToLoc(src, victim, TRUE)
+ victim.losebreath += 2
+ victim_cavity.cavity_item = src
+ to_chat(victim, "You swallow hard. [source_item? "Something small was in \the [source_item]..." : ""]")
+ discover_after = FALSE
+
+ else
+ to_chat(victim, "[source_item? "Something strange was in the \the [source_item]..." : "I just bit something strange..."] ")
+
+ return discover_after
+
+#undef MAX_MATS_PER_BITE
diff --git a/code/game/objects/items/food/_food.dm b/code/game/objects/items/food/_food.dm
new file mode 100644
index 00000000000..b8233e0ecc7
--- /dev/null
+++ b/code/game/objects/items/food/_food.dm
@@ -0,0 +1,67 @@
+///Abstract class to allow us to easily create all the generic "normal" food without too much copy pasta of adding more components
+/obj/item/food
+ name = "food"
+ desc = "you eat this"
+ resistance_flags = FLAMMABLE
+ w_class = WEIGHT_CLASS_SMALL
+ icon = 'icons/obj/food/food.dmi'
+ icon_state = null
+ lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi'
+ ///List of reagents this food gets on creation
+ var/list/food_reagents
+ ///Extra flags for things such as if the food is in a container or not
+ var/food_flags
+ ///Bitflag of the types of food this food is
+ var/foodtypes
+ ///Amount of volume the food can contain
+ var/max_volume
+ ///How long it will take to eat this food without any other modifiers
+ var/eat_time
+ ///Tastes to describe this food
+ var/list/tastes
+ ///Verbs used when eating this food in the to_chat messages
+ var/list/eatverbs
+ ///How much reagents per bite
+ var/bite_consumption
+ ///What you get if you microwave the food, this should be replaced once I fully re-work cooking.
+ var/microwaved_type
+ ///Type of atom thats spawned after eating this item
+ var/trash_type
+
+/obj/item/food/Initialize()
+ . = ..()
+ if(food_reagents)
+ food_reagents = string_assoc_list(food_reagents)
+ if(tastes)
+ tastes = string_assoc_list(tastes)
+ if(eatverbs)
+ eatverbs = string_list(eatverbs)
+ make_edible()
+ make_processable()
+ make_leave_trash()
+
+///This proc adds the edible component, overwrite this if you for some reason want to change some specific args like callbacks.
+/obj/item/food/proc/make_edible()
+ AddComponent(/datum/component/edible,\
+ initial_reagents = food_reagents,\
+ food_flags = food_flags,\
+ foodtypes = foodtypes,\
+ volume = max_volume,\
+ eat_time = eat_time,\
+ tastes = tastes,\
+ eatverbs = eatverbs,\
+ bite_consumption = bite_consumption,\
+ microwaved_type = microwaved_type,\
+ )
+
+
+///This proc handles processable elements, overwrite this if you want to add behavior such as slicing, forking, spooning, whatever, to turn the item into something else
+/obj/item/food/proc/make_processable()
+ return
+
+///This proc handles trash components, overwrite this if you want the object to spawn trash
+/obj/item/food/proc/make_leave_trash()
+ if(trash_type)
+ AddElement(/datum/element/food_trash, trash_type)
+ return
diff --git a/code/game/objects/items/food/bread.dm b/code/game/objects/items/food/bread.dm
new file mode 100644
index 00000000000..609315a9ea1
--- /dev/null
+++ b/code/game/objects/items/food/bread.dm
@@ -0,0 +1,378 @@
+
+/obj/item/food/bread
+ name = "bread?"
+ desc = "This shouldn't exist, report to codermonkeys"
+ icon = 'icons/obj/food/burgerbread.dmi'
+ max_volume = 80
+ tastes = list("bread" = 10)
+ foodtypes = GRAIN
+ eat_time = 3 SECONDS
+ /// type is spawned 5 at a time and replaces this bread loaf when processed by cutting tool
+ var/obj/item/food/breadslice/slice_type
+ /// so that the yield can change if it isnt 5
+ var/yield = 5
+
+/obj/item/food/bread/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/dunkable, 10)
+ AddComponent(/datum/component/food_storage)
+
+/obj/item/food/bread/make_processable()
+ if (slice_type)
+ AddElement(/datum/element/processable, TOOL_KNIFE, slice_type, yield, 3 SECONDS, table_required = TRUE)
+ AddElement(/datum/element/processable, TOOL_SAW, slice_type, yield, 4 SECONDS, table_required = TRUE)
+
+/obj/item/food/breadslice
+ name = "breadslice?"
+ desc = "This shouldn't exist, report to codermonkeys"
+ icon = 'icons/obj/food/burgerbread.dmi'
+ foodtypes = GRAIN
+ food_flags = FOOD_FINGER_FOOD
+ eat_time = 0.5 SECONDS
+ w_class = WEIGHT_CLASS_SMALL
+
+/obj/item/food/breadslice/Initialize()
+ . = ..()
+ AddElement(/datum/element/dunkable, 10)
+
+/obj/item/food/bread/plain
+ name = "bread"
+ desc = "Some plain old earthen bread."
+ icon_state = "bread"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 10
+ )
+ tastes = list("bread" = 10)
+ foodtypes = GRAIN
+ w_class = WEIGHT_CLASS_SMALL
+ slice_type = /obj/item/food/breadslice/plain
+
+/obj/item/food/breadslice/plain
+ name = "bread slice"
+ desc = "A slice of home."
+ icon_state = "breadslice"
+ foodtypes = GRAIN
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 2
+ )
+
+/obj/item/food/breadslice/moldy
+ name = "moldy bread slice"
+ desc = "Entire stations have been ripped apart over arguing whether this is still good to eat."
+ icon_state = "moldybreadslice"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 2,
+ /datum/reagent/consumable/mold = 10,
+ )
+ tastes = list("decaying fungus" = 1)
+ foodtypes = GROSS
+
+/obj/item/food/bread/meat
+ name = "meatbread loaf"
+ desc = "The culinary base of every self-respecting eloquen/tg/entleman."
+ icon_state = "meatbread"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 20,
+ /datum/reagent/consumable/nutriment/vitamin = 10
+ )
+ tastes = list("bread" = 10, "meat" = 10)
+ foodtypes = GRAIN | MEAT
+ slice_type = /obj/item/food/breadslice/meat
+
+/obj/item/food/breadslice/meat
+ name = "meatbread slice"
+ desc = "A slice of delicious meatbread."
+ icon_state = "meatbreadslice"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 4,
+ /datum/reagent/consumable/nutriment/vitamin = 2
+ )
+ tastes = list("bread" = 1, "meat" = 1)
+ foodtypes = GRAIN | MEAT
+
+/obj/item/food/bread/xenomeat
+ name = "xenomeatbread loaf"
+ desc = "Extra Heretical."
+ icon_state = "xenomeatbread"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 20,
+ /datum/reagent/consumable/nutriment/vitamin = 10
+ )
+ tastes = list("bread" = 10, "acid" = 10)
+ foodtypes = GRAIN | MEAT
+ slice_type = /obj/item/food/breadslice/xenomeat
+
+/obj/item/food/breadslice/xenomeat
+ name = "xenomeatbread slice"
+ desc = "A slice of delicious meatbread. Extra Heretical."
+ icon_state = "xenobreadslice"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 4,
+ /datum/reagent/consumable/nutriment/vitamin = 2
+ )
+ tastes = list("bread" = 10, "acid" = 10)
+ foodtypes = GRAIN | MEAT
+
+/obj/item/food/bread/spidermeat
+ name = "spider meat loaf"
+ desc = "Reassuringly green meatloaf made from spider meat."
+ icon_state = "spidermeatbread"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 20,
+ /datum/reagent/toxin = 15,
+ /datum/reagent/consumable/nutriment/vitamin = 10
+ )
+ tastes = list("bread" = 10, "cobwebs" = 5)
+ foodtypes = GRAIN | MEAT | TOXIC
+ slice_type = /obj/item/food/breadslice/spidermeat
+
+/obj/item/food/breadslice/spidermeat
+ name = "spider meat bread slice"
+ desc = "A slice of meatloaf made from an animal that most likely still wants you dead."
+ icon_state = "xenobreadslice"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 3,
+ /datum/reagent/toxin = 3,
+ /datum/reagent/consumable/nutriment/vitamin = 1
+ )
+ tastes = list("bread" = 10, "cobwebs" = 5)
+ foodtypes = GRAIN | MEAT | TOXIC
+
+/obj/item/food/bread/banana
+ name = "banana-nut bread"
+ desc = "A heavenly and filling treat."
+ icon_state = "bananabread"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 20,
+ /datum/reagent/consumable/banana = 20
+ )
+ tastes = list("bread" = 10) // bananjuice will also flavour
+ foodtypes = GRAIN | FRUIT
+ slice_type = /obj/item/food/breadslice/banana
+
+/obj/item/food/breadslice/banana
+ name = "banana-nut bread slice"
+ desc = "A slice of delicious banana bread."
+ icon_state = "bananabreadslice"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 4,
+ /datum/reagent/consumable/banana = 4
+ )
+ tastes = list("bread" = 10)
+ foodtypes = GRAIN | FRUIT
+
+/obj/item/food/bread/tofu
+ name = "Tofubread"
+ desc = "Like meatbread but for vegetarians. Not guaranteed to give superpowers."
+ icon_state = "tofubread"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 20,
+ /datum/reagent/consumable/nutriment/vitamin = 10
+ )
+ tastes = list("bread" = 10, "tofu" = 10)
+ foodtypes = GRAIN | VEGETABLES
+ slice_type = /obj/item/food/breadslice/tofu
+
+/obj/item/food/breadslice/tofu
+ name = "tofubread slice"
+ desc = "A slice of delicious tofubread."
+ icon_state = "tofubreadslice"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 4,
+ /datum/reagent/consumable/nutriment/vitamin = 2
+ )
+ tastes = list("bread" = 10, "tofu" = 10)
+ foodtypes = GRAIN | VEGETABLES
+
+/obj/item/food/bread/creamcheese
+ name = "cream cheese bread"
+ desc = "Just a schmear."
+ icon_state = "creamcheesebread"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 20,
+ /datum/reagent/consumable/nutriment/vitamin = 10
+ )
+ tastes = list("bread" = 10, "cheese" = 10)
+ foodtypes = GRAIN | DAIRY
+ slice_type = /obj/item/food/breadslice/creamcheese
+
+/obj/item/food/breadslice/creamcheese
+ name = "cream cheese bread slice"
+ desc = "A slice of Brotherly love!"
+ icon_state = "creamcheesebreadslice"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 3,
+ /datum/reagent/consumable/nutriment/vitamin = 2
+ )
+ tastes = list("bread" = 10, "cheese" = 10)
+ foodtypes = GRAIN | DAIRY
+
+/obj/item/food/bread/mimana
+ name = "mimana bread"
+ desc = "Best eaten in silence."
+ icon_state = "mimanabread"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 20,
+ /datum/reagent/toxin/mutetoxin = 10,
+ /datum/reagent/consumable/nothing = 10,
+ /datum/reagent/consumable/nutriment/vitamin = 10
+ )
+ tastes = list("bread" = 10, "silence" = 10)
+ foodtypes = GRAIN | FRUIT
+ slice_type = /obj/item/food/breadslice/mimana
+
+/obj/item/food/breadslice/mimana
+ name = "mimana bread slice"
+ desc = "A slice of silence!"
+ icon_state = "mimanabreadslice"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 4,
+ /datum/reagent/toxin/mutetoxin = 2,
+ /datum/reagent/consumable/nothing = 2,
+ /datum/reagent/consumable/nutriment/vitamin = 2
+ )
+ foodtypes = GRAIN | FRUIT
+
+/obj/item/food/breadslice/custom
+ name = "bread slice"
+ icon_state = "tofubreadslice"
+ foodtypes = GRAIN
+
+/obj/item/food/baguette
+ name = "baguette"
+ desc = "Bon appetit!"
+ icon = 'icons/obj/food/burgerbread.dmi'
+ icon_state = "baguette"
+ item_state = null
+ mob_overlay_state = "baguette"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 8,
+ /datum/reagent/consumable/nutriment/vitamin = 3
+ )
+ bite_consumption = 3
+ w_class = WEIGHT_CLASS_NORMAL
+ slot_flags = ITEM_SLOT_BACK|ITEM_SLOT_BELT
+ attack_verb = list("touche")
+ tastes = list("bread" = 1)
+ foodtypes = GRAIN
+
+/obj/item/food/garlicbread
+ name = "garlic bread"
+ desc = "Alas, it is limited."
+ icon = 'icons/obj/food/burgerbread.dmi'
+ icon_state = "garlicbread"
+ item_state = null
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 10,
+ /datum/reagent/consumable/nutriment/vitamin = 6,
+ /datum/reagent/consumable/garlic = 2
+ )
+ bite_consumption = 3
+ tastes = list("bread" = 1, "garlic" = 1, "butter" = 1)
+ foodtypes = GRAIN
+
+/obj/item/food/deepfryholder
+ name = "Deep Fried Foods Holder Obj"
+ desc = "If you can see this description the code for the deep fryer fucked up."
+ icon = 'icons/obj/food/food.dmi'
+ icon_state = ""
+ bite_consumption = 2
+
+/obj/item/food/deepfryholder/make_edible()
+ AddComponent(/datum/component/edible,\
+ initial_reagents = food_reagents,\
+ food_flags = food_flags,\
+ foodtypes = foodtypes,\
+ volume = max_volume,\
+ eat_time = eat_time,\
+ tastes = tastes,\
+ eatverbs = eatverbs,\
+ bite_consumption = bite_consumption,\
+ on_consume = CALLBACK(src, PROC_REF(On_Consume)))
+
+
+/obj/item/food/deepfryholder/Initialize(mapload, obj/item/fried)
+ . = ..()
+ name = fried.name //We'll determine the other stuff when it's actually removed
+ appearance = fried.appearance
+ layer = initial(layer)
+ plane = initial(plane)
+ lefthand_file = fried.lefthand_file
+ righthand_file = fried.righthand_file
+ mob_overlay_state = fried.mob_overlay_state
+ desc = fried.desc
+ w_class = fried.w_class
+ slowdown = fried.slowdown
+ equip_delay_self = fried.equip_delay_self
+ equip_delay_other = fried.equip_delay_other
+ strip_delay = fried.strip_delay
+ species_exception = fried.species_exception
+ item_flags = fried.item_flags
+ obj_flags = fried.obj_flags
+ inhand_x_dimension = fried.inhand_x_dimension
+ inhand_y_dimension = fried.inhand_y_dimension
+
+ if(!(SEND_SIGNAL(fried, COMSIG_ITEM_FRIED, src) & COMSIG_FRYING_HANDLED)) //If frying is handled by signal don't do the defaault behavior.
+ fried.forceMove(src)
+
+
+/obj/item/food/deepfryholder/Destroy()
+ if(contents)
+ QDEL_LIST(contents)
+ return ..()
+
+/obj/item/food/deepfryholder/proc/On_Consume(eater, feeder)
+ if(contents)
+ QDEL_LIST(contents)
+
+
+/obj/item/food/deepfryholder/proc/fry(cook_time = 30)
+ switch(cook_time)
+ if(0 to 15)
+ add_atom_colour(rgb(166,103,54), FIXED_COLOUR_PRIORITY)
+ name = "lightly-fried [name]"
+ desc = "[desc] It's been lightly fried in a deep fryer."
+ if(16 to 49)
+ add_atom_colour(rgb(103,63,24), FIXED_COLOUR_PRIORITY)
+ name = "fried [name]"
+ desc = "[desc] It's been fried, increasing its tastiness value by [rand(1, 75)]%."
+ if(50 to 59)
+ add_atom_colour(rgb(63,23,4), FIXED_COLOUR_PRIORITY)
+ name = "deep-fried [name]"
+ desc = "[desc] Deep-fried to perfection."
+ if(60 to INFINITY)
+ add_atom_colour(rgb(33,19,9), FIXED_COLOUR_PRIORITY)
+ name = "\proper the physical manifestation of the very concept of fried foods"
+ desc = "A heavily-fried... something. Who can tell anymore?"
+ foodtypes |= FRIED
+
+/obj/item/food/butterbiscuit
+ name = "butter biscuit"
+ desc = "Well butter my biscuit!"
+ icon = 'icons/obj/food/food.dmi'
+ icon_state = "butterbiscuit"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 6,
+ /datum/reagent/consumable/nutriment/vitamin = 1
+ )
+ tastes = list("butter" = 1, "biscuit" = 1)
+ foodtypes = GRAIN | BREAKFAST
+ w_class = WEIGHT_CLASS_SMALL
+
+/obj/item/food/butterdog
+ name = "butterdog"
+ desc = "Made from exotic butters."
+ icon = 'icons/obj/food/food.dmi'
+ icon_state = "butterdog"
+ bite_consumption = 1
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 6,
+ /datum/reagent/consumable/nutriment/vitamin = 1
+ )
+ tastes = list("butter" = 1, "exotic butter" = 1)
+ foodtypes = GRAIN | DAIRY
+ w_class = WEIGHT_CLASS_SMALL
+
+/obj/item/food/butterdog/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/slippery, 8 SECONDS)
diff --git a/code/game/objects/items/food/cake.dm b/code/game/objects/items/food/cake.dm
new file mode 100644
index 00000000000..4ab748d9e14
--- /dev/null
+++ b/code/game/objects/items/food/cake.dm
@@ -0,0 +1,538 @@
+/obj/item/food/cake
+ icon = 'icons/obj/food/piecake.dmi'
+ bite_consumption = 3
+ max_volume = 80
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 20,
+ /datum/reagent/consumable/nutriment/vitamin = 5
+ )
+ tastes = list("cake" = 1)
+ foodtypes = GRAIN | DAIRY
+ /// type is spawned 5 at a time and replaces this cake when processed by cutting tool
+ var/obj/item/food/cakeslice/slice_type
+ /// changes yield of sliced cake, default for cake is 5
+ var/yield = 5
+
+/obj/item/food/cake/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/food_storage)
+
+/obj/item/food/cake/make_processable()
+ if (slice_type)
+ AddElement(/datum/element/processable, TOOL_KNIFE, slice_type, yield, 3 SECONDS, table_required = TRUE)
+
+/obj/item/food/cakeslice
+ icon = 'icons/obj/food/piecake.dmi'
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 4,
+ /datum/reagent/consumable/nutriment/vitamin = 1
+ )
+ tastes = list("cake" = 1)
+ foodtypes = GRAIN | DAIRY
+ w_class = WEIGHT_CLASS_SMALL
+
+/obj/item/food/cake/plain
+ name = "plain cake"
+ desc = "A plain cake, not a lie." //Many of the cakes seem to follow this desc scheme, so I am going to try and put either a hint about its contents, or a fun fact. Lets try to follow this.
+ icon_state = "plaincake"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 30,
+ /datum/reagent/consumable/nutriment/vitamin = 7
+ )
+ tastes = list("sweetness" = 2, "cake" = 5)
+ foodtypes = GRAIN | DAIRY | SUGAR
+ slice_type = /obj/item/food/cakeslice/plain
+
+/obj/item/food/cakeslice/plain
+ name = "plain cake slice"
+ desc = "Just a slice of cake, it is enough for everyone."
+ icon_state = "plaincake_slice"
+ tastes = list("sweetness" = 2,"cake" = 5)
+ foodtypes = GRAIN | DAIRY | SUGAR
+
+/obj/item/food/cake/carrot
+ name = "carrot cake"
+ desc = "Scientifically proven to improve eyesight! Not a lie."
+ icon_state = "carrotcake"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 20,
+ /datum/reagent/medicine/oculine = 10,
+ /datum/reagent/consumable/nutriment/vitamin = 5
+ )
+ tastes = list("cake" = 5, "sweetness" = 2, "carrot" = 1)
+ foodtypes = GRAIN | DAIRY | VEGETABLES | SUGAR
+ slice_type = /obj/item/food/cakeslice/carrot
+
+/obj/item/food/cakeslice/carrot
+ name = "carrot cake slice"
+ desc = "Carrotty slice of Carrot Cake, carrots are good for your eyes! Also not a lie."
+ icon_state = "carrotcake_slice"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 4,
+ /datum/reagent/medicine/oculine = 2,
+ /datum/reagent/consumable/nutriment/vitamin = 1
+ )
+ tastes = list("cake" = 5, "sweetness" = 2, "carrot" = 1)
+ foodtypes = GRAIN | DAIRY | VEGETABLES | SUGAR
+
+/obj/item/food/cake/brain
+ name = "brain cake"
+ desc = "Yeah... its actually made out of brain. I wish it were a lie."
+ icon_state = "braincake"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 15,
+ /datum/reagent/medicine/mannitol = 10,
+ /datum/reagent/consumable/nutriment/vitamin = 5
+ )
+ tastes = list("cake" = 5, "sweetness" = 2, "brains" = 1)
+ foodtypes = GRAIN | DAIRY | MEAT | GROSS | SUGAR
+ slice_type = /obj/item/food/cakeslice/brain
+
+/obj/item/food/cakeslice/brain
+ name = "brain cake slice"
+ desc = "Lemme tell you something about prions. THEY'RE DELICIOUS. A terrifying not-lie."
+ icon_state = "braincakeslice"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 4,
+ /datum/reagent/medicine/mannitol = 2,
+ /datum/reagent/consumable/nutriment/vitamin = 1
+ )
+ tastes = list("cake" = 5, "sweetness" = 2, "brains" = 1)
+ foodtypes = GRAIN | DAIRY | MEAT | GROSS | SUGAR
+
+/obj/item/food/cake/cheese
+ name = "cheese cake"
+ desc = "DANGEROUSLY cheesy."
+ icon_state = "cheesecake"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 20,
+ /datum/reagent/consumable/nutriment/vitamin = 8
+ )
+ tastes = list("cake" = 4, "cream cheese" = 3)
+ foodtypes = GRAIN | DAIRY
+ slice_type = /obj/item/food/cakeslice/cheese
+
+/obj/item/food/cakeslice/cheese
+ name = "cheese cake slice"
+ desc = "Slice of pure cheestisfaction."
+ icon_state = "cheesecake_slice"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 4,
+ /datum/reagent/consumable/nutriment/vitamin = 1.3
+ )
+ tastes = list("cake" = 4, "cream cheese" = 3)
+ foodtypes = GRAIN | DAIRY
+
+/obj/item/food/cake/orange
+ name = "orange cake"
+ desc = "A cake with added orange."
+ icon_state = "orangecake"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 20,
+ /datum/reagent/consumable/nutriment/vitamin = 10
+ )
+ tastes = list("cake" = 5, "sweetness" = 2, "oranges" = 2)
+ foodtypes = GRAIN | DAIRY | FRUIT | SUGAR
+ slice_type = /obj/item/food/cakeslice/orange
+
+/obj/item/food/cakeslice/orange
+ name = "orange cake slice"
+ desc = "Just a slice of cake, it is enough for everyone."
+ icon_state = "orangecake_slice"
+ tastes = list("cake" = 5, "sweetness" = 2, "oranges" = 2)
+ foodtypes = GRAIN | DAIRY | FRUIT | SUGAR
+
+/obj/item/food/cake/lime
+ name = "lime cake"
+ desc = "A cake with added lime."
+ icon_state = "limecake"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 20,
+ /datum/reagent/consumable/nutriment/vitamin = 10
+ )
+ tastes = list("cake" = 5, "sweetness" = 2, "unbearable sourness" = 2)
+ foodtypes = GRAIN | DAIRY | FRUIT | SUGAR
+ slice_type = /obj/item/food/cakeslice/lime
+
+/obj/item/food/cakeslice/lime
+ name = "lime cake slice"
+ desc = "Just a slice of cake, it is enough for everyone."
+ icon_state = "limecake_slice"
+ tastes = list("cake" = 5, "sweetness" = 2, "unbearable sourness" = 2)
+ foodtypes = GRAIN | DAIRY | FRUIT | SUGAR
+
+/obj/item/food/cake/lemon
+ name = "lemon cake"
+ desc = "A cake with added lemon."
+ icon_state = "lemoncake"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 20,
+ /datum/reagent/consumable/nutriment/vitamin = 10
+ )
+ tastes = list("cake" = 5, "sweetness" = 3, "sourness" = 1) //lemon cake is never as sour as it is sweet, have you ever actually eaten it?
+ foodtypes = GRAIN | DAIRY | FRUIT | SUGAR
+ slice_type = /obj/item/food/cakeslice/lemon
+
+/obj/item/food/cakeslice/lemon
+ name = "lemon cake slice"
+ desc = "Just a slice of cake, it is enough for everyone."
+ icon_state = "lemoncake_slice"
+ tastes = list("cake" = 5, "sweetness" = 2, "sourness" = 2)
+ foodtypes = GRAIN | DAIRY | FRUIT | SUGAR
+
+/obj/item/food/cake/chocolate
+ name = "chocolate cake"
+ desc = "A cake with added chocolate."
+ icon_state = "chocolatecake"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 20,
+ /datum/reagent/consumable/nutriment/vitamin = 10
+ )
+ tastes = list("cake" = 5, "sweetness" = 1, "chocolate" = 4)
+ foodtypes = GRAIN | DAIRY | JUNKFOOD | SUGAR
+ slice_type = /obj/item/food/cakeslice/chocolate
+
+/obj/item/food/cakeslice/chocolate
+ name = "chocolate cake slice"
+ desc = "Just a slice of cake, it is enough for everyone."
+ icon_state = "chocolatecake_slice"
+ tastes = list("cake" = 5, "sweetness" = 1, "chocolate" = 4)
+ foodtypes = GRAIN | DAIRY | JUNKFOOD | SUGAR
+
+/obj/item/food/cake/birthday
+ name = "birthday cake"
+ desc = "Happy Birthday little clown..."
+ icon_state = "birthdaycake"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 20,
+ /datum/reagent/consumable/sprinkles = 10,
+ /datum/reagent/consumable/nutriment/vitamin = 5
+ )
+ tastes = list("cake" = 5, "sweetness" = 1)
+ foodtypes = GRAIN | DAIRY | JUNKFOOD | SUGAR
+ slice_type = /obj/item/food/cakeslice/birthday
+
+/obj/item/food/cake/birthday/microwave_act(obj/machinery/microwave/M) //super sekrit club
+ new /obj/item/clothing/head/hardhat/cakehat(get_turf(src))
+ qdel(src)
+
+/obj/item/food/cakeslice/birthday
+ name = "birthday cake slice"
+ desc = "A slice of your birthday."
+ icon_state = "birthdaycakeslice"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 4,
+ /datum/reagent/consumable/sprinkles = 2,
+ /datum/reagent/consumable/nutriment/vitamin = 1
+ )
+ tastes = list("cake" = 5, "sweetness" = 1)
+ foodtypes = GRAIN | DAIRY | JUNKFOOD | SUGAR
+
+/obj/item/food/cake/birthday/energy
+ name = "energy cake"
+ desc = "Just enough calories for a whole nuclear operative squad."
+ icon_state = "energycake"
+ force = 5
+ hitsound = 'sound/weapons/blade1.ogg'
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 10,
+ /datum/reagent/consumable/sprinkles = 10,
+ /datum/reagent/consumable/nutriment/vitamin = 5,
+ /datum/reagent/consumable/monkey_energy = 10,
+ /datum/reagent/consumable/liquidelectricity = 10
+ )
+ tastes = list("cake" = 3, "a Vlad's Salad" = 1)
+ slice_type = /obj/item/food/cakeslice/birthday/energy
+
+/obj/item/food/cake/birthday/energy/microwave_act(obj/machinery/microwave/M) //super sekriter club
+ new /obj/item/clothing/head/hardhat/cakehat/energycake(get_turf(src))
+ qdel(src)
+
+/obj/item/food/cake/birthday/energy/proc/energy_bite(mob/living/user)
+ to_chat(user, "As you eat the cake, you accidentally hurt yourself on the embedded energy sword!")
+ user.apply_damage(30, BURN, BODY_ZONE_HEAD) // ITs an ENERGY sword, so it burns, duh
+ playsound(user, 'sound/weapons/blade1.ogg', 5, TRUE)
+
+/obj/item/food/cake/birthday/energy/attack(mob/living/target_mob, mob/living/user)
+ . = ..()
+ if(HAS_TRAIT(user, TRAIT_PACIFISM) && target_mob != user) //Prevents pacifists from attacking others directly
+ return
+ energy_bite(target_mob, user)
+
+/obj/item/food/cakeslice/birthday/energy
+ name = "energy cake slice"
+ desc = "For the traitor on the go."
+ icon_state = "energycakeslice"
+ force = 2
+ hitsound = 'sound/weapons/blade1.ogg'
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 4,
+ /datum/reagent/consumable/sprinkles = 2,
+ /datum/reagent/consumable/nutriment/vitamin = 1,
+ /datum/reagent/consumable/monkey_energy = 2,
+ /datum/reagent/consumable/liquidelectricity = 2
+ )
+ tastes = list("cake" = 3, "a Vlad's Salad" = 1)
+
+/obj/item/food/cakeslice/birthday/energy/proc/energy_bite(mob/living/user)
+ to_chat(user, "As you eat the cake slice, you accidentally hurt yourself on the embedded energy dagger!")
+ user.apply_damage(18, BURN, BODY_ZONE_HEAD)
+ playsound(user, 'sound/weapons/blade1.ogg', 5, TRUE)
+
+/obj/item/food/cakeslice/birthday/energy/attack(mob/living/target_mob, mob/living/user)
+ . = ..()
+ if(HAS_TRAIT(user, TRAIT_PACIFISM) && target_mob != user) //Prevents pacifists from attacking others directly
+ return
+ energy_bite(target_mob, user)
+
+/obj/item/food/cake/apple
+ name = "apple cake"
+ desc = "A cake centred with Apple."
+ icon_state = "applecake"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 20,
+ /datum/reagent/consumable/nutriment/vitamin = 10
+ )
+ tastes = list("cake" = 5, "sweetness" = 1, "apple" = 1)
+ foodtypes = GRAIN | DAIRY | FRUIT | SUGAR
+ slice_type = /obj/item/food/cakeslice/apple
+
+/obj/item/food/cakeslice/apple
+ name = "apple cake slice"
+ desc = "A slice of heavenly cake."
+ icon_state = "applecakeslice"
+ tastes = list("cake" = 5, "sweetness" = 1, "apple" = 1)
+ foodtypes = GRAIN | DAIRY | FRUIT | SUGAR
+
+/obj/item/food/cake/slimecake
+ name = "Slime cake"
+ desc = "A cake made of slimes. Probably not electrified."
+ icon_state = "slimecake"
+ tastes = list("cake" = 5, "sweetness" = 1, "slime" = 1)
+ foodtypes = GRAIN | DAIRY | SUGAR
+ slice_type = /obj/item/food/cakeslice/slimecake
+
+/obj/item/food/cakeslice/slimecake
+ name = "slime cake slice"
+ desc = "A slice of slime cake."
+ icon_state = "slimecake_slice"
+ tastes = list("cake" = 5, "sweetness" = 1, "slime" = 1)
+ foodtypes = GRAIN | DAIRY | SUGAR
+
+/obj/item/food/cake/pumpkinspice
+ name = "pumpkin spice cake"
+ desc = "A hollow cake with real pumpkin."
+ icon_state = "pumpkinspicecake"
+ tastes = list("cake" = 5, "sweetness" = 1, "pumpkin" = 1)
+ foodtypes = GRAIN | DAIRY | VEGETABLES | SUGAR
+ slice_type = /obj/item/food/cakeslice/pumpkinspice
+
+/obj/item/food/cakeslice/pumpkinspice
+ name = "pumpkin spice cake slice"
+ desc = "A spicy slice of pumpkin goodness."
+ icon_state = "pumpkinspicecakeslice"
+ tastes = list("cake" = 5, "sweetness" = 1, "pumpkin" = 1)
+ foodtypes = GRAIN | DAIRY | VEGETABLES | SUGAR
+
+/obj/item/food/cake/bsvc // blackberry strawberries vanilla cake
+ name = "blackberry and strawberry vanilla cake"
+ desc = "A plain cake, filled with assortment of blackberries and strawberries!"
+ icon_state = "blackbarry_strawberries_cake_vanilla_cake"
+ tastes = list("blackberry" = 2, "strawberries" = 2, "vanilla" = 2, "sweetness" = 2, "cake" = 3)
+ foodtypes = GRAIN | DAIRY | FRUIT | SUGAR
+ slice_type = /obj/item/food/cakeslice/bsvc
+
+/obj/item/food/cakeslice/bsvc
+ name = "blackberry and strawberry vanilla cake slice"
+ desc = "Just a slice of cake filled with assortment of blackberries and strawberries!"
+ icon_state = "blackbarry_strawberries_cake_vanilla_slice"
+ tastes = list("blackberry" = 2, "strawberries" = 2, "vanilla" = 2, "sweetness" = 2,"cake" = 3)
+ foodtypes = GRAIN | DAIRY | FRUIT | SUGAR
+
+/obj/item/food/cake/bscc // blackbarry strawberries chocolate cake
+ name = "blackberry and strawberry chocolate cake"
+ desc = "A chocolate cake, filled with assortment of blackberries and strawberries!"
+ icon_state = "blackbarry_strawberries_cake_coco_cake"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 20,
+ /datum/reagent/consumable/nutriment/vitamin = 5,
+ /datum/reagent/consumable/coco = 5
+ )
+ tastes = list("blackberry" = 2, "strawberries" = 2, "chocolate" = 4, "sweetness" = 2,"cake" = 3)
+ foodtypes = GRAIN | DAIRY | FRUIT | SUGAR
+ slice_type = /obj/item/food/cakeslice/bscc
+
+/obj/item/food/cakeslice/bscc
+ name = "blackberry and strawberry chocolate cake slice"
+ desc = "Just a slice of cake filled with assortment of blackberries and strawberries!"
+ icon_state = "blackbarry_strawberries_cake_coco_slice"
+ tastes = list("blackberry" = 2, "strawberries" = 2, "chocolate" = 4, "sweetness" = 2,"cake" = 3)
+ foodtypes = GRAIN | DAIRY | FRUIT | SUGAR
+
+/obj/item/food/cake/holy_cake
+ name = "angel food cake"
+ desc = "A cake made for angels and chaplains alike! Contains holy water."
+ icon_state = "holy_cake"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 1,
+ /datum/reagent/consumable/nutriment/vitamin = 3,
+ /datum/reagent/water/holywater = 10
+ )
+ tastes = list("cake" = 5, "sweetness" = 1, "clouds" = 1)
+ foodtypes = GRAIN | DAIRY | SUGAR
+ slice_type = /obj/item/food/cakeslice/holy_cake_slice
+
+/obj/item/food/cakeslice/holy_cake_slice
+ name = "angel food cake slice"
+ desc = "A slice of heavenly cake."
+ icon_state = "holy_cake_slice"
+ tastes = list("cake" = 5, "sweetness" = 1, "clouds" = 1)
+ foodtypes = GRAIN | DAIRY | SUGAR
+
+/obj/item/food/cake/pound_cake
+ name = "pound cake"
+ desc = "A condensed cake made for filling people up quickly."
+ icon_state = "pound_cake"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 60,
+ /datum/reagent/consumable/nutriment/vitamin = 5
+ )
+ tastes = list("cake" = 5, "sweetness" = 1, "batter" = 1)
+ foodtypes = GRAIN | DAIRY | SUGAR | JUNKFOOD
+ slice_type = /obj/item/food/cakeslice/pound_cake_slice
+ yield = 10 //cause its so damn THICC (seriously these things are fucking huge a pound of each ingredient are you kidding)
+
+/obj/item/food/cakeslice/pound_cake_slice
+ name = "pound cake slice"
+ desc = "A slice of condensed cake made for filling people up quickly."
+ icon_state = "pound_cake_slice"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 9,
+ /datum/reagent/consumable/nutriment/vitamin = 0.5
+ )
+ tastes = list("cake" = 5, "sweetness" = 5, "batter" = 1)
+ foodtypes = GRAIN | DAIRY | SUGAR | JUNKFOOD
+
+/obj/item/food/cake/hardware_cake
+ name = "hardware cake"
+ desc = "A quote on quote cake that is made with electronic boards and leaks acid..."
+ icon_state = "hardware_cake"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 20,
+ /datum/reagent/consumable/nutriment/vitamin = 5,
+ /datum/reagent/toxin/acid = 15,
+ /datum/reagent/fuel/oil = 15
+ )
+ tastes = list("acid" = 3, "metal" = 4, "glass" = 5)
+ foodtypes = GRAIN | GROSS
+ slice_type = /obj/item/food/cakeslice/hardware_cake_slice
+
+/obj/item/food/cakeslice/hardware_cake_slice
+ name = "hardware cake slice"
+ desc = "A slice of electronic boards and some acid."
+ icon_state = "hardware_cake_slice"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 4,
+ /datum/reagent/consumable/nutriment/vitamin = 1,
+ /datum/reagent/toxin/acid = 3,
+ /datum/reagent/fuel/oil = 3
+ )
+ tastes = list("acid" = 3, "metal" = 4, "glass" = 5)
+ foodtypes = GRAIN | GROSS
+
+/obj/item/food/cake/vanilla_cake
+ name = "vanilla cake"
+ desc = "A vanilla frosted cake."
+ icon_state = "vanillacake"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 20,
+ /datum/reagent/consumable/nutriment/vitamin = 5,
+ /datum/reagent/consumable/sugar = 15,
+ /datum/reagent/consumable/vanilla = 15
+ )
+ tastes = list("cake" = 1, "sugar" = 1, "vanilla" = 10)
+ foodtypes = GRAIN | SUGAR | DAIRY
+ slice_type = /obj/item/food/cakeslice/vanilla_slice
+
+/obj/item/food/cakeslice/vanilla_slice
+ name = "vanilla cake slice"
+ desc = "A slice of vanilla frosted cake."
+ icon_state = "vanillacake_slice"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 4,
+ /datum/reagent/consumable/nutriment/vitamin = 1,
+ /datum/reagent/consumable/sugar = 3,
+ /datum/reagent/consumable/vanilla = 3
+ )
+ tastes = list("cake" = 1, "sugar" = 1, "vanilla" = 10)
+ foodtypes = GRAIN | SUGAR | DAIRY
+
+/obj/item/food/cake/clown_cake
+ name = "clown cake"
+ desc = "A funny cake with a clown face on it."
+ icon_state = "clowncake"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 20,
+ /datum/reagent/consumable/nutriment/vitamin = 5,
+ /datum/reagent/consumable/banana = 15
+ )
+ tastes = list("cake" = 1, "sugar" = 1, "joy" = 10)
+ foodtypes = GRAIN | SUGAR | DAIRY
+ slice_type = /obj/item/food/cakeslice/clown_slice
+
+/obj/item/food/cakeslice/clown_slice
+ name = "clown cake slice"
+ desc = "A slice of bad jokes, and silly props."
+ icon_state = "clowncake_slice"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 4,
+ /datum/reagent/consumable/nutriment/vitamin = 1,
+ /datum/reagent/consumable/banana = 3
+ )
+ tastes = list("cake" = 1, "sugar" = 1, "joy" = 10)
+ foodtypes = GRAIN | SUGAR | DAIRY
+
+/obj/item/food/cake/trumpet
+ name = "spaceman's cake"
+ desc = "A spaceman's trumpet frosted cake."
+ icon_state = "trumpetcake"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 20,
+ /datum/reagent/consumable/nutriment/vitamin = 5,
+ /datum/reagent/medicine/polypyr = 15,
+ /datum/reagent/consumable/cream = 5,
+ /datum/reagent/consumable/nutriment/vitamin = 5,
+ /datum/reagent/consumable/berryjuice = 5
+ )
+ tastes = list("cake" = 4, "violets" = 2, "jam" = 2)
+ foodtypes = GRAIN | DAIRY | FRUIT | SUGAR
+ slice_type = /obj/item/food/cakeslice/trumpet
+
+/obj/item/food/cakeslice/trumpet
+ name = "spaceman's cake"
+ desc = "A spaceman's trumpet frosted cake."
+ icon_state = "trumpetcakeslice"
+ food_reagents = list(
+ /datum/reagent/consumable/nutriment = 4,
+ /datum/reagent/consumable/nutriment/vitamin = 1,
+ /datum/reagent/medicine/polypyr = 3,
+ /datum/reagent/consumable/cream = 1,
+ /datum/reagent/consumable/nutriment/vitamin = 1,
+ /datum/reagent/consumable/berryjuice = 1
+ )
+ tastes = list("cake" = 4, "violets" = 2, "jam" = 2)
+ foodtypes = GRAIN | DAIRY | FRUIT | SUGAR
+
+/obj/item/food/cake/brioche
+ name = "brioche cake"
+ desc = "A ring of sweet, glazed buns."
+ icon_state = "briochecake"
+ tastes = list("cake" = 4, "butter" = 2, "cream" = 1)
+ foodtypes = GRAIN | DAIRY | SUGAR
+ slice_type = /obj/item/food/cakeslice/brioche
+ yield = 6
+
+/obj/item/food/cakeslice/brioche
+ name = "brioche cake slice"
+ desc = "Delicious sweet-bread. Who needs anything else?"
+ icon_state = "briochecake_slice"
diff --git a/code/game/objects/items/food/spaghetti.dm b/code/game/objects/items/food/spaghetti.dm
new file mode 100644
index 00000000000..a6c5394e064
--- /dev/null
+++ b/code/game/objects/items/food/spaghetti.dm
@@ -0,0 +1,98 @@
+
+/obj/item/food/spaghetti
+ icon = 'icons/obj/food/pizzaspaghetti.dmi'
+ food_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/nutriment/vitamin = 1)
+ foodtypes = GRAIN
+
+/obj/item/food/spaghetti/Initialize()
+ . = ..()
+ if(!microwaved_type) // This isn't cooked, why would you put uncooked spaghetti in your pocket?
+ var/list/display_message = list(
+ "Something wet falls out of their pocket and hits the ground. Is that... [name]?",
+ "Oh shit! All your pocket [name] fell out!")
+ AddComponent(/datum/component/spill, display_message, 'sound/effects/splat.ogg')
+
+/obj/item/food/spaghetti/raw
+ name = "spaghetti"
+ desc = "Now that's a nic'e pasta!"
+ icon_state = "spaghetti"
+ tastes = list("pasta" = 1)
+ microwaved_type = /obj/item/food/spaghetti/boiledspaghetti
+
+/obj/item/food/spaghetti/boiledspaghetti
+ name = "boiled spaghetti"
+ desc = "A plain dish of noodles, this needs more ingredients."
+ icon_state = "spaghettiboiled"
+ trash_type = /obj/item/trash/plate
+ food_reagents = list(/datum/reagent/consumable/nutriment = 2, /datum/reagent/consumable/nutriment/vitamin = 1)
+ microwaved_type = null
+
+/obj/item/food/spaghetti/pastatomato
+ name = "spaghetti"
+ desc = "Spaghetti and crushed tomatoes. Just like your abusive father used to make!"
+ icon_state = "pastatomato"
+ trash_type = /obj/item/trash/plate
+ bite_consumption = 4
+ food_reagents = list(/datum/reagent/consumable/nutriment = 6, /datum/reagent/consumable/tomatojuice = 10, /datum/reagent/consumable/nutriment/vitamin = 4)
+ microwaved_type = null
+ tastes = list("pasta" = 1, "tomato" = 1)
+ foodtypes = GRAIN | VEGETABLES
+
+/obj/item/food/spaghetti/copypasta
+ name = "copypasta"
+ desc = "You probably shouldn't try this, you always hear people talking about how bad it is..."
+ icon_state = "copypasta"
+ trash_type = /obj/item/trash/plate
+ bite_consumption = 4
+ food_reagents = list(/datum/reagent/consumable/nutriment = 12, /datum/reagent/consumable/tomatojuice = 20, /datum/reagent/consumable/nutriment/vitamin = 8)
+ microwaved_type = null
+ tastes = list("pasta" = 1, "tomato" = 1)
+ foodtypes = GRAIN | VEGETABLES
+
+/obj/item/food/spaghetti/meatballspaghetti
+ name = "spaghetti and meatballs"
+ desc = "Now that's a nic'e meatball!"
+ icon_state = "meatballspaghetti"
+ trash_type = /obj/item/trash/plate
+ food_reagents = list(/datum/reagent/consumable/nutriment = 4, /datum/reagent/consumable/nutriment/vitamin = 2)
+ microwaved_type = null
+ tastes = list("pasta" = 1, "meat" = 1)
+ foodtypes = GRAIN | MEAT
+
+/obj/item/food/spaghetti/spesslaw
+ name = "spesslaw"
+ desc = "A lawyers favourite."
+ icon_state = "spesslaw"
+ trash_type = /obj/item/trash/plate
+ food_reagents = list(/datum/reagent/consumable/nutriment = 4, /datum/reagent/consumable/nutriment/vitamin = 3)
+ microwaved_type = null
+ tastes = list("pasta" = 1, "meat" = 1)
+
+/obj/item/food/spaghetti/chowmein
+ name = "chow mein"
+ desc = "A nice mix of noodles and fried vegetables."
+ icon_state = "chowmein"
+ trash_type = /obj/item/trash/plate
+ food_reagents = list(/datum/reagent/consumable/nutriment = 6, /datum/reagent/consumable/nutriment/vitamin = 6)
+ microwaved_type = null
+ tastes = list("noodle" = 1, "tomato" = 1)
+
+/obj/item/food/spaghetti/beefnoodle
+ name = "beef noodle"
+ desc = "Nutritious, beefy and noodly."
+ icon_state = "beefnoodle"
+ trash_type = /obj/item/reagent_containers/glass/bowl
+ food_reagents = list(/datum/reagent/consumable/nutriment = 4, /datum/reagent/consumable/nutriment/vitamin = 6, /datum/reagent/liquidgibs = 3)
+ microwaved_type = null
+ tastes = list("noodle" = 1, "meat" = 1)
+ foodtypes = GRAIN | MEAT
+
+/obj/item/food/spaghetti/butternoodles
+ name = "butter noodles"
+ desc = "Noodles covered in savory butter. Simple and slippery, but delicious."
+ icon_state = "butternoodles"
+ trash_type = /obj/item/trash/plate
+ food_reagents = list(/datum/reagent/consumable/nutriment = 9, /datum/reagent/consumable/nutriment/vitamin = 2)
+ microwaved_type = null
+ tastes = list("noodle" = 1, "butter" = 1)
+ foodtypes = GRAIN | DAIRY
diff --git a/code/game/objects/items/grenades/chem_grenade.dm b/code/game/objects/items/grenades/chem_grenade.dm
index b675a001215..e8b7e0de487 100644
--- a/code/game/objects/items/grenades/chem_grenade.dm
+++ b/code/game/objects/items/grenades/chem_grenade.dm
@@ -207,7 +207,7 @@
desc = "A custom made large grenade. Larger splash range and increased ignition temperature compared to basic grenades. Fits exotic and bluespace based containers."
casedesc = "This casing affects a larger area than the basic model and can fit exotic containers, including slime cores and bluespace beakers. Heats contents by 25°K upon ignition."
icon_state = "large_grenade"
- allowed_containers = list(/obj/item/reagent_containers/glass, /obj/item/reagent_containers/food/condiment, /obj/item/reagent_containers/food/drinks)
+ allowed_containers = list(/obj/item/reagent_containers/glass, /obj/item/reagent_containers/condiment, /obj/item/reagent_containers/food/drinks)
banned_containers = list()
affected_area = 5
ignition_temp = 25 // Large grenades are slightly more effective at setting off heat-sensitive mixtures than smaller grenades.
diff --git a/code/game/objects/items/holy_weapons.dm b/code/game/objects/items/holy_weapons.dm
index 872d81323cf..291e0106710 100644
--- a/code/game/objects/items/holy_weapons.dm
+++ b/code/game/objects/items/holy_weapons.dm
@@ -290,7 +290,7 @@
var/shield_icon = "shield-red"
/obj/item/nullrod/staff/worn_overlays(isinhands)
- . = list()
+ . = ..()
if(isinhands)
. += mutable_appearance('icons/effects/effects.dmi', shield_icon, MOB_LAYER + 0.01)
diff --git a/code/game/objects/items/kitchen.dm b/code/game/objects/items/kitchen.dm
index 9e90329404f..4186e500ab9 100644
--- a/code/game/objects/items/kitchen.dm
+++ b/code/game/objects/items/kitchen.dm
@@ -88,6 +88,7 @@
item_flags = EYE_STAB
var/bayonet = FALSE //Can this be attached to a gun?
custom_price = 250
+ tool_behaviour = TOOL_KNIFE
/obj/item/kitchen/knife/ComponentInitialize()
. = ..()
diff --git a/code/game/objects/items/robot/robot_items.dm b/code/game/objects/items/robot/robot_items.dm
index 232951f0652..325cc161735 100644
--- a/code/game/objects/items/robot/robot_items.dm
+++ b/code/game/objects/items/robot/robot_items.dm
@@ -886,7 +886,7 @@
desc = "A special apparatus for carrying drinks without spilling the contents. Alt-Z or right-click to drop the beaker."
icon_state = "borg_beaker_apparatus"
storable = list(/obj/item/reagent_containers/food/drinks/,
- /obj/item/reagent_containers/food/condiment)
+ /obj/item/reagent_containers/condiment)
/obj/item/borg/apparatus/beaker/service/Initialize()
. = ..()
diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm
index ab65bf5a603..ea63b048d75 100644
--- a/code/game/objects/items/storage/belt.dm
+++ b/code/game/objects/items/storage/belt.dm
@@ -520,7 +520,7 @@
/obj/item/reagent_containers/food/snacks/cheesynachos,
/obj/item/reagent_containers/food/snacks/cubannachos,
/obj/item/reagent_containers/food/snacks/nugget,
- /obj/item/reagent_containers/food/snacks/spaghetti/pastatomato,
+ /obj/item/food/spaghetti/pastatomato,
/obj/item/reagent_containers/food/snacks/rofflewaffles,
/obj/item/reagent_containers/food/snacks/donkpocket,
/obj/item/reagent_containers/food/drinks/soda_cans/cola,
diff --git a/code/game/objects/items/storage/boxes.dm b/code/game/objects/items/storage/boxes.dm
index 8aa7b1d9c47..57b574e39b7 100644
--- a/code/game/objects/items/storage/boxes.dm
+++ b/code/game/objects/items/storage/boxes.dm
@@ -455,7 +455,7 @@
/obj/item/storage/box/condimentbottles/PopulateContents()
for(var/i in 1 to 6)
- new /obj/item/reagent_containers/food/condiment(src)
+ new /obj/item/reagent_containers/condiment(src)
/obj/item/storage/box/cups
name = "box of paper cups"
diff --git a/code/game/objects/items/storage/ration.dm b/code/game/objects/items/storage/ration.dm
index b016cc33926..91a04df9989 100644
--- a/code/game/objects/items/storage/ration.dm
+++ b/code/game/objects/items/storage/ration.dm
@@ -28,7 +28,9 @@
. = ..()
var/datum/component/storage/STR = GetComponent(/datum/component/storage)
STR.max_items = 7
- STR.set_holdable(list(/obj/item/reagent_containers/food))
+ STR.set_holdable(list(
+ /obj/item/reagent_containers/food,
+ /obj/item/ration_heater))
STR.locked = TRUE
STR.locked_flavor = "sealed closed"
diff --git a/code/game/objects/items/tanks/watertank.dm b/code/game/objects/items/tanks/watertank.dm
index 4095d159ea8..50f709dcd65 100644
--- a/code/game/objects/items/tanks/watertank.dm
+++ b/code/game/objects/items/tanks/watertank.dm
@@ -377,7 +377,7 @@
//Todo : cache these.
/obj/item/reagent_containers/chemtank/worn_overlays(isinhands = FALSE) //apply chemcolor and level
- . = list()
+ . = ..()
//inhands + reagent_filling
if(!isinhands && reagents.total_volume)
var/mutable_appearance/filling = mutable_appearance('icons/obj/reagentfillings.dmi', "backpackmob-10")
diff --git a/code/game/objects/items/tools/screwdriver.dm b/code/game/objects/items/tools/screwdriver.dm
index 3e4ab0d15de..7c35ddd67d6 100644
--- a/code/game/objects/items/tools/screwdriver.dm
+++ b/code/game/objects/items/tools/screwdriver.dm
@@ -53,7 +53,7 @@
. += base_overlay
/obj/item/screwdriver/worn_overlays(isinhands = FALSE, icon_file)
- . = list()
+ . = ..()
if(isinhands && random_color)
var/mutable_appearance/M = mutable_appearance(icon_file, "screwdriver_head")
M.appearance_flags = RESET_COLOR
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm b/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm
index 0e7ab6e0a52..2bdc4f762dc 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm
@@ -33,9 +33,9 @@
/obj/structure/closet/secure_closet/freezer/kitchen/PopulateContents()
..()
for(var/i = 0, i < 3, i++)
- new /obj/item/reagent_containers/food/condiment/flour(src)
- new /obj/item/reagent_containers/food/condiment/rice(src)
- new /obj/item/reagent_containers/food/condiment/sugar(src)
+ new /obj/item/reagent_containers/condiment/flour(src)
+ new /obj/item/reagent_containers/condiment/rice(src)
+ new /obj/item/reagent_containers/condiment/sugar(src)
/obj/structure/closet/secure_closet/freezer/kitchen/maintenance
name = "maintenance refrigerator"
@@ -45,9 +45,9 @@
/obj/structure/closet/secure_closet/freezer/kitchen/maintenance/PopulateContents()
..()
for(var/i = 0, i < 5, i++)
- new /obj/item/reagent_containers/food/condiment/milk(src)
+ new /obj/item/reagent_containers/condiment/milk(src)
for(var/i = 0, i < 5, i++)
- new /obj/item/reagent_containers/food/condiment/soymilk(src)
+ new /obj/item/reagent_containers/condiment/soymilk(src)
for(var/i = 0, i < 2, i++)
new /obj/item/storage/fancy/egg_box(src)
@@ -82,9 +82,9 @@
/obj/structure/closet/secure_closet/freezer/fridge/PopulateContents()
..()
for(var/i = 0, i < 5, i++)
- new /obj/item/reagent_containers/food/condiment/milk(src)
+ new /obj/item/reagent_containers/condiment/milk(src)
for(var/i = 0, i < 5, i++)
- new /obj/item/reagent_containers/food/condiment/soymilk(src)
+ new /obj/item/reagent_containers/condiment/soymilk(src)
for(var/i = 0, i < 2, i++)
new /obj/item/storage/fancy/egg_box(src)
diff --git a/code/game/objects/structures/icemoon/cave_entrance.dm b/code/game/objects/structures/icemoon/cave_entrance.dm
index e1caeba7b94..5ec49110a63 100644
--- a/code/game/objects/structures/icemoon/cave_entrance.dm
+++ b/code/game/objects/structures/icemoon/cave_entrance.dm
@@ -867,11 +867,11 @@ GLOBAL_LIST_INIT(ore_probability, list(
new /obj/item/clothing/gloves/butchering(loc)
new /mob/living/simple_animal/hostile/killertomato(loc)
if(prob(45))
- new /obj/item/reagent_containers/food/snacks/store/bread/meat(loc)
- new /obj/item/reagent_containers/food/snacks/store/bread/meat(loc)
- new /obj/item/reagent_containers/food/snacks/store/bread/meat(loc)
+ new /obj/item/food/bread/meat(loc)
+ new /obj/item/food/bread/meat(loc)
+ new /obj/item/food/bread/meat(loc)
if(prob(55))
- new /obj/item/reagent_containers/food/snacks/store/cake/trumpet(loc)
+ new /obj/item/food/cake/trumpet(loc)
if(prob(35))
new /obj/item/reagent_containers/food/snacks/pizza/dank(loc)
new /mob/living/simple_animal/hostile/killertomato(loc)
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index 2e4b1c60acd..45d7fae1a2a 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -1236,9 +1236,9 @@
//milk to plasmemes and skeletons, meat to lizards, electricity bars to ethereals, cookies to everyone else
var/obj/item/reagent_containers/food/cookiealt = /obj/item/reagent_containers/food/snacks/cookie
if(isskeleton(H))
- cookiealt = /obj/item/reagent_containers/food/condiment/milk
+ cookiealt = /obj/item/reagent_containers/condiment/milk
else if(isplasmaman(H))
- cookiealt = /obj/item/reagent_containers/food/condiment/milk
+ cookiealt = /obj/item/reagent_containers/condiment/milk
else if(iselzuose(H))
cookiealt = /obj/item/reagent_containers/food/snacks/energybar
// WS - More fun with cookies - Start
diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm
index 53d17bf325a..69c9248d6fb 100644
--- a/code/modules/antagonists/cult/cult_items.dm
+++ b/code/modules/antagonists/cult/cult_items.dm
@@ -423,7 +423,7 @@
return 0
/obj/item/clothing/suit/hooded/cultrobes/cult_shield/worn_overlays(isinhands)
- . = list()
+ . = ..()
if(!isinhands && current_charges)
. += mutable_appearance('icons/effects/cult_effects.dmi', "shield-cult", MOB_LAYER + 0.01)
diff --git a/code/modules/cargo/bounties/chef.dm b/code/modules/cargo/bounties/chef.dm
index d0e946ba2a6..969a41601fb 100644
--- a/code/modules/cargo/bounties/chef.dm
+++ b/code/modules/cargo/bounties/chef.dm
@@ -2,7 +2,7 @@
name = "Birthday Cake"
description = "Nanotrasen's birthday is coming up! Ship them a birthday cake to celebrate!"
reward = 4000
- wanted_types = list(/obj/item/reagent_containers/food/snacks/store/cake/birthday, /obj/item/reagent_containers/food/snacks/cakeslice/birthday)
+ wanted_types = list(/obj/item/food/cake/birthday, /obj/item/food/cakeslice/birthday)
/datum/bounty/item/chef/soup
name = "Soup"
@@ -43,7 +43,7 @@
name = "Bread"
description = "Problems with central planning have led to bread prices skyrocketing. Ship some bread to ease tensions."
reward = 1000
- wanted_types = list(/obj/item/reagent_containers/food/snacks/store/bread, /obj/item/reagent_containers/food/snacks/breadslice, /obj/item/reagent_containers/food/snacks/bun, /obj/item/reagent_containers/food/snacks/pizzabread, /obj/item/reagent_containers/food/snacks/rawpastrybase)
+ wanted_types = list(/obj/item/food/bread, /obj/item/food/breadslice, /obj/item/reagent_containers/food/snacks/bun, /obj/item/reagent_containers/food/snacks/pizzabread, /obj/item/reagent_containers/food/snacks/rawpastrybase)
/datum/bounty/item/chef/pie
name = "Pie"
diff --git a/code/modules/cargo/packs/food.dm b/code/modules/cargo/packs/food.dm
index 2c95afaa317..b365b82b6e3 100644
--- a/code/modules/cargo/packs/food.dm
+++ b/code/modules/cargo/packs/food.dm
@@ -63,12 +63,12 @@
name = "Basic Ingredients Crate"
desc = "Get things cooking with this crate full of useful ingredients! Contains a dozen eggs, two slabs of meat, some flour, some rice, a bottle of milk, a bottle of soymilk, and a bag of sugar."
cost = 300
- contains = list(/obj/item/reagent_containers/food/condiment/flour,
- /obj/item/reagent_containers/food/condiment/flour,
- /obj/item/reagent_containers/food/condiment/rice,
- /obj/item/reagent_containers/food/condiment/milk,
- /obj/item/reagent_containers/food/condiment/soymilk,
- /obj/item/reagent_containers/food/condiment/sugar,
+ contains = list(/obj/item/reagent_containers/condiment/flour,
+ /obj/item/reagent_containers/condiment/flour,
+ /obj/item/reagent_containers/condiment/rice,
+ /obj/item/reagent_containers/condiment/milk,
+ /obj/item/reagent_containers/condiment/soymilk,
+ /obj/item/reagent_containers/condiment/sugar,
/obj/item/storage/fancy/egg_box,
/obj/item/reagent_containers/food/snacks/meat/slab,
/obj/item/reagent_containers/food/snacks/meat/slab
@@ -80,13 +80,13 @@
name = "Condiments Crate"
desc = "A variety of garnishes for topping off your dish with a little extra pizzaz. Contains a bottle of enzyme, a salt shaker, a pepper mill, a bottle of ketchup, a bottle of hot sauce, a bottle of BBQ sauce, and a bottle of cream."
cost = 250
- contains = list(/obj/item/reagent_containers/food/condiment/enzyme,
- /obj/item/reagent_containers/food/condiment/saltshaker,
- /obj/item/reagent_containers/food/condiment/peppermill,
- /obj/item/reagent_containers/food/condiment/ketchup,
- /obj/item/reagent_containers/food/condiment/hotsauce,
+ contains = list(/obj/item/reagent_containers/condiment/enzyme,
+ /obj/item/reagent_containers/condiment/saltshaker,
+ /obj/item/reagent_containers/condiment/peppermill,
+ /obj/item/reagent_containers/condiment/ketchup,
+ /obj/item/reagent_containers/condiment/hotsauce,
/obj/item/reagent_containers/food/drinks/bottle/cream,
- /obj/item/reagent_containers/food/condiment/bbqsauce
+ /obj/item/reagent_containers/condiment/bbqsauce
)
crate_name = "condiments crate"
crate_type = /obj/structure/closet/crate/freezer
@@ -177,10 +177,10 @@
name = "Bread Crate"
desc = "A crate full of various breads. Bready to either be eaten or made into delicious meals."
cost = 300
- contains = list(/obj/item/reagent_containers/food/snacks/store/bread/plain,
- /obj/item/reagent_containers/food/snacks/breadslice/plain,
- /obj/item/reagent_containers/food/snacks/breadslice/plain,
- /obj/item/reagent_containers/food/snacks/breadslice/plain, //Weighted to be more common
+ contains = list(/obj/item/food/bread/plain,
+ /obj/item/food/breadslice/plain,
+ /obj/item/food/breadslice/plain,
+ /obj/item/food/breadslice/plain, //Weighted to be more common
/obj/item/reagent_containers/food/snacks/bun,
/obj/item/reagent_containers/food/snacks/tortilla,
/obj/item/reagent_containers/food/snacks/pizzabread
diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm
index 166f437dc3a..a6bb8049958 100644
--- a/code/modules/clothing/clothing.dm
+++ b/code/modules/clothing/clothing.dm
@@ -42,6 +42,10 @@
/// If this can be eaten by a moth
var/moth_edible = TRUE
+ // Not used yet
+ /// Trait modification, lazylist of traits to add/take away, on equipment/drop in the correct slot
+ var/list/clothing_traits
+
/obj/item/clothing/Initialize()
if((clothing_flags & VOICEBOX_TOGGLABLE))
actions_types += /datum/action/item_action/toggle_voice_box
@@ -111,6 +115,8 @@
..()
if(!istype(user))
return
+ for(var/trait in clothing_traits)
+ REMOVE_CLOTHING_TRAIT(user, trait)
if(LAZYLEN(user_vars_remembered))
for(var/variable in user_vars_remembered)
if(variable in user.vars)
@@ -123,12 +129,48 @@
if (!istype(user))
return
if(slot_flags & slot) //Was equipped to a valid slot for this item?
+ for(var/trait in clothing_traits)
+ ADD_CLOTHING_TRAIT(user, trait)
if (LAZYLEN(user_vars_to_edit))
for(var/variable in user_vars_to_edit)
if(variable in user.vars)
LAZYSET(user_vars_remembered, variable, user.vars[variable])
user.vv_edit_var(variable, user_vars_to_edit[variable])
+/**
+ * Inserts a trait (or multiple traits) into the clothing traits list
+ *
+ * If worn, then we will also give the wearer the trait as if equipped
+ *
+ * This is so you can add clothing traits without worrying about needing to equip or unequip them to gain effects
+ */
+/obj/item/clothing/proc/attach_clothing_traits(trait_or_traits)
+ if(!islist(trait_or_traits))
+ trait_or_traits = list(trait_or_traits)
+
+ LAZYOR(clothing_traits, trait_or_traits)
+ var/mob/wearer = loc
+ if(istype(wearer) && (wearer.get_slot_by_item(src) & slot_flags))
+ for(var/new_trait in trait_or_traits)
+ ADD_CLOTHING_TRAIT(wearer, new_trait)
+
+/**
+ * Removes a trait (or multiple traits) from the clothing traits list
+ *
+ * If worn, then we will also remove the trait from the wearer as if unequipped
+ *
+ * This is so you can add clothing traits without worrying about needing to equip or unequip them to gain effects
+ */
+/obj/item/clothing/proc/detach_clothing_traits(trait_or_traits)
+ if(!islist(trait_or_traits))
+ trait_or_traits = list(trait_or_traits)
+
+ LAZYREMOVE(clothing_traits, trait_or_traits)
+ var/mob/wearer = loc
+ if(istype(wearer))
+ for(var/new_trait in trait_or_traits)
+ REMOVE_CLOTHING_TRAIT(wearer, new_trait)
+
/obj/item/clothing/examine(mob/user)
. = ..()
switch (max_heat_protection_temperature)
diff --git a/code/modules/clothing/glasses/_glasses.dm b/code/modules/clothing/glasses/_glasses.dm
index f33a789156e..0aa514d2e3b 100644
--- a/code/modules/clothing/glasses/_glasses.dm
+++ b/code/modules/clothing/glasses/_glasses.dm
@@ -378,7 +378,7 @@
colored_before = TRUE
/obj/item/clothing/glasses/blindfold/white/worn_overlays(isinhands = FALSE, file2use)
- . = list()
+ . = ..()
if(!isinhands && ishuman(loc) && !colored_before)
var/mob/living/carbon/human/H = loc
var/mutable_appearance/M = mutable_appearance('icons/mob/clothing/eyes.dmi', "blindfoldwhite")
diff --git a/code/modules/clothing/gloves/_gloves.dm b/code/modules/clothing/gloves/_gloves.dm
index 61c06125d8f..a6e9f22ea3e 100644
--- a/code/modules/clothing/gloves/_gloves.dm
+++ b/code/modules/clothing/gloves/_gloves.dm
@@ -25,7 +25,7 @@
return TRUE
/obj/item/clothing/gloves/worn_overlays(isinhands = FALSE)
- . = list()
+ . = ..()
if(!isinhands)
if(damaged_clothes)
. += mutable_appearance('icons/effects/item_damage.dmi', "damagedgloves")
diff --git a/code/modules/clothing/gloves/miscellaneous.dm b/code/modules/clothing/gloves/miscellaneous.dm
index 9d8db3c035d..72be95bd82a 100644
--- a/code/modules/clothing/gloves/miscellaneous.dm
+++ b/code/modules/clothing/gloves/miscellaneous.dm
@@ -23,6 +23,7 @@
heat_protection = HANDS
max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT
resistance_flags = NONE
+ clothing_traits = list(TRAIT_PLANT_SAFE)
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 30)
/obj/item/clothing/gloves/combat
@@ -125,7 +126,7 @@
name = "explorer envirogloves"
icon_state = "explorerplasma"
-/obj/item/clothing/gloves/color/botanic_leather/plasmaman
+/obj/item/clothing/gloves/botanic_leather/plasmaman
name = "botany envirogloves"
desc = "Covers up those scandalous boney hands."
icon_state = "botanyplasma"
diff --git a/code/modules/clothing/head/_head.dm b/code/modules/clothing/head/_head.dm
index 4039402588f..aa1114e6b18 100644
--- a/code/modules/clothing/head/_head.dm
+++ b/code/modules/clothing/head/_head.dm
@@ -60,7 +60,7 @@
/obj/item/clothing/head/worn_overlays(isinhands = FALSE)
- . = list()
+ . = ..()
if(!isinhands)
if(damaged_clothes)
. += mutable_appearance('icons/effects/item_damage.dmi', "damagedhelmet")
diff --git a/code/modules/clothing/head/misc_special.dm b/code/modules/clothing/head/misc_special.dm
index f33d424b52e..40761c47d7b 100644
--- a/code/modules/clothing/head/misc_special.dm
+++ b/code/modules/clothing/head/misc_special.dm
@@ -241,7 +241,7 @@
return ..()
/obj/item/clothing/head/wig/worn_overlays(isinhands = FALSE, file2use)
- . = list()
+ . = ..()
if(!isinhands)
var/datum/sprite_accessory/S = GLOB.hairstyles_list[hairstyle]
if(!S)
diff --git a/code/modules/clothing/masks/_masks.dm b/code/modules/clothing/masks/_masks.dm
index 03ca246b60a..a4c1d5d509f 100644
--- a/code/modules/clothing/masks/_masks.dm
+++ b/code/modules/clothing/masks/_masks.dm
@@ -32,7 +32,7 @@
/obj/item/clothing/mask/proc/handle_speech()
/obj/item/clothing/mask/worn_overlays(isinhands = FALSE)
- . = list()
+ . = ..()
if(!isinhands)
if(body_parts_covered & HEAD)
if(damaged_clothes)
diff --git a/code/modules/clothing/neck/_neck.dm b/code/modules/clothing/neck/_neck.dm
index 7154aaccc0e..d6a81b01b05 100644
--- a/code/modules/clothing/neck/_neck.dm
+++ b/code/modules/clothing/neck/_neck.dm
@@ -11,7 +11,7 @@
greyscale_icon_state = "scarf"
/obj/item/clothing/neck/worn_overlays(isinhands = FALSE)
- . = list()
+ . = ..()
if(!isinhands)
if(body_parts_covered & HEAD)
if(damaged_clothes)
diff --git a/code/modules/clothing/outfits/ert/frontiersmen_ert.dm b/code/modules/clothing/outfits/ert/frontiersmen_ert.dm
index fb8afb35a5e..65cbcc74f4d 100644
--- a/code/modules/clothing/outfits/ert/frontiersmen_ert.dm
+++ b/code/modules/clothing/outfits/ert/frontiersmen_ert.dm
@@ -59,7 +59,7 @@
/obj/item/storage/backpack/satchel = 20,
/obj/item/storage/backpack/messenger = 20,
/obj/item/melee/baton/cattleprod/loaded = 5,
- /obj/item/reagent_containers/food/snacks/baguette = 2, // yes you can put this on your back
+ /obj/item/food/baguette = 2, // yes you can put this on your back
/obj/item/deployable_turret_folded = 1,
/obj/item/gun/ballistic/automatic/hmg/skm_lmg/extended = 1,
))
diff --git a/code/modules/clothing/outfits/plasmaman.dm b/code/modules/clothing/outfits/plasmaman.dm
index 11a26436d1a..ebc6d40b75f 100644
--- a/code/modules/clothing/outfits/plasmaman.dm
+++ b/code/modules/clothing/outfits/plasmaman.dm
@@ -17,7 +17,7 @@
head = /obj/item/clothing/head/helmet/space/plasmaman/botany
uniform = /obj/item/clothing/under/plasmaman/botany
- gloves = /obj/item/clothing/gloves/color/botanic_leather/plasmaman
+ gloves = /obj/item/clothing/gloves/botanic_leather/plasmaman
/datum/outfit/plasmaman/curator
name = "Curator Plasmaman"
diff --git a/code/modules/clothing/shoes/_shoes.dm b/code/modules/clothing/shoes/_shoes.dm
index 336ac43c7d4..cd447458809 100644
--- a/code/modules/clothing/shoes/_shoes.dm
+++ b/code/modules/clothing/shoes/_shoes.dm
@@ -29,7 +29,7 @@
var/atom/movable/screen/alert/our_alert
/obj/item/clothing/shoes/worn_overlays(isinhands = FALSE)
- . = list()
+ . = ..()
if(!isinhands)
if(damaged_clothes)
. += mutable_appearance('icons/effects/item_damage.dmi', "damagedshoe")
diff --git a/code/modules/clothing/spacesuits/hardsuit.dm b/code/modules/clothing/spacesuits/hardsuit.dm
index eb7f884f74d..a9229b79278 100644
--- a/code/modules/clothing/spacesuits/hardsuit.dm
+++ b/code/modules/clothing/spacesuits/hardsuit.dm
@@ -739,23 +739,6 @@
item_state = "capspacesuit"
helmettype = /obj/item/clothing/head/helmet/space/hardsuit/swat/captain
- //Clown
-/obj/item/clothing/head/helmet/space/hardsuit/clown
- name = "cosmohonk hardsuit helmet"
- desc = "A special helmet designed for work in a hazardous, low-humor environment. Has radiation shielding."
- icon_state = "hardsuit0-clown"
- item_state = "hardsuit0-clown"
- armor = list("melee" = 30, "bullet" = 5, "laser" = 10, "energy" = 20, "bomb" = 10, "bio" = 100, "rad" = 75, "fire" = 60, "acid" = 30)
- hardsuit_type = "clown"
-
-/obj/item/clothing/suit/space/hardsuit/clown
- name = "cosmohonk hardsuit"
- desc = "A special suit that protects against hazardous, low humor environments. Has radiation shielding. Only a true clown can wear it."
- icon_state = "hardsuit-clown"
- item_state = "clown_hardsuit"
- armor = list("melee" = 30, "bullet" = 5, "laser" = 10, "energy" = 20, "bomb" = 10, "bio" = 100, "rad" = 75, "fire" = 60, "acid" = 30)
- helmettype = /obj/item/clothing/head/helmet/space/hardsuit/clown
-
//Old Prototype
/obj/item/clothing/head/helmet/space/hardsuit/ancient
name = "prototype RIG hardsuit helmet"
@@ -868,7 +851,7 @@
C.update_inv_wear_suit()
/obj/item/clothing/suit/space/hardsuit/shielded/worn_overlays(isinhands)
- . = list()
+ . = ..()
if(!isinhands)
. += mutable_appearance('icons/effects/effects.dmi', shield_state, MOB_LAYER + 0.01)
@@ -1222,9 +1205,27 @@
item_state = "hardsuit_solgov"
armor = list("melee" = 50, "bullet" = 45, "laser" = 40, "energy" = 30, "bomb" = 60, "bio" = 100, "rad" = 60, "fire" = 90, "acid" = 75) //intentionally the fucking strong, this is master chief-tier armor //is this really what you call the strong?? is this the best solgov has to offer??????
helmettype = /obj/item/clothing/head/helmet/space/hardsuit/terragov
+ allowed = list(/obj/item/gun, /obj/item/ammo_box,/obj/item/ammo_casing, /obj/item/melee/baton, /obj/item/melee/transforming/energy/sword/saber, /obj/item/restraints/handcuffs, /obj/item/tank/internals)
slowdown = 0
supports_variations = DIGITIGRADE_VARIATION
+ //Clown
+/obj/item/clothing/head/helmet/space/hardsuit/clown
+ name = "cosmohonk hardsuit helmet"
+ desc = "A special helmet designed for work in a hazardous, low-humor environment. Has radiation shielding."
+ icon_state = "hardsuit0-clown"
+ item_state = "hardsuit0-clown"
+ armor = list("melee" = 30, "bullet" = 5, "laser" = 10, "energy" = 20, "bomb" = 10, "bio" = 100, "rad" = 75, "fire" = 60, "acid" = 30)
+ hardsuit_type = "clown"
+
+/obj/item/clothing/suit/space/hardsuit/clown
+ name = "cosmohonk hardsuit"
+ desc = "A special suit that protects against hazardous, low humor environments. Has radiation shielding. Only a true clown can wear it."
+ icon_state = "hardsuit-clown"
+ item_state = "clown_hardsuit"
+ armor = list("melee" = 30, "bullet" = 5, "laser" = 10, "energy" = 20, "bomb" = 10, "bio" = 100, "rad" = 75, "fire" = 60, "acid" = 30)
+ helmettype = /obj/item/clothing/head/helmet/space/hardsuit/clown
+
/obj/item/clothing/head/helmet/space/hardsuit/quixote
name = "\improper Quixote mobility hardsuit helmet"
desc = "The integrated helmet of a Quixote mobility hardsuit."
diff --git a/code/modules/clothing/suits/_suits.dm b/code/modules/clothing/suits/_suits.dm
index 0e7edb63f06..30d3c3c3c9b 100644
--- a/code/modules/clothing/suits/_suits.dm
+++ b/code/modules/clothing/suits/_suits.dm
@@ -19,7 +19,7 @@
mob_overlay_icon = 'icons/mob/clothing/suit.dmi'
/obj/item/clothing/suit/worn_overlays(isinhands = FALSE)
- . = list()
+ . = ..()
if(!isinhands)
if(damaged_clothes)
. += mutable_appearance('icons/effects/item_damage.dmi', "damageduniform")
diff --git a/code/modules/clothing/under/_under.dm b/code/modules/clothing/under/_under.dm
index a28d6d323a8..bc8cb512906 100644
--- a/code/modules/clothing/under/_under.dm
+++ b/code/modules/clothing/under/_under.dm
@@ -27,7 +27,7 @@
supports_variations = VOX_VARIATION
/obj/item/clothing/under/worn_overlays(isinhands = FALSE)
- . = list()
+ . = ..()
if(!isinhands)
if(damaged_clothes)
. += mutable_appearance('icons/effects/item_damage.dmi', "damageduniform")
diff --git a/code/modules/food_and_drinks/drinks/drinks/bottle.dm b/code/modules/food_and_drinks/drinks/drinks/bottle.dm
index 3ea468d19c1..39771df5e33 100644
--- a/code/modules/food_and_drinks/drinks/drinks/bottle.dm
+++ b/code/modules/food_and_drinks/drinks/drinks/bottle.dm
@@ -521,7 +521,13 @@
break
if(firestarter && active)
hit_atom.fire_act()
- new /obj/effect/hotspot(get_turf(hit_atom))
+ var/turf/T = get_turf(hit_atom)
+ T.IgniteTurf(30)
+ var/turf/otherT
+ for(var/direction in GLOB.cardinals)
+ otherT = get_step(T, direction)
+ otherT.IgniteTurf(30)
+ new /obj/effect/hotspot(otherT)
..()
/obj/item/reagent_containers/food/drinks/bottle/molotov/attackby(obj/item/I, mob/user, params)
diff --git a/code/modules/food_and_drinks/food/condiment.dm b/code/modules/food_and_drinks/food/condiment.dm
index 0adf98ba566..bd24a21d1a1 100644
--- a/code/modules/food_and_drinks/food/condiment.dm
+++ b/code/modules/food_and_drinks/food/condiment.dm
@@ -5,7 +5,7 @@
// to mixed-drinks code. If you want an object that starts pre-loaded, you need to make it in addition to the other code.
//Food items that aren't eaten normally and leave an empty container behind.
-/obj/item/reagent_containers/food/condiment
+/obj/item/reagent_containers/condiment
name = "condiment bottle"
desc = "Just your average condiment bottle."
icon = 'icons/obj/food/containers.dmi'
@@ -36,23 +36,25 @@
var/icon_empty = ""
fill_icon_thresholds = list(0, 10, 25, 50, 75, 100)
-/obj/item/reagent_containers/food/condiment/Initialize()
+/obj/item/reagent_containers/condiment/Initialize()
. = ..()
possible_states = typelist("possible_states", possible_states)
update_appearance()
-/obj/item/reagent_containers/food/condiment/update_icon()
+/obj/item/reagent_containers/condiment/update_icon()
cut_overlays()
if(reagents.reagent_list.len > 0 && possible_states.len)
- var/main_reagent = reagents.get_master_reagent_id()
- if(main_reagent in possible_states)
- icon_state = possible_states[main_reagent]["icon_state"]
- item_state = possible_states[main_reagent]["item_state"]
- icon_empty = possible_states[main_reagent]["icon_empty"]
- name = possible_states[main_reagent]["name"]
- desc = possible_states[main_reagent]["desc"]
+
+ var/datum/reagent/main_reagent_ref = reagents.get_master_reagent()
+ var/main_reagent_id = main_reagent_ref.type
+ if(main_reagent_id in possible_states)
+ icon_state = possible_states[main_reagent_id]["icon_state"]
+ item_state = possible_states[main_reagent_id]["item_state"]
+ icon_empty = possible_states[main_reagent_id]["icon_empty"]
+ name = possible_states[main_reagent_id]["name"]
+ desc = possible_states[main_reagent_id]["desc"]
return ..(TRUE) // Don't fill normally
else
name = "condiment bottle"
@@ -64,7 +66,7 @@
. = ..()
-/obj/item/reagent_containers/food/condiment/attack(mob/M, mob/user, def_zone)
+/obj/item/reagent_containers/condiment/attack(mob/M, mob/user, def_zone)
if(!reagents || !reagents.total_volume)
to_chat(user, "None of [src] left, oh no!")
@@ -90,7 +92,7 @@
playsound(M.loc,'sound/items/drink.ogg', rand(10,50), TRUE)
return 1
-/obj/item/reagent_containers/food/condiment/afterattack(obj/target, mob/user , proximity)
+/obj/item/reagent_containers/condiment/afterattack(obj/target, mob/user , proximity)
. = ..()
if(!proximity)
return
@@ -119,23 +121,23 @@
to_chat(user, "You transfer [trans] units of the condiment to [target].")
playsound(src, 'sound/items/glass_transfer.ogg', 50, 1)
-/obj/item/reagent_containers/food/condiment/on_reagent_change(changetype)
+/obj/item/reagent_containers/condiment/on_reagent_change(changetype)
update_appearance()
-/obj/item/reagent_containers/food/condiment/enzyme
+/obj/item/reagent_containers/condiment/enzyme
name = "universal enzyme"
desc = "Used in cooking various dishes."
icon_state = "enzyme"
list_reagents = list(/datum/reagent/consumable/enzyme = 50)
-/obj/item/reagent_containers/food/condiment/sugar
+/obj/item/reagent_containers/condiment/sugar
name = "sugar sack"
desc = "Tasty spacey sugar!"
icon_state = "sugar"
item_state = "flour"
list_reagents = list(/datum/reagent/consumable/sugar = 50)
-/obj/item/reagent_containers/food/condiment/saltshaker //Separate from above since it's a small shaker rather then
+/obj/item/reagent_containers/condiment/saltshaker //Separate from above since it's a small shaker rather then
name = "salt shaker" // a large one.
desc = "Salt. From space oceans, presumably."
icon_state = "saltshakersmall"
@@ -145,7 +147,7 @@
volume = 20
list_reagents = list(/datum/reagent/consumable/sodiumchloride = 20)
-/obj/item/reagent_containers/food/condiment/saltshaker/afterattack(obj/target, mob/living/user, proximity)
+/obj/item/reagent_containers/condiment/saltshaker/afterattack(obj/target, mob/living/user, proximity)
. = ..()
if(!proximity)
return
@@ -158,7 +160,7 @@
new/obj/effect/decal/cleanable/food/salt(target)
return
-/obj/item/reagent_containers/food/condiment/peppermill
+/obj/item/reagent_containers/condiment/peppermill
name = "pepper mill"
desc = "Often used to flavor food or make people sneeze."
icon_state = "peppermillsmall"
@@ -168,7 +170,7 @@
volume = 20
list_reagents = list(/datum/reagent/consumable/blackpepper = 20)
-/obj/item/reagent_containers/food/condiment/milk
+/obj/item/reagent_containers/condiment/milk
name = "space milk"
desc = "It's milk. White and nutritious goodness!"
icon_state = "milk"
@@ -177,14 +179,14 @@
righthand_file = 'icons/mob/inhands/equipment/kitchen_righthand.dmi'
list_reagents = list(/datum/reagent/consumable/milk = 50)
-/obj/item/reagent_containers/food/condiment/flour
+/obj/item/reagent_containers/condiment/flour
name = "flour sack"
desc = "A big bag of flour. Good for baking!"
icon_state = "flour"
item_state = "flour"
list_reagents = list(/datum/reagent/consumable/flour = 30)
-/obj/item/reagent_containers/food/condiment/soymilk
+/obj/item/reagent_containers/condiment/soymilk
name = "soy milk"
desc = "It's soy milk. White and nutritious goodness!"
icon_state = "soymilk"
@@ -193,20 +195,20 @@
righthand_file = 'icons/mob/inhands/equipment/kitchen_righthand.dmi'
list_reagents = list(/datum/reagent/consumable/soymilk = 50)
-/obj/item/reagent_containers/food/condiment/rice
+/obj/item/reagent_containers/condiment/rice
name = "rice sack"
desc = "A big bag of rice. Good for cooking!"
icon_state = "rice"
item_state = "flour"
list_reagents = list(/datum/reagent/consumable/rice = 30)
-/obj/item/reagent_containers/food/condiment/soysauce
+/obj/item/reagent_containers/condiment/soysauce
name = "soy sauce"
desc = "A salty soy-based flavoring."
icon_state = "soysauce"
list_reagents = list(/datum/reagent/consumable/soysauce = 50)
-/obj/item/reagent_containers/food/condiment/mayonnaise
+/obj/item/reagent_containers/condiment/mayonnaise
name = "mayonnaise"
desc = "An oily condiment made from egg yolks."
icon_state = "mayonnaise"
@@ -214,7 +216,7 @@
//Food packs. To easily apply deadly toxi... delicious sauces to your food!
-/obj/item/reagent_containers/food/condiment/pack
+/obj/item/reagent_containers/condiment/pack
name = "condiment pack"
desc = "A small plastic pack with condiments to put on your food."
icon_state = "condi_empty"
@@ -234,14 +236,19 @@
/datum/reagent/consumable/bbqsauce = list("condi_bbq", "BBQ sauce", "Hand wipes not included."),
)
-/obj/item/reagent_containers/food/condiment/pack/update_icon()
+/obj/item/reagent_containers/condiment/pack/create_reagents(max_vol, flags)
+ . = ..()
+ RegisterSignals(reagents, list(COMSIG_REAGENTS_NEW_REAGENT, COMSIG_REAGENTS_ADD_REAGENT, COMSIG_REAGENTS_REM_REAGENT), PROC_REF(on_reagent_add), TRUE)
+ RegisterSignal(reagents, COMSIG_REAGENTS_DEL_REAGENT, PROC_REF(on_reagent_del), TRUE)
+
+/obj/item/reagent_containers/condiment/pack/update_icon()
SHOULD_CALL_PARENT(FALSE)
- return ..()
+ return
-/obj/item/reagent_containers/food/condiment/pack/attack(mob/M, mob/user, def_zone) //Can't feed these to people directly.
+/obj/item/reagent_containers/condiment/pack/attack(mob/M, mob/user, def_zone) //Can't feed these to people directly.
return
-/obj/item/reagent_containers/food/condiment/pack/afterattack(obj/target, mob/user , proximity)
+/obj/item/reagent_containers/condiment/pack/afterattack(obj/target, mob/user , proximity)
. = ..()
if(!proximity)
return
@@ -257,67 +264,74 @@
src.reagents.trans_to(target, amount_per_transfer_from_this, transfered_by = user)
qdel(src)
-/obj/item/reagent_containers/food/condiment/pack/on_reagent_change(changetype)
- if(reagents.reagent_list.len > 0)
- var/main_reagent = reagents.get_master_reagent_id()
- if(main_reagent in possible_states)
- var/list/temp_list = possible_states[main_reagent]
- icon_state = temp_list[1]
- desc = temp_list[3]
- else
- icon_state = "condi_mixed"
- desc = "A small condiment pack. The label says it contains [originalname]"
+/// Handles reagents getting added to the condiment pack.
+/obj/item/reagent_containers/condiment/pack/proc/on_reagent_add(datum/reagents/reagents)
+ SIGNAL_HANDLER
+
+ var/datum/reagent/main_reagent = reagents.get_master_reagent()
+
+ var/main_reagent_type = main_reagent?.type
+ if(main_reagent_type in possible_states)
+ var/list/temp_list = possible_states[main_reagent_type]
+ icon_state = temp_list[1]
+ desc = temp_list[3]
else
- icon_state = "condi_empty"
- desc = "A small condiment pack. It is empty."
+ icon_state = "condi_mixed"
+ desc = "A small condiment pack. The label says it contains [originalname]"
+
+/// Handles reagents getting removed from the condiment pack.
+/obj/item/reagent_containers/condiment/pack/proc/on_reagent_del(datum/reagents/reagents)
+ SIGNAL_HANDLER
+ icon_state = "condi_empty"
+ desc = "A small condiment pack. It is empty."
//Ketchup
-/obj/item/reagent_containers/food/condiment/pack/ketchup
+/obj/item/reagent_containers/condiment/pack/ketchup
name = "ketchup pack"
originalname = "ketchup"
list_reagents = list(/datum/reagent/consumable/ketchup = 10)
//Hot sauce
-/obj/item/reagent_containers/food/condiment/pack/hotsauce
+/obj/item/reagent_containers/condiment/pack/hotsauce
name = "hotsauce pack"
originalname = "hotsauce"
list_reagents = list(/datum/reagent/consumable/capsaicin = 10)
-/obj/item/reagent_containers/food/condiment/pack/astrotame
+/obj/item/reagent_containers/condiment/pack/astrotame
name = "astrotame pack"
originalname = "astrotame"
list_reagents = list(/datum/reagent/consumable/astrotame = 5)
-/obj/item/reagent_containers/food/condiment/pack/bbqsauce
+/obj/item/reagent_containers/condiment/pack/bbqsauce
name = "bbq sauce pack"
originalname = "bbq sauce"
list_reagents = list(/datum/reagent/consumable/bbqsauce = 10)
-/obj/item/reagent_containers/food/condiment/ketchup
+/obj/item/reagent_containers/condiment/ketchup
name = "ketchup bottle"
desc = "You feel more american already"
icon_state = "ketchup"
list_reagents = list(/datum/reagent/consumable/ketchup = 50)
-/obj/item/reagent_containers/food/condiment/bbqsauce
+/obj/item/reagent_containers/condiment/bbqsauce
name = "bbq sauce bottle"
desc = "Hand wipes not included"
icon_state = "bbqsauce"
list_reagents = list(/datum/reagent/consumable/bbqsauce = 50)
-/obj/item/reagent_containers/food/condiment/hotsauce
+/obj/item/reagent_containers/condiment/hotsauce
name = "hot sauce bottle"
desc = "You can almost TASTE the stomach ulcers now!"
icon_state = "hotsauce"
list_reagents = list(/datum/reagent/consumable/capsaicin = 50)
-/obj/item/reagent_containers/food/condiment/coldsauce
+/obj/item/reagent_containers/condiment/coldsauce
name = "cold sauce bottle"
desc = "Leaves the tounge numb in it's passage"
icon_state = "coldsauce"
list_reagents = list(/datum/reagent/consumable/frostoil = 50)
-/obj/item/reagent_containers/food/condiment/oliveoil
+/obj/item/reagent_containers/condiment/oliveoil
name = "olive oil bottle"
desc = "A delicious oil used in cooking"
icon_state = "oliveoil"
diff --git a/code/modules/food_and_drinks/food/customizables.dm b/code/modules/food_and_drinks/food/customizables.dm
index fc3df2f7471..3147ee9a5de 100644
--- a/code/modules/food_and_drinks/food/customizables.dm
+++ b/code/modules/food_and_drinks/food/customizables.dm
@@ -45,7 +45,7 @@
to_chat(user, "The ingredient is too big for [src]!")
else if((ingredients.len >= ingMax) || (reagents.total_volume >= volume))
to_chat(user, "You can't add more ingredients to [src]!")
- else if(istype(I, /obj/item/reagent_containers/food/snacks/pizzaslice/custom) || istype(I, /obj/item/reagent_containers/food/snacks/cakeslice/custom))
+ else if(istype(I, /obj/item/reagent_containers/food/snacks/pizzaslice/custom))
to_chat(user, "Adding [I.name] to [src] would make a mess.")
else
if(!user.transferItemToLoc(I, src))
@@ -170,26 +170,6 @@
foodtype = GRAIN
-/obj/item/reagent_containers/food/snacks/customizable/bread
- name = "bread"
- ingMax = 6
- slice_path = /obj/item/reagent_containers/food/snacks/breadslice/custom
- slices_num = 5
- icon = 'icons/obj/food/burgerbread.dmi'
- icon_state = "tofubread"
- foodtype = GRAIN
-
-
-/obj/item/reagent_containers/food/snacks/customizable/cake
- name = "cake"
- ingMax = 6
- slice_path = /obj/item/reagent_containers/food/snacks/cakeslice/custom
- slices_num = 5
- icon = 'icons/obj/food/piecake.dmi'
- icon_state = "plaincake"
- foodtype = GRAIN | DAIRY
-
-
/obj/item/reagent_containers/food/snacks/customizable/kebab
name = "kebab"
desc = "Delicious food on a stick."
@@ -199,15 +179,6 @@
ingMax = 6
icon_state = "rod"
-/obj/item/reagent_containers/food/snacks/customizable/pasta
- name = "spaghetti"
- desc = "Noodles. With stuff. Delicious."
- ingredients_placement = INGREDIENTS_SCATTER
- ingMax = 6
- icon = 'icons/obj/food/pizzaspaghetti.dmi'
- icon_state = "spaghettiboiled"
- foodtype = GRAIN
-
/obj/item/reagent_containers/food/snacks/customizable/pie
name = "pie"
@@ -238,43 +209,6 @@
icon_state = "bowl"
-/obj/item/reagent_containers/food/snacks/customizable/sandwich
- name = "toast"
- desc = "A timeless classic."
- ingredients_placement = INGREDIENTS_STACK
- icon = 'icons/obj/food/burgerbread.dmi'
- icon_state = "breadslice"
- var/finished = 0
- foodtype = GRAIN
-
-/obj/item/reagent_containers/food/snacks/customizable/sandwich/initialize_custom_food(obj/item/reagent_containers/BASE, obj/item/I, mob/user)
- icon_state = BASE.icon_state
- ..()
-
-/obj/item/reagent_containers/food/snacks/customizable/sandwich/attackby(obj/item/I, mob/user, params)
- if(istype(I, /obj/item/reagent_containers/food/snacks/breadslice)) //we're finishing the custom food.
- var/obj/item/reagent_containers/food/snacks/breadslice/BS = I
- if(finished)
- return
- to_chat(user, "You finish the [src.name].")
- finished = 1
- name = "[customname] sandwich"
- BS.reagents.trans_to(src, BS.reagents.total_volume, transfered_by = user)
- ingMax = ingredients.len //can't add more ingredients after that
- var/mutable_appearance/TOP = mutable_appearance(icon, "[BS.icon_state]")
- TOP.pixel_y = 2 * ingredients.len + 3
- add_overlay(TOP)
- if(istype(BS, /obj/item/reagent_containers/food/snacks/breadslice/custom))
- var/mutable_appearance/filling = new(icon, "[initial(BS.icon_state)]_filling")
- filling.color = BS.filling_color
- filling.pixel_y = 2 * ingredients.len + 3
- add_overlay(filling)
- qdel(BS)
- return
- else
- ..()
-
-
/obj/item/reagent_containers/food/snacks/customizable/soup
name = "soup"
desc = "A bowl with liquid and... stuff in it."
diff --git a/code/modules/food_and_drinks/food/snacks.dm b/code/modules/food_and_drinks/food/snacks.dm
index 61121a3ca95..9e84c272dbb 100644
--- a/code/modules/food_and_drinks/food/snacks.dm
+++ b/code/modules/food_and_drinks/food/snacks.dm
@@ -56,6 +56,16 @@ All foods are distributed among various categories. Use common sense.
//Placeholder for effect that trigger on eating that aren't tied to reagents.
+/obj/item/reagent_containers/food/snacks/Initialize(mapload)
+ . = ..()
+ RegisterSignal(src, COMSIG_ITEM_FRIED, PROC_REF(on_fried))
+
+
+/obj/item/reagent_containers/food/snacks/proc/on_fried(fry_object)
+ reagents.trans_to(fry_object, reagents.total_volume)
+ qdel()
+ return COMSIG_FRYING_HANDLED
+
/obj/item/reagent_containers/food/snacks/add_initial_reagents()
if(tastes && tastes.len)
if(list_reagents)
@@ -175,7 +185,7 @@ All foods are distributed among various categories. Use common sense.
if(W.w_class > WEIGHT_CLASS_SMALL)
to_chat(user, span_warning("[S] is too big for [src]!"))
return FALSE
- if(istype(S) && (!S.customfoodfilling || istype(W, /obj/item/reagent_containers/food/snacks/customizable) || istype(W, /obj/item/reagent_containers/food/snacks/pizzaslice/custom) || istype(W, /obj/item/reagent_containers/food/snacks/cakeslice/custom)))
+ if(!S.customfoodfilling || istype(W, /obj/item/reagent_containers/food/snacks/customizable) || istype(W, /obj/item/reagent_containers/food/snacks/pizzaslice/custom))
to_chat(user, span_warning("[src] can't be filled with [S]!"))
return FALSE
if(contents.len >= 20)
@@ -337,26 +347,10 @@ All foods are distributed among various categories. Use common sense.
/// All the food items that can store an item inside itself, like bread or cake.
/obj/item/reagent_containers/food/snacks/store
w_class = WEIGHT_CLASS_NORMAL
- var/stored_item = 0
-/obj/item/reagent_containers/food/snacks/store/attackby(obj/item/W, mob/user, params)
- ..()
- if(W.w_class <= WEIGHT_CLASS_SMALL & !istype(W, /obj/item/reagent_containers/food/snacks)) //can't slip snacks inside, they're used for custom foods.
- if(W.get_sharpness())
- return 0
- if(stored_item)
- return 0
- if(!iscarbon(user))
- return 0
- if(contents.len >= 20)
- to_chat(user, "[src] is full.")
- return 0
- to_chat(user, "You slip [W] inside [src].")
- user.transferItemToLoc(W, src)
- add_fingerprint(user)
- contents += W
- stored_item = 1
- return 1 // no afterattack here
+/obj/item/reagent_containers/food/snacks/store/Initialize()
+ . = ..()
+ AddComponent(/datum/component/food_storage)
/obj/item/reagent_containers/food/snacks/MouseDrop(atom/over)
var/turf/T = get_turf(src)
diff --git a/code/modules/food_and_drinks/food/snacks/dough.dm b/code/modules/food_and_drinks/food/snacks/dough.dm
index 9567690dc71..4f5f0637992 100644
--- a/code/modules/food_and_drinks/food/snacks/dough.dm
+++ b/code/modules/food_and_drinks/food/snacks/dough.dm
@@ -7,7 +7,7 @@
desc = "A piece of dough."
icon = 'icons/obj/food/food_ingredients.dmi'
icon_state = "dough"
- cooked_type = /obj/item/reagent_containers/food/snacks/store/bread/plain
+ cooked_type = /obj/item/food/bread/plain
list_reagents = list(/datum/reagent/consumable/nutriment = 6)
w_class = WEIGHT_CLASS_NORMAL
tastes = list("dough" = 1)
@@ -82,7 +82,7 @@
desc = "Cook it to get a cake."
icon = 'icons/obj/food/food_ingredients.dmi'
icon_state = "cakebatter"
- cooked_type = /obj/item/reagent_containers/food/snacks/store/cake/plain
+ cooked_type = /obj/item/food/cake/plain
list_reagents = list(/datum/reagent/consumable/nutriment = 9)
w_class = WEIGHT_CLASS_NORMAL
tastes = list("batter" = 1)
diff --git a/code/modules/food_and_drinks/food/snacks_bread.dm b/code/modules/food_and_drinks/food/snacks_bread.dm
deleted file mode 100644
index 13342a96890..00000000000
--- a/code/modules/food_and_drinks/food/snacks_bread.dm
+++ /dev/null
@@ -1,302 +0,0 @@
-
-/obj/item/reagent_containers/food/snacks/store/bread
- icon = 'icons/obj/food/burgerbread.dmi'
- volume = 80
- slices_num = 5
- tastes = list("bread" = 10)
- foodtype = GRAIN
-
-/obj/item/reagent_containers/food/snacks/store/bread/Initialize()
- . = ..()
- AddElement(/datum/element/dunkable, 10)
-
-/obj/item/reagent_containers/food/snacks/breadslice
- icon = 'icons/obj/food/burgerbread.dmi'
- bitesize = 2
- custom_food_type = /obj/item/reagent_containers/food/snacks/customizable/sandwich
- filling_color = "#FFA500"
- list_reagents = list(/datum/reagent/consumable/nutriment = 2)
- slot_flags = ITEM_SLOT_HEAD
- customfoodfilling = 0 //to avoid infinite bread-ception
- foodtype = GRAIN
-
-/obj/item/reagent_containers/food/snacks/breadslice/Initialize()
- . = ..()
- AddElement(/datum/element/dunkable, 10)
-
-/obj/item/reagent_containers/food/snacks/store/bread/plain
- name = "bread"
- desc = "Some plain old earthen bread."
- icon_state = "bread"
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 7)
- list_reagents = list(/datum/reagent/consumable/nutriment = 10)
- custom_food_type = /obj/item/reagent_containers/food/snacks/customizable/bread
- slice_path = /obj/item/reagent_containers/food/snacks/breadslice/plain
- tastes = list("bread" = 10)
- foodtype = GRAIN
-
-/obj/item/reagent_containers/food/snacks/breadslice/plain
- name = "bread slice"
- desc = "A slice of home."
- icon_state = "breadslice"
- customfoodfilling = 1
- foodtype = GRAIN
-
-/obj/item/reagent_containers/food/snacks/breadslice/moldy
- name = "moldy bread slice"
- desc = "Entire stations have been ripped apart over arguing whether this is still good to eat."
- icon_state = "moldybreadslice"
- customfoodfilling = 0
- bonus_reagents = list(/datum/reagent/consumable/mold = 10)
- tastes = list("decaying fungus" = 1)
- foodtype = GROSS
-
-/obj/item/reagent_containers/food/snacks/store/bread/meat
- name = "meatbread loaf"
- desc = "The culinary base of every self-respecting eloquen/tg/entleman."
- icon_state = "meatbread"
- slice_path = /obj/item/reagent_containers/food/snacks/breadslice/meat
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 5, /datum/reagent/consumable/nutriment/vitamin = 10)
- list_reagents = list(/datum/reagent/consumable/nutriment = 30, /datum/reagent/consumable/nutriment/vitamin = 5)
- tastes = list("bread" = 10, "meat" = 10)
- foodtype = GRAIN | MEAT
-
-/obj/item/reagent_containers/food/snacks/breadslice/meat
- name = "meatbread slice"
- desc = "A slice of delicious meatbread."
- icon_state = "meatbreadslice"
- foodtype = GRAIN | MEAT
-
-/obj/item/reagent_containers/food/snacks/store/bread/xenomeat
- name = "xenomeatbread loaf"
- desc = "The culinary base of every self-respecting eloquen/tg/entleman. Extra Heretical."
- icon_state = "xenomeatbread"
- slice_path = /obj/item/reagent_containers/food/snacks/breadslice/xenomeat
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 5, /datum/reagent/consumable/nutriment/vitamin = 10)
- list_reagents = list(/datum/reagent/consumable/nutriment = 30, /datum/reagent/consumable/nutriment/vitamin = 5)
- tastes = list("bread" = 10, "acid" = 10)
- foodtype = GRAIN | MEAT
-
-/obj/item/reagent_containers/food/snacks/breadslice/xenomeat
- name = "xenomeatbread slice"
- desc = "A slice of delicious meatbread. Extra Heretical."
- icon_state = "xenobreadslice"
- filling_color = "#32CD32"
- list_reagents = list(/datum/reagent/consumable/nutriment = 6, /datum/reagent/consumable/nutriment/vitamin = 1)
- foodtype = GRAIN | MEAT
-
-/obj/item/reagent_containers/food/snacks/store/bread/spidermeat
- name = "spider meat loaf"
- desc = "Reassuringly green meatloaf made from spider meat."
- icon_state = "spidermeatbread"
- slice_path = /obj/item/reagent_containers/food/snacks/breadslice/spidermeat
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 5, /datum/reagent/consumable/nutriment/vitamin = 10)
- list_reagents = list(/datum/reagent/consumable/nutriment = 30, /datum/reagent/toxin = 15, /datum/reagent/consumable/nutriment/vitamin = 5)
- tastes = list("bread" = 10, "cobwebs" = 5)
- foodtype = GRAIN | MEAT | TOXIC
-
-/obj/item/reagent_containers/food/snacks/breadslice/spidermeat
- name = "spider meat bread slice"
- desc = "A slice of meatloaf made from an animal that most likely still wants you dead."
- icon_state = "spiderbreadslice"
- filling_color = "#7CFC00"
- list_reagents = list(/datum/reagent/consumable/nutriment = 6, /datum/reagent/toxin = 3, /datum/reagent/consumable/nutriment/vitamin = 1)
- foodtype = GRAIN | MEAT | TOXIC
-
-/obj/item/reagent_containers/food/snacks/store/bread/banana
- name = "banana-nut bread"
- desc = "A heavenly and filling treat."
- icon_state = "bananabread"
- slice_path = /obj/item/reagent_containers/food/snacks/breadslice/banana
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 5, /datum/reagent/consumable/banana = 20)
- list_reagents = list(/datum/reagent/consumable/nutriment = 20, /datum/reagent/consumable/banana = 20)
- tastes = list("bread" = 10) // bananjuice will also flavour
- foodtype = GRAIN | FRUIT
-
-
-/obj/item/reagent_containers/food/snacks/breadslice/banana
- name = "banana-nut bread slice"
- desc = "A slice of delicious banana bread."
- icon_state = "bananabreadslice"
- filling_color = "#FFD700"
- list_reagents = list(/datum/reagent/consumable/nutriment = 4, /datum/reagent/consumable/banana = 4)
- foodtype = GRAIN | FRUIT
-
-/obj/item/reagent_containers/food/snacks/store/bread/tofu
- name = "Tofubread"
- desc = "Like meatbread but for vegetarians. Not guaranteed to give superpowers."
- icon_state = "tofubread"
- slice_path = /obj/item/reagent_containers/food/snacks/breadslice/tofu
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 5, /datum/reagent/consumable/nutriment/vitamin = 10)
- list_reagents = list(/datum/reagent/consumable/nutriment = 20, /datum/reagent/consumable/nutriment/vitamin = 5)
- tastes = list("bread" = 10, "tofu" = 10)
- foodtype = GRAIN | VEGETABLES
-
-/obj/item/reagent_containers/food/snacks/breadslice/tofu
- name = "tofubread slice"
- desc = "A slice of delicious tofubread."
- icon_state = "tofubreadslice"
- filling_color = "#FF8C00"
- list_reagents = list(/datum/reagent/consumable/nutriment = 4, /datum/reagent/consumable/nutriment/vitamin = 1)
- foodtype = GRAIN | VEGETABLES
-
-/obj/item/reagent_containers/food/snacks/store/bread/creamcheese
- name = "cream cheese bread"
- desc = "Yum yum yum!"
- icon_state = "creamcheesebread"
- slice_path = /obj/item/reagent_containers/food/snacks/breadslice/creamcheese
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 5, /datum/reagent/consumable/nutriment/vitamin = 5)
- list_reagents = list(/datum/reagent/consumable/nutriment = 20, /datum/reagent/consumable/nutriment/vitamin = 5)
- tastes = list("bread" = 10, "cheese" = 10)
- foodtype = GRAIN | DAIRY
-
-/obj/item/reagent_containers/food/snacks/breadslice/creamcheese
- name = "cream cheese bread slice"
- desc = "A slice of yum!"
- icon_state = "creamcheesebreadslice"
- filling_color = "#FF8C00"
- list_reagents = list(/datum/reagent/consumable/nutriment = 4, /datum/reagent/consumable/nutriment/vitamin = 1)
- foodtype = GRAIN | DAIRY
-
-/obj/item/reagent_containers/food/snacks/store/bread/mimana
- name = "mimana bread"
- desc = "Best eaten in silence."
- icon_state = "mimanabread"
- slice_path = /obj/item/reagent_containers/food/snacks/breadslice/mimana
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 5, /datum/reagent/consumable/nutriment/vitamin = 5)
- list_reagents = list(/datum/reagent/consumable/nutriment = 20, /datum/reagent/toxin/mutetoxin = 5, /datum/reagent/consumable/nothing = 5, /datum/reagent/consumable/nutriment/vitamin = 5)
- tastes = list("bread" = 10, "silence" = 10)
- foodtype = GRAIN | FRUIT
-
-/obj/item/reagent_containers/food/snacks/breadslice/mimana
- name = "mimana bread slice"
- desc = "A slice of silence!"
- icon_state = "mimanabreadslice"
- filling_color = "#C0C0C0"
- list_reagents = list(/datum/reagent/consumable/nutriment = 2, /datum/reagent/toxin/mutetoxin = 1, /datum/reagent/consumable/nothing = 1, /datum/reagent/consumable/nutriment/vitamin = 1)
- foodtype = GRAIN | FRUIT
-
-/obj/item/reagent_containers/food/snacks/breadslice/custom
- name = "bread slice"
- icon_state = "tofubreadslice"
- filling_color = "#FFFFFF"
- foodtype = GRAIN
-
-/obj/item/reagent_containers/food/snacks/baguette
- name = "baguette"
- desc = "Bon appetit!"
- icon = 'icons/obj/food/burgerbread.dmi'
- icon_state = "baguette"
- item_state = "baguette"
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 2, /datum/reagent/consumable/nutriment/vitamin = 2)
- list_reagents = list(/datum/reagent/consumable/nutriment = 6, /datum/reagent/consumable/nutriment/vitamin = 1)
- bitesize = 3
- w_class = WEIGHT_CLASS_NORMAL
- slot_flags = ITEM_SLOT_BACK|ITEM_SLOT_BELT
- attack_verb = list("touche'd")
- tastes = list("bread" = 1)
- foodtype = GRAIN
-
-/obj/item/reagent_containers/food/snacks/garlicbread
- name = "garlic bread"
- desc = "Alas, it is limited."
- icon = 'icons/obj/food/burgerbread.dmi'
- icon_state = "garlicbread"
- item_state = "garlicbread"
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 5, /datum/reagent/consumable/nutriment/vitamin = 2)
- list_reagents = list(/datum/reagent/consumable/nutriment = 5, /datum/reagent/consumable/nutriment/vitamin = 4, /datum/reagent/consumable/garlic = 2)
- bitesize = 3
- tastes = list("bread" = 1, "garlic" = 1, "butter" = 1)
- foodtype = GRAIN
-
-/obj/item/reagent_containers/food/snacks/deepfryholder
- name = "Deep Fried Foods Holder Obj"
- desc = "If you can see this description the code for the deep fryer fucked up."
- icon = 'icons/obj/food/food.dmi'
- icon_state = ""
- bitesize = 2
-
-/obj/item/reagent_containers/food/snacks/deepfryholder/Initialize(mapload, obj/item/fried)
- . = ..()
- name = fried.name //We'll determine the other stuff when it's actually removed
- appearance = fried.appearance
- layer = initial(layer)
- plane = initial(plane)
- lefthand_file = fried.lefthand_file
- righthand_file = fried.righthand_file
- item_state = fried.item_state
- desc = fried.desc
- w_class = fried.w_class
- slowdown = fried.slowdown
- equip_delay_self = fried.equip_delay_self
- equip_delay_other = fried.equip_delay_other
- strip_delay = fried.strip_delay
- species_exception = fried.species_exception
- item_flags = fried.item_flags
- obj_flags = fried.obj_flags
- inhand_x_dimension = fried.inhand_x_dimension
- inhand_y_dimension = fried.inhand_y_dimension
-
- if(istype(fried, /obj/item/reagent_containers/food/snacks))
- fried.reagents.trans_to(src, fried.reagents.total_volume)
- qdel(fried)
- else
- fried.forceMove(src)
-
-/obj/item/reagent_containers/food/snacks/deepfryholder/Destroy()
- if(contents)
- QDEL_LIST(contents)
- . = ..()
-
-/obj/item/reagent_containers/food/snacks/deepfryholder/On_Consume(mob/living/eater)
- if(contents)
- QDEL_LIST(contents)
- ..()
-
-/obj/item/reagent_containers/food/snacks/deepfryholder/proc/fry(cook_time = 30)
- switch(cook_time)
- if(0 to 15)
- add_atom_colour(rgb(166,103,54), FIXED_COLOUR_PRIORITY)
- name = "lightly-fried [name]"
- desc = "[desc] It's been lightly fried in a deep fryer."
- if(16 to 49)
- add_atom_colour(rgb(103,63,24), FIXED_COLOUR_PRIORITY)
- name = "fried [name]"
- desc = "[desc] It's been fried, increasing its tastiness value by [rand(1, 75)]%."
- if(50 to 59)
- add_atom_colour(rgb(63,23,4), FIXED_COLOUR_PRIORITY)
- name = "deep-fried [name]"
- desc = "[desc] Deep-fried to perfection."
- if(60 to INFINITY)
- add_atom_colour(rgb(33,19,9), FIXED_COLOUR_PRIORITY)
- name = "\proper the physical manifestation of the very concept of fried foods"
- desc = "A heavily-fried...something. Who can tell anymore?"
- filling_color = color
- foodtype |= FRIED
-
-/obj/item/reagent_containers/food/snacks/butterbiscuit
- name = "butter biscuit"
- desc = "Well butter my biscuit!"
- icon = 'icons/obj/food/food.dmi'
- icon_state = "butterbiscuit"
- filling_color = "#F0E68C"
- list_reagents = list(/datum/reagent/consumable/nutriment = 5)
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/nutriment/vitamin = 1)
- tastes = list("butter" = 1, "biscuit" = 1)
- foodtype = GRAIN | BREAKFAST
-
-/obj/item/reagent_containers/food/snacks/butterdog
- name = "butterdog"
- desc = "Made from exotic butters."
- icon = 'icons/obj/food/food.dmi'
- icon_state = "butterdog"
- bitesize = 1
- filling_color = "#F1F49A"
- list_reagents = list(/datum/reagent/consumable/nutriment = 5)
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/nutriment/vitamin = 1)
- tastes = list("butter", "exotic butter")
- foodtype = GRAIN | DAIRY
-
-/obj/item/reagent_containers/food/snacks/butterdog/ComponentInitialize()
- . = ..()
- AddComponent(/datum/component/slippery, 80)
diff --git a/code/modules/food_and_drinks/food/snacks_cake.dm b/code/modules/food_and_drinks/food/snacks_cake.dm
deleted file mode 100644
index a048fb0e437..00000000000
--- a/code/modules/food_and_drinks/food/snacks_cake.dm
+++ /dev/null
@@ -1,431 +0,0 @@
-/obj/item/reagent_containers/food/snacks/store/cake
- icon = 'icons/obj/food/piecake.dmi'
- slice_path = /obj/item/reagent_containers/food/snacks/cakeslice/plain
- slices_num = 5
- bitesize = 3
- volume = 80
- list_reagents = list(/datum/reagent/consumable/nutriment = 20, /datum/reagent/consumable/nutriment/vitamin = 5)
- tastes = list("cake" = 1)
- foodtype = GRAIN | DAIRY
-
-/obj/item/reagent_containers/food/snacks/cakeslice
- icon = 'icons/obj/food/piecake.dmi'
- trash = /obj/item/trash/plate
- list_reagents = list(/datum/reagent/consumable/nutriment = 4, /datum/reagent/consumable/nutriment/vitamin = 1)
- customfoodfilling = 0 //to avoid infinite cake-ception
- tastes = list("cake" = 1)
- foodtype = GRAIN | DAIRY
-
-/obj/item/reagent_containers/food/snacks/store/cake/plain
- name = "plain cake"
- desc = "A plain cake, not a lie."
- icon_state = "plaincake"
- custom_food_type = /obj/item/reagent_containers/food/snacks/customizable/cake
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 10, /datum/reagent/consumable/nutriment/vitamin = 2)
- tastes = list("sweetness" = 2,"cake" = 5)
- foodtype = GRAIN | DAIRY | SUGAR
-
-/obj/item/reagent_containers/food/snacks/cakeslice/plain
- name = "plain cake slice"
- desc = "Just a slice of cake, it is enough for everyone."
- icon_state = "plaincake_slice"
- filling_color = "#FFD700"
- customfoodfilling = 1
- tastes = list("sweetness" = 2,"cake" = 5)
- foodtype = GRAIN | DAIRY | SUGAR
-
-/obj/item/reagent_containers/food/snacks/store/cake/carrot
- name = "carrot cake"
- desc = "A favorite desert of a certain wascally wabbit. Not a lie."
- icon_state = "carrotcake"
- slice_path = /obj/item/reagent_containers/food/snacks/cakeslice/carrot
- slices_num = 5
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 3, /datum/reagent/medicine/oculine = 5, /datum/reagent/consumable/nutriment/vitamin = 10)
- list_reagents = list(/datum/reagent/consumable/nutriment = 20, /datum/reagent/medicine/oculine = 10, /datum/reagent/consumable/nutriment/vitamin = 5)
- tastes = list("cake" = 5, "sweetness" = 2, "carrot" = 1)
- foodtype = GRAIN | DAIRY | VEGETABLES | SUGAR
-
-/obj/item/reagent_containers/food/snacks/cakeslice/carrot
- name = "carrot cake slice"
- desc = "Carrotty slice of Carrot Cake, carrots are good for your eyes! Also not a lie."
- icon_state = "carrotcake_slice"
- filling_color = "#FFA500"
- list_reagents = list(/datum/reagent/consumable/nutriment = 4, /datum/reagent/medicine/oculine = 2, /datum/reagent/consumable/nutriment/vitamin = 1)
- tastes = list("cake" = 5, "sweetness" = 2, "carrot" = 1)
- foodtype = GRAIN | DAIRY | VEGETABLES | SUGAR
-
-/obj/item/reagent_containers/food/snacks/store/cake/brain
- name = "brain cake"
- desc = "A squishy cake-thing."
- icon_state = "braincake"
- slice_path = /obj/item/reagent_containers/food/snacks/cakeslice/brain
- slices_num = 5
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 5, /datum/reagent/medicine/mannitol = 10, /datum/reagent/consumable/nutriment/vitamin = 10)
- list_reagents = list(/datum/reagent/consumable/nutriment = 20, /datum/reagent/medicine/mannitol = 10, /datum/reagent/consumable/nutriment/vitamin = 5)
- tastes = list("cake" = 5, "sweetness" = 2, "brains" = 1)
- foodtype = GRAIN | DAIRY | MEAT | GROSS | SUGAR
-
-/obj/item/reagent_containers/food/snacks/cakeslice/brain
- name = "brain cake slice"
- desc = "Lemme tell you something about prions. THEY'RE DELICIOUS."
- icon_state = "braincakeslice"
- filling_color = "#FF69B4"
- list_reagents = list(/datum/reagent/consumable/nutriment = 4, /datum/reagent/medicine/mannitol = 2, /datum/reagent/consumable/nutriment/vitamin = 1)
- tastes = list("cake" = 5, "sweetness" = 2, "brains" = 1)
- foodtype = GRAIN | DAIRY | MEAT | GROSS | SUGAR
-
-/obj/item/reagent_containers/food/snacks/store/cake/cheese
- name = "cheese cake"
- desc = "DANGEROUSLY cheesy."
- icon_state = "cheesecake"
- slice_path = /obj/item/reagent_containers/food/snacks/cakeslice/cheese
- slices_num = 5
- bonus_reagents = list(/datum/reagent/consumable/nutriment/vitamin = 10)
- tastes = list("cake" = 4, "cream cheese" = 3)
- foodtype = GRAIN | DAIRY
-
-/obj/item/reagent_containers/food/snacks/cakeslice/cheese
- name = "cheese cake slice"
- desc = "Slice of pure cheestisfaction."
- icon_state = "cheesecake_slice"
- filling_color = "#FFFACD"
- tastes = list("cake" = 4, "cream cheese" = 3)
- foodtype = GRAIN | DAIRY
-
-/obj/item/reagent_containers/food/snacks/store/cake/orange
- name = "orange cake"
- desc = "A cake with added orange."
- icon_state = "orangecake"
- slice_path = /obj/item/reagent_containers/food/snacks/cakeslice/orange
- slices_num = 5
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 3, /datum/reagent/consumable/nutriment/vitamin = 10)
- tastes = list("cake" = 5, "sweetness" = 2, "oranges" = 2)
- foodtype = GRAIN | DAIRY | FRUIT | SUGAR
-
-/obj/item/reagent_containers/food/snacks/cakeslice/orange
- name = "orange cake slice"
- desc = "Just a slice of cake, it is enough for everyone."
- icon_state = "orangecake_slice"
- filling_color = "#FFA500"
- tastes = list("cake" = 5, "sweetness" = 2, "oranges" = 2)
- foodtype = GRAIN | DAIRY | FRUIT | SUGAR
-
-/obj/item/reagent_containers/food/snacks/store/cake/lime
- name = "lime cake"
- desc = "A cake with added lime."
- icon_state = "limecake"
- slice_path = /obj/item/reagent_containers/food/snacks/cakeslice/lime
- slices_num = 5
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 3, /datum/reagent/consumable/nutriment/vitamin = 10)
- tastes = list("cake" = 5, "sweetness" = 2, "unbearable sourness" = 2)
- foodtype = GRAIN | DAIRY | FRUIT | SUGAR
-
-/obj/item/reagent_containers/food/snacks/cakeslice/lime
- name = "lime cake slice"
- desc = "Just a slice of cake, it is enough for everyone."
- icon_state = "limecake_slice"
- filling_color = "#00FF00"
- tastes = list("cake" = 5, "sweetness" = 2, "unbearable sourness" = 2)
- foodtype = GRAIN | DAIRY | FRUIT | SUGAR
-
-/obj/item/reagent_containers/food/snacks/store/cake/lemon
- name = "lemon cake"
- desc = "A cake with added lemon."
- icon_state = "lemoncake"
- slice_path = /obj/item/reagent_containers/food/snacks/cakeslice/lemon
- slices_num = 5
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 3, /datum/reagent/consumable/nutriment/vitamin = 10)
- tastes = list("cake" = 5, "sweetness" = 2, "sourness" = 2)
- foodtype = GRAIN | DAIRY | FRUIT | SUGAR
-
-/obj/item/reagent_containers/food/snacks/cakeslice/lemon
- name = "lemon cake slice"
- desc = "Just a slice of cake, it is enough for everyone."
- icon_state = "lemoncake_slice"
- filling_color = "#FFEE00"
- tastes = list("cake" = 5, "sweetness" = 2, "sourness" = 2)
- foodtype = GRAIN | DAIRY | FRUIT | SUGAR
-
-/obj/item/reagent_containers/food/snacks/store/cake/chocolate
- name = "chocolate cake"
- desc = "A cake with added chocolate."
- icon_state = "chocolatecake"
- slice_path = /obj/item/reagent_containers/food/snacks/cakeslice/chocolate
- slices_num = 5
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 3, /datum/reagent/consumable/nutriment/vitamin = 10)
- tastes = list("cake" = 5, "sweetness" = 1, "chocolate" = 4)
- foodtype = GRAIN | DAIRY | JUNKFOOD | SUGAR
-
-/obj/item/reagent_containers/food/snacks/cakeslice/chocolate
- name = "chocolate cake slice"
- desc = "Just a slice of cake, it is enough for everyone."
- icon_state = "chocolatecake_slice"
- filling_color = "#A0522D"
- tastes = list("cake" = 5, "sweetness" = 1, "chocolate" = 4)
- foodtype = GRAIN | DAIRY | JUNKFOOD | SUGAR
-
-/obj/item/reagent_containers/food/snacks/store/cake/birthday
- name = "birthday cake"
- desc = "Happy Birthday little clown..."
- icon_state = "birthdaycake"
- slice_path = /obj/item/reagent_containers/food/snacks/cakeslice/birthday
- slices_num = 5
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 7, /datum/reagent/consumable/sprinkles = 10, /datum/reagent/consumable/nutriment/vitamin = 5)
- list_reagents = list(/datum/reagent/consumable/nutriment = 20, /datum/reagent/consumable/sprinkles = 10, /datum/reagent/consumable/nutriment/vitamin = 5)
- tastes = list("cake" = 5, "sweetness" = 1)
- foodtype = GRAIN | DAIRY | JUNKFOOD | SUGAR
-
-/obj/item/reagent_containers/food/snacks/store/cake/birthday/microwave_act(obj/machinery/microwave/M) //super sekrit club
- new /obj/item/clothing/head/hardhat/cakehat(get_turf(src))
- qdel(src)
-
-/obj/item/reagent_containers/food/snacks/cakeslice/birthday
- name = "birthday cake slice"
- desc = "A slice of your birthday."
- icon_state = "birthdaycakeslice"
- filling_color = "#DC143C"
- list_reagents = list(/datum/reagent/consumable/nutriment = 4, /datum/reagent/consumable/sprinkles = 2, /datum/reagent/consumable/nutriment/vitamin = 1)
- tastes = list("cake" = 5, "sweetness" = 1)
- foodtype = GRAIN | DAIRY | JUNKFOOD | SUGAR
-
-/obj/item/reagent_containers/food/snacks/store/cake/birthday/energy
- name = "energy cake"
- desc = "Just enough calories for a whole nuclear operative squad."
- icon_state = "energycake"
- force = 5
- hitsound = 'sound/weapons/blade1.ogg'
- slice_path = /obj/item/reagent_containers/food/snacks/cakeslice/birthday/energy
- list_reagents = list(/datum/reagent/consumable/nutriment = 10, /datum/reagent/consumable/sprinkles = 10, /datum/reagent/consumable/nutriment/vitamin = 5, /datum/reagent/consumable/pwr_game = 10, /datum/reagent/consumable/liquidelectricity = 10)
- tastes = list("cake" = 3, "a Vlad's Salad" = 1)
-
-/obj/item/reagent_containers/food/snacks/store/cake/birthday/energy/proc/energy_bite(mob/living/user)
- to_chat(user, "As you eat the cake, you accidentally hurt yourself on the embedded energy sword!")
- user.apply_damage(30,BRUTE,BODY_ZONE_HEAD)
- playsound(user, 'sound/weapons/blade1.ogg', 5, TRUE)
-
-/obj/item/reagent_containers/food/snacks/store/cake/birthday/energy/attack(mob/living/M, mob/living/user)
- . = ..()
- if(HAS_TRAIT(user, TRAIT_PACIFISM) && M != user) //Prevents pacifists from attacking others directly
- return
- energy_bite(M, user)
-
-/obj/item/reagent_containers/food/snacks/store/cake/birthday/energy/microwave_act(obj/machinery/microwave/M) //super sekriter club
- new /obj/item/clothing/head/hardhat/cakehat/energycake(get_turf(src))
- qdel(src)
-
-/obj/item/reagent_containers/food/snacks/cakeslice/birthday/energy
- name = "energy cake slice"
- desc = "For the traitor on the go."
- icon_state = "energycakeslice"
- force = 2
- hitsound = 'sound/weapons/blade1.ogg'
- filling_color = "#00FF00"
- list_reagents = list(/datum/reagent/consumable/nutriment = 4, /datum/reagent/consumable/sprinkles = 2, /datum/reagent/consumable/nutriment/vitamin = 1, /datum/reagent/consumable/pwr_game = 2, /datum/reagent/consumable/liquidelectricity = 2)
- tastes = list("cake" = 3, "a Vlad's Salad" = 1)
-
-/obj/item/reagent_containers/food/snacks/cakeslice/birthday/energy/proc/energy_bite(mob/living/user)
- to_chat(user, "As you eat the cake slice, you accidentally hurt yourself on the embedded energy dagger!")
- user.apply_damage(18,BRUTE,BODY_ZONE_HEAD)
- playsound(user, 'sound/weapons/blade1.ogg', 5, TRUE)
-
-/obj/item/reagent_containers/food/snacks/cakeslice/birthday/energy/attack(mob/living/M, mob/living/user)
- . = ..()
- if(HAS_TRAIT(user, TRAIT_PACIFISM) && M != user) //Prevents pacifists from attacking others directly
- return
- energy_bite(M, user)
-
-/obj/item/reagent_containers/food/snacks/store/cake/apple
- name = "apple cake"
- desc = "A cake centred with Apple."
- icon_state = "applecake"
- slice_path = /obj/item/reagent_containers/food/snacks/cakeslice/apple
- slices_num = 5
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 3, /datum/reagent/consumable/nutriment/vitamin = 10)
- tastes = list("cake" = 5, "sweetness" = 1, "apple" = 1)
- foodtype = GRAIN | DAIRY | FRUIT | SUGAR
-
-/obj/item/reagent_containers/food/snacks/cakeslice/apple
- name = "apple cake slice"
- desc = "A slice of heavenly cake."
- icon_state = "applecakeslice"
- filling_color = "#FF4500"
- tastes = list("cake" = 5, "sweetness" = 1, "apple" = 1)
- foodtype = GRAIN | DAIRY | FRUIT | SUGAR
-
-/obj/item/reagent_containers/food/snacks/cakeslice/custom
- name = "cake slice"
- icon_state = "plaincake_slice"
- filling_color = "#FFFFFF"
- foodtype = GRAIN | DAIRY
-
-/obj/item/reagent_containers/food/snacks/store/cake/slimecake
- name = "Slime cake"
- desc = "A cake made of slimes. Probably not electrified."
- icon_state = "slimecake"
- slice_path = /obj/item/reagent_containers/food/snacks/cakeslice/slimecake
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/nutriment/vitamin = 3)
- tastes = list("cake" = 5, "sweetness" = 1, "slime" = 1)
- foodtype = GRAIN | DAIRY | SUGAR
-
-/obj/item/reagent_containers/food/snacks/cakeslice/slimecake
- name = "slime cake slice"
- desc = "A slice of slime cake."
- icon_state = "slimecake_slice"
- filling_color = "#00FFFF"
- tastes = list("cake" = 5, "sweetness" = 1, "slime" = 1)
- foodtype = GRAIN | DAIRY | SUGAR
-
-/obj/item/reagent_containers/food/snacks/store/cake/pumpkinspice
- name = "pumpkin spice cake"
- desc = "A hollow cake with real pumpkin."
- icon_state = "pumpkinspicecake"
- slice_path = /obj/item/reagent_containers/food/snacks/cakeslice/pumpkinspice
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 3, /datum/reagent/consumable/nutriment/vitamin = 5)
- tastes = list("cake" = 5, "sweetness" = 1, "pumpkin" = 1)
- foodtype = GRAIN | DAIRY | VEGETABLES | SUGAR
-
-/obj/item/reagent_containers/food/snacks/cakeslice/pumpkinspice
- name = "pumpkin spice cake slice"
- desc = "A spicy slice of pumpkin goodness."
- icon_state = "pumpkinspicecakeslice"
- filling_color = "#FFD700"
- tastes = list("cake" = 5, "sweetness" = 1, "pumpkin" = 1)
- foodtype = GRAIN | DAIRY | VEGETABLES | SUGAR
-
-/obj/item/reagent_containers/food/snacks/store/cake/bsvc // blackberry strawberries vanilla cake
- name = "blackberry and strawberry vanilla cake"
- desc = "A plain cake, filled with assortment of blackberries and strawberries!"
- icon_state = "blackbarry_strawberries_cake_vanilla_cake"
- slice_path = /obj/item/reagent_containers/food/snacks/cakeslice/bsvc
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 14, /datum/reagent/consumable/nutriment/vitamin = 4)
- tastes = list("blackberry" = 2, "strawberries" = 2, "vanilla" = 2, "sweetness" = 2, "cake" = 3)
- foodtype = GRAIN | DAIRY | FRUIT | SUGAR
-
-/obj/item/reagent_containers/food/snacks/cakeslice/bsvc
- name = "blackberry and strawberry vanilla cake slice"
- desc = "Just a slice of cake filled with assortment of blackberries and strawberries!"
- icon_state = "blackbarry_strawberries_cake_vanilla_slice"
- filling_color = "#FFD700"
- tastes = list("blackberry" = 2, "strawberries" = 2, "vanilla" = 2, "sweetness" = 2,"cake" = 3)
- foodtype = GRAIN | DAIRY | FRUIT | SUGAR
-
-/obj/item/reagent_containers/food/snacks/store/cake/bscc // blackbarry strawberries chocolate cake
- name = "blackberry and strawberry chocolate cake"
- desc = "A chocolate cake, filled with assortment of blackberries and strawberries!"
- icon_state = "blackbarry_strawberries_cake_coco_cake"
- slice_path = /obj/item/reagent_containers/food/snacks/cakeslice/bscc
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 14, /datum/reagent/consumable/nutriment/vitamin = 4, /datum/reagent/consumable/coco = 5)
- tastes = list("blackberry" = 2, "strawberries" = 2, "chocolate" = 2, "sweetness" = 2,"cake" = 3)
- foodtype = GRAIN | DAIRY | FRUIT | SUGAR
-
-/obj/item/reagent_containers/food/snacks/cakeslice/bscc
- name = "blackberry and strawberry chocolate cake slice"
- desc = "Just a slice of cake filled with assortment of blackberries and strawberries!"
- icon_state = "blackbarry_strawberries_cake_coco_slice"
- filling_color = "#FFD700"
- tastes = list("blackberry" = 2, "strawberries" = 2, "chocolate" = 2, "sweetness" = 2,"cake" = 3)
- foodtype = GRAIN | DAIRY | FRUIT | SUGAR
-
-/obj/item/reagent_containers/food/snacks/store/cake/holy_cake
- name = "angel food cake"
- desc = "A cake made for angels and chaplains alike! Contains holy water."
- icon_state = "holy_cake"
- slice_path = /obj/item/reagent_containers/food/snacks/cakeslice/holy_cake_slice
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/nutriment/vitamin = 3, /datum/reagent/water/holywater = 10)
- tastes = list("cake" = 5, "sweetness" = 1, "clouds" = 1)
- foodtype = GRAIN | DAIRY | SUGAR
-
-/obj/item/reagent_containers/food/snacks/cakeslice/holy_cake_slice
- name = "angel food cake slice"
- desc = "A slice of heavenly cake."
- icon_state = "holy_cake_slice"
- filling_color = "#00FFFF"
- tastes = list("cake" = 5, "sweetness" = 1, "clouds" = 1)
- foodtype = GRAIN | DAIRY | SUGAR
-
-/obj/item/reagent_containers/food/snacks/store/cake/pound_cake
- name = "pound cake"
- desc = "A condensed cake made for filling people up quickly."
- icon_state = "pound_cake"
- slices_num = 7 //Its ment to feed the party
- slice_path = /obj/item/reagent_containers/food/snacks/cakeslice/pound_cake_slice
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 60)
- tastes = list("cake" = 5, "sweetness" = 1, "batter" = 1)
- foodtype = GRAIN | DAIRY | SUGAR | JUNKFOOD
-
-/obj/item/reagent_containers/food/snacks/cakeslice/pound_cake_slice
- name = "pound cake slice"
- desc = "A slice of condensed cake made for filling people up quickly."
- icon_state = "pound_cake_slice"
- filling_color = "#00FFFF"
- tastes = list("cake" = 5, "sweetness" = 5, "batter" = 1)
- foodtype = GRAIN | DAIRY | SUGAR | JUNKFOOD
-
-/obj/item/reagent_containers/food/snacks/store/cake/hardware_cake
- name = "hardware cake"
- desc = "A quote on quote cake that is made with electronic boards and leaks acid..."
- icon_state = "hardware_cake"
- slice_path = /obj/item/reagent_containers/food/snacks/cakeslice/hardware_cake_slice
- bonus_reagents = list(/datum/reagent/toxin/acid = 15, /datum/reagent/fuel/oil = 15)
- tastes = list("acid" = 3, "metal" = 4, "glass" = 5)
- foodtype = GRAIN | GROSS
-
-/obj/item/reagent_containers/food/snacks/cakeslice/hardware_cake_slice
- name = "hardware cake slice"
- desc = "A slice of electronic boards and some acid."
- icon_state = "hardware_cake_slice"
- filling_color = "#00FFFF"
- tastes = list("acid" = 3, "metal" = 4, "glass" = 5)
- foodtype = GRAIN | GROSS
-
-/obj/item/reagent_containers/food/snacks/store/cake/vanilla_cake
- name = "vanilla cake"
- desc = "A vanilla frosted cake."
- icon_state = "vanillacake"
- slice_path = /obj/item/reagent_containers/food/snacks/cakeslice/vanilla_slice
- bonus_reagents = list(/datum/reagent/consumable/sugar = 15, /datum/reagent/consumable/vanilla = 15)
- tastes = list("cake" = 1, "sugar" = 1, "vanilla" = 10)
- foodtype = GRAIN | SUGAR | DAIRY
-
-/obj/item/reagent_containers/food/snacks/cakeslice/vanilla_slice
- name = "vanilla cake slice"
- desc = "A slice of vanilla frosted cake."
- icon_state = "vanillacake_slice"
- filling_color = "#00FFFF"
- tastes = list("cake" = 1, "sugar" = 1, "vanilla" = 10)
- foodtype = GRAIN | SUGAR | DAIRY
-
-/obj/item/reagent_containers/food/snacks/store/cake/clown_cake
- name = "clown cake"
- desc = "A funny cake with a clown face on it."
- icon_state = "clowncake"
- slice_path = /obj/item/reagent_containers/food/snacks/cakeslice/clown_slice
- bonus_reagents = list(/datum/reagent/consumable/sugar = 15)
- tastes = list("cake" = 1, "sugar" = 1, "joy" = 10)
- foodtype = GRAIN | SUGAR | DAIRY
-
-/obj/item/reagent_containers/food/snacks/cakeslice/clown_slice
- name = "clown cake slice"
- desc = "A slice of bad jokes, and silly props."
- icon_state = "clowncake_slice"
- filling_color = "#00FFFF"
- tastes = list("cake" = 1, "sugar" = 1, "joy" = 10)
- foodtype = GRAIN | SUGAR | DAIRY
-
-/obj/item/reagent_containers/food/snacks/store/cake/trumpet
- name = "spaceman's cake"
- desc = "A spaceman's trumpet frosted cake."
- icon_state = "trumpetcake"
- slice_path = /obj/item/reagent_containers/food/snacks/cakeslice/trumpet
- bonus_reagents = list(/datum/reagent/medicine/polypyr = 15, /datum/reagent/consumable/cream = 5, /datum/reagent/consumable/nutriment/vitamin = 5, /datum/reagent/consumable/berryjuice = 5)
- filling_color = "#7A3D80"
- tastes = list("cake" = 4, "violets" = 2, "jam" = 2)
- foodtype = GRAIN | DAIRY | FRUIT | SUGAR
-
-/obj/item/reagent_containers/food/snacks/cakeslice/trumpet
- name = "spaceman's cake"
- desc = "A spaceman's trumpet frosted cake."
- icon_state = "trumpetcakeslice"
- filling_color = "#7A3D80"
- tastes = list("cake" = 4, "violets" = 2, "jam" = 2)
- foodtype = GRAIN | DAIRY | FRUIT | SUGAR
diff --git a/code/modules/food_and_drinks/food/snacks_egg.dm b/code/modules/food_and_drinks/food/snacks_egg.dm
index 360053c28ca..665d94e1fa9 100644
--- a/code/modules/food_and_drinks/food/snacks_egg.dm
+++ b/code/modules/food_and_drinks/food/snacks_egg.dm
@@ -10,6 +10,8 @@
filling_color = "#A0522D"
tastes = list("chocolate" = 4, "sweetness" = 1)
foodtype = JUNKFOOD | SUGAR
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_TINY
/obj/item/reagent_containers/food/snacks/egg
name = "egg"
@@ -18,7 +20,8 @@
list_reagents = list(/datum/reagent/consumable/eggyolk = 5)
cooked_type = /obj/item/reagent_containers/food/snacks/boiledegg
filling_color = "#F0E68C"
- foodtype = MEAT
+ foodtype = MEAT | RAW
+ w_class = WEIGHT_CLASS_TINY
grind_results = list()
var/static/chick_count = 0 //I copied this from the chicken_count (note the "en" in there) variable from chicken code.
@@ -106,6 +109,8 @@
list_reagents = list(/datum/reagent/consumable/nutriment = 2, /datum/reagent/consumable/nutriment/vitamin = 1)
tastes = list("egg" = 1)
foodtype = MEAT | BREAKFAST
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_TINY
/obj/item/reagent_containers/food/snacks/omelette //FUCK THIS
name = "omelette du fromage"
diff --git a/code/modules/food_and_drinks/food/snacks_frozen.dm b/code/modules/food_and_drinks/food/snacks_frozen.dm
index 930fabc3177..da4e7d8d5d0 100644
--- a/code/modules/food_and_drinks/food/snacks_frozen.dm
+++ b/code/modules/food_and_drinks/food/snacks_frozen.dm
@@ -8,20 +8,24 @@
desc = "Portable Ice-cream in its own packaging."
icon = 'icons/obj/food/frozen_treats.dmi'
icon_state = "icecreamsandwich"
+ w_class = WEIGHT_CLASS_TINY
bonus_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/ice = 2)
list_reagents = list(/datum/reagent/consumable/nutriment = 2, /datum/reagent/consumable/ice = 2)
tastes = list("ice cream" = 1)
foodtype = GRAIN | DAIRY | SUGAR
+ /*food_flags = FOOD_FINGER_FOOD*/
/obj/item/reagent_containers/food/snacks/strawberryicecreamsandwich
name = "strawberry ice cream sandwich"
desc = "Portable ice-cream in its own packaging of the strawberry variety."
icon = 'icons/obj/food/frozen_treats.dmi'
icon_state = "strawberryicecreamsandwich"
+ w_class = WEIGHT_CLASS_TINY
bonus_reagents = list(/datum/reagent/consumable/nutriment = 2, /datum/reagent/consumable/ice = 2)
list_reagents = list(/datum/reagent/consumable/nutriment = 2, /datum/reagent/consumable/ice = 2)
tastes = list("ice cream" = 2, "berry" = 2)
foodtype = FRUIT | DAIRY | SUGAR
+ /*food_flags = FOOD_FINGER_FOOD*/
/obj/item/reagent_containers/food/snacks/spacefreezy
@@ -29,6 +33,7 @@
desc = "The best icecream in space."
icon = 'icons/obj/food/frozen_treats.dmi'
icon_state = "spacefreezy"
+ w_class = WEIGHT_CLASS_TINY
bonus_reagents = list(/datum/reagent/consumable/nutriment = 2, /datum/reagent/consumable/nutriment/vitamin = 2)
list_reagents = list(/datum/reagent/consumable/nutriment = 6, /datum/reagent/consumable/bluecherryjelly = 5, /datum/reagent/consumable/nutriment/vitamin = 4)
filling_color = "#87CEFA"
@@ -40,6 +45,7 @@
desc = "A classic dessert."
icon = 'icons/obj/food/frozen_treats.dmi'
icon_state = "sundae"
+ w_class = WEIGHT_CLASS_SMALL
bonus_reagents = list(/datum/reagent/consumable/nutriment = 2, /datum/reagent/consumable/nutriment/vitamin = 1)
list_reagents = list(/datum/reagent/consumable/nutriment = 6, /datum/reagent/consumable/banana = 5, /datum/reagent/consumable/nutriment/vitamin = 2)
filling_color = "#FFFACD"
@@ -66,12 +72,14 @@
desc = "It's just shaved ice. Still fun to chew on."
icon = 'icons/obj/food/frozen_treats.dmi'
icon_state = "flavorless_sc"
+ w_class = WEIGHT_CLASS_SMALL
trash = /obj/item/reagent_containers/food/drinks/sillycup //We dont eat paper cups
bonus_reagents = list(/datum/reagent/water = 10) //Base line will allways give water
list_reagents = list(/datum/reagent/water = 1) // We dont get food for water/juices
filling_color = "#FFFFFF" //Ice is white
tastes = list("ice" = 1, "water" = 1)
foodtype = SUGAR //We use SUGAR as a base line to act in as junkfood, other wise we use fruit
+ /*food_flags = FOOD_FINGER_FOOD*/
/obj/item/reagent_containers/food/snacks/snowcones/lime
name = "lime snowcone"
diff --git a/code/modules/food_and_drinks/food/snacks_meat.dm b/code/modules/food_and_drinks/food/snacks_meat.dm
index 510130ce08b..c088b225961 100644
--- a/code/modules/food_and_drinks/food/snacks_meat.dm
+++ b/code/modules/food_and_drinks/food/snacks_meat.dm
@@ -211,6 +211,8 @@
filling_color = "#800000"
tastes = list("meat" = 1)
foodtype = MEAT
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_SMALL
/obj/item/reagent_containers/food/snacks/sausage
name = "sausage"
@@ -223,6 +225,7 @@
slices_num = 6
slice_path = /obj/item/reagent_containers/food/snacks/salami
foodtype = MEAT | BREAKFAST
+ /*food_flags = FOOD_FINGER_FOOD*/
var/roasted = FALSE
/obj/item/reagent_containers/food/snacks/sausage/Initialize()
@@ -266,6 +269,8 @@
filling_color = "#CD853F"
tastes = list("the jungle" = 1, "bananas" = 1)
foodtype = MEAT | SUGAR
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_TINY
var/faction
var/spawned_mob = /mob/living/carbon/monkey
custom_price = 300
@@ -359,6 +364,8 @@
list_reagents = list(/datum/reagent/consumable/nutriment = 2)
tastes = list("\"chicken\"" = 1)
foodtype = MEAT
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_TINY
/obj/item/reagent_containers/food/snacks/nugget/Initialize()
. = ..()
diff --git a/code/modules/food_and_drinks/food/snacks_other.dm b/code/modules/food_and_drinks/food/snacks_other.dm
index 4a1bf4b1284..0759cac8df7 100644
--- a/code/modules/food_and_drinks/food/snacks_other.dm
+++ b/code/modules/food_and_drinks/food/snacks_other.dm
@@ -37,7 +37,9 @@
filling_color = "#FF1493"
tastes = list("watermelon" = 1)
foodtype = FRUIT
+ /*food_flags = FOOD_FINGER_FOOD*/
juice_results = list(/datum/reagent/consumable/watermelonjuice = 5)
+ w_class = WEIGHT_CLASS_SMALL
/obj/item/reagent_containers/food/snacks/candy_corn
name = "candy corn"
@@ -47,6 +49,8 @@
filling_color = "#FF8C00"
tastes = list("candy corn" = 1)
foodtype = JUNKFOOD | SUGAR
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_TINY
/obj/item/reagent_containers/food/snacks/candy_corn/prison
name = "desiccated candy corn"
@@ -64,6 +68,8 @@
filling_color = "#A0522D"
tastes = list("chocolate" = 1)
foodtype = JUNKFOOD | SUGAR
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_TINY
/obj/item/reagent_containers/food/snacks/hugemushroomslice
name = "huge mushroom slice"
@@ -192,6 +198,8 @@
list_reagents = list(/datum/reagent/toxin/minttoxin = 2)
filling_color = "#800000"
foodtype = TOXIC | SUGAR
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_TINY
/obj/item/reagent_containers/food/snacks/eggwrap
name = "egg wrap"
@@ -229,6 +237,8 @@
filling_color = "#00800"
tastes = list("cobwebs" = 1, "sugar" = 2)
foodtype = JUNKFOOD | SUGAR
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_TINY
/obj/item/reagent_containers/food/snacks/chococoin
name = "chocolate coin"
@@ -239,6 +249,8 @@
filling_color = "#A0522D"
tastes = list("chocolate" = 1)
foodtype = JUNKFOOD | SUGAR
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_SMALL
/obj/item/reagent_containers/food/snacks/fudgedice
name = "fudge dice"
@@ -250,6 +262,8 @@
trash = /obj/item/dice/fudge
tastes = list("fudge" = 1)
foodtype = JUNKFOOD | SUGAR
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_SMALL
/obj/item/reagent_containers/food/snacks/chocoorange
name = "chocolate orange"
@@ -260,6 +274,8 @@
filling_color = "#A0522D"
tastes = list("chocolate" = 3, "oranges" = 1)
foodtype = JUNKFOOD | SUGAR
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_SMALL
/obj/item/reagent_containers/food/snacks/eggplantparm
name = "eggplant parmigiana"
@@ -404,6 +420,8 @@
filling_color = "#F2CE91"
tastes = list("oats" = 3, "nuts" = 2, "honey" = 1)
foodtype = GRAIN | SUGAR
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_SMALL
/obj/item/reagent_containers/food/snacks/stuffedlegion
name = "stuffed legion"
@@ -484,6 +502,8 @@
next_succ = 0
tastes = list("candy" = 1)
foodtype = JUNKFOOD | SUGAR
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_TINY
/obj/item/reagent_containers/food/snacks/chewable/lollipop/Initialize()
. = ..()
@@ -575,6 +595,8 @@
list_reagents = list(/datum/reagent/consumable/sugar = 5, /datum/reagent/medicine/bicaridine = 2, /datum/reagent/medicine/kelotane = 2) //Kek
tastes = list("candy")
foodtype = JUNKFOOD
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_TINY
/obj/item/reagent_containers/food/snacks/gumball/Initialize()
. = ..()
@@ -655,6 +677,7 @@
desc = "delicious, golden, fatty goodness on a stick."
icon_state = "butteronastick"
trash = /obj/item/stack/rods
+ /*food_flags = FOOD_FINGER_FOOD*/
/obj/item/reagent_containers/food/snacks/onionrings
name = "onion rings"
@@ -665,6 +688,7 @@
gender = PLURAL
tastes = list("batter" = 3, "onion" = 1)
foodtype = VEGETABLES
+ w_class = WEIGHT_CLASS_SMALL
/obj/item/reagent_containers/food/snacks/pineappleslice
name = "pineapple slice"
@@ -674,6 +698,7 @@
juice_results = list(/datum/reagent/consumable/pineapplejuice = 3)
tastes = list("pineapple" = 1)
foodtype = FRUIT | PINEAPPLE
+ w_class = WEIGHT_CLASS_TINY
/obj/item/reagent_containers/food/snacks/tinychocolate
name = "chocolate"
diff --git a/code/modules/food_and_drinks/food/snacks_pastry.dm b/code/modules/food_and_drinks/food/snacks_pastry.dm
index 318de66636e..50a8355803c 100644
--- a/code/modules/food_and_drinks/food/snacks_pastry.dm
+++ b/code/modules/food_and_drinks/food/snacks_pastry.dm
@@ -13,6 +13,8 @@
filling_color = "#D2691E"
tastes = list("donut" = 1)
foodtype = JUNKFOOD | GRAIN | FRIED | SUGAR | BREAKFAST
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_SMALL
var/decorated_icon = "donut_homer"
var/is_decorated = FALSE
var/extra_reagent = null
@@ -336,6 +338,8 @@
filling_color = "#F4A460"
tastes = list("muffin" = 1)
foodtype = GRAIN | SUGAR | BREAKFAST
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_SMALL
/obj/item/reagent_containers/food/snacks/muffin/berry
name = "berry muffin"
@@ -420,6 +424,8 @@
filling_color = "#CD853F"
tastes = list("meat" = 2, "dough" = 2, "laziness" = 1)
foodtype = GRAIN
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_SMALL
/obj/item/reagent_containers/food/snacks/donkpocket/warm
name = "warm Donk-pocket"
@@ -565,6 +571,8 @@
filling_color = "#F0E68C"
tastes = list("cookie" = 1)
foodtype = GRAIN | SUGAR
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_SMALL
/obj/item/reagent_containers/food/snacks/cookie/Initialize()
. = ..()
@@ -583,6 +591,8 @@
filling_color = "#F4A460"
tastes = list("cookie" = 1)
foodtype = GRAIN | SUGAR
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_TINY
/obj/item/reagent_containers/food/snacks/fortunecookie/proc/get_fortune()
var/atom/drop_location = drop_location()
@@ -613,6 +623,8 @@
filling_color = "#F0E68C"
tastes = list("pretzel" = 1)
foodtype = GRAIN | SUGAR
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_SMALL
/obj/item/reagent_containers/food/snacks/plumphelmetbiscuit
name = "plump helmet biscuit"
@@ -623,6 +635,8 @@
filling_color = "#F0E68C"
tastes = list("mushroom" = 1, "biscuit" = 1)
foodtype = GRAIN | VEGETABLES
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_SMALL
/obj/item/reagent_containers/food/snacks/plumphelmetbiscuit/Initialize()
var/fey = prob(10)
@@ -644,6 +658,8 @@
filling_color = "#F0E68C"
tastes = list("cracker" = 1)
foodtype = GRAIN
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_TINY
/obj/item/reagent_containers/food/snacks/hotdog
name = "hotdog"
@@ -732,21 +748,19 @@
name = "cherry cupcake"
desc = "A sweet cupcake with cherry bits."
icon_state = "cherrycupcake"
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/nutriment/vitamin = 1)
+ bonus_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/nutriment/vitamin = 3)
list_reagents = list(/datum/reagent/consumable/nutriment = 5, /datum/reagent/consumable/nutriment/vitamin = 1)
filling_color = "#F0E68C"
tastes = list("cake" = 3, "cherry" = 1)
foodtype = GRAIN | FRUIT | SUGAR
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_SMALL
-/obj/item/reagent_containers/food/snacks/bluecherrycupcake
+/obj/item/reagent_containers/food/snacks/cherrycupcake/blue
name = "blue cherry cupcake"
desc = "Blue cherries inside a delicious cupcake."
icon_state = "bluecherrycupcake"
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/nutriment/vitamin = 3)
- list_reagents = list(/datum/reagent/consumable/nutriment = 5, /datum/reagent/consumable/nutriment/vitamin = 1)
- filling_color = "#F0E68C"
tastes = list("cake" = 3, "blue cherry" = 1)
- foodtype = GRAIN | FRUIT | SUGAR
/obj/item/reagent_containers/food/snacks/honeybun
name = "honey bun"
diff --git a/code/modules/food_and_drinks/food/snacks_sandwichtoast.dm b/code/modules/food_and_drinks/food/snacks_sandwichtoast.dm
index c15a6606be9..169d208bf30 100644
--- a/code/modules/food_and_drinks/food/snacks_sandwichtoast.dm
+++ b/code/modules/food_and_drinks/food/snacks_sandwichtoast.dm
@@ -9,6 +9,8 @@
cooked_type = /obj/item/reagent_containers/food/snacks/toastedsandwich
tastes = list("meat" = 2, "cheese" = 1, "bread" = 2, "lettuce" = 1)
foodtype = GRAIN | VEGETABLES
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_SMALL
/obj/item/reagent_containers/food/snacks/toastedsandwich
name = "toasted sandwich"
@@ -31,6 +33,8 @@
list_reagents = list(/datum/reagent/consumable/nutriment = 6, /datum/reagent/consumable/nutriment/vitamin = 1)
tastes = list("toast" = 1, "cheese" = 1)
foodtype = GRAIN | DAIRY
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_SMALL
/obj/item/reagent_containers/food/snacks/jellysandwich
name = "jelly sandwich"
diff --git a/code/modules/food_and_drinks/food/snacks_spaghetti.dm b/code/modules/food_and_drinks/food/snacks_spaghetti.dm
deleted file mode 100644
index 88c1188f1de..00000000000
--- a/code/modules/food_and_drinks/food/snacks_spaghetti.dm
+++ /dev/null
@@ -1,106 +0,0 @@
-
-/obj/item/reagent_containers/food/snacks/spaghetti
- name = "spaghetti"
- desc = "Now that's a nic'e pasta!"
- icon = 'icons/obj/food/pizzaspaghetti.dmi'
- icon_state = "spaghetti"
- list_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/nutriment/vitamin = 1)
- cooked_type = /obj/item/reagent_containers/food/snacks/spaghetti/boiledspaghetti
- filling_color = "#F0E68C"
- tastes = list("pasta" = 1)
- foodtype = GRAIN
-
-/obj/item/reagent_containers/food/snacks/spaghetti/Initialize()
- . = ..()
- if(!cooked_type) // This isn't cooked, why would you put uncooked spaghetti in your pocket?
- var/list/display_message = list(
- "Something wet falls out of their pocket and hits the ground. Is that... [name]?",
- "Oh shit! All your pocket [name] fell out!")
- AddComponent(/datum/component/spill, display_message, 'sound/effects/splat.ogg')
-
-/obj/item/reagent_containers/food/snacks/spaghetti/boiledspaghetti
- name = "boiled spaghetti"
- desc = "A plain dish of noodles, this needs more ingredients."
- icon_state = "spaghettiboiled"
- trash = /obj/item/trash/plate
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 2)
- list_reagents = list(/datum/reagent/consumable/nutriment = 2, /datum/reagent/consumable/nutriment/vitamin = 1)
- cooked_type = null
- custom_food_type = /obj/item/reagent_containers/food/snacks/customizable/pasta
-
-/obj/item/reagent_containers/food/snacks/spaghetti/pastatomato
- name = "spaghetti"
- desc = "Spaghetti and crushed tomatoes. Just like your abusive father used to make!"
- icon_state = "pastatomato"
- trash = /obj/item/trash/plate
- bitesize = 4
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/tomatojuice = 10, /datum/reagent/consumable/nutriment/vitamin = 4)
- list_reagents = list(/datum/reagent/consumable/nutriment = 6, /datum/reagent/consumable/tomatojuice = 10, /datum/reagent/consumable/nutriment/vitamin = 4)
- cooked_type = null
- filling_color = "#DC143C"
- tastes = list("pasta" = 1, "tomato" = 1)
- foodtype = GRAIN | VEGETABLES
-
-/obj/item/reagent_containers/food/snacks/spaghetti/copypasta
- name = "copypasta"
- desc = "You probably shouldn't try this, you always hear people talking about how bad it is..."
- icon_state = "copypasta"
- trash = /obj/item/trash/plate
- bitesize = 4
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/nutriment/vitamin = 4)
- list_reagents = list(/datum/reagent/consumable/nutriment = 12, /datum/reagent/consumable/tomatojuice = 20, /datum/reagent/consumable/nutriment/vitamin = 8)
- cooked_type = null
- filling_color = "#DC143C"
- tastes = list("pasta" = 1, "tomato" = 1)
- foodtype = GRAIN | VEGETABLES
-
-/obj/item/reagent_containers/food/snacks/spaghetti/meatballspaghetti
- name = "spaghetti and meatballs"
- desc = "Now that's a nic'e meatball!"
- icon_state = "meatballspaghetti"
- trash = /obj/item/trash/plate
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/nutriment/vitamin = 4)
- list_reagents = list(/datum/reagent/consumable/nutriment = 8, /datum/reagent/consumable/nutriment/vitamin = 4)
- cooked_type = null
- tastes = list("pasta" = 1, "tomato" = 1, "meat" = 1)
- foodtype = GRAIN | MEAT
-
-/obj/item/reagent_containers/food/snacks/spaghetti/spesslaw
- name = "spesslaw"
- desc = "A lawyers favourite."
- icon_state = "spesslaw"
- trash = /obj/item/trash/plate
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/nutriment/vitamin = 6)
- list_reagents = list(/datum/reagent/consumable/nutriment = 8, /datum/reagent/consumable/nutriment/vitamin = 6)
- cooked_type = null
- tastes = list("pasta" = 1, "tomato" = 1, "meat" = 1)
-
-/obj/item/reagent_containers/food/snacks/spaghetti/chowmein
- name = "chow mein"
- desc = "A nice mix of noodles and fried vegetables."
- icon_state = "chowmein"
- trash = /obj/item/trash/plate
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 3, /datum/reagent/consumable/nutriment/vitamin = 4)
- list_reagents = list(/datum/reagent/consumable/nutriment = 7, /datum/reagent/consumable/nutriment/vitamin = 6)
- cooked_type = null
- tastes = list("noodle" = 1, "tomato" = 1)
-
-/obj/item/reagent_containers/food/snacks/spaghetti/beefnoodle
- name = "beef noodle"
- desc = "Nutritious, beefy and noodly."
- icon_state = "beefnoodle"
- trash = /obj/item/reagent_containers/glass/bowl
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 5, /datum/reagent/consumable/nutriment/vitamin = 6, /datum/reagent/liquidgibs = 3)
- cooked_type = null
- tastes = list("noodle" = 1, "meat" = 1)
- foodtype = GRAIN | MEAT
-
-/obj/item/reagent_containers/food/snacks/spaghetti/butternoodles
- name = "butter noodles"
- desc = "Noodles covered in savory butter. Simple and slippery, but delicious."
- icon_state = "butternoodles"
- trash = /obj/item/trash/plate
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 8, /datum/reagent/consumable/nutriment/vitamin = 1)
- cooked_type = null
- tastes = list("noodle" = 1, "butter" = 1)
- foodtype = GRAIN | DAIRY
diff --git a/code/modules/food_and_drinks/food/snacks_vend.dm b/code/modules/food_and_drinks/food/snacks_vend.dm
index 94477d1932a..b071add1027 100644
--- a/code/modules/food_and_drinks/food/snacks_vend.dm
+++ b/code/modules/food_and_drinks/food/snacks_vend.dm
@@ -12,6 +12,8 @@
filling_color = "#D2691E"
tastes = list("candy" = 1)
foodtype = JUNKFOOD | SUGAR
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_TINY
/obj/item/reagent_containers/food/snacks/candy/bronx
name = "South Bronx Paradise bar"
@@ -82,7 +84,9 @@
filling_color = "#8B0000"
tastes = list("dried raisins" = 1)
foodtype = JUNKFOOD | FRUIT | SUGAR
+ /*food_flags = FOOD_FINGER_FOOD*/
custom_price = 90
+ w_class = WEIGHT_CLASS_SMALL
/obj/item/reagent_containers/food/snacks/no_raisin/healthy
name = "homemade raisins"
@@ -99,7 +103,9 @@
junkiness = 25
filling_color = "#FFD700"
foodtype = JUNKFOOD | GRAIN | SUGAR
+ /*food_flags = FOOD_FINGER_FOOD*/
custom_price = 30
+ w_class = WEIGHT_CLASS_SMALL
/obj/item/reagent_containers/food/snacks/candy_trash
name = "candy cigarette butt"
@@ -136,6 +142,7 @@
filling_color = "#F5F5DC"
tastes = list("sweetness" = 3, "cake" = 1)
foodtype = GRAIN | FRUIT | VEGETABLES
+ w_class = WEIGHT_CLASS_SMALL
/obj/item/reagent_containers/food/snacks/energybar
name = "High-power energy bars"
@@ -146,3 +153,5 @@
filling_color = "#97ee63"
tastes = list("pure electricity" = 3, "fitness" = 2)
foodtype = TOXIC
+ /*food_flags = FOOD_FINGER_FOOD*/
+ w_class = WEIGHT_CLASS_SMALL
diff --git a/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm b/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm
index 8eccd04c840..bcee075e0dc 100644
--- a/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm
+++ b/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm
@@ -18,6 +18,8 @@
// _- _
// -
+//God bless These Deepfried States o7 -2024
+
/obj/machinery/deepfryer
name = "deep fryer"
desc = "Deep fried everything."
@@ -27,7 +29,7 @@
use_power = IDLE_POWER_USE
idle_power_usage = IDLE_DRAW_LOW
layer = BELOW_OBJ_LAYER
- var/obj/item/reagent_containers/food/snacks/deepfryholder/frying //What's being fried RIGHT NOW?
+ var/obj/item/food/deepfryholder/frying //What's being fried RIGHT NOW?
var/cook_time = 0
var/oil_use = 0.05 //How much cooking oil is used per tick
var/fry_speed = 1 //How quickly we fry food
@@ -42,7 +44,7 @@
/obj/item/weldingtool,
/obj/item/reagent_containers/glass,
/obj/item/reagent_containers/syringe,
- /obj/item/reagent_containers/food/condiment,
+ /obj/item/reagent_containers/condiment,
/obj/item/storage,
/obj/item/smallDelivery,
)
@@ -93,7 +95,7 @@
if(I.resistance_flags & INDESTRUCTIBLE)
to_chat(user, "You don't feel it would be wise to fry [I]...")
return
- if(istype(I, /obj/item/reagent_containers/food/snacks/deepfryholder))
+ if(istype(I, /obj/item/food/deepfryholder))
to_chat(user, "Your cooking skills are not up to the legendary Doublefry technique.")
return
if(default_unfasten_wrench(user, I))
@@ -105,7 +107,7 @@
return ..()
else if(!frying && user.transferItemToLoc(I, src))
to_chat(user, "You put [I] into [src].")
- frying = new/obj/item/reagent_containers/food/snacks/deepfryholder(src, I)
+ frying = new/obj/item/food/deepfryholder(src, I)
icon_state = "fryer_on"
fry_loop.start()
diff --git a/code/modules/food_and_drinks/kitchen_machinery/icecream_vat.dm b/code/modules/food_and_drinks/kitchen_machinery/icecream_vat.dm
index 1d4e366ad6d..de4d844aa3a 100644
--- a/code/modules/food_and_drinks/kitchen_machinery/icecream_vat.dm
+++ b/code/modules/food_and_drinks/kitchen_machinery/icecream_vat.dm
@@ -210,7 +210,7 @@
return
/obj/item/reagent_containers/food/snacks/icecream
- name = "ice cream cone"
+ name = "waffle cone"
desc = "Delicious waffle cone, but no ice cream."
icon = 'icons/obj/kitchen.dmi'
icon_state = "icecream_cone_waffle" //default for admin-spawned cones, href_list["cone"] should overwrite this all the time
@@ -220,6 +220,7 @@
var/cone_type
bitesize = 4
foodtype = DAIRY | SUGAR
+ /*food_flags = FOOD_FINGER_FOOD*/
/obj/item/reagent_containers/food/snacks/icecream/Initialize()
. = ..()
diff --git a/code/modules/food_and_drinks/kitchen_machinery/microwave.dm b/code/modules/food_and_drinks/kitchen_machinery/microwave.dm
index 0df04a08658..db88c5d0cc1 100644
--- a/code/modules/food_and_drinks/kitchen_machinery/microwave.dm
+++ b/code/modules/food_and_drinks/kitchen_machinery/microwave.dm
@@ -365,6 +365,7 @@
icon_state = "ration_heater"
grind_results = list(/datum/reagent/iron = 10, /datum/reagent/water = 10, /datum/reagent/consumable/sodiumchloride = 5)
heat = 3800
+ w_class = WEIGHT_CLASS_SMALL
var/obj/item/tocook = null
var/mutable_appearance/ration_overlay
var/uses = 3
diff --git a/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm b/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm
index 5852ea34742..9a6b10e0444 100644
--- a/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm
+++ b/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm
@@ -366,7 +366,7 @@
/obj/machinery/smartfridge/drinks/accept_check(obj/item/O)
if(!istype(O, /obj/item/reagent_containers) || (O.item_flags & ABSTRACT) || !O.reagents || !O.reagents.reagent_list.len)
return FALSE
- if(istype(O, /obj/item/reagent_containers/glass) || istype(O, /obj/item/reagent_containers/food/drinks) || istype(O, /obj/item/reagent_containers/food/condiment))
+ if(istype(O, /obj/item/reagent_containers/glass) || istype(O, /obj/item/reagent_containers/food/drinks) || istype(O, /obj/item/reagent_containers/condiment))
return TRUE
// ----------------------------
diff --git a/code/modules/food_and_drinks/pizzabox.dm b/code/modules/food_and_drinks/pizzabox.dm
index 772893e3ff1..b5c4c2c42b8 100644
--- a/code/modules/food_and_drinks/pizzabox.dm
+++ b/code/modules/food_and_drinks/pizzabox.dm
@@ -96,7 +96,7 @@
. += tag_overlay
/obj/item/pizzabox/worn_overlays(isinhands, icon_file)
- . = list()
+ . = ..()
var/current_offset = 2
if(isinhands)
for(var/V in boxes) //add EXTRA BOX per box
diff --git a/code/modules/food_and_drinks/recipes/processor_recipes.dm b/code/modules/food_and_drinks/recipes/processor_recipes.dm
index 55db7cf06b8..3bad9cf0c26 100644
--- a/code/modules/food_and_drinks/recipes/processor_recipes.dm
+++ b/code/modules/food_and_drinks/recipes/processor_recipes.dm
@@ -35,7 +35,7 @@
/datum/food_processor_process/spaghetti
input = /obj/item/reagent_containers/food/snacks/doughslice
- output = /obj/item/reagent_containers/food/snacks/spaghetti
+ output = /obj/item/food/spaghetti/raw
/datum/food_processor_process/corn
input = /obj/item/reagent_containers/food/snacks/grown/corn
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_bread.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_bread.dm
index f29e948adf4..0dec69a393f 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_bread.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_bread.dm
@@ -6,83 +6,83 @@
/datum/crafting_recipe/food/meatbread
name = "Meat bread"
reqs = list(
- /obj/item/reagent_containers/food/snacks/store/bread/plain = 1,
+ /obj/item/food/bread/plain = 1,
/obj/item/reagent_containers/food/snacks/meat/cutlet/plain = 3,
/obj/item/reagent_containers/food/snacks/cheesewedge = 3
)
- result = /obj/item/reagent_containers/food/snacks/store/bread/meat
+ result = /obj/item/food/bread/meat
subcategory = CAT_BREAD
/datum/crafting_recipe/food/xenomeatbread
name = "Xenomeat bread"
reqs = list(
- /obj/item/reagent_containers/food/snacks/store/bread/plain = 1,
+ /obj/item/food/bread/plain = 1,
/obj/item/reagent_containers/food/snacks/meat/cutlet/xeno = 3,
/obj/item/reagent_containers/food/snacks/cheesewedge = 3
)
- result = /obj/item/reagent_containers/food/snacks/store/bread/xenomeat
+ result = /obj/item/food/bread/xenomeat
subcategory = CAT_BREAD
/datum/crafting_recipe/food/spidermeatbread
name = "Spidermeat bread"
reqs = list(
- /obj/item/reagent_containers/food/snacks/store/bread/plain = 1,
+ /obj/item/food/bread/plain = 1,
/obj/item/reagent_containers/food/snacks/meat/cutlet/spider = 3,
/obj/item/reagent_containers/food/snacks/cheesewedge = 3
)
- result = /obj/item/reagent_containers/food/snacks/store/bread/spidermeat
+ result = /obj/item/food/bread/spidermeat
subcategory = CAT_BREAD
/datum/crafting_recipe/food/banananutbread
name = "Banana nut bread"
reqs = list(
/datum/reagent/consumable/milk = 5,
- /obj/item/reagent_containers/food/snacks/store/bread/plain = 1,
+ /obj/item/food/bread/plain = 1,
/obj/item/reagent_containers/food/snacks/boiledegg = 3,
/obj/item/reagent_containers/food/snacks/grown/banana = 1
)
- result = /obj/item/reagent_containers/food/snacks/store/bread/banana
+ result = /obj/item/food/bread/banana
subcategory = CAT_BREAD
/datum/crafting_recipe/food/tofubread
name = "Tofu bread"
reqs = list(
- /obj/item/reagent_containers/food/snacks/store/bread/plain = 1,
+ /obj/item/food/bread/plain = 1,
/obj/item/reagent_containers/food/snacks/tofu = 3,
/obj/item/reagent_containers/food/snacks/cheesewedge = 3
)
- result = /obj/item/reagent_containers/food/snacks/store/bread/tofu
+ result = /obj/item/food/bread/tofu
subcategory = CAT_BREAD
/datum/crafting_recipe/food/creamcheesebread
name = "Cream cheese bread"
reqs = list(
/datum/reagent/consumable/milk = 5,
- /obj/item/reagent_containers/food/snacks/store/bread/plain = 1,
+ /obj/item/food/bread/plain = 1,
/obj/item/reagent_containers/food/snacks/cheesewedge = 2
)
- result = /obj/item/reagent_containers/food/snacks/store/bread/creamcheese
+ result = /obj/item/food/bread/creamcheese
subcategory = CAT_BREAD
/datum/crafting_recipe/food/mimanabread
name = "Mimana bread"
reqs = list(
/datum/reagent/consumable/soymilk = 5,
- /obj/item/reagent_containers/food/snacks/store/bread/plain = 1,
+ /obj/item/food/bread/plain = 1,
/obj/item/reagent_containers/food/snacks/tofu = 3,
/obj/item/reagent_containers/food/snacks/grown/banana/mime = 1
)
- result = /obj/item/reagent_containers/food/snacks/store/bread/mimana
+ result = /obj/item/food/bread/mimana
subcategory = CAT_BREAD
/datum/crafting_recipe/food/garlicbread
name = "Garlic Bread"
time = 40
reqs = list(/obj/item/reagent_containers/food/snacks/grown/garlic = 1,
- /obj/item/reagent_containers/food/snacks/breadslice/plain = 1,
+ /obj/item/food/breadslice/plain = 1,
/obj/item/reagent_containers/food/snacks/butter = 1
)
- result = /obj/item/reagent_containers/food/snacks/garlicbread
+ result = /obj/item/food/garlicbread
subcategory = CAT_BREAD
/datum/crafting_recipe/food/butterbiscuit
@@ -91,7 +91,7 @@
/obj/item/reagent_containers/food/snacks/bun = 1,
/obj/item/reagent_containers/food/snacks/butter = 1
)
- result = /obj/item/reagent_containers/food/snacks/butterbiscuit
+ result = /obj/item/food/butterbiscuit
subcategory = CAT_BREAD
/datum/crafting_recipe/food/butterdog
@@ -100,14 +100,14 @@
/obj/item/reagent_containers/food/snacks/bun = 1,
/obj/item/reagent_containers/food/snacks/butter = 3,
)
- result = /obj/item/reagent_containers/food/snacks/butterdog
+ result = /obj/item/food/butterdog
subcategory = CAT_BREAD
/datum/crafting_recipe/food/moldybread // why would you make this?
name = "Moldy Bread"
reqs = list(
- /obj/item/reagent_containers/food/snacks/breadslice/plain = 1,
+ /obj/item/food/breadslice/plain = 1,
/obj/item/reagent_containers/food/snacks/grown/mushroom/amanita = 1
)
- result = /obj/item/reagent_containers/food/snacks/breadslice/moldy
+ result = /obj/item/food/breadslice/moldy
subcategory = CAT_BREAD
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_cake.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_cake.dm
index dcae05095ae..7a3a4c6837b 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_cake.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_cake.dm
@@ -6,190 +6,190 @@
/datum/crafting_recipe/food/carrotcake
name = "Carrot cake"
reqs = list(
- /obj/item/reagent_containers/food/snacks/store/cake/plain = 1,
+ /obj/item/food/cake/plain = 1,
/obj/item/reagent_containers/food/snacks/grown/carrot = 2
)
- result = /obj/item/reagent_containers/food/snacks/store/cake/carrot
+ result = /obj/item/food/cake/carrot
subcategory = CAT_CAKE
/datum/crafting_recipe/food/cheesecake
name = "Cheese cake"
reqs = list(
- /obj/item/reagent_containers/food/snacks/store/cake/plain = 1,
+ /obj/item/food/cake/plain = 1,
/obj/item/reagent_containers/food/snacks/cheesewedge = 2
)
- result = /obj/item/reagent_containers/food/snacks/store/cake/cheese
+ result = /obj/item/food/cake/cheese
subcategory = CAT_CAKE
/datum/crafting_recipe/food/applecake
name = "Apple cake"
reqs = list(
- /obj/item/reagent_containers/food/snacks/store/cake/plain = 1,
+ /obj/item/food/cake/plain = 1,
/obj/item/reagent_containers/food/snacks/grown/apple = 2
)
- result = /obj/item/reagent_containers/food/snacks/store/cake/apple
+ result = /obj/item/food/cake/apple
subcategory = CAT_CAKE
/datum/crafting_recipe/food/orangecake
name = "Orange cake"
reqs = list(
- /obj/item/reagent_containers/food/snacks/store/cake/plain = 1,
+ /obj/item/food/cake/plain = 1,
/obj/item/reagent_containers/food/snacks/grown/citrus/orange = 2
)
- result = /obj/item/reagent_containers/food/snacks/store/cake/orange
+ result = /obj/item/food/cake/orange
subcategory = CAT_CAKE
/datum/crafting_recipe/food/limecake
name = "Lime cake"
reqs = list(
- /obj/item/reagent_containers/food/snacks/store/cake/plain = 1,
+ /obj/item/food/cake/plain = 1,
/obj/item/reagent_containers/food/snacks/grown/citrus/lime = 2
)
- result = /obj/item/reagent_containers/food/snacks/store/cake/lime
+ result = /obj/item/food/cake/lime
subcategory = CAT_CAKE
/datum/crafting_recipe/food/lemoncake
name = "Lemon cake"
reqs = list(
- /obj/item/reagent_containers/food/snacks/store/cake/plain = 1,
+ /obj/item/food/cake/plain = 1,
/obj/item/reagent_containers/food/snacks/grown/citrus/lemon = 2
)
- result = /obj/item/reagent_containers/food/snacks/store/cake/lemon
+ result = /obj/item/food/cake/lemon
subcategory = CAT_CAKE
/datum/crafting_recipe/food/chocolatecake
name = "Chocolate cake"
reqs = list(
- /obj/item/reagent_containers/food/snacks/store/cake/plain = 1,
+ /obj/item/food/cake/plain = 1,
/obj/item/reagent_containers/food/snacks/chocolatebar = 2
)
- result = /obj/item/reagent_containers/food/snacks/store/cake/chocolate
+ result = /obj/item/food/cake/chocolate
subcategory = CAT_CAKE
/datum/crafting_recipe/food/birthdaycake
name = "Birthday cake"
reqs = list(
- /obj/item/reagent_containers/food/snacks/store/cake/plain = 1,
+ /obj/item/food/cake/plain = 1,
/obj/item/candle = 1,
/datum/reagent/consumable/sugar = 5,
/datum/reagent/consumable/caramel = 2
)
- result = /obj/item/reagent_containers/food/snacks/store/cake/birthday
+ result = /obj/item/food/cake/birthday
subcategory = CAT_CAKE
/datum/crafting_recipe/food/energycake
name = "Energy cake"
reqs = list(
- /obj/item/reagent_containers/food/snacks/store/cake/birthday = 1,
+ /obj/item/food/cake/birthday = 1,
/obj/item/melee/transforming/energy/sword = 1,
)
- blacklist = list(/obj/item/reagent_containers/food/snacks/store/cake/birthday/energy)
- result = /obj/item/reagent_containers/food/snacks/store/cake/birthday/energy
+ blacklist = list(/obj/item/food/cake/birthday/energy)
+ result = /obj/item/food/cake/birthday/energy
subcategory = CAT_CAKE
/datum/crafting_recipe/food/braincake
name = "Brain cake"
reqs = list(
/obj/item/organ/brain = 1,
- /obj/item/reagent_containers/food/snacks/store/cake/plain = 1
+ /obj/item/food/cake/plain = 1
)
- result = /obj/item/reagent_containers/food/snacks/store/cake/brain
+ result = /obj/item/food/cake/brain
subcategory = CAT_CAKE
/datum/crafting_recipe/food/slimecake
name = "Slime cake"
reqs = list(
/obj/item/slime_extract = 1,
- /obj/item/reagent_containers/food/snacks/store/cake/plain = 1
+ /obj/item/food/cake/plain = 1
)
- result = /obj/item/reagent_containers/food/snacks/store/cake/slimecake
+ result = /obj/item/food/cake/slimecake
subcategory = CAT_CAKE
/datum/crafting_recipe/food/pumpkinspicecake
name = "Pumpkin spice cake"
reqs = list(
- /obj/item/reagent_containers/food/snacks/store/cake/plain = 1,
+ /obj/item/food/cake/plain = 1,
/obj/item/reagent_containers/food/snacks/grown/pumpkin = 2
)
- result = /obj/item/reagent_containers/food/snacks/store/cake/pumpkinspice
+ result = /obj/item/food/cake/pumpkinspice
subcategory = CAT_CAKE
/datum/crafting_recipe/food/holycake
name = "Angel food cake"
reqs = list(
/datum/reagent/water/holywater = 15,
- /obj/item/reagent_containers/food/snacks/store/cake/plain = 1
+ /obj/item/food/cake/plain = 1
)
- result = /obj/item/reagent_containers/food/snacks/store/cake/holy_cake
+ result = /obj/item/food/cake/holy_cake
subcategory = CAT_CAKE
/datum/crafting_recipe/food/poundcake
name = "Pound cake"
reqs = list(
- /obj/item/reagent_containers/food/snacks/store/cake/plain = 4
+ /obj/item/food/cake/plain = 4
)
- result = /obj/item/reagent_containers/food/snacks/store/cake/pound_cake
+ result = /obj/item/food/cake/pound_cake
subcategory = CAT_CAKE
/datum/crafting_recipe/food/hardwarecake
name = "Hardware cake"
reqs = list(
- /obj/item/reagent_containers/food/snacks/store/cake/plain = 1,
+ /obj/item/food/cake/plain = 1,
/obj/item/circuitboard = 2,
/datum/reagent/toxin/acid = 5
)
- result = /obj/item/reagent_containers/food/snacks/store/cake/hardware_cake
+ result = /obj/item/food/cake/hardware_cake
subcategory = CAT_CAKE
/datum/crafting_recipe/food/bscccake
name = "blackberry and strawberry chocolate cake"
reqs = list(
- /obj/item/reagent_containers/food/snacks/store/cake/plain = 1,
+ /obj/item/food/cake/plain = 1,
/obj/item/reagent_containers/food/snacks/chocolatebar = 2,
/obj/item/reagent_containers/food/snacks/grown/berries = 5
)
- result = /obj/item/reagent_containers/food/snacks/store/cake/bscc
+ result = /obj/item/food/cake/bscc
subcategory = CAT_CAKE
/datum/crafting_recipe/food/bscvcake
name = "blackberry and strawberry vanilla cake"
reqs = list(
- /obj/item/reagent_containers/food/snacks/store/cake/plain = 1,
+ /obj/item/food/cake/plain = 1,
/obj/item/reagent_containers/food/snacks/grown/berries = 5
)
- result = /obj/item/reagent_containers/food/snacks/store/cake/bsvc
+ result = /obj/item/food/cake/bsvc
subcategory = CAT_CAKE
/datum/crafting_recipe/food/clowncake
name = "clown cake"
always_availible = FALSE
reqs = list(
- /obj/item/reagent_containers/food/snacks/store/cake/plain = 1,
+ /obj/item/food/cake/plain = 1,
/obj/item/reagent_containers/food/snacks/sundae = 2,
/obj/item/reagent_containers/food/snacks/grown/banana = 5
)
- result = /obj/item/reagent_containers/food/snacks/store/cake/clown_cake
+ result = /obj/item/food/cake/clown_cake
subcategory = CAT_CAKE
/datum/crafting_recipe/food/vanillacake
name = "vanilla cake"
always_availible = FALSE
reqs = list(
- /obj/item/reagent_containers/food/snacks/store/cake/plain = 1,
+ /obj/item/food/cake/plain = 1,
/obj/item/reagent_containers/food/snacks/grown/vanillapod = 2
)
- result = /obj/item/reagent_containers/food/snacks/store/cake/vanilla_cake
+ result = /obj/item/food/cake/vanilla_cake
subcategory = CAT_CAKE
/datum/crafting_recipe/food/trumpetcake
name = "Spaceman's Cake"
reqs = list(
- /obj/item/reagent_containers/food/snacks/store/cake/plain = 1,
+ /obj/item/food/cake/plain = 1,
/obj/item/reagent_containers/food/snacks/grown/trumpet = 2,
/datum/reagent/consumable/cream = 5,
/datum/reagent/consumable/berryjuice = 5
)
- result = /obj/item/reagent_containers/food/snacks/store/cake/trumpet
+ result = /obj/item/food/cake/trumpet
subcategory = CAT_CAKE
@@ -198,7 +198,7 @@
reqs = list(
/obj/item/organ/brain = 1,
/obj/item/organ/heart = 1,
- /obj/item/reagent_containers/food/snacks/store/cake/birthday = 1,
+ /obj/item/food/cake/birthday = 1,
/obj/item/reagent_containers/food/snacks/meat/slab = 3,
/datum/reagent/blood = 30,
/datum/reagent/consumable/sprinkles = 5,
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_drink.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_drink.dm
index 4e0ade4fa22..96c67eca7a9 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_drink.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_drink.dm
@@ -139,7 +139,7 @@
time = 30
reqs = list(
/obj/item/storage/bag/trash = 1,
- /obj/item/reagent_containers/food/snacks/breadslice/moldy = 1,
+ /obj/item/food/breadslice/moldy = 1,
/obj/item/reagent_containers/food/snacks/grown = 4,
/obj/item/reagent_containers/food/snacks/candy_corn = 2,
/datum/reagent/water = 15
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_egg.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_egg.dm
index 522f362e777..a4ab818fdd6 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_egg.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_egg.dm
@@ -36,7 +36,7 @@
reqs = list(
/obj/item/reagent_containers/food/snacks/friedegg = 1,
/obj/item/reagent_containers/food/snacks/meat/steak = 1,
- /obj/item/reagent_containers/food/snacks/breadslice/plain = 1,
+ /obj/item/food/breadslice/plain = 1,
)
result = /obj/item/reagent_containers/food/snacks/benedict
subcategory = CAT_EGG
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_misc.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_misc.dm
index 1bb3d250a9a..b1191fda7ad 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_misc.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_misc.dm
@@ -102,7 +102,7 @@
/datum/reagent/consumable/blackpepper = 1,
/obj/item/reagent_containers/food/snacks/pastrybase = 2
)
- result = /obj/item/reagent_containers/food/snacks/baguette
+ result = /obj/item/food/baguette
subcategory = CAT_MISCFOOD
////////////////////////////////////////////////TOAST////////////////////////////////////////////////
@@ -111,7 +111,7 @@
name = "Slime toast"
reqs = list(
/datum/reagent/toxin/slimejelly = 5,
- /obj/item/reagent_containers/food/snacks/breadslice/plain = 1
+ /obj/item/food/breadslice/plain = 1
)
result = /obj/item/reagent_containers/food/snacks/jelliedtoast/slime
subcategory = CAT_MISCFOOD
@@ -120,7 +120,7 @@
name = "Jellied toast"
reqs = list(
/datum/reagent/consumable/cherryjelly = 5,
- /obj/item/reagent_containers/food/snacks/breadslice/plain = 1
+ /obj/item/food/breadslice/plain = 1
)
result = /obj/item/reagent_containers/food/snacks/jelliedtoast/cherry
subcategory = CAT_MISCFOOD
@@ -128,7 +128,7 @@
/datum/crafting_recipe/food/butteredtoast
name = "Buttered Toast"
reqs = list(
- /obj/item/reagent_containers/food/snacks/breadslice/plain = 1,
+ /obj/item/food/breadslice/plain = 1,
/obj/item/reagent_containers/food/snacks/butter = 1
)
result = /obj/item/reagent_containers/food/snacks/butteredtoast
@@ -138,7 +138,7 @@
name = "Two bread"
reqs = list(
/datum/reagent/consumable/ethanol/wine = 5,
- /obj/item/reagent_containers/food/snacks/breadslice/plain = 2
+ /obj/item/food/breadslice/plain = 2
)
result = /obj/item/reagent_containers/food/snacks/twobread
subcategory = CAT_MISCFOOD
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm
index ec04dcaa4ec..d982b6705bd 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm
@@ -474,7 +474,7 @@
reqs = list(
/datum/reagent/consumable/eggyolk = 5,
/obj/item/reagent_containers/food/snacks/cheesewedge = 1,
- /obj/item/reagent_containers/food/snacks/store/bread/plain = 1
+ /obj/item/food/bread/plain = 1
)
result = /obj/item/reagent_containers/food/snacks/khachapuri
subcategory = CAT_PASTRY
@@ -576,7 +576,7 @@
/obj/item/reagent_containers/food/snacks/pastrybase = 1,
/obj/item/reagent_containers/food/snacks/grown/bluecherries = 1
)
- result = /obj/item/reagent_containers/food/snacks/bluecherrycupcake
+ result = /obj/item/reagent_containers/food/snacks/cherrycupcake/blue
subcategory = CAT_PASTRY
/datum/crafting_recipe/food/honeybun
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_sandwich.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_sandwich.dm
index 41829e9ec2b..b8cefcb9bd8 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_sandwich.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_sandwich.dm
@@ -9,7 +9,7 @@
/datum/crafting_recipe/food/sandwich
name = "Sandwich"
reqs = list(
- /obj/item/reagent_containers/food/snacks/breadslice/plain = 2,
+ /obj/item/food/breadslice/plain = 2,
/obj/item/reagent_containers/food/snacks/meat/steak = 1,
/obj/item/reagent_containers/food/snacks/cheesewedge = 1
)
@@ -19,7 +19,7 @@
/datum/crafting_recipe/food/grilledcheesesandwich
name = "Cheese sandwich"
reqs = list(
- /obj/item/reagent_containers/food/snacks/breadslice/plain = 2,
+ /obj/item/food/breadslice/plain = 2,
/obj/item/reagent_containers/food/snacks/cheesewedge = 2
)
result = /obj/item/reagent_containers/food/snacks/grilledcheese
@@ -29,7 +29,7 @@
name = "Jelly sandwich"
reqs = list(
/datum/reagent/toxin/slimejelly = 5,
- /obj/item/reagent_containers/food/snacks/breadslice/plain = 2,
+ /obj/item/food/breadslice/plain = 2,
)
result = /obj/item/reagent_containers/food/snacks/jellysandwich/slime
subcategory = CAT_SANDWICH
@@ -38,7 +38,7 @@
name = "Jelly sandwich"
reqs = list(
/datum/reagent/consumable/cherryjelly = 5,
- /obj/item/reagent_containers/food/snacks/breadslice/plain = 2,
+ /obj/item/food/breadslice/plain = 2,
)
result = /obj/item/reagent_containers/food/snacks/jellysandwich/cherry
subcategory = CAT_SANDWICH
@@ -46,7 +46,7 @@
/datum/crafting_recipe/food/notasandwich
name = "Not a sandwich"
reqs = list(
- /obj/item/reagent_containers/food/snacks/breadslice/plain = 2,
+ /obj/item/food/breadslice/plain = 2,
/obj/item/clothing/mask/fakemoustache = 1
)
result = /obj/item/reagent_containers/food/snacks/notasandwich
@@ -55,7 +55,7 @@
/datum/crafting_recipe/food/blt
name = "BLT"
reqs = list(
- /obj/item/reagent_containers/food/snacks/breadslice/plain = 2,
+ /obj/item/food/breadslice/plain = 2,
/obj/item/reagent_containers/food/snacks/meat/bacon = 2,
/obj/item/reagent_containers/food/snacks/grown/cabbage = 1,
/obj/item/reagent_containers/food/snacks/grown/tomato = 1
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_spaghetti.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_spaghetti.dm
index 796c360c344..2ad56d0ce62 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_spaghetti.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_spaghetti.dm
@@ -6,65 +6,65 @@
/datum/crafting_recipe/food/tomatopasta
name = "Tomato pasta"
reqs = list(
- /obj/item/reagent_containers/food/snacks/spaghetti/boiledspaghetti = 1,
+ /obj/item/food/spaghetti/boiledspaghetti = 1,
/obj/item/reagent_containers/food/snacks/grown/tomato = 2
)
- result = /obj/item/reagent_containers/food/snacks/spaghetti/pastatomato
+ result = /obj/item/food/spaghetti/pastatomato
subcategory = CAT_SPAGHETTI
/datum/crafting_recipe/food/copypasta
name = "Copypasta"
reqs = list(
- /obj/item/reagent_containers/food/snacks/spaghetti/pastatomato = 2
+ /obj/item/food/spaghetti/pastatomato = 2
)
- result = /obj/item/reagent_containers/food/snacks/spaghetti/copypasta
+ result = /obj/item/food/spaghetti/copypasta
subcategory = CAT_SPAGHETTI
/datum/crafting_recipe/food/spaghettimeatball
name = "Spaghetti meatball"
reqs = list(
- /obj/item/reagent_containers/food/snacks/spaghetti/boiledspaghetti = 1,
+ /obj/item/food/spaghetti/boiledspaghetti = 1,
/obj/item/reagent_containers/food/snacks/meatball = 2
)
- result = /obj/item/reagent_containers/food/snacks/spaghetti/meatballspaghetti
+ result = /obj/item/food/spaghetti/meatballspaghetti
subcategory = CAT_SPAGHETTI
/datum/crafting_recipe/food/spesslaw
name = "Spesslaw"
reqs = list(
- /obj/item/reagent_containers/food/snacks/spaghetti/boiledspaghetti = 1,
+ /obj/item/food/spaghetti/boiledspaghetti = 1,
/obj/item/reagent_containers/food/snacks/meatball = 4
)
- result = /obj/item/reagent_containers/food/snacks/spaghetti/spesslaw
+ result = /obj/item/food/spaghetti/spesslaw
subcategory = CAT_SPAGHETTI
/datum/crafting_recipe/food/beefnoodle
name = "Beef noodle"
reqs = list(
/obj/item/reagent_containers/glass/bowl = 1,
- /obj/item/reagent_containers/food/snacks/spaghetti/boiledspaghetti = 1,
+ /obj/item/food/spaghetti/boiledspaghetti = 1,
/obj/item/reagent_containers/food/snacks/meat/cutlet = 2,
/obj/item/reagent_containers/food/snacks/grown/cabbage = 1
)
- result = /obj/item/reagent_containers/food/snacks/spaghetti/beefnoodle
+ result = /obj/item/food/spaghetti/beefnoodle
subcategory = CAT_SPAGHETTI
/datum/crafting_recipe/food/chowmein
name = "Chowmein"
reqs = list(
- /obj/item/reagent_containers/food/snacks/spaghetti/boiledspaghetti = 1,
+ /obj/item/food/spaghetti/boiledspaghetti = 1,
/obj/item/reagent_containers/food/snacks/meat/cutlet = 1,
/obj/item/reagent_containers/food/snacks/grown/cabbage = 2,
/obj/item/reagent_containers/food/snacks/grown/carrot = 1
)
- result = /obj/item/reagent_containers/food/snacks/spaghetti/chowmein
+ result = /obj/item/food/spaghetti/chowmein
subcategory = CAT_SPAGHETTI
/datum/crafting_recipe/food/butternoodles
name = "Butter Noodles"
reqs = list(
- /obj/item/reagent_containers/food/snacks/spaghetti/boiledspaghetti = 1,
+ /obj/item/food/spaghetti/boiledspaghetti = 1,
/obj/item/reagent_containers/food/snacks/butter = 1
)
- result = /obj/item/reagent_containers/food/snacks/spaghetti/butternoodles
+ result = /obj/item/food/spaghetti/butternoodles
subcategory = CAT_SPAGHETTI
diff --git a/code/modules/holiday/easter.dm b/code/modules/holiday/easter.dm
index 9d88d310755..2e40c8ed04c 100644
--- a/code/modules/holiday/easter.dm
+++ b/code/modules/holiday/easter.dm
@@ -174,36 +174,12 @@
/datum/crafting_recipe/food/hotcrossbun
name = "Hot-Cross Bun"
reqs = list(
- /obj/item/reagent_containers/food/snacks/store/bread/plain = 1,
+ /obj/item/food/bread/plain = 1,
/datum/reagent/consumable/sugar = 1
)
result = /obj/item/reagent_containers/food/snacks/hotcrossbun
subcategory = CAT_MISCFOOD
-
-/obj/item/reagent_containers/food/snacks/store/cake/brioche
- name = "brioche cake"
- desc = "A ring of sweet, glazed buns."
- icon_state = "briochecake"
- slice_path = /obj/item/reagent_containers/food/snacks/cakeslice/brioche
- slices_num = 6
- bonus_reagents = list(/datum/reagent/consumable/nutriment = 10, /datum/reagent/consumable/nutriment/vitamin = 2)
-
-/obj/item/reagent_containers/food/snacks/cakeslice/brioche
- name = "brioche cake slice"
- desc = "Delicious sweet-bread. Who needs anything else?"
- icon_state = "briochecake_slice"
- filling_color = "#FFD700"
-
-/datum/crafting_recipe/food/briochecake
- name = "Brioche cake"
- reqs = list(
- /obj/item/reagent_containers/food/snacks/store/cake/plain = 1,
- /datum/reagent/consumable/sugar = 2
- )
- result = /obj/item/reagent_containers/food/snacks/store/cake/brioche
- subcategory = CAT_MISCFOOD
-
/obj/item/reagent_containers/food/snacks/scotchegg
name = "scotch egg"
desc = "A boiled egg wrapped in a delicious, seasoned meatball."
@@ -234,7 +210,7 @@
/datum/crafting_recipe/food/mammi
name = "Mammi"
reqs = list(
- /obj/item/reagent_containers/food/snacks/store/bread/plain = 1,
+ /obj/item/food/bread/plain = 1,
/obj/item/reagent_containers/food/snacks/chocolatebar = 1,
/datum/reagent/consumable/milk = 5
)
diff --git a/code/modules/hydroponics/gene_modder.dm b/code/modules/hydroponics/gene_modder.dm
index a21e9f71afc..87e0f1a2a42 100644
--- a/code/modules/hydroponics/gene_modder.dm
+++ b/code/modules/hydroponics/gene_modder.dm
@@ -341,7 +341,6 @@
seed.genes += disk.gene.Copy()
if(istype(disk.gene, /datum/plant_gene/reagent))
seed.reagents_from_genes()
- disk.gene.apply_vars(seed)
repaint_seed()
diff --git a/code/modules/hydroponics/genes/attack.dm b/code/modules/hydroponics/genes/attack.dm
new file mode 100644
index 00000000000..37dabf0c06a
--- /dev/null
+++ b/code/modules/hydroponics/genes/attack.dm
@@ -0,0 +1,128 @@
+/// Traits that turn a plant into a weapon, giving them force and effects on attack.
+/datum/plant_gene/trait/attack
+ name = "On Attack Trait"
+ description = "It is a very dangerous weapon."
+ icon = "hand-fist"
+ /// The multiplier we apply to the potency to calculate force. Set to 0 to not affect the force.
+ var/force_multiplier = 0
+ /// If TRUE, our plant will degrade in force every hit until diappearing.
+ var/degrades_after_hit = FALSE
+ /// When we fully degrade, what degraded off of us?
+ var/degradation_noun = "leaves"
+
+/datum/plant_gene/trait/attack/on_new_plant(obj/item/our_plant, newloc)
+ . = ..()
+ if(!.)
+ return
+
+ if(force_multiplier)
+ var/obj/item/seeds/our_seed = our_plant.get_plant_seed()
+ our_plant.force = round((5 + our_seed.potency * force_multiplier), 1)
+ RegisterSignal(our_plant, COMSIG_ITEM_ATTACK, PROC_REF(on_plant_attack))
+ RegisterSignal(our_plant, COMSIG_ITEM_AFTERATTACK, PROC_REF(after_plant_attack))
+
+/// Signal proc for [COMSIG_ITEM_ATTACK] that allows for effects on attack
+/datum/plant_gene/trait/attack/proc/on_plant_attack(obj/item/source, mob/living/target, mob/living/user)
+ SIGNAL_HANDLER
+
+ INVOKE_ASYNC(src, PROC_REF(attack_effect), source, target, user)
+
+/*
+ * Effects done when we hit people with our plant, ON attack.
+ * Override on a per-plant basis.
+ *
+ * our_plant - our plant, that we're attacking with
+ * user - the person who is attacking with the plant
+ * target - the person who is attacked by the plant
+ */
+/datum/plant_gene/trait/attack/proc/attack_effect(obj/item/our_plant, mob/living/target, mob/living/user)
+ return
+
+/// Signal proc for [COMSIG_ITEM_AFTERATTACK] that allows for effects after an attack is done
+/datum/plant_gene/trait/attack/proc/after_plant_attack(obj/item/source, atom/target, mob/user, proximity_flag, click_parameters)
+ SIGNAL_HANDLER
+
+ if(!proximity_flag)
+ return
+
+ if(!ismovable(target))
+ return
+
+ if(isobj(target))
+ var/obj/object_target = target
+ if(!(object_target.obj_flags & CAN_BE_HIT))
+ return .
+
+ INVOKE_ASYNC(src, PROC_REF(after_attack_effect), source, target, user)
+ return .
+
+/*
+ * Effects done when we hit people with our plant, AFTER the attack is done.
+ * Extend on a per-plant basis.
+ *
+ * our_plant - our plant, that we're attacking with
+ * user - the person who is attacking with the plant
+ * target - the atom which is attacked by the plant
+ */
+/datum/plant_gene/trait/attack/proc/after_attack_effect(obj/item/our_plant, atom/target, mob/living/user)
+ SHOULD_CALL_PARENT(TRUE)
+
+ if(!degrades_after_hit)
+ return
+
+ // We probably hit something or someone. Reduce our force
+ if(our_plant.force > 0)
+ our_plant.force -= rand(1, (our_plant.force / 3) + 1)
+ return
+
+ // When our force degrades to zero or below, we're all done
+ to_chat(user, span_warning("All the [degradation_noun] have fallen off [our_plant] from violent whacking!"))
+ qdel(our_plant)
+
+/// Novaflower's attack effects (sets people on fire) + degradation on attack
+/datum/plant_gene/trait/attack/novaflower_attack
+ name = "Heated Petals"
+ description = "Hitting with it may cause things to combust."
+ force_multiplier = 0.2
+ degrades_after_hit = TRUE
+ degradation_noun = "petals"
+
+/datum/plant_gene/trait/attack/novaflower_attack/attack_effect(obj/item/our_plant, mob/living/target, mob/living/user)
+ if(!istype(target))
+ return
+
+ var/obj/item/seeds/our_seed = our_plant.get_plant_seed()
+ to_chat(target, span_danger("You are lit on fire from the intense heat of [our_plant]!"))
+ target.adjust_fire_stacks(round(our_seed.potency / 20))
+ if(target.IgniteMob())
+ message_admins("[ADMIN_LOOKUPFLW(user)] set [ADMIN_LOOKUPFLW(target)] on fire with [our_plant] at [AREACOORD(user)]")
+ user.log_message("set [key_name(target)] on fire with [our_plant]", LOG_ATTACK)
+ target.log_message("was set on fire by [key_name(user)] with [our_plant].", LOG_ATTACK)
+
+ our_plant.investigate_log("was used by [key_name(user)] to burn [key_name(target)] at [AREACOORD(user)]", INVESTIGATE_BOTANY)
+
+/// Sunflower's attack effect (shows cute text)
+/datum/plant_gene/trait/attack/sunflower_attack
+ name = "Bright Petals"
+ description = "Makes others feel the power on hit."
+
+/datum/plant_gene/trait/attack/sunflower_attack/after_attack_effect(obj/item/our_plant, atom/target, mob/user, proximity_flag, click_parameters)
+ if(ismob(target))
+ var/mob/target_mob = target
+ user.visible_message("[user] smacks [target_mob] with [user.p_their()] [our_plant.name]! FLOWER POWER!", ignored_mobs = list(target_mob, user))
+ if(target_mob != user)
+ to_chat(target_mob, "[user] smacks you with [our_plant]!FLOWER POWER!")
+ to_chat(user, "Your [our_plant.name]'s FLOWER POWER strikes [target_mob]!")
+
+ return ..()
+
+/// Normal nettle's force + degradation on attack
+/datum/plant_gene/trait/attack/nettle_attack
+ name = "Sharpened Leaves"
+ force_multiplier = 0.2
+ degrades_after_hit = TRUE
+
+/// Deathnettle force + degradation on attack
+/datum/plant_gene/trait/attack/nettle_attack/death
+ name = "Aggressive Sharpened Leaves"
+ force_multiplier = 0.4
diff --git a/code/modules/hydroponics/genes/backfire.dm b/code/modules/hydroponics/genes/backfire.dm
new file mode 100644
index 00000000000..338b0fb1745
--- /dev/null
+++ b/code/modules/hydroponics/genes/backfire.dm
@@ -0,0 +1,163 @@
+/// Traits for plants with backfire effects. These are negative effects that occur when a plant is handled without gloves/unsafely.
+/datum/plant_gene/trait/backfire
+ name = "Backfire Trait"
+ icon = "mitten"
+ description = "Be careful when holding it without protection."
+ /// Whether our actions are cancelled when the backfire triggers.
+ var/cancel_action_on_backfire = FALSE
+ /// A list of extra traits to check to be considered safe.
+ var/list/traits_to_check
+ /// A list of extra genes to check to be considered safe.
+ var/list/genes_to_check
+
+/datum/plant_gene/trait/backfire/on_new_plant(obj/item/our_plant, newloc)
+ . = ..()
+ if(!.)
+ return
+ if(genes_to_check)
+ genes_to_check = string_list(genes_to_check)
+ if(traits_to_check)
+ traits_to_check = string_list(traits_to_check)
+ our_plant.AddElement(/datum/element/plant_backfire, cancel_action_on_backfire, traits_to_check, genes_to_check)
+ RegisterSignal(our_plant, COMSIG_PLANT_ON_BACKFIRE, PROC_REF(on_backfire))
+
+/// Signal proc for [COMSIG_PLANT_ON_BACKFIRE] that causes the backfire effect.
+/datum/plant_gene/trait/backfire/proc/on_backfire(obj/item/source, mob/living/carbon/user)
+ SIGNAL_HANDLER
+
+ INVOKE_ASYNC(src, PROC_REF(backfire_effect), source, user)
+
+/**
+ * The actual backfire effect on the user.
+ * Override with plant-specific effects.
+ */
+/datum/plant_gene/trait/backfire/proc/backfire_effect(obj/item/our_plant, mob/living/carbon/user)
+ return
+
+/// Rose's prick on backfire
+/datum/plant_gene/trait/backfire/rose_thorns
+ name = "Rose Thorns"
+ description = "The stem has a lot of thorns."
+ traits_to_check = list(TRAIT_PIERCEIMMUNE)
+
+/datum/plant_gene/trait/backfire/rose_thorns/backfire_effect(obj/item/our_plant, mob/living/carbon/user)
+ var/obj/item/seeds/our_seed = our_plant.get_plant_seed()
+ if(!our_seed.get_gene(/datum/plant_gene/trait/sticky) && prob(66))
+ to_chat(user, span_danger("[our_plant]'s thorns nearly prick your hand. Best be careful."))
+ return
+
+ to_chat(user, span_danger("[our_plant]'s thorns prick your hand. Ouch."))
+ our_plant.investigate_log("rose-pricked [key_name(user)] at [AREACOORD(user)]", INVESTIGATE_BOTANY)
+ var/obj/item/bodypart/affecting = user.get_active_hand()
+ affecting?.receive_damage(2)
+
+/// Novaflower's hand burn on backfire
+/datum/plant_gene/trait/backfire/novaflower_heat
+ name = "Burning Stem"
+ description = "The stem may burn your hand."
+ cancel_action_on_backfire = TRUE
+
+/datum/plant_gene/trait/backfire/novaflower_heat/backfire_effect(obj/item/our_plant, mob/living/carbon/user)
+ to_chat(user, span_danger("[our_plant] singes your bare hand!"))
+ our_plant.investigate_log("self-burned [key_name(user)] for [our_plant.force] at [AREACOORD(user)]", INVESTIGATE_BOTANY)
+ var/obj/item/bodypart/affecting = user.get_active_hand()
+ return affecting?.receive_damage(0, our_plant.force)
+
+/// Normal Nettle hannd burn on backfire
+/datum/plant_gene/trait/backfire/nettle_burn
+ name = "Stinging Stem"
+ description = "The stem may sting your hand."
+
+/datum/plant_gene/trait/backfire/nettle_burn/backfire_effect(obj/item/our_plant, mob/living/carbon/user)
+ to_chat(user, span_danger("[our_plant] burns your bare hand!"))
+ our_plant.investigate_log("self-burned [key_name(user)] for [our_plant.force] at [AREACOORD(user)]", INVESTIGATE_BOTANY)
+ var/obj/item/bodypart/affecting = user.get_active_hand()
+ return affecting?.receive_damage(0, our_plant.force)
+
+/// Deathnettle hand burn + stun on backfire
+/datum/plant_gene/trait/backfire/nettle_burn/death
+ name = "Aggressive Stinging Stem"
+ cancel_action_on_backfire = TRUE
+
+/datum/plant_gene/trait/backfire/nettle_burn/death/backfire_effect(obj/item/our_plant, mob/living/carbon/user)
+ . = ..()
+ if(!. || prob(50))
+ return
+
+ user.Paralyze(10 SECONDS)
+ to_chat(user, span_userdanger("You are stunned by the powerful acids of [our_plant]!"))
+
+/*
+/// Ghost-Chili heating up on backfire
+/datum/plant_gene/trait/backfire/chili_heat
+ name = "Active Capsicum Glands"
+ description = "You may survive a cold winter with this in hand."
+ genes_to_check = list(/datum/plant_gene/trait/chem_heating)
+ /// The mob currently holding the chili.
+ var/datum/weakref/held_mob
+ /// The chili this gene is tied to, to track it for processing.
+ var/datum/weakref/our_chili
+
+/datum/plant_gene/trait/backfire/chili_heat/on_new_plant(obj/item/our_plant, newloc)
+ . = ..()
+ if(!.)
+ return
+
+ our_chili = WEAKREF(our_plant)
+ RegisterSignals(our_plant, list(COMSIG_QDELETING, COMSIG_ITEM_DROPPED), PROC_REF(stop_backfire_effect))
+
+/*
+ * Begin processing the trait on backfire.
+ *
+ * our_plant - our source plant, which is backfiring
+ * user - the mob holding our plant
+ */
+/datum/plant_gene/trait/backfire/chili_heat/backfire_effect(obj/item/our_plant, mob/living/carbon/user)
+ held_mob = WEAKREF(user)
+ START_PROCESSING(SSobj, src)
+
+/*
+ * Stop processing the trait when we're dropped or deleted.
+ *
+ * our_plant - our source plant
+ */
+/datum/plant_gene/trait/backfire/chili_heat/proc/stop_backfire_effect(datum/source)
+ SIGNAL_HANDLER
+
+ held_mob = null
+ STOP_PROCESSING(SSobj, src)
+
+/*
+ * The processing of our trait. Heats up the mob ([held_mob]) currently holding the source plant ([our_chili]).
+ * Stops processing if we're no longer being held by [held mob].
+ */
+/datum/plant_gene/trait/backfire/chili_heat/process(seconds_per_tick)
+ var/mob/living/carbon/our_mob = held_mob?.resolve()
+ var/obj/item/our_plant = our_chili?.resolve()
+
+ // If our weakrefs don't resolve, or if our mob is not holding our plant, stop processing.
+ if(!our_mob || !our_plant || !our_mob.is_holding(our_plant))
+ stop_backfire_effect()
+ return
+
+ our_mob.adjust_bodytemperature(7.5 * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick)
+ if(SPT_PROB(5, seconds_per_tick))
+ to_chat(our_mob, span_warning("Your hand holding [our_plant] burns!"))
+
+/// Bluespace Tomato squashing on the user on backfire
+/datum/plant_gene/trait/backfire/bluespace
+ name = "Bluespace Volatility"
+ description = "You may be spaced out if you hold this unprotected."
+ cancel_action_on_backfire = TRUE
+ genes_to_check = list(/datum/plant_gene/trait/squash)
+
+/datum/plant_gene/trait/backfire/bluespace/backfire_effect(obj/item/our_plant, mob/living/carbon/user)
+ if(prob(50))
+ return
+
+ to_chat(user, span_danger("[our_plant] slips out of your hand!"))
+
+ var/obj/item/seeds/our_seed = our_plant.get_plant_seed()
+ var/datum/plant_gene/trait/squash/squash_gene = our_seed.get_gene(/datum/plant_gene/trait/squash)
+ squash_gene.squash_plant(our_plant, user)
+*/
diff --git a/code/modules/hydroponics/grown.dm b/code/modules/hydroponics/grown.dm
index ed58e86e16d..4f2b2420ee5 100644
--- a/code/modules/hydroponics/grown.dm
+++ b/code/modules/hydroponics/grown.dm
@@ -44,8 +44,8 @@
dried_type = src.type
if(seed)
- for(var/datum/plant_gene/trait/T in seed.genes)
- T.on_new(src, loc)
+ for(var/datum/plant_gene/trait/trait in seed.genes)
+ trait.on_new_plant(src, loc)
seed.prepare_result(src)
transform *= TRANSFORM_USING_VARIABLE(seed.potency, 100) + 0.5 //Makes the resulting produce's sprite larger or smaller based on potency!
add_juice()
@@ -110,15 +110,13 @@
user.visible_message("[user] starts splitting \the [src].", "You dig into \the [src] and start to split it...", "You hear the sound of a sharp object digging into some plant matter.")
if(do_after(user, 20, target = src))
to_chat(user, "You split apart the [src]! Sadly you put too much force and it's remains are unusable, but hey, you got your seeds!")
- seedify(src, 1, TRUE, FALSE, src, user)
- squash(user)
+ seedify(src, 1, TRUE, TRUE, src, user)
if(TOOL_WRENCH)
playsound(loc, 'sound/misc/splort.ogg', 50, TRUE, -1)
user.visible_message("[user] starts whacking \the [src].", "You start whacking \the [src]...", "You hear the sound of a plant being whacked violently.")
if(do_after(user, 17, target = src))
to_chat(user, "You smash [src]! Sadly there's nothing left of it other than the seeds and some junk.")
- seedify(src, 1, TRUE, FALSE, src, user)
- squash(user)
+ seedify(src, 1, TRUE, TRUE, src, user)
if(!slice_path)
if(O.get_sharpness())
playsound(loc, 'sound/weapons/slice.ogg', 50, TRUE, -1)
@@ -127,44 +125,11 @@
to_chat(user, "You slice apart the [src]! You went too far and the tiny remaining scraps are worthless!")
seedify(src, 1, TRUE, TRUE, src, user)
-// Various gene procs
-/obj/item/reagent_containers/food/snacks/grown/attack_self(mob/user)
- if(seed && seed.get_gene(/datum/plant_gene/trait/squash))
- squash(user)
- ..()
-
/obj/item/reagent_containers/food/snacks/grown/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
if(!..()) //was it caught by a mob?
if(seed)
for(var/datum/plant_gene/trait/T in seed.genes)
T.on_throw_impact(src, hit_atom)
- if(seed.get_gene(/datum/plant_gene/trait/squash))
- squash(hit_atom)
-
-/obj/item/reagent_containers/food/snacks/grown/proc/squash(atom/target)
- var/turf/T = get_turf(target)
- forceMove(T)
- if(ispath(splat_type, /obj/effect/decal/cleanable/food/plant_smudge))
- if(filling_color)
- var/obj/O = new splat_type(T)
- O.color = filling_color
- O.name = "[name] smudge"
- else if(splat_type)
- new splat_type(T)
-
- if(trash)
- generate_trash(T)
-
- visible_message("[src] is squashed.","You hear a smack.")
- if(seed)
- for(var/datum/plant_gene/trait/trait in seed.genes)
- trait.on_squash(src, target)
-
- reagents.expose(T)
- for(var/A in T)
- reagents.expose(A)
-
- qdel(src)
/obj/item/reagent_containers/food/snacks/grown/On_Consume()
if(iscarbon(usr))
diff --git a/code/modules/hydroponics/grown/flowers.dm b/code/modules/hydroponics/grown/flowers.dm
index 820ecae005d..9459a1f7a81 100644
--- a/code/modules/hydroponics/grown/flowers.dm
+++ b/code/modules/hydroponics/grown/flowers.dm
@@ -144,6 +144,7 @@
species = "sunflower"
plantname = "Sunflowers"
product = /obj/item/grown/sunflower
+ genes = list(/datum/plant_gene/trait/attack/sunflower_attack)
endurance = 20
production = 2
yield = 2
@@ -169,10 +170,6 @@
throw_speed = 1
throw_range = 3
-/obj/item/grown/sunflower/attack(mob/M, mob/user)
- to_chat(M, "[user] smacks you with a sunflower!FLOWER POWER!")
- to_chat(user, "Your sunflower's FLOWER POWER strikes [M]!")
-
// Moonflower
/obj/item/seeds/sunflower/moonflower
name = "pack of moonflower seeds"
@@ -211,11 +208,18 @@
icon_grow = "novaflower-grow"
icon_dead = "sunflower-dead"
product = /obj/item/grown/novaflower
+ genes = list(/datum/plant_gene/trait/backfire/novaflower_heat, /datum/plant_gene/trait/attack/novaflower_attack)
mutatelist = list()
reagents_add = list(/datum/reagent/consumable/condensedcapsaicin = 0.25, /datum/reagent/consumable/capsaicin = 0.3, /datum/reagent/consumable/nutriment = 0)
rarity = 20
research = PLANT_RESEARCH_TIER_3
+/obj/item/seeds/sunflower/novaflower/Initialize(mapload,nogenes)
+ . = ..()
+ if(!nogenes)
+ unset_mutability(/datum/plant_gene/trait/attack/novaflower_attack, PLANT_GENE_REMOVABLE)
+ unset_mutability(/datum/plant_gene/trait/backfire/novaflower_heat, PLANT_GENE_REMOVABLE)
+
/obj/item/grown/novaflower
seed = /obj/item/seeds/sunflower/novaflower
name = "novaflower"
@@ -232,33 +236,3 @@
throw_range = 3
attack_verb = list("roasted", "scorched", "burned")
grind_results = list(/datum/reagent/consumable/capsaicin = 0, /datum/reagent/consumable/condensedcapsaicin = 0)
-
-/obj/item/grown/novaflower/add_juice()
- ..()
- force = round((5 + seed.potency / 5), 1)
-
-/obj/item/grown/novaflower/attack(mob/living/carbon/M, mob/user)
- if(!..())
- return
- if(isliving(M))
- to_chat(M, "You are lit on fire from the intense heat of the [name]!")
- M.adjust_fire_stacks(seed.potency / 20)
- if(M.IgniteMob())
- message_admins("[ADMIN_LOOKUPFLW(user)] set [ADMIN_LOOKUPFLW(M)] on fire with [src] at [AREACOORD(user)]")
- log_game("[key_name(user)] set [key_name(M)] on fire with [src] at [AREACOORD(user)]")
-
-/obj/item/grown/novaflower/afterattack(atom/A as mob|obj, mob/user,proximity)
- . = ..()
- if(!proximity)
- return
- if(force > 0)
- force -= rand(1, (force / 3) + 1)
- else
- to_chat(usr, "All the petals have fallen off the [name] from violent whacking!")
- qdel(src)
-
-/obj/item/grown/novaflower/pickup(mob/living/carbon/human/user)
- ..()
- if(!user.gloves)
- to_chat(user, "The [name] burns your bare hand!")
- user.adjustFireLoss(rand(1, 5))
diff --git a/code/modules/hydroponics/grown/mushrooms.dm b/code/modules/hydroponics/grown/mushrooms.dm
index 17d43d0a31c..c3488c43f78 100644
--- a/code/modules/hydroponics/grown/mushrooms.dm
+++ b/code/modules/hydroponics/grown/mushrooms.dm
@@ -220,7 +220,7 @@
endurance = 8
yield = 4
growthstages = 2
- genes = list(/datum/plant_gene/trait/plant_type/fungal_metabolism, /datum/plant_gene/reagent/liquidelectricity, /datum/plant_gene/trait/plant_type/carnivory)
+ genes = list(/datum/plant_gene/trait/plant_type/fungal_metabolism, /datum/plant_gene/reagent/liquidelectricity, /datum/plant_gene/trait/carnivory)
growing_icon = 'icons/obj/hydroponics/growing_mushrooms.dmi'
reagents_add = list(/datum/reagent/consumable/nutriment = 0.1)
research = PLANT_RESEARCH_TIER_3
@@ -229,7 +229,7 @@
. = ..()
if(!nogenes)
unset_mutability(/datum/plant_gene/reagent/liquidelectricity, PLANT_GENE_EXTRACTABLE)
- unset_mutability(/datum/plant_gene/trait/plant_type/carnivory, PLANT_GENE_REMOVABLE)
+ unset_mutability(/datum/plant_gene/trait/carnivory, PLANT_GENE_REMOVABLE)
/obj/item/reagent_containers/food/snacks/grown/mushroom/jupitercup
seed = /obj/item/seeds/chanter/jupitercup
diff --git a/code/modules/hydroponics/grown/nettle.dm b/code/modules/hydroponics/grown/nettle.dm
index 277245138a5..f9633ade766 100644
--- a/code/modules/hydroponics/grown/nettle.dm
+++ b/code/modules/hydroponics/grown/nettle.dm
@@ -9,10 +9,16 @@
endurance = 40 // tuff like a toiger
yield = 4
growthstages = 5
- genes = list(/datum/plant_gene/trait/repeated_harvest, /datum/plant_gene/trait/plant_type/weed_hardy)
+ genes = list(/datum/plant_gene/trait/repeated_harvest, /datum/plant_gene/trait/plant_type/weed_hardy, /datum/plant_gene/trait/attack/nettle_attack, /datum/plant_gene/trait/backfire/nettle_burn)
mutatelist = list(/obj/item/seeds/nettle/death)
reagents_add = list(/datum/reagent/toxin/acid = 0.5)
+/obj/item/seeds/nettle/Initialize(mapload,nogenes)
+ . = ..()
+ if(!nogenes)
+ unset_mutability(/datum/plant_gene/trait/attack/nettle_attack, PLANT_GENE_REMOVABLE)
+ unset_mutability(/datum/plant_gene/trait/backfire/nettle_burn, PLANT_GENE_REMOVABLE)
+
/obj/item/seeds/nettle/death
name = "pack of death-nettle seeds"
desc = "These seeds grow into death-nettles."
@@ -23,12 +29,18 @@
endurance = 25
maturation = 8
yield = 2
- genes = list(/datum/plant_gene/trait/repeated_harvest, /datum/plant_gene/trait/plant_type/weed_hardy, /datum/plant_gene/trait/stinging)
+ genes = list(/datum/plant_gene/trait/repeated_harvest, /datum/plant_gene/trait/plant_type/weed_hardy, /datum/plant_gene/trait/stinging, /datum/plant_gene/trait/attack/nettle_attack/death, /datum/plant_gene/trait/backfire/nettle_burn/death)
mutatelist = list()
reagents_add = list(/datum/reagent/toxin/acid/fluacid = 0.5, /datum/reagent/toxin/acid = 0.5)
rarity = 20
research = PLANT_RESEARCH_TIER_3
+/obj/item/seeds/nettle/death/Initialize(mapload,nogenes)
+ . = ..()
+ if(!nogenes)
+ unset_mutability(/datum/plant_gene/trait/attack/nettle_attack/death, PLANT_GENE_REMOVABLE)
+ unset_mutability(/datum/plant_gene/trait/backfire/nettle_burn/death, PLANT_GENE_REMOVABLE)
+
/obj/item/reagent_containers/food/snacks/grown/nettle // "snack"
seed = /obj/item/seeds/nettle
name = "nettle"
@@ -48,40 +60,6 @@
wine_power = 20
wine_flavor = "tingling itchiness" //WS edit: new wine flavors
-/obj/item/reagent_containers/food/snacks/grown/nettle/pickup(mob/living/user)
- ..()
- if(!iscarbon(user))
- return FALSE
- var/mob/living/carbon/C = user
- if(C.gloves)
- return FALSE
- if(HAS_TRAIT(C, TRAIT_PIERCEIMMUNE))
- return FALSE
- var/hit_zone = (C.held_index_to_dir(C.active_hand_index) == "l" ? "l_":"r_") + "arm"
- var/obj/item/bodypart/affecting = C.get_bodypart(hit_zone)
- if(affecting)
- if(affecting.receive_damage(0, force))
- C.update_damage_overlays()
- to_chat(C, "The nettle burns your bare hand!")
- return TRUE
-
-/obj/item/reagent_containers/food/snacks/grown/nettle/afterattack(atom/A as mob|obj, mob/user,proximity)
- . = ..()
- if(!proximity)
- return
- if(force > 0)
- force -= rand(1, (force / 3) + 1) // When you whack someone with it, leaves fall off
- else
- to_chat(usr, "All the leaves have fallen off the nettle from violent whacking.")
- qdel(src)
-
-/obj/item/reagent_containers/food/snacks/grown/nettle/basic
- seed = /obj/item/seeds/nettle
-
-/obj/item/reagent_containers/food/snacks/grown/nettle/basic/add_juice()
- ..()
- force = round((5 + seed.potency / 5), 1)
-
/obj/item/reagent_containers/food/snacks/grown/nettle/death
seed = /obj/item/seeds/nettle/death
name = "deathnettle"
@@ -91,26 +69,3 @@
throwforce = 15
wine_power = 50
wine_flavor = "burning rage" //WS edit: new wine flavors
-
-/obj/item/reagent_containers/food/snacks/grown/nettle/death/add_juice()
- ..()
- force = round((5 + seed.potency / 2.5), 1)
-
-/obj/item/reagent_containers/food/snacks/grown/nettle/death/pickup(mob/living/carbon/user)
- if(..())
- if(prob(50))
- user.Paralyze(100)
- to_chat(user, "You are stunned by [src] as you try picking it up!")
-
-/obj/item/reagent_containers/food/snacks/grown/nettle/death/attack(mob/living/carbon/M, mob/user)
- if(!..())
- return
- if(isliving(M))
- to_chat(M, "You are stunned by the powerful acid of [src]!")
- log_combat(user, M, "attacked", src)
-
- M.adjust_blurriness(force/7)
- if(prob(20))
- M.Unconscious(force / 0.3)
- M.Paralyze(force / 0.75)
- M.drop_all_held_items()
diff --git a/code/modules/hydroponics/growninedible.dm b/code/modules/hydroponics/growninedible.dm
index f97596c348f..ee76f02e02c 100644
--- a/code/modules/hydroponics/growninedible.dm
+++ b/code/modules/hydroponics/growninedible.dm
@@ -23,8 +23,9 @@
pixel_y = base_pixel_y + rand(-5, 5)
if(seed)
- for(var/datum/plant_gene/trait/T in seed.genes)
- T.on_new(src, newloc)
+ // Go through all traits in their genes and call on_new_plant from them.
+ for(var/datum/plant_gene/trait/trait in seed.genes)
+ trait.on_new_plant(src, newloc)
if(istype(src, seed.product)) // no adding reagents if it is just a trash item
seed.prepare_result(src)
diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm
index bbfeaeeb5b5..13faaf5f15a 100644
--- a/code/modules/hydroponics/hydroponics.dm
+++ b/code/modules/hydroponics/hydroponics.dm
@@ -194,7 +194,7 @@
//Pests & Weeds//////////////////////////////////////////////////////////
if(pestlevel >= 8)
- if(!myseed.get_gene(/datum/plant_gene/trait/plant_type/carnivory))
+ if(!myseed.get_gene(/datum/plant_gene/trait/carnivory))
adjustHealth(-2 / rating)
else
@@ -202,7 +202,7 @@
adjustPests(-1 / rating)
else if(pestlevel >= 4)
- if(!myseed.get_gene(/datum/plant_gene/trait/plant_type/carnivory))
+ if(!myseed.get_gene(/datum/plant_gene/trait/carnivory))
adjustHealth(-1 / rating)
else
@@ -210,7 +210,7 @@
if(prob(50))
adjustPests(-1 / rating)
- else if(pestlevel < 4 && myseed.get_gene(/datum/plant_gene/trait/plant_type/carnivory))
+ else if(pestlevel < 4 && myseed.get_gene(/datum/plant_gene/trait/carnivory))
adjustHealth(-2 / rating)
if(prob(5))
adjustPests(-1 / rating)
@@ -269,10 +269,7 @@
update_appearance()
if(myseed && prob(5 * (11-myseed.production)))
- for(var/g in myseed.genes)
- if(istype(g, /datum/plant_gene/trait))
- var/datum/plant_gene/trait/selectedtrait = g
- selectedtrait.on_grow(src)
+ SEND_SIGNAL(myseed, COMSIG_SEED_ON_GROW, src)
return
/obj/machinery/hydroponics/update_appearance(updates)
diff --git a/code/modules/hydroponics/plant_genes.dm b/code/modules/hydroponics/plant_genes.dm
index a57934dc551..d34490d2bd2 100644
--- a/code/modules/hydroponics/plant_genes.dm
+++ b/code/modules/hydroponics/plant_genes.dm
@@ -1,6 +1,8 @@
/datum/plant_gene
var/name
var/mutability_flags = PLANT_GENE_EXTRACTABLE | PLANT_GENE_REMOVABLE ///These flags tells the genemodder if we want the gene to be extractable, only removable or neither.
+ /// The font awesome icon name representing the gene in the seed extractor UI (Once i port that -Fallcon)
+ var/icon = "dna"
/datum/plant_gene/proc/get_name() // Used for manipulator display and gene disk name.
var/formatted_name
@@ -14,16 +16,39 @@
formatted_name += name
return formatted_name
-/datum/plant_gene/proc/can_add(obj/item/seeds/S)
- return !istype(S, /obj/item/seeds/sample) // Samples can't accept new genes
+/*
+ * Check if the seed can accept this plant gene.
+ *
+ * our_seed - the seed we're adding the gene to
+ *
+ * Returns TRUE if the seed can take the gene, and FALSE otherwise.
+ */
+/datum/plant_gene/proc/can_add(obj/item/seeds/our_seed)
+ SHOULD_CALL_PARENT(TRUE)
+ return TRUE
+/// Copies over vars and information about our current gene to a new gene and returns the new instance of gene.
/datum/plant_gene/proc/Copy()
- var/datum/plant_gene/G = new type
- G.mutability_flags = mutability_flags
- return G
-
-/datum/plant_gene/proc/apply_vars(obj/item/seeds/S) // currently used for fire resist, can prob. be further refactored
- return
+ var/datum/plant_gene/new_gene = new type
+ new_gene.mutability_flags = mutability_flags
+ return new_gene
+
+/*
+ * on_new_seed is called when seed genes are initialized on the /obj/seed.
+ *
+ * new_seed - the seed being created
+ */
+/datum/plant_gene/proc/on_new_seed(obj/item/seeds/new_seed)
+ return // Not implemented
+
+/*
+ * on_removed is called when the gene is removed from a seed.
+ * Also called when a seed is qdel'd (and all the genes are removed and deleted).
+ *
+ * old_seed - our seed, before being removed
+ */
+/datum/plant_gene/proc/on_removed(obj/item/seeds/old_seed)
+ return // Not implemented
// Core plant genes store 5 main variables: lifespan, endurance, production, yield, potency
/datum/plant_gene/core
@@ -171,28 +196,70 @@
// Various traits affecting the product.
/datum/plant_gene/trait
+ /// The rate at which this trait affects something. This can be anything really - why? I dunno.
var/rate = 0.05
var/examine_line = ""
- var/trait_id // must be set and equal for any two traits of the same type
+ /// Bonus lines displayed on examine.
+ var/description = ""
+ /// Flag - Traits that share an ID cannot be placed on the same plant.
+ var/trait_ids
+ /// Flag - Modifications made to the final product.
+ var/trait_flags
+ /// A blacklist of seeds that a trait cannot be attached to.
+ var/list/obj/item/seeds/seed_blacklist
/datum/plant_gene/trait/Copy()
var/datum/plant_gene/trait/G = ..()
G.rate = rate
return G
-/datum/plant_gene/trait/can_add(obj/item/seeds/S)
+/datum/plant_gene/trait/can_add(obj/item/seeds/source_seed)
if(!..())
return FALSE
- for(var/datum/plant_gene/trait/R in S.genes)
- if(trait_id && R.trait_id == trait_id)
+ for(var/obj/item/seeds/found_seed as anything in seed_blacklist)
+ if(istype(source_seed, found_seed))
return FALSE
- if(type == R.type)
+
+ for(var/datum/plant_gene/trait/trait in source_seed.genes)
+ if(trait_ids & trait.trait_ids)
+ return FALSE
+ if(type == trait.type)
return FALSE
+
return TRUE
-/datum/plant_gene/trait/proc/on_new(obj/item/reagent_containers/food/snacks/grown/G, newloc)
- return
+/*
+ * on_new_plant is called for every plant trait on an /obj/item/grown or /obj/item/reagent_containers/food/snacks/grown when initialized.
+ *
+ * our_plant - the source plant being created
+ * newloc - the loc of the plant
+ */
+/datum/plant_gene/trait/proc/on_new_plant(obj/item/reagent_containers/food/snacks/grown/our_plant, newloc)
+ // Plants should always have seeds, but if a plant gene is somehow being instantiated on a plant with no seed, stop initializing genes
+ // (Plants hold their genes on their seeds, so we can't really add them to something that doesn't exist)
+ if(isnull(our_plant.get_plant_seed()))
+ stack_trace("[our_plant] ([our_plant.type]) has a nulled seed value while trying to initialize [src]!")
+ return FALSE
+
+ // Add on any bonus lines on examine
+ if(description)
+ RegisterSignal(our_plant, COMSIG_PARENT_EXAMINE, PROC_REF(examine))
+ return TRUE
+
+/*
+ * on_new_seed is called when seed genes are initialized on the /obj/seed.
+ *
+ * new_seed - the seed being created
+ */
+/datum/plant_gene/trait/on_new_seed(obj/item/seeds/new_seed)
+ return TRUE
+
+/// Add on any unique examine text to the plant's examine text.
+/datum/plant_gene/trait/proc/examine(obj/item/reagent_containers/food/snacks/grown/our_plant, mob/examiner, list/examine_list)
+ SIGNAL_HANDLER
+
+ examine_list += span_info("[description]")
/datum/plant_gene/trait/proc/on_consume(obj/item/reagent_containers/food/snacks/grown/G, mob/living/carbon/target)
return
@@ -213,84 +280,173 @@
/datum/plant_gene/trait/proc/on_grow(obj/machinery/hydroponics/H)
return
+/// Allows the plant to be squashed when thrown or slipped on, leaving a colored mess and trash type item behind.
/datum/plant_gene/trait/squash
- // Allows the plant to be squashed when thrown or slipped on, leaving a colored mess and trash type item behind.
- // Also splashes everything in target turf with reagents and applies other trait effects (teleporting, etc) to the target by on_squash.
- // For code, see grown.dm
name = "Liquid Contents"
- examine_line = "It has a lot of liquid contents inside."
+ icon = "droplet"
+ description = "It may burst open from the internal pressure on impact."
+ trait_ids = THROW_IMPACT_ID | REAGENT_TRANSFER_ID | ATTACK_SELF_ID
+ mutability_flags = PLANT_GENE_REMOVABLE | PLANT_GENE_MUTATABLE | PLANT_GENE_EXTRACTABLE
+
+// Register a signal that our plant can be squashed on add.
+/datum/plant_gene/trait/squash/on_new_plant(obj/item/reagent_containers/food/snacks/grown/our_plant, newloc)
+ . = ..()
+ if(!.)
+ return
+
+ RegisterSignal(our_plant, COMSIG_PLANT_ON_SLIP, PROC_REF(squash_plant))
+ RegisterSignal(our_plant, COMSIG_MOVABLE_IMPACT, PROC_REF(squash_plant))
+ RegisterSignal(our_plant, COMSIG_ITEM_ATTACK_SELF, PROC_REF(squash_plant))
+
+/*
+ * Signal proc to squash the plant this trait belongs to, causing a smudge, exposing the target to reagents, and deleting it,
+ *
+ * Arguments
+ * our_plant - the plant this trait belongs to.
+ * target - the atom being hit by this squashed plant.
+ */
+/datum/plant_gene/trait/squash/proc/squash_plant(obj/item/reagent_containers/food/snacks/grown/our_plant, atom/target)
+ SIGNAL_HANDLER
+
+ var/turf/our_turf = get_turf(target)
+ our_plant.forceMove(our_turf)
+ if(istype(our_plant))
+ if(ispath(our_plant.splat_type, /obj/effect/decal/cleanable/food/plant_smudge))
+ var/obj/plant_smudge = new our_plant.splat_type(our_turf)
+ plant_smudge.name = "[our_plant.name] smudge"
+ if(our_plant.filling_color)
+ plant_smudge.color = our_plant.filling_color
+ else if(our_plant.splat_type)
+ new our_plant.splat_type(our_turf)
+ else
+ var/obj/effect/decal/cleanable/food/plant_smudge/misc_smudge = new(our_turf)
+ misc_smudge.name = "[our_plant.name] smudge"
+ misc_smudge.color = "#82b900"
-/datum/plant_gene/trait/squash/on_slip(obj/item/reagent_containers/food/snacks/grown/G, mob/living/carbon/C)
- // Squash the plant on slip.
- G.squash(C)
+ our_plant.visible_message(span_warning("[our_plant] is squashed."),span_hear("You hear a smack."))
+ SEND_SIGNAL(our_plant, COMSIG_PLANT_ON_SQUASH, target)
+ our_plant.reagents?.expose(our_turf)
+ for(var/things in our_turf)
+ our_plant.reagents?.expose(things)
+
+ qdel(our_plant)
+
+/*
+ * Makes plant slippery, unless it has a grown-type trash. Then the trash gets slippery.
+ * Applies other trait effects (teleporting, etc) to the target by signal.
+ */
/datum/plant_gene/trait/slip
- // Makes plant slippery, unless it has a grown-type trash. Then the trash gets slippery.
- // Applies other trait effects (teleporting, etc) to the target by on_slip.
name = "Slippery Skin"
+ description = "Watch your step around this."
+ icon = "person-falling"
rate = 1.6
- examine_line = "It has a very slippery skin."
+ mutability_flags = PLANT_GENE_REMOVABLE | PLANT_GENE_MUTATABLE | PLANT_GENE_EXTRACTABLE
-/datum/plant_gene/trait/slip/on_new(obj/item/reagent_containers/food/snacks/grown/G, newloc)
- ..()
- if(istype(G) && ispath(G.trash, /obj/item/grown))
+/datum/plant_gene/trait/slip/on_new_plant(obj/item/our_plant, newloc)
+ . = ..()
+ if(!.)
+ return
+
+ var/obj/item/reagent_containers/food/snacks/grown/grown_plant = our_plant
+ if(istype(grown_plant) && ispath(grown_plant.trash, /obj/item/grown))
return
- var/obj/item/seeds/seed = G.seed
- var/stun_len = seed.potency * rate
- if(!istype(G, /obj/item/grown/bananapeel) && (!G.reagents || !G.reagents.has_reagent(/datum/reagent/lube)))
+ var/obj/item/seeds/our_seed = our_plant.get_plant_seed()
+ var/stun_len = our_seed.potency * rate
+
+ if(!istype(our_plant, /obj/item/grown/bananapeel) && (!our_plant.reagents || !our_plant.reagents.has_reagent(/datum/reagent/lube)))
stun_len /= 3
- G.AddComponent(/datum/component/slippery, min(stun_len,140), NONE, CALLBACK(src, PROC_REF(handle_slip), G))
+ our_plant.AddComponent(/datum/component/slippery, min(stun_len, 140), NONE, CALLBACK(src, PROC_REF(handle_slip), our_plant))
+
+/// On slip, sends a signal that our plant was slipped on out.
+/datum/plant_gene/trait/slip/proc/handle_slip(obj/item/reagent_containers/food/snacks/grown/our_plant, mob/slipped_target)
+ SEND_SIGNAL(our_plant, COMSIG_PLANT_ON_SLIP, slipped_target)
-/datum/plant_gene/trait/slip/proc/handle_slip(obj/item/reagent_containers/food/snacks/grown/G, mob/M)
- for(var/datum/plant_gene/trait/T in G.seed.genes)
- T.on_slip(G, M)
+/*
+ * Cell recharging trait. Charges all mob's power cells to (potency*rate)% mark when eaten.
+ * Generates sparks on squash.
+ * Small (potency * rate) chance to shock squish or slip target for (potency * rate) damage.
+ * Also affects plant batteries see capatative cell production datum
+ */
/datum/plant_gene/trait/cell_charge
- // Cell recharging trait. Charges all mob's power cells to (potency*rate)% mark when eaten.
- // Generates sparks on squash.
- // Small (potency*rate*5) chance to shock squish or slip target for (potency*rate*5) damage.
- // Also affects plant batteries see capatative cell production datum
name = "Electrical Activity"
+ description = "It can electrocute on interaction or recharge batteries when eaten."
+ icon = "bolt"
rate = 0.2
+ mutability_flags = PLANT_GENE_REMOVABLE | PLANT_GENE_MUTATABLE | PLANT_GENE_EXTRACTABLE
-/datum/plant_gene/trait/cell_charge/on_slip(obj/item/reagent_containers/food/snacks/grown/G, mob/living/carbon/C)
- var/power = G.seed.potency*rate
- if(prob(power))
- C.electrocute_act(round(power), G, 1, SHOCK_NOGLOVES)
-
-/datum/plant_gene/trait/cell_charge/on_squash(obj/item/reagent_containers/food/snacks/grown/G, atom/target)
- if(iscarbon(target))
- var/mob/living/carbon/C = target
- var/power = G.seed.potency*rate
- if(prob(power))
- C.electrocute_act(round(power), G, 1, SHOCK_NOGLOVES)
-
-/datum/plant_gene/trait/cell_charge/on_consume(obj/item/reagent_containers/food/snacks/grown/G, mob/living/carbon/target)
- if(!G.reagents.total_volume)
- var/batteries_recharged = 0
- for(var/obj/item/stock_parts/cell/C in target.GetAllContents())
- var/newcharge = min(G.seed.potency*0.01*C.maxcharge, C.maxcharge)
- if(C.charge < newcharge)
- C.charge = newcharge
- if(isobj(C.loc))
- var/obj/O = C.loc
- O.update_appearance() //update power meters and such
- C.update_appearance()
- batteries_recharged = 1
- if(batteries_recharged)
- to_chat(target, "Your batteries are recharged!")
+/datum/plant_gene/trait/cell_charge/on_new_plant(obj/item/our_plant, newloc)
+ . = ..()
+ if(!.)
+ return
+
+ var/obj/item/seeds/our_seed = our_plant.get_plant_seed()
+ if(our_seed.get_gene(/datum/plant_gene/trait/squash))
+ // If we have the squash gene, let that handle slipping
+ RegisterSignal(our_plant, COMSIG_PLANT_ON_SQUASH, PROC_REF(zap_target))
+ else
+ RegisterSignal(our_plant, COMSIG_PLANT_ON_SLIP, PROC_REF(zap_target))
+ RegisterSignal(our_plant, COMSIG_FOOD_EATEN, PROC_REF(recharge_cells))
+/*
+ * Zaps the target with a stunning shock.
+ *
+ * our_plant - our source plant, shocking the target
+ * target - the atom being zapped by our plant
+ */
+/datum/plant_gene/trait/cell_charge/proc/zap_target(obj/item/our_plant, atom/target)
+ SIGNAL_HANDLER
+ if(!iscarbon(target))
+ return
+
+ our_plant.investigate_log("zapped [key_name(target)] at [AREACOORD(target)]. Last touched by: [our_plant.fingerprintslast].", INVESTIGATE_BOTANY)
+ var/mob/living/carbon/target_carbon = target
+ var/obj/item/seeds/our_seed = our_plant.get_plant_seed()
+ var/power = our_seed.potency * rate
+ if(prob(power))
+ target_carbon.electrocute_act(round(power), our_plant, 1, SHOCK_NOGLOVES)
+
+/*
+ * Recharges every cell the person is holding for a bit based on plant potency.
+ *
+ * our_plant - our source plant, that we consumed to charge the cells
+ * eater - the mob that bit the plant
+ * feeder - the mob that feed the eater the plant
+ */
+/datum/plant_gene/trait/cell_charge/proc/recharge_cells(obj/item/our_plant, mob/living/carbon/eater, mob/feeder)
+ SIGNAL_HANDLER
+
+ to_chat(eater, span_notice("You feel energized as you bite into [our_plant]."))
+ var/batteries_recharged = FALSE
+ var/obj/item/seeds/our_seed = our_plant.get_plant_seed()
+ for(var/obj/item/stock_parts/cell/found_cell in eater.get_contents())
+ var/newcharge = min(our_seed.potency * 0.01 * found_cell.maxcharge, found_cell.maxcharge)
+ if(found_cell.charge < newcharge)
+ found_cell.charge = newcharge
+ if(isobj(found_cell.loc))
+ var/obj/cell_location = found_cell.loc
+ cell_location.update_appearance() //update power meters and such
+ found_cell.update_appearance()
+ batteries_recharged = TRUE
+ if(batteries_recharged)
+ to_chat(eater, span_notice("Your batteries are recharged!"))
+
+/*
+ * Makes the plant glow. Makes the plant in tray glow, too.
+ * Adds (1.4 + potency * rate) light range and (potency * (rate + 0.01)) light_power to products.
+ */
/datum/plant_gene/trait/glow
- // Makes plant glow. Makes plant in tray glow too.
- // Adds 1 + potency*rate light range and potency*(rate + 0.01) light_power to products.
name = "Bioluminescence"
+ icon = "lightbulb"
rate = 0.03
- examine_line = "It emits a soft glow."
- trait_id = "glow"
+ description = "It emits a soft glow."
+ trait_ids = GLOW_ID
+ mutability_flags = PLANT_GENE_REMOVABLE | PLANT_GENE_MUTATABLE | PLANT_GENE_EXTRACTABLE
var/glow_color = "#C3E381"
/datum/plant_gene/trait/glow/proc/glow_range(obj/item/seeds/S)
@@ -299,15 +455,18 @@
/datum/plant_gene/trait/glow/proc/glow_power(obj/item/seeds/S)
return max(S.potency*(rate + 0.01), 0.1)
-/datum/plant_gene/trait/glow/on_new(obj/item/reagent_containers/food/snacks/grown/G, newloc)
+/datum/plant_gene/trait/glow/on_new_plant(obj/item/reagent_containers/food/snacks/grown/G, newloc)
. = ..()
G.light_system = MOVABLE_LIGHT
G.AddComponent(/datum/component/overlay_lighting, glow_range(G.seed), glow_power(G.seed), glow_color)
+/*
+ * Makes plant emit darkness. (Purple-ish shadows)
+ * Adds - (potency * (rate * 0.2)) light power to products.
+ */
/datum/plant_gene/trait/glow/shadow
- //makes plant emit slightly purple shadows
- //adds -potency*(rate*0.2) light power to products
name = "Shadow Emission"
+ icon = "lightbulb-o"
rate = 0.04
glow_color = "#AAD84B"
@@ -348,157 +507,359 @@
name = "Pink Bioluminescence"
glow_color = "#FFB3DA"
-
-
+/*
+ * Makes plant teleport people when squashed or slipped on.
+ * Teleport radius is roughly potency / 10.
+ */
/datum/plant_gene/trait/teleport
- // Makes plant teleport people when squashed or slipped on.
- // Teleport radius is calculated as max(round(potency*rate), 1)
name = "Bluespace Activity"
+ description = "It causes people to teleport on interaction."
+ icon = "right-left"
rate = 0.1
+ mutability_flags = PLANT_GENE_REMOVABLE | PLANT_GENE_MUTATABLE | PLANT_GENE_EXTRACTABLE
-/datum/plant_gene/trait/teleport/on_squash(obj/item/reagent_containers/food/snacks/grown/G, atom/target)
- if(isliving(target))
- var/teleport_radius = max(round(G.seed.potency / 10), 1)
- var/turf/T = get_turf(target)
- new /obj/effect/decal/cleanable/molten_object(T) //Leave a pile of goo behind for dramatic effect...
- do_teleport(target, T, teleport_radius, channel = TELEPORT_CHANNEL_BLUESPACE)
+/datum/plant_gene/trait/teleport/on_new_plant(obj/item/our_plant, newloc)
+ . = ..()
+ if(!.)
+ return
+
+ var/obj/item/seeds/our_seed = our_plant.get_plant_seed()
+ if(our_seed.get_gene(/datum/plant_gene/trait/squash))
+ // If we have the squash gene, let that handle slipping
+ RegisterSignal(our_plant, COMSIG_PLANT_ON_SQUASH, PROC_REF(squash_teleport))
+ else
+ RegisterSignal(our_plant, COMSIG_PLANT_ON_SLIP, PROC_REF(slip_teleport))
+
+/*
+ * When squashed, makes the target teleport.
+ *
+ * our_plant - our plant, being squashed, and teleporting the target
+ * target - the atom targeted by the squash
+ */
+/datum/plant_gene/trait/teleport/proc/squash_teleport(obj/item/our_plant, atom/target)
+ SIGNAL_HANDLER
+
+ if(!isliving(target))
+ return
-/datum/plant_gene/trait/teleport/on_slip(obj/item/reagent_containers/food/snacks/grown/G, mob/living/carbon/C)
- var/teleport_radius = max(round(G.seed.potency / 10), 1)
- var/turf/T = get_turf(C)
- to_chat(C, "You slip through spacetime!")
- do_teleport(C, T, teleport_radius, channel = TELEPORT_CHANNEL_BLUESPACE)
+ our_plant.investigate_log("squash-teleported [key_name(target)] at [AREACOORD(target)]. Last touched by: [our_plant.fingerprintslast].", INVESTIGATE_BOTANY)
+ var/obj/item/seeds/our_seed = our_plant.get_plant_seed()
+ var/teleport_radius = max(round(our_seed.potency / 10), 1)
+ var/turf/T = get_turf(target)
+ new /obj/effect/decal/cleanable/molten_object(T) //Leave a pile of goo behind for dramatic effect...
+ do_teleport(target, T, teleport_radius, channel = TELEPORT_CHANNEL_BLUESPACE)
+
+/*
+ * When slipped on, makes the target teleport and either teleport the source again or delete it.
+ *
+ * our_plant - our plant being slipped on
+ * target - the carbon targeted that was slipped and was teleported
+ */
+/datum/plant_gene/trait/teleport/proc/slip_teleport(obj/item/our_plant, mob/living/carbon/target)
+ SIGNAL_HANDLER
+
+ our_plant.investigate_log("slip-teleported [key_name(target)] at [AREACOORD(target)]. Last touched by: [our_plant.fingerprintslast].", INVESTIGATE_BOTANY)
+ var/obj/item/seeds/our_seed = our_plant.get_plant_seed()
+ var/teleport_radius = max(round(our_seed.potency / 10), 1)
+ var/turf/T = get_turf(target)
+ to_chat(target, span_warning("You slip through spacetime!"))
+ do_teleport(target, T, teleport_radius, channel = TELEPORT_CHANNEL_BLUESPACE)
if(prob(50))
- do_teleport(G, T, teleport_radius, channel = TELEPORT_CHANNEL_BLUESPACE)
+ do_teleport(our_plant, T, teleport_radius, channel = TELEPORT_CHANNEL_BLUESPACE)
else
new /obj/effect/decal/cleanable/molten_object(T) //Leave a pile of goo behind for dramatic effect...
- qdel(G)
-
+ qdel(our_plant)
+/**
+ * A plant trait that causes the plant's capacity to double.
+ *
+ * When harvested, the plant's individual capacity is set to double it's default.
+ */
/datum/plant_gene/trait/maxchem
- // 2x to max reagents volume.
name = "Densified Chemicals"
+ description = "The reagent volume is doubled."
+ icon = "flask-vial"
rate = 2
+ mutability_flags = PLANT_GENE_REMOVABLE | PLANT_GENE_MUTATABLE | PLANT_GENE_EXTRACTABLE
-/datum/plant_gene/trait/maxchem/on_new(obj/item/reagent_containers/food/snacks/grown/G, newloc)
- ..()
- G.reagents.maximum_volume *= rate
+/datum/plant_gene/trait/maxchem/on_new_plant(obj/item/reagent_containers/food/snacks/grown/our_plant, newloc)
+ . = ..()
+ if(!.)
+ return
+ our_plant.reagents?.maximum_volume *= rate
+
+/// Allows a plant to be harvested multiple times.
/datum/plant_gene/trait/repeated_harvest
name = "Perennial Growth"
-
-/datum/plant_gene/trait/repeated_harvest/can_add(obj/item/seeds/S)
- if(!..())
- return FALSE
- if(istype(S, /obj/item/seeds/replicapod))
- return FALSE
- return TRUE
-
+ description = "It may be harvested multiple times from the same plant."
+ icon = "cubes-stacked"
+ /// Don't allow replica pods to be multi harvested, please.
+ seed_blacklist = list(
+ /obj/item/seeds/replicapod,
+ )
+ mutability_flags = PLANT_GENE_REMOVABLE | PLANT_GENE_MUTATABLE | PLANT_GENE_EXTRACTABLE
+
+/*
+ * Allows a plant to be turned into a battery when cabling is applied.
+ * 100 potency plants are made into 2 mj batteries.
+ * Plants with electrical activity has their capacities massively increased (up to 40 mj at 100 potency)
+ */
/datum/plant_gene/trait/battery
name = "Capacitive Cell Production"
+ description = "It can work like a power cell when wired properly."
+ icon = "car-battery"
+ mutability_flags = PLANT_GENE_REMOVABLE | PLANT_GENE_MUTATABLE | PLANT_GENE_EXTRACTABLE
+ /// The number of cables needed to make a battery.
+ var/cables_needed_per_battery = 5
-/datum/plant_gene/trait/battery/on_attackby(obj/item/reagent_containers/food/snacks/grown/G, obj/item/I, mob/user)
- if(istype(I, /obj/item/stack/cable_coil))
- var/obj/item/stack/cable_coil/C = I
- if(C.use(5))
- to_chat(user, "You add some cable to [G] and slide it inside the battery encasing.")
- var/obj/item/stock_parts/cell/potato/pocell = new /obj/item/stock_parts/cell/potato(user.loc)
- pocell.icon_state = G.icon_state
- pocell.maxcharge = G.seed.potency * 20
-
- // The secret of potato supercells!
- var/datum/plant_gene/trait/cell_charge/CG = G.seed.get_gene(/datum/plant_gene/trait/cell_charge)
- if(CG) // Cell charge max is now 40MJ or otherwise known as 400KJ (Same as bluespace powercells)
- pocell.maxcharge *= CG.rate*100
- pocell.charge = pocell.maxcharge
- pocell.name = "[G.name] battery"
- pocell.desc = "A rechargeable plant-based power cell. This one has a rating of [DisplayEnergy(pocell.maxcharge)], and you should not swallow it."
-
- if(G.reagents.has_reagent(/datum/reagent/toxin/plasma, 2))
- pocell.rigged = TRUE
-
- qdel(G)
- else
- to_chat(user, "You need five lengths of cable to make a [G] battery!")
+/datum/plant_gene/trait/battery/on_new_plant(obj/item/our_plant, newloc)
+ . = ..()
+ if(!.)
+ return
+
+ RegisterSignal(our_plant, COMSIG_PARENT_ATTACKBY, PROC_REF(make_battery))
+
+/*
+ * When a plant with this gene is hit (attackby) with cables, we turn it into a battery.
+ *
+ * our_plant - our plant being hit
+ * hit_item - the item we're hitting the plant with
+ * user - the person hitting the plant with an item
+ */
+/datum/plant_gene/trait/battery/proc/make_battery(obj/item/our_plant, obj/item/hit_item, mob/user)
+ SIGNAL_HANDLER
+
+ if(!istype(hit_item, /obj/item/stack/cable_coil))
+ return
+
+ var/obj/item/seeds/our_seed = our_plant.get_plant_seed()
+ var/obj/item/stack/cable_coil/cabling = hit_item
+ if(!cabling.use(cables_needed_per_battery))
+ to_chat(user, span_warning("You need five lengths of cable to make a [our_plant] battery!"))
+ return
+ to_chat(user, span_notice("You add some cable to [our_plant] and slide it inside the battery encasing."))
+ var/obj/item/stock_parts/cell/potato/pocell = new /obj/item/stock_parts/cell/potato(user.loc)
+ pocell.icon = our_plant.icon // Just in case the plant icons get spread out in different files eventually, this trait won't cause error sprites (also yay downstreams)
+ pocell.icon_state = our_plant.icon_state
+ pocell.maxcharge = our_seed.potency
+ // The secret of potato supercells!
+ var/datum/plant_gene/trait/cell_charge/electrical_gene = our_seed.get_gene(/datum/plant_gene/trait/cell_charge)
+ if(electrical_gene) // Cell charge max is now 40MJ or otherwise known as 400KJ (Same as bluespace power cells)
+ pocell.maxcharge *= (electrical_gene.rate * 100)
+
+ pocell.charge = pocell.maxcharge
+ pocell.name = "[our_plant.name] battery"
+ pocell.desc = "A rechargeable plant-based power cell. This one has a rating of [DisplayEnergy(pocell.maxcharge)], and you should not swallow it."
+
+ if(our_plant.reagents.has_reagent(/datum/reagent/toxin/plasma, 2))
+ pocell.rigged = TRUE
+
+ qdel(our_plant)
+
+/*
+ * Injects a number of chemicals from the plant when you throw it at someone or they slip on it.
+ * At 0 potency it can inject 1 unit of its chemicals, while at 100 potency it can inject 20 units.
+ */
/datum/plant_gene/trait/stinging
name = "Hypodermic Prickles"
+ description = "It stings, passing some reagents in the process."
+ icon = "syringe"
+ trait_ids = REAGENT_TRANSFER_ID
+ mutability_flags = PLANT_GENE_REMOVABLE | PLANT_GENE_MUTATABLE | PLANT_GENE_EXTRACTABLE
+
+/datum/plant_gene/trait/stinging/on_new_plant(obj/item/our_plant, newloc)
+ . = ..()
+ if(!.)
+ return
+
+ RegisterSignal(our_plant, COMSIG_PLANT_ON_SLIP, PROC_REF(prickles_inject))
+ RegisterSignal(our_plant, COMSIG_MOVABLE_IMPACT, PROC_REF(prickles_inject))
+
+/*
+ * Injects a target with a number of reagents from our plant.
+ *
+ * our_plant - our plant that's injecting someone
+ * target - the atom being hit on thrown or slipping on our plant
+ */
+/datum/plant_gene/trait/stinging/proc/prickles_inject(obj/item/our_plant, atom/target)
+ SIGNAL_HANDLER
-/datum/plant_gene/trait/stinging/on_slip(obj/item/reagent_containers/food/snacks/grown/G, atom/target)
- on_throw_impact(G, target)
+ if(!isliving(target) || !our_plant.reagents?.total_volume)
+ return
-/datum/plant_gene/trait/stinging/on_throw_impact(obj/item/reagent_containers/food/snacks/grown/G, atom/target)
- if(isliving(target) && G.reagents && G.reagents.total_volume)
- var/mob/living/L = target
- if(L.reagents && L.can_inject(null, 0))
- var/injecting_amount = max(1, G.seed.potency*0.2) // Minimum of 1, max of 20
- G.reagents.trans_to(L, injecting_amount, method = INJECT)
- to_chat(target, "You are pricked by [G]!")
- log_combat(G, L, "pricked and attempted to inject reagents from [G] to [L]. Last touched by: [G.fingerprintslast].")
+ var/mob/living/living_target = target
+ var/obj/item/seeds/our_seed = our_plant.get_plant_seed()
+ if(living_target.reagents && living_target.can_inject())
+ var/injecting_amount = max(1, our_seed.potency * 0.2) // Minimum of 1, max of 20
+ our_plant.reagents.trans_to(living_target, injecting_amount, method = INJECT)
+ to_chat(target, span_danger("You are pricked by [our_plant]!"))
+ log_combat(our_plant, living_target, "pricked and attempted to inject reagents from [our_plant] to [living_target]. Last touched by: [our_plant.fingerprintslast].")
+ our_plant.investigate_log("pricked and injected [key_name(living_target)] and injected [injecting_amount] reagents at [AREACOORD(living_target)]. Last touched by: [our_plant.fingerprintslast].", INVESTIGATE_BOTANY)
+/// Explodes into reagent-filled smoke when squashed.
/datum/plant_gene/trait/smoke
name = "Gaseous Decomposition"
+ description = "It can be smashed to turn its Liquid Contents into smoke."
+ icon = "cloud"
+ mutability_flags = PLANT_GENE_REMOVABLE | PLANT_GENE_MUTATABLE | PLANT_GENE_EXTRACTABLE
-/datum/plant_gene/trait/smoke/on_squash(obj/item/reagent_containers/food/snacks/grown/G, atom/target)
- var/datum/effect_system/smoke_spread/chem/S = new
- var/splat_location = get_turf(target)
- var/smoke_amount = round(sqrt(G.seed.potency * 0.1), 1)
- S.attach(splat_location)
- S.set_up(G.reagents, smoke_amount, splat_location, 0)
- S.start()
- G.reagents.clear_reagents()
+/datum/plant_gene/trait/smoke/on_new_plant(obj/item/our_plant, newloc)
+ . = ..()
+ if(!.)
+ return
+
+ RegisterSignal(our_plant, COMSIG_PLANT_ON_SQUASH, PROC_REF(make_smoke))
-/datum/plant_gene/trait/fire_resistance // Lavaland
+/*
+ * Makes a cloud of reagent smoke.
+ *
+ * our_plant - our plant being squashed and smoked
+ * target - the atom the plant was squashed on
+ */
+/datum/plant_gene/trait/smoke/proc/make_smoke(obj/item/reagent_containers/food/snacks/grown/our_plant, atom/target)
+ SIGNAL_HANDLER
+
+ our_plant.investigate_log("made smoke at [AREACOORD(target)]. Last touched by: [our_plant.fingerprintslast].", INVESTIGATE_BOTANY)
+ var/datum/effect_system/smoke_spread/chem/smoke = new
+ var/splat_location = get_turf(target)
+ var/smoke_amount = round(sqrt(our_plant.seed.potency * 0.1), 1)
+ smoke.attach(splat_location)
+ smoke.set_up(our_plant.reagents, smoke_amount, splat_location, 0)
+ smoke.start()
+ our_plant.reagents.clear_reagents()
+
+/// Makes the plant and its seeds fireproof. From lavaland plants.
+/datum/plant_gene/trait/fire_resistance
name = "Fire Resistance"
+ description = "Makes the seeds, plant and produce fireproof."
+ icon = "fire"
+ mutability_flags = PLANT_GENE_REMOVABLE | PLANT_GENE_MUTATABLE | PLANT_GENE_EXTRACTABLE
-/datum/plant_gene/trait/fire_resistance/apply_vars(obj/item/seeds/S)
- if(!(S.resistance_flags & FIRE_PROOF))
- S.resistance_flags |= FIRE_PROOF
+/datum/plant_gene/trait/fire_resistance/on_new_seed(obj/item/seeds/new_seed)
+ if(!(new_seed.resistance_flags & FIRE_PROOF))
+ new_seed.resistance_flags |= FIRE_PROOF
-/datum/plant_gene/trait/fire_resistance/on_new(obj/item/reagent_containers/food/snacks/grown/G, newloc)
- if(!(G.resistance_flags & FIRE_PROOF))
- G.resistance_flags |= FIRE_PROOF
+/datum/plant_gene/trait/fire_resistance/on_removed(obj/item/seeds/old_seed)
+ if(old_seed.resistance_flags & FIRE_PROOF)
+ old_seed.resistance_flags &= ~FIRE_PROOF
+
+/datum/plant_gene/trait/fire_resistance/on_new_plant(obj/item/our_plant, newloc)
+ . = ..()
+ if(!.)
+ return
-///Invasive spreading lets the plant jump to other trays, the spreadinhg plant won't replace plants of the same type.
+ if(!(our_plant.resistance_flags & FIRE_PROOF))
+ our_plant.resistance_flags |= FIRE_PROOF
+
+/// Invasive spreading lets the plant jump to other trays, and the spreading plant won't replace plants of the same type.
/datum/plant_gene/trait/invasive
name = "Invasive Spreading"
+ description = "It attempts to spread around if not contained."
+ icon = "virus"
+ mutability_flags = PLANT_GENE_REMOVABLE | PLANT_GENE_MUTATABLE | PLANT_GENE_EXTRACTABLE
+
+/datum/plant_gene/trait/invasive/on_new_seed(obj/item/seeds/new_seed)
+ RegisterSignal(new_seed, COMSIG_SEED_ON_GROW, PROC_REF(try_spread))
+
+/datum/plant_gene/trait/invasive/on_removed(obj/item/seeds/old_seed)
+ UnregisterSignal(old_seed, COMSIG_SEED_ON_GROW)
+
+/*
+ * Attempt to find an adjacent tray we can spread to.
+ *
+ * our_seed - our plant's seed, what spreads to other trays
+ * our_tray - the hydroponics tray we're currently in
+ */
+/datum/plant_gene/trait/invasive/proc/try_spread(obj/item/seeds/our_seed, obj/machinery/hydroponics/our_tray)
+ SIGNAL_HANDLER
+
+ if(prob(100 - (5 * (11 - our_seed.production))))
+ return
-/datum/plant_gene/trait/invasive/on_grow(obj/machinery/hydroponics/H)
for(var/step_dir in GLOB.alldirs)
- var/obj/machinery/hydroponics/HY = locate() in get_step(H, step_dir)
- if(HY && prob(15))
- if(HY.myseed) // check if there is something in the tray.
- if(HY.myseed.type == H.myseed.type && HY.dead != 0)
- continue //It should not destroy its owm kind.
- qdel(HY.myseed)
- HY.myseed = null
- HY.myseed = H.myseed.Copy()
- HY.age = 0
- HY.dead = 0
- HY.plant_health = HY.myseed.endurance
- HY.lastcycle = world.time
- HY.harvest = 0
- HY.weedlevel = 0 // Reset
- HY.pestlevel = 0 // Reset
- HY.update_appearance()
- HY.visible_message("The [H.myseed.plantname] spreads!")
-
-/datum/plant_gene/trait/plant_type // Parent type
+ var/obj/machinery/hydroponics/spread_tray = locate() in get_step(our_tray, step_dir)
+ if(spread_tray && prob(15))
+ if(!our_tray.Adjacent(spread_tray))
+ continue //Don't spread through things we can't go through.
+
+ spread_seed(spread_tray, our_tray)
+
+/*
+ * Actually spread the plant to the tray we found in try_spread.
+ *
+ * target_tray - the tray we're spreading to
+ * origin_tray - the tray we're currently in
+ */
+/datum/plant_gene/trait/invasive/proc/spread_seed(obj/machinery/hydroponics/target_tray, obj/machinery/hydroponics/origin_tray)
+ if(target_tray.myseed) // Check if there's another seed in the next tray.
+ if(target_tray.myseed.type == origin_tray.myseed.type && target_tray.dead != FALSE)
+ return FALSE // It should not destroy its own kind.
+ target_tray.visible_message(span_warning("The [target_tray.myseed.plantname] is overtaken by [origin_tray.myseed.plantname]!"))
+ QDEL_NULL(target_tray.myseed)
+ target_tray.myseed = origin_tray.myseed.Copy()
+ target_tray.age = 0
+ target_tray.plant_health = target_tray.myseed.endurance
+ target_tray.lastcycle = world.time
+ target_tray.weedlevel = 0
+ target_tray.pestlevel = 0
+ target_tray.visible_message(span_warning("The [origin_tray.myseed.plantname] spreads!"))
+ if(target_tray.myseed)
+ target_tray.name = "[initial(target_tray.name)] ([target_tray.myseed.plantname])"
+ else
+ target_tray.name = initial(target_tray.name)
+
+ return TRUE
+
+/// Makes the plant embed on thrown impact.
+/datum/plant_gene/trait/sticky
+ name = "Prickly Adhesion"
+ description = "It sticks to people when thrown, also passing reagents if stingy."
+ icon = "bandage"
+ trait_ids = THROW_IMPACT_ID
+
+/datum/plant_gene/trait/sticky/on_new_plant(obj/item/our_plant, newloc)
+ . = ..()
+ if(!.)
+ return
+
+ var/obj/item/seeds/our_seed = our_plant.get_plant_seed()
+ if(our_seed.get_gene(/datum/plant_gene/trait/stinging))
+ our_plant.embedding = EMBED_POINTY
+ else
+ our_plant.embedding = EMBED_HARMLESS
+ our_plant.updateEmbedding()
+ our_plant.throwforce = (our_seed.potency/20)
+
+/datum/plant_gene/trait/carnivory
+ name = "Obligate Carnivory"
+ description = "Pests have positive effect on the plant health."
+ icon = "spider"
+
+/// Plant type traits. Incompatible with one another.
+/datum/plant_gene/trait/plant_type
name = "you shouldn't see this"
- trait_id = "plant_type"
+ trait_ids = PLANT_TYPE_ID
+ mutability_flags = PLANT_GENE_EXTRACTABLE
+/// Weeds don't get annoyed by weeds in their tray.
/datum/plant_gene/trait/plant_type/weed_hardy
name = "Weed Adaptation"
+ description = "It is a weed that needs no nutrients and doesn't suffer from other weeds."
+ icon = "seedling"
+/// Mushrooms need less light and have a minimum yield.
/datum/plant_gene/trait/plant_type/fungal_metabolism
name = "Fungal Vitality"
+ description = "It is a mushroom that needs no water, less light and can't be overtaken by weeds."
+ icon = "droplet-slash"
-/datum/plant_gene/trait/plant_type/crystal // WS edit - Crystals
- name = "Crystalline Growing Patterns"
-
+/// Currently unused and does nothing. Appears in strange seeds.
/datum/plant_gene/trait/plant_type/alien_properties
name ="?????"
+ icon = "reddit-alien"
-/datum/plant_gene/trait/plant_type/carnivory
- name = "Obligate Carnivory"
+/datum/plant_gene/trait/plant_type/crystal
+ name = "Crystalline Growing Patterns"
diff --git a/code/modules/hydroponics/seeds.dm b/code/modules/hydroponics/seeds.dm
index ad24dccff43..65f47357899 100644
--- a/code/modules/hydroponics/seeds.dm
+++ b/code/modules/hydroponics/seeds.dm
@@ -40,7 +40,7 @@
var/research = 0 // Defines "discovery value", which will give a one-time point payout if a seed is given to an R&D console. Seed discovery is determined on a ship-by-ship basis.
var/seed_flags = MUTATE_EARLY // Determines if a plant is allowed to mutate early at 30+ instability
-/obj/item/seeds/Initialize(mapload, nogenes = 0)
+/obj/item/seeds/Initialize(mapload, nogenes = FALSE)
. = ..()
pixel_x = base_pixel_y + rand(-8, 8)
pixel_y = base_pixel_x + rand(-8, 8)
@@ -66,10 +66,14 @@
genes += new /datum/plant_gene/core/potency(potency)
genes += new /datum/plant_gene/core/instability(instability)
- for(var/p in genes)
- if(ispath(p))
- genes -= p
- genes += new p
+ for(var/plant_gene in genes)
+ if(ispath(plant_gene))
+ genes -= plant_gene
+ genes += new plant_gene
+
+ // Go through all traits in their genes and call on_new_seed from them.
+ for(var/datum/plant_gene/trait/traits in genes)
+ traits.on_new_seed(src)
for(var/reag_id in reagents_add)
genes += new /datum/plant_gene/reagent(reag_id, reagents_add[reag_id])
@@ -557,3 +561,21 @@
genes += P
else
qdel(P)
+
+/*
+ * Both `/item/food/grown` and `/item/grown` implement a seed variable which tracks
+ * plant statistics, genes, traits, etc. This proc gets the seed for either grown food or
+ * grown inedibles and returns it, or returns null if it's not a plant.
+ *
+ * Returns an `/obj/item/seeds` ref for grown foods or grown inedibles.
+ * - returned seed CAN be null in weird cases but in all applications it SHOULD NOT be.
+ * Returns null if it is not a plant.
+ */
+/obj/item/proc/get_plant_seed()
+ return null
+
+/obj/item/reagent_containers/food/snacks/grown/get_plant_seed()
+ return seed
+
+/obj/item/grown/get_plant_seed()
+ return seed
diff --git a/code/modules/mapping/mapping_helpers.dm b/code/modules/mapping/mapping_helpers.dm
index e9ac7662eda..2040764eefb 100644
--- a/code/modules/mapping/mapping_helpers.dm
+++ b/code/modules/mapping/mapping_helpers.dm
@@ -281,7 +281,7 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava)
if(length(table))
var/turf/food_turf = get_turf(pick(table))
new /obj/item/kitchen/knife(food_turf)
- var/obj/item/reagent_containers/food/snacks/store/cake/birthday/iancake = new(food_turf)
+ var/obj/item/food/cake/birthday/iancake = new(food_turf)
iancake.desc = "Happy birthday, Ian!"
//some balloons! this picks an open turf and pops a few balloons in and around that turf, yay.
diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm
index 7b80559d269..2fa4d0ffc39 100644
--- a/code/modules/mining/lavaland/necropolis_chests.dm
+++ b/code/modules/mining/lavaland/necropolis_chests.dm
@@ -1107,7 +1107,7 @@
C.update_inv_wear_suit()
/obj/item/clothing/suit/armor/ascetic/worn_overlays(isinhands)
- . = list()
+ . = ..()
if(!isinhands)
. += mutable_appearance('icons/effects/effects.dmi', shield_state, MOB_LAYER - 0.01)
diff --git a/code/modules/mob/living/carbon/human/human_movement.dm b/code/modules/mob/living/carbon/human/human_movement.dm
index 77e2045e357..aab8b681bf5 100644
--- a/code/modules/mob/living/carbon/human/human_movement.dm
+++ b/code/modules/mob/living/carbon/human/human_movement.dm
@@ -26,25 +26,8 @@
return 0
return ..()
-/mob/living/carbon/human/experience_pressure_difference(pressure_difference)
- if(pressure_difference > 100)
- playsound_local(null, 'sound/effects/space_wind_big.ogg', clamp(pressure_difference / 50, 10, 100), 1)
- else
- playsound_local(null, 'sound/effects/space_wind.ogg', clamp(pressure_difference, 10, 100), 1)
- if(shoes && istype(shoes, /obj/item/clothing))
- var/obj/item/clothing/S = shoes
- if((S.clothing_flags & NOSLIP))
- return 0
- return ..()
-
-/mob/living/carbon/human/mob_has_gravity()
- . = ..()
- if(!.)
- if(mob_negates_gravity())
- . = 1
-
/mob/living/carbon/human/mob_negates_gravity()
- return ((shoes && shoes.negates_gravity()) || (dna.species.negates_gravity(src)))
+ return dna.species.negates_gravity(src) || ..()
/mob/living/carbon/human/Move(NewLoc, direct)
. = ..()
diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm
index 756af00f183..cbaa988aa5a 100644
--- a/code/modules/mob/living/carbon/human/inventory.dm
+++ b/code/modules/mob/living/carbon/human/inventory.dm
@@ -40,6 +40,45 @@
return s_store
return null
+/mob/living/carbon/human/get_slot_by_item(obj/item/looking_for)
+ if(looking_for == belt)
+ return ITEM_SLOT_BELT
+
+ if(looking_for == wear_id)
+ return ITEM_SLOT_ID
+
+ if(looking_for == ears)
+ return ITEM_SLOT_EARS
+
+ if(looking_for == glasses)
+ return ITEM_SLOT_EYES
+
+ if(looking_for == gloves)
+ return ITEM_SLOT_GLOVES
+
+ if(looking_for == head)
+ return ITEM_SLOT_HEAD
+
+ if(looking_for == shoes)
+ return ITEM_SLOT_FEET
+
+ if(looking_for == wear_suit)
+ return ITEM_SLOT_OCLOTHING
+
+ if(looking_for == w_uniform)
+ return ITEM_SLOT_ICLOTHING
+
+ if(looking_for == r_store)
+ return ITEM_SLOT_RPOCKET
+
+ if(looking_for == l_store)
+ return ITEM_SLOT_LPOCKET
+
+ if(looking_for == s_store)
+ return ITEM_SLOT_SUITSTORE
+
+ return ..()
+
/mob/living/carbon/human/proc/get_all_slots()
. = get_head_slots() | get_body_slots()
@@ -334,7 +373,8 @@
if(equip_to_slot_if_possible(thing, slot_type))
update_inv_hands()
return
- if(!SEND_SIGNAL(equipped_item, COMSIG_CONTAINS_STORAGE)) // not a storage item
+ var/datum/component/storage/storage = equipped_item.GetComponent(/datum/component/storage)
+ if(!storage)
if(!thing)
equipped_item.attack_hand(src)
else
@@ -344,10 +384,11 @@
if(!SEND_SIGNAL(equipped_item, COMSIG_TRY_STORAGE_INSERT, thing, src))
to_chat(src, "You can't fit [thing] into your [equipped_item.name]!")
return
- if(!equipped_item.contents.len) // nothing to take out
+ var/atom/real_location = storage.real_location()
+ if(!real_location.contents.len) // nothing to take out
to_chat(src, "There's nothing in your [equipped_item.name] to take out!")
return
- var/obj/item/stored = equipped_item.contents[equipped_item.contents.len]
+ var/obj/item/stored = real_location.contents[real_location.contents.len]
if(!stored || stored.on_found(src))
return
stored.attack_hand(src) // take out thing from item in storage slot
diff --git a/code/modules/mob/living/carbon/human/species_types/podpeople.dm b/code/modules/mob/living/carbon/human/species_types/podpeople.dm
index daa645a662a..d5ce34ca412 100644
--- a/code/modules/mob/living/carbon/human/species_types/podpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/podpeople.dm
@@ -4,7 +4,10 @@
id = SPECIES_POD
default_color = "59CE00"
species_traits = list(MUTCOLORS,EYECOLOR)
- inherent_traits = list(TRAIT_ALWAYS_CLEAN)
+ inherent_traits = list(
+ TRAIT_ALWAYS_CLEAN,
+ TRAIT_PLANT_SAFE,
+ )
inherent_factions = list("plants", "vines")
fixed_mut_color = "59CE00"
attack_verb = "slash"
diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm
index 814a670f2fa..0f4c1826305 100644
--- a/code/modules/mob/living/carbon/human/update_icons.dm
+++ b/code/modules/mob/living/carbon/human/update_icons.dm
@@ -779,7 +779,7 @@ There are several things that need to be remembered:
handled_by_bodytype = TRUE
if(!icon_exists(icon_file, RESOLVE_ICON_STATE(I)))
- icon_file = DEFAULT_BACK_PATH
+ icon_file = I.mob_overlay_icon ? I.mob_overlay_icon : DEFAULT_BACK_PATH
handled_by_bodytype = TRUE
var/use_autogen = handled_by_bodytype ? dna.species : null
diff --git a/code/modules/mob/living/carbon/inventory.dm b/code/modules/mob/living/carbon/inventory.dm
index d5b97a942da..9525ebd6ec9 100644
--- a/code/modules/mob/living/carbon/inventory.dm
+++ b/code/modules/mob/living/carbon/inventory.dm
@@ -14,6 +14,30 @@
return legcuffed
return null
+/mob/living/carbon/get_slot_by_item(obj/item/looking_for)
+ if(looking_for == back)
+ return ITEM_SLOT_BACK
+
+ if(back && (looking_for in back))
+ return ITEM_SLOT_BACKPACK
+
+ if(looking_for == wear_mask)
+ return ITEM_SLOT_MASK
+
+ if(looking_for == wear_neck)
+ return ITEM_SLOT_NECK
+
+ if(looking_for == head)
+ return ITEM_SLOT_HEAD
+
+ if(looking_for == handcuffed)
+ return ITEM_SLOT_HANDCUFFED
+
+ if(looking_for == legcuffed)
+ return ITEM_SLOT_LEGCUFFED
+
+ return ..()
+
/mob/living/carbon/proc/equip_in_one_of_slots(obj/item/I, list/slots, qdel_on_fail = 1)
for(var/slot in slots)
if(equip_to_slot_if_possible(I, slots[slot], qdel_on_fail = 0, disable_warning = TRUE))
diff --git a/code/modules/mob/living/carbon/update_icons.dm b/code/modules/mob/living/carbon/update_icons.dm
index c80c9a821fd..34bd7dd8632 100644
--- a/code/modules/mob/living/carbon/update_icons.dm
+++ b/code/modules/mob/living/carbon/update_icons.dm
@@ -211,6 +211,9 @@
//eg: ammo counters, primed grenade flashing, etc.
//"icon_file" is used automatically for inhands etc. to make sure it gets the right inhand file
/obj/item/proc/worn_overlays(isinhands = FALSE, icon_file)
+ SHOULD_CALL_PARENT(TRUE)
+ RETURN_TYPE(/list)
+
. = list()
diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm
index a2502529449..c054b7d50bf 100644
--- a/code/modules/mob/living/life.dm
+++ b/code/modules/mob/living/life.dm
@@ -122,7 +122,7 @@
return
/mob/living/proc/handle_gravity()
- var/gravity = mob_has_gravity()
+ var/gravity = has_gravity()
update_gravity(gravity)
if(gravity > STANDARD_GRAVITY)
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 269c74a837b..1258df8b84c 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -97,6 +97,7 @@
if(m_intent == MOVE_INTENT_WALK)
return TRUE
+ SEND_SIGNAL(src, COMSIG_LIVING_MOB_BUMP, M)
//Even if we don't push/swap places, we "touched" them, so spread fire
spreadFire(M)
@@ -831,7 +832,7 @@
return pick("trails_1", "trails_2")
/mob/living/experience_pressure_difference(pressure_difference, direction, pressure_resistance_prob_delta = 0)
- if(buckled)
+ if(buckled || mob_negates_gravity())
return
if(client && client.move_delay >= world.time + world.tick_lag*2)
pressure_resistance_prob_delta -= 30
diff --git a/code/modules/mob/living/silicon/damage_procs.dm b/code/modules/mob/living/silicon/damage_procs.dm
index 80c643e0cee..9813ac88d43 100644
--- a/code/modules/mob/living/silicon/damage_procs.dm
+++ b/code/modules/mob/living/silicon/damage_procs.dm
@@ -1,5 +1,5 @@
-/mob/living/silicon/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, break_modifier = 1, sharpness = FALSE)
+/mob/living/silicon/apply_damage(damage = 0,damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage = FALSE, break_modifier = 1, sharpness = FALSE)
var/hit_percent = (100-blocked)/100
if((!damage || (!forced && hit_percent <= 0)))
return 0
diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm
index f3db764211d..1ad6d5d24ba 100644
--- a/code/modules/mob/living/silicon/robot/robot_modules.dm
+++ b/code/modules/mob/living/silicon/robot/robot_modules.dm
@@ -777,7 +777,7 @@
basic_modules = list(
/obj/item/assembly/flash/cyborg,
/obj/item/reagent_containers/glass/beaker/large, //I know a shaker is more appropiate but this is for ease of identification
- /obj/item/reagent_containers/food/condiment/enzyme,
+ /obj/item/reagent_containers/condiment/enzyme,
/obj/item/pen,
/obj/item/toy/crayon/spraycan/borg,
/obj/item/extinguisher/mini,
@@ -801,7 +801,7 @@
/obj/item/robot_module/butler/respawn_consumable(mob/living/silicon/robot/R, coeff = 1)
..()
- var/obj/item/reagent_containers/O = locate(/obj/item/reagent_containers/food/condiment/enzyme) in basic_modules
+ var/obj/item/reagent_containers/O = locate(/obj/item/reagent_containers/condiment/enzyme) in basic_modules
if(O)
O.reagents.add_reagent(/datum/reagent/consumable/enzyme, 2 * coeff)
diff --git a/code/modules/mob/living/simple_animal/friendly/cat.dm b/code/modules/mob/living/simple_animal/friendly/cat.dm
index 1916a7c52ad..646a3eb8436 100644
--- a/code/modules/mob/living/simple_animal/friendly/cat.dm
+++ b/code/modules/mob/living/simple_animal/friendly/cat.dm
@@ -270,7 +270,7 @@
maxHealth = 50
gender = FEMALE
harm_intent_damage = 10
- butcher_results = list(/obj/item/organ/brain = 1, /obj/item/organ/heart = 1, /obj/item/reagent_containers/food/snacks/cakeslice/birthday = 3, \
+ butcher_results = list(/obj/item/organ/brain = 1, /obj/item/organ/heart = 1, /obj/item/food/cakeslice/birthday = 3, \
/obj/item/reagent_containers/food/snacks/meat/slab = 2)
response_harm_continuous = "takes a bite out of"
response_harm_simple = "take a bite out of"
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 891a6eb15a7..6edaacc7493 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -313,6 +313,14 @@
/mob/proc/get_item_by_slot(slot_id)
return null
+/// Gets what slot the item on the mob is held in.
+/// Returns null if the item isn't in any slots on our mob.
+/// Does not check if the passed item is null, which may result in unexpected outcoms.
+/mob/proc/get_slot_by_item(obj/item/looking_for)
+ if(looking_for in held_items)
+ return ITEM_SLOT_HANDS
+
+ return null
///Is the mob incapacitated
/mob/proc/incapacitated(ignore_restraints = FALSE, ignore_grab = FALSE, check_immobilized = FALSE)
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index 42d217cf96e..bd1227d94d7 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -28,6 +28,45 @@
zone = BODY_ZONE_CHEST
return zone
+/// Returns a generic path of the object based on the slot
+/proc/get_path_by_slot(slot_id)
+ switch(slot_id)
+ if(ITEM_SLOT_BACK)
+ return /obj/item/storage/backpack
+ if(ITEM_SLOT_MASK)
+ return /obj/item/clothing/mask
+ if(ITEM_SLOT_NECK)
+ return /obj/item/clothing/neck
+ if(ITEM_SLOT_HANDCUFFED)
+ return /obj/item/restraints/handcuffs
+ if(ITEM_SLOT_LEGCUFFED)
+ return /obj/item/restraints/legcuffs
+ if(ITEM_SLOT_BELT)
+ return /obj/item/storage/belt
+ if(ITEM_SLOT_ID)
+ return /obj/item/card/id
+ if(ITEM_SLOT_EARS)
+ return /obj/item/clothing/ears
+ if(ITEM_SLOT_EYES)
+ return /obj/item/clothing/glasses
+ if(ITEM_SLOT_GLOVES)
+ return /obj/item/clothing/gloves
+ if(ITEM_SLOT_HEAD)
+ return /obj/item/clothing/head
+ if(ITEM_SLOT_FEET)
+ return /obj/item/clothing/shoes
+ if(ITEM_SLOT_OCLOTHING)
+ return /obj/item/clothing/suit
+ if(ITEM_SLOT_ICLOTHING)
+ return /obj/item/clothing/under
+ if(ITEM_SLOT_LPOCKET)
+ return /obj/item
+ if(ITEM_SLOT_RPOCKET)
+ return /obj/item
+ if(ITEM_SLOT_SUITSTORE)
+ return /obj/item
+ return null
+
/**
* Return the zone or randomly, another valid zone
*
diff --git a/code/modules/mod/mod_actions.dm b/code/modules/mod/mod_actions.dm
new file mode 100644
index 00000000000..1df1f7b8894
--- /dev/null
+++ b/code/modules/mod/mod_actions.dm
@@ -0,0 +1,193 @@
+/datum/action/item_action/mod
+ background_icon_state = "bg_tech_blue"
+ icon_icon = 'icons/mob/actions/actions_mod.dmi'
+ check_flags = AB_CHECK_CONSCIOUS
+ /// Whether this action is intended for the AI. Stuff breaks a lot if this is done differently.
+ var/ai_action = FALSE
+
+/datum/action/item_action/mod/New(Target)
+ ..()
+ if(!istype(Target, /obj/item/mod/control))
+ qdel(src)
+ return
+ if(ai_action)
+ background_icon_state = ACTION_BUTTON_DEFAULT_BACKGROUND
+
+/datum/action/item_action/mod/Grant(mob/user)
+ var/obj/item/mod/control/mod = target
+ if(ai_action && user != mod.ai)
+ return
+ else if(!ai_action && user == mod.ai)
+ return
+ return ..()
+
+/datum/action/item_action/mod/Remove(mob/user)
+ var/obj/item/mod/control/mod = target
+ if(ai_action && user != mod.ai)
+ return
+ else if(!ai_action && user == mod.ai)
+ return
+ return ..()
+
+/datum/action/item_action/mod/Trigger(trigger_flags)
+ if(!IsAvailable())
+ return FALSE
+ var/obj/item/mod/control/mod = target
+ if(mod.malfunctioning && prob(75))
+ mod.balloon_alert(usr, "button malfunctions!")
+ return FALSE
+ return TRUE
+
+/datum/action/item_action/mod/deploy
+ name = "Deploy MODsuit"
+ desc = "LMB: Deploy/Undeploy part. Alt Click: Deploy/Undeploy full suit."
+ button_icon_state = "deploy"
+
+/datum/action/item_action/mod/deploy/Trigger(trigger_flags)
+ . = ..()
+ if(!.)
+ return
+ var/obj/item/mod/control/mod = target
+ if(trigger_flags & TRIGGER_SECONDARY_ACTION)
+ mod.quick_deploy(usr)
+ else
+ mod.choose_deploy(usr)
+
+/datum/action/item_action/mod/deploy/ai
+ ai_action = TRUE
+
+/datum/action/item_action/mod/activate
+ name = "Activate MODsuit"
+ desc = "LMB: Activate/Deactivate suit with prompt. Alt Click: Activate/Deactivate suit skipping prompt."
+ button_icon_state = "activate"
+ /// First time clicking this will set it to TRUE, second time will activate it.
+ var/ready = FALSE
+
+/datum/action/item_action/mod/activate/Trigger(trigger_flags)
+ . = ..()
+ if(!.)
+ return
+ if(!(trigger_flags & TRIGGER_SECONDARY_ACTION) && !ready)
+ ready = TRUE
+ button_icon_state = "activate-ready"
+ if(!ai_action)
+ background_icon_state = "bg_tech"
+ UpdateButtonIcon()
+ addtimer(CALLBACK(src, PROC_REF(reset_ready)), 3 SECONDS)
+ return
+ var/obj/item/mod/control/mod = target
+ reset_ready()
+ mod.toggle_activate(usr)
+
+/// Resets the state requiring to be doubleclicked again.
+/datum/action/item_action/mod/activate/proc/reset_ready()
+ ready = FALSE
+ button_icon_state = initial(button_icon_state)
+ if(!ai_action)
+ background_icon_state = initial(background_icon_state)
+ UpdateButtonIcon()
+
+/datum/action/item_action/mod/activate/ai
+ ai_action = TRUE
+
+/datum/action/item_action/mod/module
+ name = "Toggle Module"
+ desc = "Toggle a MODsuit module."
+ button_icon_state = "module"
+
+/datum/action/item_action/mod/module/Trigger(trigger_flags)
+ . = ..()
+ if(!.)
+ return
+ var/obj/item/mod/control/mod = target
+ mod.quick_module(usr)
+
+/datum/action/item_action/mod/module/ai
+ ai_action = TRUE
+
+/datum/action/item_action/mod/panel
+ name = "MODsuit Panel"
+ desc = "Open the MODsuit's panel."
+ button_icon_state = "panel"
+
+/datum/action/item_action/mod/panel/Trigger(trigger_flags)
+ . = ..()
+ if(!.)
+ return
+ var/obj/item/mod/control/mod = target
+ mod.ui_interact(usr)
+
+/datum/action/item_action/mod/panel/ai
+ ai_action = TRUE
+
+/datum/action/item_action/mod/pinned_module
+ desc = "Activate the module."
+ /// Overrides the icon applications.
+ var/override = FALSE
+ /// Module we are linked to.
+ var/obj/item/mod/module/module
+ /// A ref to the mob we are pinned to.
+ var/pinner_ref
+
+/datum/action/item_action/mod/pinned_module/New(Target, obj/item/mod/module/linked_module, mob/user)
+ if(isAI(user))
+ ai_action = TRUE
+ ..()
+ module = linked_module
+ name = "Activate [capitalize(linked_module.name)]"
+ desc = "Quickly activate [linked_module]."
+ icon_icon = linked_module.icon
+ button_icon_state = linked_module.icon_state
+ RegisterSignal(linked_module, COMSIG_MODULE_ACTIVATED, PROC_REF(on_module_activate))
+ RegisterSignal(linked_module, COMSIG_MODULE_DEACTIVATED, PROC_REF(on_module_deactivate))
+ RegisterSignal(linked_module, COMSIG_MODULE_USED, PROC_REF(on_module_use))
+
+/datum/action/item_action/mod/pinned_module/Destroy()
+ module.pinned_to -= pinner_ref
+ module = null
+ return ..()
+
+/datum/action/item_action/mod/pinned_module/Grant(mob/user)
+ var/user_ref = REF(user)
+ if(!pinner_ref)
+ pinner_ref = user_ref
+ module.pinned_to[pinner_ref] = src
+ else if(pinner_ref != user_ref)
+ return
+ return ..()
+
+/datum/action/item_action/mod/pinned_module/Trigger(trigger_flags)
+ . = ..()
+ if(!.)
+ return
+ module.on_select()
+
+/datum/action/item_action/mod/pinned_module/ApplyIcon(atom/movable/screen/movable/action_button/current_button, force)
+ . = ..(current_button, force = TRUE)
+ if(override)
+ return
+ var/obj/item/mod/control/mod = target
+ if(module == mod.selected_module)
+ current_button.add_overlay(image(icon = 'icons/hud/radial.dmi', icon_state = "module_selected", layer = FLOAT_LAYER-0.1))
+ else if(module.active)
+ current_button.add_overlay(image(icon = 'icons/hud/radial.dmi', icon_state = "module_active", layer = FLOAT_LAYER-0.1))
+ if(!COOLDOWN_FINISHED(module, cooldown_timer))
+ var/image/cooldown_image = image(icon = 'icons/hud/radial.dmi', icon_state = "module_cooldown")
+ current_button.add_overlay(cooldown_image)
+ addtimer(CALLBACK(current_button, TYPE_PROC_REF(/image, cut_overlay), cooldown_image), COOLDOWN_TIMELEFT(module, cooldown_timer))
+
+
+/datum/action/item_action/mod/pinned_module/proc/on_module_activate(datum/source)
+ SIGNAL_HANDLER
+
+ UpdateButtonIcon()
+
+/datum/action/item_action/mod/pinned_module/proc/on_module_deactivate(datum/source)
+ SIGNAL_HANDLER
+
+ UpdateButtonIcon()
+
+/datum/action/item_action/mod/pinned_module/proc/on_module_use(datum/source)
+ SIGNAL_HANDLER
+
+ UpdateButtonIcon()
diff --git a/code/modules/mod/mod_activation.dm b/code/modules/mod/mod_activation.dm
new file mode 100644
index 00000000000..cb61728f2cb
--- /dev/null
+++ b/code/modules/mod/mod_activation.dm
@@ -0,0 +1,244 @@
+#define MOD_ACTIVATION_STEP_FLAGS IGNORE_USER_LOC_CHANGE|IGNORE_TARGET_LOC_CHANGE|IGNORE_HELD_ITEM|IGNORE_INCAPACITATED
+
+/// Creates a radial menu from which the user chooses parts of the suit to deploy/retract. Repeats until all parts are extended or retracted.
+/obj/item/mod/control/proc/choose_deploy(mob/user)
+ if(!length(mod_parts))
+ return
+ var/list/display_names = list()
+ var/list/items = list()
+ for(var/obj/item/part as anything in mod_parts)
+ display_names[part.name] = REF(part)
+ var/image/part_image = image(icon = part.icon, icon_state = part.icon_state)
+ if(part.loc != src)
+ part_image.underlays += image(icon = 'icons/hud/radial.dmi', icon_state = "module_active")
+ items += list(part.name = part_image)
+ var/pick = show_radial_menu(user, src, items, custom_check = FALSE, require_near = TRUE, tooltips = TRUE)
+ if(!pick)
+ return
+ var/part_reference = display_names[pick]
+ var/obj/item/part = locate(part_reference) in mod_parts
+ if(!istype(part) || user.incapacitated())
+ return
+ if((active && part != helmet) || activating)
+ balloon_alert(user, "deactivate the suit first!")
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
+ return
+ var/parts_to_check = mod_parts - part
+ if(part.loc == src)
+ deploy(user, part)
+ for(var/obj/item/checking_part as anything in parts_to_check)
+ if(checking_part.loc != src)
+ continue
+ choose_deploy(user)
+ break
+ else
+ retract(user, part)
+ for(var/obj/item/checking_part as anything in parts_to_check)
+ if(checking_part.loc == src)
+ continue
+ choose_deploy(user)
+ break
+
+/// Quickly deploys all parts (or retracts if all are on the wearer)
+/obj/item/mod/control/proc/quick_deploy(mob/user)
+ if(active || activating)
+ balloon_alert(user, "deactivate the suit first!")
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
+ return FALSE
+ var/deploy = FALSE
+ for(var/obj/item/part as anything in mod_parts)
+ if(part.loc != src)
+ continue
+ deploy = TRUE
+ for(var/obj/item/part as anything in mod_parts)
+ if(deploy && part.loc == src)
+ deploy(null, part)
+ else if(!deploy && part.loc != src)
+ retract(null, part)
+ wearer.visible_message(span_notice("[wearer]'s [src] [deploy ? "deploys" : "retracts"] its' parts with a mechanical hiss."),
+ span_notice("[src] [deploy ? "deploys" : "retracts"] its' parts with a mechanical hiss."),
+ span_hear("You hear a mechanical hiss."))
+ playsound(src, 'sound/mecha/mechmove03.ogg', 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
+ return TRUE
+
+/// Deploys a part of the suit onto the user.
+/obj/item/mod/control/proc/deploy(mob/user, obj/item/part)
+ if(part.loc != src)
+ if(!user)
+ return FALSE
+ balloon_alert(user, "[part.name] already deployed!")
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
+ if(part in overslotting_parts)
+ var/obj/item/overslot = wearer.get_item_by_slot(part.slot_flags)
+ if(overslot)
+ overslotting_parts[part] = overslot
+ wearer.transferItemToLoc(overslot, part, force = TRUE)
+ RegisterSignal(part, COMSIG_ATOM_EXITED, PROC_REF(on_overslot_exit))
+ if(wearer.equip_to_slot_if_possible(part, part.slot_flags, qdel_on_fail = FALSE, disable_warning = TRUE))
+ ADD_TRAIT(part, TRAIT_NODROP, MOD_TRAIT)
+ if(!user)
+ return TRUE
+ wearer.visible_message(span_notice("[wearer]'s [part.name] deploy[part.p_s()] with a mechanical hiss."),
+ span_notice("[part] deploy[part.p_s()] with a mechanical hiss."),
+ span_hear("You hear a mechanical hiss."))
+ playsound(src, 'sound/mecha/mechmove03.ogg', 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
+ return TRUE
+ else
+ if(!user)
+ return FALSE
+ balloon_alert(user, "bodypart clothed!")
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
+ return FALSE
+
+/// Retract a part of the suit from the user.
+/obj/item/mod/control/proc/retract(mob/user, obj/item/part)
+ if(part.loc == src)
+ if(!user)
+ return FALSE
+ balloon_alert(user, "[part.name] already retracted!")
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
+ REMOVE_TRAIT(part, TRAIT_NODROP, MOD_TRAIT)
+ wearer.transferItemToLoc(part, src, force = TRUE)
+ if(overslotting_parts[part])
+ UnregisterSignal(part, COMSIG_ATOM_EXITED)
+ var/obj/item/overslot = overslotting_parts[part]
+ if(!wearer.equip_to_slot_if_possible(overslot, overslot.slot_flags, qdel_on_fail = FALSE, disable_warning = TRUE))
+ wearer.dropItemToGround(overslot, force = TRUE, silent = TRUE)
+ overslotting_parts[part] = null
+ if(!user)
+ return
+ wearer.visible_message(span_notice("[wearer]'s [part.name] retract[part.p_s()] back into [src] with a mechanical hiss."),
+ span_notice("[part] retract[part.p_s()] back into [src] with a mechanical hiss."),
+ span_hear("You hear a mechanical hiss."))
+ playsound(src, 'sound/mecha/mechmove03.ogg', 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
+
+/// Starts the activation sequence, where parts of the suit activate one by one until the whole suit is on
+/obj/item/mod/control/proc/toggle_activate(mob/user, force_deactivate = FALSE)
+ if(!wearer)
+ if(!force_deactivate)
+ balloon_alert(user, "put suit on back!")
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
+ return FALSE
+ if(!force_deactivate && (SEND_SIGNAL(src, COMSIG_MOD_ACTIVATE, user) & MOD_CANCEL_ACTIVATE))
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
+ return FALSE
+ for(var/obj/item/part as anything in mod_parts)
+ if(!force_deactivate && part.loc == src)
+ balloon_alert(user, "deploy all parts first!")
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
+ return FALSE
+ if(locked && !active && !allowed(user) && !force_deactivate)
+ balloon_alert(user, "access insufficient!")
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
+ return FALSE
+ if(!get_charge() && !force_deactivate)
+ balloon_alert(user, "suit not powered!")
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
+ return FALSE
+ if(open && !force_deactivate)
+ balloon_alert(user, "close the suit panel!")
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
+ return FALSE
+ if(activating)
+ if(!force_deactivate)
+ balloon_alert(user, "suit already [active ? "shutting down" : "starting up"]!")
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
+ return FALSE
+ for(var/obj/item/mod/module/module as anything in modules)
+ if(!module.active || module.allowed_inactive)
+ continue
+ module.on_deactivation(display_message = FALSE)
+ activating = TRUE
+ to_chat(wearer, span_notice("MODsuit [active ? "shutting down" : "starting up"]."))
+ if(do_after(wearer, activation_step_time, wearer, MOD_ACTIVATION_STEP_FLAGS, extra_checks = CALLBACK(src, PROC_REF(has_wearer)), hidden = TRUE))
+ to_chat(wearer, span_notice("[boots] [active ? "relax their grip on your legs" : "seal around your feet"]."))
+ playsound(src, 'sound/mecha/mechmove03.ogg', 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
+ seal_part(boots, seal = !active)
+ if(do_after(wearer, activation_step_time, wearer, MOD_ACTIVATION_STEP_FLAGS, extra_checks = CALLBACK(src, PROC_REF(has_wearer)), hidden = TRUE))
+ to_chat(wearer, span_notice("[gauntlets] [active ? "become loose around your fingers" : "tighten around your fingers and wrists"]."))
+ playsound(src, 'sound/mecha/mechmove03.ogg', 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
+ seal_part(gauntlets, seal = !active)
+ if(do_after(wearer, activation_step_time, wearer, MOD_ACTIVATION_STEP_FLAGS, extra_checks = CALLBACK(src, PROC_REF(has_wearer)), hidden = TRUE))
+ to_chat(wearer, span_notice("[chestplate] [active ? "releases your chest" : "cinches tightly against your chest"]."))
+ playsound(src, 'sound/mecha/mechmove03.ogg', 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
+ seal_part(chestplate, seal = !active)
+ if(do_after(wearer, activation_step_time, wearer, MOD_ACTIVATION_STEP_FLAGS, extra_checks = CALLBACK(src, PROC_REF(has_wearer)), hidden = TRUE))
+ to_chat(wearer, span_notice("[helmet] hisses [active ? "open" : "closed"]."))
+ playsound(src, 'sound/mecha/mechmove03.ogg', 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
+ seal_part(helmet, seal = !active)
+ if(do_after(wearer, activation_step_time, wearer, MOD_ACTIVATION_STEP_FLAGS, extra_checks = CALLBACK(src, PROC_REF(has_wearer)), hidden = TRUE))
+ to_chat(wearer, span_notice("Systems [active ? "shut down. Parts unsealed. Goodbye" : "started up. Parts sealed. Welcome"], [wearer]."))
+ if(ai)
+ to_chat(ai, span_notice("SYSTEMS [active ? "DEACTIVATED. GOODBYE" : "ACTIVATED. WELCOME"]: \"[ai]\""))
+ finish_activation(on = !active)
+ if(active)
+ playsound(src, 'sound/machines/synth_yes.ogg', 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE, frequency = 6000)
+ if(!malfunctioning)
+ wearer.playsound_local(get_turf(src), 'sound/mecha/nominal.ogg', 50)
+ else
+ playsound(src, 'sound/machines/synth_no.ogg', 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE, frequency = 6000)
+ activating = FALSE
+ return TRUE
+
+///Seals or unseals the given part
+/obj/item/mod/control/proc/seal_part(obj/item/clothing/part, seal)
+ if(seal)
+ part.clothing_flags |= part.visor_flags
+ part.flags_inv |= part.visor_flags_inv
+ part.flags_cover |= part.visor_flags_cover
+ part.heat_protection = initial(part.heat_protection)
+ part.cold_protection = initial(part.cold_protection)
+ part.alternate_worn_layer = null
+ else
+ part.flags_cover &= ~part.visor_flags_cover
+ part.flags_inv &= ~part.visor_flags_inv
+ part.clothing_flags &= ~part.visor_flags
+ part.heat_protection = NONE
+ part.cold_protection = NONE
+ part.alternate_worn_layer = mod_parts[part]
+ if(part == boots)
+ boots.icon_state = "[skin]-boots[seal ? "-sealed" : ""]"
+ wearer.update_inv_shoes()
+ if(part == gauntlets)
+ gauntlets.icon_state = "[skin]-gauntlets[seal ? "-sealed" : ""]"
+ wearer.update_inv_gloves()
+ if(part == chestplate)
+ chestplate.icon_state = "[skin]-chestplate[seal ? "-sealed" : ""]"
+ wearer.update_inv_wear_suit()
+ wearer.update_inv_w_uniform()
+ if(part == helmet)
+ helmet.icon_state = "[skin]-helmet[seal ? "-sealed" : ""]"
+ wearer.update_inv_head()
+ wearer.update_inv_wear_mask()
+ wearer.update_inv_glasses()
+ wearer.update_hair()
+
+/// Finishes the suit's activation, starts processing
+/obj/item/mod/control/proc/finish_activation(on)
+ active = on
+ if(active)
+ for(var/obj/item/mod/module/module as anything in modules)
+ module.on_suit_activation()
+ START_PROCESSING(SSobj, src)
+ else
+ for(var/obj/item/mod/module/module as anything in modules)
+ module.on_suit_deactivation()
+ STOP_PROCESSING(SSobj, src)
+ update_speed()
+ update_icon_state()
+ wearer.update_inv_back(slot_flags)
+
+/// Quickly deploys all the suit parts and if successful, seals them and turns on the suit. Intended mostly for outfits.
+/obj/item/mod/control/proc/quick_activation()
+ var/seal = TRUE
+ for(var/obj/item/part as anything in mod_parts)
+ if(!deploy(null, part))
+ seal = FALSE
+ if(!seal)
+ return
+ for(var/obj/item/part as anything in mod_parts)
+ seal_part(part, seal = TRUE)
+ finish_activation(on = TRUE)
+
+/obj/item/mod/control/proc/has_wearer()
+ return wearer
diff --git a/code/modules/mod/mod_ai.dm b/code/modules/mod/mod_ai.dm
new file mode 100644
index 00000000000..a0571797034
--- /dev/null
+++ b/code/modules/mod/mod_ai.dm
@@ -0,0 +1,125 @@
+/**
+ * Simple proc to insert the pAI into the MODsuit.
+ *
+ * user - The person trying to put the pAI into the MODsuit.
+ * card - The pAI card we're slotting in the MODsuit.
+ */
+
+/obj/item/mod/control/proc/insert_pai(mob/user, obj/item/paicard/card)
+ if(ai)
+ balloon_alert(user, "ai already installed!")
+ return
+ if(!card.pai || !card.pai.mind)
+ balloon_alert(user, "pai unresponsive!")
+ return
+ balloon_alert(user, "transferring to suit...")
+ if(!do_after(user, 5 SECONDS, target = src))
+ balloon_alert(user, "interrupted!")
+ return FALSE
+ if(!user.transferItemToLoc(card, src))
+ return
+
+ card.pai.canholo = FALSE
+ ai = card.pai
+ balloon_alert(user, "pAI transferred to suit")
+ balloon_alert(ai, "transferred to a suit")
+ ai.remote_control = src
+ for(var/datum/action/action as anything in actions)
+ action.Grant(ai)
+ return TRUE
+
+/**
+ * Simple proc to extract the pAI from the MODsuit. It's the proc to call if you want to take it out,
+ * remove_pai() is there so atom_destruction() doesn't have any risk of sleeping.
+ *
+ * user - The person trying to take out the pAI from the MODsuit.
+ * forced - Whether or not we skip the checks and just eject the pAI. Defaults to FALSE.
+ * feedback - Whether to give feedback via balloon alerts or not. Defaults to TRUE.
+ */
+/obj/item/mod/control/proc/extract_pai(mob/user, forced = FALSE, feedback = TRUE)
+ if(!ai)
+ if(user && feedback)
+ balloon_alert(user, "no pAI to remove!")
+ return
+ if(!ispAI(ai))
+ if(user && feedback)
+ balloon_alert(user, "onboard AI cannot fit in this card!")
+ return
+ if(!forced)
+ if(!open)
+ if(user && feedback)
+ balloon_alert(user, "open the suit panel!")
+ return FALSE
+ if(!do_after(user, 5 SECONDS, target = src))
+ if(user && feedback)
+ balloon_alert(user, "interrupted!")
+ return FALSE
+
+ remove_pai(feedback)
+
+ if(feedback && user)
+ balloon_alert(user, "pAI removed from the suit")
+
+/**
+ * Simple proc that handles the safe removal of the pAI from a MOD control unit.
+ *
+ * Arguments:
+ * * feedback - Whether or not we want to give balloon alert feedback to the ai. Defaults to FALSE.
+ */
+/obj/item/mod/control/proc/remove_pai(feedback = FALSE)
+ if(!ispAI(ai))
+ return
+ var/mob/living/silicon/pai/pai = ai
+ var/turf/drop_off = get_turf(src)
+ if(drop_off) // In case there's no drop_off, the pAI will simply get deleted.
+ pai.card.forceMove(drop_off)
+
+ for(var/datum/action/action as anything in actions)
+ if(action.owner == pai)
+ action.Remove(pai)
+
+ if(feedback)
+ balloon_alert(pai, "removed from a suit")
+ pai.remote_control = null
+ pai.canholo = TRUE
+ pai = null
+
+#define MOVE_DELAY 2
+#define WEARER_DELAY 1
+#define LONE_DELAY 5
+#define CELL_PER_STEP (DEFAULT_CHARGE_DRAIN * 2.5)
+#define AI_FALL_TIME (1 SECONDS)
+
+/*obj/item/mod/control/relaymove(mob/user, direction)
+ var/cell = get_cell()
+ if((!active && wearer) || !cell || cell.charge < CELL_PER_STEP || user != ai || !COOLDOWN_FINISHED(src, cooldown_mod_move) || (wearer?.pulledby?.grab_state > GRAB_PASSIVE))
+ return FALSE
+ var/timemodifier = MOVE_DELAY * (ISDIAGONALDIR(direction) ? SQRT_2 : 1) * (wearer ? WEARER_DELAY : LONE_DELAY)
+ if(wearer && !wearer.Process_Spacemove(direction))
+ return FALSE
+ else if(!wearer && (!has_gravity() || !isturf(loc)))
+ return FALSE
+ COOLDOWN_START(src, cooldown_mod_move, movedelay * timemodifier + slowdown)
+ cell.charge = max(0, cell.charge - CELL_PER_STEP)
+ playsound(src, 'sound/mecha/mechmove01.ogg', 25, TRUE)
+ if(ismovable(wearer?.loc))
+ return wearer.loc.relaymove(wearer, direction)
+ else if(wearer)
+
+ var/atom/movable/mover = wearer || src
+ return step(mover, direction)
+
+#undef MOVE_DELAY
+#undef WEARER_DELAY
+#undef LONE_DELAY
+#undef CELL_PER_STEP
+#undef AI_FALL_TIME
+
+ return
+ REMOVE_TRAIT(wearer, TRAIT_MOBILITY_NOREST, MOD_TRAIT)
+
+/obj/item/mod/control/ui_state(mob/user)
+ if(user == ai)
+ return GLOB.contained_state
+ return ..()
+*/
diff --git a/code/modules/mod/mod_clothes.dm b/code/modules/mod/mod_clothes.dm
new file mode 100644
index 00000000000..7a9e710c938
--- /dev/null
+++ b/code/modules/mod/mod_clothes.dm
@@ -0,0 +1,56 @@
+/obj/item/clothing/head/mod
+ name = "MOD helmet"
+ desc = "A helmet for a MODsuit."
+ icon = 'icons/obj/clothing/modsuit/mod_clothing.dmi'
+ icon_state = "standard-helmet"
+ base_icon_state = "helmet"
+ mob_overlay_icon = 'icons/mob/clothing/modsuit/mod_clothing.dmi'
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
+ body_parts_covered = HEAD
+ heat_protection = HEAD
+ cold_protection = HEAD
+ obj_flags = IMMUTABLE_SLOW
+ visor_flags = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|ALLOWINTERNALS
+
+/obj/item/clothing/suit/mod
+ name = "MOD chestplate"
+ desc = "A chestplate for a MODsuit."
+ icon = 'icons/obj/clothing/modsuit/mod_clothing.dmi'
+ icon_state = "standard-chestplate"
+ base_icon_state = "chestplate"
+ mob_overlay_icon = 'icons/mob/clothing/modsuit/mod_clothing.dmi'
+ blood_overlay_type = "armor"
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
+ body_parts_covered = CHEST|GROIN
+ heat_protection = CHEST|GROIN
+ cold_protection = CHEST|GROIN
+ obj_flags = IMMUTABLE_SLOW
+
+/obj/item/clothing/gloves/mod
+ name = "MOD gauntlets"
+ desc = "A pair of gauntlets for a MODsuit."
+ icon = 'icons/obj/clothing/modsuit/mod_clothing.dmi'
+ icon_state = "standard-gauntlets"
+ base_icon_state = "gauntlets"
+ mob_overlay_icon = 'icons/mob/clothing/modsuit/mod_clothing.dmi'
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
+ body_parts_covered = HANDS|ARMS
+ heat_protection = HANDS|ARMS
+ cold_protection = HANDS|ARMS
+ obj_flags = IMMUTABLE_SLOW
+
+/obj/item/clothing/shoes/mod
+ name = "MOD boots"
+ desc = "A pair of boots for a MODsuit."
+ icon = 'icons/obj/clothing/modsuit/mod_clothing.dmi'
+ icon_state = "standard-boots"
+ base_icon_state = "boots"
+ mob_overlay_icon = 'icons/mob/clothing/modsuit/mod_clothing.dmi'
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
+ body_parts_covered = FEET|LEGS
+ heat_protection = FEET|LEGS
+ cold_protection = FEET|LEGS
+ obj_flags = IMMUTABLE_SLOW
+ supports_variations = DIGITIGRADE_VARIATION
+ can_be_tied = FALSE
+ visor_flags_inv = HIDESHOES
diff --git a/code/modules/mod/mod_construction.dm b/code/modules/mod/mod_construction.dm
new file mode 100644
index 00000000000..0f37a4fd1f1
--- /dev/null
+++ b/code/modules/mod/mod_construction.dm
@@ -0,0 +1,275 @@
+/obj/item/mod/construction
+ desc = "A part used in MOD construction."
+ icon = 'icons/obj/clothing/modsuit/mod_construction.dmi'
+ item_state = "rack_parts"
+
+/obj/item/mod/construction/helmet
+ name = "MOD helmet"
+ icon_state = "helmet"
+
+/obj/item/mod/construction/helmet/examine(mob/user)
+ . = ..()
+ . += span_notice("You could insert these into a MOD shell...")
+
+/obj/item/mod/construction/chestplate
+ name = "MOD chestplate"
+ icon_state = "chestplate"
+
+/obj/item/mod/construction/chestplate/examine(mob/user)
+ . = ..()
+ . += span_notice("You could insert these into a MOD shell...")
+
+/obj/item/mod/construction/gauntlets
+ name = "MOD gauntlets"
+ icon_state = "gauntlets"
+
+/obj/item/mod/construction/gauntlets/examine(mob/user)
+ . = ..()
+ . += span_notice("You could insert these into a MOD shell...")
+
+/obj/item/mod/construction/boots
+ name = "MOD boots"
+ icon_state = "boots"
+
+/obj/item/mod/construction/boots/examine(mob/user)
+ . = ..()
+ . += span_notice("You could insert these into a MOD shell...")
+
+/obj/item/mod/construction/broken_core
+ name = "broken MOD core"
+ icon_state = "mod-core"
+ desc = "An internal power source for a Modular Outerwear Device. You don't seem to be able to source any power from this one, though."
+
+/obj/item/mod/construction/broken_core/examine(mob/user)
+ . = ..()
+ . += span_notice("You could repair it with a screwdriver...")
+
+/obj/item/mod/construction/broken_core/screwdriver_act(mob/living/user, obj/item/tool)
+ . = ..()
+ if(!tool.use_tool(src, user, 5 SECONDS, volume = 30))
+ return
+ new /obj/item/mod/core/standard(drop_location())
+ qdel(src)
+
+/obj/item/mod/construction/plating
+ name = "MOD external plating"
+ desc = "External plating used to finish a MOD control unit."
+ icon_state = "standard-plating"
+ var/datum/mod_theme/theme = /datum/mod_theme
+
+/obj/item/mod/construction/plating/Initialize(mapload)
+ . = ..()
+ var/datum/mod_theme/used_theme = GLOB.mod_themes[theme]
+ name = "MOD [used_theme.name] external plating"
+ desc = "[desc] [used_theme.desc]"
+ icon_state = "[used_theme.default_skin]-plating"
+
+/obj/item/mod/construction/plating/engineering
+ theme = /datum/mod_theme/engineering
+
+/obj/item/mod/construction/plating/atmospheric
+ theme = /datum/mod_theme/atmospheric
+
+/obj/item/mod/construction/plating/medical
+ theme = /datum/mod_theme/medical
+
+/obj/item/mod/construction/plating/security
+ theme = /datum/mod_theme/security
+
+#define START_STEP "start"
+#define CORE_STEP "core"
+#define SCREWED_CORE_STEP "screwed_core"
+#define HELMET_STEP "helmet"
+#define CHESTPLATE_STEP "chestplate"
+#define GAUNTLETS_STEP "gauntlets"
+#define BOOTS_STEP "boots"
+#define WRENCHED_ASSEMBLY_STEP "wrenched_assembly"
+#define SCREWED_ASSEMBLY_STEP "screwed_assembly"
+
+/obj/item/mod/construction/shell
+ name = "MOD shell"
+ icon_state = "mod-construction_start"
+ desc = "A MOD shell."
+ var/obj/item/core
+ var/obj/item/helmet
+ var/obj/item/chestplate
+ var/obj/item/gauntlets
+ var/obj/item/boots
+ var/step = START_STEP
+
+/obj/item/mod/construction/shell/examine(mob/user)
+ . = ..()
+ var/display_text
+ switch(step)
+ if(START_STEP)
+ display_text = "It looks like it's missing a MOD core..."
+ if(CORE_STEP)
+ display_text = "The core seems loose..."
+ if(SCREWED_CORE_STEP)
+ display_text = "It looks like it's missing a helmet..."
+ if(HELMET_STEP)
+ display_text = "It looks like it's missing a chestplate..."
+ if(CHESTPLATE_STEP)
+ display_text = "It looks like it's missing gauntlets..."
+ if(GAUNTLETS_STEP)
+ display_text = "It looks like it's missing boots..."
+ if(BOOTS_STEP)
+ display_text = "The assembly seems unsecured..."
+ if(WRENCHED_ASSEMBLY_STEP)
+ display_text = "The assembly seems loose..."
+ if(SCREWED_ASSEMBLY_STEP)
+ display_text = "All it's missing is external plating..."
+ . += span_notice(display_text)
+
+/obj/item/mod/construction/shell/attackby(obj/item/part, mob/user, params)
+ . = ..()
+ switch(step)
+ if(START_STEP)
+ if(!istype(part, /obj/item/mod/core))
+ return
+ if(!user.transferItemToLoc(part, src))
+ balloon_alert(user, "core stuck to your hand!")
+ return
+ playsound(src, 'sound/machines/click.ogg', 30, TRUE)
+ balloon_alert(user, "core inserted")
+ core = part
+ step = CORE_STEP
+ if(CORE_STEP)
+ if(part.tool_behaviour == TOOL_SCREWDRIVER) //Construct
+ if(part.use_tool(src, user, 0, volume=30))
+ balloon_alert(user, "core screwed")
+ step = SCREWED_CORE_STEP
+ else if(part.tool_behaviour == TOOL_CROWBAR) //Deconstruct
+ if(part.use_tool(src, user, 0, volume=30))
+ core.forceMove(drop_location())
+ balloon_alert(user, "core taken out")
+ step = START_STEP
+ if(SCREWED_CORE_STEP)
+ if(istype(part, /obj/item/mod/construction/helmet)) //Construct
+ if(!user.transferItemToLoc(part, src))
+ balloon_alert(user, "helmet stuck to your hand!")
+ return
+ playsound(src, 'sound/machines/click.ogg', 30, TRUE)
+ balloon_alert(user, "helmet added")
+ helmet = part
+ step = HELMET_STEP
+ else if(part.tool_behaviour == TOOL_SCREWDRIVER) //Deconstruct
+ if(part.use_tool(src, user, 0, volume=30))
+ balloon_alert(user, "core unscrewed")
+ step = CORE_STEP
+ if(HELMET_STEP)
+ if(istype(part, /obj/item/mod/construction/chestplate)) //Construct
+ if(!user.transferItemToLoc(part, src))
+ balloon_alert(user, "chestplate stuck to your hand!")
+ return
+ playsound(src, 'sound/machines/click.ogg', 30, TRUE)
+ balloon_alert(user, "chestplate added")
+ chestplate = part
+ step = CHESTPLATE_STEP
+ else if(part.tool_behaviour == TOOL_CROWBAR) //Deconstruct
+ if(part.use_tool(src, user, 0, volume=30))
+ helmet.forceMove(drop_location())
+ balloon_alert(user, "helmet removed")
+ helmet = null
+ step = SCREWED_CORE_STEP
+ if(CHESTPLATE_STEP)
+ if(istype(part, /obj/item/mod/construction/gauntlets)) //Construct
+ if(!user.transferItemToLoc(part, src))
+ balloon_alert(user, "gauntlets stuck to your hand!")
+ return
+ playsound(src, 'sound/machines/click.ogg', 30, TRUE)
+ balloon_alert(user, "gauntlets added")
+ gauntlets = part
+ step = GAUNTLETS_STEP
+ else if(part.tool_behaviour == TOOL_CROWBAR) //Deconstruct
+ if(part.use_tool(src, user, 0, volume=30))
+ chestplate.forceMove(drop_location())
+ balloon_alert(user, "chestplate removed")
+ chestplate = null
+ step = HELMET_STEP
+ if(GAUNTLETS_STEP)
+ if(istype(part, /obj/item/mod/construction/boots)) //Construct
+ if(!user.transferItemToLoc(part, src))
+ balloon_alert(user, "boots added")
+ return
+ playsound(src, 'sound/machines/click.ogg', 30, TRUE)
+ balloon_alert(user, "you fit [part] onto [src].")
+ boots = part
+ step = BOOTS_STEP
+ else if(part.tool_behaviour == TOOL_CROWBAR) //Deconstruct
+ if(part.use_tool(src, user, 0, volume=30))
+ gauntlets.forceMove(drop_location())
+ balloon_alert(user, "gauntlets removed")
+ gauntlets = null
+ step = CHESTPLATE_STEP
+ if(BOOTS_STEP)
+ if(part.tool_behaviour == TOOL_WRENCH) //Construct
+ if(part.use_tool(src, user, 0, volume=30))
+ balloon_alert(user, "assembly secured")
+ step = WRENCHED_ASSEMBLY_STEP
+ else if(part.tool_behaviour == TOOL_CROWBAR) //Deconstruct
+ if(part.use_tool(src, user, 0, volume=30))
+ boots.forceMove(drop_location())
+ balloon_alert(user, "boots removed")
+ boots = null
+ step = GAUNTLETS_STEP
+ if(WRENCHED_ASSEMBLY_STEP)
+ if(part.tool_behaviour == TOOL_SCREWDRIVER) //Construct
+ if(part.use_tool(src, user, 0, volume=30))
+ balloon_alert(user, "assembly screwed")
+ step = SCREWED_ASSEMBLY_STEP
+ else if(part.tool_behaviour == TOOL_WRENCH) //Deconstruct
+ if(part.use_tool(src, user, 0, volume=30))
+ balloon_alert(user, "assembly unsecured")
+ step = BOOTS_STEP
+ if(SCREWED_ASSEMBLY_STEP)
+ if(istype(part, /obj/item/mod/construction/plating)) //Construct
+ var/obj/item/mod/construction/plating/external_plating = part
+ if(!user.transferItemToLoc(part, src))
+ return
+ playsound(src, 'sound/machines/click.ogg', 30, TRUE)
+ balloon_alert(user, "suit finished")
+ var/obj/item/mod = new /obj/item/mod/control(drop_location(), external_plating.theme, null, core)
+ core = null
+ qdel(src)
+ user.put_in_hands(mod)
+ else if(part.tool_behaviour == TOOL_SCREWDRIVER) //Construct
+ if(part.use_tool(src, user, 0, volume=30))
+ balloon_alert(user, "assembly unscrewed")
+ step = SCREWED_ASSEMBLY_STEP
+ update_icon_state()
+
+/obj/item/mod/construction/shell/update_icon_state()
+ . = ..()
+ icon_state = "mod-construction_[step]"
+
+/obj/item/mod/construction/shell/Destroy()
+ QDEL_NULL(core)
+ QDEL_NULL(helmet)
+ QDEL_NULL(chestplate)
+ QDEL_NULL(gauntlets)
+ QDEL_NULL(boots)
+ return ..()
+
+/obj/item/mod/construction/shell/handle_atom_del(atom/deleted_atom)
+ if(deleted_atom == core)
+ core = null
+ if(deleted_atom == helmet)
+ helmet = null
+ if(deleted_atom == chestplate)
+ chestplate = null
+ if(deleted_atom == gauntlets)
+ gauntlets = null
+ if(deleted_atom == boots)
+ boots = null
+ return ..()
+
+#undef START_STEP
+#undef CORE_STEP
+#undef SCREWED_CORE_STEP
+#undef HELMET_STEP
+#undef CHESTPLATE_STEP
+#undef GAUNTLETS_STEP
+#undef BOOTS_STEP
+#undef WRENCHED_ASSEMBLY_STEP
+#undef SCREWED_ASSEMBLY_STEP
diff --git a/code/modules/mod/mod_control.dm b/code/modules/mod/mod_control.dm
new file mode 100644
index 00000000000..0199662862f
--- /dev/null
+++ b/code/modules/mod/mod_control.dm
@@ -0,0 +1,713 @@
+/// MODsuits, trade-off between armor and utility
+/obj/item/mod
+ name = "Base MOD"
+ desc = "You should not see this, yell at a coder!"
+ icon = 'icons/obj/clothing/modsuit/mod_clothing.dmi'
+
+/obj/item/mod/control
+ name = "MOD control unit"
+ desc = "The control unit of a Modular Outerwear Device, a powered, back-mounted suit that protects against various environments."
+ icon_state = "control"
+ base_icon_state = "control"
+ item_state = "mod_control"
+ mob_overlay_icon = 'icons/mob/clothing/modsuit/mod_clothing.dmi'
+ w_class = WEIGHT_CLASS_BULKY
+ slot_flags = ITEM_SLOT_BACK
+ strip_delay = 10 SECONDS
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "fire" = 0, "acid" = 0)
+ actions_types = list(
+ /datum/action/item_action/mod/deploy,
+ /datum/action/item_action/mod/activate,
+ /datum/action/item_action/mod/panel,
+ /datum/action/item_action/mod/module,
+ /datum/action/item_action/mod/deploy/ai,
+ /datum/action/item_action/mod/activate/ai,
+ /datum/action/item_action/mod/panel/ai,
+ /datum/action/item_action/mod/module/ai,
+ )
+ resistance_flags = NONE
+ max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT
+ min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT
+ siemens_coefficient = 0.5
+ //alternate_worn_layer = HAND_LAYER+0.1 //we want it to go above generally everything, but not hands
+ /// The MOD's theme, decides on some stuff like armor and statistics.
+ var/datum/mod_theme/theme = /datum/mod_theme
+ /// Looks of the MOD.
+ var/skin = "standard"
+ /// Theme of the MOD TGUI
+ var/ui_theme = "ntos"
+ /// If the suit is deployed and turned on.
+ var/active = FALSE
+ /// If the suit wire/module hatch is open.
+ var/open = FALSE
+ /// If the suit is ID locked.
+ var/locked = FALSE
+ /// If the suit is malfunctioning.
+ var/malfunctioning = FALSE
+ /// If the suit is currently activating/deactivating.
+ var/activating = FALSE
+ /// How long the MOD is electrified for.
+ var/seconds_electrified = MACHINE_NOT_ELECTRIFIED
+ /// If the suit interface is broken.
+ var/interface_break = FALSE
+ /// How much module complexity can this MOD carry.
+ var/complexity_max = DEFAULT_MAX_COMPLEXITY
+ /// How much module complexity this MOD is carrying.
+ var/complexity = 0
+ /// Power usage of the MOD.
+ var/charge_drain = DEFAULT_CHARGE_DRAIN
+ /// Slowdown of the MOD when not active.
+ var/slowdown_inactive = 1.25
+ /// Slowdown of the MOD when active.
+ var/slowdown_active = 0.75
+ /// How long this MOD takes each part to seal.
+ var/activation_step_time = MOD_ACTIVATION_STEP_TIME
+ /// Extended description of the theme.
+ var/extended_desc
+ /// MOD helmet.
+ var/obj/item/clothing/head/mod/helmet
+ /// MOD chestplate.
+ var/obj/item/clothing/suit/mod/chestplate
+ /// MOD gauntlets.
+ var/obj/item/clothing/gloves/mod/gauntlets
+ /// MOD boots.
+ var/obj/item/clothing/shoes/mod/boots
+ /// MOD core.
+ var/obj/item/mod/core/core
+ /// Associated list of parts (helmet, chestplate, gauntlets, boots) to their unsealed worn layer.
+ var/list/mod_parts = list()
+ /// Associated list of parts that can overslot to their overslot (overslot means the part can cover another layer of clothing).
+ var/list/overslotting_parts = list()
+ /// Modules the MOD should spawn with.
+ var/list/initial_modules = list()
+ /// Modules the MOD currently possesses.
+ var/list/modules = list()
+ /// Currently used module.
+ var/obj/item/mod/module/selected_module
+ /// AI mob inhabiting the MOD.
+ var/mob/living/silicon/ai/ai
+ /// Delay between moves as AI.
+ var/movedelay = 0
+ /// Cooldown for AI moves.
+ COOLDOWN_DECLARE(cooldown_mod_move)
+ /// Person wearing the MODsuit.
+ var/mob/living/carbon/human/wearer
+
+/obj/item/mod/control/Initialize(mapload, datum/mod_theme/new_theme, new_skin, obj/item/mod/core/new_core)
+ . = ..()
+ if(new_theme)
+ theme = new_theme
+ theme = GLOB.mod_themes[theme]
+ slot_flags = theme.slot_flags
+ extended_desc = theme.extended_desc
+ slowdown_inactive = theme.slowdown_inactive
+ slowdown_active = theme.slowdown_active
+ complexity_max = theme.complexity_max
+ ui_theme = theme.ui_theme
+ charge_drain = theme.charge_drain
+ initial_modules += theme.inbuilt_modules
+ wires = new /datum/wires/mod(src)
+ if(length(req_access))
+ locked = TRUE
+ new_core?.install(src)
+ helmet = new /obj/item/clothing/head/mod(src)
+ mod_parts += helmet
+ chestplate = new /obj/item/clothing/suit/mod(src)
+ chestplate.allowed = typecacheof(theme.allowed_suit_storage)
+ mod_parts += chestplate
+ gauntlets = new /obj/item/clothing/gloves/mod(src)
+ mod_parts += gauntlets
+ boots = new /obj/item/clothing/shoes/mod(src)
+ mod_parts += boots
+ var/list/all_parts = mod_parts + src
+ for(var/obj/item/part as anything in all_parts)
+ part.name = "[theme.name] [part.name]"
+ part.desc = "[part.desc] [theme.desc]"
+ part.armor = getArmor(arglist(theme.armor))
+ part.resistance_flags = theme.resistance_flags
+ part.flags_1 |= theme.atom_flags //flags like initialization or admin spawning are here, so we cant set, have to add
+ part.heat_protection = NONE
+ part.cold_protection = NONE
+ part.max_heat_protection_temperature = theme.max_heat_protection_temperature
+ part.min_cold_protection_temperature = theme.min_cold_protection_temperature
+ part.siemens_coefficient = theme.siemens_coefficient
+ for(var/obj/item/part as anything in mod_parts)
+ RegisterSignal(part, COMSIG_OBJ_DESTRUCTION, PROC_REF(on_part_destruction))
+ RegisterSignal(part, COMSIG_PARENT_QDELETING, PROC_REF(on_part_deletion))
+ set_mod_skin(new_skin || theme.default_skin)
+ update_speed()
+ for(var/obj/item/mod/module/module as anything in initial_modules)
+ module = new module(src)
+ install(module)
+ RegisterSignal(src, COMSIG_ATOM_EXITED, PROC_REF(on_exit))
+ movedelay = CONFIG_GET(number/movedelay/run_delay)
+
+/obj/item/mod/control/Destroy()
+ if(active)
+ STOP_PROCESSING(SSobj, src)
+ for(var/obj/item/mod/module/module as anything in modules)
+ uninstall(module, deleting = TRUE)
+ for(var/obj/item/part as anything in mod_parts)
+ overslotting_parts -= part
+ var/atom/deleting_atom
+ if(!QDELETED(helmet))
+ deleting_atom = helmet
+ helmet = null
+ mod_parts -= deleting_atom
+ qdel(deleting_atom)
+ if(!QDELETED(chestplate))
+ deleting_atom = chestplate
+ chestplate = null
+ mod_parts -= deleting_atom
+ qdel(deleting_atom)
+ if(!QDELETED(gauntlets))
+ deleting_atom = gauntlets
+ gauntlets = null
+ mod_parts -= deleting_atom
+ qdel(deleting_atom)
+ if(!QDELETED(boots))
+ deleting_atom = boots
+ boots = null
+ mod_parts -= deleting_atom
+ qdel(deleting_atom)
+ if(core)
+ QDEL_NULL(core)
+ QDEL_NULL(wires)
+ return ..()
+
+/obj/item/mod/control/obj_destruction(damage_flag)
+ for(var/obj/item/mod/module/module as anything in modules)
+ uninstall(module)
+ for(var/obj/item/part as anything in mod_parts)
+ if(!overslotting_parts[part])
+ continue
+ var/obj/item/overslot = overslotting_parts[part]
+ overslot.forceMove(drop_location())
+ overslotting_parts[part] = null
+ /*if(ai)
+ ai.controlled_equipment = null
+ ai.remote_control = null
+ for(var/datum/action/action as anything in actions)
+ if(action.owner == ai)
+ action.Remove(ai)
+ new /obj/item/mod/ai_minicard(drop_location(), ai)*/
+ return ..()
+
+/obj/item/mod/control/examine(mob/user)
+ . = ..()
+ if(active)
+ . += span_notice("Charge: [core ? "[get_charge_percent()]%" : "No core"].")
+ . += span_notice("Selected module: [selected_module || "None"].")
+ if(!open && !active)
+ . += span_notice("You could put it on your back to turn it on.")
+ . += span_notice("You could open the cover with a screwdriver.")
+ else if(open)
+ . += span_notice("You could close the cover with a screwdriver.")
+ . += span_notice("You could use modules on it to install them.")
+ . += span_notice("You could remove modules with a crowbar.")
+ . += span_notice("You could update the access lock with an ID.")
+ . += span_notice("You could access the wire panel with a wire tool.")
+ if(core)
+ . += span_notice("You could remove [core] with a wrench.")
+ else
+ . += span_notice("You could use a MOD core on it to install one.")
+ if(ai)
+ . += span_notice("You could remove [ai] with an intellicard.")
+ else
+ . += span_notice("You could install an AI with an intellicard.")
+
+/obj/item/mod/control/examine_more(mob/user)
+ . = ..()
+ . += "[extended_desc]"
+
+/obj/item/mod/control/process(delta_time)
+ if(seconds_electrified > MACHINE_NOT_ELECTRIFIED)
+ seconds_electrified--
+ if(!get_charge() && active && !activating)
+ power_off()
+ return PROCESS_KILL
+ var/malfunctioning_charge_drain = 0
+ if(malfunctioning)
+ malfunctioning_charge_drain = rand(1,20)
+ subtract_charge((charge_drain + malfunctioning_charge_drain)*delta_time)
+ update_charge_alert()
+ for(var/obj/item/mod/module/module as anything in modules)
+ if(malfunctioning && module.active && DT_PROB(5, delta_time))
+ module.on_deactivation(display_message = TRUE)
+ module.on_process(delta_time)
+
+/obj/item/mod/control/equipped(mob/user, slot)
+ ..()
+ if(slot == slot_flags)
+ set_wearer(user)
+ else if(wearer)
+ unset_wearer()
+
+/obj/item/mod/control/dropped(mob/user)
+ . = ..()
+ if(!wearer)
+ return
+ clean_up()
+
+/obj/item/mod/control/item_action_slot_check(slot)
+ if(slot & slot_flags)
+ return TRUE
+
+/obj/item/mod/control/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
+ . = ..()
+ if(!wearer || old_loc != wearer || loc == wearer)
+ return
+ clean_up()
+
+/obj/item/mod/control/allow_attack_hand_drop(mob/user)
+ if(user != wearer)
+ return ..()
+ for(var/obj/item/part as anything in mod_parts)
+ if(part.loc != src)
+ balloon_alert(user, "retract parts first!")
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, FALSE, SILENCED_SOUND_EXTRARANGE)
+ return FALSE
+
+/obj/item/mod/control/MouseDrop(atom/over_object)
+ if(usr != wearer || !istype(over_object, /atom/movable/screen/inventory/hand))
+ return ..()
+ for(var/obj/item/part as anything in mod_parts)
+ if(part.loc != src)
+ balloon_alert(wearer, "retract parts first!")
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, FALSE, SILENCED_SOUND_EXTRARANGE)
+ return
+ if(!wearer.incapacitated())
+ var/atom/movable/screen/inventory/hand/ui_hand = over_object
+ if(wearer.putItemFromInventoryInHandIfPossible(src, ui_hand.held_index))
+ add_fingerprint(usr)
+ return ..()
+
+/obj/item/mod/control/wrench_act(mob/living/user, obj/item/wrench)
+ if(..())
+ return TRUE
+ if(seconds_electrified && get_charge() && shock(user))
+ return TRUE
+ if(open)
+ if(!core)
+ balloon_alert(user, "no core!")
+ return TRUE
+ balloon_alert(user, "removing core...")
+ wrench.play_tool_sound(src, 100)
+ if(!wrench.use_tool(src, user, 3 SECONDS) || !open)
+ balloon_alert(user, "interrupted!")
+ return TRUE
+ wrench.play_tool_sound(src, 100)
+ balloon_alert(user, "core removed")
+ core.forceMove(drop_location())
+ update_charge_alert()
+ return TRUE
+ return ..()
+
+/obj/item/mod/control/screwdriver_act(mob/living/user, obj/item/screwdriver)
+ if(..())
+ return TRUE
+ if(active || activating)// || ai_controller)
+ balloon_alert(user, "deactivate suit first!")
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
+ return FALSE
+ balloon_alert(user, "[open ? "closing" : "opening"] cover...")
+ screwdriver.play_tool_sound(src, 100)
+ if(screwdriver.use_tool(src, user, 1 SECONDS))
+ if(active || activating)
+ balloon_alert(user, "deactivate suit first!")
+ screwdriver.play_tool_sound(src, 100)
+ balloon_alert(user, "cover [open ? "closed" : "opened"]")
+ open = !open
+ else
+ balloon_alert(user, "interrupted!")
+ return TRUE
+
+/obj/item/mod/control/crowbar_act(mob/living/user, obj/item/crowbar)
+ . = ..()
+ if(!open)
+ balloon_alert(user, "open the cover first!")
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
+ return FALSE
+ if(!allowed(user))
+ balloon_alert(user, "insufficient access!")
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
+ return
+ if(SEND_SIGNAL(src, COMSIG_MOD_MODULE_REMOVAL, user) & MOD_CANCEL_REMOVAL)
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
+ return FALSE
+ if(length(modules))
+ var/list/removable_modules = list()
+ for(var/obj/item/mod/module/module as anything in modules)
+ if(!module.removable)
+ continue
+ removable_modules += module
+ var/obj/item/mod/module/module_to_remove = tgui_input_list(user, "Which module to remove?", "Module Removal", removable_modules)
+ if(!module_to_remove?.mod)
+ return FALSE
+ uninstall(module_to_remove)
+ module_to_remove.forceMove(drop_location())
+ crowbar.play_tool_sound(src, 100)
+ return TRUE
+ balloon_alert(user, "no modules!")
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
+ return FALSE
+
+/obj/item/mod/control/attackby(obj/item/attacking_item, mob/living/user, params)
+ if(istype(attacking_item, /obj/item/mod/module))
+ if(!open)
+ balloon_alert(user, "open the cover first!")
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
+ return FALSE
+ install(attacking_item, user)
+ return TRUE
+ else if(istype(attacking_item, /obj/item/mod/core))
+ if(!open)
+ balloon_alert(user, "open the cover first!")
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
+ return FALSE
+ if(core)
+ balloon_alert(user, "core already installed!")
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
+ return FALSE
+ var/obj/item/mod/core/attacking_core = attacking_item
+ attacking_core.install(src)
+ balloon_alert(user, "core installed")
+ playsound(src, 'sound/machines/click.ogg', 50, TRUE, SILENCED_SOUND_EXTRARANGE)
+ update_charge_alert()
+ return TRUE
+ else if(is_wire_tool(attacking_item) && open)
+ wires.interact(user)
+ return TRUE
+ else if(open && attacking_item.GetID())
+ update_access(user, attacking_item.GetID())
+ return TRUE
+ else if(open && istype(attacking_item, /obj/item/stock_parts/cell) && istype(core, /obj/item/mod/core/standard))
+ var/obj/item/mod/core/standard/attacked_core = core
+ attacked_core.on_attackby(src, attacking_item, wearer)
+ return TRUE
+ return ..()
+
+/obj/item/mod/control/get_cell()
+ if(!open)
+ return
+ var/obj/item/stock_parts/cell/cell = get_charge_source()
+ if(!istype(cell))
+ return
+ return cell
+
+/obj/item/mod/control/GetAccess()
+ /*if(ai_controller)
+ return req_access.Copy()
+ else */
+ return ..()
+
+/obj/item/mod/control/emag_act(mob/user)
+ locked = !locked
+ balloon_alert(user, "suit access [locked ? "locked" : "unlocked"]")
+
+/obj/item/mod/control/emp_act(severity)
+ . = ..()
+ if(!active || !wearer)
+ return
+ to_chat(wearer, span_notice("[severity > 1 ? "Light" : "Strong"] electromagnetic pulse detected!"))
+ if(. & EMP_PROTECT_CONTENTS)
+ return
+ selected_module?.on_deactivation(display_message = TRUE)
+ wearer.apply_damage(10 / severity, BURN, spread_damage=TRUE)
+ to_chat(wearer, span_danger("You feel [src] heat up from the EMP, burning you slightly."))
+ if(wearer.stat < UNCONSCIOUS && prob(10))
+ wearer.emote("scream")
+
+/*obj/item/mod/control/on_outfit_equip(mob/living/carbon/human/outfit_wearer, visuals_only, item_slot)
+ if(visuals_only)
+ set_wearer(outfit_wearer) //we need to set wearer manually since it doesnt call equipped
+ quick_activation()*/
+
+/obj/item/mod/control/doStrip(mob/stripper, mob/owner)
+ if(active && !toggle_activate(stripper, force_deactivate = TRUE))
+ return
+ for(var/obj/item/part as anything in mod_parts)
+ if(part.loc == src)
+ continue
+ retract(null, part)
+ return ..()
+
+/obj/item/mod/control/worn_overlays(isinhands = FALSE, icon_file)
+ . = ..()
+ for(var/obj/item/mod/module/module as anything in modules)
+ var/list/module_icons = module.generate_worn_overlay(src.layer)
+ if(!length(module_icons))
+ continue
+ . += module_icons
+
+/obj/item/mod/control/update_icon_state()
+ item_state = "[skin]-control[active ? "-sealed" : ""]"
+ return ..()
+
+/obj/item/mod/control/proc/set_wearer(mob/user)
+ wearer = user
+ SEND_SIGNAL(src, COMSIG_MOD_WEARER_SET, wearer)
+ RegisterSignal(wearer, COMSIG_ATOM_EXITED, PROC_REF(on_exit))
+ RegisterSignal(wearer, COMSIG_SPECIES_GAIN, PROC_REF(on_species_gain))
+ update_charge_alert()
+ for(var/obj/item/mod/module/module as anything in modules)
+ module.on_equip()
+
+/obj/item/mod/control/proc/unset_wearer()
+ for(var/obj/item/mod/module/module as anything in modules)
+ module.on_unequip()
+ UnregisterSignal(wearer, list(COMSIG_ATOM_EXITED, COMSIG_SPECIES_GAIN))
+ wearer.clear_alert("mod_charge")
+ SEND_SIGNAL(src, COMSIG_MOD_WEARER_UNSET, wearer)
+ wearer = null
+
+/obj/item/mod/control/proc/clean_up()
+ if(active || activating)
+ for(var/obj/item/mod/module/module as anything in modules)
+ if(!module.active)
+ continue
+ module.on_deactivation(display_message = FALSE)
+ for(var/obj/item/part as anything in mod_parts)
+ seal_part(part, seal = FALSE)
+ for(var/obj/item/part as anything in mod_parts)
+ retract(null, part)
+ if(active)
+ finish_activation(on = FALSE)
+ var/mob/old_wearer = wearer
+ unset_wearer()
+ old_wearer.temporarilyRemoveItemFromInventory(src)
+
+/obj/item/mod/control/proc/on_species_gain(datum/source, datum/species/new_species, datum/species/old_species)
+ SIGNAL_HANDLER
+
+ var/list/all_parts = mod_parts + src
+ for(var/obj/item/part in all_parts)
+ if(!(part.slot_flags in new_species.no_equip) || is_type_in_list(new_species, part.species_exception))
+ continue
+ forceMove(drop_location())
+ return
+
+/obj/item/mod/control/proc/quick_module(mob/user)
+ if(!length(modules))
+ return
+ var/list/display_names = list()
+ var/list/items = list()
+ for(var/obj/item/mod/module/module as anything in modules)
+ if(module.module_type == MODULE_PASSIVE)
+ continue
+ display_names[module.name] = REF(module)
+ var/image/module_image = image(icon = module.icon, icon_state = module.icon_state)
+ if(module == selected_module)
+ module_image.underlays += image(icon = 'icons/hud/radial.dmi', icon_state = "module_selected")
+ else if(module.active)
+ module_image.underlays += image(icon = 'icons/hud/radial.dmi', icon_state = "module_active")
+ if(!COOLDOWN_FINISHED(module, cooldown_timer))
+ module_image.add_overlay(image(icon = 'icons/hud/radial.dmi', icon_state = "module_cooldown"))
+ items += list(module.name = module_image)
+ if(!length(items))
+ return
+ var/radial_anchor = src
+ if(istype(user.loc, /obj/effect/dummy/phased_mob))
+ radial_anchor = get_turf(user.loc) //they're phased out via some module, anchor the radial on the turf so it may still display
+ var/pick = show_radial_menu(user, radial_anchor, items, custom_check = FALSE, require_near = TRUE, tooltips = TRUE)
+ if(!pick)
+ return
+ var/module_reference = display_names[pick]
+ var/obj/item/mod/module/picked_module = locate(module_reference) in modules
+ if(!istype(picked_module))
+ return
+ picked_module.on_select()
+
+/obj/item/mod/control/proc/shock(mob/living/user)
+ if(!istype(user) || get_charge() < 1)
+ return FALSE
+ do_sparks(5, TRUE, src)
+ var/check_range = TRUE
+ return electrocute_mob(user, get_charge_source(), src, 0.7, check_range)
+
+/obj/item/mod/control/proc/install(obj/item/mod/module/new_module, mob/user)
+ for(var/obj/item/mod/module/old_module as anything in modules)
+ if(is_type_in_list(new_module, old_module.incompatible_modules) || is_type_in_list(old_module, new_module.incompatible_modules))
+ if(user)
+ balloon_alert(user, "[new_module] incompatible with [old_module]!")
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
+ return
+ if(is_type_in_list(new_module, theme.module_blacklist))
+ if(user)
+ balloon_alert(user, "[src] doesn't accept [new_module]!")
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
+ return
+ var/complexity_with_module = complexity
+ complexity_with_module += new_module.complexity
+ if(complexity_with_module > complexity_max)
+ if(user)
+ balloon_alert(user, "[new_module] would make [src] too complex!")
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
+ return
+ new_module.forceMove(src)
+ modules += new_module
+ complexity += new_module.complexity
+ new_module.mod = src
+ new_module.on_install()
+ if(wearer)
+ new_module.on_equip()
+
+ if(user)
+ balloon_alert(user, "[new_module] added")
+ playsound(src, 'sound/machines/click.ogg', 50, TRUE, SILENCED_SOUND_EXTRARANGE)
+
+/obj/item/mod/control/proc/uninstall(obj/item/mod/module/old_module, deleting = FALSE)
+ modules -= old_module
+ complexity -= old_module.complexity
+ if(active)
+ old_module.on_suit_deactivation(deleting = deleting)
+ if(old_module.active)
+ old_module.on_deactivation(display_message = !deleting, deleting = deleting)
+ old_module.on_uninstall(deleting = deleting)
+ QDEL_LIST_ASSOC_VAL(old_module.pinned_to)
+ old_module.mod = null
+
+/// Intended for callbacks, don't use normally, just get wearer by itself.
+/obj/item/mod/control/proc/get_wearer()
+ return wearer
+
+/obj/item/mod/control/proc/update_access(mob/user, obj/item/card/id/card)
+ if(!allowed(user))
+ balloon_alert(user, "insufficient access!")
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
+ return
+ req_access = card.access.Copy()
+ balloon_alert(user, "access updated")
+
+/obj/item/mod/control/proc/get_charge_source()
+ return core?.charge_source()
+
+/obj/item/mod/control/proc/get_charge()
+ return core?.charge_amount() || 0
+
+/obj/item/mod/control/proc/get_max_charge()
+ return core?.max_charge_amount() || 1 //avoid dividing by 0
+
+/obj/item/mod/control/proc/get_charge_percent()
+ return ROUND_UP((get_charge() / get_max_charge()) * 100)
+
+/obj/item/mod/control/proc/add_charge(amount)
+ return core?.add_charge(amount) || FALSE
+
+/obj/item/mod/control/proc/subtract_charge(amount)
+ return core?.subtract_charge(amount) || FALSE
+
+/obj/item/mod/control/proc/check_charge(amount)
+ return core?.check_charge(amount) || FALSE
+
+/obj/item/mod/control/proc/update_charge_alert()
+ if(!wearer)
+ return
+ if(!core)
+ wearer.throw_alert("mod_charge", /atom/movable/screen/alert/nocore)
+ return
+ core.update_charge_alert()
+
+/obj/item/mod/control/proc/update_speed()
+ var/list/all_parts = mod_parts + src
+ for(var/obj/item/part as anything in all_parts)
+ part.slowdown = (active ? slowdown_active : slowdown_inactive) / length(all_parts)
+ wearer?.update_equipment_speed_mods()
+
+/obj/item/mod/control/proc/power_off()
+ balloon_alert(wearer, "Нет энергии!")
+ toggle_activate(wearer, force_deactivate = TRUE)
+
+/obj/item/mod/control/proc/set_mod_color(new_color)
+ var/list/all_parts = mod_parts + src
+ for(var/obj/item/part as anything in all_parts)
+ part.remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
+ part.add_atom_colour(new_color, FIXED_COLOUR_PRIORITY)
+ wearer?.regenerate_icons()
+
+/obj/item/mod/control/proc/set_mod_skin(new_skin)
+ if(active)
+ CRASH("[src] tried to set skin while active!")
+ skin = new_skin
+ var/list/used_skin = theme.skins[new_skin]
+ if(used_skin[CONTROL_LAYER])
+ alternate_worn_layer = used_skin[CONTROL_LAYER]
+ var/list/skin_updating = mod_parts + src
+ for(var/obj/item/part as anything in skin_updating)
+ part.icon = used_skin[MOD_ICON_OVERRIDE] || 'icons/obj/clothing/modsuit/mod_clothing.dmi'
+ //part.mob_overlay_icon = used_skin[MOD_WORN_ICON_OVERRIDE] || 'icons/mob/clothing/modsuit/mod_clothing.dmi'
+ part.icon_state = "[skin]-[part.base_icon_state]"
+ for(var/obj/item/clothing/part as anything in mod_parts)
+ var/used_category
+ if(part == helmet)
+ used_category = HELMET_FLAGS
+ if(part == chestplate)
+ used_category = CHESTPLATE_FLAGS
+ if(part == gauntlets)
+ used_category = GAUNTLETS_FLAGS
+ if(part == boots)
+ used_category = BOOTS_FLAGS
+ var/list/category = used_skin[used_category]
+ part.clothing_flags = category[UNSEALED_CLOTHING] || NONE
+ part.visor_flags = category[SEALED_CLOTHING] || NONE
+ part.flags_inv = category[UNSEALED_INVISIBILITY] || NONE
+ part.visor_flags_inv = category[SEALED_INVISIBILITY] || NONE
+ part.flags_cover = category[UNSEALED_COVER] || NONE
+ part.visor_flags_cover = category[SEALED_COVER] || NONE
+ part.alternate_worn_layer = category[UNSEALED_LAYER]
+ mod_parts[part] = part.alternate_worn_layer
+ if(!category[CAN_OVERSLOT])
+ if(overslotting_parts[part])
+ var/obj/item/overslot = overslotting_parts[part]
+ overslot.forceMove(drop_location())
+ overslotting_parts -= part
+ continue
+ overslotting_parts |= part
+ wearer?.regenerate_icons()
+
+/obj/item/mod/control/proc/on_exit(datum/source, atom/movable/part, direction)
+ SIGNAL_HANDLER
+
+ if(part.loc == src)
+ return
+ if(part == core)
+ core.uninstall()
+ update_charge_alert()
+ return
+ if(part.loc == wearer)
+ return
+ if(part in modules)
+ uninstall(part)
+ return
+ if(part in mod_parts)
+ if(!wearer)
+ part.forceMove(src)
+ return
+ retract(wearer, part)
+ if(active)
+ INVOKE_ASYNC(src, PROC_REF(toggle_activate), wearer, TRUE)
+
+/obj/item/mod/control/proc/on_part_destruction(obj/item/part, damage_flag)
+ SIGNAL_HANDLER
+
+ if(overslotting_parts[part])
+ var/obj/item/overslot = overslotting_parts[part]
+ overslot.forceMove(drop_location())
+ overslotting_parts[part] = null
+ if(QDELETED(src))
+ return
+ obj_destruction(damage_flag)
+
+/obj/item/mod/control/proc/on_part_deletion(obj/item/part)
+ SIGNAL_HANDLER
+
+ if(QDELETED(src))
+ return
+ qdel(src)
+
+/obj/item/mod/control/proc/on_overslot_exit(datum/source, atom/movable/overslot, direction)
+ SIGNAL_HANDLER
+
+ if(overslot != overslotting_parts[source])
+ return
+ overslotting_parts[source] = null
diff --git a/code/modules/mod/mod_core.dm b/code/modules/mod/mod_core.dm
new file mode 100644
index 00000000000..4c9d16ef7b7
--- /dev/null
+++ b/code/modules/mod/mod_core.dm
@@ -0,0 +1,357 @@
+/obj/item/mod/core
+ name = "MOD core"
+ desc = "A non-functional MOD core. Inform the admins if you see this."
+ icon = 'icons/obj/clothing/modsuit/mod_construction.dmi'
+ icon_state = "mod-core"
+ item_state = "electronic"
+ lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
+ /// MOD unit we are powering.
+ var/obj/item/mod/control/mod
+
+/obj/item/mod/core/Destroy()
+ if(mod)
+ uninstall()
+ return ..()
+
+/obj/item/mod/core/proc/install(obj/item/mod/control/mod_unit)
+ mod = mod_unit
+ mod.core = src
+ forceMove(mod)
+
+/obj/item/mod/core/proc/uninstall()
+ mod.core = null
+ mod = null
+
+/obj/item/mod/core/proc/charge_source()
+ return
+
+/obj/item/mod/core/proc/charge_amount()
+ return 0
+
+/obj/item/mod/core/proc/max_charge_amount()
+ return 1
+
+/obj/item/mod/core/proc/add_charge(amount)
+ return FALSE
+
+/obj/item/mod/core/proc/subtract_charge(amount)
+ return FALSE
+
+/obj/item/mod/core/proc/check_charge(amount)
+ return FALSE
+
+/obj/item/mod/core/proc/update_charge_alert()
+ mod.wearer.clear_alert("mod_charge")
+
+/obj/item/mod/core/infinite
+ name = "MOD infinite core"
+ icon_state = "mod-core-infinite"
+ desc = "A fusion core using the rare Fixium to sustain enough energy for the lifetime of the MOD's user. \
+ This might be because of the slowly killing poison inside, but those are just rumors."
+
+/obj/item/mod/core/infinite/charge_source()
+ return src
+
+/obj/item/mod/core/infinite/charge_amount()
+ return INFINITY
+
+/obj/item/mod/core/infinite/max_charge_amount()
+ return INFINITY
+
+/obj/item/mod/core/infinite/add_charge(amount)
+ return TRUE
+
+/obj/item/mod/core/infinite/subtract_charge(amount)
+ return TRUE
+
+/obj/item/mod/core/infinite/check_charge(amount)
+ return TRUE
+
+/obj/item/mod/core/standard
+ name = "MOD standard core"
+ icon_state = "mod-core-standard"
+ desc = "Growing in the most lush, fertile areas of the planet Sprout, there is a crystal known as the Heartbloom. \
+ These rare, organic piezoelectric crystals are of incredible cultural significance to the artist castes of the \
+ Ethereals, owing to their appearance; which is exactly similar to that of an Ethereal's heart.\n\
+ Which one you have in your suit is unclear, but either way, \
+ it's been repurposed to be an internal power source for a Modular Outerwear Device."
+ /// Installed cell.
+ var/obj/item/stock_parts/cell/cell
+
+/obj/item/mod/core/standard/Destroy()
+ if(cell)
+ QDEL_NULL(cell)
+ return ..()
+
+/obj/item/mod/core/standard/install(obj/item/mod/control/mod_unit)
+ . = ..()
+ if(cell)
+ install_cell(cell)
+ RegisterSignal(mod, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
+ RegisterSignal(mod, COMSIG_ATOM_ATTACK_HAND, PROC_REF(on_attack_hand))
+ RegisterSignal(mod, COMSIG_PARENT_ATTACKBY, PROC_REF(on_attackby))
+ RegisterSignal(mod, COMSIG_MOD_WEARER_SET, PROC_REF(on_wearer_set))
+ if(mod.wearer)
+ on_wearer_set(mod, mod.wearer)
+
+/obj/item/mod/core/standard/uninstall()
+ if(!QDELETED(cell))
+ cell.forceMove(drop_location())
+ UnregisterSignal(mod, list(COMSIG_PARENT_EXAMINE, COMSIG_ATOM_ATTACK_HAND, COMSIG_PARENT_ATTACKBY, COMSIG_MOD_WEARER_SET))
+ if(mod.wearer)
+ on_wearer_unset(mod, mod.wearer)
+ return ..()
+
+/obj/item/mod/core/standard/charge_source()
+ return cell
+
+/obj/item/mod/core/standard/charge_amount()
+ var/obj/item/stock_parts/cell/charge_source = charge_source()
+ return charge_source?.charge || 0
+
+/obj/item/mod/core/standard/max_charge_amount(amount)
+ var/obj/item/stock_parts/cell/charge_source = charge_source()
+ return charge_source?.maxcharge || 1
+
+/obj/item/mod/core/standard/add_charge(amount)
+ var/obj/item/stock_parts/cell/charge_source = charge_source()
+ if(!charge_source)
+ return FALSE
+ return charge_source.give(amount)
+
+/obj/item/mod/core/standard/subtract_charge(amount)
+ var/obj/item/stock_parts/cell/charge_source = charge_source()
+ if(!charge_source)
+ return FALSE
+ return charge_source.use(amount, TRUE)
+
+/obj/item/mod/core/standard/check_charge(amount)
+ return charge_amount() >= amount
+
+/obj/item/mod/core/standard/update_charge_alert()
+ var/obj/item/stock_parts/cell/charge_source = charge_source()
+ if(!charge_source)
+ mod.wearer.throw_alert("mod_charge", /atom/movable/screen/alert/nocell)
+ return
+ var/remaining_cell = charge_amount() / max_charge_amount()
+ switch(remaining_cell)
+ if(0.75 to INFINITY)
+ mod.wearer.clear_alert("mod_charge")
+ if(0.5 to 0.75)
+ mod.wearer.throw_alert("mod_charge", /atom/movable/screen/alert/lowcell, 1)
+ if(0.25 to 0.5)
+ mod.wearer.throw_alert("mod_charge", /atom/movable/screen/alert/lowcell, 2)
+ if(0.01 to 0.25)
+ mod.wearer.throw_alert("mod_charge", /atom/movable/screen/alert/lowcell, 3)
+ else
+ mod.wearer.throw_alert("mod_charge", /atom/movable/screen/alert/emptycell)
+
+/obj/item/mod/core/standard/proc/install_cell(new_cell)
+ cell = new_cell
+ cell.forceMove(src)
+ RegisterSignal(src, COMSIG_ATOM_EXITED, PROC_REF(on_exit))
+
+/obj/item/mod/core/standard/proc/uninstall_cell()
+ if(!cell)
+ return
+ cell = null
+ UnregisterSignal(src, COMSIG_ATOM_EXITED)
+
+/obj/item/mod/core/standard/proc/on_exit(datum/source, obj/item/stock_parts/cell, direction)
+ SIGNAL_HANDLER
+
+ if(!istype(cell) || cell.loc == src)
+ return
+ uninstall_cell()
+
+/obj/item/mod/core/standard/proc/on_examine(datum/source, mob/examiner, list/examine_text)
+ SIGNAL_HANDLER
+
+ if(!mod.open)
+ return
+ examine_text += cell ? "You could remove the cell with an empty hand." : "You could use a cell on it to install one."
+
+/obj/item/mod/core/standard/proc/on_attack_hand(datum/source, mob/living/user)
+ SIGNAL_HANDLER
+
+ if(mod.seconds_electrified && charge_amount() && mod.shock(user))
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+ if(mod.open && mod.loc == user)
+ INVOKE_ASYNC(src, PROC_REF(mod_uninstall_cell), user)
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+ return NONE
+
+/obj/item/mod/core/standard/proc/mod_uninstall_cell(mob/living/user)
+ if(!cell)
+ mod.balloon_alert(user, "no cell!")
+ return
+ mod.balloon_alert(user, "removing cell...")
+ if(!do_after(user, 1.5 SECONDS, target = mod))
+ mod.balloon_alert(user, "interrupted!")
+ return
+ mod.balloon_alert(user, "cell removed")
+ playsound(mod, 'sound/machines/click.ogg', 50, TRUE, SILENCED_SOUND_EXTRARANGE)
+ var/obj/item/cell_to_move = cell
+ cell_to_move.forceMove(drop_location())
+ user.put_in_hands(cell_to_move)
+ mod.update_charge_alert()
+
+/obj/item/mod/core/standard/proc/on_attackby(datum/source, obj/item/attacking_item, mob/user)
+ SIGNAL_HANDLER
+
+ if(istype(attacking_item, /obj/item/stock_parts/cell))
+ if(!mod.open)
+ mod.balloon_alert(user, "open the cover first!")
+ playsound(mod, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
+ return NONE
+ if(cell)
+ mod.balloon_alert(user, "cell already installed!")
+ playsound(mod, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
+ return COMPONENT_NO_AFTERATTACK
+ install_cell(attacking_item)
+ mod.balloon_alert(user, "cell installed")
+ playsound(mod, 'sound/machines/click.ogg', 50, TRUE, SILENCED_SOUND_EXTRARANGE)
+ mod.update_charge_alert()
+ return COMPONENT_NO_AFTERATTACK
+ return NONE
+
+/obj/item/mod/core/standard/proc/on_wearer_set(datum/source, mob/user)
+ SIGNAL_HANDLER
+
+ RegisterSignal(mod.wearer, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, PROC_REF(on_borg_charge))
+ RegisterSignal(mod, COMSIG_MOD_WEARER_UNSET, PROC_REF(on_wearer_unset))
+
+/obj/item/mod/core/standard/proc/on_wearer_unset(datum/source, mob/user)
+ SIGNAL_HANDLER
+
+ UnregisterSignal(mod.wearer, COMSIG_PROCESS_BORGCHARGER_OCCUPANT)
+ UnregisterSignal(mod, COMSIG_MOD_WEARER_UNSET)
+
+/obj/item/mod/core/standard/proc/on_borg_charge(datum/source, amount)
+ SIGNAL_HANDLER
+
+ add_charge(amount)
+ mod.update_charge_alert()
+
+/obj/item/mod/core/ethereal
+ name = "MOD ethereal core"
+ icon_state = "mod-core-ethereal"
+ desc = "A reverse engineered core of a Modular Outerwear Device. Using natural liquid electricity from Ethereals, \
+ preventing the need to use external sources to convert electric charge."
+ /// A modifier to all charge we use, ethereals don't need to spend as much energy as normal suits.
+ var/charge_modifier = 0.1
+
+/obj/item/mod/core/ethereal/charge_source()
+ var/obj/item/organ/stomach/ethereal/ethereal_stomach = mod.wearer.getorganslot(ORGAN_SLOT_STOMACH)
+ if(!istype(ethereal_stomach))
+ return
+ return ethereal_stomach
+
+/obj/item/mod/core/ethereal/charge_amount()
+ var/obj/item/organ/stomach/ethereal/charge_source = charge_source()
+ return charge_source?.crystal_charge || ELZUOSE_CHARGE_NONE
+
+/obj/item/mod/core/ethereal/max_charge_amount()
+ return ELZUOSE_CHARGE_FULL
+
+/obj/item/mod/core/ethereal/add_charge(amount)
+ var/obj/item/organ/stomach/ethereal/charge_source = charge_source()
+ if(!charge_source)
+ return FALSE
+ charge_source.adjust_charge(amount*charge_modifier)
+ return TRUE
+
+/obj/item/mod/core/ethereal/subtract_charge(amount)
+ var/obj/item/organ/stomach/ethereal/charge_source = charge_source()
+ if(!charge_source)
+ return FALSE
+ charge_source.adjust_charge(-amount*charge_modifier)
+ return TRUE
+
+/obj/item/mod/core/ethereal/check_charge(amount)
+ return charge_amount() >= amount*charge_modifier
+
+/obj/item/mod/core/ethereal/update_charge_alert()
+ var/obj/item/organ/stomach/ethereal/charge_source = charge_source()
+ if(charge_source)
+ mod.wearer.clear_alert("mod_charge")
+ return
+ mod.wearer.throw_alert("mod_charge", /atom/movable/screen/alert/nocell)
+
+/obj/item/mod/core/plasma
+ name = "MOD plasma core"
+ icon_state = "mod-core-plasma"
+ desc = "Nanotrasen's attempt at capitalizing on their plasma research. These plasma cores are refueled \
+ through plasma ore, allowing for easy continued use by their mining squads."
+ /// How much charge we can store.
+ var/maxcharge = 10000
+ /// How much charge we are currently storing.
+ var/charge = 10000
+ /// Associated list of charge sources and how much they charge, only stacks allowed.
+ var/list/charger_list = list(/obj/item/stack/ore/plasma = 1500, /obj/item/stack/sheet/mineral/plasma = 2000)
+
+/obj/item/mod/core/plasma/install(obj/item/mod/control/mod_unit)
+ . = ..()
+ RegisterSignal(mod, COMSIG_PARENT_ATTACKBY, PROC_REF(on_attackby))
+
+/obj/item/mod/core/plasma/uninstall()
+ UnregisterSignal(mod, COMSIG_PARENT_ATTACKBY)
+ return ..()
+
+/obj/item/mod/core/plasma/attackby(obj/item/attacking_item, mob/user, params)
+ if(charge_plasma(attacking_item, user))
+ return TRUE
+ return ..()
+
+/obj/item/mod/core/plasma/charge_source()
+ return src
+
+/obj/item/mod/core/plasma/charge_amount()
+ return charge
+
+/obj/item/mod/core/plasma/max_charge_amount()
+ return maxcharge
+
+/obj/item/mod/core/plasma/add_charge(amount)
+ charge = min(maxcharge, charge + amount)
+ return TRUE
+
+/obj/item/mod/core/plasma/subtract_charge(amount)
+ charge = max(0, charge - amount)
+ return TRUE
+
+/obj/item/mod/core/plasma/check_charge(amount)
+ return charge_amount() >= amount
+
+/obj/item/mod/core/plasma/update_charge_alert()
+ var/remaining_plasma = charge_amount() / max_charge_amount()
+ switch(remaining_plasma)
+ if(0.75 to INFINITY)
+ mod.wearer.clear_alert("mod_charge")
+ if(0.5 to 0.75)
+ mod.wearer.throw_alert("mod_charge", /atom/movable/screen/alert/lowcell/plasma, 1)
+ if(0.25 to 0.5)
+ mod.wearer.throw_alert("mod_charge", /atom/movable/screen/alert/lowcell/plasma, 2)
+ if(0.01 to 0.25)
+ mod.wearer.throw_alert("mod_charge", /atom/movable/screen/alert/lowcell/plasma, 3)
+ else
+ mod.wearer.throw_alert("mod_charge", /atom/movable/screen/alert/emptycell/plasma)
+
+/obj/item/mod/core/plasma/proc/on_attackby(datum/source, obj/item/attacking_item, mob/user)
+ SIGNAL_HANDLER
+
+ if(charge_plasma(attacking_item, user))
+ return COMPONENT_NO_AFTERATTACK
+ return NONE
+
+/obj/item/mod/core/plasma/proc/charge_plasma(obj/item/stack/plasma, mob/user)
+ var/charge_given = is_type_in_list(plasma, charger_list, zebra = TRUE)
+ if(!charge_given)
+ return FALSE
+ var/uses_needed = min(plasma.amount, ROUND_UP((max_charge_amount() - charge_amount()) / charge_given))
+ if(!plasma.use(uses_needed))
+ return FALSE
+ add_charge(uses_needed * charge_given)
+ balloon_alert(user, "core refueled")
+ return TRUE
diff --git a/code/modules/mod/mod_paint.dm b/code/modules/mod/mod_paint.dm
new file mode 100644
index 00000000000..aead577224b
--- /dev/null
+++ b/code/modules/mod/mod_paint.dm
@@ -0,0 +1,192 @@
+#define MODPAINT_MAX_COLOR_VALUE 1.25
+#define MODPAINT_MIN_COLOR_VALUE 0
+#define MODPAINT_MAX_SECTION_COLORS 2
+#define MODPAINT_MIN_SECTION_COLORS 0.25
+#define MODPAINT_MAX_OVERALL_COLORS 4
+#define MODPAINT_MIN_OVERALL_COLORS 1.5
+
+/obj/item/mod/paint
+ name = "MOD paint kit"
+ desc = "This kit will repaint your MODsuit to something unique."
+ icon = 'icons/obj/clothing/modsuit/mod_construction.dmi'
+ icon_state = "paintkit"
+ var/obj/item/mod/control/editing_mod
+ var/atom/movable/screen/map_view/proxy_view
+ var/list/current_color
+
+/obj/item/mod/paint/Initialize(mapload)
+ . = ..()
+ current_color = color_matrix_identity()
+
+/obj/item/mod/paint/examine(mob/user)
+ . = ..()
+ . += span_notice("Left-click a MODsuit to change skin.")
+ //. += span_notice("Right-click a MODsuit to recolor.")
+
+/obj/item/mod/paint/pre_attack(atom/attacked_atom, mob/living/user, params)
+ if(!istype(attacked_atom, /obj/item/mod/control))
+ return ..()
+ var/obj/item/mod/control/mod = attacked_atom
+ if(mod.active || mod.activating)
+ balloon_alert(user, "suit is active!")
+ return TRUE
+ paint_skin(mod, user)
+
+/*obj/item/mod/paint/pre_attack_secondary(atom/attacked_atom, mob/living/user, params)
+ if(!istype(attacked_atom, /obj/item/mod/control))
+ return .()
+ var/obj/item/mod/control/mod = attacked_atom
+ if(mod.active || mod.activating)
+ balloon_alert(user, "suit is active!")
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+ if(editing_mod)
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+ editing_mod = mod
+ proxy_view = new()
+ proxy_view.generate_view("color_matrix_proxy_[REF(user.client)]")
+
+ proxy_view.appearance = editing_mod.appearance
+ proxy_view.color = null
+ proxy_view.display_to(user)
+ ui_interact(user)
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN*/
+
+/obj/item/mod/paint/ui_interact(mob/user, datum/tgui/ui)
+ if(!editing_mod)
+ return
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "MODpaint", name)
+ ui.open()
+
+/obj/item/mod/paint/ui_host()
+ return editing_mod
+
+/obj/item/mod/paint/ui_close(mob/user)
+ . = ..()
+ editing_mod = null
+ QDEL_NULL(proxy_view)
+ current_color = color_matrix_identity()
+
+/obj/item/mod/paint/ui_status(mob/user)
+ if(check_menu(editing_mod, user))
+ return ..()
+ return UI_CLOSE
+
+/obj/item/mod/paint/ui_static_data(mob/user)
+ var/list/data = list()
+ data["mapRef"] = proxy_view.assigned_map
+ return data
+
+/obj/item/mod/paint/ui_data(mob/user)
+ var/list/data = list()
+ data["currentColor"] = current_color
+ return data
+
+/obj/item/mod/paint/ui_act(action, list/params)
+ . = ..()
+ if(.)
+ return
+ switch(action)
+ if("transition_color")
+ current_color = params["color"]
+ animate(proxy_view, time = 0.5 SECONDS, color = current_color)
+ if("confirm")
+ if(length(current_color) != 20) //20 is the length of a matrix identity list
+ return
+ for(var/color_value in current_color)
+ if(isnum(color_value))
+ continue
+ return
+ var/total_color_value = 0
+ var/list/total_colors = current_color.Copy()
+ total_colors.Cut(13, length(total_colors)) // 13 to 20 are just a and c, dont want to count them
+ var/red_value = current_color[1] + current_color[5] + current_color[9] //rr + gr + br
+ var/green_value = current_color[2] + current_color[6] + current_color[10] //rg + gg + bg
+ var/blue_value = current_color[3] + current_color[7] + current_color[11] //rb + gb + bb
+ if(red_value > MODPAINT_MAX_SECTION_COLORS)
+ balloon_alert(usr, "total red too high! ([red_value*100]%/[MODPAINT_MAX_SECTION_COLORS*100]%)")
+ return
+ else if(red_value < MODPAINT_MIN_SECTION_COLORS)
+ balloon_alert(usr, "total red too low! ([red_value*100]%/[MODPAINT_MIN_SECTION_COLORS*100]%)")
+ return
+ if(green_value > MODPAINT_MAX_SECTION_COLORS)
+ balloon_alert(usr, "total green too high! ([green_value*100]%/[MODPAINT_MAX_SECTION_COLORS*100]%)")
+ return
+ else if(green_value < MODPAINT_MIN_SECTION_COLORS)
+ balloon_alert(usr, "total green too low! ([green_value*100]%/[MODPAINT_MIN_SECTION_COLORS*100]%)")
+ return
+ if(blue_value > MODPAINT_MAX_SECTION_COLORS)
+ balloon_alert(usr, "total blue too high! ([blue_value*100]%/[MODPAINT_MAX_SECTION_COLORS*100]%)")
+ return
+ else if(blue_value < MODPAINT_MIN_SECTION_COLORS)
+ balloon_alert(usr, "total blue too low! ([blue_value*100]%/[MODPAINT_MIN_SECTION_COLORS*100]%)")
+ return
+ for(var/color_value in total_colors)
+ total_color_value += color_value
+ if(color_value > MODPAINT_MAX_COLOR_VALUE)
+ balloon_alert(usr, "one of colors too high! ([color_value*100]%/[MODPAINT_MAX_COLOR_VALUE*100]%")
+ return
+ else if(color_value < MODPAINT_MIN_COLOR_VALUE)
+ balloon_alert(usr, "one of colors too low! ([color_value*100]%/[MODPAINT_MIN_COLOR_VALUE*100]%")
+ return
+ if(total_color_value > MODPAINT_MAX_OVERALL_COLORS)
+ balloon_alert(usr, "total colors too high! ([total_color_value*100]%/[MODPAINT_MAX_OVERALL_COLORS*100]%)")
+ return
+ else if(total_color_value < MODPAINT_MIN_OVERALL_COLORS)
+ balloon_alert(usr, "total colors too low! ([total_color_value*100]%/[MODPAINT_MIN_OVERALL_COLORS*100]%)")
+ return
+ editing_mod.set_mod_color(current_color)
+ SStgui.close_uis(src)
+
+/obj/item/mod/paint/proc/paint_skin(obj/item/mod/control/mod, mob/user)
+ if(length(mod.theme.skins) <= 1)
+ balloon_alert(user, "no alternate skins!")
+ return
+ var/list/skins = list()
+ for(var/mod_skin in mod.theme.skins)
+ skins[mod_skin] = image(icon = mod.icon, icon_state = "[mod_skin]-control")
+ var/pick = show_radial_menu(user, mod, skins, custom_check = CALLBACK(src, PROC_REF(check_menu), mod, user), require_near = TRUE)
+ if(!pick)
+ balloon_alert(user, "no skin picked!")
+ return
+ mod.set_mod_skin(pick)
+
+/obj/item/mod/paint/proc/check_menu(obj/item/mod/control/mod, mob/user)
+ if(user.incapacitated() || !user.is_holding(src) || !mod || mod.active || mod.activating)
+ return FALSE
+ return TRUE
+
+#undef MODPAINT_MAX_COLOR_VALUE
+#undef MODPAINT_MIN_COLOR_VALUE
+#undef MODPAINT_MAX_SECTION_COLORS
+#undef MODPAINT_MIN_SECTION_COLORS
+#undef MODPAINT_MAX_OVERALL_COLORS
+#undef MODPAINT_MIN_OVERALL_COLORS
+
+/obj/item/mod/skin_applier
+ name = "MOD skin applier"
+ desc = "This one-use skin applier will add a skin to MODsuits of a specific type."
+ icon = 'icons/obj/clothing/modsuit/mod_construction.dmi'
+ icon_state = "skinapplier"
+ var/skin = "civilian"
+ var/compatible_theme = /datum/mod_theme
+
+/obj/item/mod/skin_applier/Initialize(mapload)
+ . = ..()
+ name = "MOD [skin] skin applier"
+
+/obj/item/mod/skin_applier/pre_attack(atom/attacked_atom, mob/living/user, params)
+ if(!istype(attacked_atom, /obj/item/mod/control))
+ return ..()
+ var/obj/item/mod/control/mod = attacked_atom
+ if(mod.active || mod.activating)
+ balloon_alert(user, "suit is active!")
+ return TRUE
+ if(!istype(mod.theme, compatible_theme))
+ balloon_alert(user, "incompatible theme!")
+ return TRUE
+ mod.set_mod_skin(skin)
+ balloon_alert(user, "skin applied")
+ qdel(src)
+ return TRUE
diff --git a/code/modules/mod/mod_theme.dm b/code/modules/mod/mod_theme.dm
new file mode 100644
index 00000000000..2e6325df919
--- /dev/null
+++ b/code/modules/mod/mod_theme.dm
@@ -0,0 +1,1154 @@
+/// Global proc that sets up all MOD themes as singletons in a list and returns it.
+/proc/setup_mod_themes()
+ . = list()
+ for(var/path in typesof(/datum/mod_theme))
+ var/datum/mod_theme/new_theme = new path()
+ .[path] = new_theme
+
+/// MODsuit theme, instanced once and then used by MODsuits to grab various statistics.
+/datum/mod_theme
+ /// Theme name for the MOD.
+ var/name = "standard"
+ /// Description added to the MOD.
+ var/desc = "A MOD suit. Placeholder Desc"
+ /// Extended description on examine_more
+ var/extended_desc = "Placeholder Desc"
+ /// Default skin of the MOD.
+ var/default_skin = "standard"
+ /// The slot this mod theme fits on
+ var/slot_flags = ITEM_SLOT_BACK
+ /// Armor shared across the MOD parts.
+ var/armor = list("melee" = 10, "bullet" = 5, "laser" = 5, "energy" = 5, "bomb" = 0, "bio" = 100, "fire" = 25, "acid" = 25)
+ /// Resistance flags shared across the MOD parts.
+ var/resistance_flags = NONE
+ /// Atom flags shared across the MOD parts.
+ var/atom_flags = NONE
+ /// Max heat protection shared across the MOD parts.
+ var/max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT
+ /// Max cold protection shared across the MOD parts.
+ var/min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT
+ /// Siemens shared across the MOD parts.
+ var/siemens_coefficient = 0.5
+ /// How much modules can the MOD carry without malfunctioning.
+ var/complexity_max = DEFAULT_MAX_COMPLEXITY
+ /// How much battery power the MOD uses by just being on
+ var/charge_drain = DEFAULT_CHARGE_DRAIN
+ /// Slowdown of the MOD when not active.
+ var/slowdown_inactive = 1.25
+ /// Slowdown of the MOD when active.
+ var/slowdown_active = 0.75
+ /// Theme used by the MOD TGUI.
+ var/ui_theme = "ntos"
+ /// List of inbuilt modules. These are different from the pre-equipped suits, you should mainly use these for unremovable modules with 0 complexity.
+ var/list/inbuilt_modules = list()
+ /// Modules blacklisted from the MOD.
+ var/list/module_blacklist = list()
+ /// Allowed items in the chestplate's suit storage.
+ var/list/allowed_suit_storage = list(
+ /obj/item/flashlight,
+ /obj/item/tank/internals,
+ )
+ /// List of skins with their appropriate clothing flags.
+ var/list/skins = list(
+ "standard" = list(
+ HELMET_FLAGS = list(
+ UNSEALED_LAYER = NECK_LAYER,
+ UNSEALED_CLOTHING = SNUG_FIT,
+ SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE,
+ SEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT,
+ SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF,
+ ),
+ CHESTPLATE_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ SEALED_INVISIBILITY = HIDEJUMPSUIT,
+ ),
+ GAUNTLETS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ BOOTS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ ),
+ "civilian" = list(
+ HELMET_FLAGS = list(
+ UNSEALED_LAYER = null,
+ UNSEALED_CLOTHING = SNUG_FIT|THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT,
+ UNSEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF,
+ ),
+ CHESTPLATE_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ SEALED_INVISIBILITY = HIDEJUMPSUIT,
+ ),
+ GAUNTLETS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ BOOTS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ ),
+ )
+
+/datum/mod_theme/engineering
+ name = "engineering"
+ default_skin = "engineering"
+ armor = list("melee" = 10, "bullet" = 5, "laser" = 20, "energy" = 10, "bomb" = 10, "bio" = 100, "fire" = 100, "acid" = 25)
+ resistance_flags = FIRE_PROOF
+ max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT
+ siemens_coefficient = 0
+ slowdown_inactive = 1.5
+ slowdown_active = 1
+ allowed_suit_storage = list(
+ /obj/item/flashlight,
+ /obj/item/tank/internals,
+ /obj/item/construction/rcd,
+ /obj/item/storage/bag/construction,
+ )
+ skins = list(
+ "engineering" = list(
+ HELMET_FLAGS = list(
+ UNSEALED_LAYER = NECK_LAYER,
+ UNSEALED_CLOTHING = SNUG_FIT,
+ SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE,
+ UNSEALED_INVISIBILITY = HIDEFACIALHAIR,
+ SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT,
+ SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF,
+ ),
+ CHESTPLATE_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ SEALED_INVISIBILITY = HIDEJUMPSUIT,
+ ),
+ GAUNTLETS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ BOOTS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ ),
+ )
+
+/datum/mod_theme/atmospheric
+ name = "atmospheric"
+ default_skin = "atmospheric"
+ armor = list("melee" = 10, "bullet" = 5, "laser" = 10, "energy" = 15, "bomb" = 10, "bio" = 100, "fire" = 100, "acid" = 75)
+ resistance_flags = FIRE_PROOF
+ max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
+ slowdown_inactive = 1.5
+ slowdown_active = 1
+ allowed_suit_storage = list(
+ /obj/item/flashlight,
+ /obj/item/tank/internals,
+ /obj/item/analyzer,
+ /obj/item/t_scanner,
+ /obj/item/pipe_dispenser,
+ )
+ skins = list(
+ "atmospheric" = list(
+ HELMET_FLAGS = list(
+ UNSEALED_LAYER = NECK_LAYER,
+ UNSEALED_CLOTHING = SNUG_FIT,
+ SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE|BLOCK_GAS_SMOKE_EFFECT,
+ UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDESNOUT,
+ SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR,
+ UNSEALED_COVER = HEADCOVERSMOUTH,
+ SEALED_COVER = HEADCOVERSEYES|PEPPERPROOF,
+ ),
+ CHESTPLATE_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ SEALED_INVISIBILITY = HIDEJUMPSUIT,
+ ),
+ GAUNTLETS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ BOOTS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ ),
+ )
+
+/datum/mod_theme/advanced
+ name = "advanced"
+ default_skin = "advanced"
+ armor = list("melee" = 15, "bullet" = 5, "laser" = 20, "energy" = 15, "bomb" = 50, "bio" = 100, "fire" = 100, "acid" = 90)
+ resistance_flags = FIRE_PROOF
+ max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
+ siemens_coefficient = 0
+ slowdown_inactive = 1
+ slowdown_active = 0.5
+ inbuilt_modules = list(/obj/item/mod/module/magboot/advanced)
+ allowed_suit_storage = list(
+ /obj/item/flashlight,
+ /obj/item/tank/internals,
+ /obj/item/analyzer,
+ /obj/item/t_scanner,
+ /obj/item/pipe_dispenser,
+ /obj/item/construction/rcd,
+ /obj/item/storage/bag/construction,
+ /obj/item/melee/classic_baton/telescopic,
+ )
+ skins = list(
+ "advanced" = list(
+ HELMET_FLAGS = list(
+ UNSEALED_LAYER = NECK_LAYER,
+ UNSEALED_CLOTHING = SNUG_FIT,
+ SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE|BLOCK_GAS_SMOKE_EFFECT,
+ UNSEALED_INVISIBILITY = HIDEFACIALHAIR,
+ SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT,
+ SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF,
+ ),
+ CHESTPLATE_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ SEALED_INVISIBILITY = HIDEJUMPSUIT,
+ ),
+ GAUNTLETS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ BOOTS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ ),
+ )
+
+/datum/mod_theme/mining
+ name = "mining"
+ default_skin = "mining"
+ armor = list("melee" = 15, "bullet" = 5, "laser" = 5, "energy" = 5, "bomb" = 30, "bio" = 100, "fire" = 100, "acid" = 75)
+ resistance_flags = FIRE_PROOF|LAVA_PROOF
+ max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT
+ complexity_max = DEFAULT_MAX_COMPLEXITY - 5
+ charge_drain = DEFAULT_CHARGE_DRAIN * 2
+ allowed_suit_storage = list(
+ /obj/item/flashlight,
+ /obj/item/tank/internals,
+ /obj/item/resonator,
+ /obj/item/mining_scanner,
+ /obj/item/t_scanner/adv_mining_scanner,
+ /obj/item/pickaxe,
+ /obj/item/kinetic_crusher,
+ /obj/item/stack/ore/plasma,
+ /obj/item/storage/bag/ore,
+ )
+ inbuilt_modules = list()
+ skins = list(
+ "mining" = list(
+ HELMET_FLAGS = list(
+ UNSEALED_LAYER = null,
+ UNSEALED_CLOTHING = SNUG_FIT,
+ SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE,
+ UNSEALED_INVISIBILITY = HIDEEARS|HIDEHAIR,
+ SEALED_INVISIBILITY = HIDEMASK|HIDEEYES|HIDEFACE|HIDEFACIALHAIR|HIDESNOUT,
+ SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF,
+ ),
+ CHESTPLATE_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ SEALED_INVISIBILITY = HIDEJUMPSUIT,
+ ),
+ GAUNTLETS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ BOOTS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ ),
+ "asteroid" = list(
+ HELMET_FLAGS = list(
+ UNSEALED_LAYER = null,
+ UNSEALED_CLOTHING = SNUG_FIT|THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEEARS|HIDEHAIR|HIDESNOUT,
+ SEALED_INVISIBILITY = HIDEMASK|HIDEEYES|HIDEFACE,
+ SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF,
+ ),
+ CHESTPLATE_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ SEALED_INVISIBILITY = HIDEJUMPSUIT,
+ ),
+ GAUNTLETS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ BOOTS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ ),
+ )
+
+/datum/mod_theme/loader
+ name = "loader"
+ default_skin = "loader"
+ armor = list("melee" = 15, "bullet" = 5, "laser" = 5, "energy" = 5, "bomb" = 10, "bio" = 10, "fire" = 25, "acid" = 25)
+ max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT
+ min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT
+ siemens_coefficient = 0.25
+ complexity_max = DEFAULT_MAX_COMPLEXITY - 5
+ slowdown_inactive = 0.5
+ slowdown_active = 0
+ allowed_suit_storage = list(
+ /obj/item/flashlight,
+ /obj/item/tank/internals,
+ /obj/item/paper
+ )
+ inbuilt_modules = list(/obj/item/mod/module/clamp/loader, /obj/item/mod/module/magnet)
+ skins = list(
+ "loader" = list(
+ HELMET_FLAGS = list(
+ UNSEALED_LAYER = null,
+ UNSEALED_CLOTHING = SNUG_FIT|THICKMATERIAL,
+ UNSEALED_INVISIBILITY = HIDEEARS|HIDEHAIR,
+ SEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEMASK|HIDEEYES|HIDEFACE|HIDESNOUT,
+ SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF,
+ ),
+ CHESTPLATE_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ ),
+ GAUNTLETS_FLAGS = list(
+ SEALED_CLOTHING = THICKMATERIAL,
+ CAN_OVERSLOT = TRUE,
+ ),
+ BOOTS_FLAGS = list(
+ SEALED_CLOTHING = THICKMATERIAL,
+ CAN_OVERSLOT = TRUE,
+ ),
+ ),
+ )
+
+/datum/mod_theme/medical
+ name = "medical"
+ default_skin = "medical"
+ armor = list("melee" = 5, "bullet" = 5, "laser" = 5, "energy" = 5, "bomb" = 10, "bio" = 100, "fire" = 60, "acid" = 75)
+ charge_drain = DEFAULT_CHARGE_DRAIN * 1.5
+ slowdown_inactive = 1
+ slowdown_active = 0.5
+ allowed_suit_storage = list(
+ /obj/item/flashlight,
+ /obj/item/tank/internals,
+ /obj/item/healthanalyzer,
+ /obj/item/reagent_containers/dropper,
+ /obj/item/reagent_containers/glass/beaker,
+ /obj/item/reagent_containers/glass/bottle,
+ /obj/item/reagent_containers/hypospray,
+ /obj/item/reagent_containers/pill,
+ /obj/item/reagent_containers/syringe,
+ /obj/item/stack/medical,
+ /obj/item/sensor_device,
+ /obj/item/storage/pill_bottle,
+ /obj/item/storage/bag/chemistry,
+ /obj/item/storage/bag/bio,
+ )
+ skins = list(
+ "medical" = list(
+ HELMET_FLAGS = list(
+ UNSEALED_LAYER = NECK_LAYER,
+ UNSEALED_CLOTHING = SNUG_FIT,
+ SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE|BLOCK_GAS_SMOKE_EFFECT,
+ UNSEALED_INVISIBILITY = HIDEFACIALHAIR,
+ SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT,
+ SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF,
+ ),
+ CHESTPLATE_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ SEALED_INVISIBILITY = HIDEJUMPSUIT,
+ ),
+ GAUNTLETS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ BOOTS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ ),
+ "corpsman" = list(
+ HELMET_FLAGS = list(
+ UNSEALED_LAYER = NECK_LAYER,
+ UNSEALED_CLOTHING = SNUG_FIT,
+ SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE|BLOCK_GAS_SMOKE_EFFECT,
+ UNSEALED_INVISIBILITY = HIDEFACIALHAIR,
+ SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT,
+ SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF,
+ ),
+ CHESTPLATE_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ SEALED_INVISIBILITY = HIDEJUMPSUIT,
+ ),
+ GAUNTLETS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ BOOTS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ ),
+ )
+
+/datum/mod_theme/rescue
+ name = "rescue"
+ default_skin = "rescue"
+ armor = list("melee" = 10, "bullet" = 10, "laser" = 5, "energy" = 5, "bomb" = 10, "bio" = 100, "fire" = 100, "acid" = 100)
+ resistance_flags = FIRE_PROOF|ACID_PROOF
+ max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT
+ charge_drain = DEFAULT_CHARGE_DRAIN * 1.5
+ slowdown_inactive = 0.75
+ slowdown_active = 0.25
+ inbuilt_modules = list(/obj/item/mod/module/quick_carry/advanced)
+ allowed_suit_storage = list(
+ /obj/item/flashlight,
+ /obj/item/tank/internals,
+ /obj/item/healthanalyzer,
+ /obj/item/reagent_containers/dropper,
+ /obj/item/reagent_containers/glass/beaker,
+ /obj/item/reagent_containers/glass/bottle,
+ /obj/item/reagent_containers/hypospray,
+ /obj/item/reagent_containers/pill,
+ /obj/item/reagent_containers/syringe,
+ /obj/item/stack/medical,
+ /obj/item/sensor_device,
+ /obj/item/storage/pill_bottle,
+ /obj/item/storage/bag/chemistry,
+ /obj/item/storage/bag/bio,
+ /obj/item/melee/classic_baton/telescopic,
+ )
+ skins = list(
+ "rescue" = list(
+ HELMET_FLAGS = list(
+ UNSEALED_LAYER = NECK_LAYER,
+ UNSEALED_CLOTHING = SNUG_FIT,
+ SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE|BLOCK_GAS_SMOKE_EFFECT,
+ UNSEALED_INVISIBILITY = HIDEFACIALHAIR,
+ SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT,
+ SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF,
+ ),
+ CHESTPLATE_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ SEALED_INVISIBILITY = HIDEJUMPSUIT,
+ ),
+ GAUNTLETS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ BOOTS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ ),
+ )
+
+/datum/mod_theme/research
+ name = "research"
+ default_skin = "research"
+ armor = list("melee" = 20, "bullet" = 15, "laser" = 5, "energy" = 5, "bomb" = 100, "bio" = 100, "fire" = 100, "acid" = 100)
+ resistance_flags = FIRE_PROOF|ACID_PROOF
+ atom_flags = PREVENT_CONTENTS_EXPLOSION_1
+ max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT
+ complexity_max = DEFAULT_MAX_COMPLEXITY + 5
+ slowdown_inactive = 1.75
+ slowdown_active = 1.25
+ inbuilt_modules = list(/obj/item/mod/module/reagent_scanner/advanced)
+ allowed_suit_storage = list(
+ /obj/item/flashlight,
+ /obj/item/tank/internals,
+ /obj/item/analyzer,
+ /obj/item/dnainjector,
+ /obj/item/storage/bag/bio,
+ /obj/item/melee/classic_baton/telescopic,
+ )
+ skins = list(
+ "research" = list(
+ HELMET_FLAGS = list(
+ UNSEALED_LAYER = null,
+ UNSEALED_CLOTHING = SNUG_FIT|THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE|BLOCK_GAS_SMOKE_EFFECT,
+ UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT,
+ UNSEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF,
+ ),
+ CHESTPLATE_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ SEALED_INVISIBILITY = HIDEJUMPSUIT,
+ ),
+ GAUNTLETS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ BOOTS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ ),
+ )
+
+/datum/mod_theme/security
+ name = "security"
+ default_skin = "security"
+ armor = list("melee" = 15, "bullet" = 15, "laser" = 15, "energy" = 15, "bomb" = 25, "bio" = 100, "fire" = 75, "acid" = 75)
+ complexity_max = DEFAULT_MAX_COMPLEXITY - 3
+ slowdown_inactive = 1
+ slowdown_active = 0.5
+ allowed_suit_storage = list(
+ /obj/item/flashlight,
+ /obj/item/tank/internals,
+ /obj/item/ammo_box,
+ /obj/item/ammo_casing,
+ /obj/item/reagent_containers/spray/pepper,
+ /obj/item/restraints/handcuffs,
+ /obj/item/assembly/flash,
+ /obj/item/melee/baton,
+ )
+ skins = list(
+ "security" = list(
+ HELMET_FLAGS = list(
+ UNSEALED_LAYER = null,
+ UNSEALED_CLOTHING = SNUG_FIT|THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEEARS|HIDEHAIR|HIDESNOUT,
+ SEALED_INVISIBILITY = HIDEMASK|HIDEEYES|HIDEFACE,
+ UNSEALED_COVER = HEADCOVERSMOUTH,
+ SEALED_COVER = HEADCOVERSEYES|PEPPERPROOF,
+ ),
+ CHESTPLATE_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ SEALED_INVISIBILITY = HIDEJUMPSUIT,
+ ),
+ GAUNTLETS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ BOOTS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ ),
+ )
+
+/datum/mod_theme/safeguard
+ name = "safeguard"
+ default_skin = "safeguard"
+ armor = list("melee" = 15, "bullet" = 15, "laser" = 15, "energy" = 15, "bomb" = 40, "bio" = 100, "fire" = 100, "acid" = 95)
+ resistance_flags = FIRE_PROOF
+ max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT
+ slowdown_inactive = 0.75
+ slowdown_active = 0.25
+ allowed_suit_storage = list(
+ /obj/item/flashlight,
+ /obj/item/tank/internals,
+ /obj/item/ammo_box,
+ /obj/item/ammo_casing,
+ /obj/item/reagent_containers/spray/pepper,
+ /obj/item/restraints/handcuffs,
+ /obj/item/assembly/flash,
+ /obj/item/melee/baton,
+ )
+ skins = list(
+ "safeguard" = list(
+ HELMET_FLAGS = list(
+ UNSEALED_LAYER = null,
+ UNSEALED_CLOTHING = SNUG_FIT|THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT,
+ UNSEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF,
+ ),
+ CHESTPLATE_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ SEALED_INVISIBILITY = HIDEJUMPSUIT,
+ ),
+ GAUNTLETS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ BOOTS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ ),
+ )
+
+/datum/mod_theme/magnate
+ name = "magnate"
+ default_skin = "magnate"
+ armor = list("melee" = 20, "bullet" = 15, "laser" = 15, "energy" = 15, "bomb" = 50, "bio" = 100, "fire" = 100, "acid" = 100)
+ resistance_flags = FIRE_PROOF|ACID_PROOF
+ atom_flags = PREVENT_CONTENTS_EXPLOSION_1
+ max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
+ siemens_coefficient = 0
+ complexity_max = DEFAULT_MAX_COMPLEXITY + 5
+ slowdown_inactive = 0.75
+ slowdown_active = 0.25
+ allowed_suit_storage = list(
+ /obj/item/flashlight,
+ /obj/item/tank/internals,
+ /obj/item/ammo_box,
+ /obj/item/ammo_casing,
+ /obj/item/restraints/handcuffs,
+ /obj/item/assembly/flash,
+ /obj/item/melee/baton,
+ )
+ skins = list(
+ "magnate" = list(
+ HELMET_FLAGS = list(
+ UNSEALED_LAYER = NECK_LAYER,
+ UNSEALED_CLOTHING = SNUG_FIT,
+ SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE,
+ UNSEALED_INVISIBILITY = HIDEFACIALHAIR,
+ SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT,
+ SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF,
+ ),
+ CHESTPLATE_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ SEALED_INVISIBILITY = HIDEJUMPSUIT,
+ ),
+ GAUNTLETS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ BOOTS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ ),
+ )
+
+/datum/mod_theme/syndicate
+ name = "syndicate"
+ default_skin = "syndicate"
+ armor = list("melee" = 15, "bullet" = 20, "laser" = 15, "energy" = 15, "bomb" = 35, "bio" = 100, "fire" = 50, "acid" = 90)
+ atom_flags = PREVENT_CONTENTS_EXPLOSION_1
+ max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT
+ siemens_coefficient = 0
+ slowdown_inactive = 1
+ slowdown_active = 0.5
+ ui_theme = "syndicate"
+ inbuilt_modules = list(/obj/item/mod/module/armor_booster)
+ allowed_suit_storage = list(
+ /obj/item/flashlight,
+ /obj/item/tank/internals,
+ /obj/item/ammo_box,
+ /obj/item/ammo_casing,
+ /obj/item/restraints/handcuffs,
+ /obj/item/assembly/flash,
+ /obj/item/melee/baton,
+ /obj/item/melee/transforming/energy/sword,
+ /obj/item/shield/energy,
+ )
+ skins = list(
+ "syndicate" = list(
+ HELMET_FLAGS = list(
+ UNSEALED_LAYER = NECK_LAYER,
+ UNSEALED_CLOTHING = SNUG_FIT,
+ SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE,
+ UNSEALED_INVISIBILITY = HIDEFACIALHAIR,
+ SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT,
+ SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF,
+ ),
+ CHESTPLATE_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ SEALED_INVISIBILITY = HIDEJUMPSUIT,
+ ),
+ GAUNTLETS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ BOOTS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ ),
+ )
+
+/datum/mod_theme/elite
+ name = "elite"
+ default_skin = "elite"
+ armor = list("melee" = 35, "bullet" = 30, "laser" = 35, "energy" = 35, "bomb" = 55, "bio" = 100, "fire" = 100, "acid" = 100)
+ resistance_flags = FIRE_PROOF|ACID_PROOF
+ atom_flags = PREVENT_CONTENTS_EXPLOSION_1
+ max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
+ siemens_coefficient = 0
+ slowdown_inactive = 1
+ slowdown_active = 0.5
+ ui_theme = "syndicate"
+ inbuilt_modules = list(/obj/item/mod/module/armor_booster)
+ allowed_suit_storage = list(
+ /obj/item/flashlight,
+ /obj/item/tank/internals,
+ /obj/item/ammo_box,
+ /obj/item/ammo_casing,
+ /obj/item/restraints/handcuffs,
+ /obj/item/assembly/flash,
+ /obj/item/melee/baton,
+ /obj/item/melee/transforming/energy/sword,
+ /obj/item/shield/energy,
+ )
+ skins = list(
+ "elite" = list(
+ HELMET_FLAGS = list(
+ UNSEALED_LAYER = null,
+ UNSEALED_CLOTHING = SNUG_FIT,
+ SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE|BLOCK_GAS_SMOKE_EFFECT,
+ UNSEALED_INVISIBILITY = HIDEFACIALHAIR,
+ SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT,
+ SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF,
+ ),
+ CHESTPLATE_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ SEALED_INVISIBILITY = HIDEJUMPSUIT,
+ ),
+ GAUNTLETS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ BOOTS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ ),
+ )
+
+/datum/mod_theme/ninja
+ name = "ninja"
+ default_skin = "ninja"
+ armor = list("melee" = 40, "bullet" = 30, "laser" = 20, "energy" = 30, "bomb" = 30, "bio" = 100, "fire" = 100, "acid" = 100)
+ resistance_flags = LAVA_PROOF|FIRE_PROOF|ACID_PROOF
+ charge_drain = DEFAULT_CHARGE_DRAIN * 0.5
+ siemens_coefficient = 0
+ slowdown_inactive = 0.5
+ slowdown_active = 0
+ ui_theme = "hackerman"
+ inbuilt_modules = list(/obj/item/mod/module/welding/camera_vision, /obj/item/mod/module/hacker, /obj/item/mod/module/weapon_recall, /obj/item/mod/module/adrenaline_boost, /obj/item/mod/module/energy_net)
+ allowed_suit_storage = list(
+ /obj/item/flashlight,
+ /obj/item/tank/internals,
+ /obj/item/gun,
+ /obj/item/ammo_box,
+ /obj/item/ammo_casing,
+ /obj/item/melee/baton,
+ /obj/item/restraints/handcuffs,
+ )
+ skins = list(
+ "ninja" = list(
+ HELMET_FLAGS = list(
+ UNSEALED_LAYER = null,
+ UNSEALED_CLOTHING = SNUG_FIT|THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ UNSEALED_INVISIBILITY = HIDEEARS|HIDEHAIR,
+ SEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEMASK|HIDEEYES|HIDEFACE|HIDESNOUT,
+ SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF,
+ ),
+ CHESTPLATE_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ SEALED_INVISIBILITY = HIDEJUMPSUIT,
+ ),
+ GAUNTLETS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ BOOTS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ ),
+ )
+
+/datum/mod_theme/prototype
+ name = "prototype"
+ default_skin = "prototype"
+ armor = list("melee" = 20, "bullet" = 5, "laser" = 10, "energy" = 10, "bomb" = 50, "bio" = 100, "fire" = 100, "acid" = 75)
+ resistance_flags = FIRE_PROOF
+ siemens_coefficient = 0
+ complexity_max = DEFAULT_MAX_COMPLEXITY + 5
+ charge_drain = DEFAULT_CHARGE_DRAIN * 2
+ slowdown_inactive = 2
+ slowdown_active = 1.5
+ ui_theme = "hackerman"
+ //inbuilt_modules = list(/obj/item/mod/module/anomaly_locked/kinesis/prebuilt/prototype)
+ allowed_suit_storage = list(
+ /obj/item/flashlight,
+ /obj/item/tank/internals,
+ /obj/item/analyzer,
+ /obj/item/t_scanner,
+ /obj/item/pipe_dispenser,
+ /obj/item/construction/rcd,
+ )
+ skins = list(
+ "prototype" = list(
+ HELMET_FLAGS = list(
+ UNSEALED_LAYER = null,
+ UNSEALED_CLOTHING = SNUG_FIT|THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT,
+ UNSEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF,
+ ),
+ CHESTPLATE_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ SEALED_INVISIBILITY = HIDEJUMPSUIT,
+ ),
+ GAUNTLETS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ BOOTS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ ),
+ )
+
+/datum/mod_theme/responsory
+ name = "responsory"
+ default_skin = "responsory"
+ armor = list("melee" = 50, "bullet" = 40, "laser" = 50, "energy" = 50, "bomb" = 50, "bio" = 100, "fire" = 100, "acid" = 90)
+ atom_flags = PREVENT_CONTENTS_EXPLOSION_1
+ resistance_flags = FIRE_PROOF
+ max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
+ siemens_coefficient = 0
+ slowdown_inactive = 0.5
+ slowdown_active = 0
+ allowed_suit_storage = list(
+ /obj/item/flashlight,
+ /obj/item/tank/internals,
+ /obj/item/ammo_box,
+ /obj/item/ammo_casing,
+ /obj/item/restraints/handcuffs,
+ /obj/item/assembly/flash,
+ /obj/item/melee/baton,
+ )
+ skins = list(
+ "responsory" = list(
+ HELMET_FLAGS = list(
+ UNSEALED_LAYER = NECK_LAYER,
+ UNSEALED_CLOTHING = SNUG_FIT,
+ SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE|BLOCK_GAS_SMOKE_EFFECT,
+ UNSEALED_INVISIBILITY = HIDEFACIALHAIR,
+ SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT,
+ SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF,
+ ),
+ CHESTPLATE_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ SEALED_INVISIBILITY = HIDEJUMPSUIT,
+ ),
+ GAUNTLETS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ BOOTS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ ),
+ "inquisitory" = list(
+ HELMET_FLAGS = list(
+ UNSEALED_LAYER = null,
+ UNSEALED_CLOTHING = SNUG_FIT|THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE|BLOCK_GAS_SMOKE_EFFECT,
+ UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT,
+ UNSEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF,
+ ),
+ CHESTPLATE_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ SEALED_INVISIBILITY = HIDEJUMPSUIT,
+ ),
+ GAUNTLETS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ BOOTS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ ),
+ )
+
+/datum/mod_theme/apocryphal
+ name = "apocryphal"
+ default_skin = "apocryphal"
+ armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 60, "bomb" = 100, "bio" = 100, "fire" = 100, "acid" = 100)
+ resistance_flags = FIRE_PROOF|ACID_PROOF
+ atom_flags = PREVENT_CONTENTS_EXPLOSION_1
+ max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
+ siemens_coefficient = 0
+ complexity_max = DEFAULT_MAX_COMPLEXITY + 10
+ allowed_suit_storage = list(
+ /obj/item/flashlight,
+ /obj/item/tank/internals,
+ /obj/item/ammo_box,
+ /obj/item/ammo_casing,
+ /obj/item/restraints/handcuffs,
+ /obj/item/assembly/flash,
+ /obj/item/melee/baton,
+ /obj/item/melee/transforming/energy/sword,
+ /obj/item/shield/energy,
+ )
+ skins = list(
+ "apocryphal" = list(
+ HELMET_FLAGS = list(
+ UNSEALED_LAYER = null,
+ UNSEALED_CLOTHING = SNUG_FIT|THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ UNSEALED_INVISIBILITY = HIDEEARS|HIDEHAIR,
+ SEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEMASK|HIDEEYES|HIDEFACE|HIDESNOUT,
+ SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF,
+ ),
+ CHESTPLATE_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ SEALED_INVISIBILITY = HIDEJUMPSUIT,
+ ),
+ GAUNTLETS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ BOOTS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ ),
+ )
+
+/datum/mod_theme/corporate
+ name = "corporate"
+ default_skin = "corporate"
+ armor = list("melee" = 50, "bullet" = 40, "laser" = 50, "energy" = 50, "bomb" = 50, "bio" = 100, "fire" = 100, "acid" = 100)
+ resistance_flags = FIRE_PROOF|ACID_PROOF
+ atom_flags = PREVENT_CONTENTS_EXPLOSION_1
+ max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
+ siemens_coefficient = 0
+ slowdown_inactive = 0.5
+ slowdown_active = 0
+ allowed_suit_storage = list(
+ /obj/item/flashlight,
+ /obj/item/tank/internals,
+ /obj/item/ammo_box,
+ /obj/item/ammo_casing,
+ /obj/item/restraints/handcuffs,
+ /obj/item/assembly/flash,
+ /obj/item/melee/baton,
+ )
+ skins = list(
+ "corporate" = list(
+ HELMET_FLAGS = list(
+ UNSEALED_LAYER = null,
+ UNSEALED_CLOTHING = SNUG_FIT|THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEEARS|HIDEHAIR|HIDESNOUT,
+ SEALED_INVISIBILITY = HIDEMASK|HIDEEYES|HIDEFACE,
+ SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF,
+ ),
+ CHESTPLATE_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ SEALED_INVISIBILITY = HIDEJUMPSUIT,
+ ),
+ GAUNTLETS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ BOOTS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ ),
+ )
+
+/datum/mod_theme/chrono
+ name = "chrono"
+ default_skin = "chrono"
+ armor = list("melee" = 60, "bullet" = 60, "laser" = 60, "energy" = 60, "bomb" = 30, "bio" = 100, "fire" = 100, "acid" = 100)
+ resistance_flags = FIRE_PROOF|ACID_PROOF
+ max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT
+ complexity_max = DEFAULT_MAX_COMPLEXITY - 10
+ slowdown_inactive = 0
+ slowdown_active = 0
+ allowed_suit_storage = list(
+ /obj/item/flashlight,
+ /obj/item/tank/internals,
+ /obj/item/restraints/handcuffs,
+ )
+ skins = list(
+ "chrono" = list(
+ HELMET_FLAGS = list(
+ UNSEALED_LAYER = NECK_LAYER,
+ UNSEALED_CLOTHING = SNUG_FIT,
+ SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE|BLOCK_GAS_SMOKE_EFFECT,
+ UNSEALED_INVISIBILITY = HIDEFACIALHAIR,
+ SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT,
+ SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF,
+ ),
+ CHESTPLATE_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ SEALED_INVISIBILITY = HIDEJUMPSUIT,
+ ),
+ GAUNTLETS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ BOOTS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ ),
+ )
+
+/datum/mod_theme/debug
+ name = "debug"
+ default_skin = "debug"
+ armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 100, "bio" = 100, "fire" = 100, "acid" = 100)
+ resistance_flags = FIRE_PROOF|ACID_PROOF
+ atom_flags = PREVENT_CONTENTS_EXPLOSION_1
+ max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT
+ complexity_max = 50
+ siemens_coefficient = 0
+ slowdown_inactive = 0.5
+ slowdown_active = 0
+ allowed_suit_storage = list(
+ /obj/item/flashlight,
+ /obj/item/tank/internals,
+ /obj/item/gun,
+ )
+ skins = list(
+ "debug" = list(
+ HELMET_FLAGS = list(
+ UNSEALED_LAYER = null,
+ UNSEALED_CLOTHING = SNUG_FIT|THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE|BLOCK_GAS_SMOKE_EFFECT,
+ UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEEARS|HIDEHAIR|HIDESNOUT,
+ SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE,
+ UNSEALED_COVER = HEADCOVERSMOUTH,
+ SEALED_COVER = HEADCOVERSEYES|PEPPERPROOF,
+ ),
+ CHESTPLATE_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ SEALED_INVISIBILITY = HIDEJUMPSUIT,
+ ),
+ GAUNTLETS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ BOOTS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL,
+ SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ ),
+ )
+
+/datum/mod_theme/administrative
+ name = "administrative"
+ default_skin = "debug"
+ armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 100, "bio" = 100, "fire" = 100, "acid" = 100)
+ resistance_flags = INDESTRUCTIBLE|LAVA_PROOF|FIRE_PROOF|UNACIDABLE|ACID_PROOF
+ atom_flags = PREVENT_CONTENTS_EXPLOSION_1
+ max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
+ complexity_max = 1000
+ charge_drain = DEFAULT_CHARGE_DRAIN * 0
+ siemens_coefficient = 0
+ slowdown_inactive = 0
+ slowdown_active = 0
+ allowed_suit_storage = list(
+ /obj/item/flashlight,
+ /obj/item/tank/internals,
+ /obj/item/gun,
+ )
+ skins = list(
+ "debug" = list(
+ HELMET_FLAGS = list(
+ UNSEALED_LAYER = null,
+ UNSEALED_CLOTHING = SNUG_FIT|THICKMATERIAL|STOPSPRESSUREDAMAGE|BLOCK_GAS_SMOKE_EFFECT,
+ UNSEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEEARS|HIDEHAIR|HIDESNOUT,
+ SEALED_INVISIBILITY = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE,
+ UNSEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF,
+ ),
+ CHESTPLATE_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE|BLOCKS_SHOVE_KNOCKDOWN,
+ SEALED_INVISIBILITY = HIDEJUMPSUIT,
+ ),
+ GAUNTLETS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ BOOTS_FLAGS = list(
+ UNSEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE,
+ CAN_OVERSLOT = TRUE,
+ ),
+ ),
+ )
diff --git a/code/modules/mod/mod_types.dm b/code/modules/mod/mod_types.dm
new file mode 100644
index 00000000000..6cd33197e42
--- /dev/null
+++ b/code/modules/mod/mod_types.dm
@@ -0,0 +1,331 @@
+/obj/item/mod/control/pre_equipped
+ /// The skin we apply to the suit, defaults to the default_skin of the theme.
+ var/applied_skin
+ /// The MOD core we apply to the suit.
+ var/applied_core = /obj/item/mod/core/standard
+ /// The cell we apply to the core. Only applies to standard core suits.
+ var/applied_cell = /obj/item/stock_parts/cell/high
+
+/obj/item/mod/control/pre_equipped/Initialize(mapload, new_theme, new_skin, new_core)
+ new_skin = applied_skin
+ new_core = new applied_core(src)
+ if(istype(new_core, /obj/item/mod/core/standard))
+ var/obj/item/mod/core/standard/cell_core = new_core
+ cell_core.cell = new applied_cell()
+ return ..()
+
+/obj/item/mod/control/pre_equipped/standard
+ initial_modules = list(
+ /obj/item/mod/module/storage,
+ /obj/item/mod/module/welding,
+ /obj/item/mod/module/flashlight,
+ )
+
+/obj/item/mod/control/pre_equipped/engineering
+ theme = /datum/mod_theme/engineering
+ initial_modules = list(
+ /obj/item/mod/module/storage,
+ /obj/item/mod/module/welding,
+ /obj/item/mod/module/flashlight,
+ /obj/item/mod/module/magboot,
+ )
+
+/obj/item/mod/control/pre_equipped/atmospheric
+ theme = /datum/mod_theme/atmospheric
+ initial_modules = list(
+ /obj/item/mod/module/storage,
+ /obj/item/mod/module/welding,
+ /obj/item/mod/module/flashlight,
+ /obj/item/mod/module/t_ray,
+ )
+
+/obj/item/mod/control/pre_equipped/advanced
+ theme = /datum/mod_theme/advanced
+ applied_cell = /obj/item/stock_parts/cell/super
+ initial_modules = list(
+ /obj/item/mod/module/storage/large_capacity,
+ /obj/item/mod/module/welding,
+ /obj/item/mod/module/flashlight,
+ /obj/item/mod/module/jetpack,
+ )
+
+/obj/item/mod/control/pre_equipped/loader
+ theme = /datum/mod_theme/loader
+ initial_modules = list(
+ /obj/item/mod/module/storage/large_capacity,
+ /obj/item/mod/module/flashlight,
+ /obj/item/mod/module/paper_dispenser,
+ /obj/item/mod/module/stamp,
+ )
+
+/obj/item/mod/control/pre_equipped/mining
+ theme = /datum/mod_theme/mining
+ applied_core = /obj/item/mod/core/plasma
+ initial_modules = list(
+ /obj/item/mod/module/storage,
+ /obj/item/mod/module/gps,
+ /obj/item/mod/module/orebag,
+ /obj/item/mod/module/clamp,
+ /obj/item/mod/module/drill,
+ )
+
+/obj/item/mod/control/pre_equipped/medical
+ theme = /datum/mod_theme/medical
+ initial_modules = list(
+ /obj/item/mod/module/storage,
+ /obj/item/mod/module/flashlight,
+ /obj/item/mod/module/health_analyzer,
+ /obj/item/mod/module/quick_carry,
+ )
+
+/obj/item/mod/control/pre_equipped/rescue
+ theme = /datum/mod_theme/rescue
+ applied_cell = /obj/item/stock_parts/cell/super
+ initial_modules = list(
+ /obj/item/mod/module/storage/large_capacity,
+ /obj/item/mod/module/flashlight,
+ /obj/item/mod/module/health_analyzer,
+ /obj/item/mod/module/injector,
+ )
+
+/obj/item/mod/control/pre_equipped/research
+ theme = /datum/mod_theme/research
+ applied_cell = /obj/item/stock_parts/cell/super
+ initial_modules = list(
+ /obj/item/mod/module/storage/large_capacity,
+ /obj/item/mod/module/welding,
+ /obj/item/mod/module/flashlight,
+ //obj/item/mod/module/circuit,
+ /obj/item/mod/module/t_ray,
+ )
+
+/obj/item/mod/control/pre_equipped/security
+ theme = /datum/mod_theme/security
+ initial_modules = list(
+ /obj/item/mod/module/storage,
+ /obj/item/mod/module/magnetic_harness,
+ /obj/item/mod/module/flashlight,
+ )
+
+/obj/item/mod/control/pre_equipped/safeguard
+ theme = /datum/mod_theme/safeguard
+ applied_cell = /obj/item/stock_parts/cell/super
+ initial_modules = list(
+ /obj/item/mod/module/storage/large_capacity,
+ /obj/item/mod/module/magnetic_harness,
+ /obj/item/mod/module/flashlight,
+ /obj/item/mod/module/jetpack,
+ /obj/item/mod/module/megaphone,
+ )
+
+/obj/item/mod/control/pre_equipped/magnate
+ theme = /datum/mod_theme/magnate
+ applied_cell = /obj/item/stock_parts/cell/hyper
+ initial_modules = list(
+ /obj/item/mod/module/storage/large_capacity,
+ /obj/item/mod/module/hat_stabilizer,
+ /obj/item/mod/module/magnetic_harness,
+ /obj/item/mod/module/jetpack/advanced,
+ )
+
+/obj/item/mod/control/pre_equipped/traitor
+ theme = /datum/mod_theme/syndicate
+ applied_cell = /obj/item/stock_parts/cell/super
+ initial_modules = list(
+ /obj/item/mod/module/storage/syndicate,
+ /obj/item/mod/module/emp_shield,
+ /obj/item/mod/module/magnetic_harness,
+ /obj/item/mod/module/jetpack,
+ /obj/item/mod/module/flashlight,
+ /obj/item/mod/module/dna_lock,
+ )
+
+/obj/item/mod/control/pre_equipped/traitor_elite
+ theme = /datum/mod_theme/elite
+ applied_cell = /obj/item/stock_parts/cell/bluespace
+ initial_modules = list(
+ /obj/item/mod/module/storage/syndicate,
+ /obj/item/mod/module/emp_shield,
+ /obj/item/mod/module/magnetic_harness,
+ /obj/item/mod/module/jetpack/advanced,
+ /obj/item/mod/module/flashlight,
+ /obj/item/mod/module/dna_lock,
+ )
+
+/obj/item/mod/control/pre_equipped/nuclear
+ theme = /datum/mod_theme/syndicate
+ applied_cell = /obj/item/stock_parts/cell/hyper
+ req_access = list(ACCESS_SYNDICATE)
+ initial_modules = list(
+ /obj/item/mod/module/storage/syndicate,
+ /obj/item/mod/module/emp_shield,
+ /obj/item/mod/module/magnetic_harness,
+ /obj/item/mod/module/jetpack/advanced,
+ /obj/item/mod/module/flashlight,
+ )
+
+/obj/item/mod/control/pre_equipped/elite
+ theme = /datum/mod_theme/elite
+ applied_cell = /obj/item/stock_parts/cell/bluespace
+ req_access = list(ACCESS_SYNDICATE)
+ initial_modules = list(
+ /obj/item/mod/module/storage/syndicate,
+ /obj/item/mod/module/emp_shield,
+ /obj/item/mod/module/magnetic_harness,
+ /obj/item/mod/module/jetpack/advanced,
+ /obj/item/mod/module/flashlight,
+ )
+
+/obj/item/mod/control/pre_equipped/elite/flamethrower
+ initial_modules = list(
+ /obj/item/mod/module/storage/syndicate,
+ /obj/item/mod/module/emp_shield,
+ /obj/item/mod/module/magnetic_harness,
+ /obj/item/mod/module/thermal_regulator,
+ /obj/item/mod/module/jetpack/advanced,
+ /obj/item/mod/module/flashlight,
+ /obj/item/mod/module/flamethrower,
+ )
+
+/obj/item/mod/control/pre_equipped/ninja
+ theme = /datum/mod_theme/ninja
+ applied_cell = /obj/item/stock_parts/cell/super
+ initial_modules = list(
+ /obj/item/mod/module/storage,
+ /obj/item/mod/module/noslip,
+ /obj/item/mod/module/status_readout,
+ /obj/item/mod/module/stealth/ninja,
+ /obj/item/mod/module/dispenser/ninja,
+ /obj/item/mod/module/dna_lock/reinforced,
+ /obj/item/mod/module/emp_shield/pulse,
+ )
+
+/obj/item/mod/control/pre_equipped/prototype
+ theme = /datum/mod_theme/prototype
+ req_access = list(ACCESS_AWAY_GENERAL)
+ initial_modules = list(
+ /obj/item/mod/module/storage,
+ /obj/item/mod/module/welding,
+ /obj/item/mod/module/flashlight,
+ /obj/item/mod/module/tether,
+ )
+
+/obj/item/mod/control/pre_equipped/responsory
+ theme = /datum/mod_theme/responsory
+ applied_cell = /obj/item/stock_parts/cell/hyper
+ req_access = list(ACCESS_CENT_GENERAL)
+ initial_modules = list(
+ /obj/item/mod/module/storage/large_capacity,
+ /obj/item/mod/module/welding,
+ /obj/item/mod/module/emp_shield,
+ /obj/item/mod/module/magnetic_harness,
+ /obj/item/mod/module/flashlight,
+ )
+ /// The insignia type, insignias show what sort of member of the ERT you're dealing with.
+ var/insignia_type = /obj/item/mod/module/insignia
+ /// Additional module we add, as a treat.
+ var/additional_module = /obj/item/mod/module
+
+/obj/item/mod/control/pre_equipped/responsory/Initialize(mapload, new_theme, new_skin, new_core)
+ initial_modules.Insert(1, insignia_type)
+ initial_modules.Add(additional_module)
+ return ..()
+
+/obj/item/mod/control/pre_equipped/responsory/commander
+ insignia_type = /obj/item/mod/module/insignia/commander
+ additional_module = /obj/item/mod/module/power_kick
+
+/obj/item/mod/control/pre_equipped/responsory/security
+ insignia_type = /obj/item/mod/module/insignia/security
+ additional_module = /obj/item/mod/module/megaphone
+
+/obj/item/mod/control/pre_equipped/responsory/engineer
+ insignia_type = /obj/item/mod/module/insignia/engineer
+ additional_module = /obj/item/mod/module/magboot
+
+/obj/item/mod/control/pre_equipped/responsory/medic
+ insignia_type = /obj/item/mod/module/insignia/medic
+ additional_module = /obj/item/mod/module/quick_carry
+
+/obj/item/mod/control/pre_equipped/responsory/janitor
+ insignia_type = /obj/item/mod/module/insignia/janitor
+ additional_module = /obj/item/mod/module/clamp
+
+/obj/item/mod/control/pre_equipped/responsory/chaplain
+ insignia_type = /obj/item/mod/module/insignia/chaplain
+ additional_module = /obj/item/mod/module/injector
+
+/obj/item/mod/control/pre_equipped/apocryphal
+ theme = /datum/mod_theme/apocryphal
+ applied_cell = /obj/item/stock_parts/cell/bluespace
+ req_access = list(ACCESS_CENT_SPECOPS)
+ initial_modules = list(
+ /obj/item/mod/module/storage/bluespace,
+ /obj/item/mod/module/welding,
+ /obj/item/mod/module/emp_shield/advanced,
+ /obj/item/mod/module/magnetic_harness,
+ /obj/item/mod/module/jetpack,
+ )
+
+/obj/item/mod/control/pre_equipped/corporate
+ theme = /datum/mod_theme/corporate
+ applied_core = /obj/item/mod/core/infinite
+ req_access = list(ACCESS_CENT_SPECOPS)
+ initial_modules = list(
+ /obj/item/mod/module/storage/bluespace,
+ /obj/item/mod/module/hat_stabilizer,
+ /obj/item/mod/module/magnetic_harness,
+ /obj/item/mod/module/emp_shield/advanced,
+ )
+
+/*obj/item/mod/control/pre_equipped/chrono
+ theme = /datum/mod_theme/chrono
+ applied_core = /obj/item/mod/core/infinite
+ initial_modules = list(
+ /obj/item/mod/module/eradication_lock,
+ /obj/item/mod/module/emp_shield,
+ /obj/item/mod/module/timeline_jumper,
+ /obj/item/mod/module/timestopper,
+ /obj/item/mod/module/rewinder,
+ /obj/item/mod/module/tem,
+ /obj/item/mod/module/anomaly_locked/kinesis/plus,
+ )*/
+
+/obj/item/mod/control/pre_equipped/debug
+ theme = /datum/mod_theme/debug
+ applied_core = /obj/item/mod/core/infinite
+ initial_modules = list(
+ /obj/item/mod/module/storage/bluespace,
+ /obj/item/mod/module/welding,
+ /obj/item/mod/module/flashlight,
+ /obj/item/mod/module/tether,
+ /obj/item/mod/module/injector,
+ )
+
+/obj/item/mod/control/pre_equipped/administrative
+ theme = /datum/mod_theme/administrative
+ applied_core = /obj/item/mod/core/infinite
+ initial_modules = list(
+ /obj/item/mod/module/storage/bluespace,
+ /obj/item/mod/module/emp_shield/advanced,
+ /obj/item/mod/module/welding,
+ /obj/item/mod/module/stealth/ninja,
+ /obj/item/mod/module/quick_carry/advanced,
+ /obj/item/mod/module/magboot/advanced,
+ /obj/item/mod/module/jetpack/advanced,
+ //obj/item/mod/module/anomaly_locked/kinesis/plus,
+ )
+
+//these exist for the prefs menu
+/obj/item/mod/control/pre_equipped/empty
+
+/obj/item/mod/control/pre_equipped/empty/syndicate
+ theme = /datum/mod_theme/syndicate
+
+/obj/item/mod/control/pre_equipped/empty/elite
+ theme = /datum/mod_theme/elite
+
+/obj/item/mod/control/pre_equipped/empty/ninja
+ theme = /datum/mod_theme/ninja
+
+INITIALIZE_IMMEDIATE(/obj/item/mod/control/pre_equipped/empty)
diff --git a/code/modules/mod/mod_ui.dm b/code/modules/mod/mod_ui.dm
new file mode 100644
index 00000000000..3bfa930dea7
--- /dev/null
+++ b/code/modules/mod/mod_ui.dm
@@ -0,0 +1,86 @@
+/obj/item/mod/control/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "MODsuit", name)
+ ui.open()
+
+/obj/item/mod/control/ui_data(mob/user)
+ var/data = list()
+ data["interface_break"] = interface_break
+ data["malfunctioning"] = malfunctioning
+ data["open"] = open
+ data["active"] = active
+ data["locked"] = locked
+ data["complexity"] = complexity
+ data["selected_module"] = selected_module?.name
+ data["wearer_name"] = wearer ? (wearer.get_authentification_name("Unknown") || "Unknown") : "No Occupant"
+ data["wearer_job"] = wearer ? wearer.get_assignment("Unknown", "Unknown", FALSE) : "No Job"
+ //data[JOB_AI] = ai?.name
+ data["core"] = core?.name
+ data["charge"] = get_charge_percent()
+ data["modules"] = list()
+ for(var/obj/item/mod/module/module as anything in modules)
+ var/list/module_data = list(
+ "module_name" = module.name,
+ "description" = module.desc,
+ "module_type" = module.module_type,
+ "module_active" = module.active,
+ "pinned" = module.pinned_to[user],
+ "idle_power" = module.idle_power_cost,
+ "active_power" = module.active_power_cost,
+ "use_power" = module.use_power_cost,
+ "module_complexity" = module.complexity,
+ "cooldown_time" = module.cooldown_time,
+ "cooldown" = round(COOLDOWN_TIMELEFT(module, cooldown_timer), 1 SECONDS),
+ "id" = module.tgui_id,
+ "ref" = REF(module),
+ "configuration_data" = module.get_configuration()
+ )
+ module_data += module.add_ui_data()
+ data["modules"] += list(module_data)
+ return data
+
+/obj/item/mod/control/ui_static_data(mob/user)
+ var/data = list()
+ data["ui_theme"] = ui_theme
+ data["control"] = name
+ data["complexity_max"] = complexity_max
+ data["helmet"] = helmet?.name
+ data["chestplate"] = chestplate?.name
+ data["gauntlets"] = gauntlets?.name
+ data["boots"] = boots?.name
+ return data
+
+/obj/item/mod/control/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+ if(locked && !allowed(usr))
+ balloon_alert(usr, "insufficient access!")
+ playsound(src, 'sound/machines/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
+ return
+ if(malfunctioning && prob(75))
+ balloon_alert(usr, "button malfunctions!")
+ return
+ switch(action)
+ if("lock")
+ locked = !locked
+ balloon_alert(usr, "[locked ? "locked" : "unlocked"]!")
+ if("activate")
+ toggle_activate(usr)
+ if("select")
+ var/obj/item/mod/module/module = locate(params["ref"]) in modules
+ if(!module)
+ return
+ module.on_select()
+ if("configure")
+ var/obj/item/mod/module/module = locate(params["ref"]) in modules
+ if(!module)
+ return
+ module.configure_edit(params["key"], params["value"])
+ if("pin")
+ var/obj/item/mod/module/module = locate(params["ref"]) in modules
+ if(!module)
+ return
+ module.pin(usr)
+ return TRUE
diff --git a/code/modules/mod/modules/_module.dm b/code/modules/mod/modules/_module.dm
new file mode 100644
index 00000000000..7264066e5d1
--- /dev/null
+++ b/code/modules/mod/modules/_module.dm
@@ -0,0 +1,399 @@
+///MOD Module - A special device installed in a MODsuit allowing the suit to do new stuff.
+/obj/item/mod/module
+ name = "MOD module"
+ icon = 'icons/obj/clothing/modsuit/mod_modules.dmi'
+ icon_state = "module"
+ /// If it can be removed
+ var/removable = TRUE
+ /// If it's passive, togglable, usable or active
+ var/module_type = MODULE_PASSIVE
+ /// Is the module active
+ var/active = FALSE
+ /// How much space it takes up in the MOD
+ var/complexity = 0
+ /// Power use when idle
+ var/idle_power_cost = DEFAULT_CHARGE_DRAIN * 0
+ /// Power use when active
+ var/active_power_cost = DEFAULT_CHARGE_DRAIN * 0
+ /// Power use when used, we call it manually
+ var/use_power_cost = DEFAULT_CHARGE_DRAIN * 0
+ /// ID used by their TGUI
+ var/tgui_id
+ /// Linked MODsuit
+ var/obj/item/mod/control/mod
+ /// If we're an active module, what item are we?
+ var/obj/item/device
+ /// Overlay given to the user when the module is inactive
+ var/overlay_state_inactive
+ /// Overlay given to the user when the module is active
+ var/overlay_state_active
+ /// Overlay given to the user when the module is used, lasts until cooldown finishes
+ var/overlay_state_use
+ /// Icon file for the overlay.
+ var/overlay_icon_file = 'icons/mob/clothing/modsuit/mod_modules.dmi'
+ /// Does the overlay use the control unit's colors?
+ var/use_mod_colors = FALSE
+ /// What modules are we incompatible with?
+ var/list/incompatible_modules = list()
+ /// Cooldown after use
+ var/cooldown_time = 0
+ /// The mouse button needed to use this module
+ var/used_signal
+ /// List of REF()s mobs we are pinned to, linked with their action buttons
+ var/list/pinned_to = list()
+ /// If we're allowed to use this module while phased out.
+ var/allowed_in_phaseout = FALSE
+ /// If we're allowed to use this module while the suit is disabled.
+ var/allowed_inactive = FALSE
+ /// Timer for the cooldown
+ COOLDOWN_DECLARE(cooldown_timer)
+
+/obj/item/mod/module/Initialize(mapload)
+ . = ..()
+ if(module_type != MODULE_ACTIVE)
+ return
+ if(ispath(device))
+ device = new device(src)
+ ADD_TRAIT(device, TRAIT_NODROP, MOD_TRAIT)
+ RegisterSignal(device, COMSIG_PARENT_QDELETING, PROC_REF(on_device_deletion))
+ RegisterSignal(src, COMSIG_ATOM_EXITED, PROC_REF(on_exit))
+
+/obj/item/mod/module/Destroy()
+ mod?.uninstall(src)
+ if(device)
+ UnregisterSignal(device, COMSIG_PARENT_QDELETING)
+ QDEL_NULL(device)
+ return ..()
+
+/obj/item/mod/module/examine(mob/user)
+ . = ..()
+ if(HAS_TRAIT(user, TRAIT_DIAGNOSTIC_HUD))
+ . += span_notice("Complexity level: [complexity]")
+
+
+/// Called when the module is selected from the TGUI, radial or the action button
+/obj/item/mod/module/proc/on_select()
+ if(((!mod.active || mod.activating) && !allowed_inactive) || module_type == MODULE_PASSIVE)
+ if(mod.wearer)
+ balloon_alert(mod.wearer, "not active!")
+ return
+ if(module_type != MODULE_USABLE)
+ if(active)
+ on_deactivation()
+ else
+ on_activation()
+ else
+ on_use()
+ SEND_SIGNAL(mod, COMSIG_MOD_MODULE_SELECTED, src)
+
+/// Called when the module is activated
+/obj/item/mod/module/proc/on_activation()
+ if(!COOLDOWN_FINISHED(src, cooldown_timer))
+ balloon_alert(mod.wearer, "on cooldown!")
+ return FALSE
+ if(!mod.active || mod.activating || !mod.get_charge())
+ balloon_alert(mod.wearer, "unpowered!")
+ return FALSE
+ if(!allowed_in_phaseout && istype(mod.wearer.loc, /obj/effect/dummy/phased_mob))
+ //specifically a to_chat because the user is phased out.
+ to_chat(mod.wearer, span_warning("You cannot activate this right now."))
+ return FALSE
+ if(SEND_SIGNAL(src, COMSIG_MODULE_TRIGGERED) & MOD_ABORT_USE)
+ return FALSE
+ if(module_type == MODULE_ACTIVE)
+ if(mod.selected_module && !mod.selected_module.on_deactivation(display_message = FALSE))
+ return FALSE
+ mod.selected_module = src
+ if(device)
+ if(mod.wearer.put_in_hands(device))
+ balloon_alert(mod.wearer, "[device] extended")
+ RegisterSignal(mod.wearer, COMSIG_ATOM_EXITED, PROC_REF(on_exit))
+ RegisterSignal(mod.wearer, COMSIG_KB_MOB_DROPITEM_DOWN, PROC_REF(dropkey))
+ else
+ balloon_alert(mod.wearer, "can't extend [device]!")
+ mod.wearer.transferItemToLoc(device, src, force = TRUE)
+ return FALSE
+ else
+ var/used_button = MIDDLE_CLICK
+ update_signal(used_button)
+ balloon_alert(mod.wearer, "[src] activated, [used_button]-click to use")
+ active = TRUE
+ COOLDOWN_START(src, cooldown_timer, cooldown_time)
+ mod.wearer.update_inv_back(mod.slot_flags)
+ SEND_SIGNAL(src, COMSIG_MODULE_ACTIVATED)
+ return TRUE
+
+/// Called when the module is deactivated
+/obj/item/mod/module/proc/on_deactivation(display_message = TRUE, deleting = FALSE)
+ active = FALSE
+ if(module_type == MODULE_ACTIVE)
+ mod.selected_module = null
+ if(display_message)
+ balloon_alert(mod.wearer, device ? "[device] retracted" : "[src] deactivated")
+ if(device)
+ mod.wearer.transferItemToLoc(device, src, force = TRUE)
+ UnregisterSignal(mod.wearer, COMSIG_ATOM_EXITED)
+ UnregisterSignal(mod.wearer, COMSIG_KB_MOB_DROPITEM_DOWN)
+ else
+ UnregisterSignal(mod.wearer, used_signal)
+ used_signal = null
+ mod.wearer.update_inv_back(mod.slot_flags)
+ SEND_SIGNAL(src, COMSIG_MODULE_DEACTIVATED)
+ return TRUE
+
+/// Called when the module is used
+/obj/item/mod/module/proc/on_use()
+ if(!COOLDOWN_FINISHED(src, cooldown_timer))
+ balloon_alert(mod.wearer, "on cooldown!")
+ return FALSE
+ if(!check_power(use_power_cost))
+ balloon_alert(mod.wearer, "not enough charge!")
+ return FALSE
+ if(!allowed_in_phaseout && istype(mod.wearer.loc, /obj/effect/dummy/phased_mob))
+ //specifically a to_chat because the user is phased out.
+ to_chat(mod.wearer, span_warning("You cannot activate this right now."))
+ return FALSE
+ if(SEND_SIGNAL(src, COMSIG_MODULE_TRIGGERED) & MOD_ABORT_USE)
+ return FALSE
+ COOLDOWN_START(src, cooldown_timer, cooldown_time)
+ addtimer(CALLBACK(mod.wearer, TYPE_PROC_REF(/mob, update_inv_back), mod.slot_flags), cooldown_time+1) //need to run it a bit after the cooldown starts to avoid conflicts
+ mod.wearer.update_inv_back(mod.slot_flags)
+ SEND_SIGNAL(src, COMSIG_MODULE_USED)
+ return TRUE
+
+/// Called when an activated module without a device is used
+/obj/item/mod/module/proc/on_select_use(atom/target)
+ if(mod.wearer.incapacitated(IGNORE_GRAB))
+ return FALSE
+ mod.wearer.face_atom(target)
+ if(!on_use())
+ return FALSE
+ return TRUE
+
+/// Called when an activated module without a device is active and the user alt/middle-clicks
+/obj/item/mod/module/proc/on_special_click(mob/source, atom/target)
+ SIGNAL_HANDLER
+ on_select_use(target)
+ return COMSIG_MOB_CANCEL_CLICKON
+
+/// Called on the MODsuit's process
+/obj/item/mod/module/proc/on_process(delta_time)
+ if(active)
+ if(!drain_power(active_power_cost * delta_time))
+ on_deactivation()
+ return FALSE
+ on_active_process(delta_time)
+ else
+ drain_power(idle_power_cost * delta_time)
+ return TRUE
+
+/// Called on the MODsuit's process if it is an active module
+/obj/item/mod/module/proc/on_active_process(delta_time)
+ return
+
+/// Called from MODsuit's install() proc, so when the module is installed.
+/obj/item/mod/module/proc/on_install()
+ return
+
+/// Called from MODsuit's uninstall() proc, so when the module is uninstalled.
+/obj/item/mod/module/proc/on_uninstall(deleting = FALSE)
+ return
+
+/// Called when the MODsuit is activated
+/obj/item/mod/module/proc/on_suit_activation()
+ return
+
+/// Called when the MODsuit is deactivated
+/obj/item/mod/module/proc/on_suit_deactivation(deleting = FALSE)
+ return
+
+/// Called when the MODsuit is equipped
+/obj/item/mod/module/proc/on_equip()
+ return
+
+/// Called when the MODsuit is unequipped
+/obj/item/mod/module/proc/on_unequip()
+ return
+
+/// Drains power from the suit charge
+/obj/item/mod/module/proc/drain_power(amount)
+ if(!check_power(amount))
+ return FALSE
+ mod.subtract_charge(amount)
+ mod.update_charge_alert()
+ return TRUE
+
+/// Checks if there is enough power in the suit
+/obj/item/mod/module/proc/check_power(amount)
+ return mod.check_charge(amount)
+
+/// Adds additional things to the MODsuit ui_data()
+/obj/item/mod/module/proc/add_ui_data()
+ return list()
+
+/// Creates a list of configuring options for this module
+/obj/item/mod/module/proc/get_configuration()
+ return list()
+
+/// Generates an element of the get_configuration list with a display name, type and value
+/obj/item/mod/module/proc/add_ui_configuration(display_name, type, value, list/values)
+ return list("display_name" = display_name, "type" = type, "value" = value, "values" = values)
+
+/// Receives configure edits from the TGUI and edits the vars
+/obj/item/mod/module/proc/configure_edit(key, value)
+ return
+
+/// Called when the device moves to a different place on active modules
+/obj/item/mod/module/proc/on_exit(datum/source, atom/movable/part, direction)
+ SIGNAL_HANDLER
+
+ if(!active)
+ return
+ if(part.loc == src)
+ return
+ if(part.loc == mod.wearer)
+ return
+ if(part == device)
+ on_deactivation(display_message = FALSE)
+
+/// Called when the device gets deleted on active modules
+/obj/item/mod/module/proc/on_device_deletion(datum/source)
+ SIGNAL_HANDLER
+
+ if(source == device)
+ device = null
+ qdel(src)
+
+/// Generates an icon to be used for the suit's worn overlays
+/obj/item/mod/module/proc/generate_worn_overlay(mod_layer)
+ . = list()
+ if(!mod.active)
+ return
+ var/used_overlay
+ if(overlay_state_use && !COOLDOWN_FINISHED(src, cooldown_timer))
+ used_overlay = overlay_state_use
+ else if(overlay_state_active && active)
+ used_overlay = overlay_state_active
+ else if(overlay_state_inactive)
+ used_overlay = overlay_state_inactive
+ else
+ return
+ var/mutable_appearance/module_icon = mutable_appearance(overlay_icon_file, used_overlay, layer = mod_layer + 0.1)
+ if(!use_mod_colors)
+ module_icon.appearance_flags |= RESET_COLOR
+ . += module_icon
+
+/// Updates the signal used by active modules to be activated
+/obj/item/mod/module/proc/update_signal(value)
+ switch(value)
+ if(MIDDLE_CLICK)
+ mod.selected_module.used_signal = COMSIG_MOB_MIDDLECLICKON
+ if(ALT_CLICK)
+ mod.selected_module.used_signal = COMSIG_MOB_ALTCLICKON
+ RegisterSignal(mod.wearer, mod.selected_module.used_signal, TYPE_PROC_REF(/obj/item/mod/module, on_special_click))
+
+/// Pins the module to the user's action buttons
+/obj/item/mod/module/proc/pin(mob/user)
+ var/datum/action/item_action/mod/pinned_module/existing_action = pinned_to[REF(user)]
+ if(existing_action)
+ mod.remove_item_action(existing_action)
+ return
+
+ var/datum/action/item_action/mod/pinned_module/new_action = new(mod, src, user)
+ mod.add_item_action(new_action)
+
+/// On drop key, concels a device item.
+/obj/item/mod/module/proc/dropkey(mob/living/user)
+ SIGNAL_HANDLER
+
+ if(user.get_active_held_item() != device)
+ return
+ on_deactivation()
+ return COMSIG_KB_ACTIVATED
+
+///Anomaly Locked - Causes the module to not function without an anomaly.
+/obj/item/mod/module/anomaly_locked
+ name = "MOD anomaly locked module"
+ desc = "A form of a module, locked behind an anomalous core to function."
+ incompatible_modules = list(/obj/item/mod/module/anomaly_locked)
+ /// The core item the module runs off.
+ var/obj/item/assembly/signaler/anomaly/core
+ /// Accepted types of anomaly cores.
+ var/list/accepted_anomalies = list(/obj/item/assembly/signaler/anomaly)
+ /// If this one starts with a core in.
+ var/prebuilt = FALSE
+
+/obj/item/mod/module/anomaly_locked/Initialize(mapload)
+ . = ..()
+ if(!prebuilt || !length(accepted_anomalies))
+ return
+ var/core_path = pick(accepted_anomalies)
+ core = new core_path(src)
+ update_icon_state()
+
+/obj/item/mod/module/anomaly_locked/Destroy()
+ QDEL_NULL(core)
+ return ..()
+
+/obj/item/mod/module/anomaly_locked/examine(mob/user)
+ . = ..()
+ if(!length(accepted_anomalies))
+ return
+ if(core)
+ . += span_notice("There is a [core.name] installed in it. You could remove it with a screwdriver...")
+ else
+ var/list/core_list = list()
+ for(var/path in accepted_anomalies)
+ var/atom/core_path = path
+ core_list += initial(core_path.name)
+ . += span_notice("You need to insert \a [english_list(core_list, and_text = " or ")] for this module to function.")
+
+/obj/item/mod/module/anomaly_locked/on_select()
+ if(!core)
+ balloon_alert(mod.wearer, "no core!")
+ return
+ return ..()
+
+/obj/item/mod/module/anomaly_locked/on_process(delta_time)
+ . = ..()
+ if(!core)
+ return FALSE
+
+/obj/item/mod/module/anomaly_locked/on_active_process(delta_time)
+ if(!core)
+ return FALSE
+ return TRUE
+
+/obj/item/mod/module/anomaly_locked/attackby(obj/item/item, mob/living/user, params)
+ if(item.type in accepted_anomalies)
+ if(core)
+ balloon_alert(user, "core already in!")
+ return
+ if(!user.transferItemToLoc(item, src))
+ return
+ core = item
+ balloon_alert(user, "core installed")
+ playsound(src, 'sound/machines/click.ogg', 30, TRUE)
+ update_icon_state()
+ else
+ return ..()
+
+/obj/item/mod/module/anomaly_locked/screwdriver_act(mob/living/user, obj/item/tool)
+ . = ..()
+ if(!core)
+ balloon_alert(user, "no core!")
+ return
+ balloon_alert(user, "removing core...")
+ if(!do_after(user, 3 SECONDS, target = src))
+ balloon_alert(user, "interrupted!")
+ return
+ balloon_alert(user, "core removed")
+ core.forceMove(drop_location())
+ if(Adjacent(user) && !issilicon(user))
+ user.put_in_hands(core)
+ core = null
+ update_icon_state()
+
+/obj/item/mod/module/anomaly_locked/update_icon_state()
+ icon_state = initial(icon_state) + (core ? "-core" : "")
+ return ..()
diff --git a/code/modules/mod/modules/modules_antag.dm b/code/modules/mod/modules/modules_antag.dm
new file mode 100644
index 00000000000..33edd75e173
--- /dev/null
+++ b/code/modules/mod/modules/modules_antag.dm
@@ -0,0 +1,398 @@
+//Antag modules for MODsuits
+
+///Armor Booster - Grants your suit more armor and speed in exchange for EVA protection. Also acts as a welding screen.
+/obj/item/mod/module/armor_booster
+ name = "MOD armor booster module"
+ desc = "A retrofitted series of retractable armor plates, allowing the suit to function as essentially power armor, \
+ giving the user incredible protection against conventional firearms, or everyday attacks in close-quarters. \
+ However, the additional plating cannot deploy alongside parts of the suit used for vacuum sealing, \
+ so this extra armor provides zero ability for extravehicular activity while deployed."
+ icon_state = "armor_booster"
+ module_type = MODULE_TOGGLE
+ active_power_cost = DEFAULT_CHARGE_DRAIN * 0.3
+ removable = TRUE
+ incompatible_modules = list(/obj/item/mod/module/armor_booster, /obj/item/mod/module/welding)
+ cooldown_time = 0.5 SECONDS
+ overlay_state_inactive = "module_armorbooster_off"
+ overlay_state_active = "module_armorbooster_on"
+ use_mod_colors = TRUE
+ /// Whether or not this module removes pressure protection.
+ var/remove_pressure_protection = TRUE
+ /// Speed added to the control unit.
+ var/speed_added = 0.5
+ /// Speed that we actually added.
+ var/actual_speed_added = 0
+ /// Armor values added to the suit parts.
+ var/list/armor_values = list("melee" = 25, "bullet" = 30, "laser" = 15, "energy" = 15)
+ /// List of parts of the suit that are spaceproofed, for giving them back the pressure protection.
+ var/list/spaceproofed = list()
+
+/obj/item/mod/module/armor_booster/on_suit_activation()
+ mod.helmet.flash_protect = FLASH_PROTECTION_WELDER
+
+/obj/item/mod/module/armor_booster/on_suit_deactivation(deleting = FALSE)
+ if(deleting)
+ return
+ mod.helmet.flash_protect = initial(mod.helmet.flash_protect)
+
+/obj/item/mod/module/armor_booster/on_activation()
+ . = ..()
+ if(!.)
+ return
+ playsound(src, 'sound/mecha/mechmove03.ogg', 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
+ actual_speed_added = max(0, min(mod.slowdown_active, speed_added))
+ mod.slowdown -= actual_speed_added
+ mod.wearer.update_equipment_speed_mods()
+ var/list/parts = mod.mod_parts + mod
+ for(var/obj/item/part as anything in parts)
+ part.armor = part.armor.modifyRating(arglist(armor_values))
+ if(!remove_pressure_protection || !isclothing(part))
+ continue
+ var/obj/item/clothing/clothing_part = part
+ if(clothing_part.clothing_flags & STOPSPRESSUREDAMAGE)
+ clothing_part.clothing_flags &= ~STOPSPRESSUREDAMAGE
+ spaceproofed[clothing_part] = TRUE
+
+/obj/item/mod/module/armor_booster/on_deactivation(display_message = TRUE, deleting = FALSE)
+ . = ..()
+ if(!.)
+ return
+ if(!deleting)
+ playsound(src, 'sound/mecha/mechmove03.ogg', 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
+ mod.slowdown += actual_speed_added
+ mod.wearer.update_equipment_speed_mods()
+ var/list/parts = mod.mod_parts + mod
+ var/list/removed_armor = armor_values.Copy()
+ for(var/armor_type in removed_armor)
+ removed_armor[armor_type] = -removed_armor[armor_type]
+ for(var/obj/item/part as anything in parts)
+ part.armor = part.armor.modifyRating(arglist(removed_armor))
+ if(!remove_pressure_protection || !isclothing(part))
+ continue
+ var/obj/item/clothing/clothing_part = part
+ if(spaceproofed[clothing_part])
+ clothing_part.clothing_flags |= STOPSPRESSUREDAMAGE
+ spaceproofed = list()
+
+/obj/item/mod/module/armor_booster/generate_worn_overlay(mutable_appearance/standing)
+ overlay_state_inactive = "[initial(overlay_state_inactive)]-[mod.skin]"
+ overlay_state_active = "[initial(overlay_state_active)]-[mod.skin]"
+ return ..()
+
+///Energy Shield - Gives you a rechargeable energy shield that nullifies attacks.
+/obj/item/mod/module/energy_shield
+ name = "MOD energy shield module"
+ desc = "A personal, protective forcefield typically seen in military applications. \
+ This advanced deflector shield is essentially a scaled down version of those seen on starships, \
+ and the power cost can be an easy indicator of this. However, it is capable of blocking nearly any incoming attack, \
+ though with its' low amount of separate charges, the user remains mortal."
+ icon_state = "energy_shield"
+ complexity = 3
+ idle_power_cost = DEFAULT_CHARGE_DRAIN * 0.5
+ use_power_cost = DEFAULT_CHARGE_DRAIN * 2
+ incompatible_modules = list(/obj/item/mod/module/energy_shield)
+ /// Max charges of the shield.
+ var/max_charges = 3
+ /// The time it takes for the first charge to recover.
+ var/recharge_start_delay = 20 SECONDS
+ /// How much time it takes for charges to recover after they started recharging.
+ var/charge_increment_delay = 1 SECONDS
+ /// How much charge is recovered per recovery.
+ var/charge_recovery = 1
+ /// Whether or not this shield can lose multiple charges.
+ var/lose_multiple_charges = FALSE
+ /// The item path to recharge this shielkd.
+ var/recharge_path = null
+ /// The icon file of the shield.
+ var/shield_icon_file = 'icons/effects/effects.dmi'
+ /// The icon_state of the shield.
+ var/shield_icon = "shield-red"
+ /// Charges the shield should start with.
+ var/charges
+
+/obj/item/mod/module/energy_shield/Initialize(mapload)
+ . = ..()
+ charges = max_charges
+
+/obj/item/mod/module/energy_shield/on_suit_activation()
+ mod.AddComponent(/datum/component/shielded, max_charges = max_charges, recharge_start_delay = recharge_start_delay, charge_increment_delay = charge_increment_delay, \
+ charge_recovery = charge_recovery, lose_multiple_charges = lose_multiple_charges, recharge_path = recharge_path, starting_charges = charges, shield_icon_file = shield_icon_file, shield_icon = shield_icon)
+ RegisterSignal(mod.wearer, COMSIG_HUMAN_CHECK_SHIELDS, PROC_REF(shield_reaction))
+
+/obj/item/mod/module/energy_shield/on_suit_deactivation(deleting = FALSE)
+ var/datum/component/shielded/shield = mod.GetComponent(/datum/component/shielded)
+ charges = shield.current_charges
+ qdel(shield)
+ UnregisterSignal(mod.wearer, COMSIG_HUMAN_CHECK_SHIELDS)
+
+/obj/item/mod/module/energy_shield/proc/shield_reaction(mob/living/carbon/human/owner, atom/movable/hitby, damage = 0, attack_text = "the attack", attack_type = MELEE_ATTACK, armour_penetration = 0)
+ if(SEND_SIGNAL(mod, COMSIG_ITEM_HIT_REACT, owner, hitby, attack_text, 0, damage, attack_type) & COMPONENT_HIT_REACTION_BLOCK)
+ drain_power(use_power_cost)
+ return SHIELD_BLOCK
+ return NONE
+
+///Insignia - Gives you a skin specific stripe.
+/obj/item/mod/module/insignia
+ name = "MOD insignia module"
+ desc = "Despite the existence of IFF systems, radio communique, and modern methods of deductive reasoning involving \
+ the wearer's own eyes, colorful paint jobs remain a popular way for different factions in the galaxy to display who \
+ they are. This system utilizes a series of tiny moving paint sprayers to both apply and remove different \
+ color patterns to and from the suit."
+ icon_state = "insignia"
+ removable = FALSE
+ incompatible_modules = list(/obj/item/mod/module/insignia)
+ overlay_state_inactive = "module_insignia"
+
+/obj/item/mod/module/insignia/generate_worn_overlay(mutable_appearance/standing)
+ overlay_state_inactive = "[initial(overlay_state_inactive)]-[mod.skin]"
+ . = ..()
+ for(var/mutable_appearance/appearance as anything in .)
+ appearance.color = color
+
+/obj/item/mod/module/insignia/commander
+ color = "#4980a5"
+
+/obj/item/mod/module/insignia/security
+ color = "#b30d1e"
+
+/obj/item/mod/module/insignia/engineer
+ color = "#e9c80e"
+
+/obj/item/mod/module/insignia/medic
+ color = "#ebebf5"
+
+/obj/item/mod/module/insignia/janitor
+ color = "#7925c7"
+
+/obj/item/mod/module/insignia/chaplain
+ color = "#f0a00c"
+
+///Anti Slip - Prevents you from slipping on water.
+/obj/item/mod/module/noslip
+ name = "MOD anti slip module"
+ desc = "These are a modified variant of standard magnetic boots, utilizing piezoelectric crystals on the soles. \
+ The two plates on the bottom of the boots automatically extend and magnetize as the user steps; \
+ a pull that's too weak to offer them the ability to affix to a hull, but just strong enough to \
+ protect against the fact that you didn't read the wet floor sign. Honk Co. has come out numerous times \
+ in protest of these modules being legal."
+ icon_state = "noslip"
+ complexity = 1
+ idle_power_cost = DEFAULT_CHARGE_DRAIN * 0.1
+ incompatible_modules = list(/obj/item/mod/module/noslip)
+
+/obj/item/mod/module/noslip/on_suit_activation()
+ mod.boots.clothing_flags |= NOSLIP
+
+/obj/item/mod/module/noslip/on_suit_deactivation(deleting = FALSE)
+ mod.boots.clothing_flags &= ~NOSLIP
+
+//Bite of 87 Springlock - Equips faster, disguised as DNA lock.
+/obj/item/mod/module/springlock/bite_of_87
+
+/obj/item/mod/module/springlock/bite_of_87/Initialize(mapload)
+ . = ..()
+ var/obj/item/mod/module/dna_lock/the_dna_lock_behind_the_slaughter = /obj/item/mod/module/dna_lock
+ name = initial(the_dna_lock_behind_the_slaughter.name)
+ desc = initial(the_dna_lock_behind_the_slaughter.desc)
+ icon_state = initial(the_dna_lock_behind_the_slaughter.icon_state)
+ complexity = initial(the_dna_lock_behind_the_slaughter.complexity)
+ use_power_cost = initial(the_dna_lock_behind_the_slaughter.use_power_cost)
+
+/obj/item/mod/module/springlock/bite_of_87/on_install()
+ mod.activation_step_time *= 0.1
+
+/obj/item/mod/module/springlock/bite_of_87/on_uninstall(deleting = FALSE)
+ mod.activation_step_time *= 10
+
+/obj/item/mod/module/springlock/bite_of_87/on_suit_activation()
+ ..()
+ if(SSevents.holidays && SSevents.holidays[APRIL_FOOLS] || prob(1))
+ mod.set_mod_color("#b17f00")
+ mod.wearer.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) // turns purple guy purple
+ mod.wearer.add_atom_colour("#704b96", FIXED_COLOUR_PRIORITY)
+
+///Flamethrower - Launches fire across the area.
+/obj/item/mod/module/flamethrower
+ name = "MOD flamethrower module"
+ desc = "A custom-manufactured flamethrower, used to burn through your path. Burn well."
+ icon_state = "flamethrower"
+ module_type = MODULE_ACTIVE
+ complexity = 3
+ use_power_cost = DEFAULT_CHARGE_DRAIN * 3
+ incompatible_modules = list(/obj/item/mod/module/flamethrower)
+ cooldown_time = 2.5 SECONDS
+ overlay_state_inactive = "module_flamethrower"
+ overlay_state_active = "module_flamethrower_on"
+
+/obj/item/mod/module/flamethrower/on_select_use(atom/target)
+ . = ..()
+ if(!.)
+ return
+ var/obj/projectile/flame = new /obj/projectile/bullet/incendiary(mod.wearer.loc)
+ flame.preparePixelProjectile(target, mod.wearer)
+ flame.firer = mod.wearer
+ playsound(src, 'sound/items/modsuit/flamethrower.ogg', 75, TRUE)
+ INVOKE_ASYNC(flame, TYPE_PROC_REF(/obj/projectile, fire))
+ drain_power(use_power_cost)
+
+///Power kick - Lets the user launch themselves at someone to kick them.
+/obj/item/mod/module/power_kick
+ name = "MOD power kick module"
+ desc = "This module uses high-power myomer to generate an incredible amount of energy, transferred into the power of a kick."
+ icon_state = "power_kick"
+ module_type = MODULE_ACTIVE
+ removable = FALSE
+ use_power_cost = DEFAULT_CHARGE_DRAIN*5
+ incompatible_modules = list(/obj/item/mod/module/power_kick)
+ cooldown_time = 5 SECONDS
+ /// Damage on kick.
+ var/damage = 20
+ /// The wound bonus of the kick.
+ var/wounding_power = 35
+ /// How long we knockdown for on the kick.
+ var/knockdown_time = 2 SECONDS
+
+/obj/item/mod/module/power_kick/on_select_use(atom/target)
+ . = ..()
+ if(!.)
+ return
+ mod.wearer.visible_message(span_warning("[mod.wearer] starts charging a kick!"), \
+ blind_message = span_hear("You hear a charging sound."))
+ playsound(src, 'sound/items/modsuit/loader_charge.ogg', 75, TRUE)
+ balloon_alert(mod.wearer, "you start charging...")
+ animate(mod.wearer, 0.3 SECONDS, pixel_z = 16, flags = ANIMATION_RELATIVE, easing = SINE_EASING|EASE_OUT)
+ addtimer(CALLBACK(mod.wearer, TYPE_PROC_REF(/atom, SpinAnimation), 3, 2), 0.3 SECONDS)
+ if(!do_after(mod.wearer, 1 SECONDS, target = mod))
+ animate(mod.wearer, 0.2 SECONDS, pixel_z = -16, flags = ANIMATION_RELATIVE, easing = SINE_EASING|EASE_OUT)
+ return
+ animate(mod.wearer)
+ drain_power(use_power_cost)
+ playsound(src, 'sound/items/modsuit/loader_launch.ogg', 75, TRUE)
+ var/angle = get_angle(mod.wearer, target) + 180
+ mod.wearer.transform = mod.wearer.transform.Turn(angle)
+ RegisterSignal(mod.wearer, COMSIG_MOVABLE_IMPACT, PROC_REF(on_throw_impact))
+ mod.wearer.throw_at(target, range = 7, speed = 2, thrower = mod.wearer, spin = FALSE, gentle = TRUE, callback = CALLBACK(src, PROC_REF(on_throw_end), mod.wearer, -angle))
+
+/obj/item/mod/module/power_kick/proc/on_throw_end(mob/user, angle)
+ if(!user)
+ return
+ user.transform = user.transform.Turn(angle)
+ animate(user, 0.2 SECONDS, pixel_z = -16, flags = ANIMATION_RELATIVE, easing = SINE_EASING|EASE_OUT)
+
+/obj/item/mod/module/power_kick/proc/on_throw_impact(mob/living/source, obj/target, datum/thrownthing/thrownthing)
+ SIGNAL_HANDLER
+
+ UnregisterSignal(source, COMSIG_MOVABLE_IMPACT)
+ if(!mod?.wearer)
+ return
+ if(isliving(target))
+ var/mob/living/living_target = target
+ living_target.apply_damage(damage, BRUTE, mod.wearer.zone_selected)
+ living_target.Knockdown(knockdown_time)
+ else if(target.obj_integrity)
+ target.take_damage(damage, BRUTE)
+ else
+ return
+ mod.wearer.do_attack_animation(target, ATTACK_EFFECT_SMASH)
+
+///Chameleon - lets the suit disguise as any item that would fit on that slot.
+/obj/item/mod/module/chameleon
+ name = "MOD chameleon module"
+ desc = "A module using chameleon technology to disguise the suit as another object."
+ icon_state = "chameleon"
+ module_type = MODULE_USABLE
+ complexity = 2
+ incompatible_modules = list(/obj/item/mod/module/chameleon)
+ cooldown_time = 0.5 SECONDS
+ allowed_inactive = TRUE
+ /// A list of all the items the suit can disguise as.
+ var/list/possible_disguises = list()
+ /// The path of the item we're disguised as.
+ var/obj/item/current_disguise
+
+/obj/item/mod/module/chameleon/on_install()
+ var/list/all_disguises = sortList(subtypesof(get_path_by_slot(mod.slot_flags)), GLOBAL_PROC_REF(cmp_typepaths_asc))
+ for(var/clothing_path in all_disguises)
+ var/obj/item/clothing = clothing_path
+ if(!initial(clothing.icon_state))
+ continue
+ var/chameleon_item_name = "[initial(clothing.name)] ([initial(clothing.icon_state)])"
+ possible_disguises[chameleon_item_name] = clothing_path
+
+/obj/item/mod/module/chameleon/on_uninstall(deleting = FALSE)
+ if(current_disguise)
+ return_look()
+ possible_disguises = null
+
+/obj/item/mod/module/chameleon/on_use()
+ if(mod.active || mod.activating)
+ balloon_alert(mod.wearer, "suit active!")
+ return
+ . = ..()
+ if(!.)
+ return
+ if(current_disguise)
+ return_look()
+ return
+ var/picked_name = tgui_input_list(mod.wearer, "Select look to change into", "Chameleon Settings", possible_disguises)
+ if(!possible_disguises[picked_name] || mod.active || mod.activating)
+ return
+ current_disguise = possible_disguises[picked_name]
+ update_look()
+
+/obj/item/mod/module/chameleon/proc/update_look()
+ mod.name = initial(current_disguise.name)
+ mod.desc = initial(current_disguise.desc)
+ mod.icon_state = initial(current_disguise.icon_state)
+ mod.icon = initial(current_disguise.icon)
+ mod.mob_overlay_icon = initial(current_disguise.mob_overlay_icon)
+ mod.alternate_worn_layer = initial(current_disguise.alternate_worn_layer)
+ mod.lefthand_file = initial(current_disguise.lefthand_file)
+ mod.righthand_file = initial(current_disguise.righthand_file)
+ //mod.mob_overlay_state = initial(current_disguise.mob_overlay_state)
+ mod.item_state = initial(current_disguise.item_state)
+ mod.wearer.update_inv_back(mod.slot_flags)
+ RegisterSignal(mod, COMSIG_MOD_ACTIVATE, PROC_REF(return_look))
+
+/obj/item/mod/module/chameleon/proc/return_look()
+ mod.name = "[mod.theme.name] [initial(mod.name)]"
+ mod.desc = "[initial(mod.desc)] [mod.theme.desc]"
+ mod.icon_state = "[mod.skin]-[initial(mod.icon_state)]"
+ var/list/mod_skin = mod.theme.skins[mod.skin]
+ mod.icon = mod_skin[MOD_ICON_OVERRIDE] || 'icons/obj/clothing/modsuit/mod_clothing.dmi'
+ mod.mob_overlay_icon = mod_skin[MOD_WORN_ICON_OVERRIDE] || 'icons/mob/clothing/modsuit/mod_clothing.dmi'
+ mod.alternate_worn_layer = mod_skin[CONTROL_LAYER]
+ mod.lefthand_file = initial(mod.lefthand_file)
+ mod.righthand_file = initial(mod.righthand_file)
+ //___callbacknewmod.worn_icon_state = null
+ mod.item_state = null
+ mod.wearer.update_inv_back(mod.slot_flags)
+ current_disguise = null
+ UnregisterSignal(mod, COMSIG_MOD_ACTIVATE)
+
+///Plate Compression - Compresses the suit to normal size
+/obj/item/mod/module/plate_compression
+ name = "MOD plate compression module"
+ desc = "A module that keeps the suit in a very tightly fit state, lowering the overall size. \
+ Due to the pressure on all the parts, typical storage modules do not fit."
+ icon_state = "plate_compression"
+ complexity = 2
+ incompatible_modules = list(/obj/item/mod/module/plate_compression, /obj/item/mod/module/storage)
+ /// The size we set the suit to.
+ var/new_size = WEIGHT_CLASS_NORMAL
+ /// The suit's size before the module is installed.
+ var/old_size
+
+/obj/item/mod/module/plate_compression/on_install()
+ old_size = mod.w_class
+ mod.w_class = new_size
+
+/obj/item/mod/module/plate_compression/on_uninstall(deleting = FALSE)
+ mod.w_class = old_size
+ old_size = null
+ if(!mod.loc)
+ return
+ var/datum/component/storage/concrete/holding_storage = mod.GetComponent(/datum/component/storage/concrete)
+ if(!holding_storage || holding_storage.max_combined_w_class >= mod.w_class)
+ return
+ mod.forceMove(drop_location())
diff --git a/code/modules/mod/modules/modules_engineering.dm b/code/modules/mod/modules/modules_engineering.dm
new file mode 100644
index 00000000000..4905b3ae691
--- /dev/null
+++ b/code/modules/mod/modules/modules_engineering.dm
@@ -0,0 +1,169 @@
+//Engineering modules for MODsuits
+
+///Welding Protection - Makes the helmet protect from flashes and welding.
+/obj/item/mod/module/welding
+ name = "MOD welding protection module"
+ desc = "A module installed into the visor of the suit, this projects a \
+ polarized, holographic overlay in front of the user's eyes. It's rated high enough for \
+ immunity against extremities such as spot and arc welding, solar eclipses, and handheld flashlights."
+ icon_state = "welding"
+ complexity = 1
+ incompatible_modules = list(/obj/item/mod/module/welding, /obj/item/mod/module/armor_booster)
+ overlay_state_inactive = "module_welding"
+
+/obj/item/mod/module/welding/on_suit_activation()
+ mod.helmet.flash_protect = FLASH_PROTECTION_WELDER
+
+/obj/item/mod/module/welding/on_suit_deactivation(deleting = FALSE)
+ if(deleting)
+ return
+ mod.helmet.flash_protect = initial(mod.helmet.flash_protect)
+
+///T-Ray Scan - Scans the terrain for undertile objects.
+/obj/item/mod/module/t_ray
+ name = "MOD t-ray scan module"
+ desc = "A module installed into the visor of the suit, allowing the user to use a pulse of terahertz radiation \
+ to essentially echolocate things beneath the floor, mostly cables and pipes. \
+ A staple of atmospherics work, and counter-smuggling work."
+ icon_state = "tray"
+ module_type = MODULE_TOGGLE
+ complexity = 1
+ active_power_cost = DEFAULT_CHARGE_DRAIN * 0.5
+ incompatible_modules = list(/obj/item/mod/module/t_ray)
+ cooldown_time = 0.5 SECONDS
+ /// T-ray scan range.
+ var/range = 4
+
+/obj/item/mod/module/t_ray/on_active_process(delta_time)
+ t_ray_scan(mod.wearer, 0.8 SECONDS, range)
+
+///Magnetic Stability - Gives the user a slowdown but makes them negate gravity and be immune to slips.
+/obj/item/mod/module/magboot
+ name = "MOD magnetic stability module"
+ desc = "These are powerful electromagnets fitted into the suit's boots, allowing users both \
+ excellent traction no matter the condition indoors, and to essentially hitch a ride on the exterior of a hull. \
+ However, these basic models do not feature computerized systems to automatically toggle them on and off, \
+ so numerous users report a certain stickiness to their steps."
+ icon_state = "magnet"
+ module_type = MODULE_TOGGLE
+ complexity = 2
+ active_power_cost = DEFAULT_CHARGE_DRAIN * 0.5
+ incompatible_modules = list(/obj/item/mod/module/magboot)
+ cooldown_time = 0.5 SECONDS
+ /// Slowdown added onto the suit.
+ var/slowdown_active = 0.5
+
+/obj/item/mod/module/magboot/on_activation()
+ . = ..()
+ if(!.)
+ return
+ ADD_TRAIT(mod.wearer, TRAIT_NOSLIPWATER, MOD_TRAIT)
+ mod.slowdown += slowdown_active
+ mod.wearer.update_gravity(mod.wearer.has_gravity())
+ mod.wearer.update_equipment_speed_mods()
+
+/obj/item/mod/module/magboot/on_deactivation(display_message = TRUE, deleting = FALSE)
+ . = ..()
+ if(!.)
+ return
+ REMOVE_TRAIT(mod.wearer, TRAIT_NOSLIPWATER, MOD_TRAIT)
+ mod.slowdown -= slowdown_active
+ mod.wearer.update_gravity(mod.wearer.has_gravity())
+ mod.wearer.update_equipment_speed_mods()
+
+/obj/item/mod/module/magboot/advanced
+ name = "MOD advanced magnetic stability module"
+ removable = FALSE
+ complexity = 0
+ slowdown_active = 0
+
+///Emergency Tether - Shoots a grappling hook projectile in 0g that throws the user towards it.
+/obj/item/mod/module/tether
+ name = "MOD emergency tether module"
+ desc = "A custom-built grappling-hook powered by a winch capable of hauling the user. \
+ While some older models of cargo-oriented grapples have capacities of a few tons, \
+ these are only capable of working in zero-gravity environments, a blessing to some Engineers."
+ icon_state = "tether"
+ module_type = MODULE_ACTIVE
+ complexity = 3
+ use_power_cost = DEFAULT_CHARGE_DRAIN
+ incompatible_modules = list(/obj/item/mod/module/tether)
+ cooldown_time = 1.5 SECONDS
+
+/obj/item/mod/module/tether/on_use()
+ if(mod.wearer.has_gravity(get_turf(src)))
+ balloon_alert(mod.wearer, "too much gravity!!")
+ playsound(src, 'sound/weapons/gun/general/dry_fire.ogg', 25, TRUE)
+ return FALSE
+ return ..()
+
+/obj/item/mod/module/tether/on_select_use(atom/target)
+ . = ..()
+ if(!.)
+ return
+ var/obj/projectile/tether = new /obj/projectile/tether(mod.wearer.loc)
+ tether.preparePixelProjectile(target, mod.wearer)
+ tether.firer = mod.wearer
+ playsound(src, 'sound/weapons/batonextend.ogg', 25, TRUE)
+ INVOKE_ASYNC(tether, TYPE_PROC_REF(/obj/projectile, fire))
+ drain_power(use_power_cost)
+
+/obj/projectile/tether
+ name = "tether"
+ icon_state = "tether_projectile"
+ icon = 'icons/obj/clothing/modsuit/mod_modules.dmi'
+ damage = 0
+ nodamage = TRUE
+ range = 10
+ hitsound = 'sound/weapons/batonextend.ogg'
+ suppressed = SUPPRESSED_VERY
+ //hit_threshhold = LATTICE_LAYER
+ /// Reference to the beam following the projectile.
+ var/line
+
+/obj/projectile/tether/fire(setAngle)
+ if(firer)
+ line = firer.Beam(src, "line", 'icons/obj/clothing/modsuit/mod_modules.dmi')
+ ..()
+
+/obj/projectile/tether/on_hit(atom/target)
+ . = ..()
+ if(firer)
+ firer.throw_at(target, 10, 1, firer, FALSE, FALSE, null, MOVE_FORCE_NORMAL, TRUE)
+
+/obj/projectile/tether/Destroy()
+ QDEL_NULL(line)
+ return ..()
+
+///Mister - Sprays water over an area.
+/obj/item/mod/module/mister
+ name = "MOD water mister module"
+ desc = "A module containing a mister, able to spray it over areas."
+ icon_state = "mister"
+ module_type = MODULE_ACTIVE
+ complexity = 2
+ active_power_cost = DEFAULT_CHARGE_DRAIN * 0.3
+ device = /obj/item/reagent_containers/spray/mister
+ incompatible_modules = list(/obj/item/mod/module/mister)
+ cooldown_time = 0.5 SECONDS
+ /// Volume of our reagent holder.
+ var/volume = 500
+
+/obj/item/mod/module/mister/Initialize(mapload)
+ create_reagents(volume, OPENCONTAINER)
+ return ..()
+
+///Resin Mister - Sprays resin over an area.
+/obj/item/mod/module/mister/atmos
+ name = "MOD resin mister module"
+ desc = "An atmospheric resin mister, able to fix up areas quickly."
+ device = /obj/item/extinguisher/mini/nozzle/mod
+ volume = 250
+
+/obj/item/mod/module/mister/atmos/Initialize(mapload)
+ . = ..()
+ reagents.add_reagent(/datum/reagent/water, volume)
+
+/obj/item/extinguisher/mini/nozzle/mod
+ name = "MOD atmospheric mister"
+ desc = "An atmospheric resin mister with three modes, mounted as a module."
diff --git a/code/modules/mod/modules/modules_general.dm b/code/modules/mod/modules/modules_general.dm
new file mode 100644
index 00000000000..8c5f9e27cf5
--- /dev/null
+++ b/code/modules/mod/modules/modules_general.dm
@@ -0,0 +1,445 @@
+//General modules for MODsuits
+
+///Ion Jetpack - Lets the user fly freely through space using battery charge.
+/obj/item/mod/module/jetpack
+ name = "MOD ion jetpack module"
+ desc = "A series of electric thrusters installed across the suit, this is a module highly anticipated by trainee Engineers. \
+ Rather than using gasses for combustion thrust, these jets are capable of accelerating ions using \
+ charge from the suit's charge. Some say this isn't Nakamura Engineering's first foray into jet-enabled suits."
+ icon_state = "jetpack"
+ module_type = MODULE_TOGGLE
+ complexity = 3
+ active_power_cost = DEFAULT_CHARGE_DRAIN * 0.5
+ use_power_cost = DEFAULT_CHARGE_DRAIN
+ incompatible_modules = list(/obj/item/mod/module/jetpack)
+ cooldown_time = 0.5 SECONDS
+ overlay_state_inactive = "module_jetpack"
+ overlay_state_active = "module_jetpack_on"
+ /// Do we stop the wearer from gliding in space.
+ var/stabilizers = FALSE
+ /// Do we give the wearer a speed buff.
+ var/full_speed = FALSE
+ var/datum/callback/get_mover
+ var/datum/callback/check_on_move
+
+/obj/item/mod/module/jetpack/Initialize(mapload)
+ . = ..()
+ get_mover = CALLBACK(src, PROC_REF(get_user))
+ check_on_move = CALLBACK(src, PROC_REF(allow_thrust))
+ refresh_jetpack()
+
+/obj/item/mod/module/jetpack/Destroy()
+ get_mover = null
+ check_on_move = null
+ return ..()
+
+/obj/item/mod/module/jetpack/proc/refresh_jetpack()
+ AddComponent(/datum/component/jetpack, stabilizers, COMSIG_MODULE_TRIGGERED, COMSIG_MODULE_DEACTIVATED, MOD_ABORT_USE, get_mover, check_on_move, /datum/effect_system/trail_follow/ion/grav_allowed)
+
+/obj/item/mod/module/jetpack/proc/set_stabilizers(new_stabilizers)
+ if(stabilizers == new_stabilizers)
+ return
+ stabilizers = new_stabilizers
+ refresh_jetpack()
+
+/obj/item/mod/module/jetpack/on_activation()
+ . = ..()
+ if(!.)
+ return
+ if(full_speed)
+ mod.wearer.add_movespeed_modifier(/datum/movespeed_modifier/jetpack/fullspeed)
+
+/obj/item/mod/module/jetpack/on_deactivation(display_message = TRUE, deleting = FALSE)
+ . = ..()
+ if(full_speed)
+ mod.wearer.remove_movespeed_modifier(/datum/movespeed_modifier/jetpack/fullspeed)
+
+/obj/item/mod/module/jetpack/get_configuration()
+ . = ..()
+ .["stabilizers"] = add_ui_configuration("Stabilizers", "bool", stabilizers)
+
+/obj/item/mod/module/jetpack/configure_edit(key, value)
+ switch(key)
+ if("stabilizers")
+ set_stabilizers(text2num(value))
+
+/obj/item/mod/module/jetpack/proc/allow_thrust(use_fuel = TRUE)
+ if(!use_fuel)
+ return check_power(use_power_cost)
+ if(!drain_power(use_power_cost))
+ return FALSE
+ return TRUE
+
+/obj/item/mod/module/jetpack/proc/get_user()
+ return mod.wearer
+
+/obj/item/mod/module/jetpack/advanced
+ name = "MOD advanced ion jetpack module"
+ desc = "An improvement on the previous model of electric thrusters. This one achieves higher speeds through \
+ mounting of more jets and a red paint applied on it."
+ icon_state = "jetpack_advanced"
+ overlay_state_inactive = "module_jetpackadv"
+ overlay_state_active = "module_jetpackadv_on"
+ full_speed = TRUE
+
+///Eating Apparatus - Lets the user eat/drink with the suit on.
+/obj/item/mod/module/mouthhole
+ name = "MOD eating apparatus module"
+ desc = "A favorite by Miners, this modification to the helmet utilizes a nanotechnology barrier infront of the mouth \
+ to allow eating and drinking while retaining protection and atmosphere. However, it won't free you from masks, \
+ and it will do nothing to improve the taste of a goliath steak."
+ icon_state = "apparatus"
+ complexity = 1
+ incompatible_modules = list(/obj/item/mod/module/mouthhole)
+ overlay_state_inactive = "module_apparatus"
+ /// Former flags of the helmet.
+ var/former_flags = NONE
+ /// Former visor flags of the helmet.
+ var/former_visor_flags = NONE
+
+/obj/item/mod/module/mouthhole/on_install()
+ former_flags = mod.helmet.flags_cover
+ former_visor_flags = mod.helmet.visor_flags_cover
+ mod.helmet.flags_cover &= ~HEADCOVERSMOUTH|PEPPERPROOF
+ mod.helmet.visor_flags_cover &= ~HEADCOVERSMOUTH|PEPPERPROOF
+
+/obj/item/mod/module/mouthhole/on_uninstall(deleting = FALSE)
+ if(deleting)
+ return
+ mod.helmet.flags_cover |= former_flags
+ mod.helmet.visor_flags_cover |= former_visor_flags
+
+///EMP Shield - Protects the suit from EMPs.
+/obj/item/mod/module/emp_shield
+ name = "MOD EMP shield module"
+ desc = "A field inhibitor installed into the suit, protecting it against feedback such as \
+ electromagnetic pulses that would otherwise damage the electronic systems of the suit or devices on the wearer. \
+ However, it will take from the suit's power to do so. Luckily, your PDA already has one of these."
+ icon_state = "empshield"
+ complexity = 1
+ idle_power_cost = DEFAULT_CHARGE_DRAIN * 0.3
+ incompatible_modules = list(/obj/item/mod/module/emp_shield)
+
+/obj/item/mod/module/emp_shield/on_install()
+ mod.AddElement(/datum/element/empprotection, EMP_PROTECT_SELF|EMP_PROTECT_WIRES|EMP_PROTECT_CONTENTS)
+
+/obj/item/mod/module/emp_shield/on_uninstall(deleting = FALSE)
+ mod.RemoveElement(/datum/element/empprotection, EMP_PROTECT_SELF|EMP_PROTECT_WIRES|EMP_PROTECT_CONTENTS)
+
+/obj/item/mod/module/emp_shield/advanced
+ name = "MOD advanced EMP shield module"
+ desc = "An enhnanced field inhibitor installed into the suit, protecting it against feedback such as \
+ electromagnetic pulses that would otherwise damage the electronic systems of the suit or devices on the wearer \
+ including augmentations. However, it will take from the suit's power to do so. Luckily, your PDA already has one of these."
+ complexity = 2
+
+/obj/item/mod/module/emp_shield/advanced/on_suit_activation()
+ mod.wearer.AddElement(/datum/element/empprotection, EMP_PROTECT_SELF|EMP_PROTECT_CONTENTS)
+
+/obj/item/mod/module/emp_shield/advanced/on_suit_deactivation(deleting)
+ mod.wearer.RemoveElement(/datum/element/empprotection, EMP_PROTECT_SELF|EMP_PROTECT_CONTENTS)
+
+///Flashlight - Gives the suit a customizable flashlight.
+/obj/item/mod/module/flashlight
+ name = "MOD flashlight module"
+ desc = "A simple pair of flashlights installed on the left and right sides of the helmet."
+ icon_state = "flashlight"
+ module_type = MODULE_TOGGLE
+ complexity = 1
+ active_power_cost = DEFAULT_CHARGE_DRAIN * 0.3
+ incompatible_modules = list(/obj/item/mod/module/flashlight)
+ cooldown_time = 0.5 SECONDS
+ overlay_state_inactive = "module_light"
+ light_system = MOVABLE_LIGHT_DIRECTIONAL
+ light_color = COLOR_WHITE
+ light_range = 4
+ light_power = 1
+ light_on = FALSE
+ /// Charge drain per range amount.
+ var/base_power = DEFAULT_CHARGE_DRAIN * 0.1
+
+/obj/item/mod/module/flashlight/on_activation()
+ . = ..()
+ if(!.)
+ return
+ set_light_flags(light_flags | LIGHT_ATTACHED)
+ set_light_on(active)
+ active_power_cost = base_power * light_range
+
+/obj/item/mod/module/flashlight/on_deactivation(display_message = TRUE, deleting = FALSE)
+ . = ..()
+ if(!.)
+ return
+ set_light_flags(light_flags & ~LIGHT_ATTACHED)
+ set_light_on(active)
+
+/obj/item/mod/module/flashlight/on_process(delta_time)
+ active_power_cost = base_power * light_range
+ return ..()
+
+/obj/item/mod/module/flashlight/generate_worn_overlay(mutable_appearance/standing)
+ . = ..()
+ if(!active)
+ return
+ var/mutable_appearance/light_icon = mutable_appearance(overlay_icon_file, "module_light_on", layer = standing + 0.2)
+ light_icon.appearance_flags = RESET_COLOR
+ light_icon.color = light_color
+ . += light_icon
+
+///Dispenser - Dispenses an item after a time passes.
+/obj/item/mod/module/dispenser
+ name = "MOD burger dispenser module"
+ desc = "A rare piece of technology reverse-engineered from a prototype found in a Donk Corporation vessel. \
+ This can draw incredible amounts of power from the suit's charge to create edible organic matter in the \
+ palm of the wearer's glove; however, research seemed to have entirely stopped at burgers. \
+ Notably, all attempts to get it to dispense Earl Grey tea have failed."
+ icon_state = "dispenser"
+ module_type = MODULE_USABLE
+ complexity = 3
+ use_power_cost = DEFAULT_CHARGE_DRAIN * 2
+ incompatible_modules = list(/obj/item/mod/module/dispenser)
+ cooldown_time = 5 SECONDS
+ /// Path we dispense.
+ var/dispense_type = /obj/item/reagent_containers/food/snacks/burger
+ /// Time it takes for us to dispense.
+ var/dispense_time = 0 SECONDS
+
+/obj/item/mod/module/dispenser/on_use()
+ . = ..()
+ if(!.)
+ return
+ if(dispense_time && !do_after(mod.wearer, dispense_time, target = mod))
+ balloon_alert(mod.wearer, "interrupted!")
+ return FALSE
+ var/obj/item/dispensed = new dispense_type(mod.wearer.loc)
+ mod.wearer.put_in_hands(dispensed)
+ balloon_alert(mod.wearer, "[dispensed] dispensed")
+ playsound(src, 'sound/machines/click.ogg', 100, TRUE)
+ drain_power(use_power_cost)
+ return dispensed
+
+///Thermal Regulator - Regulates the wearer's core temperature.
+/obj/item/mod/module/thermal_regulator
+ name = "MOD thermal regulator module"
+ desc = "Advanced climate control, using an inner body glove interwoven with thousands of tiny, \
+ flexible cooling lines. This circulates coolant at various user-controlled temperatures, \
+ ensuring they're comfortable; even if they're some that like it hot."
+ icon_state = "regulator"
+ module_type = MODULE_TOGGLE
+ complexity = 2
+ active_power_cost = DEFAULT_CHARGE_DRAIN * 0.3
+ incompatible_modules = list(/obj/item/mod/module/thermal_regulator)
+ cooldown_time = 0.5 SECONDS
+ /// The temperature we are regulating to.
+ var/temperature_setting = BODYTEMP_NORMAL
+ /// Minimum temperature we can set.
+ var/min_temp = 293.15
+ /// Maximum temperature we can set.
+ var/max_temp = 318.15
+
+/obj/item/mod/module/thermal_regulator/get_configuration()
+ . = ..()
+ .["temperature_setting"] = add_ui_configuration("Temperature", "number", temperature_setting - T0C)
+
+/obj/item/mod/module/thermal_regulator/configure_edit(key, value)
+ switch(key)
+ if("temperature_setting")
+ temperature_setting = clamp(value + T0C, min_temp, max_temp)
+
+/obj/item/mod/module/thermal_regulator/on_active_process(delta_time)
+ mod.wearer.adjust_bodytemperature(get_temp_change_amount((temperature_setting - mod.wearer.bodytemperature), 0.08 * delta_time))
+
+///DNA Lock - Prevents people without the set DNA from activating the suit.
+/obj/item/mod/module/dna_lock
+ name = "MOD DNA lock module"
+ desc = "A module which engages with the various locks and seals tied to the suit's systems, \
+ enabling it to only be worn by someone corresponding with the user's exact DNA profile; \
+ however, this incredibly sensitive module is shorted out by EMPs. Luckily, cloning has been outlawed."
+ icon_state = "dnalock"
+ module_type = MODULE_USABLE
+ complexity = 2
+ use_power_cost = DEFAULT_CHARGE_DRAIN * 3
+ incompatible_modules = list(/obj/item/mod/module/dna_lock/*, obj/item/mod/module/eradication_lock*/)
+ cooldown_time = 0.5 SECONDS
+ /// The DNA we lock with.
+ var/dna = null
+
+/obj/item/mod/module/dna_lock/on_install()
+ RegisterSignal(mod, COMSIG_MOD_ACTIVATE, PROC_REF(on_mod_activation))
+ RegisterSignal(mod, COMSIG_MOD_MODULE_REMOVAL, PROC_REF(on_mod_removal))
+ RegisterSignal(mod, COMSIG_ATOM_EMP_ACT, PROC_REF(on_emp))
+ RegisterSignal(mod, COMSIG_ATOM_EMAG_ACT, PROC_REF(on_emag))
+
+/obj/item/mod/module/dna_lock/on_uninstall(deleting = FALSE)
+ UnregisterSignal(mod, COMSIG_MOD_ACTIVATE)
+ UnregisterSignal(mod, COMSIG_MOD_MODULE_REMOVAL)
+ UnregisterSignal(mod, COMSIG_ATOM_EMP_ACT)
+ UnregisterSignal(mod, COMSIG_ATOM_EMAG_ACT)
+
+/obj/item/mod/module/dna_lock/on_use()
+ . = ..()
+ if(!.)
+ return
+ dna = mod.wearer.dna.unique_enzymes
+ balloon_alert(mod.wearer, "dna updated")
+ drain_power(use_power_cost)
+
+/obj/item/mod/module/dna_lock/emp_act(severity)
+ . = ..()
+ if(. & EMP_PROTECT_SELF)
+ return
+ on_emp(src, severity)
+
+/obj/item/mod/module/dna_lock/emag_act(mob/user, obj/item/card/emag/emag_card)
+ . = ..()
+ on_emag(src, user, emag_card)
+
+/obj/item/mod/module/dna_lock/proc/dna_check(mob/user)
+ if(!iscarbon(user))
+ return FALSE
+ var/mob/living/carbon/carbon_user = user
+ if(!dna || (carbon_user.has_dna() && carbon_user.dna.unique_enzymes == dna))
+ return TRUE
+ balloon_alert(user, "dna locked!")
+ return FALSE
+
+/obj/item/mod/module/dna_lock/proc/on_emp(datum/source, severity)
+ SIGNAL_HANDLER
+
+ dna = null
+
+/obj/item/mod/module/dna_lock/proc/on_emag(datum/source, mob/user, obj/item/card/emag/emag_card)
+ SIGNAL_HANDLER
+
+ dna = null
+
+/obj/item/mod/module/dna_lock/proc/on_mod_activation(datum/source, mob/user)
+ SIGNAL_HANDLER
+
+ if(!dna_check(user))
+ return MOD_CANCEL_ACTIVATE
+
+/obj/item/mod/module/dna_lock/proc/on_mod_removal(datum/source, mob/user)
+ SIGNAL_HANDLER
+
+ if(!dna_check(user))
+ return MOD_CANCEL_REMOVAL
+
+///Plasma Stabilizer - Prevents plasmamen from igniting in the suit
+/obj/item/mod/module/plasma_stabilizer
+ name = "MOD plasma stabilizer module"
+ desc = "This system essentially forms an atmosphere of its' own inside the suit, \
+ safely ejecting oxygen from the inside and allowing the wearer, a plasmaman, \
+ to have their internal plasma circulate around them somewhat like a sauna. \
+ This prevents them from self-igniting, and leads to greater comfort overall. \
+ The purple glass of the visor seems to be constructed for nostalgic purposes."
+ icon_state = "plasma_stabilizer"
+ complexity = 1
+ idle_power_cost = DEFAULT_CHARGE_DRAIN * 0.3
+ incompatible_modules = list(/obj/item/mod/module/plasma_stabilizer)
+ overlay_state_inactive = "module_plasma"
+
+/obj/item/mod/module/plasma_stabilizer/on_equip()
+ ADD_TRAIT(mod.wearer, TRAIT_NOSELFIGNITION_HEAD_ONLY, MOD_TRAIT)
+
+/obj/item/mod/module/plasma_stabilizer/on_unequip()
+ REMOVE_TRAIT(mod.wearer, TRAIT_NOSELFIGNITION_HEAD_ONLY, MOD_TRAIT)
+
+
+//Finally, https://pipe.miroware.io/5b52ba1d94357d5d623f74aa/mspfa/Nuke%20Ops/Panels/0648.gif can be real:
+///Hat Stabilizer - Allows displaying a hat over the MOD-helmet, à la plasmamen helmets.
+/obj/item/mod/module/hat_stabilizer
+ name = "MOD hat stabilizer module"
+ desc = "A simple set of deployable stands, directly atop one's head; \
+ these will deploy under a select few hats to keep them from falling off, allowing them to be worn atop the sealed helmet. \
+ You still need to take the hat off your head while the helmet deploys, though. \
+ This is a must-have for Nanotrasen Captains, enabling them to show off their authoritative hat even while in their MODsuit."
+ icon_state = "hat_holder"
+ incompatible_modules = list(/obj/item/mod/module/hat_stabilizer)
+ /*Intentionally left inheriting 0 complexity and removable = TRUE;
+ even though it comes inbuilt into the Magnate/Corporate MODS and spawns in maints, I like the idea of stealing them*/
+ /// Currently "stored" hat. No armor or function will be inherited, ONLY the icon.
+ var/obj/item/clothing/head/attached_hat
+ /// Whitelist of attachable hats, read note in Initialize() below this line
+ var/static/list/attachable_hats_list
+
+/obj/item/mod/module/hat_stabilizer/Initialize(mapload)
+ . = ..()
+ attachable_hats_list = typecacheof(
+ //List of attachable hats. Make sure these and their subtypes are all tested, so they dont appear janky.
+ //This list should also be gimmicky, so captains can have fun. I.E. the Santahat, Pirate hat, Tophat, Chefhat...
+ //Yes, I said it, the captain should have fun.
+ list(
+ /obj/item/clothing/head/caphat,
+ /obj/item/clothing/head/crown,
+ /obj/item/clothing/head/centhat,
+ /obj/item/clothing/head/pirate,
+ /obj/item/clothing/head/santa,
+ /obj/item/clothing/head/hardhat/reindeer,
+ /obj/item/clothing/head/sombrero,
+ /obj/item/clothing/head/kitty,
+ /obj/item/clothing/head/rabbitears,
+ /obj/item/clothing/head/festive,
+ /obj/item/clothing/head/powdered_wig,
+ /obj/item/clothing/head/that,
+ /obj/item/clothing/head/nursehat,
+ /obj/item/clothing/head/chefhat,
+ /obj/item/clothing/head/papersack,
+ ))
+
+/obj/item/mod/module/hat_stabilizer/on_suit_activation()
+ RegisterSignal(mod.helmet, COMSIG_PARENT_EXAMINE, PROC_REF(add_examine))
+ RegisterSignal(mod.helmet, COMSIG_PARENT_ATTACKBY, PROC_REF(place_hat))
+ RegisterSignal(mod.helmet, COMSIG_CLICK_ALT, PROC_REF(remove_hat))
+
+/obj/item/mod/module/hat_stabilizer/on_suit_deactivation(deleting = FALSE)
+ if(deleting)
+ return
+ if(attached_hat) //knock off the helmet if its on their head. Or, technically, auto-rightclick it for them; that way it saves us code, AND gives them the bubble
+ remove_hat(src, mod.wearer)
+ UnregisterSignal(mod.helmet, COMSIG_PARENT_EXAMINE)
+ UnregisterSignal(mod.helmet, COMSIG_PARENT_ATTACKBY)
+ UnregisterSignal(mod.helmet, COMSIG_CLICK_ALT)
+
+/obj/item/mod/module/hat_stabilizer/proc/add_examine(datum/source, mob/user, list/base_examine)
+ SIGNAL_HANDLER
+ if(attached_hat)
+ base_examine += span_notice("There's \a [attached_hat] placed on the helmet. Alt-click to remove it.")
+ else
+ base_examine += span_notice("There's nothing placed on the helmet. Yet.")
+
+/obj/item/mod/module/hat_stabilizer/proc/place_hat(datum/source, obj/item/hitting_item, mob/user)
+ SIGNAL_HANDLER
+ if(!istype(hitting_item, /obj/item/clothing/head))
+ return
+ if(!mod.active)
+ balloon_alert(user, "suit must be active!")
+ return
+ if(!is_type_in_typecache(hitting_item, attachable_hats_list))
+ balloon_alert(user, "this hat won't fit!")
+ return
+ if(attached_hat)
+ balloon_alert(user, "hat already attached!")
+ return
+ if(mod.wearer.transferItemToLoc(hitting_item, src, force = FALSE, silent = TRUE))
+ attached_hat = hitting_item
+ balloon_alert(user, "hat attached, alt-click to remove")
+ mod.wearer.update_inv_back(mod.slot_flags)
+
+/obj/item/mod/module/hat_stabilizer/generate_worn_overlay()
+ . = ..()
+ if(attached_hat)
+ . += attached_hat.build_worn_icon(default_layer = ABOVE_MOB_LAYER, default_icon_file = 'icons/mob/clothing/head.dmi')
+
+/obj/item/mod/module/hat_stabilizer/proc/remove_hat(datum/source, mob/user)
+ SIGNAL_HANDLER
+ . = SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+ if(!attached_hat)
+ return
+ attached_hat.forceMove(drop_location())
+ if(user.put_in_active_hand(attached_hat))
+ balloon_alert(user, "hat removed")
+ else
+ balloon_alert_to_viewers("the hat falls to the floor!")
+ attached_hat = null
+ mod.wearer.update_inv_back(mod.slot_flags)
diff --git a/code/modules/mod/modules/modules_maint.dm b/code/modules/mod/modules/modules_maint.dm
new file mode 100644
index 00000000000..e735654ef2c
--- /dev/null
+++ b/code/modules/mod/modules/modules_maint.dm
@@ -0,0 +1,148 @@
+//Maint modules for MODsuits
+
+///Springlock Mechanism - allows your modsuit to activate faster, but reagents are very dangerous.
+/obj/item/mod/module/springlock
+ name = "MOD springlock module"
+ desc = "A module that spans the entire size of the MOD unit, sitting under the outer shell. \
+ This mechanical exoskeleton pushes out of the way when the user enters and it helps in booting \
+ up, but was taken out of modern suits because of the springlock's tendency to \"snap\" back \
+ into place when exposed to humidity. You know what it's like to have an entire exoskeleton enter you?"
+ icon_state = "springlock"
+ complexity = 3 // it is inside every part of your suit, so
+ incompatible_modules = list(/obj/item/mod/module/springlock)
+
+/obj/item/mod/module/springlock/on_install()
+ mod.activation_step_time *= 0.5
+
+/obj/item/mod/module/springlock/on_uninstall(deleting = FALSE)
+ mod.activation_step_time *= 2
+
+/obj/item/mod/module/springlock/on_suit_activation()
+ RegisterSignal(mod.wearer, COMSIG_ATOM_EXPOSE_REAGENTS, PROC_REF(on_wearer_exposed))
+
+/obj/item/mod/module/springlock/on_suit_deactivation(deleting = FALSE)
+ UnregisterSignal(mod.wearer, COMSIG_ATOM_EXPOSE_REAGENTS)
+
+///Signal fired when wearer is exposed to reagents
+/obj/item/mod/module/springlock/proc/on_wearer_exposed(atom/source, list/reagents, datum/reagents/source_reagents, methods, volume_modifier, show_message)
+ SIGNAL_HANDLER
+
+ if(!(methods & (VAPOR|PATCH|TOUCH)))
+ return //remove non-touch reagent exposure
+ to_chat(mod.wearer, span_danger("[src] makes an ominous click sound..."))
+ playsound(src, 'sound/items/modsuit/springlock.ogg', 75, TRUE)
+ addtimer(CALLBACK(src, PROC_REF(snap_shut)), rand(3 SECONDS, 5 SECONDS))
+ RegisterSignal(mod, COMSIG_MOD_ACTIVATE, PROC_REF(on_activate_spring_block))
+
+///Signal fired when wearer attempts to activate/deactivate suits
+/obj/item/mod/module/springlock/proc/on_activate_spring_block(datum/source, user)
+ SIGNAL_HANDLER
+
+ balloon_alert(user, "springlocks aren't responding...?")
+ return MOD_CANCEL_ACTIVATE
+
+///Delayed death proc of the suit after the wearer is exposed to reagents
+/obj/item/mod/module/springlock/proc/snap_shut()
+ UnregisterSignal(mod, COMSIG_MOD_ACTIVATE)
+ if(!mod.wearer) //while there is a guaranteed user when on_wearer_exposed() fires, that isn't the same case for this proc
+ return
+ mod.wearer.visible_message("[src] inside [mod.wearer]'s [mod.name] snaps shut, mutilating the user inside!", span_userdanger("*SNAP*"))
+ mod.wearer.emote("scream")
+ playsound(mod.wearer, 'sound/effects/snap.ogg', 75, TRUE, frequency = 0.5)
+ playsound(mod.wearer, 'sound/effects/splat.ogg', 50, TRUE, frequency = 0.5)
+ mod.wearer.apply_damage(500, BRUTE, forced = TRUE, spread_damage = TRUE) //boggers, bogchamp, etc
+ if(!HAS_TRAIT(mod.wearer, TRAIT_NODEATH))
+ mod.wearer.death() //just in case, for some reason, they're still alive
+ flash_color(mod.wearer, flash_color = "#FF0000", flash_time = 10 SECONDS)
+
+///Balloon Blower - Blows a balloon.
+/obj/item/mod/module/balloon
+ name = "MOD balloon blower module"
+ desc = "A strange module invented years ago by some ingenious mimes. It blows balloons."
+ icon_state = "bloon"
+ module_type = MODULE_USABLE
+ complexity = 1
+ use_power_cost = DEFAULT_CHARGE_DRAIN * 0.5
+ incompatible_modules = list(/obj/item/mod/module/balloon)
+ cooldown_time = 15 SECONDS
+
+/obj/item/mod/module/balloon/on_use()
+ . = ..()
+ if(!.)
+ return
+ if(!do_after(mod.wearer, 10 SECONDS, target = mod))
+ return FALSE
+ mod.wearer.adjustOxyLoss(20)
+ playsound(src, 'sound/items/modsuit/inflate_bloon.ogg', 50, TRUE)
+ var/obj/item/toy/balloon/balloon = new(get_turf(src))
+ mod.wearer.put_in_hands(balloon)
+ drain_power(use_power_cost)
+
+///Paper Dispenser - Dispenses (sometimes burning) paper sheets.
+/obj/item/mod/module/paper_dispenser
+ name = "MOD paper dispenser module"
+ desc = "A simple module designed by the bureaucrats of Torch Bay. \
+ It dispenses 'warm, clean, and crisp sheets of paper' onto a nearby table. Usually."
+ icon_state = "paper_maker"
+ module_type = MODULE_USABLE
+ complexity = 1
+ use_power_cost = DEFAULT_CHARGE_DRAIN * 0.5
+ incompatible_modules = list(/obj/item/mod/module/paper_dispenser)
+ cooldown_time = 5 SECONDS
+ /// The total number of sheets created by this MOD. The more sheets, them more likely they set on fire.
+ var/num_sheets_dispensed = 0
+
+/obj/item/mod/module/paper_dispenser/on_use()
+ . = ..()
+ if(!.)
+ return
+ if(!do_after(mod.wearer, 1 SECONDS, target = mod))
+ return FALSE
+
+ var/obj/item/paper/crisp_paper = new(get_turf(src))
+ crisp_paper.desc = "It's crisp and warm to the touch. Must be fresh."
+
+ var/obj/structure/table/nearby_table = locate() in range(1, mod.wearer)
+ playsound(get_turf(src), 'sound/machines/click.ogg', 50, TRUE)
+ balloon_alert(mod.wearer, "dispensed paper[nearby_table ? " onto table":""]")
+
+ mod.wearer.put_in_hands(crisp_paper)
+ if(nearby_table)
+ mod.wearer.transferItemToLoc(crisp_paper, nearby_table.drop_location(), silent = FALSE)
+
+ // Up to a 30% chance to set the sheet on fire, +2% per sheet made
+ if(prob(min(num_sheets_dispensed * 2, 30)))
+ if(crisp_paper in mod.wearer.held_items)
+ mod.wearer.dropItemToGround(crisp_paper, force = TRUE)
+ crisp_paper.balloon_alert(mod.wearer, "pc load letter!")
+ crisp_paper.visible_message(span_warning("[crisp_paper] bursts into flames, it's too crisp!"))
+ crisp_paper.fire_act(1000, 100)
+
+ drain_power(use_power_cost)
+ num_sheets_dispensed++
+
+
+///Stamper - Extends a stamp that can switch between accept/deny modes.
+/obj/item/mod/module/stamp
+ name = "MOD stamper module"
+ desc = "A module installed into the wrist of the suit, this functions as a high-power stamp, \
+ able to switch between accept and deny modes."
+ icon_state = "stamp"
+ module_type = MODULE_ACTIVE
+ complexity = 1
+ active_power_cost = DEFAULT_CHARGE_DRAIN * 0.3
+ device = /obj/item/stamp/mod
+ incompatible_modules = list(/obj/item/mod/module/stamp)
+ cooldown_time = 0.5 SECONDS
+
+/obj/item/stamp/mod
+ name = "MOD electronic stamp"
+ desc = "A high-power stamp, able to switch between accept and deny mode when used."
+
+/obj/item/stamp/mod/attack_self(mob/user, modifiers)
+ . = ..()
+ if(icon_state == "stamp-ok")
+ icon_state = "stamp-deny"
+ else
+ icon_state = "stamp-ok"
+ balloon_alert(user, "switched mode")
diff --git a/code/modules/mod/modules/modules_medical.dm b/code/modules/mod/modules/modules_medical.dm
new file mode 100644
index 00000000000..798f065ffe7
--- /dev/null
+++ b/code/modules/mod/modules/modules_medical.dm
@@ -0,0 +1,110 @@
+//Medical modules for MODsuits
+
+#define HEALTH_SCAN "Health"
+#define WOUND_SCAN "Wound"
+#define CHEM_SCAN "Chemical"
+
+///Health Analyzer - Gives the user a ranged health analyzer and their health status in the panel.
+/obj/item/mod/module/health_analyzer
+ name = "MOD health analyzer module"
+ desc = "A module installed into the glove of the suit. This is a high-tech biological scanning suite, \
+ allowing the user indepth information on the vitals and injuries of others even at a distance, \
+ all with the flick of the wrist. Data is displayed in a convenient package on HUD in the helmet, \
+ but it's up to you to do something with it."
+ icon_state = "health"
+ module_type = MODULE_ACTIVE
+ complexity = 2
+ use_power_cost = DEFAULT_CHARGE_DRAIN
+ incompatible_modules = list(/obj/item/mod/module/health_analyzer)
+ cooldown_time = 0.5 SECONDS
+ tgui_id = "health_analyzer"
+ /// Scanning mode, changes how we scan something.
+ var/mode = HEALTH_SCAN
+ /// List of all scanning modes.
+ var/static/list/modes = list(HEALTH_SCAN, WOUND_SCAN, CHEM_SCAN)
+
+/obj/item/mod/module/health_analyzer/add_ui_data()
+ . = ..()
+ .["userhealth"] = mod.wearer?.health || 0
+ .["usermaxhealth"] = mod.wearer?.getMaxHealth() || 0
+ .["userbrute"] = mod.wearer?.getBruteLoss() || 0
+ .["userburn"] = mod.wearer?.getFireLoss() || 0
+ .["usertoxin"] = mod.wearer?.getToxLoss() || 0
+ .["useroxy"] = mod.wearer?.getOxyLoss() || 0
+
+/obj/item/mod/module/health_analyzer/on_select_use(atom/target)
+ . = ..()
+ if(!.)
+ return
+ if(!isliving(target) || !mod.wearer.can_read(src))
+ return
+ switch(mode)
+ if(HEALTH_SCAN)
+ healthscan(mod.wearer, target)
+ if(CHEM_SCAN)
+ chemscan(mod.wearer, target)
+ drain_power(use_power_cost)
+
+/obj/item/mod/module/health_analyzer/get_configuration()
+ . = ..()
+ .["mode"] = add_ui_configuration("Scan Mode", "list", mode, modes)
+
+/obj/item/mod/module/health_analyzer/configure_edit(key, value)
+ switch(key)
+ if("mode")
+ mode = value
+
+#undef HEALTH_SCAN
+#undef WOUND_SCAN
+#undef CHEM_SCAN
+
+///Quick Carry - Lets the user carry bodies quicker.
+/obj/item/mod/module/quick_carry
+ name = "MOD quick carry module"
+ desc = "A suite of advanced servos, redirecting power from the suit's arms to help carry the wounded; \
+ or simply for fun. However, Nanotrasen has locked the module's ability to assist in hand-to-hand combat."
+ icon_state = "carry"
+ complexity = 1
+ idle_power_cost = DEFAULT_CHARGE_DRAIN * 0.3
+ incompatible_modules = list(/obj/item/mod/module/quick_carry)
+
+/obj/item/mod/module/quick_carry/on_suit_activation()
+ ADD_TRAIT(mod.wearer, TRAIT_QUICK_CARRY, MOD_TRAIT)
+
+/obj/item/mod/module/quick_carry/on_suit_deactivation(deleting = FALSE)
+ REMOVE_TRAIT(mod.wearer, TRAIT_QUICK_CARRY, MOD_TRAIT)
+
+/obj/item/mod/module/quick_carry/advanced
+ name = "MOD advanced quick carry module"
+ removable = FALSE
+ complexity = 0
+
+/obj/item/mod/module/quick_carry/on_suit_activation()
+ ADD_TRAIT(mod.wearer, TRAIT_QUICKER_CARRY, MOD_TRAIT)
+ ADD_TRAIT(mod.wearer, TRAIT_FASTMED, MOD_TRAIT)
+
+/obj/item/mod/module/quick_carry/on_suit_deactivation(deleting = FALSE)
+ REMOVE_TRAIT(mod.wearer, TRAIT_QUICKER_CARRY, MOD_TRAIT)
+ REMOVE_TRAIT(mod.wearer, TRAIT_FASTMED, MOD_TRAIT)
+
+///Injector - Gives the suit an extendable large-capacity piercing syringe.
+/obj/item/mod/module/injector
+ name = "MOD injector module"
+ desc = "A module installed into the wrist of the suit, this functions as a high-capacity syringe, \
+ with a tip fine enough to locate the emergency injection ports on any suit of armor, \
+ penetrating it with ease. Even yours."
+ icon_state = "injector"
+ module_type = MODULE_ACTIVE
+ complexity = 1
+ active_power_cost = DEFAULT_CHARGE_DRAIN * 0.3
+ device = /obj/item/reagent_containers/syringe/mod
+ incompatible_modules = list(/obj/item/mod/module/injector)
+ cooldown_time = 0.5 SECONDS
+
+/obj/item/reagent_containers/syringe/mod
+ name = "MOD injector syringe"
+ desc = "A high-capacity syringe, with a tip fine enough to locate \
+ the emergency injection ports on any suit of armor, penetrating it with ease. Even yours."
+ amount_per_transfer_from_this = 30
+ possible_transfer_amounts = list(5, 10, 15, 20, 30)
+ volume = 30
diff --git a/code/modules/mod/modules/modules_ninja.dm b/code/modules/mod/modules/modules_ninja.dm
new file mode 100644
index 00000000000..69da2287eb7
--- /dev/null
+++ b/code/modules/mod/modules/modules_ninja.dm
@@ -0,0 +1,446 @@
+//Ninja modules for MODsuits
+
+///Cloaking - Lowers the user's visibility, can be interrupted by being touched or attacked.
+/obj/item/mod/module/stealth
+ name = "MOD prototype cloaking module"
+ desc = "A complete retrofitting of the suit, this is a form of visual concealment tech employing esoteric technology \
+ to bend light around the user, as well as mimetic materials to make the surface of the suit match the \
+ surroundings based off sensor data. For some reason, this tech is rarely seen."
+ icon_state = "cloak"
+ module_type = MODULE_TOGGLE
+ complexity = 4
+ active_power_cost = DEFAULT_CHARGE_DRAIN * 2
+ use_power_cost = DEFAULT_CHARGE_DRAIN * 10
+ incompatible_modules = list(/obj/item/mod/module/stealth)
+ cooldown_time = 5 SECONDS
+ /// Whether or not the cloak turns off on bumping.
+ var/bumpoff = TRUE
+ /// The alpha applied when the cloak is on.
+ var/stealth_alpha = 50
+
+/obj/item/mod/module/stealth/on_activation()
+ . = ..()
+ if(!.)
+ return
+ if(bumpoff)
+ RegisterSignal(mod.wearer, COMSIG_LIVING_MOB_BUMP, PROC_REF(unstealth))
+ RegisterSignal(mod.wearer, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, PROC_REF(on_unarmed_attack))
+ RegisterSignal(mod.wearer, COMSIG_ATOM_BULLET_ACT, PROC_REF(on_bullet_act))
+ RegisterSignal(mod.wearer, list(COMSIG_MOB_ITEM_ATTACK, COMSIG_PARENT_ATTACKBY, COMSIG_ATOM_ATTACK_HAND/*, COMSIG_ATOM_HITBY*/, COMSIG_ATOM_HULK_ATTACK, COMSIG_ATOM_ATTACK_PAW, COMSIG_CARBON_CUFF_ATTEMPTED), PROC_REF(unstealth))
+ animate(mod.wearer, alpha = stealth_alpha, time = 1.5 SECONDS)
+ drain_power(use_power_cost)
+
+/obj/item/mod/module/stealth/on_deactivation(display_message = TRUE, deleting = FALSE)
+ . = ..()
+ if(!.)
+ return
+ if(bumpoff)
+ UnregisterSignal(mod.wearer, COMSIG_LIVING_MOB_BUMP)
+ UnregisterSignal(mod.wearer, list(COMSIG_HUMAN_MELEE_UNARMED_ATTACK, COMSIG_MOB_ITEM_ATTACK, COMSIG_PARENT_ATTACKBY, COMSIG_ATOM_ATTACK_HAND, COMSIG_ATOM_BULLET_ACT/*, COMSIG_ATOM_HITBY*/, COMSIG_ATOM_HULK_ATTACK, COMSIG_ATOM_ATTACK_PAW, COMSIG_CARBON_CUFF_ATTEMPTED))
+ animate(mod.wearer, alpha = 255, time = 1.5 SECONDS)
+
+/obj/item/mod/module/stealth/proc/unstealth(datum/source)
+ SIGNAL_HANDLER
+
+ to_chat(mod.wearer, span_warning("[src] gets discharged from contact!"))
+ do_sparks(2, TRUE, src)
+ drain_power(use_power_cost)
+ on_deactivation(display_message = TRUE, deleting = FALSE)
+
+/obj/item/mod/module/stealth/proc/on_unarmed_attack(datum/source, atom/target)
+ SIGNAL_HANDLER
+
+ if(!isliving(target))
+ return
+ unstealth(source)
+
+/obj/item/mod/module/stealth/proc/on_bullet_act(datum/source, obj/projectile/projectile)
+ SIGNAL_HANDLER
+
+ if(projectile.nodamage)
+ return
+ unstealth(source)
+
+//Advanced Cloaking - Doesn't turf off on bump, less power drain, more stealthy.
+/obj/item/mod/module/stealth/ninja
+ name = "MOD advanced cloaking module"
+ desc = "The latest in stealth technology, this module is a definite upgrade over previous versions. \
+ The field has been tuned to be even more responsive and fast-acting, with enough stability to \
+ continue operation of the field even if the user bumps into others. \
+ The power draw has been reduced drastically, making this perfect for activities like \
+ standing near sentry turrets for extended periods of time."
+ icon_state = "cloak_ninja"
+ bumpoff = FALSE
+ stealth_alpha = 20
+ active_power_cost = DEFAULT_CHARGE_DRAIN
+ use_power_cost = DEFAULT_CHARGE_DRAIN * 5
+ cooldown_time = 3 SECONDS
+
+///Camera Vision - Prevents flashes, blocks tracking.
+/obj/item/mod/module/welding/camera_vision
+ name = "MOD camera vision module"
+ desc = "A module installed into the suit's helmet. This specialized piece of technology is built for subterfuge, \
+ replacing the standard visor with a nanotech display; capable of displaying specialized imagery at \
+ just the right frequency to jam all known forms of camera tracking and facial recognition, \
+ as well as automatically dimming incoming flashes of light to protect the user's eyes. Become the unseen."
+ icon_state = "welding_camera"
+ removable = FALSE
+ complexity = 0
+ overlay_state_inactive = null
+
+/obj/item/mod/module/welding/camera_vision/on_suit_activation()
+ . = ..()
+ RegisterSignal(mod.wearer, COMSIG_LIVING_CAN_TRACK, PROC_REF(can_track))
+
+/obj/item/mod/module/welding/camera_vision/on_suit_deactivation(deleting = FALSE)
+ . = ..()
+ UnregisterSignal(mod.wearer, COMSIG_LIVING_CAN_TRACK)
+
+/obj/item/mod/module/welding/camera_vision/proc/can_track(datum/source, mob/user)
+ SIGNAL_HANDLER
+
+ return COMPONENT_CANT_TRACK
+
+//Ninja Star Dispenser - Dispenses ninja stars.
+/obj/item/mod/module/dispenser/ninja
+ name = "MOD ninja star dispenser module"
+ desc = "This piece of Spider Clan technology can exploit known energy-matter equivalence principles, \
+ using the nanites already hosted in the wearer's suit to transmute into monomolecular shuriken. \
+ While these lack the intense bleeding edge of conventional throwing stars, \
+ they have been set to electrify fleeing targets; and branded with the Spider Clan symbol."
+ dispense_type = /obj/item/throwing_star/stamina
+ cooldown_time = 0.5 SECONDS
+
+///Hacker - This module hooks onto your right-clicks with empty hands and causes ninja actions.
+/obj/item/mod/module/hacker
+ name = "MOD hacker module"
+ desc = "Built for one purpose, electronic warfare, this module is built into the hands. \
+ Using near-field communication alongside precise electro-stimulation of the wires in machines, \
+ this decker's dream is normally used to pass through doors like a phantom. \
+ It's also capable of non-precise electro-stimulation of an assassin-saboteur's opponents on disarming attacks."
+ icon_state = "hacker"
+ removable = FALSE
+ incompatible_modules = list(/obj/item/mod/module/hacker)
+ /// Minimum amount of power we can drain in a single drain action
+ var/mindrain = 200
+ /// Maximum amount of power we can drain in a single drain action
+ var/maxdrain = 400
+ /// Whether or not the communication console hack was used to summon another antagonist.
+ var/communication_console_hack_success = FALSE
+ /// How many times the module has been used to force open doors.
+ var/door_hack_counter = 0
+ ///Used for the research objective (see antagonist file)
+ var/datum/techweb/stored_research
+
+/obj/item/mod/module/hacker/on_suit_activation()
+ RegisterSignal(mod.wearer, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, PROC_REF(hack))
+
+/obj/item/mod/module/hacker/on_suit_deactivation(deleting = FALSE)
+ UnregisterSignal(mod.wearer, COMSIG_HUMAN_EARLY_UNARMED_ATTACK)
+
+/obj/item/mod/module/hacker/proc/hack(mob/living/carbon/human/source, atom/target, proximity, modifiers)
+ SIGNAL_HANDLER
+
+ if(!LAZYACCESS(modifiers, RIGHT_CLICK) || !proximity)
+ return NONE
+ target.add_fingerprint(mod.wearer)
+ return target.ninjadrain_act(mod.wearer, src)
+
+/obj/item/mod/module/hacker/proc/charge_message(atom/drained_atom, drain_amount)
+ if(drain_amount)
+ to_chat(mod.wearer, span_notice("Получено [drain_amount] единиц энергии с [drained_atom]."))
+ else
+ to_chat(mod.wearer, span_warning("[drained_atom] истощен, необходимо найти другой источник питания!"))
+
+///Weapon Recall - Teleports your katana to you, prevents gun use.
+/obj/item/mod/module/weapon_recall
+ name = "MOD weapon recall module"
+ desc = "The cornerstone of a clanmember's life as a blademaster, and a module symbolizing their eternal bond with their weapon. \
+ This hooks to the micro bluespace drive inside an energy katana's handle, capable of recalling it to the user's \
+ skilled hands wherever they are. However, those that make such a bond with their weapon are cursed to \
+ fusing their existence with acts of combat, with a singular purpose; Cutting Down Their Opponent. \
+ Their hand a hand that is cutting, their body a body that is cutting, their mind, a mind that is cutting. \
+ Ranged weapons are forbidden."
+ icon_state = "recall"
+ removable = FALSE
+ module_type = MODULE_USABLE
+ use_power_cost = DEFAULT_CHARGE_DRAIN * 2
+ incompatible_modules = list(/obj/item/mod/module/weapon_recall)
+ cooldown_time = 0.5 SECONDS
+ /// The item linked to the module that will get recalled.
+ var/obj/item/linked_weapon
+ /// The accepted typepath we can link to.
+ var/accepted_type = /obj/item/energy_katana
+
+/obj/item/mod/module/weapon_recall/on_suit_activation()
+ ADD_TRAIT(mod.wearer, TRAIT_NOGUNS, MOD_TRAIT)
+
+/obj/item/mod/module/weapon_recall/on_suit_deactivation(deleting = FALSE)
+ REMOVE_TRAIT(mod.wearer, TRAIT_NOGUNS, MOD_TRAIT)
+
+/obj/item/mod/module/weapon_recall/on_use()
+ . = ..()
+ if(!.)
+ return
+ if(!linked_weapon)
+ var/obj/item/weapon_to_link = mod.wearer.is_holding_item_of_type(accepted_type)
+ if(!weapon_to_link)
+ balloon_alert(mod.wearer, "can't locate weapon!")
+ return
+ set_weapon(weapon_to_link)
+ balloon_alert(mod.wearer, "[linked_weapon.name] linked")
+ return
+ if(linked_weapon in mod.wearer.get_all_contents())
+ balloon_alert(mod.wearer, "already on self!")
+ return
+ var/distance = get_dist(mod.wearer, linked_weapon)
+ var/in_view = (linked_weapon in view(mod.wearer))
+ if(!in_view && !drain_power(use_power_cost * distance))
+ balloon_alert(mod.wearer, "not enough charge!")
+ return
+ linked_weapon.forceMove(linked_weapon.drop_location())
+ if(in_view)
+ do_sparks(5, FALSE, linked_weapon)
+ mod.wearer.visible_message(span_danger("[linked_weapon] flies towards [mod.wearer]!"),span_warning("You hold out your hand and [linked_weapon] flies towards you!"))
+ linked_weapon.throw_at(mod.wearer, distance+1, linked_weapon.throw_speed, mod.wearer)
+ else
+ recall_weapon()
+
+/obj/item/mod/module/weapon_recall/proc/set_weapon(obj/item/weapon)
+ linked_weapon = weapon
+ RegisterSignal(linked_weapon, COMSIG_MOVABLE_IMPACT, PROC_REF(catch_weapon))
+ RegisterSignal(linked_weapon, COMSIG_PARENT_QDELETING, PROC_REF(deleted_weapon))
+
+/obj/item/mod/module/weapon_recall/proc/recall_weapon(caught = FALSE)
+ linked_weapon.forceMove(get_turf(src))
+ var/alert = ""
+ if(mod.wearer.put_in_hands(linked_weapon))
+ alert = "[linked_weapon.name] teleports to your hand"
+ else if(mod.wearer.equip_to_slot_if_possible(linked_weapon, ITEM_SLOT_BELT, disable_warning = TRUE))
+ alert = "[linked_weapon.name] sheathes itself in your belt"
+ else
+ alert = "[linked_weapon.name] teleports under you"
+ if(caught)
+ if(mod.wearer.is_holding(linked_weapon))
+ alert = "you catch [linked_weapon.name]"
+ else
+ alert = "[linked_weapon.name] lands under you"
+ else
+ do_sparks(5, FALSE, linked_weapon)
+ if(alert)
+ balloon_alert(mod.wearer, alert)
+
+/obj/item/mod/module/weapon_recall/proc/catch_weapon(obj/item/source, atom/hit_atom, datum/thrownthing/thrownthing)
+ SIGNAL_HANDLER
+
+ if(!mod)
+ return
+ if(hit_atom != mod.wearer)
+ return
+ INVOKE_ASYNC(src, PROC_REF(recall_weapon), TRUE)
+ return COMPONENT_MOVABLE_IMPACT_NEVERMIND
+
+/obj/item/mod/module/weapon_recall/proc/deleted_weapon(obj/item/source)
+ SIGNAL_HANDLER
+
+ linked_weapon = null
+
+//Reinforced DNA Lock - Gibs if wrong DNA, emp-proof.
+/obj/item/mod/module/dna_lock/reinforced
+ name = "MOD reinforced DNA lock module"
+ desc = "A module which engages with the various locks and seals tied to the suit's systems, \
+ enabling it to only be worn by someone corresponding with the user's exact DNA profile. \
+ Due to utilizing a skintight dampening shield, this one is entirely sealed against electromagnetic interference; \
+ it also dutifully protects the secrets of the Spider Clan from unknowing outsiders."
+ icon_state = "dnalock_ninja"
+ use_power_cost = DEFAULT_CHARGE_DRAIN * 0.5
+
+/obj/item/mod/module/dna_lock/reinforced/on_mod_activation(datum/source, mob/user)
+ . = ..()
+ if(. != MOD_CANCEL_ACTIVATE || !isliving(user))
+ return
+ var/mob/living/living_user = user
+ to_chat(living_user, span_danger("fATaL EERRoR: 382200-*#00CODE RED\nUNAUTHORIZED USE DETECteD\nCoMMENCING SUB-R0UTIN3 13...\nTERMInATING U-U-USER..."))
+ living_user.gib()
+
+/obj/item/mod/module/dna_lock/reinforced/on_emp(datum/source, severity)
+ return
+
+//EMP Pulse - In addition to normal shielding, can also launch an EMP itself.
+/obj/item/mod/module/emp_shield/pulse
+ name = "MOD EMP pulse module"
+ desc = "This module is normally set to activate on dramatic gestures, inverting and expanding the suit's \
+ EMP dampening shield to cause an electromagnetic pulse of its own. While this won't interfere with the wearer, \
+ it will piss off everyone around them."
+ icon_state = "emp_pulse"
+ module_type = MODULE_USABLE
+ use_power_cost = DEFAULT_CHARGE_DRAIN * 10
+ cooldown_time = 8 SECONDS
+
+/obj/item/mod/module/emp_shield/pulse/on_use()
+ . = ..()
+ if(!.)
+ return
+ playsound(src, 'sound/effects/empulse.ogg', 60, TRUE)
+ empulse(src, heavy_range = 4, light_range = 6)
+ drain_power(use_power_cost)
+
+///Status Readout - Puts a lot of information including health, nutrition, fingerprints, temperature to the suit TGUI.
+/obj/item/mod/module/status_readout
+ name = "MOD status readout module"
+ desc = "A once-common module, this technology went unfortunately out of fashion; \
+ and right into the arachnid grip of the Spider Clan. This hooks into the suit's spine, \
+ capable of capturing and displaying all possible biometric data of the wearer; sleep, nutrition, fitness, fingerprints, \
+ and even useful information such as their overall health and wellness."
+ icon_state = "status"
+ complexity = 1
+ use_power_cost = DEFAULT_CHARGE_DRAIN * 0.1
+ incompatible_modules = list(/obj/item/mod/module/status_readout)
+ tgui_id = "status_readout"
+
+/obj/item/mod/module/status_readout/add_ui_data()
+ . = ..()
+ .["statustime"] = station_time_timestamp()
+ .["statusid"] = GLOB.round_id
+ .["statushealth"] = mod.wearer?.health || 0
+ .["statusmaxhealth"] = mod.wearer?.getMaxHealth() || 0
+ .["statusbrute"] = mod.wearer?.getBruteLoss() || 0
+ .["statusburn"] = mod.wearer?.getFireLoss() || 0
+ .["statustoxin"] = mod.wearer?.getToxLoss() || 0
+ .["statusoxy"] = mod.wearer?.getOxyLoss() || 0
+ .["statustemp"] = mod.wearer?.bodytemperature || 0
+ .["statusnutrition"] = mod.wearer?.nutrition || 0
+ //.["statusfingerprints"] = mod.wearer ? md5(mod.wearer.dna.unique_identity) : null
+ .["statusdna"] = mod.wearer?.dna.unique_enzymes
+ .["statusviruses"] = null
+ if(!length(mod.wearer?.diseases))
+ return
+ var/list/viruses = list()
+ for(var/datum/disease/virus as anything in mod.wearer.diseases)
+ var/list/virus_data = list()
+ virus_data["name"] = virus.name
+ virus_data["type"] = virus.spread_text
+ virus_data["stage"] = virus.stage
+ virus_data["maxstage"] = virus.max_stages
+ virus_data["cure"] = virus.cure_text
+ viruses += list(virus_data)
+ .["statusviruses"] = viruses
+
+///Energy Net - Ensnares enemies in a net that prevents movement.
+/obj/item/mod/module/energy_net
+ name = "MOD energy net module"
+ desc = "A custom-built net-thrower. While conventional implementations of this capturing device \
+ tilize monomolecular fibers or cutting razorwire, this uses hardlight technology to deploy a \
+ trapping field capable of immobilizing even the strongest opponents."
+ icon_state = "energy_net"
+ removable = FALSE
+ module_type = MODULE_ACTIVE
+ use_power_cost = DEFAULT_CHARGE_DRAIN * 6
+ incompatible_modules = list(/obj/item/mod/module/energy_net)
+ cooldown_time = 1.5 SECONDS
+
+/obj/item/mod/module/energy_net/on_select_use(atom/target)
+ . = ..()
+ if(!.)
+ return
+ if(!isliving(target))
+ balloon_alert(mod.wearer, "invalid target!")
+ return
+ var/mob/living/living_target = target
+ if(locate(/obj/structure/energy_net) in get_turf(living_target))
+ balloon_alert(mod.wearer, "already trapped!")
+ return
+ for(var/turf/between_turf as anything in get_line(get_turf(mod.wearer), get_turf(living_target)))
+ if(between_turf.density)
+ balloon_alert(mod.wearer, "not through obstacles!")
+ return
+ //if(IS_SPACE_NINJA(mod.wearer))
+ // mod.wearer.say("Get over here!", forced = type)
+ mod.wearer.Beam(living_target, "n_beam", time = 1.5 SECONDS)
+ var/obj/structure/energy_net/net = new /obj/structure/energy_net(living_target.drop_location())
+ net.affecting = living_target
+ mod.wearer.visible_message(span_danger("[mod.wearer] caught [living_target] with an energy net!"), span_notice("You caught [living_target] with an energy net!"))
+ if(living_target.buckled)
+ living_target.buckled.unbuckle_mob(living_target, force = TRUE)
+ net.buckle_mob(living_target, force = TRUE)
+ drain_power(use_power_cost)
+
+///Adrenaline Boost - Stops all stuns the ninja is affected with, increases his speed.
+/obj/item/mod/module/adrenaline_boost
+ name = "MOD adrenaline boost module"
+ desc = "The secrets of the Spider Clan are many. The exact specifications of their suits, \
+ the techniques they use to make every singular cut make their enemies weep with admiration, \
+ but one of their greatest mysteries is the chemical compound their assassin-saboteurs use in times of need. \
+ It's capable of clearing any fatigue whatsoever from the user, any immobilizing effect, and can even \
+ cure total paralysis. All that's known is that the fluid requires radiation to properly 'cook,' \
+ so this module demands radium to be refilled with."
+ icon_state = "adrenaline_boost"
+ removable = FALSE
+ module_type = MODULE_USABLE
+ incompatible_modules = list(/obj/item/mod/module/adrenaline_boost)
+ cooldown_time = 12 SECONDS
+ /// What reagent we need to refill?
+ var/reagent_required = /datum/reagent/uranium/radium
+ /// How much of a reagent we need to refill the boost.
+ var/reagent_required_amount = 20
+
+/obj/item/mod/module/adrenaline_boost/Initialize(mapload)
+ . = ..()
+ create_reagents(reagent_required_amount)
+ reagents.add_reagent(reagent_required, reagent_required_amount)
+
+/obj/item/mod/module/adrenaline_boost/on_use()
+ if(!reagents.has_reagent(reagent_required, reagent_required_amount))
+ balloon_alert(mod.wearer, "no charge!")
+ return
+ . = ..()
+ if(!.)
+ return
+ //if(IS_SPACE_NINJA(mod.wearer))
+ // mod.wearer.say(pick_list_replacements(NINJA_FILE, "lines"), forced = type)
+ to_chat(mod.wearer, span_notice("You have used the adrenaline boost."))
+ mod.wearer.SetUnconscious(0)
+ mod.wearer.SetStun(0)
+ mod.wearer.SetKnockdown(0)
+ mod.wearer.SetImmobilized(0)
+ mod.wearer.SetParalyzed(0)
+ mod.wearer.adjustStaminaLoss(-200)
+ mod.wearer.stuttering = 0
+ mod.wearer.reagents.add_reagent(/datum/reagent/medicine/stimulants, 5)
+ reagents.remove_reagent(reagent_required, reagents.total_volume * 0.75)
+ addtimer(CALLBACK(src, PROC_REF(boost_aftereffects), mod.wearer), 7 SECONDS)
+
+/obj/item/mod/module/adrenaline_boost/on_install()
+ RegisterSignal(mod, COMSIG_PARENT_ATTACKBY, PROC_REF(on_attackby))
+
+/obj/item/mod/module/adrenaline_boost/on_uninstall(deleting)
+ UnregisterSignal(mod, COMSIG_PARENT_ATTACKBY)
+
+/obj/item/mod/module/adrenaline_boost/attackby(obj/item/attacking_item, mob/user, params)
+ if(charge_boost(attacking_item, user))
+ return TRUE
+ return ..()
+
+/obj/item/mod/module/adrenaline_boost/proc/on_attackby(datum/source, obj/item/attacking_item, mob/user)
+ SIGNAL_HANDLER
+
+ if(charge_boost(attacking_item, user))
+ return COMPONENT_NO_AFTERATTACK
+ return NONE
+
+/obj/item/mod/module/adrenaline_boost/proc/charge_boost(obj/item/attacking_item, mob/user)
+ if(!attacking_item.is_open_container())
+ return FALSE
+ if(reagents.has_reagent(reagent_required, reagent_required_amount))
+ balloon_alert(mod.wearer, "already charged!")
+ return FALSE
+ if(!attacking_item.reagents.trans_id_to(src, reagent_required, reagent_required_amount))
+ return FALSE
+ balloon_alert(mod.wearer, "charge [reagents.has_reagent(reagent_required, reagent_required_amount) ? "fully" : "partially"] reloaded")
+ return TRUE
+
+/obj/item/mod/module/adrenaline_boost/proc/boost_aftereffects(mob/affected_mob)
+ if(!affected_mob)
+ return
+ reagents.trans_to(affected_mob, reagents.total_volume)
+ to_chat(affected_mob, span_danger("You are beginning to feel the after-effect of the injection."))
diff --git a/code/modules/mod/modules/modules_science.dm b/code/modules/mod/modules/modules_science.dm
new file mode 100644
index 00000000000..02025ea1b42
--- /dev/null
+++ b/code/modules/mod/modules/modules_science.dm
@@ -0,0 +1,132 @@
+//Science modules for MODsuits
+
+///Reagent Scanner - Lets the user scan reagents.
+/obj/item/mod/module/reagent_scanner
+ name = "MOD reagent scanner module"
+ desc = "A module based off research-oriented Nanotrasen HUDs, this is capable of scanning the contents of \
+ containers and projecting the information in an easy-to-read format on the wearer's display. \
+ It cannot detect flavors, so that's up to you."
+ icon_state = "scanner"
+ module_type = MODULE_TOGGLE
+ complexity = 1
+ active_power_cost = DEFAULT_CHARGE_DRAIN * 0.2
+ incompatible_modules = list(/obj/item/mod/module/reagent_scanner)
+ cooldown_time = 0.5 SECONDS
+
+/obj/item/mod/module/reagent_scanner/on_activation()
+ . = ..()
+ if(!.)
+ return
+ mod.helmet.clothing_flags |= SCAN_REAGENTS
+
+/obj/item/mod/module/reagent_scanner/on_deactivation(display_message = TRUE, deleting = FALSE)
+ . = ..()
+ if(!.)
+ return
+ mod.helmet.clothing_flags &= ~SCAN_REAGENTS
+
+/obj/item/mod/module/reagent_scanner/advanced
+ name = "MOD advanced reagent scanner module"
+ complexity = 0
+ removable = FALSE
+ var/explosion_detection_dist = 21
+
+/obj/item/mod/module/reagent_scanner/advanced/on_activation()
+ . = ..()
+ if(!.)
+ return
+ mod.helmet.clothing_flags |= SCAN_REAGENTS
+ RegisterSignal(SSdcs, COMSIG_GLOB_EXPLOSION, PROC_REF(sense_explosion))
+
+/obj/item/mod/module/reagent_scanner/advanced/on_deactivation(display_message = TRUE, deleting = FALSE)
+ . = ..()
+ if(!.)
+ return
+ mod.helmet.clothing_flags |= SCAN_REAGENTS
+ UnregisterSignal(SSdcs, COMSIG_GLOB_EXPLOSION)
+
+/obj/item/mod/module/reagent_scanner/advanced/proc/sense_explosion(datum/source, turf/epicenter,
+ devastation_range, heavy_impact_range, light_impact_range, took, orig_dev_range, orig_heavy_range, orig_light_range)
+ SIGNAL_HANDLER
+ var/turf/wearer_turf = get_turf(mod.wearer)
+ if(wearer_turf.z != epicenter.z)
+ return
+ if(get_dist(epicenter, wearer_turf) > explosion_detection_dist)
+ return
+ to_chat(mod.wearer, span_notice("Explosion detected! Epicenter: [devastation_range], Outer: [heavy_impact_range], Shock: [light_impact_range]"))
+
+///Anti-Gravity - Makes the user weightless.
+/obj/item/mod/module/anomaly_locked/antigrav
+ name = "MOD anti-gravity module"
+ desc = "A module that uses a gravitational core to make the user completely weightless."
+ icon_state = "antigrav"
+ module_type = MODULE_TOGGLE
+ complexity = 3
+ active_power_cost = DEFAULT_CHARGE_DRAIN * 0.7
+ incompatible_modules = list(/obj/item/mod/module/anomaly_locked)
+ cooldown_time = 0.5 SECONDS
+ accepted_anomalies = list(/obj/item/assembly/signaler/anomaly/grav)
+
+/obj/item/mod/module/anomaly_locked/antigrav/on_activation()
+ . = ..()
+ if(!.)
+ return
+ if(mod.wearer.has_gravity())
+ new /obj/effect/temp_visual/mook_dust(get_turf(src))
+ mod.wearer.AddElement(/datum/element/forced_gravity, 0)
+ mod.wearer.update_gravity(mod.wearer.has_gravity())
+ playsound(src, 'sound/effects/gravhit.ogg', 50)
+
+/obj/item/mod/module/anomaly_locked/antigrav/on_deactivation(display_message = TRUE, deleting = FALSE)
+ . = ..()
+ if(!.)
+ return
+ mod.wearer.RemoveElement(/datum/element/forced_gravity, 0)
+ mod.wearer.update_gravity(mod.wearer.has_gravity())
+ if(deleting)
+ return
+ if(mod.wearer.has_gravity())
+ new /obj/effect/temp_visual/mook_dust(get_turf(src))
+ playsound(src, 'sound/effects/gravhit.ogg', 50)
+
+/obj/item/mod/module/anomaly_locked/antigrav/prebuilt
+ prebuilt = TRUE
+
+///Teleporter - Lets the user teleport to a nearby location.
+/obj/item/mod/module/anomaly_locked/teleporter
+ name = "MOD teleporter module"
+ desc = "A module that uses a bluespace core to let the user transport their particles elsewhere."
+ icon_state = "teleporter"
+ module_type = MODULE_ACTIVE
+ complexity = 3
+ use_power_cost = DEFAULT_CHARGE_DRAIN * 5
+ cooldown_time = 5 SECONDS
+ accepted_anomalies = list(/obj/item/assembly/signaler/anomaly/bluespace)
+ /// Time it takes to teleport
+ var/teleport_time = 3 SECONDS
+
+/obj/item/mod/module/anomaly_locked/teleporter/on_select_use(atom/target)
+ . = ..()
+ if(!.)
+ return
+ var/turf/open/target_turf = get_turf(target)
+ if(!istype(target_turf) || target_turf.is_blocked_turf() || !(target_turf in view(mod.wearer)))
+ balloon_alert(mod.wearer, "invalid target!")
+ return
+ balloon_alert(mod.wearer, "teleporting...")
+ var/matrix/pre_matrix = matrix()
+ pre_matrix.Scale(4, 0.25)
+ var/matrix/post_matrix = matrix()
+ post_matrix.Scale(0.25, 4)
+ animate(mod.wearer, teleport_time, color = COLOR_CYAN, transform = pre_matrix.Multiply(mod.wearer.transform), easing = SINE_EASING|EASE_OUT)
+ if(!do_after(mod.wearer, teleport_time, target = mod))
+ balloon_alert(mod.wearer, "interrupted!")
+ animate(mod.wearer, teleport_time*0.1, color = null, transform = post_matrix.Multiply(mod.wearer.transform), easing = SINE_EASING|EASE_OUT)
+ return
+ animate(mod.wearer, teleport_time*0.1, color = null, transform = post_matrix.Multiply(mod.wearer.transform), easing = SINE_EASING|EASE_OUT)
+ if(!do_teleport(mod.wearer, target_turf, asoundin = 'sound/effects/phasein.ogg'))
+ return
+ drain_power(use_power_cost)
+
+/obj/item/mod/module/anomaly_locked/teleporter/prebuilt
+ prebuilt = TRUE
diff --git a/code/modules/mod/modules/modules_security.dm b/code/modules/mod/modules/modules_security.dm
new file mode 100644
index 00000000000..d3ac5384646
--- /dev/null
+++ b/code/modules/mod/modules/modules_security.dm
@@ -0,0 +1,136 @@
+//Security modules for MODsuits
+
+///Magnetic Harness - Automatically puts guns in your suit storage when you drop them.
+/obj/item/mod/module/magnetic_harness
+ name = "MOD magnetic harness module"
+ desc = "Based off old TerraGov harness kits, this magnetic harness automatically attaches dropped guns back to the wearer."
+ icon_state = "mag_harness"
+ complexity = 2
+ use_power_cost = DEFAULT_CHARGE_DRAIN
+ incompatible_modules = list(/obj/item/mod/module/magnetic_harness)
+ /// Time before we activate the magnet.
+ var/magnet_delay = 0.8 SECONDS
+ /// The typecache of all guns we allow.
+ var/static/list/guns_typecache
+ /// The guns already allowed by the modsuit chestplate.
+ var/list/already_allowed_guns = list()
+
+/obj/item/mod/module/magnetic_harness/Initialize(mapload)
+ . = ..()
+ if(!guns_typecache)
+ guns_typecache = typecacheof(list(/obj/item/gun/ballistic, /obj/item/gun/energy, /obj/item/gun/grenadelauncher, /obj/item/gun/chem, /obj/item/gun/syringe))
+
+/obj/item/mod/module/magnetic_harness/on_install()
+ already_allowed_guns = guns_typecache & mod.chestplate.allowed
+ mod.chestplate.allowed |= guns_typecache
+
+/obj/item/mod/module/magnetic_harness/on_uninstall(deleting = FALSE)
+ if(deleting)
+ return
+ mod.chestplate.allowed -= (guns_typecache - already_allowed_guns)
+
+/obj/item/mod/module/magnetic_harness/on_suit_activation()
+ RegisterSignal(mod.wearer, COMSIG_MOB_UNEQUIPPED_ITEM, PROC_REF(check_dropped_item))
+
+/obj/item/mod/module/magnetic_harness/on_suit_deactivation(deleting = FALSE)
+ UnregisterSignal(mod.wearer, COMSIG_MOB_UNEQUIPPED_ITEM)
+
+/obj/item/mod/module/magnetic_harness/proc/check_dropped_item(datum/source, obj/item/dropped_item, force, new_location)
+ SIGNAL_HANDLER
+
+ if(!is_type_in_typecache(dropped_item, guns_typecache))
+ return
+ if(new_location != get_turf(src))
+ return
+ addtimer(CALLBACK(src, PROC_REF(pick_up_item), dropped_item), magnet_delay)
+
+/obj/item/mod/module/magnetic_harness/proc/pick_up_item(obj/item/item)
+ if(!isturf(item.loc) || !item.Adjacent(mod.wearer))
+ return
+ if(!mod.wearer.equip_to_slot_if_possible(item, ITEM_SLOT_SUITSTORE, qdel_on_fail = FALSE, disable_warning = TRUE))
+ return
+ playsound(src, 'sound/items/modsuit/magnetic_harness.ogg', 50, TRUE)
+ balloon_alert(mod.wearer, "[item] reattached")
+ drain_power(use_power_cost)
+
+///Holster - Instantly holsters any not huge gun.
+/obj/item/mod/module/holster
+ name = "MOD holster module"
+ desc = "Based off typical storage compartments, this system allows the suit to holster a \
+ standard firearm across its surface and allow for extremely quick retrieval. \
+ While some users prefer the chest, others the forearm for quick deployment, \
+ some law enforcement prefer the holster to extend from the thigh."
+ icon_state = "holster"
+ module_type = MODULE_USABLE
+ complexity = 2
+ incompatible_modules = list(/obj/item/mod/module/holster)
+ cooldown_time = 0.5 SECONDS
+ allowed_inactive = TRUE
+ /// Gun we have holstered.
+ var/obj/item/gun/holstered
+
+/obj/item/mod/module/holster/on_use()
+ . = ..()
+ if(!.)
+ return
+ if(!holstered)
+ var/obj/item/gun/holding = mod.wearer.get_active_held_item()
+ if(!holding)
+ balloon_alert(mod.wearer, "nothing to holster!")
+ return
+ if(!istype(holding) || holding.w_class > WEIGHT_CLASS_BULKY)
+ balloon_alert(mod.wearer, "it doesn't fit!")
+ return
+ if(mod.wearer.transferItemToLoc(holding, src, force = FALSE, silent = TRUE))
+ holstered = holding
+ balloon_alert(mod.wearer, "weapon holstered")
+ playsound(src, 'sound/weapons/gun/revolver/empty.ogg', 100, TRUE)
+ else if(mod.wearer.put_in_active_hand(holstered, forced = FALSE, ignore_animation = TRUE))
+ balloon_alert(mod.wearer, "weapon drawn")
+ playsound(src, 'sound/weapons/gun/revolver/empty.ogg', 100, TRUE)
+ else
+ balloon_alert(mod.wearer, "holster full!")
+
+/obj/item/mod/module/holster/on_uninstall(deleting = FALSE)
+ if(holstered)
+ holstered.forceMove(drop_location())
+
+/obj/item/mod/module/holster/Exited(atom/movable/gone, direction)
+ . = ..()
+ if(gone == holstered)
+ holstered = null
+
+/obj/item/mod/module/holster/Destroy()
+ QDEL_NULL(holstered)
+ return ..()
+
+///Megaphone - Lets you speak loud.
+/obj/item/mod/module/megaphone
+ name = "MOD megaphone module"
+ desc = "A microchip megaphone linked to a MODsuit, for very important purposes, like: loudness."
+ icon_state = "megaphone"
+ module_type = MODULE_TOGGLE
+ complexity = 1
+ use_power_cost = DEFAULT_CHARGE_DRAIN * 0.5
+ incompatible_modules = list(/obj/item/mod/module/megaphone)
+ cooldown_time = 0.5 SECONDS
+ /// List of spans we add to the speaker.
+ var/list/voicespan = list(SPAN_COMMAND)
+
+/obj/item/mod/module/megaphone/on_activation()
+ . = ..()
+ if(!.)
+ return
+ RegisterSignal(mod.wearer, COMSIG_MOB_SAY, PROC_REF(handle_speech))
+
+/obj/item/mod/module/megaphone/on_deactivation(display_message = TRUE, deleting = FALSE)
+ . = ..()
+ if(!.)
+ return
+ UnregisterSignal(mod.wearer, COMSIG_MOB_SAY)
+
+/obj/item/mod/module/megaphone/proc/handle_speech(datum/source, list/speech_args)
+ SIGNAL_HANDLER
+
+ speech_args[SPEECH_SPANS] |= voicespan
+ drain_power(use_power_cost)
diff --git a/code/modules/mod/modules/modules_service.dm b/code/modules/mod/modules/modules_service.dm
new file mode 100644
index 00000000000..e983bbc3dbc
--- /dev/null
+++ b/code/modules/mod/modules/modules_service.dm
@@ -0,0 +1,56 @@
+//Service modules for MODsuits
+
+///Bike Horn - Plays a bike horn sound.
+/obj/item/mod/module/bikehorn
+ name = "MOD bike horn module"
+ desc = "A shoulder-mounted piece of heavy sonic artillery, this module uses the finest femto-manipulator technology to \
+ precisely deliver an almost lethal squeeze to... a bike horn, producing a significantly memorable sound."
+ icon_state = "bikehorn"
+ module_type = MODULE_USABLE
+ complexity = 1
+ use_power_cost = DEFAULT_CHARGE_DRAIN
+ incompatible_modules = list(/obj/item/mod/module/bikehorn)
+ cooldown_time = 1 SECONDS
+
+/obj/item/mod/module/bikehorn/on_use()
+ . = ..()
+ if(!.)
+ return
+ playsound(src, 'sound/items/bikehorn.ogg', 100, FALSE)
+ drain_power(use_power_cost)
+
+///Microwave Beam - Microwaves items instantly.
+/obj/item/mod/module/microwave_beam
+ name = "MOD microwave beam module"
+ desc = "An oddly domestic device, this module is installed into the user's palm, \
+ hooking up with culinary scanners located in the helmet to blast food with precise microwave radiation, \
+ allowing them to cook food from a distance, with the greatest of ease. Not recommended for use against grapes."
+ icon_state = "microwave_beam"
+ module_type = MODULE_ACTIVE
+ complexity = 2
+ use_power_cost = DEFAULT_CHARGE_DRAIN * 5
+ incompatible_modules = list(/obj/item/mod/module/microwave_beam)
+ cooldown_time = 10 SECONDS
+
+/obj/item/mod/module/microwave_beam/on_select_use(atom/target)
+ . = ..()
+ if(!.)
+ return
+ if(!istype(target, /obj/item))
+ return
+ if(!isturf(target.loc))
+ balloon_alert(mod.wearer, "must be on the floor!")
+ return
+ var/obj/item/microwave_target = target
+ var/datum/effect_system/spark_spread/spark_effect = new()
+ spark_effect.set_up(2, 1, mod.wearer)
+ spark_effect.start()
+ mod.wearer.Beam(target,icon_state="lightning[rand(1,12)]", time = 5)
+ if(microwave_target.microwave_act())
+ playsound(src, 'sound/machines/microwave/microwave-end.ogg', 50, FALSE)
+ else
+ balloon_alert(mod.wearer, "can't be microwaved!")
+ var/datum/effect_system/spark_spread/spark_effect_two = new()
+ spark_effect_two.set_up(2, 1, microwave_target)
+ spark_effect_two.start()
+ drain_power(use_power_cost)
diff --git a/code/modules/mod/modules/modules_storage.dm b/code/modules/mod/modules/modules_storage.dm
new file mode 100644
index 00000000000..25caad6806f
--- /dev/null
+++ b/code/modules/mod/modules/modules_storage.dm
@@ -0,0 +1,60 @@
+/obj/item/mod/module/storage
+ name = "MOD storage module"
+ desc = "What amounts to a series of integrated storage compartments and specialized pockets installed across \
+ the surface of the suit, useful for storing various bits, and or bobs."
+ icon_state = "storage"
+ complexity = 3
+ incompatible_modules = list(/obj/item/mod/module/storage)
+ var/datum/component/storage/concrete/storage
+ var/max_w_class = WEIGHT_CLASS_NORMAL
+ var/max_combined_w_class = 15
+ var/max_items = 7
+
+/obj/item/mod/module/storage/Initialize(mapload)
+ . = ..()
+ storage = AddComponent(/datum/component/storage/concrete)
+ storage.max_w_class = max_w_class
+ storage.max_combined_w_class = max_combined_w_class
+ storage.max_items = max_items
+ storage.allow_big_nesting = TRUE
+ SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, TRUE)
+
+/obj/item/mod/module/storage/on_install()
+ var/datum/component/storage/modstorage = mod.AddComponent(/datum/component/storage, storage)
+ modstorage.max_w_class = max_w_class
+ modstorage.max_combined_w_class = max_combined_w_class
+ modstorage.max_items = max_items
+ SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, FALSE)
+
+/obj/item/mod/module/storage/on_uninstall(deleting = FALSE)
+ var/datum/component/storage/modstorage = mod.GetComponent(/datum/component/storage)
+ storage.slaves -= modstorage
+ qdel(modstorage)
+ SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, TRUE)
+
+/obj/item/mod/module/storage/large_capacity
+ name = "MOD expanded storage module"
+ desc = "Reverse engineered by Nakamura Engineering from Donk Corporation designs, this system of hidden compartments \
+ is entirely within the suit, distributing items and weight evenly to ensure a comfortable experience for the user; \
+ whether smuggling, or simply hauling."
+ icon_state = "storage_large"
+ max_combined_w_class = 21
+ max_items = 14
+
+/obj/item/mod/module/storage/syndicate
+ name = "MOD syndicate storage module"
+ desc = "A storage system using nanotechnology developed by Cybersun Industries, these compartments use \
+ esoteric technology to compress the physical matter of items put inside of them, \
+ essentially shrinking items for much easier and more portable storage."
+ icon_state = "storage_syndi"
+ max_combined_w_class = 30
+ max_items = 21
+
+/obj/item/mod/module/storage/bluespace
+ name = "MOD bluespace storage module"
+ desc = "A storage system developed by Nanotrasen, these compartments employ \
+ miniaturized bluespace pockets for the ultimate in storage technology; regardless of the weight of objects put inside."
+ icon_state = "storage_large"
+ max_w_class = WEIGHT_CLASS_GIGANTIC
+ max_combined_w_class = 60
+ max_items = 21
diff --git a/code/modules/mod/modules/modules_supply.dm b/code/modules/mod/modules/modules_supply.dm
new file mode 100644
index 00000000000..04f0aaf73ad
--- /dev/null
+++ b/code/modules/mod/modules/modules_supply.dm
@@ -0,0 +1,306 @@
+//Supply modules for MODsuits
+
+///Internal GPS - Extends a GPS you can use.
+/obj/item/mod/module/gps
+ name = "MOD internal GPS module"
+ desc = "This module uses common Nanotrasen technology to calculate the user's position anywhere in space, \
+ down to the exact coordinates. This information is fed to a central database viewable from the device itself, \
+ though using it to help people is up to you."
+ icon_state = "gps"
+ module_type = MODULE_USABLE
+ complexity = 1
+ use_power_cost = DEFAULT_CHARGE_DRAIN * 0.2
+ incompatible_modules = list(/obj/item/mod/module/gps)
+ cooldown_time = 0.5 SECONDS
+ allowed_inactive = TRUE
+
+/obj/item/mod/module/gps/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/gps/item, "MOD0")
+
+/obj/item/mod/module/gps/on_use()
+ . = ..()
+ if(!.)
+ return
+ attack_self(mod.wearer)
+
+///Hydraulic Clamp - Lets you pick up and drop crates.
+/obj/item/mod/module/clamp
+ name = "MOD hydraulic clamp module"
+ desc = "A series of actuators installed into both arms of the suit, boasting a lifting capacity of almost a ton. \
+ However, this design has been locked by Nanotrasen to be primarily utilized for lifting various crates. \
+ A lot of people would say that loading cargo is a dull job, but you could not disagree more."
+ icon_state = "clamp"
+ module_type = MODULE_ACTIVE
+ complexity = 3
+ use_power_cost = DEFAULT_CHARGE_DRAIN
+ incompatible_modules = list(/obj/item/mod/module/clamp)
+ cooldown_time = 0.5 SECONDS
+ overlay_state_inactive = "module_clamp"
+ overlay_state_active = "module_clamp_on"
+ /// Time it takes to load a crate.
+ var/load_time = 3 SECONDS
+ /// The max amount of crates you can carry.
+ var/max_crates = 3
+ /// The crates stored in the module.
+ var/list/stored_crates = list()
+
+/obj/item/mod/module/clamp/on_select_use(atom/target)
+ . = ..()
+ if(!.)
+ return
+ if(!mod.wearer.Adjacent(target))
+ return
+ if(istype(target, /obj/structure/closet/crate))// || istype(target, /obj/item/delivery/big))
+ var/atom/movable/picked_crate = target
+ if(!check_crate_pickup(picked_crate))
+ return
+ playsound(src, 'sound/mecha/hydraulic.ogg', 25, TRUE)
+ if(!do_after(mod.wearer, load_time, target = target))
+ balloon_alert(mod.wearer, "interrupted!")
+ return
+ if(!check_crate_pickup(picked_crate))
+ return
+ stored_crates += picked_crate
+ picked_crate.forceMove(src)
+ balloon_alert(mod.wearer, "picked up [picked_crate]")
+ drain_power(use_power_cost)
+ mod.wearer.update_inv_back()
+ else if(length(stored_crates))
+ var/turf/target_turf = get_turf(target)
+ if(target_turf.is_blocked_turf())
+ return
+ playsound(src, 'sound/mecha/hydraulic.ogg', 25, TRUE)
+ if(!do_after(mod.wearer, load_time, target = target))
+ balloon_alert(mod.wearer, "interrupted!")
+ return
+ if(target_turf.is_blocked_turf())
+ return
+ var/atom/movable/dropped_crate = pop(stored_crates)
+ dropped_crate.forceMove(target_turf)
+ balloon_alert(mod.wearer, "dropped [dropped_crate]")
+ drain_power(use_power_cost)
+ mod.wearer.update_inv_back()
+ else
+ balloon_alert(mod.wearer, "invalid target!")
+
+/obj/item/mod/module/clamp/on_suit_deactivation(deleting = FALSE)
+ if(deleting)
+ return
+ for(var/atom/movable/crate as anything in stored_crates)
+ crate.forceMove(drop_location())
+ stored_crates -= crate
+
+/obj/item/mod/module/clamp/proc/check_crate_pickup(atom/movable/target)
+ if(length(stored_crates) >= max_crates)
+ balloon_alert(mod.wearer, "too many crates!")
+ return FALSE
+ for(var/mob/living/mob in target.get_all_contents())
+ if(mob.mob_size < MOB_SIZE_HUMAN)
+ continue
+ balloon_alert(mod.wearer, "crate too heavy!")
+ return FALSE
+ return TRUE
+
+/obj/item/mod/module/clamp/loader
+ name = "MOD loader hydraulic clamp module"
+ icon_state = "clamp_loader"
+ complexity = 0
+ removable = FALSE
+ overlay_state_inactive = null
+ overlay_state_active = "module_clamp_loader"
+ load_time = 1 SECONDS
+ max_crates = 5
+ use_mod_colors = TRUE
+
+///Drill - Lets you dig through rock and basalt.
+/obj/item/mod/module/drill
+ name = "MOD drill module"
+ desc = "An integrated drill, typically extending over the user's hand. While useful for drilling through rock, \
+ your drill is surely the one that both pierces and creates the heavens."
+ icon_state = "drill"
+ module_type = MODULE_ACTIVE
+ complexity = 2
+ use_power_cost = DEFAULT_CHARGE_DRAIN
+ incompatible_modules = list(/obj/item/mod/module/drill)
+ cooldown_time = 0.5 SECONDS
+ overlay_state_active = "module_drill"
+
+/obj/item/mod/module/drill/on_activation()
+ . = ..()
+ if(!.)
+ return
+ RegisterSignal(mod.wearer, COMSIG_MOVABLE_BUMP, PROC_REF(bump_mine))
+
+/obj/item/mod/module/drill/on_deactivation(display_message = TRUE, deleting = FALSE)
+ . = ..()
+ if(!.)
+ return
+ UnregisterSignal(mod.wearer, COMSIG_MOVABLE_BUMP)
+
+/obj/item/mod/module/drill/on_select_use(atom/target)
+ . = ..()
+ if(!.)
+ return
+ if(!mod.wearer.Adjacent(target))
+ return
+ if(istype(target, /turf/closed/mineral))
+ var/turf/closed/mineral/mineral_turf = target
+ mineral_turf.gets_drilled(mod.wearer)
+ drain_power(use_power_cost)
+ else if(istype(target, /turf/open/floor/plating/asteroid))
+ var/turf/open/floor/plating/asteroid/sand_turf = target
+ if(!sand_turf.can_dig(mod.wearer))
+ return
+ sand_turf.getDug()
+ drain_power(use_power_cost)
+
+/obj/item/mod/module/drill/proc/bump_mine(mob/living/carbon/human/bumper, atom/bumped_into, proximity)
+ SIGNAL_HANDLER
+ if(!istype(bumped_into, /turf/closed/mineral) || !drain_power(use_power_cost))
+ return
+ var/turf/closed/mineral/mineral_turf = bumped_into
+ mineral_turf.gets_drilled(mod.wearer)
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+
+///Ore Bag - Lets you pick up ores and drop them from the suit.
+/obj/item/mod/module/orebag
+ name = "MOD ore bag module"
+ desc = "An integrated ore storage system installed into the suit, \
+ this utilizes precise electromagnets and storage compartments to automatically collect and deposit ore. \
+ It's recommended by Nakamura Engineering to actually deposit that ore at local refineries."
+ icon_state = "ore"
+ module_type = MODULE_USABLE
+ complexity = 1
+ use_power_cost = DEFAULT_CHARGE_DRAIN * 0.2
+ incompatible_modules = list(/obj/item/mod/module/orebag)
+ cooldown_time = 0.5 SECONDS
+ allowed_inactive = TRUE
+ /// The ores stored in the bag.
+ var/list/ores = list()
+
+/obj/item/mod/module/orebag/on_equip()
+ RegisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED, PROC_REF(ore_pickup))
+
+/obj/item/mod/module/orebag/on_unequip()
+ UnregisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED)
+
+/obj/item/mod/module/orebag/proc/ore_pickup(atom/movable/source, atom/old_loc, dir, forced)
+ SIGNAL_HANDLER
+
+ for(var/obj/item/stack/ore/ore in get_turf(mod.wearer))
+ INVOKE_ASYNC(src, PROC_REF(move_ore), ore)
+ playsound(src, SFX_RUSTLE, 50, TRUE)
+
+/obj/item/mod/module/orebag/proc/move_ore(obj/item/stack/ore)
+ for(var/obj/item/stack/stored_ore as anything in ores)
+ if(!ore.can_merge(stored_ore))
+ continue
+ ore.merge(stored_ore)
+ if(QDELETED(ore))
+ return
+ break
+ ore.forceMove(src)
+ ores += ore
+
+/obj/item/mod/module/orebag/on_use()
+ . = ..()
+ if(!.)
+ return
+ for(var/obj/item/ore as anything in ores)
+ ore.forceMove(drop_location())
+ ores -= ore
+ drain_power(use_power_cost)
+
+/obj/item/mod/module/disposal_connector
+ name = "MOD disposal selector module"
+ desc = "A module that connects to the disposal pipeline, causing the user to go into their config selected disposal. \
+ Only seems to work when the suit is on."
+ icon_state = "disposal"
+ complexity = 2
+ idle_power_cost = DEFAULT_CHARGE_DRAIN * 0.3
+ incompatible_modules = list(/obj/item/mod/module/disposal_connector)
+ var/disposal_tag = NONE
+
+/obj/item/mod/module/disposal_connector/Initialize(mapload)
+ . = ..()
+ disposal_tag = pick(GLOB.TAGGERLOCATIONS)
+
+/obj/item/mod/module/disposal_connector/on_suit_activation()
+ RegisterSignal(mod.wearer, COMSIG_MOVABLE_DISPOSING, PROC_REF(disposal_handling))
+
+/obj/item/mod/module/disposal_connector/on_suit_deactivation(deleting = FALSE)
+ UnregisterSignal(mod.wearer, COMSIG_MOVABLE_DISPOSING)
+
+/obj/item/mod/module/disposal_connector/get_configuration()
+ . = ..()
+ .["disposal_tag"] = add_ui_configuration("Disposal Tag", "list", GLOB.TAGGERLOCATIONS[disposal_tag], GLOB.TAGGERLOCATIONS)
+
+/obj/item/mod/module/disposal_connector/configure_edit(key, value)
+ switch(key)
+ if("disposal_tag")
+ for(var/tag in 1 to length(GLOB.TAGGERLOCATIONS))
+ if(GLOB.TAGGERLOCATIONS[tag] == value)
+ disposal_tag = tag
+ break
+
+/obj/item/mod/module/disposal_connector/proc/disposal_handling(datum/disposal_source, obj/structure/disposalholder/disposal_holder, obj/machinery/disposal/disposal_machine, hasmob)
+ SIGNAL_HANDLER
+
+ disposal_holder.destinationTag = disposal_tag
+
+/obj/item/mod/module/magnet
+ name = "MOD loader hydraulic magnet module"
+ desc = "A powerful hydraulic electromagnet able to launch crates and lockers towards the user, and keep 'em attached."
+ icon_state = "magnet_loader"
+ module_type = MODULE_ACTIVE
+ removable = FALSE
+ use_power_cost = DEFAULT_CHARGE_DRAIN*3
+ incompatible_modules = list(/obj/item/mod/module/magnet)
+ cooldown_time = 1.5 SECONDS
+ overlay_state_active = "module_magnet"
+ use_mod_colors = TRUE
+
+/obj/item/mod/module/magnet/on_select_use(atom/target)
+ . = ..()
+ if(!.)
+ return
+ if(istype(mod.wearer.pulling, /obj/structure/closet))
+ var/obj/structure/closet/locker = mod.wearer.pulling
+ playsound(locker, 'sound/effects/gravhit.ogg', 75, TRUE)
+ locker.forceMove(mod.wearer.loc)
+ locker.throw_at(target, range = 7, speed = 4, thrower = mod.wearer)
+ return
+ if(!istype(target, /obj/structure/closet) || !(target in view(mod.wearer)))
+ balloon_alert(mod.wearer, "invalid target!")
+ return
+ var/obj/structure/closet/locker = target
+ if(locker.anchored || locker.move_resist >= MOVE_FORCE_OVERPOWERING)
+ balloon_alert(mod.wearer, "target anchored!")
+ return
+ new /obj/effect/temp_visual/mook_dust(get_turf(locker))
+ playsound(locker, 'sound/effects/gravhit.ogg', 75, TRUE)
+ locker.throw_at(mod.wearer, range = 7, speed = 3, force = MOVE_FORCE_WEAK, \
+ callback = CALLBACK(src, PROC_REF(check_locker), locker))
+
+/obj/item/mod/module/magnet/on_deactivation(display_message = TRUE, deleting = FALSE)
+ . = ..()
+ if(!.)
+ return
+ if(istype(mod.wearer.pulling, /obj/structure/closet))
+ mod.wearer.stop_pulling()
+
+/obj/item/mod/module/magnet/proc/check_locker(obj/structure/closet/locker)
+ if(!mod?.wearer)
+ return
+ if(!locker.Adjacent(mod.wearer) || !isturf(locker.loc) || !isturf(mod.wearer.loc))
+ return
+ mod.wearer.start_pulling(locker)
+ //locker.strong_grab = TRUE
+ RegisterSignal(locker, COMSIG_ATOM_NO_LONGER_PULLED, PROC_REF(on_stop_pull))
+
+/obj/item/mod/module/magnet/proc/on_stop_pull(obj/structure/closet/locker, atom/movable/last_puller)
+ SIGNAL_HANDLER
+
+ //locker.strong_grab = FALSE
+ UnregisterSignal(locker, COMSIG_ATOM_NO_LONGER_PULLED)
diff --git a/code/modules/mod/modules/modules_visor.dm b/code/modules/mod/modules/modules_visor.dm
new file mode 100644
index 00000000000..e1516c2aa0a
--- /dev/null
+++ b/code/modules/mod/modules/modules_visor.dm
@@ -0,0 +1,85 @@
+//Visor modules for MODsuits
+
+///Base Visor - Adds a specific HUD and traits to you.
+/obj/item/mod/module/visor
+ name = "MOD visor module"
+ desc = "A heads-up display installed into the visor of the suit. They say these also let you see behind you."
+ module_type = MODULE_TOGGLE
+ complexity = 2
+ active_power_cost = DEFAULT_CHARGE_DRAIN * 0.3
+ incompatible_modules = list(/obj/item/mod/module/visor)
+ cooldown_time = 0.5 SECONDS
+ /// The HUD type given by the visor.
+ var/hud_type
+ /// The traits given by the visor.
+ var/list/visor_traits = list()
+
+/obj/item/mod/module/visor/on_activation()
+ . = ..()
+ if(!.)
+ return
+ if(hud_type)
+ var/datum/atom_hud/hud = GLOB.huds[hud_type]
+ hud.add_hud_to(mod.wearer)
+ for(var/trait in visor_traits)
+ ADD_TRAIT(mod.wearer, trait, MOD_TRAIT)
+ mod.wearer.update_sight()
+
+/obj/item/mod/module/visor/on_deactivation(display_message = TRUE, deleting = FALSE)
+ . = ..()
+ if(!.)
+ return
+ if(hud_type)
+ var/datum/atom_hud/hud = GLOB.huds[hud_type]
+ hud.remove_hud_from(mod.wearer)
+ for(var/trait in visor_traits)
+ REMOVE_TRAIT(mod.wearer, trait, MOD_TRAIT)
+ mod.wearer.update_sight()
+
+//Medical Visor - Gives you a medical HUD.
+/obj/item/mod/module/visor/medhud
+ name = "MOD medical visor module"
+ desc = "A heads-up display installed into the visor of the suit. This cross-references suit sensor data with a modern \
+ biological scanning suite, allowing the user to visualize the current health of organic lifeforms, as well as \
+ access data such as patient files in a convenient readout. They say these also let you see behind you."
+ icon_state = "medhud_visor"
+ hud_type = DATA_HUD_MEDICAL_ADVANCED
+ visor_traits = list(TRAIT_MEDICAL_HUD)
+
+//Diagnostic Visor - Gives you a diagnostic HUD.
+/obj/item/mod/module/visor/diaghud
+ name = "MOD diagnostic visor module"
+ desc = "A heads-up display installed into the visor of the suit. This uses a series of advanced sensors to access data \
+ from advanced machinery, exosuits, and other devices, allowing the user to visualize current power levels \
+ and integrity of such. They say these also let you see behind you."
+ icon_state = "diaghud_visor"
+ hud_type = DATA_HUD_DIAGNOSTIC_ADVANCED
+ visor_traits = list(TRAIT_DIAGNOSTIC_HUD)
+
+//Security Visor - Gives you a security HUD.
+/obj/item/mod/module/visor/sechud
+ name = "MOD security visor module"
+ desc = "A heads-up display installed into the visor of the suit. This module is a heavily-retrofitted targeting system, \
+ plugged into various criminal databases to be able to view arrest records, command simple security-oriented robots, \
+ and generally know who to shoot. They say these also let you see behind you."
+ icon_state = "sechud_visor"
+ hud_type = DATA_HUD_SECURITY_ADVANCED
+ visor_traits = list(TRAIT_SECURITY_HUD)
+
+//Meson Visor - Gives you meson vision.
+/obj/item/mod/module/visor/meson
+ name = "MOD meson visor module"
+ desc = "A heads-up display installed into the visor of the suit. This module is based off well-loved meson scanner \
+ technology, used by construction workers and miners across the galaxy to see basic structural and terrain layouts \
+ through walls, regardless of lighting conditions. They say these also let you see behind you."
+ icon_state = "meson_visor"
+ visor_traits = list(SEE_TURFS)
+
+//Thermal Visor - Gives you thermal vision.
+/obj/item/mod/module/visor/thermal
+ name = "MOD thermal visor module"
+ desc = "A heads-up display installed into the visor of the suit. This uses a small IR scanner to detect and identify \
+ the thermal radiation output of objects near the user. While it can detect the heat output of even something as \
+ small as a rodent, it still produces irritating red overlay. They say these also let you see behind you."
+ icon_state = "thermal_visor"
+ visor_traits = list(SEE_MOBS)
diff --git a/code/modules/movespeed/modifiers/items.dm b/code/modules/movespeed/modifiers/items.dm
index c858582af6a..4c967a58a7e 100644
--- a/code/modules/movespeed/modifiers/items.dm
+++ b/code/modules/movespeed/modifiers/items.dm
@@ -18,3 +18,5 @@
/datum/movespeed_modifier/berserk
multiplicative_slowdown = -0.2
+/datum/movespeed_modifier/sphere
+ multiplicative_slowdown = -0.5
diff --git a/code/modules/paperwork/fax.dm b/code/modules/paperwork/fax.dm
index 6a8d30608e8..e7dd7435935 100644
--- a/code/modules/paperwork/fax.dm
+++ b/code/modules/paperwork/fax.dm
@@ -36,7 +36,7 @@
/// List of types which should be allowed to be faxed if hacked
var/static/list/exotic_types = list(
/obj/item/reagent_containers/food/snacks/pizzaslice,
- /obj/item/reagent_containers/food/snacks/breadslice,
+ /obj/item/food/breadslice,
/obj/item/reagent_containers/food/snacks/donkpocket,
/obj/item/reagent_containers/food/snacks/cookie,
/obj/item/reagent_containers/food/snacks/salami,
diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm
index 86f4e05226e..f345d327488 100644
--- a/code/modules/reagents/chemistry/holder.dm
+++ b/code/modules/reagents/chemistry/holder.dm
@@ -162,7 +162,7 @@
handle_reactions()
return amount
-/// Get the name of the reagent there is the most of in this holder
+/// DEPRICATED use get_master_regent. Get the name of the reagent there is the most of in this holder
/datum/reagents/proc/get_master_reagent_name()
var/list/cached_reagents = reagent_list
var/name
@@ -175,7 +175,7 @@
return name
-/// Get the id of the reagent there is the most of in this holder
+/// DEPRICATED use get_master_regent. Get the id of the reagent there is the most of in this holder
/datum/reagents/proc/get_master_reagent_id()
var/list/cached_reagents = reagent_list
var/max_type
@@ -611,6 +611,7 @@
//Clear from relevant lists
addiction_list -= R
reagent_list -= R
+ SEND_SIGNAL(src, COMSIG_REAGENTS_DEL_REAGENT, R)
qdel(R)
update_total()
if(my_atom)
@@ -638,6 +639,8 @@
del_reagent(R.type)
if(my_atom)
my_atom.on_reagent_change(CLEAR_REAGENTS)
+
+ SEND_SIGNAL(src, COMSIG_REAGENTS_CLEAR_REAGENTS)
return 0
/**
@@ -747,6 +750,8 @@
if(my_atom)
my_atom.on_reagent_change(ADD_REAGENT)
R.on_merge(data, amount)
+
+ SEND_SIGNAL(src, COMSIG_REAGENTS_ADD_REAGENT, cached_reagents, amount, reagtemp, data, no_react)
if(!no_react)
handle_reactions()
return TRUE
@@ -765,6 +770,8 @@
update_total()
if(my_atom)
my_atom.on_reagent_change(ADD_REAGENT)
+
+ SEND_SIGNAL(src, COMSIG_REAGENTS_NEW_REAGENT, reagent, amount, reagtemp, data, no_react)
if(!no_react)
handle_reactions()
return TRUE
@@ -797,6 +804,7 @@
//and zero, to prevent removing more than the holder has stored
amount = clamp(amount, 0, R.volume)
R.volume -= amount
+ SEND_SIGNAL(src, COMSIG_REAGENTS_REM_REAGENT, A, amount)
update_total()
if(!safety)//So it does not handle reactions when it need not to
handle_reactions()
diff --git a/code/modules/reagents/chemistry/machinery/chem_master.dm b/code/modules/reagents/chemistry/machinery/chem_master.dm
index c9791666b54..021cbd618b1 100644
--- a/code/modules/reagents/chemistry/machinery/chem_master.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_master.dm
@@ -341,18 +341,18 @@
reagents.trans_to(P, vol_each, transfered_by = usr)
return TRUE
if(item_type == "condimentPack")
- var/obj/item/reagent_containers/food/condiment/pack/P
+ var/obj/item/reagent_containers/condiment/pack/P
for(var/i = 0; i < amount; i++)
- P = new/obj/item/reagent_containers/food/condiment/pack(drop_location())
+ P = new/obj/item/reagent_containers/condiment/pack(drop_location())
P.originalname = name
P.name = trim("[name] pack")
P.desc = "A small condiment pack. The label says it contains [name]."
reagents.trans_to(P, vol_each, transfered_by = usr)
return TRUE
if(item_type == "condimentBottle")
- var/obj/item/reagent_containers/food/condiment/P
+ var/obj/item/reagent_containers/condiment/P
for(var/i = 0; i < amount; i++)
- P = new/obj/item/reagent_containers/food/condiment(drop_location())
+ P = new/obj/item/reagent_containers/condiment(drop_location())
P.originalname = name
P.name = trim("[name] bottle")
reagents.trans_to(P, vol_each, transfered_by = usr)
diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm
index d21fe72db23..8f00c731525 100644
--- a/code/modules/reagents/chemistry/reagents/food_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm
@@ -113,9 +113,9 @@
/datum/reagent/consumable/cooking_oil/expose_obj(obj/O, reac_volume)
if(holder && holder.chem_temp >= fry_temperature)
- if(isitem(O) && !istype(O, /obj/item/reagent_containers/food/snacks/deepfryholder))
+ if(isitem(O) && !istype(O, /obj/item/food/deepfryholder))
O.loc.visible_message("[O] rapidly fries as it's splashed with hot oil! Somehow.")
- var/obj/item/reagent_containers/food/snacks/deepfryholder/F = new(O.drop_location(), O)
+ var/obj/item/food/deepfryholder/F = new(O.drop_location(), O)
F.fry(volume)
F.reagents.add_reagent(/datum/reagent/consumable/cooking_oil, reac_volume)
diff --git a/code/modules/reagents/chemistry/reagents/trickwine_reagents.dm b/code/modules/reagents/chemistry/reagents/trickwine_reagents.dm
index be5e2ce35f9..8b64e02b936 100644
--- a/code/modules/reagents/chemistry/reagents/trickwine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/trickwine_reagents.dm
@@ -127,7 +127,7 @@
T.IgniteTurf(reac_volume)
new /obj/effect/hotspot(T, reac_volume * 1, FIRE_MINIMUM_TEMPERATURE_TO_EXIST + reac_volume * 10)
var/turf/otherT
- for(var/direction in GLOB.cardinals)
+ for(var/direction in GLOB.alldirs)
otherT = get_step(T, direction)
otherT.IgniteTurf(reac_volume)
new /obj/effect/hotspot(otherT, reac_volume * 1, FIRE_MINIMUM_TEMPERATURE_TO_EXIST + reac_volume * 10)
diff --git a/code/modules/reagents/chemistry/recipes/slime_extracts.dm b/code/modules/reagents/chemistry/recipes/slime_extracts.dm
index d3bf1e21129..7429db4c1de 100644
--- a/code/modules/reagents/chemistry/recipes/slime_extracts.dm
+++ b/code/modules/reagents/chemistry/recipes/slime_extracts.dm
@@ -139,7 +139,7 @@
var/chosen = getbork()
var/obj/B = new chosen(T)
if(prob(5))//Fry it!
- var/obj/item/reagent_containers/food/snacks/deepfryholder/fried
+ var/obj/item/food/deepfryholder/fried
fried = new(T, B)
fried.fry() // actually set the name and colour it
B = fried
diff --git a/code/modules/research/xenobiology/crossbreeding/charged.dm b/code/modules/research/xenobiology/crossbreeding/charged.dm
index 25aa7930291..f01b148d4d5 100644
--- a/code/modules/research/xenobiology/crossbreeding/charged.dm
+++ b/code/modules/research/xenobiology/crossbreeding/charged.dm
@@ -107,7 +107,7 @@ Charged extracts:
effect_desc = "Creates a slime cake and some drinks."
/obj/item/slimecross/charged/silver/do_effect(mob/user)
- new /obj/item/reagent_containers/food/snacks/store/cake/slimecake(get_turf(user))
+ new /obj/item/food/cake/slimecake(get_turf(user))
for(var/i in 1 to 10)
var/drink_type = get_random_drink()
new drink_type(get_turf(user))
diff --git a/code/modules/surgery/organs/heart.dm b/code/modules/surgery/organs/heart.dm
index 26b93d2c464..6ba9ce69d70 100644
--- a/code/modules/surgery/organs/heart.dm
+++ b/code/modules/surgery/organs/heart.dm
@@ -55,7 +55,7 @@
update_appearance()
return 1
-/obj/item/organ/heart/OnEatFrom(eater, feeder)
+/obj/item/organ/heart/on_eat_from(eater, feeder)
. = ..()
beating = FALSE
update_appearance()
diff --git a/code/modules/surgery/organs/organ_internal.dm b/code/modules/surgery/organs/organ_internal.dm
index b8871b21524..72b7fba9d2d 100644
--- a/code/modules/surgery/organs/organ_internal.dm
+++ b/code/modules/surgery/organs/organ_internal.dm
@@ -38,7 +38,14 @@
/obj/item/organ/Initialize()
. = ..()
if(organ_flags & ORGAN_EDIBLE)
- AddComponent(/datum/component/edible, food_reagents, null, RAW | MEAT | GORE, null, 10, null, null, null, COLOR_PINK, CALLBACK(src, PROC_REF(OnEatFrom)))
+ AddComponent(/datum/component/edible,\
+ initial_reagents = food_reagents,\
+ foodtypes = RAW | MEAT | GORE,\
+ volume = 10,\
+ filling_color = COLOR_PINK,\
+ pre_eat = CALLBACK(src, PROC_REF(pre_eat)),\
+ on_compost = CALLBACK(src, PROC_REF(pre_compost)),\
+ after_eat = CALLBACK(src, PROC_REF(on_eat_from)))
///When you take a bite you cant jam it in for surgery anymore.
/obj/item/organ/proc/Insert(mob/living/carbon/M, special = 0, drop_if_replaced = TRUE)
@@ -133,8 +140,21 @@
STOP_PROCESSING(SSobj, src)
return ..()
-/obj/item/organ/proc/OnEatFrom(eater, feeder)
- useable = FALSE //You can't use it anymore after eating it you spaztic
+// Put any "can we eat this" checks for edible organs here
+/obj/item/organ/proc/pre_eat(eater, feeder)
+ if(iscarbon(eater))
+ var/mob/living/carbon/target = eater
+ for(var/S in target.surgeries)
+ var/datum/surgery/surgery = S
+ if(surgery.location == zone)
+ return FALSE
+ return TRUE
+
+/obj/item/organ/proc/pre_compost(user)
+ return TRUE
+
+/obj/item/organ/proc/on_eat_from(eater, feeder)
+ useable = FALSE //You bit it, no more using it
/obj/item/organ/item_action_slot_check(slot,mob/user)
return //so we don't grant the organ's action to mobs who pick up the organ.
diff --git a/code/modules/unit_tests/create_and_destroy.dm b/code/modules/unit_tests/create_and_destroy.dm
index 017356d9152..9aee2ca1062 100644
--- a/code/modules/unit_tests/create_and_destroy.dm
+++ b/code/modules/unit_tests/create_and_destroy.dm
@@ -72,7 +72,7 @@
ignore += typesof(/obj/effect/pod_landingzone_effect)
ignore += typesof(/obj/effect/pod_landingzone)
//These want fried food to take on the shape of, we can't pass that in
- ignore += typesof(/obj/item/reagent_containers/food/snacks/deepfryholder)
+ ignore += typesof(/obj/item/food/deepfryholder)
//Can't pass in a thing to glow
ignore += typesof(/obj/effect/abstract/eye_lighting)
//It wants a lot more context then we have
diff --git a/code/modules/unit_tests/serving_tray.dm b/code/modules/unit_tests/serving_tray.dm
index 9bd487ba68a..b4dc1f77a52 100644
--- a/code/modules/unit_tests/serving_tray.dm
+++ b/code/modules/unit_tests/serving_tray.dm
@@ -6,7 +6,7 @@
var/obj/structure/table/the_table = allocate(/obj/structure/table)
var/obj/item/storage/bag/tray/test_tray = allocate(/obj/item/storage/bag/tray)
var/obj/item/reagent_containers/food/banana = allocate(/obj/item/reagent_containers/food/snacks/grown/banana)
- var/obj/item/reagent_containers/food/the_bread = allocate(/obj/item/reagent_containers/food/snacks/breadslice)
+ var/obj/item/reagent_containers/food/the_bread = allocate(/obj/item/food/breadslice)
var/obj/item/reagent_containers/food/sugarcookie = allocate(/obj/item/reagent_containers/food/snacks/sugarcookie)
var/obj/item/clothing/under/jumpsuit = allocate(/obj/item/clothing/under/color/black)
diff --git a/code/modules/vending/drinnerware.dm b/code/modules/vending/drinnerware.dm
index 5dbd8ea733d..14062e0f56b 100644
--- a/code/modules/vending/drinnerware.dm
+++ b/code/modules/vending/drinnerware.dm
@@ -8,11 +8,11 @@
/obj/item/reagent_containers/glass/bowl = 20,
/obj/item/kitchen/fork = 6,
/obj/item/reagent_containers/food/drinks/drinkingglass = 8,
- /obj/item/reagent_containers/food/condiment/pack/ketchup = 5,
- /obj/item/reagent_containers/food/condiment/pack/hotsauce = 5,
- /obj/item/reagent_containers/food/condiment/pack/astrotame = 5,
- /obj/item/reagent_containers/food/condiment/saltshaker = 5,
- /obj/item/reagent_containers/food/condiment/peppermill = 5,
+ /obj/item/reagent_containers/condiment/pack/ketchup = 5,
+ /obj/item/reagent_containers/condiment/pack/hotsauce = 5,
+ /obj/item/reagent_containers/condiment/pack/astrotame = 5,
+ /obj/item/reagent_containers/condiment/saltshaker = 5,
+ /obj/item/reagent_containers/condiment/peppermill = 5,
/obj/item/clothing/suit/apron/chef = 2,
/obj/item/kitchen/rollingpin = 2,
/obj/item/kitchen/knife = 2,
diff --git a/code/modules/vending/sustenance.dm b/code/modules/vending/sustenance.dm
index 0519285d26a..0677a77edb5 100644
--- a/code/modules/vending/sustenance.dm
+++ b/code/modules/vending/sustenance.dm
@@ -7,7 +7,7 @@
icon_state = "sustenance"
products = list(
/obj/item/reagent_containers/food/snacks/tofu/prison = 24,
- /obj/item/reagent_containers/food/snacks/breadslice/moldy = 15,
+ /obj/item/food/breadslice/moldy = 15,
/obj/item/reagent_containers/food/drinks/ice/prison = 12,
/obj/item/reagent_containers/food/snacks/candy_corn/prison = 6)
contraband = list(
diff --git a/icons/effects/magic.dmi b/icons/effects/magic.dmi
new file mode 100644
index 00000000000..480332df134
Binary files /dev/null and b/icons/effects/magic.dmi differ
diff --git a/icons/hud/radial.dmi b/icons/hud/radial.dmi
new file mode 100644
index 00000000000..9786d403e0d
Binary files /dev/null and b/icons/hud/radial.dmi differ
diff --git a/icons/mob/actions/actions_mod.dmi b/icons/mob/actions/actions_mod.dmi
new file mode 100644
index 00000000000..7f030ad53d4
Binary files /dev/null and b/icons/mob/actions/actions_mod.dmi differ
diff --git a/icons/mob/clothing/modsuit/mod_clothing.dmi b/icons/mob/clothing/modsuit/mod_clothing.dmi
new file mode 100644
index 00000000000..27d4df3b902
Binary files /dev/null and b/icons/mob/clothing/modsuit/mod_clothing.dmi differ
diff --git a/icons/mob/clothing/modsuit/mod_modules.dmi b/icons/mob/clothing/modsuit/mod_modules.dmi
new file mode 100644
index 00000000000..11259428cf4
Binary files /dev/null and b/icons/mob/clothing/modsuit/mod_modules.dmi differ
diff --git a/icons/obj/clothing/modsuit/mod_clothing.dmi b/icons/obj/clothing/modsuit/mod_clothing.dmi
new file mode 100644
index 00000000000..d2d9e0c72e3
Binary files /dev/null and b/icons/obj/clothing/modsuit/mod_clothing.dmi differ
diff --git a/icons/obj/clothing/modsuit/mod_construction.dmi b/icons/obj/clothing/modsuit/mod_construction.dmi
new file mode 100644
index 00000000000..a6be94284af
Binary files /dev/null and b/icons/obj/clothing/modsuit/mod_construction.dmi differ
diff --git a/icons/obj/clothing/modsuit/mod_modules.dmi b/icons/obj/clothing/modsuit/mod_modules.dmi
new file mode 100644
index 00000000000..69affa3fa49
Binary files /dev/null and b/icons/obj/clothing/modsuit/mod_modules.dmi differ
diff --git a/icons/obj/contraband.dmi b/icons/obj/contraband.dmi
index b34b3777ada..20fe8abf2d5 100644
Binary files a/icons/obj/contraband.dmi and b/icons/obj/contraband.dmi differ
diff --git a/shiptest.dme b/shiptest.dme
index 30c48b1e18b..806f87fac04 100644
--- a/shiptest.dme
+++ b/shiptest.dme
@@ -25,6 +25,7 @@
#include "code\__DEFINES\_tick.dm"
#include "code\__DEFINES\access.dm"
#include "code\__DEFINES\achievements.dm"
+#include "code\__DEFINES\actions.dm"
#include "code\__DEFINES\admin.dm"
#include "code\__DEFINES\anomalies.dm"
#include "code\__DEFINES\antagonists.dm"
@@ -42,6 +43,7 @@
#include "code\__DEFINES\chat.dm"
#include "code\__DEFINES\cinematics.dm"
#include "code\__DEFINES\cleaning.dm"
+#include "code\__DEFINES\clothing.dm"
#include "code\__DEFINES\colors.dm"
#include "code\__DEFINES\combat.dm"
#include "code\__DEFINES\configuration.dm"
@@ -95,6 +97,7 @@
#include "code\__DEFINES\menu.dm"
#include "code\__DEFINES\misc.dm"
#include "code\__DEFINES\mobs.dm"
+#include "code\__DEFINES\mod.dm"
#include "code\__DEFINES\monkeys.dm"
#include "code\__DEFINES\move_force.dm"
#include "code\__DEFINES\movement.dm"
@@ -110,6 +113,7 @@
#include "code\__DEFINES\plumbing.dm"
#include "code\__DEFINES\power.dm"
#include "code\__DEFINES\preferences.dm"
+#include "code\__DEFINES\processing.dm"
#include "code\__DEFINES\procpath.dm"
#include "code\__DEFINES\profile.dm"
#include "code\__DEFINES\projectiles.dm"
@@ -157,13 +161,29 @@
#include "code\__DEFINES\wires.dm"
#include "code\__DEFINES\dcs\flags.dm"
#include "code\__DEFINES\dcs\helpers.dm"
-#include "code\__DEFINES\dcs\signals.dm"
+#include "code\__DEFINES\dcs\signals\signals.dm"
+#include "code\__DEFINES\dcs\signals\signals_mod.dm"
+#include "code\__DEFINES\dcs\signals\signals_reagent.dm"
+#include "code\__DEFINES\dcs\signals\signals_storage.dm"
+#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_carbon.dm"
+#include "code\__DEFINES\dcs\signals\signals_obj\signals_object.dm"
+#include "code\__DEFINES\dcs\signals\signals_obj\signals_item\signals_clothing.dm"
+#include "code\__DEFINES\dcs\signals\signals_obj\signals_item\signals_food.dm"
+#include "code\__DEFINES\dcs\signals\signals_obj\signals_item\signals_grenade.dm"
+#include "code\__DEFINES\dcs\signals\signals_obj\signals_item\signals_hydroponic.dm"
+#include "code\__DEFINES\dcs\signals\signals_obj\signals_item\signals_implant.dm"
+#include "code\__DEFINES\dcs\signals\signals_obj\signals_item\signals_item.dm"
+#include "code\__DEFINES\dcs\signals\signals_obj\signals_machine\signals_aquarium.dm"
+#include "code\__DEFINES\dcs\signals\signals_obj\signals_machine\signals_machinery.dm"
+#include "code\__DEFINES\dcs\signals\signals_obj\signals_machine\signals_supermatter.dm"
#include "code\__HELPERS\_auxtools_api.dm"
#include "code\__HELPERS\_lists.dm"
#include "code\__HELPERS\_logging.dm"
+#include "code\__HELPERS\_planes.dm"
#include "code\__HELPERS\_string_lists.dm"
#include "code\__HELPERS\areas.dm"
#include "code\__HELPERS\AStar.dm"
+#include "code\__HELPERS\atoms.dm"
#include "code\__HELPERS\bindings.dm"
#include "code\__HELPERS\bitflag_lists.dm"
#include "code\__HELPERS\chat.dm"
@@ -182,6 +202,7 @@
#include "code\__HELPERS\icons.dm"
#include "code\__HELPERS\level_traits.dm"
#include "code\__HELPERS\lighting.dm"
+#include "code\__HELPERS\maths.dm"
#include "code\__HELPERS\matrices.dm"
#include "code\__HELPERS\mobs.dm"
#include "code\__HELPERS\mouse_control.dm"
@@ -481,7 +502,6 @@
#include "code\datums\components\deployable.dm"
#include "code\datums\components\dooropendeathproc.dm"
#include "code\datums\components\earprotection.dm"
-#include "code\datums\components\edible.dm"
#include "code\datums\components\edit_complainer.dm"
#include "code\datums\components\embedded.dm"
#include "code\datums\components\empprotection.dm"
@@ -497,6 +517,7 @@
#include "code\datums\components\hot_ice.dm"
#include "code\datums\components\igniter.dm"
#include "code\datums\components\infective.dm"
+#include "code\datums\components\jetpack.dm"
#include "code\datums\components\jousting.dm"
#include "code\datums\components\knockback.dm"
#include "code\datums\components\knockoff.dm"
@@ -524,6 +545,7 @@
#include "code\datums\components\remote_materials.dm"
#include "code\datums\components\riding.dm"
#include "code\datums\components\rotation.dm"
+#include "code\datums\components\shielded.dm"
#include "code\datums\components\shrink.dm"
#include "code\datums\components\sitcomlaughter.dm"
#include "code\datums\components\sizzle.dm"
@@ -560,6 +582,8 @@
#include "code\datums\components\fantasy\affix.dm"
#include "code\datums\components\fantasy\prefixes.dm"
#include "code\datums\components\fantasy\suffixes.dm"
+#include "code\datums\components\food\edible.dm"
+#include "code\datums\components\food\food_storage.dm"
#include "code\datums\components\plumbing\_plumbing.dm"
#include "code\datums\components\plumbing\chemical_acclimator.dm"
#include "code\datums\components\plumbing\filter.dm"
@@ -642,14 +666,15 @@
#include "code\datums\elements\cleaning.dm"
#include "code\datums\elements\connect_loc.dm"
#include "code\datums\elements\digitalcamo.dm"
-#include "code\datums\elements\dunkable.dm"
#include "code\datums\elements\earhealing.dm"
#include "code\datums\elements\embed.dm"
+#include "code\datums\elements\empprotection.dm"
#include "code\datums\elements\firestacker.dm"
#include "code\datums\elements\forced_gravity.dm"
#include "code\datums\elements\lazy_fishing_spot.dm"
#include "code\datums\elements\light_blocking.dm"
#include "code\datums\elements\mobappearance.dm"
+#include "code\datums\elements\plant_backfire.dm"
#include "code\datums\elements\renamemob.dm"
#include "code\datums\elements\selfknockback.dm"
#include "code\datums\elements\snail_crawl.dm"
@@ -663,6 +688,9 @@
#include "code\datums\elements\world_icon.dm"
#include "code\datums\elements\decals\_decals.dm"
#include "code\datums\elements\decals\blood.dm"
+#include "code\datums\elements\food\dunkable.dm"
+#include "code\datums\elements\food\food_trash.dm"
+#include "code\datums\elements\food\processable.dm"
#include "code\datums\helper_datums\events.dm"
#include "code\datums\helper_datums\getrev.dm"
#include "code\datums\helper_datums\icon_snapshot.dm"
@@ -792,6 +820,7 @@
#include "code\datums\wires\fax.dm"
#include "code\datums\wires\microwave.dm"
#include "code\datums\wires\mines.dm"
+#include "code\datums\wires\mod.dm"
#include "code\datums\wires\mulebot.dm"
#include "code\datums\wires\particle_accelerator.dm"
#include "code\datums\wires\r_n_d.dm"
@@ -1254,6 +1283,10 @@
#include "code\game\objects\items\devices\radio\headset.dm"
#include "code\game\objects\items\devices\radio\intercom.dm"
#include "code\game\objects\items\devices\radio\radio.dm"
+#include "code\game\objects\items\food\_food.dm"
+#include "code\game\objects\items\food\bread.dm"
+#include "code\game\objects\items\food\cake.dm"
+#include "code\game\objects\items\food\spaghetti.dm"
#include "code\game\objects\items\grenades\antigravity.dm"
#include "code\game\objects\items\grenades\chem_grenade.dm"
#include "code\game\objects\items\grenades\clusterbuster.dm"
@@ -2180,9 +2213,7 @@
#include "code\modules\food_and_drinks\food\customizables.dm"
#include "code\modules\food_and_drinks\food\ration.dm"
#include "code\modules\food_and_drinks\food\snacks.dm"
-#include "code\modules\food_and_drinks\food\snacks_bread.dm"
#include "code\modules\food_and_drinks\food\snacks_burgers.dm"
-#include "code\modules\food_and_drinks\food\snacks_cake.dm"
#include "code\modules\food_and_drinks\food\snacks_egg.dm"
#include "code\modules\food_and_drinks\food\snacks_frozen.dm"
#include "code\modules\food_and_drinks\food\snacks_meat.dm"
@@ -2193,7 +2224,6 @@
#include "code\modules\food_and_drinks\food\snacks_salad.dm"
#include "code\modules\food_and_drinks\food\snacks_sandwichtoast.dm"
#include "code\modules\food_and_drinks\food\snacks_soup.dm"
-#include "code\modules\food_and_drinks\food\snacks_spaghetti.dm"
#include "code\modules\food_and_drinks\food\snacks_vend.dm"
#include "code\modules\food_and_drinks\food\snacks\dough.dm"
#include "code\modules\food_and_drinks\food\snacks\meat.dm"
@@ -2251,6 +2281,8 @@
#include "code\modules\hydroponics\beekeeping\beekeeper_suit.dm"
#include "code\modules\hydroponics\beekeeping\honey_frame.dm"
#include "code\modules\hydroponics\beekeeping\honeycomb.dm"
+#include "code\modules\hydroponics\genes\attack.dm"
+#include "code\modules\hydroponics\genes\backfire.dm"
#include "code\modules\hydroponics\grown\ambrosia.dm"
#include "code\modules\hydroponics\grown\apple.dm"
#include "code\modules\hydroponics\grown\banana.dm"
@@ -2806,6 +2838,30 @@
#include "code\modules\mob\living\simple_animal\slime\slime.dm"
#include "code\modules\mob\living\simple_animal\slime\slime_say.dm"
#include "code\modules\mob\living\simple_animal\slime\subtypes.dm"
+#include "code\modules\mod\mod_actions.dm"
+#include "code\modules\mod\mod_activation.dm"
+#include "code\modules\mod\mod_ai.dm"
+#include "code\modules\mod\mod_clothes.dm"
+#include "code\modules\mod\mod_construction.dm"
+#include "code\modules\mod\mod_control.dm"
+#include "code\modules\mod\mod_core.dm"
+#include "code\modules\mod\mod_paint.dm"
+#include "code\modules\mod\mod_theme.dm"
+#include "code\modules\mod\mod_types.dm"
+#include "code\modules\mod\mod_ui.dm"
+#include "code\modules\mod\modules\_module.dm"
+#include "code\modules\mod\modules\modules_antag.dm"
+#include "code\modules\mod\modules\modules_engineering.dm"
+#include "code\modules\mod\modules\modules_general.dm"
+#include "code\modules\mod\modules\modules_maint.dm"
+#include "code\modules\mod\modules\modules_medical.dm"
+#include "code\modules\mod\modules\modules_ninja.dm"
+#include "code\modules\mod\modules\modules_science.dm"
+#include "code\modules\mod\modules\modules_security.dm"
+#include "code\modules\mod\modules\modules_service.dm"
+#include "code\modules\mod\modules\modules_storage.dm"
+#include "code\modules\mod\modules\modules_supply.dm"
+#include "code\modules\mod\modules\modules_visor.dm"
#include "code\modules\modular_computers\laptop_vendor.dm"
#include "code\modules\modular_computers\computers\_modular_computer_shared.dm"
#include "code\modules\modular_computers\computers\item\computer.dm"
diff --git a/sound/items/modsuit/atrocinator_step.ogg b/sound/items/modsuit/atrocinator_step.ogg
new file mode 100644
index 00000000000..deda85ac354
Binary files /dev/null and b/sound/items/modsuit/atrocinator_step.ogg differ
diff --git a/sound/items/modsuit/ballin.ogg b/sound/items/modsuit/ballin.ogg
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/sound/items/modsuit/ballout.ogg b/sound/items/modsuit/ballout.ogg
new file mode 100644
index 00000000000..f911f1a6a61
Binary files /dev/null and b/sound/items/modsuit/ballout.ogg differ
diff --git a/sound/items/modsuit/flamethrower.ogg b/sound/items/modsuit/flamethrower.ogg
new file mode 100644
index 00000000000..447245d50b6
Binary files /dev/null and b/sound/items/modsuit/flamethrower.ogg differ
diff --git a/sound/items/modsuit/inflate_bloon.ogg b/sound/items/modsuit/inflate_bloon.ogg
new file mode 100644
index 00000000000..9b030d66ced
Binary files /dev/null and b/sound/items/modsuit/inflate_bloon.ogg differ
diff --git a/sound/items/modsuit/loader_charge.ogg b/sound/items/modsuit/loader_charge.ogg
new file mode 100644
index 00000000000..61d5531f72e
Binary files /dev/null and b/sound/items/modsuit/loader_charge.ogg differ
diff --git a/sound/items/modsuit/loader_launch.ogg b/sound/items/modsuit/loader_launch.ogg
new file mode 100644
index 00000000000..513118f3c68
Binary files /dev/null and b/sound/items/modsuit/loader_launch.ogg differ
diff --git a/sound/items/modsuit/magnetic_harness.ogg b/sound/items/modsuit/magnetic_harness.ogg
new file mode 100644
index 00000000000..3d19fccc569
Binary files /dev/null and b/sound/items/modsuit/magnetic_harness.ogg differ
diff --git a/sound/items/modsuit/rewinder.ogg b/sound/items/modsuit/rewinder.ogg
new file mode 100644
index 00000000000..2587562dc11
Binary files /dev/null and b/sound/items/modsuit/rewinder.ogg differ
diff --git a/sound/items/modsuit/springlock.ogg b/sound/items/modsuit/springlock.ogg
new file mode 100644
index 00000000000..8d0013d2630
Binary files /dev/null and b/sound/items/modsuit/springlock.ogg differ
diff --git a/sound/items/modsuit/tem_shot.ogg b/sound/items/modsuit/tem_shot.ogg
new file mode 100644
index 00000000000..50905b95f11
Binary files /dev/null and b/sound/items/modsuit/tem_shot.ogg differ
diff --git a/sound/items/modsuit/time_anchor_set.ogg b/sound/items/modsuit/time_anchor_set.ogg
new file mode 100644
index 00000000000..457f8e6dbae
Binary files /dev/null and b/sound/items/modsuit/time_anchor_set.ogg differ
diff --git a/sound/mecha/hydraulic.ogg b/sound/mecha/hydraulic.ogg
new file mode 100644
index 00000000000..3281ed2dc0f
Binary files /dev/null and b/sound/mecha/hydraulic.ogg differ
diff --git a/tgui/packages/tgui/interfaces/MODpaint.js b/tgui/packages/tgui/interfaces/MODpaint.js
new file mode 100644
index 00000000000..22d14b65611
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/MODpaint.js
@@ -0,0 +1,161 @@
+import { useBackend } from '../backend';
+import {
+ Box,
+ Stack,
+ Section,
+ ByondUi,
+ Slider,
+ Flex,
+ Button,
+} from '../components';
+import { Window } from '../layouts';
+import { capitalize } from 'common/string';
+
+const colorToMatrix = (param) => {
+ switch (param) {
+ case 'red':
+ return [
+ 1, 0, 0, 0, 0.25, 0.5, 0, 0, 0.25, 0, 0.5, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+ ];
+ case 'yellow':
+ return [
+ 0.5, 0.5, 0, 0, 0.5, 0.5, 0, 0, 0.25, 0.25, 0.5, 0, 0, 0, 0, 1, 0, 0, 0,
+ 0,
+ ];
+ case 'green':
+ return [
+ 0.5, 0.25, 0, 0, 0, 1, 0, 0, 0, 0.25, 0.5, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+ ];
+ case 'teal':
+ return [
+ 0.25, 0.25, 0.25, 0, 0, 0.5, 0.5, 0, 0, 0.5, 0.5, 0, 0, 0, 0, 1, 0, 0,
+ 0, 0,
+ ];
+ case 'blue':
+ return [
+ 0.25, 0, 0.25, 0, 0, 0.5, 0.25, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+ ];
+ case 'purple':
+ return [
+ 0.5, 0, 0.5, 0, 0.25, 0.5, 0.25, 0, 0.5, 0, 0.5, 0, 0, 0, 0, 1, 0, 0, 0,
+ 0,
+ ];
+ }
+};
+
+const displayText = (param) => {
+ switch (param) {
+ case 'r':
+ return 'Red';
+ case 'g':
+ return 'Green';
+ case 'b':
+ return 'Blue';
+ }
+};
+
+export const MODpaint = (props, context) => {
+ const { act, data } = useBackend(context);
+ const { mapRef, currentColor } = data;
+ const [
+ [rr, rg, rb, ra],
+ [gr, gg, gb, ga],
+ [br, bg, bb, ba],
+ [ar, ag, ab, aa],
+ [cr, cg, cb, ca],
+ ] = currentColor;
+ const presets = ['red', 'yellow', 'green', 'teal', 'blue', 'purple'];
+ const prefixes = ['r', 'g', 'b'];
+ return (
+
+
+
+
+ {[0, 1, 2].map((row) => (
+
+ {[0, 1, 2].map((col) => (
+
+
+
+ {`${displayText(prefixes[col])}:`}
+
+
+
+ `${value}%`}
+ onDrag={(e, value) => {
+ let retColor = currentColor;
+ retColor[row * 4 + col] = value / 100;
+ act('transition_color', { color: retColor });
+ }}
+ />
+
+
+ ))}
+
+ ))}
+
+
+
+
+ {presets.map((preset) => (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/MODsuit.js b/tgui/packages/tgui/interfaces/MODsuit.js
new file mode 100644
index 00000000000..d8edbbc4e3f
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/MODsuit.js
@@ -0,0 +1,797 @@
+import { useBackend, useLocalState } from '../backend';
+import {
+ Button,
+ ColorBox,
+ LabeledList,
+ ProgressBar,
+ Section,
+ Collapsible,
+ Box,
+ Icon,
+ Stack,
+ Table,
+ Dimmer,
+ NumberInput,
+ Flex,
+ AnimatedNumber,
+ Dropdown,
+} from '../components';
+import { Window } from '../layouts';
+
+const ConfigureNumberEntry = (props, context) => {
+ const { name, value, module_ref } = props;
+ const { act } = useBackend(context);
+ return (
+
+ act('configure', {
+ 'key': name,
+ 'value': value,
+ 'ref': module_ref,
+ })
+ }
+ />
+ );
+};
+
+const ConfigureBoolEntry = (props, context) => {
+ const { name, value, module_ref } = props;
+ const { act } = useBackend(context);
+ return (
+
+ act('configure', {
+ 'key': name,
+ 'value': !value,
+ 'ref': module_ref,
+ })
+ }
+ />
+ );
+};
+
+const ConfigureColorEntry = (props, context) => {
+ const { name, value, module_ref } = props;
+ const { act } = useBackend(context);
+ return (
+ <>
+