diff --git a/_maps/map_files/MetaStation/MetaStation.v41A.II.dmm b/_maps/map_files/MetaStation/MetaStation.v41A.II.dmm
index c7d1817de52..f7070386264 100644
--- a/_maps/map_files/MetaStation/MetaStation.v41A.II.dmm
+++ b/_maps/map_files/MetaStation/MetaStation.v41A.II.dmm
@@ -8368,7 +8368,7 @@
"deZ" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 10},/turf/simulated/floor/engine,/area/engine/mechanic_workshop)
"dfa" = (/obj/machinery/computer/rdconsole/mechanics,/turf/simulated/floor/plasteel{icon_state = "vault"; dir = 5},/area/engine/mechanic_workshop)
"dfb" = (/obj/machinery/door/poddoor{density = 0; icon_state = "open"; id_tag = "mechpod"; name = "Mechanic's Workshop Inner Door"; opacity = 0},/turf/simulated/floor/plasteel{icon_state = "vault"; dir = 5},/area/engine/mechanic_workshop)
-"dfc" = (/obj/machinery/spod_part_fabricator,/obj/machinery/light{dir = 1; in_use = 1},/turf/simulated/floor/plasteel{icon_state = "vault"; dir = 5},/area/engine/mechanic_workshop)
+"dfc" = (/obj/machinery/mecha_part_fabricator/spacepod,/obj/machinery/light{dir = 1; in_use = 1},/turf/simulated/floor/plasteel{icon_state = "vault"; dir = 5},/area/engine/mechanic_workshop)
"dfd" = (/obj/machinery/camera,/turf/simulated/floor/plasteel{icon_state = "vault"; dir = 5},/area/engine/mechanic_workshop)
"dfe" = (/obj/structure/table,/obj/item/pod_parts/core,/obj/item/circuitboard/mecha/pod,/obj/item/clothing/glasses/welding{pixel_y = 12},/turf/simulated/floor/plasteel{icon_state = "vault"; dir = 5},/area/engine/mechanic_workshop)
"dff" = (/obj/structure/rack{dir = 1},/obj/item/extinguisher,/obj/item/storage/belt/utility,/obj/item/storage/toolbox/electrical,/obj/item/storage/toolbox/mechanical{pixel_x = -2; pixel_y = -1},/obj/item/device/radio{pixel_y = 6},/turf/simulated/floor/plasteel{icon_state = "vault"; dir = 5},/area/engine/mechanic_workshop)
diff --git a/_maps/map_files/RandomZLevels/stationCollision.dmm b/_maps/map_files/RandomZLevels/stationCollision.dmm
index 7c6c48303c6..170e79d7f50 100644
--- a/_maps/map_files/RandomZLevels/stationCollision.dmm
+++ b/_maps/map_files/RandomZLevels/stationCollision.dmm
@@ -37,7 +37,7 @@
"aK" = (/turf/space,/area/awaymission/syndishuttle)
"aL" = (/obj/machinery/recharge_station,/turf/simulated/floor/plasteel,/area/awaymission/northblock)
"aM" = (/obj/structure/table,/turf/simulated/floor/plasteel,/area/awaymission/northblock)
-"aN" = (/obj/machinery/robotic_fabricator,/turf/simulated/floor/plasteel,/area/awaymission/northblock)
+"aN" = (/obj/machinery/mecha_part_fabricator/robot,/turf/simulated/floor/plasteel,/area/awaymission/northblock)
"aO" = (/obj/structure/shuttle/engine/propulsion{tag = "icon-propulsion_r (EAST)"; icon_state = "propulsion_r"; dir = 4},/turf/space,/area/awaymission/syndishuttle)
"aP" = (/obj/structure/shuttle/engine/heater{tag = "icon-heater (WEST)"; icon_state = "heater"; dir = 8},/turf/simulated/floor/plating/airless,/area/awaymission/syndishuttle)
"aQ" = (/turf/simulated/floor/plating/airless,/turf/simulated/shuttle/wall{icon_state = "diagonalWall3"},/area/awaymission/syndishuttle)
diff --git a/_maps/map_files/cyberiad/cyberiad.dmm b/_maps/map_files/cyberiad/cyberiad.dmm
index 01442085b31..0c5bd05dd40 100644
--- a/_maps/map_files/cyberiad/cyberiad.dmm
+++ b/_maps/map_files/cyberiad/cyberiad.dmm
@@ -6957,6 +6957,7 @@
"cDO" = (/obj/machinery/atmospherics/unary/portables_connector{dir = 8},/obj/structure/sign/poster/contraband/random{pixel_x = 32},/turf/simulated/floor/plasteel{icon_state = "floorgrime"},/area/maintenance/incinerator)
"cDP" = (/obj/machinery/atmospherics/unary/vent_pump{dir = 4; external_pressure_bound = 0; icon_state = "in"; initialize_directions = 1; internal_pressure_bound = 4000; on = 1; pressure_checks = 2; pump_direction = 0},/turf/simulated/floor/engine/insulated,/area/maintenance/incinerator)
"cDQ" = (/obj/structure/closet/toolcloset,/turf/simulated/floor/plating,/area/maintenance/apmaint)
+"cDR" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/obj/structure/sign/securearea{desc = "A warning sign which reads 'EXTERNAL AIRLOCK'"; icon_state = "space"; layer = 4; name = "EXTERNAL AIRLOCK"; pixel_x = 0; pixel_y = 32},/obj/machinery/mecha_part_fabricator/spacepod,/obj/effect/decal/warning_stripes/yellow/hollow,/turf/simulated/floor/plasteel{dir = 8; icon_state = "warning"},/area/engine/mechanic_workshop)
"cDS" = (/obj/machinery/light,/turf/simulated/floor/plating,/area/maintenance/consarea)
"cDT" = (/obj/item/stack/rods{amount = 8},/turf/simulated/floor/plating,/area/maintenance/consarea)
"cDU" = (/obj/machinery/atmospherics/binary/pump{dir = 4; on = 1},/obj/structure/sign/fire{pixel_y = -32},/turf/simulated/floor/engine/insulated,/area/maintenance/incinerator)
@@ -7076,7 +7077,7 @@
"cGe" = (/obj/machinery/light{dir = 1; on = 1},/turf/simulated/floor/engine,/area/engine/mechanic_workshop)
"cGf" = (/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"; tag = ""},/obj/effect/decal/warning_stripes/yellow,/turf/simulated/floor/engine,/area/engine/mechanic_workshop)
"cGg" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 6},/obj/machinery/camera{c_tag = "Mechanic's Workshop West"; dir = 2; network = list("SS13")},/turf/simulated/floor/engine,/area/engine/mechanic_workshop)
-"cGh" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/obj/structure/sign/securearea{desc = "A warning sign which reads 'EXTERNAL AIRLOCK'"; icon_state = "space"; layer = 4; name = "EXTERNAL AIRLOCK"; pixel_x = 0; pixel_y = 32},/turf/simulated/floor/plasteel{dir = 8; icon_state = "warning"},/area/engine/mechanic_workshop)
+"cGh" = (/obj/structure/rack{dir = 1},/obj/item/extinguisher,/obj/item/storage/belt/utility,/obj/item/storage/toolbox/electrical,/obj/item/storage/toolbox/mechanical{pixel_x = -2; pixel_y = -1},/obj/item/device/radio{pixel_y = 6},/obj/machinery/firealarm{dir = 1; pixel_y = -24},/turf/simulated/floor/plasteel,/area/engine/mechanic_workshop)
"cGi" = (/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/obj/machinery/door/poddoor{density = 0; icon_state = "open"; id_tag = "mechpod"; name = "Mechanic's Workshop Inner Door"; opacity = 0},/turf/simulated/floor/engine,/area/engine/mechanic_workshop)
"cGj" = (/obj/machinery/light{dir = 1},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{dir = 4},/obj/machinery/computer/podtracker,/turf/simulated/floor/plasteel{dir = 4; icon_state = "warning"},/area/engine/mechanic_workshop)
"cGk" = (/obj/machinery/atmospherics/pipe/manifold/hidden/scrubbers{dir = 1; initialize_directions = 11; level = 1},/obj/machinery/requests_console{department = "Mechanic"; departmentType = 2; name = "Mechanic's Workshop Requests Console"; pixel_y = 30},/obj/structure/disposalpipe/trunk,/obj/machinery/disposal,/turf/simulated/floor/plasteel,/area/engine/mechanic_workshop)
@@ -7272,7 +7273,6 @@
"cJZ" = (/turf/simulated/wall,/area/hallway/primary/aft)
"cKa" = (/obj/machinery/door/airlock/maintenance{name = "Mechanic Workshop Maintenance"; req_access = null; req_access_txt = "70"},/turf/simulated/floor/plating,/area/maintenance/aft)
"cKb" = (/turf/simulated/wall,/area/assembly/assembly_line)
-"cKc" = (/obj/structure/rack{dir = 1},/obj/item/extinguisher,/obj/item/storage/belt/utility,/obj/item/storage/toolbox/electrical,/obj/item/storage/toolbox/mechanical{pixel_x = -2; pixel_y = -1},/obj/item/device/radio{pixel_y = 6},/turf/simulated/floor/plasteel,/area/engine/mechanic_workshop)
"cKd" = (/obj/structure/table,/obj/item/paper_bin{pixel_x = 1; pixel_y = 9},/obj/item/folder/yellow,/obj/item/pen,/obj/item/book/manual/sop_engineering,/turf/simulated/floor/plasteel{icon_state = "dark"},/area/engine/mechanic_workshop)
"cKe" = (/obj/effect/spawner/window/reinforced,/obj/structure/disposalpipe/segment,/turf/simulated/floor/plating,/area/assembly/assembly_line)
"cKf" = (/obj/effect/spawner/window/reinforced,/turf/simulated/floor/plating,/area/assembly/assembly_line)
@@ -7336,7 +7336,6 @@
"cLn" = (/obj/structure/cable{d1 = 1; d2 = 4; icon_state = "1-4"; tag = ""},/turf/simulated/floor/plating{tag = "icon-warnplate (NORTH)"; icon_state = "warnplate"; dir = 1},/area/maintenance/aft)
"cLo" = (/obj/structure/cable{d1 = 2; d2 = 8; icon_state = "2-8"; tag = ""},/turf/simulated/floor/plating,/area/maintenance/aft)
"cLp" = (/obj/machinery/light/small{dir = 1},/obj/structure/cable{d1 = 4; d2 = 8; icon_state = "4-8"},/turf/simulated/floor/plating,/area/maintenance/aft)
-"cLq" = (/obj/machinery/firealarm{dir = 1; pixel_y = -24},/obj/machinery/spod_part_fabricator,/turf/simulated/floor/plasteel{icon_state = "bot"},/area/engine/mechanic_workshop)
"cLr" = (/obj/machinery/door_control{id = "mechpod"; name = "Mechanic's Inner Door Control"; pixel_x = 0; pixel_y = -24; req_access_txt = "70"},/obj/structure/table,/obj/item/stack/sheet/metal{amount = 50},/obj/item/stack/sheet/glass{amount = 50},/obj/item/stack/rods{amount = 50},/obj/item/stack/sheet/mineral/plasma{amount = 30},/turf/simulated/floor/plasteel,/area/engine/mechanic_workshop)
"cLs" = (/obj/machinery/light_switch{name = "light switch "; dir = 2; pixel_x = 0; pixel_y = -22},/turf/simulated/floor/plasteel{dir = 5; icon_state = "dark"; tag = "icon-vault (NORTHEAST)"},/area/engine/mechanic_workshop)
"cLt" = (/obj/machinery/alarm{dir = 1; icon_state = "alarm0"; pixel_y = -22},/obj/structure/window/reinforced{dir = 8},/obj/machinery/vending/coffee,/turf/simulated/floor/plasteel{icon_state = "dark"},/area/engine/mechanic_workshop)
@@ -8994,11 +8993,11 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacBicCwcCxcCqcCyczJcCtcCzcCBczJcCDcCCcCEcskcgQcoLcngaaaaaaaaaaaaaaacngcoLcCFcwFcCAcwIcwIcwIcwIcCGcCIcCHcCJcngaaacCMcwUcCNcwVcCOcwOcwOcCRcCPcCKcCLcCUcCTcCVcBvcsDcCYcCQcCZcCScDacvHcDccyDcyDcyDcDdcvIckScmJctZcQkcudcufcuecUTcugcujcuhcumculcupcuncQncuqcuscurcQkcsLcrDcrDcuucutcuxcuwcuzcuycuBcuAcqMcuCcuDbQXcepaabaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacDqcDycDAcDzcDucDBcDwcDwcDCcDwcDwcDwcDwcDwcDEcDDcDGcDFcDHbGHcDIcjYbGHbGHaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaciecDJcDMcDKciecieckbckbcDNckbckbciecieaabaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacBicDPcAXcDUcAXcDVcDLcDXcDZcBbcDOcwxcEacqBcgQcqzcvpaaaaaaaaaaaaaaacvpcDQcvocwFcwFcwFcDScDTcwIcEbcvocoLcqPcvpaaaaaaaaaaaacvqcEdcDWcEecEecvxcDYcvxcEfcsHcnycEicEjcEccElcEkcEkcEkcEscEgcyDcEhcyDcEtcvIckScmJcuEcQkcuFcuIcuHcQkcvOcUTcvPcvQcQncQncQncQkcQkcQkcQkcQkcsLcrDcvRcvTcvScuxcvUcvWcvVcvYcvXcqMcsLctrbQXcepaabaabaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacDqcEHcEOcENcERcEQcEXcEVcEZcDwcDwcEPcDwcDwcDEcDGcDGcDGcFbbGHcFcbGGbGHaabaabaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaciccEScETclAcidciecieckbckcckbciecieaabaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacEUcEUcEUcEUcEUcwxcFdcEWcEWcEWcEWcEWcFecEYcEYcEYcEWcEWcEWcEWcEWcEWcEWcEYcEYcEYcvocvocvocvocvocvocvocqFcFfcFacFacFacFacFacvqcvxcvxcvxcvxcvxcvxcvxcFgcsHcnycFhcFjcFicFlcFkcFrcFocFucFtcFwcFmcFncFBcvIckScmJcKOcBPcvZdcCcQkcQkcwacwbcUTcwdcwccwfcwecwhcwgcwicQkcPccnVcrDcwkcwmcwlcuxcuxbUAcwncwscwrcqMcsLctrbQXctsaaaaabaabaabaaaaaaaaaaaaaaaaaaaaaaaaaaacDqcFNcFPcFOcFKcFQcFMcFRcFScDwcDwcDwcDwcFTcFYcFUcGacFZcGdbGHcFcbGGbGHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacicciccicciccidcidcieciecieciecijaabaabaaaaaaabpaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabpaaaaaaaaaaaaaaaaaaaaacFVcFWcFXcGecFXcGfcFWcGgcGbcGccGccGicGhcGkcGjcGmcGlcGocGncGqcGpcGpcGrcGpcGscGtcGtcGvcGucGxcGwcGycGwcGwcGwcGxcGwcGwcGzcGBcGAcGDcsHcnycGJcGNcGMcGCcsDcGOczZcGEcGFcGGcGHcxicwtcGKckScmJcxlchfdcDcxpcxncQkcxqcxrcUTcxtcxscxvcxucUTcxwcxxcQkcsLcBwcrDcxycmycxzcxBcuxcBPcBPcBPcBPchfcebctrbQXctsaaaaaaaabaabaabaaaaaaaaaaaaaaaaaaaaaaaacBScBRcHmcHgcBScHncHicHjcHvcHtcHxcHwcDwcDwcHocHpcHqcHrcHsbGHcFcbGGbZZaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabpaaaaaaaaaaaaaaaaaaaaaaabaabaabaabaaaaaaaaaaaaaabaabaabaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabpaaaaaaaaaaaaaaaaaaaaacFVcFWcFXcGecFXcGfcFWcGgcGbcGccGccGicDRcGkcGjcGmcGlcGocGncGqcGpcGpcGrcGpcGscGtcGtcGvcGucGxcGwcGycGwcGwcGwcGxcGwcGwcGzcGBcGAcGDcsHcnycGJcGNcGMcGCcsDcGOczZcGEcGFcGGcGHcxicwtcGKckScmJcxlchfdcDcxpcxncQkcxqcxrcUTcxtcxscxvcxucUTcxwcxxcQkcsLcBwcrDcxycmycxzcxBcuxcBPcBPcBPcBPchfcebctrbQXctsaaaaaaaabaabaabaaaaaaaaaaaaaaaaaaaaaaaacBScBRcHmcHgcBScHncHicHjcHvcHtcHxcHwcDwcDwcHocHpcHqcHrcHsbGHcFcbGGbZZaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabpaaaaaaaaaaaaaaaaaaaaaaabaabaabaabaaaaaaaaaaaaaabaabaabaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacFVcFWcFXcFXcFXcGfcFWcHycFXcANcFXcHBcHAcHEcHCcHzcHucHzcHFcHJcHDclXcHDcHDcHKcnAcnAcHDcHGcHDcHDcHDcHDcHDcHDcHDcHDcHDcHDcHHcHIcHMcHLcHOcHNcHUcHTcHPcHQcHRbWTcdGcaycHVcHYcxDcxCcGKckScmJctZcucdducxEddwcQkcxFcxGcUTcxJcxHcxPcxLcUTcxRcxTcQkcyJcsLcrDcyKcyMcyLcpQcrDcyNcyNchfcyOcyPcebctrbQXctsaaaaaaaaaaabaabaabaaaaaaaaaaaaaaaaaaaaacBRcIzcIBcIAcBRcAPcAPcAPcAPcAPcAPcICcHwcIDcIHcIFcIEcAPcAPbGHcFcbGGbZZaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacFVcIIcFXcIGcFXcGfcIJcIMcIKcINcILcITcIPcIOcIUcIQcIRcIScIVcIXcIWcJacIYcJbcqVcIZcqVcIXcJccIXcIXcIXcIXcIXcIXcIXcIXcIXcIXcJdcHDcKmcJecJicJhcJgcJjcJodcdcfjcfhcgRcfucJycJpcyRcyQcGKckScmJcsLchfchfcySchfcQkcQkcQkcQkcyVcyUcyXcyWcUTcyYczacyZcyJcsLchfchfchfchfchfchfcsLcyNchfczbchfcsLctrbQXcepaaaaaaaaaaaaaabcBRcBRcBRcBRcBRcBRcBRcBRcBRcJJcJNcJMcxNcJKcJKcJLcBRaabcAPcAPcJOcAPcJTcJRcJPcAPcJQcuQcFcbGGbZZaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacJUcJScEWcEWcEWcEWcJYcEYcEYcEYcEYcKacEYcKccJVcJVcJWcJXcKdcEYcJZcKgcKbcKbcKicKbcKjcKbcKbcKecKfcKbcKbcKfcKfcKfcKbcKbcKbcKbcKkcKhcLKcKlcKtcKqcKxcKndcgcKncKncKncKnchWcKycKrczccKzcGKczdczecsLczfcsLcYiczgcuccTBcKOcyZcQkcQkcQkcQkcQkcQkcQkcQkcsLcKOchfczhczjcziczkchfczlchfchfchfchfchfctrbQXcepaaaaaaaaaaaaaaacBRcKPcKQcKRcKWcKVcKYcKXcxNcLccLfcLdcxNcJKcKZcLacBRaabaabcLbcLkcLjcLecLlcLgcLbbGGbGGcFccLhbGHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalwalwalwalvalvalwalvalvaabamhaabayiaaaayiayicLicLncLmcLmcLmcLpcLocEYbTmcLrcLqcLtcLscLucEYcLycLwcKfcLGcLBcLxcLIcLzcLAcLJcLCcLDcKbcLCcLEcLFcLGcLGcLHcKbcLLcVbcHJcLNcnycLTcLMdcicLOdcjcjRdcjcwocmRcLXcLWczncMacGKcGKcbvcsLcnVcsLcYicsLcuEcsLcsLctZcsLcsLcsLcKOctZczocsLcyJcsLcsLcBQcsLczqczpczrcyJczschfcztchfczuchfczvbQXcepaaaaaacepcepcepcBRcJKcMgcJKcMlcMjcEHcJNcxNcxQcMocxQcxNcJKcJKcMkcBRaabaaacLbcMucMmcLecLecMncLbcMpbGGcFcbKlcMsaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacJUcJScEWcEWcEWcEWcJYcEYcEYcEYcEYcKacEYcJVcJVcJVcJWcJXcKdcEYcJZcKgcKbcKbcKicKbcKjcKbcKbcKecKfcKbcKbcKfcKfcKfcKbcKbcKbcKbcKkcKhcLKcKlcKtcKqcKxcKndcgcKncKncKncKnchWcKycKrczccKzcGKczdczecsLczfcsLcYiczgcuccTBcKOcyZcQkcQkcQkcQkcQkcQkcQkcQkcsLcKOchfczhczjcziczkchfczlchfchfchfchfchfctrbQXcepaaaaaaaaaaaaaaacBRcKPcKQcKRcKWcKVcKYcKXcxNcLccLfcLdcxNcJKcKZcLacBRaabaabcLbcLkcLjcLecLlcLgcLbbGGbGGcFccLhbGHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalwalwalwalvalvalwalvalvaabamhaabayiaaaayiayicLicLncLmcLmcLmcLpcLocEYbTmcLrcGhcLtcLscLucEYcLycLwcKfcLGcLBcLxcLIcLzcLAcLJcLCcLDcKbcLCcLEcLFcLGcLGcLHcKbcLLcVbcHJcLNcnycLTcLMdcicLOdcjcjRdcjcwocmRcLXcLWczncMacGKcGKcbvcsLcnVcsLcYicsLcuEcsLcsLctZcsLcsLcsLcKOctZczocsLcyJcsLcsLcBQcsLczqczpczrcyJczschfcztchfczuchfczvbQXcepaaaaaacepcepcepcBRcJKcMgcJKcMlcMjcEHcJNcxNcxQcMocxQcxNcJKcJKcMkcBRaabaaacLbcMucMmcLecLecMncLbcMpbGGcFcbKlcMsaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalwaaaaaaaabaabaaaaabaabaaaaaaaabaabaabaabaabcLicLicMqcLicLicMrcMvcEYcEYcEYcEYcEYcEYcEYcEYcLicLwcKfcMtcMxcMwcMycMycMycMycMCcMzcMDcLGcMAcLGcLGcMBcLGcMEcMGcMFcMHcLNcnycMLcHQcMPcLOdcBcHSdcEcLOdcGcMRcMNcAjczxcAlcAkcAncAmcMXcAmcApcAocArcAqcrecrecAscrecAtcsLcsLcsLcAucyJcyJcsLcBQcAvcAvcAwcAycAxcAzchfcAAcAvcACchfcADcsLcBPcepcepcepbQXcAEcBRcJKcJKcJKcNmcNlcNocNncNqcNpcNscNrcxNcNtcNycNxcBRaaaaaacLbcNzcLecNucLecNvcLbbGGbGGcFccMsbGHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacNwaaaalwaaacNFcNEcNMaaacNFcNEcNMaaacNFcNEcNMaaaaaacLicNAcNBcNCcLicNDcNNcLmcLmcLmcLmcLmcLmcLmcLmcLmcNRcKfcNGcLGcMBcNHcLGcLGcLGcNIcLGcNGcNGcLGcLGcLGcLGcNJcNKcMGcNLcHDcNScyEcHQcHQcNTcLOcNOcNPcNQcLOcNUcHVcNVcAGcAFcAHcGKcsKcsLcNYcsLcAIdfMcAOcAMcARcARcARcARcBxcATcBycBycBycBycBAcyJcMfczqcyJcyJcebcsLcsLcBCcAvcAvczqcBCcBYcsLcsLcBGcBHciYbQXcBIcBRcxNcxNcxNcxNcOkcEHcOlcOncOmcOpcOocxNcOqcOtcOrcBRaaaaaacLbcOvcOucOscLecODcLbcOCbGGcFcbGHaabaabaabaabaaaaaaaaaaaaabpaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaalwaaacNFcOFcNMaaacNFcOFcNMaaacNFcOFcNMaaaayicLicNBcNBcNBcLicLicOwcOxcLicLicOxcOxcLicLicOxcOxcLwcKfcNGcOycLGcOzcOAcOBcOBcMAcLGcNGcOGcLGcLGcLGcLGcOHcOEcOJcOIclXcOKcOMcOLcOYcORcLOcNOcOZcONcLOcOOcHVcGKcGKcGKcBJcGKcZScOTcOUcOTcOWcOTcOTcOTcOTcOTcBKcBPcBPcBPcBPcBPcBPcBPcBLcnVcBQcsLcBOcBMcBTcsLcsLchfcBWcBVcBXchfcDbcCXcPhcDecDgciYbQXcDhcBRcKPcKQcKRcPmcPlcNocPncPjcPocPqcPpcPscPrcPpcPtcBRaaaaaacLbcLbcPycPAcPzcLbcLbbGHbGGcPBcuQcuQcuQbGHaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm
new file mode 100644
index 00000000000..6f14b75b2ec
--- /dev/null
+++ b/code/__DEFINES/components.dm
@@ -0,0 +1,151 @@
+//shorthand
+#define GET_COMPONENT_FROM(varname, path, target) var##path/##varname = ##target.GetComponent(##path)
+#define GET_COMPONENT(varname, path) GET_COMPONENT_FROM(varname, path, src)
+
+#define COMPONENT_INCOMPATIBLE 1
+
+// How multiple components of the exact same type are handled in the same datum
+
+#define COMPONENT_DUPE_HIGHLANDER 0 //old component is deleted (default)
+#define COMPONENT_DUPE_ALLOWED 1 //duplicates allowed
+#define COMPONENT_DUPE_UNIQUE 2 //new component is deleted
+#define COMPONENT_DUPE_UNIQUE_PASSARGS 4 //old component is given the initialization args of the new
+
+// All signals. Format:
+// When the signal is called: (signal arguments)
+
+// /datum signals
+#define COMSIG_COMPONENT_ADDED "component_added" //when a component is added to a datum: (/datum/component)
+#define COMSIG_COMPONENT_REMOVING "component_removing" //before a component is removed from a datum because of RemoveComponent: (/datum/component)
+#define COMSIG_PARENT_QDELETED "parent_qdeleted" //before a datum's Destroy() is called: ()
+
+// /atom signals
+#define COMSIG_PARENT_ATTACKBY "atom_attackby" //from base of atom/attackby(): (/obj/item, /mob/living, params)
+ #define COMPONENT_NO_AFTERATTACK 1 //Return this in response if you don't want afterattack to be called
+#define COMSIG_ATOM_HULK_ATTACK "hulk_attack" //from base of atom/attack_hulk(): (/mob/living/carbon/human)
+#define COMSIG_PARENT_EXAMINE "atom_examine" //from base of atom/examine(): (/mob)
+#define COMSIG_ATOM_GET_EXAMINE_NAME "atom_examine_name" //from base of atom/get_examine_name(): (/mob, list/overrides)
+ //Positions for overrides list
+ #define EXAMINE_POSITION_ARTICLE 1
+ #define EXAMINE_POSITION_BEFORE 2
+ //End positions
+ #define COMPONENT_EXNAME_CHANGED 1
+#define COMSIG_ATOM_ENTERED "atom_entered" //from base of atom/Entered(): (atom/movable/entering, /atom)
+#define COMSIG_ATOM_EXITED "atom_exited" //from base of atom/Exited(): (atom/movable/exiting, atom/newloc)
+#define COMSIG_ATOM_EX_ACT "atom_ex_act" //from base of atom/ex_act(): (severity, target)
+#define COMSIG_ATOM_EMP_ACT "atom_emp_act" //from base of atom/emp_act(): (severity)
+#define COMSIG_ATOM_FIRE_ACT "atom_fire_act" //from base of atom/fire_act(): (exposed_temperature, exposed_volume)
+#define COMSIG_ATOM_BULLET_ACT "atom_bullet_act" //from base of atom/bullet_act(): (/obj/item/projectile, def_zone)
+#define COMSIG_ATOM_BLOB_ACT "atom_blob_act" //from base of atom/blob_act(): (/obj/structure/blob)
+#define COMSIG_ATOM_ACID_ACT "atom_acid_act" //from base of atom/acid_act(): (acidpwr, acid_volume)
+#define COMSIG_ATOM_EMAG_ACT "atom_emag_act" //from base of atom/emag_act(): ()
+#define COMSIG_ATOM_RAD_ACT "atom_rad_act" //from base of atom/rad_act(intensity)
+#define COMSIG_ATOM_NARSIE_ACT "atom_narsie_act" //from base of atom/narsie_act(): ()
+#define COMSIG_ATOM_RATVAR_ACT "atom_ratvar_act" //from base of atom/ratvar_act(): ()
+#define COMSIG_ATOM_RCD_ACT "atom_rcd_act" //from base of atom/rcd_act(): (/mob, /obj/item/construction/rcd, passed_mode)
+#define COMSIG_ATOM_SING_PULL "atom_sing_pull" //from base of atom/singularity_pull(): (S, current_size)
+#define COMSIG_ATOM_SET_LIGHT "atom_set_light" //from base of atom/set_light(): (l_range, l_power, l_color)
+#define COMSIG_ATOM_ROTATE "atom_rotate" //from base of atom/shuttleRotate(): (rotation, params)
+#define COMSIG_ATOM_DIR_CHANGE "atom_dir_change" //from base of atom/setDir(): (old_dir, new_dir)
+#define COMSIG_ATOM_CONTENTS_DEL "atom_contents_del" //from base of atom/handle_atom_del(): (atom/deleted)
+/////////////////
+#define COMSIG_ATOM_ATTACK_GHOST "atom_attack_ghost" //from base of atom/attack_ghost(): (mob/dead/observer/ghost)
+#define COMSIG_ATOM_ATTACK_HAND "atom_attack_hand" //from base of atom/attack_hand(): (mob/user)
+#define COMSIG_ATOM_ATTACK_PAW "atom_attack_paw" //from base of atom/attack_paw(): (mob/user)
+ #define COMPONENT_NO_ATTACK_HAND 1 //works on all 3.
+/////////////////
+
+#define COMSIG_ENTER_AREA "enter_area" //from base of area/Entered(): (/area)
+#define COMSIG_EXIT_AREA "exit_area" //from base of area/Exited(): (/area)
+
+#define COMSIG_CLICK "atom_click" //from base of atom/Click(): (location, control, params)
+#define COMSIG_CLICK_SHIFT "shift_click" //from base of atom/ShiftClick(): (/mob)
+#define COMSIG_CLICK_CTRL "ctrl_click" //from base of atom/CtrlClickOn(): (/mob)
+#define COMSIG_CLICK_ALT "alt_click" //from base of atom/AltClick(): (/mob)
+#define COMSIG_CLICK_CTRL_SHIFT "ctrl_shift_click" //from base of atom/CtrlShiftClick(/mob)
+#define COMSIG_MOUSEDROP_ONTO "mousedrop_onto" //from base of atom/MouseDrop(): (/atom/over, /mob/user)
+ #define COMPONENT_NO_MOUSEDROP 1
+#define COMSIG_MOUSEDROPPED_ONTO "mousedropped_onto" //from base of atom/MouseDrop_T: (/atom/from, /mob/user)
+
+// /area signals
+#define COMSIG_AREA_ENTERED "area_entered" //from base of area/Entered(): (atom/movable/M)
+#define COMSIG_AREA_EXITED "area_exited" //from base of area/Exited(): (atom/movable/M)
+
+// /atom/movable signals
+#define COMSIG_MOVABLE_MOVED "movable_moved" //from base of atom/movable/Moved(): (/atom, dir)
+#define COMSIG_MOVABLE_CROSSED "movable_crossed" //from base of atom/movable/Crossed(): (/atom/movable)
+#define COMSIG_MOVABLE_UNCROSSED "movable_uncrossed" //from base of atom/movable/Uncrossed(): (/atom/movable)
+#define COMSIG_MOVABLE_COLLIDE "movable_collide" //from base of atom/movable/Collide(): (/atom)
+#define COMSIG_MOVABLE_IMPACT "movable_impact" //from base of atom/movable/throw_impact(): (/atom/hit_atom, /datum/thrownthing/throwingdatum)
+#define COMSIG_MOVABLE_IMPACT_ZONE "item_impact_zone" //from base of mob/living/hitby(): (mob/living/target, hit_zone)
+#define COMSIG_MOVABLE_BUCKLE "buckle" //from base of atom/movable/buckle_mob(): (mob, force)
+#define COMSIG_MOVABLE_UNBUCKLE "unbuckle" //from base of atom/movable/unbuckle_mob(): (mob, force)
+#define COMSIG_MOVABLE_THROW "movable_throw" //from base of atom/movable/throw_at(): (datum/thrownthing, spin)
+
+// /obj signals
+#define COMSIG_OBJ_DECONSTRUCT "obj_deconstruct" //from base of obj/deconstruct(): (disassembled)
+
+// /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 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_EQUIPPED "item_equip" //from base of obj/item/equipped(): (/mob/equipper, slot)
+#define COMSIG_ITEM_DROPPED "item_drop"
+#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)
+
+// /obj/item/clothing signals
+#define COMSIG_SHOES_STEP_ACTION "shoes_step_action" //from base of obj/item/clothing/shoes/proc/step_action(): ()
+
+// /obj/machinery signals
+#define COMSIG_MACHINE_PROCESS "machine_process" //from machinery subsystem fire(): ()
+#define COMSIG_MACHINE_PROCESS_ATMOS "machine_process_atmos" //from air subsystem process_atmos_machinery(): ()
+
+// /mob/living/carbon/human signals
+#define COMSIG_HUMAN_MELEE_UNARMED_ATTACK "human_melee_unarmed_attack" //from mob/living/carbon/human/UnarmedAttack(): (atom/target)
+#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)
+
+/*******Component Specific Signals*******/
+//Janitor
+#define COMSIG_TURF_IS_WET "check_turf_wet" //(): Returns bitflags of wet values.
+#define COMSIG_TURF_MAKE_DRY "make_turf_try" //(max_strength, immediate, duration_decrease = INFINITY): Returns bool.
+#define COMSIG_COMPONENT_CLEAN_ACT "clean_act" //called on an object to clean it of cleanables. Usualy with soap: (num/strength)
+
+//Food
+#define COMSIG_FOOD_EATEN "food_eaten" //from base of obj/item/reagent_containers/food/snacks/attack(): (mob/living/eater, mob/feeder)
+
+//Mood
+#define COMSIG_ADD_MOOD_EVENT "add_mood" //Called when you send a mood event from anywhere in the code.
+#define COMSIG_CLEAR_MOOD_EVENT "clear_mood" //Called when you clear a mood event from anywhere in the code.
+
+//NTnet
+#define COMSIG_COMPONENT_NTNET_RECIEVE "ntnet_recieve" //called on an object by its NTNET connection component on recieve. (sending_id(number), sending_netname(text), data(datum/netdata))
+
+// /datum/component/storage signals
+#define COMSIG_CONTAINS_STORAGE "is_storage" //() - returns bool.
+#define COMSIG_TRY_STORAGE_INSERT "storage_try_insert" //(obj/item/inserting, mob/user, silent, force) - returns bool
+#define COMSIG_TRY_STORAGE_SHOW "storage_show_to" //(mob/show_to, force) - returns bool.
+#define COMSIG_TRY_STORAGE_HIDE_FROM "storage_hide_from" //(mob/hide_from) - returns bool
+#define COMSIG_TRY_STORAGE_HIDE_ALL "storage_hide_all" //returns bool
+#define COMSIG_TRY_STORAGE_SET_LOCKSTATE "storage_lock_set_state" //(newstate)
+#define COMSIG_IS_STORAGE_LOCKED "storage_get_lockstate" //() - returns bool. MUST CHECK IF STORAGE IS THERE FIRST!
+#define COMSIG_TRY_STORAGE_TAKE_TYPE "storage_take_type" //(type, atom/destination, amount = INFINITY, check_adjacent, force, mob/user, list/inserted) - returns bool - type can be a list of types.
+#define COMSIG_TRY_STORAGE_FILL_TYPE "storage_fill_type" //(type, amount = INFINITY, force = FALSE) //don't fuck this up. Force will ignore max_items, and amount is normally clamped to max_items.
+#define COMSIG_TRY_STORAGE_TAKE "storage_take_obj" //(obj, new_loc, force = FALSE) - returns bool
+#define COMSIG_TRY_STORAGE_QUICK_EMPTY "storage_quick_empty" //(loc) - returns bool - if loc is null it will dump at parent location.
+#define COMSIG_TRY_STORAGE_RETURN_INVENTORY "storage_return_inventory" //(list/list_to_inject_results_into)
+#define COMSIG_TRY_STORAGE_CAN_INSERT "storage_can_equip" //(obj/item/insertion_candidate, mob/user, silent) - returns bool
+
+/*******Non-Signal Component Related Defines*******/
+
+//Arch
+#define ARCH_PROB "probability" //Probability for each item
+#define ARCH_MAXDROP "max_drop_amount" //each item's max drop amount
+
+//Ouch my toes!
+#define CALTROP_BYPASS_SHOES 1
+#define CALTROP_IGNORE_WALKERS 2
diff --git a/code/__DEFINES/construction.dm b/code/__DEFINES/construction.dm
index 1e5599a998d..9d8ca0b3013 100644
--- a/code/__DEFINES/construction.dm
+++ b/code/__DEFINES/construction.dm
@@ -40,10 +40,12 @@
#define MAT_DIAMOND "$diamond"
#define MAT_URANIUM "$uranium"
#define MAT_PLASMA "$plasma"
+#define MAT_BLUESPACE "$bluespace"
#define MAT_BANANIUM "$bananium"
#define MAT_TRANQUILLITE "$tranquillite"
#define MAT_TITANIUM "$titanium"
#define MAT_BIOMASS "$biomass"
+#define MAT_PLASTIC "$plastic"
//The amount of materials you get from a sheet of mineral like iron/diamond/glass etc
#define MINERAL_MATERIAL_AMOUNT 2000
//The maximum size of a stack object.
diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm
index 9be310c94ef..0ec1567f86d 100644
--- a/code/__DEFINES/flags.dm
+++ b/code/__DEFINES/flags.dm
@@ -24,6 +24,36 @@
#define DROPDEL 16384 // When dropped, it calls qdel on itself
+
+/* Secondary atom flags, for the flags_2 var, denoted with a _2 */
+
+#define SLOWS_WHILE_IN_HAND_2 1
+#define NO_EMP_WIRES_2 2
+#define HOLOGRAM_2 4
+#define FROZEN_2 8
+#define STATIONLOVING_2 16
+#define INFORM_ADMINS_ON_RELOCATE_2 32
+#define BANG_PROTECT_2 64
+
+// An item worn in the ear slot with HEALS_EARS will heal your ears each
+// Life() tick, even if normally your ears would be too damaged to heal.
+#define HEALS_EARS_2 128
+
+// A mob with OMNITONGUE has no restriction in the ability to speak
+// languages that they know. So even if they wouldn't normally be able to
+// through mob or tongue restrictions, this flag allows them to ignore
+// those restrictions.
+#define OMNITONGUE_2 256
+
+// TESLA_IGNORE grants immunity from being targeted by tesla-style electricity
+#define TESLA_IGNORE_2 512
+
+// Stops you from putting things like an RCD or other items into an ORM or protolathe for materials.
+#define NO_MAT_REDEMPTION_2 1024
+
+// LAVA_PROTECT used on the flags_2 variable for both SUIT and HEAD items, and stops lava damage. Must be present in both to stop lava damage.
+#define LAVA_PROTECT_2 2048
+
//Reagent flags
#define REAGENT_NOREACT 1
diff --git a/code/__DEFINES/machines.dm b/code/__DEFINES/machines.dm
index 146d3bf27f0..160fa30a5c6 100644
--- a/code/__DEFINES/machines.dm
+++ b/code/__DEFINES/machines.dm
@@ -22,6 +22,7 @@
#define MECHFAB 16 //Remember, objects utilising this flag should have construction_time and construction_cost vars.
#define PODFAB 32 //Used by the spacepod part fabricator. Same idea as the mechfab
#define BIOGENERATOR 64 //Uses biomass
+#define SMELTER 128 //uses various minerals
//Note: More then one of these can be added to a design but imprinter and lathe designs are incompatable.
#define HYDRO_SPEED_MULTIPLIER 1
diff --git a/code/__HELPERS/type2type.dm b/code/__HELPERS/type2type.dm
index 569c17d1dbe..f90a9c4e5ad 100644
--- a/code/__HELPERS/type2type.dm
+++ b/code/__HELPERS/type2type.dm
@@ -364,4 +364,19 @@
value = splittext(value, "]") //"name]!" --> list("name","!")
for(var/A in value)
if(var_source.vars.Find(A))
- . += A
\ No newline at end of file
+ . += A
+
+/proc/type2parent(child)
+ var/string_type = "[child]"
+ var/last_slash = findlasttext(string_type, "/")
+ if(last_slash == 1)
+ switch(child)
+ if(/datum)
+ return null
+ if(/obj || /mob)
+ return /atom/movable
+ if(/area || /turf)
+ return /atom
+ else
+ return /datum
+ return text2path(copytext(string_type, 1, last_slash))
\ No newline at end of file
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index 908708de464..ab794b23b18 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -14,10 +14,10 @@
// No comment
/atom/proc/attackby(obj/item/W, mob/user, params)
- return
+ return SendSignal(COMSIG_PARENT_ATTACKBY, W, user, params)
/obj/attackby(obj/item/I, mob/living/user, params)
- return I.attack_obj(src, user)
+ return ..() || I.attack_obj(src, user)
/mob/living/attackby(obj/item/I, mob/living/user, params)
user.changeNext_move(CLICK_CD_MELEE)
diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm
index 05dab2bd97b..067114e52be 100644
--- a/code/controllers/subsystem/garbage.dm
+++ b/code/controllers/subsystem/garbage.dm
@@ -295,6 +295,7 @@ SUBSYSTEM_DEF(garbage)
if(isnull(D.gc_destroyed))
+ D.SendSignal(COMSIG_PARENT_QDELETED)
D.gc_destroyed = GC_CURRENTLY_BEING_QDELETED
var/start_time = world.time
var/start_tick = world.tick_usage
diff --git a/code/datums/cargoprofile.dm b/code/datums/cargoprofile.dm
index e0cdba221d7..32d0cf63b1b 100644
--- a/code/datums/cargoprofile.dm
+++ b/code/datums/cargoprofile.dm
@@ -101,7 +101,7 @@
name = "Move Small Containers"
id = "boxes"
blacklist = null
- whitelist = list(/obj/item/storage, /obj/item/moneybag, /obj/item/evidencebag,
+ whitelist = list(/obj/item/storage, /obj/item/storage/bag/money, /obj/item/evidencebag,
/obj/item/storage/bag/tray, /obj/item/pizzabox, /obj/item/clipboard,
/obj/item/smallDelivery, /obj/structure/bigDelivery)
diff --git a/code/datums/components/README.md b/code/datums/components/README.md
new file mode 100644
index 00000000000..ee156e45525
--- /dev/null
+++ b/code/datums/components/README.md
@@ -0,0 +1,120 @@
+# Datum Component System (DCS)
+
+## Concept
+
+Loosely adapted from /vg/. This is an entity component system for adding behaviours to datums when inheritance doesn't quite cut it. By using signals and events instead of direct inheritance, you can inject behaviours without hacky overloads. It requires a different method of thinking, but is not hard to use correctly. If a behaviour can have application across more than one thing. Make it generic, make it a component. Atom/mob/obj event? Give it a signal, and forward it's arguments with a `SendSignal()` call. Now every component that want's to can also know about this happening.
+
+### In the code
+
+#### Slippery things
+
+At the time of this writing, every object that is slippery overrides atom/Crossed does some checks, then slips the mob. Instead of all those Crossed overrides they could add a slippery component to all these objects. And have the checks in one proc that is run by the Crossed event
+
+#### Powercells
+
+A lot of objects have powercells. The `get_cell()` proc was added to give generic access to the cell var if it had one. This is just a specific use case of `GetComponent()`
+
+#### Radios
+
+The radio object as it is should not exist, given that more things use the _concept_ of radios rather than the object itself. The actual function of the radio can exist in a component which all the things that use it (Request consoles, actual radios, the SM shard) can add to themselves.
+
+#### Standos
+
+Stands have a lot of procs which mimic mob procs. Rather than inserting hooks for all these procs in overrides, the same can be accomplished with signals
+
+## API
+
+### Defines
+
+1. `COMPONENT_INCOMPATIBLE` Return this from `/datum/component/Initialize` or `datum/component/OnTransfer` to have the component be deleted if it's applied to an incorrect type. `parent` must not be modified if this is to be returned.
+
+### Vars
+
+1. `/datum/var/list/datum_components` (private)
+ * Lazy associated list of type -> component/list of components.
+1. `/datum/component/var/enabled` (protected, boolean)
+ * If the component is enabled. If not, it will not react to signals
+ * `FALSE` by default, set to `TRUE` when a signal is registered
+1. `/datum/component/var/dupe_mode` (protected, enum)
+ * How duplicate component types are handled when added to the datum.
+ * `COMPONENT_DUPE_HIGHLANDER` (default): Old component will be deleted, new component will first have `/datum/component/proc/InheritComponent(datum/component/old, FALSE)` on it
+ * `COMPONENT_DUPE_ALLOWED`: The components will be treated as separate, `GetComponent()` will return the first added
+ * `COMPONENT_DUPE_UNIQUE`: New component will be deleted, old component will first have `/datum/component/proc/InheritComponent(datum/component/new, TRUE)` on it
+ * `COMPONENT_DUPE_UNIQUE_PASSARGS`: New component will never exist and instead its initialization arguments will be passed on to the old component.
+1. `/datum/component/var/dupe_type` (protected, type)
+ * Definition of a duplicate component type
+ * `null` means exact match on `type` (default)
+ * Any other type means that and all subtypes
+1. `/datum/component/var/list/signal_procs` (private)
+ * Associated lazy list of signals -> `/datum/callback`s that will be run when the parent datum recieves that signal
+1. `/datum/component/var/datum/parent` (protected, read-only)
+ * The datum this component belongs to
+ * Never `null` in child procs
+
+### Procs
+
+1. `/datum/proc/GetComponent(component_type(type)) -> datum/component?` (public, final)
+ * Returns a reference to a component of component_type if it exists in the datum, null otherwise
+1. `/datum/proc/GetComponents(component_type(type)) -> list` (public, final)
+ * Returns a list of references to all components of component_type that exist in the datum
+1. `/datum/proc/GetExactComponent(component_type(type)) -> datum/component?` (public, final)
+ * Returns a reference to a component whose type MATCHES component_type if that component exists in the datum, null otherwise
+1. `GET_COMPONENT(varname, component_type)` OR `GET_COMPONENT_FROM(varname, component_type, src)`
+ * Shorthand for `var/component_type/varname = src.GetComponent(component_type)`
+1. `/datum/proc/AddComponent(component_type(type), ...) -> datum/component` (public, final)
+ * Creates an instance of `component_type` in the datum and passes `...` to its `Initialize()` call
+ * Sends the `COMSIG_COMPONENT_ADDED` signal to the datum
+ * All components a datum owns are deleted with the datum
+ * Returns the component that was created. Or the old component in a dupe situation where `COMPONENT_DUPE_UNIQUE` was set
+ * If this tries to add an component to an incompatible type, the component will be deleted and the result will be `null`. This is very unperformant, try not to do it
+ * Properly handles duplicate situations based on the `dupe_mode` var
+1. `/datum/proc/LoadComponent(component_type(type), ...) -> datum/component` (public, final)
+ * Equivalent to calling `GetComponent(component_type)` where, if the result would be `null`, returns `AddComponent(component_type, ...)` instead
+1. `/datum/proc/ComponentActivated(datum/component/C)` (abstract, async)
+ * Called on a component's `parent` after a signal recieved causes it to activate. `src` is the parameter
+ * Will only be called if a component's callback returns `TRUE`
+1. `/datum/proc/TakeComponent(datum/component/C)` (public, final)
+ * Properly transfers ownership of a component from one datum to another
+ * Signals `COMSIG_COMPONENT_REMOVING` on the parent
+ * Called on the datum you want to own the component with another datum's component
+1. `/datum/proc/SendSignal(signal, ...)` (public, final)
+ * Call to send a signal to the components of the target datum
+ * Extra arguments are to be specified in the signal definition
+ * Returns a bitflag with signal specific information assembled from all activated components
+1. `/datum/component/New(datum/parent, ...)` (private, final)
+ * Runs internal setup for the component
+ * Extra arguments are passed to `Initialize()`
+1. `/datum/component/Initialize(...)` (abstract, no-sleep)
+ * Called by `New()` with the same argments excluding `parent`
+ * Component does not exist in `parent`'s `datum_components` list yet, although `parent` is set and may be used
+ * Signals will not be recieved while this function is running
+ * Component may be deleted after this function completes without being attached
+ * Do not call `qdel(src)` from this function
+1. `/datum/component/Destroy(force(bool), silent(bool))` (virtual, no-sleep)
+ * Sends the `COMSIG_COMPONENT_REMOVING` signal to the parent datum if the `parent` isn't being qdeleted
+ * Properly removes the component from `parent` and cleans up references
+ * Setting `force` makes it not check for and remove the component from the parent
+ * Setting `silent` deletes the component without sending a `COMSIG_COMPONENT_REMOVING` signal
+1. `/datum/component/proc/InheritComponent(datum/component/C, i_am_original(boolean))` (abstract, no-sleep)
+ * Called on a component when a component of the same type was added to the same parent
+ * See `/datum/component/var/dupe_mode`
+ * `C`'s type will always be the same of the called component
+1. `/datum/component/proc/AfterComponentActivated()` (abstract, async)
+ * Called on a component that was activated after it's `parent`'s `ComponentActivated()` is called
+1. `/datum/component/proc/OnTransfer(datum/new_parent)` (abstract, no-sleep)
+ * Called before `new_parent` is assigned to `parent` in `TakeComponent()`
+ * Allows the component to react to ownership transfers
+1. `/datum/component/proc/_RemoveFromParent()` (private, final)
+ * Clears `parent` and removes the component from it's component list
+1. `/datum/component/proc/_JoinParent` (private, final)
+ * Tries to add the component to it's `parent`s `datum_components` list
+1. `/datum/component/proc/RegisterSignal(signal(string/list of strings), proc_ref(type), override(boolean))` (protected, final) (Consider removing for performance gainz)
+ * If signal is a list it will be as if RegisterSignal was called for each of the entries with the same following arguments
+ * Makes a component listen for the specified `signal` on it's `parent` datum.
+ * When that signal is recieved `proc_ref` will be called on the component, along with associated arguments
+ * Example proc ref: `.proc/OnEvent`
+ * If a previous registration is overwritten by the call, a runtime occurs. Setting `override` to TRUE prevents this
+ * These callbacks run asyncronously
+ * Returning `TRUE` from these callbacks will trigger a `TRUE` return from the `SendSignal()` that initiated it
+
+### See/Define signals and their arguments in __DEFINES\components.dm
diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm
new file mode 100644
index 00000000000..0da932d1575
--- /dev/null
+++ b/code/datums/components/_component.dm
@@ -0,0 +1,264 @@
+/datum/component
+ var/enabled = FALSE
+ var/dupe_mode = COMPONENT_DUPE_HIGHLANDER
+ var/dupe_type
+ var/list/signal_procs
+ var/datum/parent
+
+/datum/component/New(datum/P, ...)
+ parent = P
+ var/list/arguments = args.Copy(2)
+ if(Initialize(arglist(arguments)) == COMPONENT_INCOMPATIBLE)
+ qdel(src, TRUE, TRUE)
+ CRASH("Incompatible [type] assigned to a [P]!")
+
+ _JoinParent(P)
+
+/datum/component/proc/_JoinParent()
+ var/datum/P = parent
+ //lazy init the parent's dc list
+ var/list/dc = P.datum_components
+ if(!dc)
+ P.datum_components = dc = list()
+
+ //set up the typecache
+ var/our_type = type
+ for(var/I in _GetInverseTypeList(our_type))
+ var/test = dc[I]
+ if(test) //already another component of this type here
+ var/list/components_of_type
+ if(!length(test))
+ components_of_type = list(test)
+ dc[I] = components_of_type
+ else
+ components_of_type = test
+ if(I == our_type) //exact match, take priority
+ var/inserted = FALSE
+ for(var/J in 1 to components_of_type.len)
+ var/datum/component/C = components_of_type[J]
+ if(C.type != our_type) //but not over other exact matches
+ components_of_type.Insert(J, I)
+ inserted = TRUE
+ break
+ if(!inserted)
+ components_of_type += src
+ else //indirect match, back of the line with ya
+ components_of_type += src
+ else //only component of this type, no list
+ dc[I] = src
+
+/datum/component/proc/Initialize(...)
+ return
+
+/datum/component/Destroy(force=FALSE, silent=FALSE)
+ enabled = FALSE
+ var/datum/P = parent
+ if(!force)
+ _RemoveFromParent()
+ if(!silent)
+ P.SendSignal(COMSIG_COMPONENT_REMOVING, src)
+ parent = null
+ LAZYCLEARLIST(signal_procs)
+ return ..()
+
+/datum/component/proc/_RemoveFromParent()
+ var/datum/P = parent
+ var/list/dc = P.datum_components
+ for(var/I in _GetInverseTypeList())
+ var/list/components_of_type = dc[I]
+ if(length(components_of_type)) //
+ var/list/subtracted = components_of_type - src
+ if(subtracted.len == 1) //only 1 guy left
+ dc[I] = subtracted[1] //make him special
+ else
+ dc[I] = subtracted
+ else //just us
+ dc -= I
+ if(!dc.len)
+ P.datum_components = null
+
+/datum/component/proc/RegisterSignal(sig_type_or_types, proc_or_callback, override = FALSE)
+ if(QDELETED(src))
+ return
+ var/list/procs = signal_procs
+ if(!procs)
+ procs = list()
+ signal_procs = procs
+
+ var/list/sig_types = islist(sig_type_or_types) ? sig_type_or_types : list(sig_type_or_types)
+ for(var/sig_type in sig_types)
+ if(!override)
+ . = procs[sig_type]
+ if(.)
+ stack_trace("[sig_type] overridden. Use override = TRUE to suppress this warning")
+
+ if(!istype(proc_or_callback, /datum/callback)) //if it wasnt a callback before, it is now
+ proc_or_callback = CALLBACK(src, proc_or_callback)
+ procs[sig_type] = proc_or_callback
+
+ enabled = TRUE
+
+/datum/component/proc/InheritComponent(datum/component/C, i_am_original)
+ return
+
+/datum/component/proc/OnTransfer(datum/new_parent)
+ return
+
+/datum/component/proc/_GetInverseTypeList(our_type = type)
+ #if DM_VERSION >= 513
+ #warning 512 is definitely stable now, remove the old code
+ #endif
+
+ #if DM_VERSION < 512
+ //remove this when we use 512 full time
+ set invisibility = 101
+ #endif
+ //we can do this one simple trick
+ var/current_type = parent_type
+ . = list(our_type, current_type)
+ //and since most components are root level + 1, this won't even have to run
+ while(current_type != /datum/component)
+ current_type = type2parent(current_type)
+ . += current_type
+
+/datum/proc/SendSignal(sigtype, ...)
+ var/list/comps = datum_components
+ if(!comps)
+ return NONE
+ var/list/arguments = args.Copy(2)
+ var/target = comps[/datum/component]
+ if(!length(target))
+ var/datum/component/C = target
+ if(!C.enabled)
+ return NONE
+ var/datum/callback/CB = C.signal_procs[sigtype]
+ if(!CB)
+ return NONE
+ return CB.InvokeAsync(arglist(arguments))
+ . = NONE
+ for(var/I in target)
+ var/datum/component/C = I
+ if(!C.enabled)
+ continue
+ var/datum/callback/CB = C.signal_procs[sigtype]
+ if(!CB)
+ continue
+ . |= CB.InvokeAsync(arglist(arguments))
+
+/datum/proc/GetComponent(c_type)
+ var/list/dc = datum_components
+ if(!dc)
+ return null
+ . = dc[c_type]
+ if(length(.))
+ return .[1]
+
+/datum/proc/GetExactComponent(c_type)
+ var/list/dc = datum_components
+ if(!dc)
+ return null
+ var/datum/component/C = dc[c_type]
+ if(C)
+ if(length(C))
+ C = C[1]
+ if(C.type == c_type)
+ return C
+ return null
+
+/datum/proc/GetComponents(c_type)
+ var/list/dc = datum_components
+ if(!dc)
+ return null
+ . = dc[c_type]
+ if(!length(.))
+ return list(.)
+
+/datum/proc/AddComponent(new_type, ...)
+ var/datum/component/nt = new_type
+ var/dm = initial(nt.dupe_mode)
+ var/dt = initial(nt.dupe_type)
+
+ var/datum/component/old_comp
+ var/datum/component/new_comp
+
+ if(ispath(nt))
+ if(nt == /datum/component)
+ CRASH("[nt] attempted instantiation!")
+ if(!isnum(dm))
+ CRASH("[nt]: Invalid dupe_mode ([dm])!")
+ if(dt && !ispath(dt))
+ CRASH("[nt]: Invalid dupe_type ([dt])!")
+ else
+ new_comp = nt
+
+ args[1] = src
+
+ if(dm != COMPONENT_DUPE_ALLOWED)
+ if(!dt)
+ old_comp = GetExactComponent(nt)
+ else
+ old_comp = GetComponent(dt)
+ if(old_comp)
+ switch(dm)
+ if(COMPONENT_DUPE_UNIQUE)
+ if(!new_comp)
+ new_comp = new nt(arglist(args))
+ if(!QDELETED(new_comp))
+ old_comp.InheritComponent(new_comp, TRUE)
+ QDEL_NULL(new_comp)
+ if(COMPONENT_DUPE_HIGHLANDER)
+ if(!new_comp)
+ new_comp = new nt(arglist(args))
+ if(!QDELETED(new_comp))
+ new_comp.InheritComponent(old_comp, FALSE)
+ QDEL_NULL(old_comp)
+ if(COMPONENT_DUPE_UNIQUE_PASSARGS)
+ if(!new_comp)
+ var/list/arguments = args.Copy(2)
+ old_comp.InheritComponent(null, TRUE, arguments)
+ else
+ old_comp.InheritComponent(new_comp, TRUE)
+ else if(!new_comp)
+ new_comp = new nt(arglist(args)) // There's a valid dupe mode but there's no old component, act like normal
+ else if(!new_comp)
+ new_comp = new nt(arglist(args)) // Dupes are allowed, act like normal
+
+ if(!old_comp && !QDELETED(new_comp)) // Nothing related to duplicate components happened and the new component is healthy
+ SendSignal(COMSIG_COMPONENT_ADDED, new_comp)
+ return new_comp
+ return old_comp
+
+/datum/proc/LoadComponent(component_type, ...)
+ . = GetComponent(component_type)
+ if(!.)
+ return AddComponent(arglist(args))
+
+/datum/proc/TakeComponent(datum/component/C)
+ if(!C)
+ return
+ var/datum/helicopter = C.parent
+ if(helicopter == src)
+ //if we're taking to the same thing no need for anything
+ return
+ if(C.OnTransfer(src) == COMPONENT_INCOMPATIBLE)
+ qdel(C)
+ return
+ C._RemoveFromParent()
+ helicopter.SendSignal(COMSIG_COMPONENT_REMOVING, C)
+ C.parent = src
+ if(C == AddComponent(C))
+ C._JoinParent()
+
+/datum/proc/TransferComponents(datum/target)
+ var/list/dc = datum_components
+ if(!dc)
+ return
+ var/comps = dc[/datum/component]
+ if(islist(comps))
+ for(var/I in comps)
+ target.TakeComponent(I)
+ else
+ target.TakeComponent(comps)
+
+/datum/component/nano_host()
+ return parent
diff --git a/code/datums/components/material_container.dm b/code/datums/components/material_container.dm
new file mode 100644
index 00000000000..f08181cb5f4
--- /dev/null
+++ b/code/datums/components/material_container.dm
@@ -0,0 +1,389 @@
+/*
+ This datum should be used for handling mineral contents of machines and whatever else is supposed to hold minerals and make use of them.
+
+ Variables:
+ amount - raw amount of the mineral this container is holding, calculated by the defined value MINERAL_MATERIAL_AMOUNT=2000.
+ max_amount - max raw amount of mineral this container can hold.
+ sheet_type - type of the mineral sheet the container handles, used for output.
+ parent - object that this container is being used by, used for output.
+ MAX_STACK_SIZE - size of a stack of mineral sheets. Constant.
+*/
+
+/datum/component/material_container
+ var/total_amount = 0
+ var/max_amount
+ var/sheet_type
+ var/list/materials
+ var/show_on_examine
+ var/list/allowed_typecache
+ var/last_inserted_id
+ var/precise_insertion = FALSE
+ var/datum/callback/precondition
+ var/datum/callback/after_insert
+
+/datum/component/material_container/Initialize(list/mat_list, max_amt = 0, _show_on_examine = FALSE, list/allowed_types, datum/callback/_precondition, datum/callback/_after_insert)
+ materials = list()
+ max_amount = max(0, max_amt)
+ show_on_examine = _show_on_examine
+ if(allowed_types)
+ allowed_typecache = typecacheof(allowed_types)
+ precondition = _precondition
+ after_insert = _after_insert
+
+ RegisterSignal(COMSIG_PARENT_ATTACKBY, .proc/OnAttackBy)
+ RegisterSignal(COMSIG_PARENT_EXAMINE, .proc/OnExamine)
+
+ var/list/possible_mats = list()
+ for(var/mat_type in subtypesof(/datum/material))
+ var/datum/material/MT = mat_type
+ possible_mats[initial(MT.id)] = mat_type
+ for(var/id in mat_list)
+ if(possible_mats[id])
+ var/mat_path = possible_mats[id]
+ materials[id] = new mat_path()
+
+/datum/component/material_container/proc/OnExamine(mob/user)
+ for(var/I in materials)
+ var/datum/material/M = materials[I]
+ var/amt = amount(M.id)
+ if(amt)
+ to_chat(user, "It has [amt] units of [lowertext(M.name)] stored.")
+
+/datum/component/material_container/proc/OnAttackBy(obj/item/I, mob/living/user)
+ var/list/tc = allowed_typecache
+ if(user.a_intent != INTENT_HELP)
+ return
+ if((I.flags_2 & (HOLOGRAM_2 | NO_MAT_REDEMPTION_2)) || (tc && !is_type_in_typecache(I, tc)))
+ to_chat(user, "[parent] won't accept [I]!")
+ return
+ . = COMPONENT_NO_AFTERATTACK
+ var/datum/callback/pc = precondition
+ if(pc && !pc.Invoke(user))
+ return
+ var/material_amount = get_item_material_amount(I)
+ if(!material_amount)
+ to_chat(user, "[I] does not contain sufficient amounts of metal or glass to be accepted by [parent].")
+ return
+ if(!has_space(material_amount))
+ to_chat(user, "[parent] is full. Please remove metal or glass from [parent] in order to insert more.")
+ return
+ user_insert(I, user)
+
+/datum/component/material_container/proc/user_insert(obj/item/I, mob/living/user)
+ set waitfor = FALSE
+ var/requested_amount
+ if(istype(I, /obj/item/stack) && precise_insertion)
+ var/atom/current_parent = parent
+ var/obj/item/stack/S = I
+ requested_amount = input(user, "How much do you want to insert?", "Inserting [S.singular_name]s") as num|null
+ if(isnull(requested_amount) || (requested_amount <= 0))
+ return
+ if(QDELETED(I) || QDELETED(user) || QDELETED(src) || parent != current_parent || user.incapacitated() || !in_range(current_parent, user) || user.l_hand != I && user.r_hand != I)
+ return
+ if(!user.drop_item())
+ to_chat(user, "[I] is stuck to you and cannot be placed into [parent].")
+ return
+ var/inserted = insert_item(I, stack_amt = requested_amount)
+ if(inserted)
+ if(istype(I, /obj/item/stack))
+ var/obj/item/stack/S = I
+ to_chat(user, "You insert [inserted] [S.singular_name][inserted>1 ? "s" : ""] into [parent].")
+ if(!QDELETED(I))
+ if(!user.put_in_hands(I))
+ stack_trace("Warning: User could not put object back in hand during material container insertion, line [__LINE__]! This can lead to issues.")
+ I.forceMove(user.loc)
+ else
+ to_chat(user, "You insert a material total of [inserted] into [parent].")
+ qdel(I)
+ if(after_insert)
+ after_insert.Invoke(I.type, last_inserted_id, inserted)
+ else
+ user.put_in_active_hand(I)
+
+//For inserting an amount of material
+/datum/component/material_container/proc/insert_amount(amt, id = null)
+ if(amt > 0 && has_space(amt))
+ var/total_amount_saved = total_amount
+ if(id)
+ var/datum/material/M = materials[id]
+ if(M)
+ M.amount += amt
+ total_amount += amt
+ else
+ for(var/i in materials)
+ var/datum/material/M = materials[i]
+ M.amount += amt
+ total_amount += amt
+ return (total_amount - total_amount_saved)
+ return FALSE
+
+/datum/component/material_container/proc/insert_stack(obj/item/stack/S, amt, multiplier = 1)
+ if(isnull(amt))
+ amt = S.amount
+
+ if(amt <= 0)
+ return FALSE
+
+ if(amt > S.amount)
+ amt = S.amount
+
+ var/material_amt = get_item_material_amount(S)
+ if(!material_amt)
+ return FALSE
+
+ amt = min(amt, round(((max_amount - total_amount) / material_amt)))
+ if(!amt)
+ return FALSE
+
+ last_inserted_id = insert_materials(S,amt * multiplier)
+ S.use(amt)
+ return amt
+
+/datum/component/material_container/proc/insert_item(obj/item/I, multiplier = 1, stack_amt)
+ if(!I)
+ return FALSE
+ if(istype(I, /obj/item/stack))
+ return insert_stack(I, stack_amt, multiplier)
+
+ var/material_amount = get_item_material_amount(I)
+ if(!material_amount || !has_space(material_amount))
+ return FALSE
+
+ last_inserted_id = insert_materials(I, multiplier)
+ return material_amount
+
+/datum/component/material_container/proc/insert_materials(obj/item/I, multiplier = 1) //for internal usage only
+ var/datum/material/M
+ var/primary_mat
+ var/max_mat_value = 0
+ for(var/MAT in materials)
+ M = materials[MAT]
+ M.amount += I.materials[MAT] * multiplier
+ total_amount += I.materials[MAT] * multiplier
+ if(I.materials[MAT] > max_mat_value)
+ primary_mat = MAT
+ return primary_mat
+
+//For consuming material
+//mats is a list of types of material to use and the corresponding amounts, example: list(MAT_METAL=100, MAT_GLASS=200)
+/datum/component/material_container/proc/use_amount(list/mats, multiplier=1)
+ if(!mats || !mats.len)
+ return FALSE
+
+ var/datum/material/M
+ for(var/MAT in materials)
+ M = materials[MAT]
+ if(M.amount < (mats[MAT] * multiplier))
+ return FALSE
+
+ var/total_amount_save = total_amount
+ for(var/MAT in materials)
+ M = materials[MAT]
+ M.amount -= mats[MAT] * multiplier
+ total_amount -= mats[MAT] * multiplier
+
+ return total_amount_save - total_amount
+
+
+/datum/component/material_container/proc/use_amount_type(amt, id)
+ var/datum/material/M = materials[id]
+ if(M)
+ if(M.amount >= amt)
+ M.amount -= amt
+ total_amount -= amt
+ return amt
+ return FALSE
+
+/datum/component/material_container/proc/transer_amt_to(var/datum/component/material_container/T, amt, id)
+ if((amt==0)||(!T)||(!id))
+ return FALSE
+ if(amt<0)
+ return T.transer_amt_to(src, -amt, id)
+ var/datum/material/M = materials[id]
+
+ if(M)
+ var/tr = min(amt, M.amount,T.can_insert_amount(amt, id))
+ if(tr)
+ use_amount_type(tr, id)
+ T.insert_amount(tr, id)
+ return tr
+ return FALSE
+
+/datum/component/material_container/proc/can_insert_amount(amt, id)
+ if(amt && id)
+ var/datum/material/M = materials[id]
+ if(M)
+ if((total_amount + amt) <= max_amount)
+ return amt
+ else
+ return (max_amount-total_amount)
+
+/datum/component/material_container/proc/can_use_amount(amt, id, list/mats)
+ if(amt && id)
+ var/datum/material/M = materials[id]
+ if(M && M.amount >= amt)
+ return TRUE
+ else if(istype(mats))
+ for(var/M in mats)
+ if(materials[M] && (mats[M] <= materials[M]))
+ continue
+ else
+ return FALSE
+ return TRUE
+ return FALSE
+
+//For spawning mineral sheets; internal use only
+/datum/component/material_container/proc/retrieve(sheet_amt, datum/material/M, target = null)
+ if(!M.sheet_type)
+ return FALSE
+ if(sheet_amt > 0)
+ if(M.amount < (sheet_amt * MINERAL_MATERIAL_AMOUNT))
+ sheet_amt = round(M.amount / MINERAL_MATERIAL_AMOUNT)
+ var/count = 0
+ while(sheet_amt > MAX_STACK_SIZE)
+ new M.sheet_type(get_turf(parent), MAX_STACK_SIZE)
+ count += MAX_STACK_SIZE
+ use_amount_type(sheet_amt * MINERAL_MATERIAL_AMOUNT, M.id)
+ sheet_amt -= MAX_STACK_SIZE
+ if(round((sheet_amt * MINERAL_MATERIAL_AMOUNT) / MINERAL_MATERIAL_AMOUNT))
+ var/obj/item/stack/sheet/s = new M.sheet_type(get_turf(parent), sheet_amt)
+ if(target)
+ s.forceMove(target)
+ count += sheet_amt
+ use_amount_type(sheet_amt * MINERAL_MATERIAL_AMOUNT, M.id)
+ return count
+ return FALSE
+
+/datum/component/material_container/proc/retrieve_sheets(sheet_amt, id, target = null)
+ if(materials[id])
+ return retrieve(sheet_amt, materials[id], target)
+ return FALSE
+
+/datum/component/material_container/proc/retrieve_amount(amt, id, target)
+ return retrieve_sheets(amount2sheet(amt), id, target)
+
+/datum/component/material_container/proc/retrieve_all(target = null)
+ var/result = 0
+ var/datum/material/M
+ for(var/MAT in materials)
+ M = materials[MAT]
+ result += retrieve_sheets(amount2sheet(M.amount), MAT, target)
+ return result
+
+/datum/component/material_container/proc/has_space(amt = 0)
+ return (total_amount + amt) <= max_amount
+
+/datum/component/material_container/proc/has_materials(list/mats, multiplier=1)
+ if(!mats || !mats.len)
+ return FALSE
+
+ var/datum/material/M
+ for(var/MAT in mats)
+ M = materials[MAT]
+ if(M.amount < (mats[MAT] * multiplier))
+ return FALSE
+ return TRUE
+
+/datum/component/material_container/proc/amount2sheet(amt)
+ if(amt >= MINERAL_MATERIAL_AMOUNT)
+ return round(amt / MINERAL_MATERIAL_AMOUNT)
+ return FALSE
+
+/datum/component/material_container/proc/sheet2amount(sheet_amt)
+ if(sheet_amt > 0)
+ return sheet_amt * MINERAL_MATERIAL_AMOUNT
+ return FALSE
+
+/datum/component/material_container/proc/amount(id)
+ var/datum/material/M = materials[id]
+ return M ? M.amount : 0
+
+//returns the amount of material relevant to this container;
+//if this container does not support glass, any glass in 'I' will not be taken into account
+/datum/component/material_container/proc/get_item_material_amount(obj/item/I)
+ if(!istype(I))
+ return FALSE
+ var/material_amount = 0
+ for(var/MAT in materials)
+ material_amount += I.materials[MAT]
+ return material_amount
+
+
+/datum/material
+ var/name
+ var/amount = 0
+ var/id = null
+ var/sheet_type = null
+ var/coin_type = null
+
+/datum/material/metal
+ name = "Metal"
+ id = MAT_METAL
+ sheet_type = /obj/item/stack/sheet/metal
+ coin_type = /obj/item/coin/iron
+
+/datum/material/glass
+ name = "Glass"
+ id = MAT_GLASS
+ sheet_type = /obj/item/stack/sheet/glass
+
+/datum/material/silver
+ name = "Silver"
+ id = MAT_SILVER
+ sheet_type = /obj/item/stack/sheet/mineral/silver
+ coin_type = /obj/item/coin/silver
+
+/datum/material/gold
+ name = "Gold"
+ id = MAT_GOLD
+ sheet_type = /obj/item/stack/sheet/mineral/gold
+ coin_type = /obj/item/coin/gold
+
+/datum/material/diamond
+ name = "Diamond"
+ id = MAT_DIAMOND
+ sheet_type = /obj/item/stack/sheet/mineral/diamond
+ coin_type = /obj/item/coin/diamond
+
+/datum/material/uranium
+ name = "Uranium"
+ id = MAT_URANIUM
+ sheet_type = /obj/item/stack/sheet/mineral/uranium
+ coin_type = /obj/item/coin/uranium
+
+/datum/material/plasma
+ name = "Solid Plasma"
+ id = MAT_PLASMA
+ sheet_type = /obj/item/stack/sheet/mineral/plasma
+ coin_type = /obj/item/coin/plasma
+
+/datum/material/bluespace
+ name = "Bluespace Mesh"
+ id = MAT_BLUESPACE
+ sheet_type = /obj/item/stack/sheet/bluespace_crystal
+
+/datum/material/bananium
+ name = "Bananium"
+ id = MAT_BANANIUM
+ sheet_type = /obj/item/stack/sheet/mineral/bananium
+ coin_type = /obj/item/coin/clown
+
+/datum/material/tranquillite
+ name = "Tranquillite"
+ id = MAT_TRANQUILLITE
+ sheet_type = /obj/item/stack/sheet/mineral/tranquillite
+ coin_type = /obj/item/coin/mime
+
+/datum/material/titanium
+ name = "Titanium"
+ id = MAT_TITANIUM
+ sheet_type = /obj/item/stack/sheet/mineral/titanium
+
+/datum/material/biomass
+ name = "Biomass"
+ id = MAT_BIOMASS
+
+/datum/material/plastic
+ name = "Plastic"
+ id = MAT_PLASTIC
+ sheet_type = /obj/item/stack/sheet/plastic
diff --git a/code/datums/datum.dm b/code/datums/datum.dm
index 68745b4dda1..3e8c11cc1e8 100644
--- a/code/datums/datum.dm
+++ b/code/datums/datum.dm
@@ -1,6 +1,6 @@
/datum
var/gc_destroyed //Time when this object was destroyed.
-
+ var/list/datum_components //for /datum/components
var/var_edited = FALSE //Warranty void if seal is broken
var/tmp/unique_datum_id = null
@@ -15,4 +15,17 @@
// Return the appropriate QDEL_HINT; in most cases this is QDEL_HINT_QUEUE.
/datum/proc/Destroy(force = FALSE, ...)
tag = null
+
+ var/list/dc = datum_components
+ if(dc)
+ var/all_components = dc[/datum/component]
+ if(length(all_components))
+ for(var/I in all_components)
+ var/datum/component/C = I
+ qdel(C, FALSE, TRUE)
+ else
+ var/datum/component/C = all_components
+ qdel(C, FALSE, TRUE)
+ dc.Cut()
+
return QDEL_HINT_QUEUE
\ No newline at end of file
diff --git a/code/datums/material_container.dm b/code/datums/material_container.dm
deleted file mode 100644
index 8e31e8ae033..00000000000
--- a/code/datums/material_container.dm
+++ /dev/null
@@ -1,292 +0,0 @@
-/*
- This datum should be used for handling mineral contents of machines and whatever else is supposed to hold minerals and make use of them.
-
- Variables:
- amount - raw amount of the mineral this container is holding, calculated by the defined value MINERAL_MATERIAL_AMOUNT=2000.
- max_amount - max raw amount of mineral this container can hold.
- sheet_type - type of the mineral sheet the container handles, used for output.
- owner - object that this container is being used by, used for output.
- MAX_STACK_SIZE - size of a stack of mineral sheets. Constant.
-*/
-
-/datum/material_container
- var/total_amount = 0
- var/max_amount
- var/sheet_type
- var/obj/owner
- var/list/materials = list()
- //MAX_STACK_SIZE = 50
- //MINERAL_MATERIAL_AMOUNT = 2000
-
-/datum/material_container/New(obj/O, list/mat_list, max_amt = 0)
- owner = O
- max_amount = max(0, max_amt)
-
- if(mat_list[MAT_METAL])
- materials[MAT_METAL] = new /datum/material/metal()
- if(mat_list[MAT_GLASS])
- materials[MAT_GLASS] = new /datum/material/glass()
- if(mat_list[MAT_SILVER])
- materials[MAT_SILVER] = new /datum/material/silver()
- if(mat_list[MAT_GOLD])
- materials[MAT_GOLD] = new /datum/material/gold()
- if(mat_list[MAT_DIAMOND])
- materials[MAT_DIAMOND] = new /datum/material/diamond()
- if(mat_list[MAT_URANIUM])
- materials[MAT_URANIUM] = new /datum/material/uranium()
- if(mat_list[MAT_PLASMA])
- materials[MAT_PLASMA] = new /datum/material/plasma()
- if(mat_list[MAT_BANANIUM])
- materials[MAT_BANANIUM] = new /datum/material/bananium()
- if(mat_list[MAT_TRANQUILLITE])
- materials[MAT_TRANQUILLITE] = new /datum/material/tranquillite()
- if(mat_list[MAT_TITANIUM])
- materials[MAT_TITANIUM] = new /datum/material/titanium()
-
-/datum/material_container/Destroy()
- QDEL_LIST_ASSOC_VAL(materials)
- owner = null
- return ..()
-
-//For inserting an amount of material
-/datum/material_container/proc/insert_amount(amt, material_type = null)
- if(amt > 0 && has_space(amt))
- var/total_amount_saved = total_amount
- if(material_type)
- var/datum/material/M = materials[material_type]
- if(M)
- M.amount += amt
- total_amount += amt
- else
- for(var/datum/material/M in materials)
- M.amount += amt
- total_amount += amt
- return (total_amount - total_amount_saved)
- return 0
-
-/datum/material_container/proc/insert_stack(obj/item/stack/S, amt = 0)
- if(amt <= 0)
- return 0
- if(amt > S.amount)
- amt = S.amount
- var/material_amt = get_item_material_amount(S)
- if(!material_amt)
- return 0
-
- amt = min(amt, round(((max_amount - total_amount) / material_amt)))
- if(!amt)
- return 0
-
- insert_materials(S,amt)
- S.use(amt)
- return amt
-
-/datum/material_container/proc/insert_item(obj/item/I, multiplier = 1)
- if(!I)
- return 0
- if(istype(I,/obj/item/stack))
- var/obj/item/stack/S = I
- return insert_stack(I, S.amount)
-
- var/material_amount = get_item_material_amount(I)
- if(!material_amount || !has_space(material_amount))
- return 0
-
- insert_materials(I, multiplier)
- return material_amount
-
-/datum/material_container/proc/insert_materials(obj/item/I, multiplier = 1) //for internal usage only
- var/datum/material/M
- for(var/MAT in materials)
- M = materials[MAT]
- M.amount += I.materials[MAT] * multiplier
- total_amount += I.materials[MAT] * multiplier
-
-//For consuming material
-//mats is a list of types of material to use and the corresponding amounts, example: list(MAT_METAL=100, MAT_GLASS=200)
-/datum/material_container/proc/use_amount(list/mats, multiplier=1)
- if(!mats || !mats.len)
- return 0
-
- var/datum/material/M
- for(var/MAT in materials)
- M = materials[MAT]
- if(M.amount < (mats[MAT] * multiplier))
- return 0
-
- var/total_amount_save = total_amount
- for(var/MAT in materials)
- M = materials[MAT]
- M.amount -= mats[MAT] * multiplier
- total_amount -= mats[MAT] * multiplier
-
- return total_amount_save - total_amount
-
-
-/datum/material_container/proc/use_amount_type(amt, material_type)
- var/datum/material/M
- M = materials[material_type]
- if(M)
- if(M.amount >= amt)
- M.amount -= amt
- total_amount -= amt
- return amt
- return 0
-
-//For spawning mineral sheets; internal use only
-/datum/material_container/proc/retrieve(sheet_amt, datum/material/M)
- if(!M.sheet_type)
- return 0
- if(sheet_amt > 0)
- if(M.amount < (sheet_amt * MINERAL_MATERIAL_AMOUNT))
- sheet_amt = round(M.amount / MINERAL_MATERIAL_AMOUNT)
- var/count = 0
-
- while(sheet_amt > MAX_STACK_SIZE)
- new M.sheet_type(get_turf(owner), MAX_STACK_SIZE)
- count += MAX_STACK_SIZE
- use_amount_type(sheet_amt * MINERAL_MATERIAL_AMOUNT, M.material_type)
- sheet_amt -= MAX_STACK_SIZE
-
- if(round(M.amount / MINERAL_MATERIAL_AMOUNT))
- new M.sheet_type(get_turf(owner), sheet_amt)
- count += sheet_amt
- use_amount_type(sheet_amt * MINERAL_MATERIAL_AMOUNT, M.material_type)
- return count
- return 0
-
-/datum/material_container/proc/retrieve_sheets(sheet_amt, material_type)
- if(materials[material_type])
- return retrieve(sheet_amt, materials[material_type])
- return 0
-
-/datum/material_container/proc/retrieve_amount(amt, material_type)
- return retrieve_sheets(amount2sheet(amt),material_type)
-
-/datum/material_container/proc/retrieve_all()
- var/result = 0
- var/datum/material/M
- for(var/MAT in materials)
- M = materials[MAT]
- result += retrieve_sheets(amount2sheet(M.amount), MAT)
- return result
-
-/datum/material_container/proc/has_space(amt = 0)
- return (total_amount + amt) <= max_amount
-
-/datum/material_container/proc/has_materials(list/mats, multiplier=1)
- if(!mats || !mats.len)
- return 0
-
- var/datum/material/M
- for(var/MAT in mats)
- M = materials[MAT]
- if(M.amount < (mats[MAT] * multiplier))
- return 0
- return 1
-
-/datum/material_container/proc/amount2sheet(amt)
- if(amt >= MINERAL_MATERIAL_AMOUNT)
- return round(amt / MINERAL_MATERIAL_AMOUNT)
- return 0
-
-/datum/material_container/proc/sheet2amount(sheet_amt)
- if(sheet_amt > 0)
- return sheet_amt * MINERAL_MATERIAL_AMOUNT
- return 0
-
-/datum/material_container/proc/amount(material_type)
- var/datum/material/M = materials[material_type]
- return M ? M.amount : 0
-
-//returns the amount of material relevant to this container;
-//if this container does not support glass, any glass in 'I' will not be taken into account
-/datum/material_container/proc/get_item_material_amount(obj/item/I)
- if(!istype(I))
- return 0
- var/material_amount = 0
- for(var/MAT in materials)
- material_amount += I.materials[MAT]
- return material_amount
-
-
-/datum/material
- var/amount = 0
- var/material_type = null
- var/sheet_type = null
-
-/datum/material/metal
-
-/datum/material/metal/New()
- ..()
- material_type = MAT_METAL
- sheet_type = /obj/item/stack/sheet/metal
-
-/datum/material/glass
-
-/datum/material/glass/New()
- ..()
- material_type = MAT_GLASS
- sheet_type = /obj/item/stack/sheet/glass
-
-/datum/material/silver
-
-/datum/material/silver/New()
- ..()
- material_type = MAT_SILVER
- sheet_type = /obj/item/stack/sheet/mineral/silver
-
-/datum/material/gold
-
-/datum/material/gold/New()
- ..()
- material_type = MAT_GOLD
- sheet_type = /obj/item/stack/sheet/mineral/gold
-
-/datum/material/diamond
-
-/datum/material/diamond/New()
- ..()
- material_type = MAT_DIAMOND
- sheet_type = /obj/item/stack/sheet/mineral/diamond
-
-/datum/material/uranium
-
-/datum/material/uranium/New()
- ..()
- material_type = MAT_URANIUM
- sheet_type = /obj/item/stack/sheet/mineral/uranium
-
-/datum/material/plasma
-
-/datum/material/plasma/New()
- ..()
- material_type = MAT_PLASMA
- sheet_type = /obj/item/stack/sheet/mineral/plasma
-
-/datum/material/bananium
-
-/datum/material/bananium/New()
- ..()
- material_type = MAT_BANANIUM
- sheet_type = /obj/item/stack/sheet/mineral/bananium
-
-/datum/material/tranquillite
-
-/datum/material/tranquillite/New()
- ..()
- material_type = MAT_TRANQUILLITE
- sheet_type = /obj/item/stack/sheet/mineral/tranquillite
-
-/datum/material/titanium
-
-/datum/material/titanium/New()
- ..()
- material_type = MAT_TITANIUM
- sheet_type = /obj/item/stack/sheet/mineral/titanium
-
-/datum/material/biomass
-
-/datum/material/biomass/New()
- ..()
- material_type = MAT_BIOMASS
\ No newline at end of file
diff --git a/code/datums/supplypacks.dm b/code/datums/supplypacks.dm
index 1abc1c4020f..496e7e05eac 100644
--- a/code/datums/supplypacks.dm
+++ b/code/datums/supplypacks.dm
@@ -1236,8 +1236,8 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine
/datum/supply_packs/materials/plastic30
name = "30 Plastic Sheets Crate"
contains = list(/obj/item/stack/sheet/plastic)
- amount = 30
- cost = 25
+ amount = 50
+ cost = 10
containername = "plastic sheets crate"
//////////////////////////////////////////////////////////////////////////////
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 33265ff8a49..d57199d5f91 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -1,7 +1,8 @@
/atom
layer = 2
var/level = 2
- var/flags = 0
+ var/flags = NONE
+ var/flags_2 = NONE
var/list/fingerprints
var/list/fingerprintshidden
var/fingerprintslast = null
@@ -220,6 +221,8 @@
else
to_chat(user, "Nothing.")
+ SendSignal(COMSIG_PARENT_EXAMINE, user)
+
return distance == -1 || (get_dist(src, user) <= distance) || isobserver(user) //observers do not have a range limit
/atom/proc/relaymove()
diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm
index 2fbf67e6d66..81ee03c5ee8 100644
--- a/code/game/machinery/autolathe.dm
+++ b/code/game/machinery/autolathe.dm
@@ -35,11 +35,10 @@
var/selected_category
var/screen = 1
- var/datum/material_container/materials
-
var/list/categories = list("Tools", "Electronics", "Construction", "Communication", "Security", "Machinery", "Medical", "Miscellaneous", "Dinnerware", "Imported")
/obj/machinery/autolathe/New()
+ AddComponent(/datum/component/material_container, list(MAT_METAL, MAT_GLASS), 0, FALSE, null, null, CALLBACK(src, .proc/AfterMaterialInsert))
..()
component_parts = list()
component_parts += new /obj/item/circuitboard/autolathe(null)
@@ -48,7 +47,6 @@
component_parts += new /obj/item/stock_parts/matter_bin(null)
component_parts += new /obj/item/stock_parts/manipulator(null)
component_parts += new /obj/item/stock_parts/console_screen(null)
- materials = new /datum/material_container(src, list(MAT_METAL = 1, MAT_GLASS = 1))
RefreshParts()
wires = new(src)
@@ -68,7 +66,8 @@
/obj/machinery/autolathe/Destroy()
QDEL_NULL(wires)
- QDEL_NULL(materials)
+ GET_COMPONENT(materials, /datum/component/material_container)
+ materials.retrieve_all()
return ..()
/obj/machinery/autolathe/interact(mob/user)
@@ -88,6 +87,7 @@
ui.open()
/obj/machinery/autolathe/ui_data(mob/user, ui_key = "main", datum/topic_state/state = default_state)
+ GET_COMPONENT(materials, /datum/component/material_container)
var/data[0]
data["screen"] = screen
data["total_amount"] = materials.total_amount
@@ -138,7 +138,7 @@
/obj/machinery/autolathe/proc/design_cost_data(datum/design/D)
var/list/data = list()
var/coeff = get_coeff(D)
-
+ GET_COMPONENT(materials, /datum/component/material_container)
var/has_metal = 1
if(D.materials[MAT_METAL] && (materials.amount(MAT_METAL) < (D.materials[MAT_METAL] / coeff)))
has_metal = 0
@@ -152,6 +152,7 @@
return data
/obj/machinery/autolathe/proc/queue_data(list/data)
+ GET_COMPONENT(materials, /datum/component/material_container)
var/temp_metal = materials.amount(MAT_METAL)
var/temp_glass = materials.amount(MAT_GLASS)
data["processing"] = being_built.len ? get_processing_line() : null
@@ -183,7 +184,6 @@
if(panel_open)
if(istype(O, /obj/item/crowbar))
- materials.retrieve_all()
default_deconstruction_crowbar(O)
return 1
else
@@ -213,32 +213,18 @@
to_chat(user, "This is not the correct type of disk for the autolathe!")
return 1
- var/material_amount = materials.get_item_material_amount(O)
- if(!material_amount)
- to_chat(user, "This object does not contain sufficient amounts of metal or glass to be accepted by the autolathe.")
- return 1
- if(!materials.has_space(material_amount))
- to_chat(user, "The autolathe is full. Please remove metal or glass from the autolathe in order to insert more.")
- return 1
- if(!user.unEquip(O))
- to_chat(user, "\The [O] is stuck to you and cannot be placed into the autolathe.")
- return 1
+ return ..()
- busy = 1
- var/inserted = materials.insert_item(O)
- if(inserted)
- if(istype(O,/obj/item/stack))
- if(O.materials[MAT_METAL])
+/obj/machinery/autolathe/proc/AfterMaterialInsert(type_inserted, id_inserted, amount_inserted)
+ if(ispath(type_inserted, /obj/item/ore/bluespace_crystal))
+ use_power(MINERAL_MATERIAL_AMOUNT / 10)
+ else
+ switch(id_inserted)
+ if(MAT_METAL)
flick("autolathe_o",src)//plays metal insertion animation
- if(O.materials[MAT_GLASS])
- flick("autolathe_r", src)//plays glass insertion animation
- to_chat(user, "You insert [inserted] sheet[inserted>1 ? "s" : ""] to the autolathe.")
- use_power(inserted * 100)
- else
- to_chat(user, "You insert a material total of [inserted] to the autolathe.")
- use_power(max(500, inserted / 10))
- qdel(O)
- busy = 0
+ if(MAT_GLASS)
+ flick("autolathe_r",src)//plays glass insertion animation
+ use_power(min(1000, amount_inserted / 100))
SSnanoui.update_uis(src)
/obj/machinery/autolathe/attack_ghost(mob/user)
@@ -274,6 +260,7 @@
//multiplier checks : only stacks can have one and its value is 1, 10 ,25 or max_multiplier
var/multiplier = text2num(href_list["multiplier"])
+ GET_COMPONENT(materials, /datum/component/material_container)
var/max_multiplier = min(design_last_ordered.maxstack, design_last_ordered.materials[MAT_METAL] ?round(materials.amount(MAT_METAL)/design_last_ordered.materials[MAT_METAL]):INFINITY,design_last_ordered.materials[MAT_GLASS]?round(materials.amount(MAT_GLASS)/design_last_ordered.materials[MAT_GLASS]):INFINITY)
var/is_stack = ispath(design_last_ordered.build_path, /obj/item/stack)
@@ -327,6 +314,7 @@
for(var/obj/item/stock_parts/matter_bin/MB in component_parts)
tot_rating += MB.rating
tot_rating *= 25000
+ GET_COMPONENT(materials, /datum/component/material_container)
materials.max_amount = tot_rating * 3
for(var/obj/item/stock_parts/manipulator/M in component_parts)
prod_coeff += M.rating - 1
@@ -339,6 +327,7 @@
desc = initial(desc)+"\nIt's building \a [initial(D.name)]."
var/is_stack = ispath(D.build_path, /obj/item/stack)
var/coeff = get_coeff(D)
+ GET_COMPONENT(materials, /datum/component/material_container)
var/metal_cost = D.materials[MAT_METAL]
var/glass_cost = D.materials[MAT_GLASS]
var/power = max(2000, (metal_cost+glass_cost)*multiplier/5)
@@ -370,7 +359,7 @@
return 0
var/coeff = get_coeff(D)
-
+ GET_COMPONENT(materials, /datum/component/material_container)
var/metal_amount = materials.amount(MAT_METAL)
if(custom_metal)
metal_amount = custom_metal
diff --git a/code/game/machinery/constructable_frame.dm b/code/game/machinery/constructable_frame.dm
index 65a7f851a4c..56583eb0b15 100644
--- a/code/game/machinery/constructable_frame.dm
+++ b/code/game/machinery/constructable_frame.dm
@@ -652,7 +652,7 @@ obj/item/circuitboard/rdserver
/obj/item/circuitboard/podfab
name = "Circuit board (Spacepod Fabricator)"
- build_path = /obj/machinery/spod_part_fabricator //ah fuck my life
+ build_path = /obj/machinery/mecha_part_fabricator/spacepod
board_type = "machine"
origin_tech = "programming=2;engineering=2"
frame_desc = "Requires 3 Matter Bins, 2 Manipulators, 2 Micro-Lasers, and 1 Console Screen."
diff --git a/code/game/machinery/recycler.dm b/code/game/machinery/recycler.dm
index e52a3f12532..13a2ba49ddd 100644
--- a/code/game/machinery/recycler.dm
+++ b/code/game/machinery/recycler.dm
@@ -13,25 +13,20 @@ var/const/SAFETY_COOLDOWN = 100
var/blood = 0
var/eat_dir = WEST
var/amount_produced = 1
- var/datum/material_container/materials
var/crush_damage = 1000
var/eat_victim_items = 1
var/item_recycle_sound = 'sound/machines/recycler.ogg'
/obj/machinery/recycler/New()
+ AddComponent(/datum/component/material_container, list(MAT_METAL, MAT_GLASS, MAT_PLASMA, MAT_SILVER, MAT_GOLD, MAT_DIAMOND, MAT_URANIUM, MAT_BANANIUM, MAT_TRANQUILLITE, MAT_TITANIUM, MAT_PLASTIC, MAT_BLUESPACE))
..()
component_parts = list()
component_parts += new /obj/item/circuitboard/recycler(null)
component_parts += new /obj/item/stock_parts/matter_bin(null)
component_parts += new /obj/item/stock_parts/manipulator(null)
- materials = new /datum/material_container(src, list(MAT_METAL=1, MAT_GLASS=1, MAT_SILVER=1, MAT_GOLD=1, MAT_DIAMOND=1, MAT_PLASMA=1, MAT_URANIUM=1, MAT_BANANIUM=1, MAT_TRANQUILLITE=1, MAT_TITANIUM=1))
RefreshParts()
update_icon()
-/obj/machinery/recycler/Destroy()
- QDEL_NULL(materials)
- return ..()
-
/obj/machinery/recycler/RefreshParts()
var/amt_made = 0
var/mat_mod = 0
@@ -40,6 +35,7 @@ var/const/SAFETY_COOLDOWN = 100
mat_mod *= 50000
for(var/obj/item/stock_parts/manipulator/M in component_parts)
amt_made = 25 * M.rating //% of materials salvaged
+ GET_COMPONENT(materials, /datum/component/material_container)
materials.max_amount = mat_mod
amount_produced = min(100, amt_made)
@@ -130,8 +126,9 @@ var/const/SAFETY_COOLDOWN = 100
playsound(loc, item_recycle_sound, 100, 0)
/obj/machinery/recycler/proc/recycle_item(obj/item/I)
- I.loc = loc
+ I.forceMove(loc)
+ GET_COMPONENT(materials, /datum/component/material_container)
var/material_amount = materials.get_item_material_amount(I)
if(!material_amount)
qdel(I)
diff --git a/code/game/machinery/robot_fabricator.dm b/code/game/machinery/robot_fabricator.dm
deleted file mode 100644
index 70a623982af..00000000000
--- a/code/game/machinery/robot_fabricator.dm
+++ /dev/null
@@ -1,146 +0,0 @@
-/obj/machinery/robotic_fabricator
- name = "Robotic Fabricator"
- icon = 'icons/obj/robotics.dmi'
- icon_state = "fab-idle"
- density = 1
- anchored = 1
- var/metal_amount = 0
- var/operating = 0
- var/obj/item/robot_parts/being_built = null
- use_power = 1
- idle_power_usage = 20
- active_power_usage = 5000
-
-/obj/machinery/robotic_fabricator/attackby(var/obj/item/O as obj, var/mob/user as mob, params)
- if(istype(O, /obj/item/stack/sheet/metal))
- if(src.metal_amount < 150000.0)
- var/count = 0
- src.overlays += "fab-load-metal"
- spawn(15)
- if(O)
- if(!O:amount)
- return
- while(metal_amount < 150000 && O:amount)
- src.metal_amount += O:materials[MAT_METAL] /*O:height * O:width * O:length * 100000.0*/
- O:amount--
- count++
-
- if(O:amount < 1)
- qdel(O)
-
- to_chat(user, "You insert [count] metal sheet\s into the fabricator.")
- src.overlays -= "fab-load-metal"
- updateDialog()
- else
- to_chat(user, "The robot part maker is full. Please remove metal from the robot part maker in order to insert more.")
-
-/obj/machinery/robotic_fabricator/power_change()
- if(powered())
- stat &= ~NOPOWER
- else
- stat |= NOPOWER
-
-/obj/machinery/robotic_fabricator/attack_hand(user as mob)
- var/dat
- if(..())
- return
-
- if(src.operating)
- dat = {"
-Building [src.being_built.name].
-Please wait until completion...
-
-"}
- else
- dat = {"
-Metal Amount: [min(150000, src.metal_amount)] cm3 (MAX: 150,000)
| - [left_part] - | -- [list_queue()] - | -