diff --git a/SQL/tgstation_schema.sql b/SQL/tgstation_schema.sql
index 9696082994db..9991a037ee06 100644
--- a/SQL/tgstation_schema.sql
+++ b/SQL/tgstation_schema.sql
@@ -802,6 +802,23 @@ CREATE TABLE `subsystem_metrics` (
PRIMARY KEY (`id`) USING BTREE
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+--
+-- Table structure for table `cassettes`
+--
+DROP TABLE IF EXISTS `cassettes`;
+CREATE TABLE `cassettes` (
+ `id` VARCHAR(255) NOT NULL PRIMARY KEY,
+ `name` VARCHAR(42) NOT NULL,
+ `desc` VARCHAR(144) NOT NULL,
+ `status` TINYINT UNSIGNED NOT NULL,
+ `author_name` VARCHAR(42) NOT NULL,
+ `author_ckey` VARCHAR(30) NOT NULL,
+ `front` TEXT NOT NULL DEFAULT '{}',
+ `back` TEXT NOT NULL DEFAULT '{}',
+ CONSTRAINT `front` CHECK (json_valid(`front`)),
+ CONSTRAINT `back` CHECK (json_valid(`back`))
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
+
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
diff --git a/_maps/map_files/BoxStation/BoxStation.dmm b/_maps/map_files/BoxStation/BoxStation.dmm
index caa3b936439a..616e6a194c19 100644
--- a/_maps/map_files/BoxStation/BoxStation.dmm
+++ b/_maps/map_files/BoxStation/BoxStation.dmm
@@ -1643,16 +1643,6 @@
/obj/effect/spawner/random/structure/crate,
/turf/open/floor/plating,
/area/station/maintenance/starboard/aft)
-"aAY" = (
-/obj/effect/turf_decal/box,
-/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
-/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
-/obj/structure/cable,
-/obj/machinery/holopad,
-/obj/machinery/duct,
-/mob/living/basic/bot/cleanbot/medbay,
-/turf/open/floor/iron/white/smooth_large,
-/area/station/medical/storage)
"aBj" = (
/obj/effect/turf_decal/stripes/line,
/obj/effect/turf_decal/stripes/line{
@@ -3633,6 +3623,16 @@
},
/turf/open/floor/iron/dark,
/area/station/hallway/primary/port)
+"bjF" = (
+/obj/effect/turf_decal/trimline/yellow/filled/warning,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
+/obj/structure/cable,
+/obj/machinery/duct,
+/obj/effect/mapping_helpers/mail_sorting/engineering/general,
+/obj/structure/disposalpipe/sorting/mail,
+/turf/open/floor/iron/dark/side,
+/area/station/engineering/break_room)
"bjY" = (
/obj/effect/turf_decal/stripes/corner{
dir = 1
@@ -18604,6 +18604,26 @@
/obj/effect/spawner/random/trash/garbage,
/turf/open/floor/plating,
/area/station/maintenance/department/cargo)
+"gbO" = (
+/obj/effect/turf_decal/tile/darkest_green/full,
+/obj/structure/table/reinforced/rglass,
+/obj/structure/window/reinforced/spawner/directional/east,
+/obj/structure/window/reinforced/spawner/directional/south,
+/obj/machinery/door/window/brigdoor/left/directional/west{
+ name = "Core Modules";
+ req_access = list("rd")
+ },
+/obj/effect/spawner/random/aimodule/neutral{
+ pixel_x = -3;
+ pixel_y = -3
+ },
+/obj/effect/spawner/random/aimodule/neutral{
+ pixel_y = 3;
+ pixel_x = 3
+ },
+/obj/effect/spawner/random/aimodule/harmless,
+/turf/open/floor/iron/dark/smooth_large,
+/area/station/ai_monitored/turret_protected/ai_upload)
"gcp" = (
/obj/effect/turf_decal/tile/yellow/anticorner/contrasted{
dir = 1
@@ -21387,53 +21407,6 @@
/obj/machinery/quantum_server,
/turf/open/floor/iron/dark,
/area/station/security/bitden)
-"gSC" = (
-/obj/structure/sign/painting/library_private{
- pixel_y = -32
- },
-/obj/structure/table/wood,
-/obj/item/device/cassette_tape/friday{
- pixel_y = 2;
- pixel_x = 9
- },
-/obj/item/device/walkman{
- pixel_y = 7;
- pixel_x = -8
- },
-/obj/item/device/walkman{
- pixel_y = 7;
- pixel_x = -8
- },
-/obj/item/device/walkman{
- pixel_y = 7;
- pixel_x = -8
- },
-/obj/item/device/walkman{
- pixel_y = 7;
- pixel_x = -8
- },
-/obj/item/device/walkman{
- pixel_y = 7;
- pixel_x = -8
- },
-/obj/item/device/walkman{
- pixel_y = 7;
- pixel_x = -8
- },
-/obj/item/device/walkman{
- pixel_y = 7;
- pixel_x = -8
- },
-/obj/item/device/walkman{
- pixel_y = 7;
- pixel_x = -8
- },
-/obj/item/device/walkman{
- pixel_y = 7;
- pixel_x = -8
- },
-/turf/open/floor/iron/vaporwave,
-/area/station/service/library/printer)
"gSE" = (
/turf/open/floor/carpet/purple,
/area/station/command/heads_quarters/rd)
@@ -45064,26 +45037,6 @@
/obj/machinery/airalarm/directional/east,
/turf/open/floor/iron/dark,
/area/station/command/bridge)
-"oFm" = (
-/obj/effect/turf_decal/tile/darkest_green/full,
-/obj/structure/table/reinforced/rglass,
-/obj/structure/window/reinforced/spawner/directional/east,
-/obj/structure/window/reinforced/spawner/directional/south,
-/obj/machinery/door/window/brigdoor/left/directional/west{
- name = "Core Modules";
- req_access = list("rd")
- },
-/obj/effect/spawner/random/aimodule/neutral{
- pixel_x = -3;
- pixel_y = -3
- },
-/obj/effect/spawner/random/aimodule/neutral{
- pixel_y = 3;
- pixel_x = 3
- },
-/obj/effect/spawner/random/aimodule/harmless,
-/turf/open/floor/iron/dark/smooth_large,
-/area/station/ai_monitored/turret_protected/ai_upload)
"oFy" = (
/obj/structure/disposalpipe/segment,
/obj/effect/turf_decal/trimline/red/filled/corner{
@@ -46333,6 +46286,16 @@
/obj/machinery/stasis,
/turf/open/floor/iron/white,
/area/station/medical/exam_room)
+"oZG" = (
+/obj/effect/turf_decal/box,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
+/obj/structure/cable,
+/obj/machinery/holopad,
+/obj/machinery/duct,
+/mob/living/basic/bot/cleanbot/medbay,
+/turf/open/floor/iron/white/smooth_large,
+/area/station/medical/storage)
"oZQ" = (
/obj/effect/turf_decal/trimline/neutral/corner{
dir = 4
@@ -55210,6 +55173,53 @@
dir = 4
},
/area/station/cargo/office)
+"rYS" = (
+/obj/structure/sign/painting/library_private{
+ pixel_y = -32
+ },
+/obj/structure/table/wood,
+/obj/item/cassette_tape/friday{
+ pixel_y = 2;
+ pixel_x = 9
+ },
+/obj/item/device/walkman{
+ pixel_y = 7;
+ pixel_x = -8
+ },
+/obj/item/device/walkman{
+ pixel_y = 7;
+ pixel_x = -8
+ },
+/obj/item/device/walkman{
+ pixel_y = 7;
+ pixel_x = -8
+ },
+/obj/item/device/walkman{
+ pixel_y = 7;
+ pixel_x = -8
+ },
+/obj/item/device/walkman{
+ pixel_y = 7;
+ pixel_x = -8
+ },
+/obj/item/device/walkman{
+ pixel_y = 7;
+ pixel_x = -8
+ },
+/obj/item/device/walkman{
+ pixel_y = 7;
+ pixel_x = -8
+ },
+/obj/item/device/walkman{
+ pixel_y = 7;
+ pixel_x = -8
+ },
+/obj/item/device/walkman{
+ pixel_y = 7;
+ pixel_x = -8
+ },
+/turf/open/floor/iron/vaporwave,
+/area/station/service/library/printer)
"rZq" = (
/turf/closed/wall/r_wall,
/area/station/ai_monitored/turret_protected/aisat/foyer)
@@ -59783,6 +59793,15 @@
/obj/machinery/atmospherics/components/unary/vent_pump/on/layer4,
/turf/open/floor/engine,
/area/station/ai_monitored/turret_protected/aisat/atmos)
+"tys" = (
+/obj/effect/turf_decal/trimline/yellow/filled/line{
+ dir = 5
+ },
+/obj/machinery/rnd/production/circuit_imprinter/department/engineering,
+/turf/open/floor/iron/dark/side{
+ dir = 5
+ },
+/area/station/engineering/break_room)
"tyt" = (
/obj/machinery/exodrone_launcher,
/obj/item/exodrone,
@@ -60851,16 +60870,6 @@
/obj/structure/cable,
/turf/open/floor/iron,
/area/station/engineering/break_room)
-"tQC" = (
-/obj/effect/turf_decal/trimline/yellow/filled/warning,
-/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
-/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
-/obj/structure/cable,
-/obj/machinery/duct,
-/obj/effect/mapping_helpers/mail_sorting/engineering/general,
-/obj/structure/disposalpipe/sorting/mail,
-/turf/open/floor/iron/dark/side,
-/area/station/engineering/break_room)
"tQD" = (
/obj/effect/decal/cleanable/dirt,
/obj/effect/spawner/random/trash/graffiti,
@@ -60901,22 +60910,6 @@
/obj/effect/spawner/random/trash/garbage,
/turf/open/floor/plating,
/area/station/maintenance/department/cargo)
-"tRj" = (
-/obj/effect/turf_decal/tile/dark_red/full,
-/obj/structure/table/reinforced/rglass,
-/obj/structure/window/reinforced/spawner/directional/east,
-/obj/structure/window/reinforced/spawner/directional/north,
-/obj/machinery/door/window/brigdoor/right/directional/west{
- name = "High-Risk Modules";
- req_access = list("rd")
- },
-/obj/item/ai_module/reset/purge{
- pixel_y = 4;
- pixel_x = -5
- },
-/obj/item/ai_module/reset,
-/turf/open/floor/iron/dark/smooth_large,
-/area/station/ai_monitored/turret_protected/ai_upload)
"tRo" = (
/obj/machinery/door/airlock/maintenance{
name = "Chapel Office Maintenance"
@@ -63585,6 +63578,22 @@
/obj/effect/mapping_helpers/requests_console/information,
/turf/open/floor/wood,
/area/station/command/meeting_room)
+"uLm" = (
+/obj/effect/turf_decal/tile/dark_red/full,
+/obj/structure/table/reinforced/rglass,
+/obj/structure/window/reinforced/spawner/directional/east,
+/obj/structure/window/reinforced/spawner/directional/north,
+/obj/machinery/door/window/brigdoor/right/directional/west{
+ name = "High-Risk Modules";
+ req_access = list("rd")
+ },
+/obj/item/ai_module/reset/purge{
+ pixel_y = 4;
+ pixel_x = -5
+ },
+/obj/item/ai_module/reset,
+/turf/open/floor/iron/dark/smooth_large,
+/area/station/ai_monitored/turret_protected/ai_upload)
"uLo" = (
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
@@ -74246,15 +74255,6 @@
dir = 4
},
/area/station/service/hydroponics)
-"yaQ" = (
-/obj/effect/turf_decal/trimline/yellow/filled/line{
- dir = 5
- },
-/obj/machinery/rnd/production/circuit_imprinter/department/engineering,
-/turf/open/floor/iron/dark/side{
- dir = 5
- },
-/area/station/engineering/break_room)
"yaX" = (
/obj/effect/turf_decal/siding/wood{
dir = 8
@@ -98110,11 +98110,11 @@ btp
sYe
ufV
nga
-tRj
+uLm
uDB
hfF
sTW
-oFm
+gbO
jFo
jFo
jFo
@@ -105628,7 +105628,7 @@ iUc
aFN
nBJ
nBp
-tQC
+bjF
bfT
tmQ
dPO
@@ -106650,7 +106650,7 @@ jnS
xtS
jnS
ufs
-yaQ
+tys
onk
oNF
tMn
@@ -112290,7 +112290,7 @@ hDo
sxS
uvI
skz
-aAY
+oZG
sna
pFu
ocd
@@ -123078,7 +123078,7 @@ qaG
gji
dOL
nVc
-gSC
+rYS
nBE
nep
mhc
diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm
index 1db384df2751..121d9d78560e 100644
--- a/_maps/map_files/MetaStation/MetaStation.dmm
+++ b/_maps/map_files/MetaStation/MetaStation.dmm
@@ -974,10 +974,6 @@
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
/turf/open/floor/iron/grimy,
/area/station/tcommsat/computer)
-"arW" = (
-/obj/machinery/light/floor/has_bulb,
-/turf/open/floor/wood,
-/area/station/commons/lounge)
"ase" = (
/obj/machinery/airalarm/directional/west,
/obj/effect/mapping_helpers/airalarm/tlv_no_checks,
@@ -2991,6 +2987,14 @@
/obj/structure/trash_pile,
/turf/open/floor/plating,
/area/station/maintenance/port/fore)
+"aYp" = (
+/obj/structure/cable,
+/obj/effect/turf_decal/siding/wood{
+ dir = 1
+ },
+/obj/machinery/light/floor/has_bulb,
+/turf/open/floor/wood/large,
+/area/station/commons/lounge)
"aYt" = (
/obj/machinery/door/firedoor,
/obj/machinery/door/poddoor/shutters/preopen{
@@ -3114,6 +3118,10 @@
},
/turf/open/floor/plating,
/area/station/maintenance/aft/lesser)
+"aZI" = (
+/obj/machinery/light/floor/has_bulb,
+/turf/open/floor/wood,
+/area/station/commons/lounge)
"aZL" = (
/obj/structure/cable,
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
@@ -8323,6 +8331,11 @@
},
/turf/open/floor/iron,
/area/station/science/lab)
+"dbg" = (
+/obj/machinery/duct,
+/obj/machinery/light/floor/has_bulb,
+/turf/open/floor/iron/white,
+/area/station/science/xenobiology)
"dbh" = (
/obj/structure/cable,
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
@@ -10063,13 +10076,6 @@
/obj/machinery/door/window/right/directional/west,
/turf/open/floor/wood,
/area/station/command/heads_quarters/captain/private)
-"dJj" = (
-/obj/effect/turf_decal/siding/wood{
- dir = 1
- },
-/obj/machinery/light/floor/has_bulb,
-/turf/open/floor/wood/large,
-/area/station/commons/lounge)
"dJK" = (
/turf/open/floor/iron/stairs/right{
dir = 1
@@ -12973,6 +12979,14 @@
"eIO" = (
/turf/closed/wall,
/area/station/maintenance/department/medical/central)
+"eJe" = (
+/obj/structure/cable,
+/obj/effect/turf_decal/siding/wood{
+ dir = 1
+ },
+/obj/machinery/light/floor/has_bulb,
+/turf/open/floor/wood,
+/area/station/commons/lounge)
"eJo" = (
/obj/structure/anvil,
/turf/open/floor/wood,
@@ -16321,14 +16335,6 @@
},
/turf/open/floor/iron/dark,
/area/station/ai_monitored/aisat/exterior)
-"fTy" = (
-/obj/structure/cable,
-/obj/effect/turf_decal/siding/wood{
- dir = 1
- },
-/obj/machinery/light/floor/has_bulb,
-/turf/open/floor/wood/large,
-/area/station/commons/lounge)
"fTE" = (
/obj/machinery/duct,
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
@@ -19705,6 +19711,11 @@
/obj/structure/cable,
/turf/open/floor/iron/dark/corner,
/area/station/engineering/atmos/storage/gas)
+"hcd" = (
+/obj/structure/window/spawner/directional/west,
+/obj/machinery/light/floor/has_bulb,
+/turf/open/floor/carpet,
+/area/station/service/theater)
"hcv" = (
/obj/effect/landmark/observer_start,
/obj/effect/turf_decal/plaque{
@@ -20189,6 +20200,11 @@
},
/turf/open/floor/plating,
/area/station/security/execution/transfer)
+"hjN" = (
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
+/obj/machinery/light/floor/has_bulb,
+/turf/open/floor/wood,
+/area/station/commons/lounge)
"hjS" = (
/obj/structure/cable,
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
@@ -20982,6 +20998,23 @@
/obj/effect/spawner/random/maintenance,
/turf/open/floor/plating,
/area/station/maintenance/port/fore)
+"hwx" = (
+/obj/machinery/flasher/directional/north{
+ id = "AI"
+ },
+/obj/structure/table/wood/fancy/blue,
+/obj/effect/spawner/random/aimodule/neutral,
+/obj/machinery/door/window{
+ base_state = "right";
+ dir = 4;
+ icon_state = "right";
+ name = "Core Modules";
+ req_access = list("rd")
+ },
+/obj/structure/window/reinforced/spawner/directional/south,
+/obj/item/ai_module/core/freeformcore,
+/turf/open/floor/circuit,
+/area/station/ai_monitored/turret_protected/ai_upload)
"hwz" = (
/obj/effect/turf_decal/stripes/line{
dir = 5
@@ -24625,15 +24658,6 @@
/obj/effect/spawner/random/trash/grille_or_waste,
/turf/open/floor/plating,
/area/station/maintenance/aft/lesser)
-"iHy" = (
-/obj/effect/turf_decal/bot,
-/obj/effect/turf_decal/tile/yellow{
- dir = 4
- },
-/obj/machinery/airalarm/directional/east,
-/obj/machinery/rnd/production/circuit_imprinter/department/engineering,
-/turf/open/floor/iron/dark/corner,
-/area/station/engineering/storage_shared)
"iHD" = (
/turf/closed/wall/r_wall,
/area/station/security/courtroom)
@@ -33418,6 +33442,34 @@
},
/turf/open/floor/iron/dark,
/area/station/ai_monitored/security/armory)
+"lDj" = (
+/obj/structure/table/wood,
+/obj/item/device/walkman{
+ pixel_y = 4;
+ pixel_x = -6
+ },
+/obj/item/device/walkman{
+ pixel_y = 4;
+ pixel_x = -6
+ },
+/obj/item/device/walkman{
+ pixel_y = 4;
+ pixel_x = -6
+ },
+/obj/item/cassette_tape/blank{
+ pixel_y = 3;
+ pixel_x = 5
+ },
+/obj/item/cassette_tape/blank{
+ pixel_y = 3;
+ pixel_x = 5
+ },
+/obj/item/cassette_tape/blank{
+ pixel_y = 3;
+ pixel_x = 5
+ },
+/turf/open/floor/wood,
+/area/station/service/library)
"lDo" = (
/obj/item/radio/off,
/obj/effect/turf_decal/stripes/line{
@@ -34045,22 +34097,6 @@
},
/turf/open/floor/iron/dark,
/area/station/medical/medbay/central)
-"lOY" = (
-/obj/structure/table/glass,
-/obj/machinery/reagentgrinder{
- pixel_x = -1;
- pixel_y = 8
- },
-/obj/machinery/button/door{
- id = "XenoPens";
- layer = 3.3;
- name = "Xenobiology Lockdown";
- pixel_y = 0;
- req_access = list("xenobiology");
- pixel_x = -24
- },
-/turf/open/floor/iron/white,
-/area/station/science/xenobiology)
"lOZ" = (
/obj/machinery/power/port_gen/pacman/pre_loaded,
/turf/open/floor/plating,
@@ -38396,11 +38432,6 @@
/obj/effect/turf_decal/tile/red/half/contrasted,
/turf/open/floor/iron,
/area/station/security/office)
-"nlu" = (
-/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
-/obj/machinery/light/floor/has_bulb,
-/turf/open/floor/wood,
-/area/station/commons/lounge)
"nlL" = (
/obj/effect/turf_decal/trimline/yellow/filled/line{
dir = 8
@@ -38924,14 +38955,6 @@
/obj/effect/mapping_helpers/airlock/access/all/science/general,
/turf/open/floor/iron/white,
/area/station/science/lab)
-"nti" = (
-/obj/structure/cable,
-/obj/effect/turf_decal/siding/wood{
- dir = 1
- },
-/obj/machinery/light/floor/has_bulb,
-/turf/open/floor/wood,
-/area/station/commons/lounge)
"ntk" = (
/obj/effect/turf_decal/trimline/blue/filled/line{
dir = 8
@@ -41733,6 +41756,13 @@
/obj/structure/cable,
/turf/open/floor/iron/dark,
/area/station/maintenance/aft/lesser)
+"orS" = (
+/obj/effect/turf_decal/siding/wood{
+ dir = 1
+ },
+/obj/machinery/light/floor/has_bulb,
+/turf/open/floor/wood/large,
+/area/station/commons/lounge)
"orU" = (
/obj/machinery/atmospherics/components/unary/vent_pump/on/layer4{
dir = 1
@@ -43658,11 +43688,6 @@
},
/turf/open/floor/iron/white,
/area/station/medical/chemistry)
-"pbB" = (
-/obj/machinery/duct,
-/obj/machinery/light/floor/has_bulb,
-/turf/open/floor/iron/white,
-/area/station/science/xenobiology)
"pbL" = (
/obj/machinery/door/firedoor,
/turf/open/floor/iron/white/side,
@@ -44668,6 +44693,22 @@
/obj/effect/mapping_helpers/broken_floor,
/turf/open/floor/plating,
/area/station/maintenance/fore)
+"puj" = (
+/obj/structure/table/glass,
+/obj/machinery/reagentgrinder{
+ pixel_x = -1;
+ pixel_y = 8
+ },
+/obj/machinery/button/door{
+ id = "XenoPens";
+ layer = 3.3;
+ name = "Xenobiology Lockdown";
+ pixel_y = 0;
+ req_access = list("xenobiology");
+ pixel_x = -24
+ },
+/turf/open/floor/iron/white,
+/area/station/science/xenobiology)
"pul" = (
/obj/machinery/atmospherics/pipe/smart/simple/purple/visible{
dir = 4
@@ -50483,6 +50524,23 @@
},
/turf/open/floor/iron,
/area/station/engineering/main)
+"ruy" = (
+/obj/structure/window/reinforced/spawner/directional/south,
+/obj/machinery/flasher/directional/north{
+ id = "AI"
+ },
+/obj/effect/spawner/random/aimodule/harmful,
+/obj/structure/table/wood/fancy/red,
+/obj/machinery/door/window/brigdoor/left/directional/south{
+ dir = 8;
+ name = "High-Risk Modules";
+ req_access = list("rd")
+ },
+/obj/item/ai_module/reset/purge{
+ pixel_y = 11
+ },
+/turf/open/floor/circuit/red,
+/area/station/ai_monitored/turret_protected/ai_upload)
"ruz" = (
/obj/structure/chair/stool/directional/north,
/turf/open/floor/wood,
@@ -51311,34 +51369,6 @@
},
/turf/open/floor/iron,
/area/station/engineering/atmos)
-"rIa" = (
-/obj/structure/table/wood,
-/obj/item/device/walkman{
- pixel_y = 4;
- pixel_x = -6
- },
-/obj/item/device/walkman{
- pixel_y = 4;
- pixel_x = -6
- },
-/obj/item/device/walkman{
- pixel_y = 4;
- pixel_x = -6
- },
-/obj/item/device/cassette_tape/blank{
- pixel_y = 3;
- pixel_x = 5
- },
-/obj/item/device/cassette_tape/blank{
- pixel_y = 3;
- pixel_x = 5
- },
-/obj/item/device/cassette_tape/blank{
- pixel_y = 3;
- pixel_x = 5
- },
-/turf/open/floor/wood,
-/area/station/service/library)
"rIh" = (
/obj/machinery/light/directional/east,
/obj/effect/turf_decal/tile/neutral{
@@ -52150,23 +52180,6 @@
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
/turf/open/floor/iron,
/area/station/construction/storage_wing)
-"rVO" = (
-/obj/structure/window/reinforced/spawner/directional/south,
-/obj/machinery/flasher/directional/north{
- id = "AI"
- },
-/obj/effect/spawner/random/aimodule/harmful,
-/obj/structure/table/wood/fancy/red,
-/obj/machinery/door/window/brigdoor/left/directional/south{
- dir = 8;
- name = "High-Risk Modules";
- req_access = list("rd")
- },
-/obj/item/ai_module/reset/purge{
- pixel_y = 11
- },
-/turf/open/floor/circuit/red,
-/area/station/ai_monitored/turret_protected/ai_upload)
"rVY" = (
/obj/effect/turf_decal/stripes/line,
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
@@ -58793,11 +58806,6 @@
/obj/machinery/light/directional/west,
/turf/open/floor/iron/white,
/area/station/security/prison/mess)
-"umP" = (
-/obj/structure/window/spawner/directional/west,
-/obj/machinery/light/floor/has_bulb,
-/turf/open/floor/carpet,
-/area/station/service/theater)
"umS" = (
/obj/item/radio/intercom/directional/west,
/obj/machinery/computer/records/security{
@@ -59524,6 +59532,15 @@
},
/turf/open/floor/iron/white,
/area/station/command/heads_quarters/cmo)
+"uyZ" = (
+/obj/effect/turf_decal/bot,
+/obj/effect/turf_decal/tile/yellow{
+ dir = 4
+ },
+/obj/machinery/airalarm/directional/east,
+/obj/machinery/rnd/production/circuit_imprinter/department/engineering,
+/turf/open/floor/iron/dark/corner,
+/area/station/engineering/storage_shared)
"uza" = (
/turf/closed/wall/r_wall,
/area/station/security/prison/visit)
@@ -66440,23 +66457,6 @@
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
/turf/open/floor/iron,
/area/station/commons/fitness/recreation)
-"wWd" = (
-/obj/machinery/flasher/directional/north{
- id = "AI"
- },
-/obj/structure/table/wood/fancy/blue,
-/obj/effect/spawner/random/aimodule/neutral,
-/obj/machinery/door/window{
- base_state = "right";
- dir = 4;
- icon_state = "right";
- name = "Core Modules";
- req_access = list("rd")
- },
-/obj/structure/window/reinforced/spawner/directional/south,
-/obj/item/ai_module/core/freeformcore,
-/turf/open/floor/circuit,
-/area/station/ai_monitored/turret_protected/ai_upload)
"wWk" = (
/obj/effect/turf_decal/tile/blue{
dir = 1
@@ -89368,7 +89368,7 @@ nxF
sVY
cyk
qzS
-rIa
+lDj
ahD
wki
jIY
@@ -94741,7 +94741,7 @@ qWF
aaa
aJS
aJS
-wWd
+hwx
pQG
kUq
dvk
@@ -96283,7 +96283,7 @@ qWF
aaa
aJS
aJS
-rVO
+ruy
vrJ
iXa
uGb
@@ -104269,11 +104269,11 @@ fRS
tLx
bvJ
dYK
-arW
+aZI
gae
uFf
qVt
-dJj
+orS
qPd
oqq
mva
@@ -105297,11 +105297,11 @@ fRS
cAf
bvJ
eHL
-nlu
+hjN
fSd
fSd
gqp
-dJj
+orS
qiU
pPb
mTm
@@ -106325,11 +106325,11 @@ pKi
axU
rac
hiG
-nti
+eJe
wKu
wKu
iLL
-fTy
+aYp
lOB
qnf
mTm
@@ -107098,7 +107098,7 @@ obG
buf
mzn
foU
-umP
+hcd
gMz
otc
etz
@@ -112739,7 +112739,7 @@ aaa
aaa
aaa
uJz
-iHy
+uyZ
xgR
nmb
rtP
@@ -118197,7 +118197,7 @@ eJI
ycv
kCw
ycv
-lOY
+puj
shY
gjv
aft
@@ -118452,7 +118452,7 @@ mtu
vHm
fHs
ycv
-pbB
+dbg
kCw
gRY
xCA
diff --git a/_maps/map_files/Voidraptor/VoidRaptor.dmm b/_maps/map_files/Voidraptor/VoidRaptor.dmm
index 50a131a3a333..df0e56f44dd2 100644
--- a/_maps/map_files/Voidraptor/VoidRaptor.dmm
+++ b/_maps/map_files/Voidraptor/VoidRaptor.dmm
@@ -953,53 +953,6 @@
},
/turf/open/floor/iron/large,
/area/station/hallway/primary/fore)
-"amZ" = (
-/obj/machinery/firealarm/directional/west,
-/obj/effect/turf_decal/trimline/purple/filled/line{
- dir = 8
- },
-/obj/effect/turf_decal/stripes/line{
- dir = 8
- },
-/obj/effect/turf_decal/bot,
-/obj/machinery/light/cold/directional/west,
-/obj/structure/table,
-/obj/item/storage/medkit{
- pixel_x = -3;
- pixel_y = 8
- },
-/obj/item/healthanalyzer{
- pixel_x = -3;
- pixel_y = 7
- },
-/obj/item/storage/medkit{
- pixel_x = -3;
- pixel_y = 0
- },
-/obj/item/healthanalyzer{
- pixel_x = -3;
- pixel_y = -2
- },
-/obj/item/assembly/prox_sensor{
- pixel_x = 10;
- pixel_y = 14
- },
-/obj/item/assembly/prox_sensor{
- pixel_x = 11;
- pixel_y = 8
- },
-/obj/item/assembly/prox_sensor{
- pixel_x = 10;
- pixel_y = 2
- },
-/obj/item/assembly/prox_sensor{
- pixel_x = 11;
- pixel_y = -4
- },
-/turf/open/floor/iron/dark/textured_edge{
- dir = 4
- },
-/area/station/science/robotics/lab)
"anq" = (
/obj/machinery/atmospherics/components/unary/vent_pump/on/layer4{
dir = 8
@@ -1126,6 +1079,14 @@
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
/turf/open/floor/iron/textured,
/area/station/science/ordnance/storage)
+"apj" = (
+/obj/effect/turf_decal/trimline/yellow/filled/line{
+ dir = 1
+ },
+/obj/structure/chair,
+/obj/effect/landmark/start/depsec/engineering,
+/turf/open/floor/iron/textured_edge,
+/area/station/security/checkpoint/engineering)
"apl" = (
/obj/structure/spider/stickyweb,
/obj/machinery/power/port_gen/pacman,
@@ -2617,17 +2578,6 @@
/obj/machinery/status_display/evac,
/turf/closed/wall/r_wall,
/area/station/ai_monitored/turret_protected/aisat_interior)
-"aMf" = (
-/obj/structure/extinguisher_cabinet/directional/east,
-/obj/effect/turf_decal/trimline/purple/filled/line{
- dir = 5
- },
-/obj/effect/turf_decal/stripes/line{
- dir = 5
- },
-/obj/structure/reagent_dispensers/fueltank/large,
-/turf/open/floor/iron/dark/textured,
-/area/station/science/robotics/lab)
"aMk" = (
/obj/machinery/growing/tray,
/turf/open/floor/iron/dark/textured_large,
@@ -3837,26 +3787,6 @@
},
/turf/open/floor/iron/textured,
/area/station/engineering/lobby)
-"bbW" = (
-/obj/machinery/door/firedoor,
-/obj/structure/cable,
-/obj/effect/turf_decal/stripes/line{
- dir = 4
- },
-/obj/effect/turf_decal/stripes/line{
- dir = 8
- },
-/obj/structure/disposalpipe/segment{
- dir = 4
- },
-/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
-/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
-/obj/machinery/door/airlock/engineering/glass{
- name = "Shared Engineering Storage"
- },
-/obj/effect/mapping_helpers/airlock/access/any/engineering/construction,
-/turf/open/floor/iron/textured_large,
-/area/station/engineering/storage_shared)
"bcf" = (
/obj/structure/table_frame,
/turf/open/floor/iron/textured,
@@ -4719,6 +4649,24 @@
},
/turf/open/floor/iron/freezer,
/area/station/medical/chemistry)
+"bqS" = (
+/obj/structure/chair/sofa/corp/right{
+ color = "#DE3A3A"
+ },
+/obj/machinery/airalarm/directional/north,
+/obj/effect/turf_decal/siding/wood{
+ dir = 1
+ },
+/obj/machinery/camera/directional/north{
+ c_tag = "Research Division - Port";
+ dir = 9;
+ name = "science camera";
+ network = list("ss13","rd")
+ },
+/obj/item/pai_card,
+/obj/effect/landmark/start/depsec/science,
+/turf/open/floor/wood/large,
+/area/station/science/research)
"brb" = (
/obj/effect/landmark/start/hangover,
/obj/effect/landmark/event_spawn,
@@ -6262,6 +6210,22 @@
/obj/effect/turf_decal/trimline/purple/filled/corner,
/turf/open/floor/iron/dark/textured,
/area/station/security/corrections_officer)
+"bPX" = (
+/obj/structure/disposalpipe/segment{
+ dir = 4
+ },
+/obj/effect/turf_decal/trimline/purple/filled/corner{
+ dir = 8
+ },
+/obj/effect/turf_decal/stripes/corner{
+ dir = 8
+ },
+/obj/effect/turf_decal/bot,
+/obj/machinery/autolathe,
+/turf/open/floor/iron/dark/textured_corner{
+ dir = 8
+ },
+/area/station/science/robotics/lab)
"bPZ" = (
/turf/closed/wall,
/area/station/service/cafeteria)
@@ -6396,6 +6360,15 @@
/obj/effect/turf_decal/bot,
/turf/open/floor/iron/dark/textured_edge,
/area/station/security/lockers)
+"bSm" = (
+/obj/effect/turf_decal/trimline/purple/filled/line{
+ dir = 1
+ },
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
+/obj/structure/cable,
+/obj/structure/sink/directional/south,
+/turf/open/floor/iron/white/textured_edge,
+/area/station/science/genetics)
"bSp" = (
/obj/structure/closet/emcloset/wall{
pixel_x = -32
@@ -6615,6 +6588,13 @@
/obj/effect/turf_decal/siding/wood,
/turf/open/floor/wood,
/area/station/service/library/abandoned)
+"bWv" = (
+/obj/structure/table,
+/obj/effect/turf_decal/tile/neutral,
+/obj/effect/turf_decal/tile/dark_red/anticorner/contrasted,
+/obj/item/surgery_tray,
+/turf/open/floor/iron/dark/textured,
+/area/station/science/robotics/lab)
"bWK" = (
/obj/machinery/ai_slipper{
uses = 10
@@ -8003,17 +7983,6 @@
},
/turf/open/floor/iron/dark/textured_large,
/area/station/hallway/secondary/exit/departure_lounge)
-"cuq" = (
-/obj/item/radio/intercom/directional/south,
-/obj/effect/turf_decal/trimline/purple/filled/line,
-/obj/effect/turf_decal/stripes/line,
-/obj/machinery/modular_computer/preset/civilian{
- dir = 4
- },
-/turf/open/floor/iron/dark/textured_edge{
- dir = 1
- },
-/area/station/science/robotics/lab)
"cuA" = (
/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2{
dir = 4
@@ -10022,22 +9991,6 @@
/obj/structure/sign/warning/secure_area,
/turf/closed/wall/r_wall,
/area/station/maintenance/aft/greater)
-"cYI" = (
-/obj/structure/table/wood,
-/obj/item/radio/intercom/directional/east,
-/obj/machinery/light/warm/directional/east,
-/obj/effect/turf_decal/siding/thinplating/dark{
- dir = 4
- },
-/obj/item/radio/radio_mic{
- pixel_y = 7
- },
-/obj/item/device/cassette_tape/friday{
- pixel_y = -6;
- pixel_x = -8
- },
-/turf/open/floor/cult,
-/area/station/service/library)
"cYN" = (
/obj/structure/chair/sofa/corp/right{
dir = 8;
@@ -10331,15 +10284,6 @@
dir = 4
},
/area/station/cargo/storage)
-"dcF" = (
-/obj/effect/turf_decal/trimline/purple/filled/line{
- dir = 1
- },
-/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
-/obj/structure/cable,
-/obj/structure/sink/directional/south,
-/turf/open/floor/iron/white/textured_edge,
-/area/station/science/genetics)
"dcJ" = (
/obj/effect/turf_decal/trimline/green/filled/warning{
dir = 4
@@ -10803,15 +10747,6 @@
/obj/structure/disposalpipe/segment,
/turf/open/floor/iron/freezer,
/area/station/medical/chemistry)
-"diH" = (
-/obj/structure/table/wood/fancy/blue,
-/obj/effect/spawner/random/aimodule/neutral,
-/obj/machinery/door/window/left/directional/south{
- name = "Core Modules";
- req_access = list("rd")
- },
-/turf/open/floor/circuit,
-/area/station/ai_monitored/turret_protected/ai_upload)
"diL" = (
/obj/effect/turf_decal/nova_decals/enclave/top/middle{
color = "#52B4E9"
@@ -11735,6 +11670,21 @@
/obj/effect/turf_decal/trimline/neutral/mid_joiner,
/turf/open/floor/iron/textured_large,
/area/station/cargo/sorting)
+"dwh" = (
+/obj/effect/turf_decal/trimline/purple/filled/line{
+ dir = 4
+ },
+/obj/effect/turf_decal/stripes/line{
+ dir = 4
+ },
+/obj/effect/turf_decal/bot,
+/obj/machinery/light/cold/directional/east,
+/obj/effect/landmark/start/cyborg,
+/obj/machinery/recharge_station,
+/turf/open/floor/iron/dark/textured_edge{
+ dir = 8
+ },
+/area/station/science/robotics/lab)
"dwi" = (
/obj/effect/turf_decal/siding/wood{
dir = 1
@@ -11742,19 +11692,6 @@
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/wood,
/area/station/maintenance/port/aft)
-"dwE" = (
-/obj/effect/turf_decal/trimline/purple/filled/line{
- dir = 9
- },
-/obj/machinery/airalarm/directional/west,
-/obj/effect/turf_decal/delivery,
-/obj/machinery/requests_console/directional/north{
- department = "Genetics";
- name = "Genetics Requests console"
- },
-/obj/machinery/clonepod,
-/turf/open/floor/iron/white,
-/area/station/science/genetics)
"dwH" = (
/obj/effect/decal/cleanable/blood/old,
/obj/effect/decal/remains/human,
@@ -11934,6 +11871,13 @@
/obj/effect/turf_decal/bot,
/turf/open/floor/iron/textured,
/area/station/hallway/primary/aft)
+"dzC" = (
+/obj/effect/turf_decal/tile/blue/fourcorners,
+/obj/item/radio/intercom/directional/west,
+/obj/structure/sink/directional/east,
+/obj/effect/landmark/start/depsec/medical,
+/turf/open/floor/iron/freezer,
+/area/station/security/checkpoint/medical)
"dzE" = (
/obj/structure/chair/comfy/beige{
dir = 1
@@ -14749,6 +14693,22 @@
/obj/structure/flora/bush/flowers_pp/style_random,
/turf/open/floor/grass,
/area/station/service/chapel)
+"ekr" = (
+/obj/effect/turf_decal/trimline/purple/filled/line{
+ dir = 4
+ },
+/obj/effect/turf_decal/trimline/purple/filled/line{
+ dir = 4
+ },
+/obj/structure/disposalpipe/segment,
+/obj/machinery/vending/wardrobe/gene_wardrobe,
+/obj/item/toy/figure/geneticist{
+ pixel_y = 18
+ },
+/turf/open/floor/iron/white/textured_edge{
+ dir = 8
+ },
+/area/station/science/genetics)
"eks" = (
/obj/machinery/door/firedoor,
/obj/structure/table/reinforced,
@@ -15592,22 +15552,6 @@
},
/turf/open/floor/iron/dark/textured,
/area/station/security/prison/mess)
-"exy" = (
-/obj/effect/turf_decal/trimline/purple/filled/line{
- dir = 4
- },
-/obj/effect/turf_decal/trimline/purple/filled/line{
- dir = 4
- },
-/obj/structure/disposalpipe/segment,
-/obj/machinery/vending/wardrobe/gene_wardrobe,
-/obj/item/toy/figure/geneticist{
- pixel_y = 18
- },
-/turf/open/floor/iron/white/textured_edge{
- dir = 8
- },
-/area/station/science/genetics)
"exF" = (
/obj/structure/disposalpipe/segment{
dir = 4
@@ -17673,14 +17617,6 @@
/obj/effect/turf_decal/tile/purple/diagonal_centre,
/turf/open/floor/iron/white/diagonal,
/area/station/science/research)
-"fch" = (
-/obj/effect/turf_decal/tile/blue/fourcorners,
-/obj/structure/chair/office/light{
- dir = 4
- },
-/obj/effect/landmark/start/depsec/medical,
-/turf/open/floor/iron/freezer,
-/area/station/security/checkpoint/medical)
"fci" = (
/obj/effect/turf_decal/siding/wood{
dir = 6
@@ -20540,14 +20476,6 @@
dir = 6
},
/area/station/service/chapel)
-"fWZ" = (
-/obj/effect/turf_decal/trimline/yellow/filled/line{
- dir = 1
- },
-/obj/structure/chair,
-/obj/effect/landmark/start/depsec/engineering,
-/turf/open/floor/iron/textured_edge,
-/area/station/security/checkpoint/engineering)
"fXi" = (
/obj/structure/railing/wood{
dir = 8
@@ -21551,6 +21479,18 @@
},
/turf/open/floor/catwalk_floor/iron_smooth,
/area/station/maintenance/department/science/xenobiology)
+"gkd" = (
+/obj/effect/turf_decal/trimline/brown/filled/line{
+ dir = 8
+ },
+/obj/structure/chair/office{
+ dir = 1
+ },
+/obj/effect/landmark/start/depsec/supply,
+/turf/open/floor/iron/textured_edge{
+ dir = 4
+ },
+/area/station/security/checkpoint/supply)
"gkk" = (
/obj/machinery/atmospherics/pipe/heat_exchanging/simple{
dir = 8
@@ -22335,13 +22275,6 @@
"gvG" = (
/turf/open/floor/engine,
/area/station/science/auxlab/firing_range)
-"gvI" = (
-/obj/structure/cable,
-/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
-/obj/machinery/duct,
-/mob/living/basic/bot/cleanbot/medbay,
-/turf/open/floor/iron/white/textured_large,
-/area/station/medical/storage)
"gvO" = (
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
/obj/effect/turf_decal/siding/dark,
@@ -22917,15 +22850,6 @@
/obj/machinery/door/airlock/maintenance_hatch,
/turf/open/floor/catwalk_floor/iron_smooth,
/area/station/maintenance/port/aft)
-"gEJ" = (
-/obj/structure/table/wood/fancy/red,
-/obj/effect/spawner/random/aimodule/harmful,
-/obj/machinery/door/window/brigdoor/left/directional/north{
- name = "High-Risk Modules";
- req_access = list("rd")
- },
-/turf/open/floor/circuit/red,
-/area/station/ai_monitored/turret_protected/ai_upload)
"gEO" = (
/obj/structure/rack/gunrack,
/obj/effect/turf_decal/tile/dark_red/anticorner{
@@ -23169,19 +23093,6 @@
},
/turf/open/floor/iron/dark/textured_large,
/area/station/command/bridge)
-"gIr" = (
-/obj/effect/turf_decal/trimline/purple/filled/line{
- dir = 8
- },
-/obj/effect/turf_decal/bot,
-/obj/machinery/light/cold/directional/west,
-/obj/machinery/computer/cloning{
- dir = 4
- },
-/turf/open/floor/iron/white/textured_edge{
- dir = 4
- },
-/area/station/science/genetics)
"gIu" = (
/obj/effect/turf_decal/box,
/obj/machinery/portable_atmospherics/canister,
@@ -23361,13 +23272,6 @@
"gKO" = (
/turf/open/floor/catwalk_floor/iron_smooth,
/area/station/hallway/primary/aft)
-"gKY" = (
-/obj/structure/table,
-/obj/effect/turf_decal/tile/neutral,
-/obj/effect/turf_decal/tile/dark_red/anticorner/contrasted,
-/obj/item/surgery_tray,
-/turf/open/floor/iron/dark/textured,
-/area/station/science/robotics/lab)
"gKZ" = (
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
@@ -25056,6 +24960,21 @@
/obj/effect/turf_decal/caution/stand_clear,
/turf/open/floor/iron/textured_large,
/area/station/cargo/storage)
+"hii" = (
+/obj/effect/turf_decal/trimline/purple/filled/line{
+ dir = 8
+ },
+/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2{
+ dir = 4
+ },
+/obj/structure/sign/nanotrasen{
+ pixel_x = -32
+ },
+/obj/effect/landmark/start/depsec/science,
+/turf/open/floor/iron/white/textured_edge{
+ dir = 4
+ },
+/area/station/security/checkpoint/science/research)
"hio" = (
/obj/effect/turf_decal/trimline/green/corner{
dir = 8
@@ -27812,12 +27731,6 @@
/obj/machinery/digital_clock,
/turf/closed/wall/r_wall,
/area/station/cargo/quartermaster)
-"hXj" = (
-/obj/item/radio/intercom/directional/west,
-/obj/effect/turf_decal/bot,
-/obj/machinery/rnd/production/circuit_imprinter/department/engineering,
-/turf/open/floor/iron/dark/textured_large,
-/area/station/engineering/storage_shared)
"hXs" = (
/obj/structure/table/wood/fancy,
/obj/item/folder{
@@ -28547,6 +28460,15 @@
/obj/item/storage/box/lights/mixed,
/turf/open/floor/iron/textured,
/area/station/maintenance/department/bridge)
+"ijh" = (
+/obj/item/radio/intercom/directional/south,
+/obj/effect/turf_decal/trimline/purple/filled/line{
+ dir = 6
+ },
+/obj/effect/turf_decal/box,
+/obj/machinery/mechpad,
+/turf/open/floor/iron/dark/textured,
+/area/station/science/robotics/mechbay)
"ijj" = (
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
/obj/structure/cable,
@@ -29960,21 +29882,6 @@
},
/turf/open/floor/iron/checker,
/area/station/service/theater)
-"izJ" = (
-/obj/effect/turf_decal/trimline/purple/filled/line{
- dir = 8
- },
-/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2{
- dir = 4
- },
-/obj/structure/sign/nanotrasen{
- pixel_x = -32
- },
-/obj/effect/landmark/start/depsec/science,
-/turf/open/floor/iron/white/textured_edge{
- dir = 4
- },
-/area/station/security/checkpoint/science/research)
"izY" = (
/obj/machinery/door/airlock/grunge{
name = "Chapel"
@@ -30329,15 +30236,6 @@
/obj/effect/spawner/random/engineering/vending_restock,
/turf/open/floor/iron/textured,
/area/station/maintenance/starboard/lesser)
-"iGc" = (
-/obj/effect/landmark/start/roboticist,
-/obj/effect/turf_decal/tile/dark_red{
- dir = 4
- },
-/turf/open/floor/iron/dark/textured_corner{
- dir = 4
- },
-/area/station/science/robotics/lab)
"iGg" = (
/obj/machinery/atmospherics/pipe/smart/manifold/purple/visible{
dir = 8
@@ -31251,13 +31149,6 @@
"iTA" = (
/turf/closed/wall,
/area/station/hallway/secondary/entry)
-"iTJ" = (
-/obj/effect/turf_decal/trimline/purple/filled/line{
- dir = 1
- },
-/obj/effect/landmark/start/depsec/science,
-/turf/open/floor/iron/white/textured_edge,
-/area/station/security/checkpoint/science/research)
"iTS" = (
/obj/machinery/atmospherics/components/unary/vent_pump/on/layer4,
/obj/effect/turf_decal/stripes/line{
@@ -31696,13 +31587,6 @@
},
/turf/open/floor/carpet/blue,
/area/station/command/bridge)
-"iYZ" = (
-/obj/structure/chair{
- dir = 8
- },
-/obj/effect/landmark/start/depsec/engineering,
-/turf/open/floor/iron/textured_large,
-/area/station/security/checkpoint/engineering)
"iZa" = (
/obj/effect/landmark/event_spawn,
/obj/machinery/duct,
@@ -31878,22 +31762,6 @@
},
/turf/open/floor/pod/dark,
/area/station/service/chapel/funeral)
-"jbi" = (
-/obj/structure/disposalpipe/segment{
- dir = 4
- },
-/obj/effect/turf_decal/trimline/purple/filled/corner{
- dir = 8
- },
-/obj/effect/turf_decal/stripes/corner{
- dir = 8
- },
-/obj/effect/turf_decal/bot,
-/obj/machinery/autolathe,
-/turf/open/floor/iron/dark/textured_corner{
- dir = 8
- },
-/area/station/science/robotics/lab)
"jbo" = (
/obj/effect/turf_decal/siding/wood{
dir = 8
@@ -31910,6 +31778,22 @@
/obj/effect/turf_decal/tile/bar/opposingcorners,
/turf/open/floor/iron,
/area/station/service/bar)
+"jbG" = (
+/obj/structure/table/wood,
+/obj/item/radio/intercom/directional/east,
+/obj/machinery/light/warm/directional/east,
+/obj/effect/turf_decal/siding/thinplating/dark{
+ dir = 4
+ },
+/obj/item/radio/radio_mic{
+ pixel_y = 7
+ },
+/obj/item/cassette_tape/friday{
+ pixel_y = -6;
+ pixel_x = -8
+ },
+/turf/open/floor/cult,
+/area/station/service/library)
"jbI" = (
/obj/effect/turf_decal/trimline/yellow/filled/line{
dir = 1
@@ -35101,6 +34985,19 @@
/obj/structure/chair/stool/directional/west,
/turf/open/floor/wood/large,
/area/station/commons/fitness/recreation)
+"jTK" = (
+/obj/effect/turf_decal/trimline/purple/filled/line{
+ dir = 8
+ },
+/obj/structure/disposalpipe/segment{
+ dir = 4
+ },
+/obj/effect/turf_decal/bot,
+/obj/machinery/dna_scannernew,
+/turf/open/floor/iron/white/textured_edge{
+ dir = 4
+ },
+/area/station/science/genetics)
"jTQ" = (
/obj/effect/turf_decal/trimline/brown/filled/warning{
dir = 4
@@ -37028,6 +36925,15 @@
/obj/effect/spawner/random/entertainment/money,
/turf/open/floor/iron/textured_large,
/area/station/maintenance/disposal)
+"kxM" = (
+/obj/structure/table/wood/fancy/red,
+/obj/effect/spawner/random/aimodule/harmful,
+/obj/machinery/door/window/brigdoor/left/directional/north{
+ name = "High-Risk Modules";
+ req_access = list("rd")
+ },
+/turf/open/floor/circuit/red,
+/area/station/ai_monitored/turret_protected/ai_upload)
"kxN" = (
/obj/structure/disposalpipe/segment{
dir = 4
@@ -37374,18 +37280,6 @@
/obj/effect/spawner/random/trash/garbage,
/turf/open/floor/catwalk_floor/iron_smooth,
/area/station/maintenance/port/upper)
-"kBs" = (
-/obj/effect/turf_decal/trimline/brown/filled/line{
- dir = 8
- },
-/obj/structure/chair/office{
- dir = 1
- },
-/obj/effect/landmark/start/depsec/supply,
-/turf/open/floor/iron/textured_edge{
- dir = 4
- },
-/area/station/security/checkpoint/supply)
"kBS" = (
/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2{
dir = 4
@@ -38056,11 +37950,6 @@
},
/turf/open/floor/iron/textured,
/area/station/maintenance/aft/upper)
-"kJi" = (
-/obj/machinery/holopad,
-/obj/effect/turf_decal/bot,
-/turf/open/floor/iron/dark/textured_large,
-/area/station/science/robotics/lab)
"kJk" = (
/obj/machinery/atmospherics/components/unary/vent_pump/siphon/monitored/oxygen_output{
dir = 4
@@ -38702,6 +38591,11 @@
/obj/effect/mapping_helpers/airlock/access/all/engineering/general,
/turf/open/floor/engine,
/area/station/engineering/supermatter)
+"kTa" = (
+/obj/effect/turf_decal/tile/blue/fourcorners,
+/obj/effect/landmark/start/depsec/medical,
+/turf/open/floor/iron/freezer,
+/area/station/security/checkpoint/medical)
"kTc" = (
/obj/effect/spawner/structure/window/reinforced,
/turf/open/floor/plating,
@@ -40312,6 +40206,12 @@
/obj/effect/landmark/start/chief_medical_officer,
/turf/open/floor/carpet/blue,
/area/station/command/heads_quarters/cmo)
+"lqg" = (
+/obj/item/radio/intercom/directional/west,
+/obj/effect/turf_decal/bot,
+/obj/machinery/rnd/production/circuit_imprinter/department/engineering,
+/turf/open/floor/iron/dark/textured_large,
+/area/station/engineering/storage_shared)
"lqw" = (
/obj/machinery/status_display/evac/directional/east,
/obj/structure/closet/secure_closet/detective,
@@ -41140,18 +41040,6 @@
/obj/item/kirbyplants/random,
/turf/open/floor/vault,
/area/station/ai_monitored/turret_protected/aisat/foyer)
-"lCb" = (
-/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2{
- dir = 8
- },
-/obj/effect/turf_decal/trimline/brown/filled/warning{
- dir = 4
- },
-/obj/effect/landmark/start/depsec/supply,
-/turf/open/floor/iron/textured_edge{
- dir = 8
- },
-/area/station/security/checkpoint/supply)
"lCg" = (
/obj/effect/decal/cleanable/dirt,
/obj/effect/turf_decal/siding/green,
@@ -41284,15 +41172,6 @@
/obj/structure/window/fulltile,
/turf/open/floor/grass,
/area/station/common/cryopods)
-"lDK" = (
-/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2{
- dir = 1
- },
-/mob/living/basic/pet/dog/corgi{
- name = "Dirk"
- },
-/turf/open/floor/iron/dark/textured_large,
-/area/station/science/robotics/lab)
"lDP" = (
/obj/structure/frame/machine,
/obj/effect/decal/cleanable/dirt,
@@ -42120,6 +41999,13 @@
/obj/effect/turf_decal/bot,
/turf/open/floor/iron/dark/textured,
/area/station/ai_monitored/command/storage/eva)
+"lPj" = (
+/obj/effect/turf_decal/trimline/purple/filled/line{
+ dir = 1
+ },
+/obj/effect/landmark/start/depsec/science,
+/turf/open/floor/iron/white/textured_edge,
+/area/station/security/checkpoint/science/research)
"lPo" = (
/obj/effect/spawner/structure/window,
/obj/structure/curtain/cloth/fancy/mechanical{
@@ -42694,17 +42580,6 @@
/obj/effect/turf_decal/tile/blue/diagonal_centre,
/turf/open/floor/iron/diagonal,
/area/station/hallway/secondary/exit/departure_lounge)
-"lWk" = (
-/obj/structure/chair/sofa/corp{
- color = "#DE3A3A"
- },
-/obj/effect/turf_decal/siding/wood{
- dir = 1
- },
-/obj/item/radio/intercom/directional/north,
-/obj/effect/landmark/start/research_director,
-/turf/open/floor/wood/large,
-/area/station/science/research)
"lWr" = (
/obj/machinery/door/airlock/external/ruin,
/obj/effect/mapping_helpers/airlock/cyclelink_helper{
@@ -43377,18 +43252,6 @@
"mhj" = (
/turf/open/floor/iron/white/small,
/area/station/common/pool)
-"mhn" = (
-/obj/structure/cable,
-/obj/machinery/power/apc/auto_name/directional/west,
-/obj/effect/turf_decal/trimline/purple/filled/line{
- dir = 10
- },
-/obj/effect/turf_decal/box,
-/obj/machinery/computer/mechpad{
- dir = 1
- },
-/turf/open/floor/iron/dark/textured,
-/area/station/science/robotics/mechbay)
"mhx" = (
/turf/open/floor/engine,
/area/station/engineering/supermatter)
@@ -43477,15 +43340,6 @@
/obj/structure/table/wood/fancy,
/turf/open/floor/pod/dark,
/area/station/service/chapel/office)
-"miV" = (
-/obj/effect/turf_decal/trimline/purple/filled/warning{
- dir = 5
- },
-/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
-/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
-/obj/structure/disposalpipe/segment,
-/turf/open/floor/iron/white,
-/area/station/science/research)
"miZ" = (
/obj/effect/turf_decal/siding/wood{
dir = 4
@@ -44033,6 +43887,18 @@
/obj/effect/turf_decal/siding/wood,
/turf/open/floor/wood/large,
/area/station/commons/fitness/recreation/entertainment)
+"mqD" = (
+/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2{
+ dir = 8
+ },
+/obj/effect/turf_decal/trimline/brown/filled/warning{
+ dir = 4
+ },
+/obj/effect/landmark/start/depsec/supply,
+/turf/open/floor/iron/textured_edge{
+ dir = 8
+ },
+/area/station/security/checkpoint/supply)
"mqJ" = (
/obj/effect/turf_decal/trimline/brown/filled/line{
dir = 9
@@ -45311,6 +45177,15 @@
dir = 8
},
/area/station/hallway/primary/fore)
+"mLe" = (
+/obj/structure/table/wood/fancy/blue,
+/obj/effect/spawner/random/aimodule/neutral,
+/obj/machinery/door/window/left/directional/south{
+ name = "Core Modules";
+ req_access = list("rd")
+ },
+/turf/open/floor/circuit,
+/area/station/ai_monitored/turret_protected/ai_upload)
"mLj" = (
/obj/structure/plasticflaps/opaque{
name = "Security Deliveries"
@@ -45887,15 +45762,6 @@
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
/turf/open/floor/wood,
/area/station/hallway/secondary/entry)
-"mSx" = (
-/obj/item/radio/intercom/directional/south,
-/obj/effect/turf_decal/trimline/purple/filled/line{
- dir = 6
- },
-/obj/effect/turf_decal/box,
-/obj/machinery/mechpad,
-/turf/open/floor/iron/dark/textured,
-/area/station/science/robotics/mechbay)
"mSI" = (
/obj/machinery/portable_atmospherics/canister/carbon_dioxide,
/obj/effect/turf_decal/box,
@@ -46010,6 +45876,26 @@
/obj/structure/chair/stool/directional/west,
/turf/open/floor/iron/dark/textured_large,
/area/station/service/theater)
+"mUG" = (
+/obj/machinery/door/firedoor,
+/obj/structure/cable,
+/obj/effect/turf_decal/stripes/line{
+ dir = 4
+ },
+/obj/effect/turf_decal/stripes/line{
+ dir = 8
+ },
+/obj/structure/disposalpipe/segment{
+ dir = 4
+ },
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
+/obj/machinery/door/airlock/engineering/glass{
+ name = "Shared Engineering Storage"
+ },
+/obj/effect/mapping_helpers/airlock/access/any/engineering/construction,
+/turf/open/floor/iron/textured_large,
+/area/station/engineering/storage_shared)
"mUM" = (
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
@@ -48820,11 +48706,6 @@
/obj/effect/turf_decal/tile/neutral/diagonal_centre,
/turf/open/floor/iron/diagonal,
/area/station/commons/dorms)
-"nJe" = (
-/obj/effect/turf_decal/tile/blue/fourcorners,
-/obj/effect/landmark/start/depsec/medical,
-/turf/open/floor/iron/freezer,
-/area/station/security/checkpoint/medical)
"nJj" = (
/obj/effect/turf_decal/siding/wood{
dir = 5
@@ -48960,19 +48841,6 @@
},
/turf/open/floor/iron/textured_large,
/area/station/cargo/sorting)
-"nLM" = (
-/obj/structure/disposalpipe/segment{
- dir = 4
- },
-/obj/effect/turf_decal/trimline/purple/filled/line{
- dir = 10
- },
-/obj/effect/turf_decal/stripes/line{
- dir = 10
- },
-/obj/machinery/rnd/production/circuit_imprinter/department/science,
-/turf/open/floor/iron/dark/textured,
-/area/station/science/robotics/lab)
"nMf" = (
/obj/structure/reagent_dispensers/plumbed{
name = "medbay water reservoir"
@@ -49848,6 +49716,15 @@
/obj/machinery/light/directional/north,
/turf/open/floor/iron/textured_edge,
/area/station/cargo/warehouse)
+"nWp" = (
+/obj/effect/landmark/start/roboticist,
+/obj/effect/turf_decal/tile/dark_red{
+ dir = 4
+ },
+/turf/open/floor/iron/dark/textured_corner{
+ dir = 4
+ },
+/area/station/science/robotics/lab)
"nWq" = (
/obj/effect/turf_decal/trimline/brown/filled/line{
dir = 8
@@ -49931,23 +49808,6 @@
},
/turf/open/floor/iron/dark/textured,
/area/station/security/brig)
-"nXH" = (
-/obj/effect/turf_decal/trimline/purple/filled/line{
- dir = 4
- },
-/obj/structure/chair/office,
-/obj/machinery/computer/security/telescreen{
- desc = "Used for monitoring medbay to ensure patient safety.";
- dir = 8;
- name = "Science Monitor";
- network = list("rd","toxins","minisat","xeno","test");
- pixel_x = 32
- },
-/obj/effect/landmark/start/depsec/science,
-/turf/open/floor/iron/white/textured_edge{
- dir = 8
- },
-/area/station/security/checkpoint/science/research)
"nXM" = (
/obj/machinery/atmospherics/components/unary/vent_pump/on/layer4{
dir = 8
@@ -50312,6 +50172,19 @@
dir = 4
},
/area/station/commons/fitness/recreation/entertainment)
+"oeG" = (
+/obj/structure/sign/poster/contraband/borg_fancy_1/directional/west,
+/obj/structure/window/reinforced/spawner/directional/north,
+/obj/effect/turf_decal/tile/dark_red/anticorner/contrasted{
+ dir = 1
+ },
+/obj/effect/turf_decal/bot_red,
+/obj/structure/table,
+/obj/item/clothing/gloves/latex,
+/obj/item/clothing/mask/surgical,
+/obj/item/clothing/suit/apron/surgical,
+/turf/open/floor/iron/dark/textured,
+/area/station/science/robotics/lab)
"oeK" = (
/obj/effect/turf_decal/trimline/purple/filled/line{
dir = 8
@@ -53293,24 +53166,6 @@
},
/turf/open/floor/iron/dark/textured,
/area/station/medical/break_room)
-"oVw" = (
-/obj/structure/chair/sofa/corp/right{
- color = "#DE3A3A"
- },
-/obj/machinery/airalarm/directional/north,
-/obj/effect/turf_decal/siding/wood{
- dir = 1
- },
-/obj/machinery/camera/directional/north{
- c_tag = "Research Division - Port";
- dir = 9;
- name = "science camera";
- network = list("ss13","rd")
- },
-/obj/item/pai_card,
-/obj/effect/landmark/start/depsec/science,
-/turf/open/floor/wood/large,
-/area/station/science/research)
"oVM" = (
/obj/structure/railing,
/obj/structure/cable,
@@ -55425,6 +55280,15 @@
/obj/structure/cable,
/turf/closed/wall/r_wall,
/area/station/security/checkpoint/science/research)
+"pxC" = (
+/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2{
+ dir = 1
+ },
+/mob/living/basic/pet/dog/corgi{
+ name = "Dirk"
+ },
+/turf/open/floor/iron/dark/textured_large,
+/area/station/science/robotics/lab)
"pxK" = (
/obj/structure/disposalpipe/segment{
dir = 4
@@ -56011,6 +55875,19 @@
/obj/machinery/light_switch/directional/east,
/turf/open/floor/iron/textured,
/area/station/engineering/atmos/storage/gas)
+"pFH" = (
+/obj/structure/disposalpipe/segment{
+ dir = 4
+ },
+/obj/effect/turf_decal/trimline/purple/filled/line{
+ dir = 10
+ },
+/obj/effect/turf_decal/stripes/line{
+ dir = 10
+ },
+/obj/machinery/rnd/production/circuit_imprinter/department/science,
+/turf/open/floor/iron/dark/textured,
+/area/station/science/robotics/lab)
"pGh" = (
/turf/open/floor/iron/white/textured_large,
/area/station/hallway/primary/fore)
@@ -56116,13 +55993,6 @@
/obj/effect/landmark/start/hangover,
/turf/open/floor/iron/textured_edge,
/area/station/hallway/primary/aft)
-"pHC" = (
-/obj/effect/turf_decal/trimline/brown/filled/line,
-/obj/effect/landmark/start/depsec/supply,
-/turf/open/floor/iron/textured_edge{
- dir = 1
- },
-/area/station/security/checkpoint/supply)
"pHG" = (
/obj/structure/flora/grass/jungle,
/obj/structure/flora/bush/grassy,
@@ -58435,6 +58305,13 @@
dir = 8
},
/area/station/medical/exam_room)
+"qkd" = (
+/obj/effect/turf_decal/trimline/brown/filled/line,
+/obj/effect/landmark/start/depsec/supply,
+/turf/open/floor/iron/textured_edge{
+ dir = 1
+ },
+/area/station/security/checkpoint/supply)
"qki" = (
/obj/structure/table/reinforced,
/obj/item/assembly/voice{
@@ -60462,6 +60339,17 @@
dir = 1
},
/area/station/cargo/storage)
+"qND" = (
+/obj/structure/chair/sofa/corp{
+ color = "#DE3A3A"
+ },
+/obj/effect/turf_decal/siding/wood{
+ dir = 1
+ },
+/obj/item/radio/intercom/directional/north,
+/obj/effect/landmark/start/research_director,
+/turf/open/floor/wood/large,
+/area/station/science/research)
"qNW" = (
/obj/structure/window/spawner/directional/west,
/obj/structure/window/spawner/directional/north,
@@ -61546,6 +61434,53 @@
},
/turf/open/floor/iron/dark/textured,
/area/station/security/execution/education)
+"rdQ" = (
+/obj/machinery/firealarm/directional/west,
+/obj/effect/turf_decal/trimline/purple/filled/line{
+ dir = 8
+ },
+/obj/effect/turf_decal/stripes/line{
+ dir = 8
+ },
+/obj/effect/turf_decal/bot,
+/obj/machinery/light/cold/directional/west,
+/obj/structure/table,
+/obj/item/storage/medkit{
+ pixel_x = -3;
+ pixel_y = 8
+ },
+/obj/item/healthanalyzer{
+ pixel_x = -3;
+ pixel_y = 7
+ },
+/obj/item/storage/medkit{
+ pixel_x = -3;
+ pixel_y = 0
+ },
+/obj/item/healthanalyzer{
+ pixel_x = -3;
+ pixel_y = -2
+ },
+/obj/item/assembly/prox_sensor{
+ pixel_x = 10;
+ pixel_y = 14
+ },
+/obj/item/assembly/prox_sensor{
+ pixel_x = 11;
+ pixel_y = 8
+ },
+/obj/item/assembly/prox_sensor{
+ pixel_x = 10;
+ pixel_y = 2
+ },
+/obj/item/assembly/prox_sensor{
+ pixel_x = 11;
+ pixel_y = -4
+ },
+/turf/open/floor/iron/dark/textured_edge{
+ dir = 4
+ },
+/area/station/science/robotics/lab)
"rdT" = (
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
@@ -62037,6 +61972,26 @@
/obj/machinery/firealarm/directional/north,
/turf/open/floor/wood/large,
/area/station/service/theater)
+"rkz" = (
+/obj/machinery/door/firedoor,
+/obj/structure/cable,
+/obj/effect/turf_decal/stripes/line{
+ dir = 1
+ },
+/obj/effect/turf_decal/stripes/line,
+/obj/structure/disposalpipe/segment,
+/obj/machinery/door/poddoor/preopen{
+ id = "engielock";
+ name = "Engineering Lockdown Blast Door"
+ },
+/obj/machinery/door/airlock/engineering/glass{
+ name = "EngineRoom"
+ },
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
+/obj/effect/mapping_helpers/airlock/access/any/engineering/construction,
+/turf/open/floor/iron/textured_large,
+/area/station/engineering/main)
"rkG" = (
/obj/effect/turf_decal/trimline/brown/filled/warning,
/turf/open/floor/iron/textured_edge{
@@ -62819,6 +62774,11 @@
/obj/structure/disposalpipe/segment,
/turf/open/floor/catwalk_floor/iron_smooth,
/area/station/maintenance/starboard/lesser)
+"rxL" = (
+/obj/machinery/holopad,
+/obj/effect/turf_decal/bot,
+/turf/open/floor/iron/dark/textured_large,
+/area/station/science/robotics/lab)
"rxO" = (
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
/turf/open/floor/carpet,
@@ -63895,6 +63855,13 @@
},
/turf/open/floor/catwalk_floor/iron_smooth,
/area/station/maintenance/starboard/lesser)
+"rOg" = (
+/obj/structure/chair{
+ dir = 8
+ },
+/obj/effect/landmark/start/depsec/engineering,
+/turf/open/floor/iron/textured_large,
+/area/station/security/checkpoint/engineering)
"rOi" = (
/obj/structure/window/reinforced/spawner/directional/south,
/obj/effect/artifact_spawner,
@@ -64403,6 +64370,19 @@
dir = 1
},
/area/station/cargo/lobby)
+"rUV" = (
+/obj/effect/turf_decal/trimline/purple/filled/line{
+ dir = 8
+ },
+/obj/effect/turf_decal/bot,
+/obj/machinery/light/cold/directional/west,
+/obj/machinery/computer/cloning{
+ dir = 4
+ },
+/turf/open/floor/iron/white/textured_edge{
+ dir = 4
+ },
+/area/station/science/genetics)
"rVl" = (
/obj/structure/cable,
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
@@ -65514,13 +65494,6 @@
dir = 1
},
/area/station/security/prison/work)
-"snJ" = (
-/obj/effect/turf_decal/tile/blue/fourcorners,
-/obj/item/radio/intercom/directional/west,
-/obj/structure/sink/directional/east,
-/obj/effect/landmark/start/depsec/medical,
-/turf/open/floor/iron/freezer,
-/area/station/security/checkpoint/medical)
"snO" = (
/obj/machinery/chem_dispenser/drinks{
dir = 4
@@ -71075,6 +71048,23 @@
/obj/structure/cable,
/turf/open/floor/engine,
/area/station/engineering/supermatter)
+"tHk" = (
+/obj/effect/turf_decal/trimline/purple/filled/line{
+ dir = 4
+ },
+/obj/structure/chair/office,
+/obj/machinery/computer/security/telescreen{
+ desc = "Used for monitoring medbay to ensure patient safety.";
+ dir = 8;
+ name = "Science Monitor";
+ network = list("rd","toxins","minisat","xeno","test");
+ pixel_x = 32
+ },
+/obj/effect/landmark/start/depsec/science,
+/turf/open/floor/iron/white/textured_edge{
+ dir = 8
+ },
+/area/station/security/checkpoint/science/research)
"tHC" = (
/obj/effect/spawner/random/trash/grille_or_waste,
/turf/open/floor/iron/textured,
@@ -73358,6 +73348,15 @@
/obj/effect/turf_decal/bot,
/turf/open/floor/wood/large,
/area/station/commons/fitness/recreation/entertainment)
+"uqv" = (
+/obj/effect/turf_decal/trimline/purple/filled/line{
+ dir = 4
+ },
+/obj/machinery/vending/mechcomp,
+/turf/open/floor/iron/white/textured_edge{
+ dir = 8
+ },
+/area/station/science/research)
"uqw" = (
/obj/machinery/door/firedoor,
/obj/machinery/door/airlock/engineering{
@@ -73707,6 +73706,17 @@
/obj/structure/displaycase/trophy,
/turf/open/floor/iron/grimy,
/area/station/hallway/primary/central/fore)
+"utL" = (
+/obj/structure/extinguisher_cabinet/directional/east,
+/obj/effect/turf_decal/trimline/purple/filled/line{
+ dir = 5
+ },
+/obj/effect/turf_decal/stripes/line{
+ dir = 5
+ },
+/obj/structure/reagent_dispensers/fueltank/large,
+/turf/open/floor/iron/dark/textured,
+/area/station/science/robotics/lab)
"utM" = (
/obj/effect/turf_decal/trimline/brown/filled/warning{
dir = 4
@@ -74314,18 +74324,6 @@
/obj/machinery/newscaster/directional/south,
/turf/open/floor/iron/textured,
/area/station/maintenance/department/electrical)
-"uAE" = (
-/obj/effect/turf_decal/trimline/yellow/filled/corner{
- dir = 8
- },
-/obj/structure/chair{
- dir = 8
- },
-/obj/effect/landmark/start/depsec/engineering,
-/turf/open/floor/iron/textured_corner{
- dir = 8
- },
-/area/station/security/checkpoint/engineering)
"uAN" = (
/obj/structure/flora/grass/jungle,
/obj/structure/window/fulltile,
@@ -75959,15 +75957,6 @@
"uZu" = (
/turf/open/floor/grass,
/area/station/security/prison/garden)
-"uZB" = (
-/obj/effect/turf_decal/trimline/purple/filled/line{
- dir = 4
- },
-/obj/machinery/vending/mechcomp,
-/turf/open/floor/iron/white/textured_edge{
- dir = 8
- },
-/area/station/science/research)
"uZU" = (
/obj/machinery/atmospherics/components/unary/portables_connector/visible{
dir = 8
@@ -76784,19 +76773,6 @@
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/iron/textured,
/area/station/maintenance/aft/lesser)
-"vmI" = (
-/obj/effect/turf_decal/trimline/purple/filled/line{
- dir = 8
- },
-/obj/structure/disposalpipe/segment{
- dir = 4
- },
-/obj/effect/turf_decal/bot,
-/obj/machinery/dna_scannernew,
-/turf/open/floor/iron/white/textured_edge{
- dir = 4
- },
-/area/station/science/genetics)
"vmX" = (
/obj/effect/decal/cleanable/dirt,
/obj/machinery/door/airlock{
@@ -78458,6 +78434,13 @@
dir = 8
},
/area/station/engineering/atmos)
+"vLC" = (
+/obj/structure/cable,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
+/obj/machinery/duct,
+/mob/living/basic/bot/cleanbot/medbay,
+/turf/open/floor/iron/white/textured_large,
+/area/station/medical/storage)
"vLO" = (
/obj/structure/cable,
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
@@ -78841,16 +78824,6 @@
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
/turf/open/floor/iron/freezer,
/area/station/medical/pharmacy)
-"vRB" = (
-/obj/structure/extinguisher_cabinet/directional/west,
-/obj/effect/turf_decal/tile/dark_red/half/contrasted{
- dir = 8
- },
-/obj/structure/sink/directional/south,
-/turf/open/floor/iron/dark/textured_edge{
- dir = 4
- },
-/area/station/science/robotics/lab)
"vRH" = (
/obj/effect/turf_decal/stripes/line{
dir = 6
@@ -80076,6 +80049,15 @@
/obj/effect/mapping_helpers/airalarm/tlv_no_checks,
/turf/open/floor/engine,
/area/station/engineering/supermatter)
+"wjg" = (
+/obj/effect/turf_decal/trimline/purple/filled/warning{
+ dir = 5
+ },
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
+/obj/structure/disposalpipe/segment,
+/turf/open/floor/iron/white,
+/area/station/science/research)
"wjk" = (
/obj/machinery/drone_dispenser,
/obj/effect/turf_decal/bot,
@@ -80958,6 +80940,14 @@
/obj/structure/window/reinforced/plasma/spawner/directional/east,
/turf/open/floor/iron/textured,
/area/station/commons/vacant_room/office)
+"wuV" = (
+/obj/effect/turf_decal/tile/blue/fourcorners,
+/obj/structure/chair/office/light{
+ dir = 4
+ },
+/obj/effect/landmark/start/depsec/medical,
+/turf/open/floor/iron/freezer,
+/area/station/security/checkpoint/medical)
"wuW" = (
/obj/effect/spawner/structure/window/reinforced,
/turf/open/floor/plating,
@@ -81362,6 +81352,18 @@
},
/turf/open/floor/iron/white,
/area/station/medical/surgery)
+"wzR" = (
+/obj/structure/cable,
+/obj/machinery/power/apc/auto_name/directional/west,
+/obj/effect/turf_decal/trimline/purple/filled/line{
+ dir = 10
+ },
+/obj/effect/turf_decal/box,
+/obj/machinery/computer/mechpad{
+ dir = 1
+ },
+/turf/open/floor/iron/dark/textured,
+/area/station/science/robotics/mechbay)
"wzT" = (
/obj/machinery/atmospherics/pipe/smart/manifold4w/orange/visible,
/obj/effect/turf_decal/stripes/line{
@@ -82814,19 +82816,6 @@
},
/turf/open/floor/iron/textured_large,
/area/station/hallway/secondary/construction)
-"wVw" = (
-/obj/structure/sign/poster/contraband/borg_fancy_1/directional/west,
-/obj/structure/window/reinforced/spawner/directional/north,
-/obj/effect/turf_decal/tile/dark_red/anticorner/contrasted{
- dir = 1
- },
-/obj/effect/turf_decal/bot_red,
-/obj/structure/table,
-/obj/item/clothing/gloves/latex,
-/obj/item/clothing/mask/surgical,
-/obj/item/clothing/suit/apron/surgical,
-/turf/open/floor/iron/dark/textured,
-/area/station/science/robotics/lab)
"wVD" = (
/obj/effect/spawner/structure/window/reinforced,
/turf/open/floor/plating,
@@ -82984,6 +82973,16 @@
dir = 8
},
/area/station/commons/fitness/recreation/entertainment)
+"xac" = (
+/obj/structure/extinguisher_cabinet/directional/west,
+/obj/effect/turf_decal/tile/dark_red/half/contrasted{
+ dir = 8
+ },
+/obj/structure/sink/directional/south,
+/turf/open/floor/iron/dark/textured_edge{
+ dir = 4
+ },
+/area/station/science/robotics/lab)
"xag" = (
/obj/machinery/atmospherics/components/unary/vent_pump/on/layer4{
dir = 8
@@ -83225,6 +83224,19 @@
/obj/machinery/airalarm/directional/west,
/turf/open/floor/carpet/cyan,
/area/station/command/heads_quarters/blueshield)
+"xfs" = (
+/obj/effect/turf_decal/trimline/purple/filled/line{
+ dir = 9
+ },
+/obj/machinery/airalarm/directional/west,
+/obj/effect/turf_decal/delivery,
+/obj/machinery/requests_console/directional/north{
+ department = "Genetics";
+ name = "Genetics Requests console"
+ },
+/obj/machinery/clonepod,
+/turf/open/floor/iron/white,
+/area/station/science/genetics)
"xfx" = (
/obj/structure/railing{
dir = 4
@@ -83283,6 +83295,17 @@
/obj/structure/sign/poster/official/random/directional/west,
/turf/open/floor/iron/dark/textured_large,
/area/station/engineering/break_room)
+"xgi" = (
+/obj/item/radio/intercom/directional/south,
+/obj/effect/turf_decal/trimline/purple/filled/line,
+/obj/effect/turf_decal/stripes/line,
+/obj/machinery/modular_computer/preset/civilian{
+ dir = 4
+ },
+/turf/open/floor/iron/dark/textured_edge{
+ dir = 1
+ },
+/area/station/science/robotics/lab)
"xgj" = (
/obj/machinery/atmospherics/pipe/smart/simple/green/visible{
dir = 10
@@ -83424,6 +83447,18 @@
},
/turf/open/floor/carpet,
/area/station/medical/psychology)
+"xib" = (
+/obj/effect/turf_decal/trimline/yellow/filled/corner{
+ dir = 8
+ },
+/obj/structure/chair{
+ dir = 8
+ },
+/obj/effect/landmark/start/depsec/engineering,
+/turf/open/floor/iron/textured_corner{
+ dir = 8
+ },
+/area/station/security/checkpoint/engineering)
"xig" = (
/obj/machinery/light/small/directional/east,
/obj/machinery/growing/soil,
@@ -85590,26 +85625,6 @@
/obj/structure/cable,
/turf/open/floor/plating/airless,
/area/station/command/heads_quarters/rd)
-"xNl" = (
-/obj/machinery/door/firedoor,
-/obj/structure/cable,
-/obj/effect/turf_decal/stripes/line{
- dir = 1
- },
-/obj/effect/turf_decal/stripes/line,
-/obj/structure/disposalpipe/segment,
-/obj/machinery/door/poddoor/preopen{
- id = "engielock";
- name = "Engineering Lockdown Blast Door"
- },
-/obj/machinery/door/airlock/engineering/glass{
- name = "EngineRoom"
- },
-/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
-/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
-/obj/effect/mapping_helpers/airlock/access/any/engineering/construction,
-/turf/open/floor/iron/textured_large,
-/area/station/engineering/main)
"xNE" = (
/obj/machinery/portable_atmospherics/canister/air,
/obj/effect/decal/cleanable/dirt,
@@ -86631,21 +86646,6 @@
/obj/machinery/light/floor/has_bulb,
/turf/open/floor/iron/textured_large,
/area/station/engineering/atmos)
-"ybV" = (
-/obj/effect/turf_decal/trimline/purple/filled/line{
- dir = 4
- },
-/obj/effect/turf_decal/stripes/line{
- dir = 4
- },
-/obj/effect/turf_decal/bot,
-/obj/machinery/light/cold/directional/east,
-/obj/effect/landmark/start/cyborg,
-/obj/machinery/recharge_station,
-/turf/open/floor/iron/dark/textured_edge{
- dir = 8
- },
-/area/station/science/robotics/lab)
"ycf" = (
/obj/structure/disposalpipe/segment,
/obj/effect/turf_decal/tile/neutral/diagonal_centre,
@@ -111825,7 +111825,7 @@ uyk
uyk
byq
xEs
-gvI
+vLC
gJS
urP
niG
@@ -114589,7 +114589,7 @@ cIG
qpn
jgD
dow
-uZB
+uqv
jDA
pxK
dlB
@@ -114648,7 +114648,7 @@ ixX
gzZ
qxO
voi
-snJ
+dzC
qxO
kCj
qft
@@ -115102,7 +115102,7 @@ gvl
uwZ
opQ
jgD
-oVw
+bqS
oee
lXR
eaV
@@ -115112,7 +115112,7 @@ rQr
xrI
qHf
wuT
-izJ
+hii
bJt
dje
fKS
@@ -115342,7 +115342,7 @@ iqc
sfj
fXK
hIA
-diH
+mLe
fgT
wKt
xyx
@@ -115350,7 +115350,7 @@ bES
kTS
wKt
caG
-gEJ
+kxM
bFO
uex
pjh
@@ -115359,7 +115359,7 @@ oFy
aKW
fDJ
jgD
-lWk
+qND
oee
mwx
uln
@@ -115418,7 +115418,7 @@ cnC
ixX
lZg
nFV
-nJe
+kTa
rLB
lbI
nmy
@@ -115625,7 +115625,7 @@ wCL
lSX
rhM
qHf
-iTJ
+lPj
wpv
lBP
dje
@@ -115675,7 +115675,7 @@ cnC
abF
lZg
uHn
-fch
+wuV
tKO
ivR
wBL
@@ -115883,7 +115883,7 @@ sms
cuZ
dje
hMD
-nXH
+tHk
lDz
dje
lPo
@@ -116899,7 +116899,7 @@ gkm
lai
neu
vuI
-miV
+wjg
ipW
eoB
jzi
@@ -117405,9 +117405,9 @@ aIL
jjg
ijg
vBP
-dwE
-gIr
-vmI
+xfs
+rUV
+jTK
gWa
tqZ
oMC
@@ -117424,10 +117424,10 @@ aZy
heq
iSr
hYl
-amZ
-nLM
-wVw
-vRB
+rdQ
+pFH
+oeG
+xac
dIO
tlq
wvU
@@ -117684,7 +117684,7 @@ lTr
kPu
elp
oEs
-iGc
+nWp
aqg
tlq
bmt
@@ -117920,7 +117920,7 @@ for
frk
vBP
mVv
-exy
+ekr
vrs
hyx
akq
@@ -117936,13 +117936,13 @@ nEm
swP
qpy
ajz
-lDK
-kJi
+pxC
+rxL
lTr
-jbi
+bPX
rlk
fnq
-gKY
+bWv
tlq
bmt
euG
@@ -118197,7 +118197,7 @@ dEb
rqo
qEU
wvu
-cuq
+xgi
qlN
tBV
qlN
@@ -118279,8 +118279,8 @@ erh
sxd
hFa
hpe
-iYZ
-uAE
+rOg
+xib
tlS
hxy
tQP
@@ -118435,7 +118435,7 @@ irl
vBP
jYI
eKU
-dcF
+bSm
hyx
uNE
lTE
@@ -118706,8 +118706,8 @@ gMy
qlN
uPU
qlN
-aMf
-ybV
+utL
+dwh
vOI
imz
oqe
@@ -118791,7 +118791,7 @@ uWW
gXL
erh
sxd
-fWZ
+apj
hpe
qks
hOM
@@ -119055,7 +119055,7 @@ czC
fmD
bYQ
daQ
-hXj
+lqg
xpC
hgs
wBI
@@ -119225,7 +119225,7 @@ lue
sYe
yeD
uVY
-mhn
+wzR
xqf
pDu
nqS
@@ -119482,7 +119482,7 @@ oUE
dvq
vNz
dHE
-mSx
+ijh
nmx
dto
dto
@@ -120341,7 +120341,7 @@ qfk
wBI
wBI
uIw
-bbW
+mUG
uIw
wBI
jtJ
@@ -120852,7 +120852,7 @@ fol
oxL
enl
lOY
-xNl
+rkz
sJO
qQu
pkH
@@ -125164,7 +125164,7 @@ jzQ
nQl
fLp
dWg
-cYI
+jbG
vZM
uuO
rCr
@@ -129327,7 +129327,7 @@ uho
cHk
shC
mqJ
-kBs
+gkd
nnr
oLo
feh
@@ -129585,7 +129585,7 @@ iZL
oLo
nGM
lGO
-pHC
+qkd
shC
dsj
idx
@@ -130098,7 +130098,7 @@ htz
yaa
oLo
iiU
-lCb
+mqD
ovi
oLo
phj
diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index 78d1283bcc46..3692aa4d4b9a 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -164,6 +164,7 @@
#define INIT_ORDER_OUTPUTS 35
#define INIT_ORDER_RESTAURANT 34
#define INIT_ORDER_POLLUTION 32
+#define INIT_ORDER_CASSETTES 31 // monkestation addition: cassettes initialize before atoms, so that cassette stuff can be used in Initialize()
#define INIT_ORDER_ATOMS 30
#define INIT_ORDER_ARMAMENTS 27
#define INIT_ORDER_LANGUAGE 25
diff --git a/code/__DEFINES/traits/monkestation/declarations.dm b/code/__DEFINES/traits/monkestation/declarations.dm
index 29777a2e6fbc..7863be4ac5d9 100644
--- a/code/__DEFINES/traits/monkestation/declarations.dm
+++ b/code/__DEFINES/traits/monkestation/declarations.dm
@@ -1,5 +1,9 @@
// BEGIN TRAIT DEFINES
+// /mob
+/// This mob can hear the music from the DJ station.
+#define TRAIT_CAN_HEAR_MUSIC "can_hear_radio"
+
// /mob/living
/// Monkeys are friendly/neutral to this mob by defaulot.
#define TRAIT_MONKEYFRIEND "monkeyfriend"
diff --git a/code/__DEFINES/~monkestation/cassettes.dm b/code/__DEFINES/~monkestation/cassettes.dm
new file mode 100644
index 000000000000..e4ec6a1cdcb2
--- /dev/null
+++ b/code/__DEFINES/~monkestation/cassettes.dm
@@ -0,0 +1,13 @@
+/// Path to the base directory for cassette stuff
+#define CASSETTE_BASE_DIR "data/cassette_storage/"
+/// Path to the file containing a list of cassette IDs.
+#define CASSETTE_ID_FILE (CASSETTE_BASE_DIR + "ids.json")
+/// Path to the data for the cassette of the given ID.
+#define CASSETTE_FILE(id) (CASSETTE_BASE_DIR + "[id].json")
+
+/// This cassette is unapproved, and has not been submitted for review.
+#define CASSETTE_STATUS_UNAPPROVED 0
+/// This cassette is under review.
+#define CASSETTE_STATUS_REVIEWING 1
+/// This cassette has been approved.
+#define CASSETTE_STATUS_APPROVED 2
diff --git a/code/__DEFINES/~monkestation/dcs/signals/signals_global.dm b/code/__DEFINES/~monkestation/dcs/signals/signals_global.dm
index 7505478a365c..409cb8bf4043 100644
--- a/code/__DEFINES/~monkestation/dcs/signals/signals_global.dm
+++ b/code/__DEFINES/~monkestation/dcs/signals/signals_global.dm
@@ -2,3 +2,7 @@
#define COMSIG_GLOB_GOLDENEYE_KEY_CREATED "!goldeneye_key_created"
/// Sent whenever a camera network broadcast is started/stopped/updated: (camera_net, is_show_active, announcement)
#define COMSIG_GLOB_NETWORK_BROADCAST_UPDATED "!network_broadcast_updated"
+/// Sent whenever a mob becomes capable of hearing DJ music: (mob/listener)
+#define COMSIG_GLOB_ADD_MUSIC_LISTENER "!add_music_listener"
+/// Sent whenever a mob becomes no longer capable of hearing DJ music: (mob/listener)
+#define COMSIG_GLOB_REMOVE_MUSIC_LISTENER "!remove_music_listener"
diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm
index 7112c0447ae6..4fedbe19a931 100644
--- a/code/__HELPERS/roundend.dm
+++ b/code/__HELPERS/roundend.dm
@@ -301,7 +301,8 @@ GLOBAL_LIST_INIT(round_end_images, world.file2list("data/image_urls.txt")) // MO
// monkestation start: token backups, monkecoin rewards, challenges, and roundend webhook
save_tokens()
- refund_cassette()
+#warn TODO: cassette refunds
+ // refund_cassette()
distribute_rewards()
sleep(5 SECONDS)
ready_for_reboot = TRUE
diff --git a/code/__HELPERS/~monkestation-helpers/roundend.dm b/code/__HELPERS/~monkestation-helpers/roundend.dm
index 5351e9d2bcff..544031be499f 100644
--- a/code/__HELPERS/~monkestation-helpers/roundend.dm
+++ b/code/__HELPERS/~monkestation-helpers/roundend.dm
@@ -37,6 +37,8 @@
if(total_payout)
client?.prefs?.adjust_metacoins(client?.ckey, total_payout, "Challenge rewards.")
+#warn TODO: cassette refunds
+/*
/datum/controller/subsystem/ticker/proc/refund_cassette()
if(!length(GLOB.cassette_reviews))
return
@@ -62,3 +64,4 @@
message_admins("Balance not adjusted for Cassette:[review.submitted_tape.name], Balance for [client]; Previous:[prev_bal], Expected:[prev_bal + 5000], Current:[client?.prefs?.metacoins]. Issue logged.")
log_admin("Balance not adjusted for Cassette:[review.submitted_tape.name], Balance for [client]; Previous:[prev_bal], Expected:[prev_bal + 5000], Current:[client?.prefs?.metacoins].")
qdel(review)
+*/
diff --git a/code/__HELPERS/~monkestation-helpers/text.dm b/code/__HELPERS/~monkestation-helpers/text.dm
new file mode 100644
index 000000000000..cb6383f2e031
--- /dev/null
+++ b/code/__HELPERS/~monkestation-helpers/text.dm
@@ -0,0 +1,6 @@
+/// Checks to see if a string starts with http:// or https://
+/proc/is_http_protocol(text)
+ var/static/regex/http_regex
+ if(isnull(http_regex))
+ http_regex = new("^https?://")
+ return findtext(text, http_regex)
diff --git a/code/_globalvars/_regexes.dm b/code/_globalvars/_regexes.dm
index 7297d509a918..e850889a42b7 100644
--- a/code/_globalvars/_regexes.dm
+++ b/code/_globalvars/_regexes.dm
@@ -1,7 +1,4 @@
//These are a bunch of regex datums for use /((any|every|no|some|head|foot)where(wolf)?\sand\s)+(\.[\.\s]+\s?where\?)?/i
-GLOBAL_DATUM_INIT(is_http_protocol, /regex, regex("^https?://"))
-GLOBAL_DATUM_INIT(is_http_protocol_non_secure, /regex, regex("^http?://"))
-
GLOBAL_DATUM_INIT(is_website, /regex, regex("http|www.|\[a-z0-9_-]+.(com|org|net|mil|edu)+", "i"))
GLOBAL_DATUM_INIT(is_email, /regex, regex("\[a-z0-9_-]+@\[a-z0-9_-]+.\[a-z0-9_-]+", "i"))
diff --git a/code/controllers/configuration/entries/monkestation.dm b/code/controllers/configuration/entries/monkestation.dm
index 988fae0853c4..d9e5cdf60084 100644
--- a/code/controllers/configuration/entries/monkestation.dm
+++ b/code/controllers/configuration/entries/monkestation.dm
@@ -69,6 +69,8 @@
default = "http://127.0.0.1:1330"
/datum/config_entry/string/plexora_url/ValidateAndSet(str_val)
- if(!findtext(str_val, GLOB.is_http_protocol))
+ if(!is_http_protocol(str_val))
return FALSE
return ..()
+
+/datum/config_entry/flag/cassettes_in_db
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index d2077b1fed80..d61b48fb7b4e 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -19,7 +19,8 @@ GLOBAL_PROTECT(admin_verbs_default)
/client/proc/reload_admins,
/client/proc/requests,
/client/proc/secrets,
- /client/proc/review_cassettes, /*monkestation addition Opens the Cassette Review menu*/
+#warn TODO: cassette reviews
+ // /client/proc/review_cassettes, /*monkestation addition Opens the Cassette Review menu*/
/client/proc/stop_sounds,
/client/proc/tag_datum_mapview,
)
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index fcc56af55df6..3ae0bff78b46 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -1806,6 +1806,8 @@
message_admins("[key_name_admin(owner)] rejected a [token_holder.in_queue] token from [ADMIN_LOOKUPFLW(user_client)]")
log_admin("[user_client]'s [token_holder.in_queue] token has been rejected by [owner].")
+#warn TODO: cassette reviews
+/*
else if(href_list["open_music_review"])
if(!check_rights(R_ADMIN))
return
@@ -1814,7 +1816,7 @@
if(!istype(cassette_review))
return
cassette_review.ui_interact(usr)
-
+*/
else if(href_list["approve_token_event"])
if(!check_rights(R_ADMIN))
return
diff --git a/code/modules/admin/verbs/playsound.dm b/code/modules/admin/verbs/playsound.dm
index 784963f00d13..15e12625c8a9 100644
--- a/code/modules/admin/verbs/playsound.dm
+++ b/code/modules/admin/verbs/playsound.dm
@@ -150,7 +150,7 @@
message_admins("[key_name(user)] stopped web sounds.")
web_sound_url = null
stop_web_sounds = TRUE
- if(web_sound_url && !findtext(web_sound_url, GLOB.is_http_protocol))
+ if(web_sound_url && !is_http_protocol(web_sound_url))
tgui_alert(user, "The media provider returned a content URL that isn't using the HTTP or HTTPS protocol. This is a security risk and the sound will not be played.", "Security Risk", list("OK"))
to_chat(user, span_boldwarning("BLOCKED: Content URL not using HTTP(S) Protocol!"), confidential = TRUE)
@@ -183,7 +183,7 @@
if(length(web_sound_input))
web_sound_input = trim(web_sound_input)
- if(findtext(web_sound_input, ":") && !findtext(web_sound_input, GLOB.is_http_protocol))
+ if(findtext(web_sound_input, ":") && !is_http_protocol(web_sound_input))
to_chat(src, span_boldwarning("Non-http(s) URIs are not allowed."), confidential = TRUE)
to_chat(src, span_warning("For youtube-dl shortcuts like ytsearch: please use the appropriate full URL from the website."), confidential = TRUE)
return
diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm
index 6fb429925942..5500820e12b7 100644
--- a/code/modules/mob/dead/new_player/new_player.dm
+++ b/code/modules/mob/dead/new_player/new_player.dm
@@ -244,12 +244,6 @@
humanc.load_persistent_scars()
SSpersistence.load_modular_persistence(humanc.get_organ_slot(ORGAN_SLOT_BRAIN))
- //monkestation edit start
- if(GLOB.dj_booth)
- var/obj/machinery/cassette/dj_station/dj = GLOB.dj_booth
- dj.add_new_player(humanc)
- //monkestation edit end
-
if(GLOB.curse_of_madness_triggered)
give_madness(humanc, GLOB.curse_of_madness_triggered)
diff --git a/code/modules/requests/request_manager.dm b/code/modules/requests/request_manager.dm
index b39cb2d9ad72..34e987799e2d 100644
--- a/code/modules/requests/request_manager.dm
+++ b/code/modules/requests/request_manager.dm
@@ -246,7 +246,7 @@ GLOBAL_DATUM_INIT(requests, /datum/request_manager, new)
if(request.req_type != REQUEST_INTERNET_SOUND)
to_chat(usr, "Request doesn't have a sound to play.", confidential = TRUE)
return TRUE
- if(findtext(request.message, ":") && !findtext(request.message, GLOB.is_http_protocol))
+ if(findtext(request.message, ":") && !is_http_protocol(request.message))
to_chat(usr, "Request is not a valid URL.", confidential = TRUE)
return TRUE
diff --git a/code/modules/tgui_panel/audio.dm b/code/modules/tgui_panel/audio.dm
index 680696159943..3397434b8970 100644
--- a/code/modules/tgui_panel/audio.dm
+++ b/code/modules/tgui_panel/audio.dm
@@ -20,9 +20,7 @@
* optional extra_data list Optional settings.
*/
/datum/tgui_panel/proc/play_music(url, extra_data)
- if(!is_ready())
- return
- if(!findtext(url, GLOB.is_http_protocol))
+ if(!is_ready() || !is_http_protocol(url))
return
var/list/payload = list()
if(length(extra_data) > 0)
diff --git a/code/modules/unit_tests/unit_test.dm b/code/modules/unit_tests/unit_test.dm
index 1877abe8ddf5..2b1656903f0a 100644
--- a/code/modules/unit_tests/unit_test.dm
+++ b/code/modules/unit_tests/unit_test.dm
@@ -328,8 +328,8 @@ GLOBAL_VAR_INIT(focused_tests, focused_tests())
///we generate mobs in these and create destroy does this in null space
ignore += typesof(/obj/item/loot_table_maker)
///we need to use json_decode to run randoms properly
- ignore += typesof(/obj/item/device/cassette_tape)
- ignore += typesof(/datum/cassette/cassette_tape)
+ ignore += typesof(/obj/item/cassette_tape)
+ ignore += typesof(/datum/cassette)
///we also dont want weathers or weather events as they will hold refs to alot of stuff as they shouldn't be deleted
ignore += typesof(/datum/weather_event)
ignore += typesof(/datum/particle_weather)
diff --git a/config/config.txt b/config/config.txt
index 1078e1d6e593..98867df83c30 100644
--- a/config/config.txt
+++ b/config/config.txt
@@ -567,3 +567,5 @@ CONFIG_ERRORS_RUNTIME
## The age in days if minimum account age is on
#MINIMUM_AGE
+## If enabled, cassette tapes will be stored in the database, rather than JSON files on-disk.
+#CASSETTES_IN_DB
diff --git a/monkestation/code/game/objects/items/devices/radio/headset.dm b/monkestation/code/game/objects/items/devices/radio/headset.dm
index 8be0cd0cbe8d..d51020d250b9 100644
--- a/monkestation/code/game/objects/items/devices/radio/headset.dm
+++ b/monkestation/code/game/objects/items/devices/radio/headset.dm
@@ -1,3 +1,12 @@
+/obj/item/radio/headset/equipped(mob/user, slot, initial)
+ . = ..()
+ if(slot_flags & slot)
+ ADD_TRAIT(user, TRAIT_CAN_HEAR_MUSIC, REF(src))
+
+/obj/item/radio/headset/dropped(mob/user, silent)
+ . = ..()
+ REMOVE_TRAIT(user, TRAIT_CAN_HEAR_MUSIC, REF(src))
+
/obj/item/radio/headset/headset_secmed
name = "brig physician radio headset"
desc = "This is used by your secure doctor."
diff --git a/monkestation/code/game/objects/items/implants/implant_misc.dm b/monkestation/code/game/objects/items/implants/implant_misc.dm
new file mode 100644
index 000000000000..a84c63c522fd
--- /dev/null
+++ b/monkestation/code/game/objects/items/implants/implant_misc.dm
@@ -0,0 +1,9 @@
+/obj/item/implant/radio/implant(mob/living/target, mob/user, silent, force)
+ . = ..()
+ if(.)
+ ADD_TRAIT(target, TRAIT_CAN_HEAR_MUSIC, REF(src))
+
+/obj/item/implant/radio/removed(mob/living/source, silent, special)
+ . = ..()
+ if(.)
+ REMOVE_TRAIT(source, TRAIT_CAN_HEAR_MUSIC, REF(src))
diff --git a/monkestation/code/modules/admin/verbs/spawn_mixtape.dm b/monkestation/code/modules/admin/verbs/spawn_mixtape.dm
index bffca51317a0..db39c7529087 100644
--- a/monkestation/code/modules/admin/verbs/spawn_mixtape.dm
+++ b/monkestation/code/modules/admin/verbs/spawn_mixtape.dm
@@ -3,19 +3,22 @@
set name = "Spawn Mixtape"
set desc = "Select an approved mixtape to spawn at your location."
- var/datum/mixtape_spawner/tgui = new(usr)//create the datum
- tgui.ui_interact(usr)//datum has a tgui component, here we open the window
+ if(!check_rights(R_ADMIN))
+ return
+ new /datum/mixtape_spawner(src)
/datum/mixtape_spawner
- var/client/holder //client of whoever is using this datum
+ /// The client of whoever is using this datum.
+ var/client/holder
/datum/mixtape_spawner/New(user)//user can either be a client or a mob due to byondcode(tm)
- if (istype(user, /client))
- var/client/user_client = user
- holder = user_client //if its a client, assign it to holder
- else
- var/mob/user_mob = user
- holder = user_mob.client //if its a mob, assign the mob's client to holder
+ . = ..()
+ holder = get_player_client(user)
+ ui_interact(holder.mob)
+
+/datum/mixtape_spawner/Destroy(force)
+ holder = null
+ return ..()
/datum/mixtape_spawner/ui_state(mob/user)
return GLOB.admin_state
@@ -27,31 +30,38 @@
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "MixtapeSpawner")
+ ui.set_autoupdate(FALSE)
ui.open()
-/datum/mixtape_spawner/ui_data(mob/user)
- var/list/data = list()
- if(!length(SScassette_storage.cassette_datums))
- return
- for(var/datum/cassette_data/cassette in SScassette_storage.cassette_datums)
- data["approved_cassettes"] += list(list(
- "name" = cassette.cassette_name,
- "desc" = cassette.cassette_desc,
- "cassette_design_front" = cassette.cassette_design_front,
- "creator_ckey" = cassette.cassette_author_ckey,
- "creator_name" = cassette.cassette_author,
- "song_names" = cassette.song_names,
- "id" = cassette.cassette_id
+/datum/mixtape_spawner/ui_static_data(mob/user)
+ var/list/approved_cassettes = list()
+ for(var/datum/cassette/cassette as anything in SScassettes.cassettes)
+ if(cassette.status != CASSETTE_STATUS_APPROVED)
+ continue
+ approved_cassettes += list(list(
+ "name" = cassette.name,
+ "desc" = cassette.desc,
+ "cassette_design_front" = cassette.front.design,
+ "creator_ckey" = ckey(cassette.author.ckey),
+ "creator_name" = cassette.author.name,
+ "song_names" = cassette.list_song_names(),
+ "id" = cassette.id,
))
- return data
+ return list("approved_cassettes" = approved_cassettes)
-/datum/mixtape_spawner/ui_act(action, params)
+/datum/mixtape_spawner/ui_act(action, list/params, datum/tgui/ui)
. = ..()
if(.)
return
+ var/mob/user = ui.user
switch(action)
if("spawn")
- if (params["id"])
- new/obj/item/device/cassette_tape(usr.loc, params["id"])
- SSblackbox.record_feedback("tally", "admin_verb", 1, "Spawn Mixtape")
- log_admin("[key_name(usr)] created mixtape [params["id"]] at [usr.loc].")
+ var/id = params["id"]
+ if(!id)
+ return
+ var/atom/spawn_loc = user.drop_location()
+ new /obj/item/cassette_tape(spawn_loc, id)
+ SSblackbox.record_feedback("tally", "admin_verb", 1, "Spawn Mixtape")
+ message_admins("[key_name_admin(user)] spawned mixtape [id] at [ADMIN_COORDJMP(spawn_loc)].")
+ log_admin("[key_name(user)] spawned mixtape [id] at [loc_name(spawn_loc)].")
+ return TRUE
diff --git a/monkestation/code/modules/cargo/crates/goodies.dm b/monkestation/code/modules/cargo/crates/goodies.dm
index 79ed63cfb20d..d199df9b2f9f 100644
--- a/monkestation/code/modules/cargo/crates/goodies.dm
+++ b/monkestation/code/modules/cargo/crates/goodies.dm
@@ -8,10 +8,10 @@
name = "Cassette Mini-Pack"
desc = "Alright, we'll admit it, 10 cassettes are too much for the majority of our users. Contains 3 Approved Cassettes."
cost = PAYCHECK_CREW * 5
- contains = list(/obj/item/device/cassette_tape/random = 3)
+ contains = list(/obj/item/cassette_tape/random = 3)
/datum/supply_pack/goody/blankcassette
name = "Blank Cassette Mini-Pack"
desc = "NO! We wont admit defeat! You will march yourself down to the Service section and purchase the 10 Blank Cassette pack instead of this Weak 3 Blank Cassette Pack!"
cost = PAYCHECK_CREW * 3
- contains = list(/obj/item/device/cassette_tape/blank = 3)
+ contains = list(/obj/item/cassette_tape/blank = 3)
diff --git a/monkestation/code/modules/cargo/crates/service.dm b/monkestation/code/modules/cargo/crates/service.dm
index 3b0c69c0efe9..fe7fbcd48ba3 100644
--- a/monkestation/code/modules/cargo/crates/service.dm
+++ b/monkestation/code/modules/cargo/crates/service.dm
@@ -45,13 +45,13 @@
/datum/supply_pack/service/cassettes/fill(obj/structure/closet/crate/our_crate)
for(var/id in unique_random_tapes(10))
- new /obj/item/device/cassette_tape(our_crate, id)
+ new /obj/item/cassette_tape(our_crate, id)
/datum/supply_pack/service/blankcassettes
name = "Blank Cassettes Crate"
desc = "in the VERY unlikely event you have run out of blank cassettes, you can get 10 blank ones here. Contains 10 blank cassettes for use in Walkmans."
cost = CARGO_CRATE_VALUE * 2
- contains = list(/obj/item/device/cassette_tape/blank = 10)
+ contains = list(/obj/item/cassette_tape/blank = 10)
crate_name = "cassette crate"
/datum/supply_pack/service/walkmen
diff --git a/monkestation/code/modules/cassettes/cassette.dm b/monkestation/code/modules/cassettes/cassette.dm
index 9fdfe816cb7e..e43e88b4aa12 100644
--- a/monkestation/code/modules/cassettes/cassette.dm
+++ b/monkestation/code/modules/cassettes/cassette.dm
@@ -1,86 +1,75 @@
-/obj/item/device/cassette_tape
+/obj/item/cassette_tape
name = "Debug Cassette Tape"
desc = "You shouldn't be seeing this!"
icon = 'monkestation/code/modules/cassettes/icons/walkman.dmi'
icon_state = "cassette_flip"
w_class = WEIGHT_CLASS_SMALL
- ///icon of the cassettes front side
- var/side1_icon = "cassette_worstmap"
- var/side2_icon = "cassette_worstmap"
- ///if the cassette is flipped, for playing second list of songs
+ item_flags = NOBLUDGEON
+ /// If the cassette is flipped, for playing second list of songs.
var/flipped = FALSE
- ///list of songs each side has to play
- var/list/songs = list("side1" = list(),
- "side2" = list())
- ///list of each songs name in the order they appear
- var/list/song_names = list("side1" = list(),
- "side2" = list())
- ///the id of the cassette
- var/id
- ///the ckey of the cassette author
- var/ckey_author
- ///the authors name displayed in examine text
- var/author_name
- ///are we an approved tape?
- var/approved_tape = FALSE
- ///are we random?
+ /// The data for this cassette.
+ var/datum/cassette/cassette_data
+ /// Should we just spawn a random cassette?
var/random = FALSE
- var/cassette_desc_string = "Generic Desc"
+ /// ID of the cassette to spawn in as by default.
+ var/id
-/obj/item/device/cassette_tape/Initialize(mapload, spawned_id)
+/obj/item/cassette_tape/Initialize(mapload, spawned_id)
. = ..()
- if(!length(GLOB.approved_ids))
- GLOB.approved_ids = initialize_approved_ids()
-
- if(length(GLOB.approved_ids))
- if(spawned_id && (spawned_id in GLOB.approved_ids))
- id = spawned_id
- else if(random)
- id = pick(GLOB.approved_ids)
-
- var/file = file("data/cassette_storage/[id].json")
- if(!fexists(file))
- return
+ spawned_id ||= id
+ if(!isnull(spawned_id))
+ cassette_data = SScassettes.load_cassette(spawned_id)
+ else if(random)
+ var/list/random_cassette = SScassettes.unique_random_cassettes(amount = 1, status = CASSETTE_STATUS_APPROVED)
+ if(length(random_cassette))
+ cassette_data = random_cassette[1]
+ cassette_data ||= new
+ update_appearance(UPDATE_DESC | UPDATE_ICON_STATE)
- var/list/data = json_decode(file2text(file))
- name = data["name"]
- cassette_desc_string = data["desc"]
- icon_state = data["side1_icon"]
- side1_icon = data["side1_icon"]
- side2_icon = data["side2_icon"]
- songs = data["songs"]
- song_names = data["song_names"]
- author_name = data["author_name"]
- ckey_author = data["author_ckey"]
- approved_tape = data["approved"]
+/obj/item/cassette_tape/Destroy(force)
+ cassette_data = null
+ return ..()
- update_appearance()
-
-/obj/item/device/cassette_tape/attack_self(mob/user)
- ..()
- icon_state = flipped ? side1_icon : side2_icon
+/obj/item/cassette_tape/attack_self(mob/user)
+ . = ..()
flipped = !flipped
to_chat(user, span_notice("You flip [src]."))
+ update_appearance(UPDATE_ICON_STATE)
+
+/obj/item/cassette_tape/update_desc(updates)
+ desc = cassette_data.desc || "A generic cassette."
+ return ..()
-/obj/item/device/cassette_tape/update_desc(updates)
+/obj/item/cassette_tape/update_icon_state()
+ icon_state = cassette_data.get_side(!flipped)?.design || src::icon_state
+ return ..()
+
+/obj/item/cassette_tape/examine(mob/user)
. = ..()
- desc = cassette_desc_string
- desc += "\n"
- if(!approved_tape)
- desc += span_warning("It appears to be a bootleg tape, quality is not a guarantee!\n")
- if(author_name)
- desc += span_notice("Mixed by [author_name]\n")
+ switch(cassette_data.status)
+ if(CASSETTE_STATUS_UNAPPROVED)
+ . += span_warning("It appears to be a bootleg tape, quality is not a guarantee!")
+ . += span_notice("In order to play this tape for the whole station, it must be submitted to the Space Board of Music and approved.")
+ if(CASSETTE_STATUS_REVIEWING)
+ . += span_warning("It seems this tape is still being reviewed by the Space Board of Music.")
+ if(CASSETTE_STATUS_APPROVED)
+ . += span_info("This cassette has been approved by the Space Board of Music, and can be played for the whole station with the Cassette Player.")
+ else
+ stack_trace("Unknown status [cassette_data.status] for cassette [cassette_data.name] ([cassette_data.id])")
-/obj/item/device/cassette_tape/attackby(obj/item/item, mob/living/user)
+ if(cassette_data.author.name)
+ . += span_info("Mixed by [span_name(cassette_data.author.name)]")
+
+/obj/item/cassette_tape/attackby(obj/item/item, mob/living/user)
if(!istype(item, /obj/item/pen))
return ..()
- var/choice = tgui_input_list(usr, "What would you like to change?", items = list("Cassette Name", "Cassette Description", "Cancel"))
+ var/choice = tgui_input_list(user, "What would you like to change?", items = list("Cassette Name", "Cassette Description", "Cancel"))
switch(choice)
if("Cassette Name")
///the name we are giving the cassette
- var/newcassettename = reject_bad_text(tgui_input_text(user, "Write a new Cassette name:", name, name, max_length = MAX_NAME_LEN))
- if(!user.can_perform_action (src, TRUE))
+ var/newcassettename = reject_bad_text(tgui_input_text(user, "Write a new Cassette name:", name, html_decode(name), max_length = MAX_NAME_LEN))
+ if(!user.can_perform_action(src, TRUE))
return
if(length(newcassettename) > MAX_NAME_LEN)
to_chat(user, span_warning("That name is too long!"))
@@ -92,7 +81,7 @@
name = "[lowertext(newcassettename)]"
if("Cassette Description")
///the description we are giving the cassette
- var/newdesc = tgui_input_text(user, "Write a new description:", name, desc, max_length = 180)
+ var/newdesc = tgui_input_text(user, "Write a new description:", name, html_decode(desc), max_length = 180)
if(!user.can_perform_action(src, TRUE))
return
if (length(newdesc) > 180)
@@ -101,29 +90,11 @@
if(!newdesc)
to_chat(user, span_warning("That description is invalid."))
return
- cassette_desc_string = newdesc
- update_appearance()
- else
- return
-
-/datum/cassette/cassette_tape
- var/name = "Broken Cassette"
- var/desc = "You shouldn't be seeing this! Make an issue about it"
- var/icon_state = "cassette_flip"
- var/side1_icon = "cassette_flip"
- var/side2_icon = "cassette_flip"
- var/id = "blank"
- var/creator_ckey = "Dwasint"
- var/creator_name = "Collects-The-Candy"
- var/approved = TRUE
- var/list/song_names = list("side1" = list(),
- "side2" = list())
-
- var/list/songs = list("side1" = list(),
- "side2" = list())
+ cassette_data.desc = newdesc
+ update_appearance(UPDATE_DESC)
-/obj/item/device/cassette_tape/blank
+/obj/item/cassette_tape/blank
id = "blank"
-/obj/item/device/cassette_tape/friday
+/obj/item/cassette_tape/friday
id = "friday"
diff --git a/monkestation/code/modules/cassettes/cassette_approval.dm b/monkestation/code/modules/cassettes/cassette_approval.dm
index e53173d972aa..099307a903e5 100644
--- a/monkestation/code/modules/cassettes/cassette_approval.dm
+++ b/monkestation/code/modules/cassettes/cassette_approval.dm
@@ -1,7 +1,9 @@
+#warn TODO: cassette reviewing/approvals
+/*
GLOBAL_LIST_INIT(cassette_reviews, list())
#define ADMIN_OPEN_REVIEW(id) "(Open Review)"
-/proc/submit_cassette_for_review(obj/item/device/cassette_tape/submitted, mob/user)
+/proc/submit_cassette_for_review(obj/item/cassette_tape/submitted, mob/user)
if(!user.client)
return
var/datum/cassette_review/new_review = new
@@ -25,7 +27,7 @@ GLOBAL_LIST_INIT(cassette_reviews, list())
has requested a review on their cassette."))]")
to_chat(user, span_notice("Your Cassette has been sent to the Space Board of Music for review, you will be notified when an outcome has been made."))
-/obj/item/device/cassette_tape/proc/generate_cassette_json()
+/obj/item/cassette_tape/proc/generate_cassette_json()
if(approved_tape)
return
if(!length(GLOB.approved_ids))
@@ -70,7 +72,7 @@ GLOBAL_LIST_INIT(cassette_reviews, list())
"song_url" = list()
)
)
- var/obj/item/device/cassette_tape/submitted_tape
+ var/obj/item/cassette_tape/submitted_tape
var/action_taken = FALSE
var/verdict = "NONE"
@@ -220,3 +222,4 @@ GLOBAL_LIST_INIT(cassette_reviews, list())
return
+*/
diff --git a/monkestation/code/modules/cassettes/cassette_db/cassette_datum.dm b/monkestation/code/modules/cassettes/cassette_db/cassette_datum.dm
index 07efe51ea95e..3671e96c50c1 100644
--- a/monkestation/code/modules/cassettes/cassette_db/cassette_datum.dm
+++ b/monkestation/code/modules/cassettes/cassette_db/cassette_datum.dm
@@ -1,61 +1,182 @@
-/datum/cassette_data
- var/cassette_name
- var/cassette_author
- var/cassette_desc
- var/cassette_author_ckey
-
- var/cassette_design_front
- var/cassette_design_back
-
- var/list/songs
-
- var/list/song_names
-
- var/cassette_id
- var/approved
- var/file_name
-
-
-/datum/cassette_data/proc/populate_data(file_id)
- var/file = file("data/cassette_storage/[file_id].json")
- if(!fexists(file))
+/datum/cassette
+ /// The unique ID of the cassette.
+ var/id
+ /// The name of the cassette.
+ var/name
+ /// The description of the cassette.
+ var/desc
+ /// The status of this cassette.
+ var/status = CASSETTE_STATUS_UNAPPROVED
+ /// Information about the author of this cassette.
+ var/datum/cassette_author/author
+
+ /// The front side of the cassette.
+ var/datum/cassette_side/front
+ /// The back side of the cassette.
+ var/datum/cassette_side/back
+
+/datum/cassette/New()
+ . = ..()
+ author = new
+ front = new
+ back = new
+
+/datum/cassette/Destroy(force)
+ QDEL_NULL(author)
+ QDEL_NULL(front)
+ QDEL_NULL(back)
+ return ..()
+
+/// Imports cassette date from the old format.
+/datum/cassette/proc/import_old_format(list/data)
+ name = data["name"]
+ desc = data["desc"]
+ if("status" in data)
+ status = data["status"]
+ else
+ status = data["approved"] ? CASSETTE_STATUS_APPROVED : CASSETTE_STATUS_UNAPPROVED
+
+ author.name = data["author_name"]
+ author.ckey = ckey(data["author_ckey"])
+
+ for(var/i in 1 to 2)
+ var/datum/cassette_side/side = get_side(i % 2) // side2 = 0, side1 = 1
+ var/side_name = "side[i]"
+ var/list/song_urls = data["songs"][side_name]
+ var/list/song_names = data["song_names"][side_name]
+ if(length(song_urls) != length(song_names))
+ stack_trace("amount of song urls for [side_name] ([length(song_urls)]) did not match amount of song names for [side_name] ([length(song_names)])")
+ continue
+ side.design = data["[side_name]_icon"]
+ for(var/idx in 1 to length(song_urls))
+ side.songs += new /datum/cassette_song(song_names[idx], song_urls[idx])
+
+/// Exports cassette date in the old format.
+/datum/cassette/proc/export_old_format() as /list
+ RETURN_TYPE(/list)
+ . = list(
+ "name" = name,
+ "desc" = desc,
+ "side1_icon" = /datum/cassette_side::design,
+ "side2_icon" = /datum/cassette_side::design,
+ "author_name" = author.name,
+ "author_ckey" = ckey(author.ckey),
+ "approved" = status == CASSETTE_STATUS_APPROVED,
+ "status" = status,
+ "songs" = list(
+ "side1" = list(),
+ "side2" = list(),
+ ),
+ "song_names" = list(
+ "side1" = list(),
+ "side2" = list(),
+ ),
+ )
+ for(var/i in 1 to 2)
+ var/datum/cassette_side/side = get_side(i % 2) // side2 = 0, side1 = 1
+ var/side_name = "side[i]"
+ var/list/names = list()
+ var/list/urls = list()
+ .["[side_name]_icon"] = side.design
+ for(var/datum/cassette_song/song as anything in side.songs)
+ names += song.name
+ urls += song.url
+ .["song_names"][side_name] = names
+ .["songs"][side_name] = urls
+
+/// Saves the cassette to the data folder, in JSON format.
+/datum/cassette/proc/save_to_file()
+ if(!id)
+ CRASH("Attempted to save cassette without an ID to disk")
+ rustg_file_write(json_encode(export_old_format(), JSON_PRETTY_PRINT), CASSETTE_FILE(id))
+
+/// Saves the cassette to the database.
+/// Returns TRUE if successful, FALSE otherwise.
+/datum/cassette/proc/save_to_db()
+ if(!id)
+ CRASH("Attempted to save cassette without an ID to database")
+ if(!SSdbcore.Connect())
+ CRASH("Could not save cassette [id], database not connected")
+ var/datum/db_query/query_save_cassette = SSdbcore.NewQuery({"
+ INSERT INTO [format_table_name("cassettes")]
+ (id, name, desc, status, author_name, author_ckey, front, back)
+ VALUES
+ (:id, :name, :desc, :status, :author_name, :author_ckey, :front, :back)
+ ON DUPLICATE KEY UPDATE
+ name = VALUES(name),
+ desc = VALUES(desc),
+ status = VALUES(status),
+ author_name = VALUES(author_name),
+ author_ckey = VALUES(author_ckey),
+ front = VALUES(front),
+ back = VALUES(back)
+ "}, list(
+ "id" = id,
+ "name" = name,
+ "desc" = desc,
+ "status" = status,
+ "author_name" = author.name,
+ "author_name" = ckey(author.ckey),
+ "front" = json_encode(front.export_for_db()),
+ "back" = json_encode(back.export_for_db()),
+ ))
+ if(!query_save_cassette.warn_execute())
+ qdel(query_save_cassette)
+ stack_trace("Failed to save cassette [id] to database")
return FALSE
- var/list/data = json_decode(file2text(file))
-
- cassette_name = data["name"]
- cassette_desc = data["desc"]
-
- cassette_design_front = data["side1_icon"]
- cassette_design_back = data["side2_icon"]
-
- songs = data["songs"]
-
- song_names = data["song_names"]
-
- cassette_author = data["author_name"]
- cassette_author_ckey = data["author_ckey"]
-
- cassette_id = file_id
-
- approved = data["approved"]
-
- file_name = "data/cassette_storage/[file_id].json"
-
+ qdel(query_save_cassette)
return TRUE
-/datum/cassette_data/proc/generate_cassette(turf/location)
- if(!location)
- return
- var/obj/item/device/cassette_tape/new_tape = new(location)
- new_tape.name = cassette_name
- new_tape.cassette_desc_string = cassette_desc
- new_tape.icon_state = cassette_design_front
- new_tape.side1_icon = cassette_design_front
- new_tape.side2_icon = cassette_design_back
- new_tape.songs = songs
- new_tape.song_names = song_names
- new_tape.author_name = cassette_author
- new_tape.ckey_author = cassette_author_ckey
- new_tape.approved_tape = approved
-
- new_tape.update_appearance()
+
+/// Simple helper to get a side of the cassette.
+/// TRUE is front side, FALSE is back side.
+/datum/cassette/proc/get_side(front_side = TRUE) as /datum/cassette_side
+ RETURN_TYPE(/datum/cassette_side)
+ return front_side ? front : back
+
+/// Returns a list of all the song names in this cassette.
+/// Really only useful for searching for cassettes via contained song names.
+/datum/cassette/proc/list_song_names() as /list
+ RETURN_TYPE(/list)
+ . = list()
+ for(var/datum/cassette_song/song as anything in front.songs + back.songs)
+ . |= song.name
+
+/datum/cassette_author
+ /// The character name of the cassette author.
+ var/name
+ /// The ckey of the cassette author.
+ var/ckey
+
+/datum/cassette_side
+ /// The design of this side of the cassette.
+ var/design = "cassette_flip"
+ /// The songs on this side of the cassette.
+ var/list/datum/cassette_song/songs = list()
+
+/// Imports data for this cassette side to the JSON format used by the database.
+/datum/cassette_side/proc/import_from_db(list/data)
+ design = data["design"]
+ for(var/list/song as anything in data["songs"])
+ songs += new /datum/cassette_song(song["name"], song["url"])
+
+/// Exports data from this cassette side in the JSON format used by the database.
+/datum/cassette_side/proc/export_for_db()
+ . = list("design" = design, "songs" = list())
+ for(var/datum/cassette_song/song as anything in songs)
+ .["songs"] += list(list("name" = song.name, "url" = song.url))
+
+/datum/cassette_side/Destroy(force)
+ QDEL_LIST(songs)
+ return ..()
+
+/datum/cassette_song
+ /// The name of the song.
+ var/name
+ /// The URL of the song.
+ var/url
+
+/datum/cassette_song/New(name, url)
+ . = ..()
+ src.name = name
+ src.url = url
diff --git a/monkestation/code/modules/cassettes/cassette_db/cassette_manager.dm b/monkestation/code/modules/cassettes/cassette_db/cassette_manager.dm
new file mode 100644
index 000000000000..043786409120
--- /dev/null
+++ b/monkestation/code/modules/cassettes/cassette_db/cassette_manager.dm
@@ -0,0 +1,206 @@
+SUBSYSTEM_DEF(cassettes)
+ name = "Cassetes"
+ init_order = INIT_ORDER_CASSETTES
+ flags = SS_NO_FIRE
+ /// An associative list of IDs to cassette data.
+ var/list/datum/cassette/cassettes = list()
+
+/datum/controller/subsystem/cassettes/Initialize()
+ . = SS_INIT_FAILURE
+ if(CONFIG_GET(flag/cassettes_in_db) && !CONFIG_GET(flag/sql_enabled))
+ stack_trace("CASSETTES_IN_DB was enabled, despite the SQL database not being enabled! Disabling CASSETTES_IN_DB.")
+ CONFIG_SET(flag/cassettes_in_db, FALSE)
+ if(CONFIG_GET(flag/cassettes_in_db))
+ if(!SSdbcore.Connect())
+ CRASH("Database-based cassettes are enabled, but a connection to the database could not be established!")
+ if(!load_all_cassettes_from_db())
+ CRASH("Failed to load all cassettes from database!")
+ else
+ if(!load_all_cassettes_from_json())
+ CRASH("Failed to load all cassettes from data folder!")
+ return SS_INIT_SUCCESS
+
+/datum/controller/subsystem/cassettes/Recover()
+ flags |= SS_NO_INIT
+ cassettes = SScassettes.cassettes
+
+/// Loads the cassette with the given ID.
+/// If `db` is TRUE, it will load the cassette from the database.
+/// If `db` is FALSE, the cassette will be loaded from a JSON in the `data/cassette_storage` folder.
+/// If `db` is null (the default), it will load from the database if the `CASSETTES_IN_DB` config option is set, otherwise it will load from the JSON files.
+/datum/controller/subsystem/cassettes/proc/load_cassette(id, db = null) as /datum/cassette
+ RETURN_TYPE(/datum/cassette)
+ if(!id)
+ return null
+ else if(istype(id, /datum/cassette)) // so i can be lazy
+ return id
+ if(id in cassettes)
+ return cassettes[id]
+ if(isnull(db))
+ db = CONFIG_GET(flag/cassettes_in_db)
+ var/datum/cassette/cassette_data = db ? load_cassette_from_db_raw(id) : load_cassette_from_json_raw(id)
+ if(cassette_data)
+ cassettes[id] = cassette_data
+ return cassette_data
+
+/// Loads the cassette with the given ID from a JSON in the `data/cassette_storage` folder.
+/// This does not check the SScassettes.cassettes cache, and you should not use this - this is only used to initialize SScassettes.cassettes
+/datum/controller/subsystem/cassettes/proc/load_cassette_from_json_raw(id) as /datum/cassette
+ RETURN_TYPE(/datum/cassette)
+ var/cassette_file = CASSETTE_FILE(id)
+ if(!rustg_file_exists(cassette_file))
+ return null
+ var/cassette_file_data = rustg_file_read(cassette_file)
+ if(!rustg_json_is_valid(cassette_file_data))
+ CRASH("Cassette file [cassette_file] had invalid JSON!")
+ var/list/cassette_json = json_decode(cassette_file_data)
+ var/datum/cassette/cassette_data = new
+ cassette_data.import_old_format(cassette_json)
+ cassette_data.id = id
+ return cassette_data
+
+/// Loads the cassette with the given ID from the database.
+/datum/controller/subsystem/cassettes/proc/load_cassette_from_db_raw(id) as /datum/cassette
+ RETURN_TYPE(/datum/cassette)
+ if(!SSdbcore.Connect() || !id)
+ return
+ var/datum/db_query/query_cassette = SSdbcore.NewQuery("SELECT name, desc, status, author_name, author_ckey, front, back FROM [format_table_name("cassettes")] WHERE id = :id", list("id" = id))
+ if(!query_cassette.Execute() || !query_cassette.NextRow())
+ qdel(query_cassette)
+ return
+ var/name = query_cassette.item[1]
+ var/desc = query_cassette.item[2]
+ var/status = query_cassette.item[3]
+ var/author_name = query_cassette.item[4]
+ var/author_ckey = query_cassette.item[5]
+ var/list/front = json_decode(query_cassette.item[6])
+ var/list/back = json_decode(query_cassette.item[7])
+ qdel(query_cassette)
+
+ var/datum/cassette/cassette = new
+ cassette.id = id
+ cassette.name = name
+ cassette.desc = desc
+ cassette.status = status
+ cassette.author.name = author_name
+ cassette.author.ckey = author_ckey
+ cassette.front.import_from_db(front)
+ cassette.back.import_from_db(back)
+ return cassette
+
+/// Returns an associative list of id to cassette datums, of all existing saved cassettes.
+/// This uses the database.
+/datum/controller/subsystem/cassettes/proc/load_all_cassettes_from_db()
+ . = FALSE
+ if(!SSdbcore.Connect())
+ CRASH("Failed to connect to database")
+ var/datum/db_query/query_cassettes = SSdbcore.NewQuery("SELECT id, name, desc, status, author_name, author_ckey, front, back FROM [format_table_name("cassettes")]")
+ if(!query_cassettes.Execute())
+ qdel(query_cassettes)
+ CRASH("Failed to load cassettes from database")
+ while(query_cassettes.NextRow())
+ var/id = query_cassettes.item[1]
+ var/name = query_cassettes.item[2]
+ var/desc = query_cassettes.item[3]
+ var/status = query_cassettes.item[4]
+ var/author_name = query_cassettes.item[5]
+ var/author_ckey = query_cassettes.item[6]
+ var/list/front = json_decode(query_cassettes.item[7])
+ var/list/back = json_decode(query_cassettes.item[8])
+
+ var/datum/cassette/cassette = new
+ cassette.id = id
+ cassette.name = name
+ cassette.desc = desc
+ cassette.status = status
+ cassette.author.name = author_name
+ cassette.author.ckey = author_ckey
+ cassette.front.import_from_db(front)
+ cassette.back.import_from_db(back)
+
+ cassettes[id] = cassette
+ qdel(query_cassettes)
+ return TRUE
+
+/// Returns an associative list of id to cassette datums, of all existing saved cassettes.
+/// This uses JSON files.
+/datum/controller/subsystem/cassettes/proc/load_all_cassettes_from_json()
+ . = FALSE
+ if(!rustg_file_exists(CASSETTE_ID_FILE)) // this just means there's no cassettes at all i guess? which is valid.
+ return TRUE
+ var/list/ids = json_decode(rustg_file_read(CASSETTE_ID_FILE))
+ for(var/id in ids)
+ if(!ids)
+ continue
+ var/datum/cassette/cassette_data = load_cassette_from_json_raw(id)
+ if(isnull(cassette_data))
+ stack_trace("Failed to load cassette [id]")
+ continue
+ cassettes[id] = cassette_data
+ return TRUE
+
+/// Updates the ids.json file on-disk.
+/datum/controller/subsystem/cassettes/proc/save_ids_json()
+ var/list/ids = list()
+ if(rustg_file_exists(CASSETTE_ID_FILE))
+ // Verify that each cassette ID still exists and is still considered "approved" before adding them to the list.
+ for(var/id in json_decode(rustg_file_read(CASSETTE_ID_FILE)))
+ if(!rustg_file_exists(CASSETTE_FILE(id)))
+ continue
+ ids += id
+ for(var/id in cassettes)
+ var/datum/cassette/cassette = cassettes[id]
+ if(cassette.status == CASSETTE_STATUS_UNAPPROVED)
+ ids -= id
+ else
+ ids |= id
+ rustg_file_write(ids, CASSETTE_ID_FILE)
+
+/// Returns all the cassettes that match the given arguments.
+/datum/controller/subsystem/cassettes/proc/filtered_cassettes(status, user_ckey, list/id_blacklist) as /list
+ RETURN_TYPE(/list/datum/cassette)
+ . = list()
+ if(!isnull(user_ckey))
+ user_ckey = ckey(user_ckey)
+ for(var/id in cassettes)
+ if(!isnull(id_blacklist) && (id in id_blacklist))
+ continue
+ var/datum/cassette/cassette = cassettes[id]
+ if(!isnull(user_ckey) && ckey(cassette.author.ckey) != user_ckey)
+ continue
+ if(!isnull(status) && cassette.status != status)
+ continue
+ . += cassette
+
+/// Returns a list containing up to the specified amount of random, unique cassettes that match the given arguments.
+/datum/controller/subsystem/cassettes/proc/unique_random_cassettes(amount = 1, status = CASSETTE_STATUS_APPROVED, user_ckey, list/id_blacklist) as /list
+ RETURN_TYPE(/list/datum/cassette)
+ . = list()
+ var/list/cassettes = filtered_cassettes(status, user_ckey, id_blacklist)
+ for(var/i in min(amount, length(cassettes)))
+ . += pick_n_take(cassettes)
+
+/datum/controller/subsystem/cassettes/proc/migrate_json_cassettes_to_db()
+ if(!SSdbcore.Connect())
+ CRASH("Cannot migrate JSON cassettes to the database if we can't even connect to the database!")
+ var/list/old_cassettes = cassettes.Copy()
+ cassettes.Cut()
+ if(!load_all_cassettes_from_json())
+ cassettes = old_cassettes
+ CRASH("Failed to load cassettes from JSON")
+ var/list/sql_cassettes = list()
+ for(var/id in cassettes)
+ var/datum/cassette/cassette = cassettes[id]
+ sql_cassettes += list(list(
+ "id" = id,
+ "name" = cassette.name,
+ "desc" = cassette.desc,
+ "status" = cassette.status,
+ "author_name" = cassette.author.name,
+ "author_ckey" = ckey(cassette.author.ckey),
+ "front" = cassette.front.export_for_db(),
+ "back" = cassette.back.export_for_db(),
+ ))
+ if(!length(sql_cassettes))
+ return
+ SSdbcore.MassInsert(format_table_name("cassettes"), sql_cassettes, duplicate_key = TRUE, warn = TRUE)
diff --git a/monkestation/code/modules/cassettes/cassette_db/subsystem.dm b/monkestation/code/modules/cassettes/cassette_db/subsystem.dm
deleted file mode 100644
index bbca8076a20c..000000000000
--- a/monkestation/code/modules/cassettes/cassette_db/subsystem.dm
+++ /dev/null
@@ -1,30 +0,0 @@
-SUBSYSTEM_DEF(cassette_storage)
- name = "Cassette Storage"
- flags = SS_NO_FIRE
- runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
- var/list/cassette_datums = list()
-
-
-/datum/controller/subsystem/cassette_storage/Initialize()
- if(!length(GLOB.approved_ids))
- GLOB.approved_ids = initialize_approved_ids()
- generate_cassette_datums()
- return SS_INIT_SUCCESS
-
-/datum/controller/subsystem/cassette_storage/proc/generate_cassette_datums()
- for(var/id in GLOB.approved_ids)
- var/datum/cassette_data/new_data = new
- if(!new_data.populate_data(id))
- qdel(new_data)
- continue
- cassette_datums += new_data
-
-/datum/controller/subsystem/cassette_storage/proc/get_cassettes_by_ckey(user_ckey) as /list
- RETURN_TYPE(/list)
- . = list()
- if(!user_ckey)
- return
- user_ckey = ckey(user_ckey)
- for(var/datum/cassette_data/tape as anything in SScassette_storage.cassette_datums)
- if(ckey(tape.cassette_author_ckey) == user_ckey)
- . += tape
diff --git a/monkestation/code/modules/cassettes/dj/dj_music_field.dm b/monkestation/code/modules/cassettes/dj/dj_music_field.dm
new file mode 100644
index 000000000000..7ae01f489138
--- /dev/null
+++ b/monkestation/code/modules/cassettes/dj/dj_music_field.dm
@@ -0,0 +1,48 @@
+/// A proximity monitor field that allows mobs near objects to hear DJ music.
+/datum/proximity_monitor/advanced/dj_music
+ edge_is_a_field = TRUE
+ /// List of mobs that can currently hear music from this field.
+ var/list/mob/listeners
+
+/datum/proximity_monitor/advanced/dj_music/Destroy()
+ for(var/mob/listener as anything in listeners)
+ remove_mob(listener)
+ return ..()
+
+/datum/proximity_monitor/advanced/dj_music/field_turf_crossed(atom/movable/crosser, turf/old_location, turf/new_location)
+ if(isliving(crosser))
+ add_mob(crosser)
+ var/list/hearing_contents = crosser.important_recursive_contents?[RECURSIVE_CONTENTS_HEARING_SENSITIVE]
+ for(var/mob/living/target in hearing_contents)
+ add_mob(target)
+
+/datum/proximity_monitor/advanced/dj_music/field_turf_uncrossed(atom/movable/crosser, turf/old_location, turf/new_location)
+ if(isliving(crosser))
+ remove_mob(crosser)
+ var/list/hearing_contents = crosser.important_recursive_contents?[RECURSIVE_CONTENTS_HEARING_SENSITIVE]
+ for(var/mob/living/target in hearing_contents)
+ remove_mob(target)
+
+/datum/proximity_monitor/advanced/dj_music/setup_field_turf(turf/target)
+ for(var/atom/movable/thing in target)
+ if(isliving(thing) || length(thing.important_recursive_contents?[RECURSIVE_CONTENTS_HEARING_SENSITIVE]))
+ field_turf_crossed(thing)
+
+/datum/proximity_monitor/advanced/dj_music/cleanup_field_turf(turf/target)
+ for(var/atom/movable/thing in target)
+ if(isliving(thing) || length(thing.important_recursive_contents?[RECURSIVE_CONTENTS_HEARING_SENSITIVE]))
+ field_turf_uncrossed(thing)
+
+/datum/proximity_monitor/advanced/dj_music/proc/add_mob(mob/living/target)
+ if(QDELING(src) || !isliving(target) || QDELING(target) || HAS_TRAIT_FROM(target, TRAIT_CAN_HEAR_MUSIC, INNATE_TRAIT) || (target in listeners))
+ return
+ LAZYADD(listeners, target)
+ ADD_TRAIT(target, TRAIT_CAN_HEAR_MUSIC, REF(src))
+ RegisterSignal(target, COMSIG_QDELETING, PROC_REF(remove_mob))
+
+/datum/proximity_monitor/advanced/dj_music/proc/remove_mob(mob/living/target)
+ if(!isliving(target) || !(target in listeners))
+ return
+ LAZYREMOVE(listeners, target)
+ REMOVE_TRAIT(target, TRAIT_CAN_HEAR_MUSIC, REF(src))
+ UnregisterSignal(target, COMSIG_QDELETING)
diff --git a/monkestation/code/modules/cassettes/dj/intercom.dm b/monkestation/code/modules/cassettes/dj/intercom.dm
new file mode 100644
index 000000000000..5c12cc6cd5ac
--- /dev/null
+++ b/monkestation/code/modules/cassettes/dj/intercom.dm
@@ -0,0 +1,13 @@
+/obj/item/radio/intercom
+ /// The proximity monitor used to allow people to hear DJ music while in hearing range.
+ var/datum/proximity_monitor/advanced/dj_music/music_field
+
+/obj/item/radio/intercom/Initialize(mapload, ndir, building)
+ . = ..()
+ var/range = isnull(listening_range) ? canhear_range : listening_range
+ if(isturf(loc) && range > 0 && (is_station_level(loc.z) || is_centcom_level(loc.z)))
+ music_field = new(src, range)
+
+/obj/item/radio/intercom/Destroy()
+ QDEL_NULL(music_field)
+ return ..()
diff --git a/monkestation/code/modules/cassettes/dj/mob_can_hear.dm b/monkestation/code/modules/cassettes/dj/mob_can_hear.dm
new file mode 100644
index 000000000000..4c09e1cd1406
--- /dev/null
+++ b/monkestation/code/modules/cassettes/dj/mob_can_hear.dm
@@ -0,0 +1,29 @@
+/// A list of all mobs that can hear music.
+GLOBAL_LIST_EMPTY_TYPED(music_listeners, /mob)
+
+/mob/Initialize(mapload)
+ . = ..()
+ RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_CAN_HEAR_MUSIC), PROC_REF(on_can_hear_music_trait_gain))
+ RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_CAN_HEAR_MUSIC), PROC_REF(on_can_hear_music_trait_loss))
+
+ // just in case we already have the trait
+ if(HAS_TRAIT(src, TRAIT_CAN_HEAR_MUSIC))
+ on_can_hear_music_trait_gain(src)
+
+/mob/Destroy(force)
+ on_can_hear_music_trait_loss(src)
+ return ..()
+
+/mob/proc/on_can_hear_music_trait_gain(datum/source)
+ SIGNAL_HANDLER
+ if(src in GLOB.music_listeners)
+ return
+ GLOB.music_listeners += src
+ SEND_GLOBAL_SIGNAL(COMSIG_GLOB_ADD_MUSIC_LISTENER, src)
+
+/mob/proc/on_can_hear_music_trait_loss(datum/source)
+ SIGNAL_HANDLER
+ if(!(src in GLOB.music_listeners))
+ return
+ GLOB.music_listeners -= src
+ SEND_GLOBAL_SIGNAL(COMSIG_GLOB_REMOVE_MUSIC_LISTENER, src)
diff --git a/monkestation/code/modules/cassettes/machines/cassette_rack.dm b/monkestation/code/modules/cassettes/machines/cassette_rack.dm
index 9abbeabb0371..f6c683baadba 100644
--- a/monkestation/code/modules/cassettes/machines/cassette_rack.dm
+++ b/monkestation/code/modules/cassettes/machines/cassette_rack.dm
@@ -29,7 +29,7 @@
/datum/storage/cassette_rack/New()
. = ..()
- set_holdable(/obj/item/device/cassette_tape)
+ set_holdable(/obj/item/cassette_tape)
// Allow opening on a normal left click
/datum/storage/cassette_rack/on_attack(datum/source, mob/user)
@@ -50,9 +50,9 @@
REGISTER_REQUIRED_MAP_ITEM(1, INFINITY)
RegisterSignal(SSdcs, COMSIG_GLOB_CREWMEMBER_JOINED, PROC_REF(spawn_curator_tapes))
for(var/i in 1 to spawn_blanks)
- new /obj/item/device/cassette_tape/blank(src)
+ new /obj/item/cassette_tape/blank(src)
for(var/id in unique_random_tapes(spawn_random))
- new /obj/item/device/cassette_tape(src, id)
+ new /obj/item/cassette_tape(src, id)
update_appearance()
/obj/structure/cassette_rack/prefilled/Destroy()
@@ -68,23 +68,17 @@
add_user_tapes(new_crewmember.ckey)
/obj/structure/cassette_rack/prefilled/proc/add_user_tapes(user_ckey, max_amt = 3, expand_max_size = TRUE)
- var/list/user_tapes = SScassette_storage.get_cassettes_by_ckey(user_ckey)
- if(!length(user_tapes))
- return FALSE
- var/list/existing_tapes = list()
- for(var/obj/item/device/cassette_tape/tape in src)
- if(tape.id)
- existing_tapes[tape.id] = TRUE
- for(var/iter in 1 to max_amt)
- if(!length(user_tapes))
- break
- var/datum/cassette_data/tape = pick_n_take(user_tapes)
- if(existing_tapes[tape.cassette_id])
- continue
- new /obj/item/device/cassette_tape(src, tape.cassette_id)
- if(expand_max_size && !QDELETED(atom_storage))
- atom_storage.max_slots += max_amt
- atom_storage.max_total_storage += max_amt * WEIGHT_CLASS_SMALL
+ var/list/existing_cassettes = list()
+ for(var/obj/item/cassette_tape/tape in src)
+ if(tape.cassette_data.id)
+ existing_cassettes |= tape.cassette_data.id
+ var/amount_spawned = 0
+ for(var/datum/cassette/cassette as anything in SScassettes.unique_random_cassettes(max_amt, CASSETTE_STATUS_APPROVED, user_ckey, existing_cassettes))
+ new /obj/item/cassette_tape(src, cassette)
+ amount_spawned++
+ if(expand_max_size && !QDELETED(atom_storage) && amount_spawned > 0)
+ atom_storage.max_slots += amount_spawned
+ atom_storage.max_total_storage += amount_spawned * WEIGHT_CLASS_SMALL
return TRUE
#undef DEFAULT_BLANKS_TO_SPAWN
diff --git a/monkestation/code/modules/cassettes/machines/dj_station.dm b/monkestation/code/modules/cassettes/machines/dj_station.dm
index 386de43c5f57..a7742247f810 100644
--- a/monkestation/code/modules/cassettes/machines/dj_station.dm
+++ b/monkestation/code/modules/cassettes/machines/dj_station.dm
@@ -1,6 +1,5 @@
GLOBAL_VAR(dj_broadcast)
-GLOBAL_VAR(dj_booth)
-
+GLOBAL_DATUM(dj_booth, /obj/machinery/cassette/dj_station)
/obj/item/clothing/ears
//can we be used to listen to radio?
@@ -8,42 +7,35 @@ GLOBAL_VAR(dj_booth)
/obj/machinery/cassette/dj_station
name = "Cassette Player"
- desc = "Plays Space Music Board approved cassettes for anyone in the station to listen to "
+ desc = "Plays Space Music Board approved cassettes for anyone in the station to listen to."
icon = 'monkestation/code/modules/cassettes/icons/radio_station.dmi'
icon_state = "cassette_player"
- active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION
+ use_power = NO_POWER_USE
+
+ resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
+ move_resist = MOVE_FORCE_OVERPOWERING
- resistance_flags = INDESTRUCTIBLE
anchored = TRUE
density = TRUE
- var/broadcasting = FALSE
- var/obj/item/device/cassette_tape/inserted_tape
- var/time_left = 0
- var/current_song_duration = 0
- var/list/people_with_signals = list()
- var/list/active_listeners = list()
- var/waiting_for_yield = FALSE
-
- //tape stuff goes here
- var/pl_index = 0
- var/list/current_playlist = list()
- var/list/current_namelist = list()
+ var/broadcasting = FALSE
COOLDOWN_DECLARE(next_song_timer)
/obj/machinery/cassette/dj_station/Initialize(mapload)
. = ..()
REGISTER_REQUIRED_MAP_ITEM(1, INFINITY)
- GLOB.dj_booth = src
register_context()
+ if(QDELETED(GLOB.dj_booth))
+ GLOB.dj_booth = src
/obj/machinery/cassette/dj_station/Destroy()
- . = ..()
- GLOB.dj_booth = null
- STOP_PROCESSING(SSprocessing, src)
+ if(GLOB.dj_booth == src)
+ GLOB.dj_booth = null
+ return ..()
+/*
/obj/machinery/cassette/dj_station/add_context(atom/source, list/context, obj/item/held_item, mob/user)
. = ..()
if(inserted_tape)
@@ -51,327 +43,4 @@ GLOBAL_VAR(dj_booth)
if(!broadcasting)
context[SCREENTIP_CONTEXT_LMB] = "Play Tape"
return CONTEXTUAL_SCREENTIP_SET
-
-/obj/machinery/cassette/dj_station/examine(mob/user)
- . = ..()
- if(time_left > 0 || next_song_timer)
- . += span_notice("It seems to be cooling down, you estimate it will take about [time_left ? DisplayTimeText(((time_left * 10) + 6000)) : DisplayTimeText(COOLDOWN_TIMELEFT(src, next_song_timer))].")
-
-/obj/machinery/cassette/dj_station/process(seconds_per_tick)
- if(waiting_for_yield)
- return
- time_left -= round(seconds_per_tick)
- if(time_left <= 0)
- time_left = 0
- if(COOLDOWN_FINISHED(src, next_song_timer) && broadcasting)
- COOLDOWN_START(src, next_song_timer, 10 MINUTES)
- broadcasting = FALSE
-
-/obj/machinery/cassette/dj_station/attack_hand(mob/user)
- . = ..()
- if(!inserted_tape)
- return
- if((!COOLDOWN_FINISHED(src, next_song_timer)) && !broadcasting)
- to_chat(user, span_notice("The [src] feels hot to the touch and needs time to cooldown."))
- to_chat(user, span_info("You estimate it will take about [time_left ? DisplayTimeText(((time_left * 10) + 6000)) : DisplayTimeText(COOLDOWN_TIMELEFT(src, next_song_timer))] to cool down."))
- return
- message_admins("[src] started broadcasting [inserted_tape] interacted with by [user]")
- logger.Log(LOG_CATEGORY_MUSIC, "[src] started broadcasting [inserted_tape]")
- start_broadcast()
-
-/obj/machinery/cassette/dj_station/AltClick(mob/user)
- . = ..()
- if(!isliving(user) || !user.Adjacent(src))
- return
- if(!inserted_tape)
- return
- if(broadcasting)
- next_song()
-
-/obj/machinery/cassette/dj_station/CtrlClick(mob/user)
- . = ..()
- if(!inserted_tape || broadcasting)
- return
- if(Adjacent(user) && !issiliconoradminghost(user))
- if(!user.put_in_hands(inserted_tape))
- inserted_tape.forceMove(drop_location())
- else
- inserted_tape.forceMove(drop_location())
- inserted_tape = null
- time_left = 0
- current_song_duration = 0
- pl_index = 0
- current_playlist = list()
- current_namelist = list()
- stop_broadcast(TRUE)
-
-/obj/machinery/cassette/dj_station/attackby(obj/item/weapon, mob/user, params)
- if(!istype(weapon, /obj/item/device/cassette_tape))
- return
- var/obj/item/device/cassette_tape/attacked = weapon
- if(!attacked.approved_tape)
- to_chat(user, span_warning("The [src] smartly rejects the bootleg cassette tape"))
- return
- if(!inserted_tape)
- insert_tape(attacked)
- else
- if(!broadcasting)
- if(Adjacent(user) && !issiliconoradminghost(user))
- if(!user.put_in_hands(inserted_tape))
- inserted_tape.forceMove(drop_location())
- else
- inserted_tape.forceMove(drop_location())
- inserted_tape = null
- time_left = 0
- current_song_duration = 0
- pl_index = 0
- current_playlist = list()
- current_namelist = list()
- insert_tape(attacked)
- if(broadcasting)
- stop_broadcast(TRUE)
-
-/obj/machinery/cassette/dj_station/proc/insert_tape(obj/item/device/cassette_tape/CTape)
- if(inserted_tape || !istype(CTape))
- return
-
- inserted_tape = CTape
- CTape.forceMove(src)
-
- update_appearance()
- pl_index = 1
- if(inserted_tape.songs["side1"] && inserted_tape.songs["side2"])
- var/list/list = inserted_tape.songs["[inserted_tape.flipped ? "side2" : "side1"]"]
- for(var/song in list)
- current_playlist += song
-
- var/list/name_list = inserted_tape.song_names["[inserted_tape.flipped ? "side2" : "side1"]"]
- for(var/song in name_list)
- current_namelist += song
-
-/obj/machinery/cassette/dj_station/proc/stop_broadcast(soft = FALSE)
- STOP_PROCESSING(SSprocessing, src)
- GLOB.dj_broadcast = FALSE
- broadcasting = FALSE
- message_admins("[src] has stopped broadcasting [inserted_tape].")
- logger.Log(LOG_CATEGORY_MUSIC, "[src] has stopped broadcasting [inserted_tape]")
- for(var/client/anything as anything in active_listeners)
- if(!istype(anything))
- continue
- anything.tgui_panel?.stop_music()
- GLOB.youtube_exempt["dj-station"] -= anything
- active_listeners = list()
-
- if(!soft)
- for(var/mob/living/carbon/anything as anything in people_with_signals)
- if(!istype(anything))
- continue
- UnregisterSignal(anything, COMSIG_CARBON_UNEQUIP_EARS)
- UnregisterSignal(anything, COMSIG_CARBON_EQUIP_EARS)
- UnregisterSignal(anything, COMSIG_MOVABLE_Z_CHANGED)
- people_with_signals = list()
-
-/obj/machinery/cassette/dj_station/proc/start_broadcast()
- var/choice = tgui_input_list(usr, "Choose which song to play.", "[src]", current_namelist)
- if(!choice)
- return
- var/list_index = current_namelist.Find(choice)
- if(!list_index)
- return
- GLOB.dj_broadcast = TRUE
- pl_index = list_index
-
- var/list/viable_z = SSmapping.levels_by_any_trait(list(ZTRAIT_STATION, ZTRAIT_MINING, ZTRAIT_CENTCOM, ZTRAIT_RESERVED))
- for(var/mob/person as anything in GLOB.player_list)
- if(issilicon(person) || isobserver(person) || isaicamera(person) || isbot(person))
- active_listeners |= person.client
- continue
- if(iscarbon(person))
- var/mob/living/carbon/anything = person
- if(!(anything in people_with_signals))
- if(!istype(anything))
- continue
-
- RegisterSignal(anything, COMSIG_CARBON_UNEQUIP_EARS, PROC_REF(stop_solo_broadcast))
- RegisterSignal(anything, COMSIG_CARBON_EQUIP_EARS, PROC_REF(check_solo_broadcast))
- RegisterSignal(anything, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(check_solo_broadcast))
- people_with_signals |= anything
-
- if(!(anything.client in active_listeners))
- if(!(anything.z in viable_z))
- continue
-
- if(!anything.client)
- continue
-
- if(anything.client in GLOB.youtube_exempt["walkman"])
- continue
-
- var/obj/item/ear_slot = anything.get_item_by_slot(ITEM_SLOT_EARS)
- if(istype(ear_slot, /obj/item/clothing/ears))
- var/obj/item/clothing/ears/worn
- if(!worn || !worn?.radio_compat)
- continue
- else if(!istype(ear_slot, /obj/item/radio/headset))
- continue
-
- if(!anything.client.prefs?.read_preference(/datum/preference/toggle/hear_music))
- continue
-
- active_listeners |= anything.client
-
- if(!length(active_listeners))
- return
-
- start_playing(active_listeners)
- START_PROCESSING(SSprocessing, src)
-
-
-/obj/machinery/cassette/dj_station/proc/check_solo_broadcast(mob/living/carbon/source, obj/item/clothing/ears/ear_item)
- SIGNAL_HANDLER
-
- if(!istype(source))
- return
-
- if(istype(ear_item, /obj/item/clothing/ears))
- var/obj/item/clothing/ears/worn
- if(!worn || !worn?.radio_compat)
- return
- else if(!istype(ear_item, /obj/item/radio/headset))
- return
-
- var/list/viable_z = SSmapping.levels_by_any_trait(list(ZTRAIT_STATION, ZTRAIT_MINING, ZTRAIT_CENTCOM))
- if(!(source.z in viable_z) || !source.client)
- return
-
- if(!source.client.prefs?.read_preference(/datum/preference/toggle/hear_music))
- return
-
- active_listeners |= source.client
- GLOB.youtube_exempt["dj-station"] |= source.client
- INVOKE_ASYNC(src, PROC_REF(start_playing),list(source.client))
-
-/obj/machinery/cassette/dj_station/proc/stop_solo_broadcast(mob/living/carbon/source)
- SIGNAL_HANDLER
-
- if(!source.client || !(source.client in active_listeners))
- return
-
- active_listeners -= source.client
- GLOB.youtube_exempt["dj-station"] -= source.client
- source.client.tgui_panel?.stop_music()
-
-/obj/machinery/cassette/dj_station/proc/start_playing(list/clients)
- if(!inserted_tape)
- if(broadcasting)
- stop_broadcast(TRUE)
- return
-
- waiting_for_yield = TRUE
- if(findtext(current_playlist[pl_index], GLOB.is_http_protocol))
- ///invoking youtube-dl
- var/ytdl = CONFIG_GET(string/invoke_youtubedl)
- ///the input for ytdl handled by the song list
- var/web_sound_input
- ///the url for youtube-dl
- var/web_sound_url = ""
- ///all extra data from the youtube-dl really want the name
- var/list/music_extra_data = list()
- web_sound_input = trim(current_playlist[pl_index])
- if(!(web_sound_input in GLOB.parsed_audio))
- ///scrubbing the input before putting it in the shell
- var/shell_scrubbed_input = shell_url_scrub(web_sound_input)
- ///putting it in the shell
- var/list/output = world.shelleo("[ytdl] --geo-bypass --format \"bestaudio\[ext=mp3]/best\[ext=mp4]\[height <= 360]/bestaudio\[ext=m4a]/bestaudio\[ext=aac]\" --dump-single-json --no-playlist --extractor-args \"youtube:lang=en\" -- \"[shell_scrubbed_input]\"")
- ///any errors
- var/errorlevel = output[SHELLEO_ERRORLEVEL]
- ///the standard output
- var/stdout = output[SHELLEO_STDOUT]
- if(!errorlevel)
- ///list for all the output data to go to
- var/list/data
- try
- data = json_decode(stdout)
- catch(var/exception/error) ///catch errors here
- to_chat(src, "Youtube-dl JSON parsing FAILED:", confidential = TRUE)
- to_chat(src, "[error]: [stdout]", confidential = TRUE)
- return
-
- if (data["url"])
- web_sound_url = data["url"]
- music_extra_data["start"] = data["start_time"]
- music_extra_data["end"] = data["end_time"]
- music_extra_data["link"] = data["webpage_url"]
- music_extra_data["title"] = data["title"]
- if(music_extra_data["start"])
- time_left = data["duration"] - music_extra_data["start"]
- else
- time_left = data["duration"]
-
- current_song_duration = data["duration"]
-
- GLOB.parsed_audio["[web_sound_input]"] = data
- else
- var/list/data = GLOB.parsed_audio["[web_sound_input]"]
- web_sound_url = data["url"]
- music_extra_data["start"] = data["start_time"]
- music_extra_data["end"] = data["end_time"]
- music_extra_data["link"] = data["webpage_url"]
- music_extra_data["title"] = data["title"]
- if(time_left <= 0)
- if(music_extra_data["start"])
- time_left = data["duration"] - music_extra_data["start"]
- else
- time_left = data["duration"]
-
- current_song_duration = data["duration"]
- music_extra_data["duration"] = data["duration"]
-
- if(time_left > 0)
- music_extra_data["start"] = music_extra_data["duration"] - time_left
-
- for(var/client/anything as anything in clients)
- if(!istype(anything))
- continue
- anything.tgui_panel?.play_music(web_sound_url, music_extra_data)
- GLOB.youtube_exempt["dj-station"] |= anything
- broadcasting = TRUE
- waiting_for_yield = FALSE
-
-/obj/machinery/cassette/dj_station/proc/add_new_player(mob/living/carbon/new_player)
- if(!(new_player in people_with_signals))
- RegisterSignal(new_player, COMSIG_CARBON_UNEQUIP_EARS, PROC_REF(stop_solo_broadcast))
- RegisterSignal(new_player, COMSIG_CARBON_EQUIP_EARS, PROC_REF(check_solo_broadcast))
- RegisterSignal(new_player, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(check_solo_broadcast))
- people_with_signals |= new_player
-
- if(!broadcasting)
- return
-
- var/obj/item/ear_slot = new_player.get_item_by_slot(ITEM_SLOT_EARS)
- if(istype(ear_slot, /obj/item/clothing/ears))
- var/obj/item/clothing/ears/worn
- if(!worn || !worn?.radio_compat)
- return
- else if(!istype(ear_slot, /obj/item/radio/headset))
- return
- var/list/viable_z = SSmapping.levels_by_any_trait(list(ZTRAIT_STATION, ZTRAIT_MINING, ZTRAIT_CENTCOM))
- if(!(new_player.z in viable_z))
- return
-
- if(!(new_player.client in active_listeners))
- active_listeners |= new_player.client
- start_playing(list(new_player.client))
-
-/obj/machinery/cassette/dj_station/proc/next_song()
- waiting_for_yield = TRUE
- var/choice = tgui_input_number(usr, "Choose which song number to play.", "[src]", 1, length(current_playlist), 1)
- if(!choice)
- waiting_for_yield = FALSE
- stop_broadcast()
- return
- GLOB.dj_broadcast = TRUE
- pl_index = choice
-
- pl_index++
- start_playing(active_listeners)
+*/
diff --git a/monkestation/code/modules/cassettes/machines/dj_station_old.dm b/monkestation/code/modules/cassettes/machines/dj_station_old.dm
new file mode 100644
index 000000000000..f9b24f679fc8
--- /dev/null
+++ b/monkestation/code/modules/cassettes/machines/dj_station_old.dm
@@ -0,0 +1,377 @@
+GLOBAL_VAR(dj_broadcast)
+GLOBAL_DATUM(dj_booth, /obj/machinery/cassette/dj_station)
+
+/obj/item/clothing/ears
+ //can we be used to listen to radio?
+ var/radio_compat = FALSE
+
+/obj/machinery/cassette/dj_station
+ name = "Cassette Player"
+ desc = "Plays Space Music Board approved cassettes for anyone in the station to listen to."
+
+ icon = 'monkestation/code/modules/cassettes/icons/radio_station.dmi'
+ icon_state = "cassette_player"
+
+ active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION
+
+ resistance_flags = INDESTRUCTIBLE
+ anchored = TRUE
+ density = TRUE
+ var/broadcasting = FALSE
+ var/obj/item/cassette_tape/inserted_tape
+ var/time_left = 0
+ var/current_song_duration = 0
+ var/list/people_with_signals = list()
+ var/list/active_listeners = list()
+ var/waiting_for_yield = FALSE
+
+ //tape stuff goes here
+ var/pl_index = 0
+ var/list/current_playlist = list()
+ var/list/current_namelist = list()
+
+ COOLDOWN_DECLARE(next_song_timer)
+
+/obj/machinery/cassette/dj_station/Initialize(mapload)
+ . = ..()
+ REGISTER_REQUIRED_MAP_ITEM(1, INFINITY)
+ register_context()
+ if(QDELETED(GLOB.dj_booth))
+ GLOB.dj_booth = src
+
+/obj/machinery/cassette/dj_station/Destroy()
+ if(GLOB.dj_booth == src)
+ GLOB.dj_booth = null
+ return ..()
+
+/obj/machinery/cassette/dj_station/add_context(atom/source, list/context, obj/item/held_item, mob/user)
+ . = ..()
+ if(inserted_tape)
+ context[SCREENTIP_CONTEXT_CTRL_LMB] = "Eject Tape"
+ if(!broadcasting)
+ context[SCREENTIP_CONTEXT_LMB] = "Play Tape"
+ return CONTEXTUAL_SCREENTIP_SET
+
+/obj/machinery/cassette/dj_station/examine(mob/user)
+ . = ..()
+ if(time_left > 0 || next_song_timer)
+ . += span_notice("It seems to be cooling down, you estimate it will take about [time_left ? DisplayTimeText(((time_left * 10) + 6000)) : DisplayTimeText(COOLDOWN_TIMELEFT(src, next_song_timer))].")
+
+/obj/machinery/cassette/dj_station/process(seconds_per_tick)
+ if(waiting_for_yield)
+ return
+ time_left -= round(seconds_per_tick)
+ if(time_left <= 0)
+ time_left = 0
+ if(COOLDOWN_FINISHED(src, next_song_timer) && broadcasting)
+ COOLDOWN_START(src, next_song_timer, 10 MINUTES)
+ broadcasting = FALSE
+
+/obj/machinery/cassette/dj_station/attack_hand(mob/user)
+ . = ..()
+ if(!inserted_tape)
+ return
+ if((!COOLDOWN_FINISHED(src, next_song_timer)) && !broadcasting)
+ to_chat(user, span_notice("The [src] feels hot to the touch and needs time to cooldown."))
+ to_chat(user, span_info("You estimate it will take about [time_left ? DisplayTimeText(((time_left * 10) + 6000)) : DisplayTimeText(COOLDOWN_TIMELEFT(src, next_song_timer))] to cool down."))
+ return
+ message_admins("[src] started broadcasting [inserted_tape] interacted with by [user]")
+ logger.Log(LOG_CATEGORY_MUSIC, "[src] started broadcasting [inserted_tape]")
+ start_broadcast()
+
+/obj/machinery/cassette/dj_station/AltClick(mob/user)
+ . = ..()
+ if(!isliving(user) || !user.Adjacent(src))
+ return
+ if(!inserted_tape)
+ return
+ if(broadcasting)
+ next_song()
+
+/obj/machinery/cassette/dj_station/CtrlClick(mob/user)
+ . = ..()
+ if(!inserted_tape || broadcasting)
+ return
+ if(Adjacent(user) && !issiliconoradminghost(user))
+ if(!user.put_in_hands(inserted_tape))
+ inserted_tape.forceMove(drop_location())
+ else
+ inserted_tape.forceMove(drop_location())
+ inserted_tape = null
+ time_left = 0
+ current_song_duration = 0
+ pl_index = 0
+ current_playlist = list()
+ current_namelist = list()
+ stop_broadcast(TRUE)
+
+/obj/machinery/cassette/dj_station/attackby(obj/item/weapon, mob/user, params)
+ if(!istype(weapon, /obj/item/cassette_tape))
+ return
+ var/obj/item/cassette_tape/attacked = weapon
+ if(!attacked.approved_tape)
+ to_chat(user, span_warning("The [src] smartly rejects the bootleg cassette tape"))
+ return
+ if(!inserted_tape)
+ insert_tape(attacked)
+ else
+ if(!broadcasting)
+ if(Adjacent(user) && !issiliconoradminghost(user))
+ if(!user.put_in_hands(inserted_tape))
+ inserted_tape.forceMove(drop_location())
+ else
+ inserted_tape.forceMove(drop_location())
+ inserted_tape = null
+ time_left = 0
+ current_song_duration = 0
+ pl_index = 0
+ current_playlist = list()
+ current_namelist = list()
+ insert_tape(attacked)
+ if(broadcasting)
+ stop_broadcast(TRUE)
+
+/obj/machinery/cassette/dj_station/proc/insert_tape(obj/item/cassette_tape/CTape)
+ if(inserted_tape || !istype(CTape))
+ return
+
+ inserted_tape = CTape
+ CTape.forceMove(src)
+
+ update_appearance()
+ pl_index = 1
+ if(inserted_tape.songs["side1"] && inserted_tape.songs["side2"])
+ var/list/list = inserted_tape.songs["[inserted_tape.flipped ? "side2" : "side1"]"]
+ for(var/song in list)
+ current_playlist += song
+
+ var/list/name_list = inserted_tape.song_names["[inserted_tape.flipped ? "side2" : "side1"]"]
+ for(var/song in name_list)
+ current_namelist += song
+
+/obj/machinery/cassette/dj_station/proc/stop_broadcast(soft = FALSE)
+ STOP_PROCESSING(SSprocessing, src)
+ GLOB.dj_broadcast = FALSE
+ broadcasting = FALSE
+ message_admins("[src] has stopped broadcasting [inserted_tape].")
+ logger.Log(LOG_CATEGORY_MUSIC, "[src] has stopped broadcasting [inserted_tape]")
+ for(var/client/anything as anything in active_listeners)
+ if(!istype(anything))
+ continue
+ anything.tgui_panel?.stop_music()
+ GLOB.youtube_exempt["dj-station"] -= anything
+ active_listeners = list()
+
+ if(!soft)
+ for(var/mob/living/carbon/anything as anything in people_with_signals)
+ if(!istype(anything))
+ continue
+ UnregisterSignal(anything, COMSIG_CARBON_UNEQUIP_EARS)
+ UnregisterSignal(anything, COMSIG_CARBON_EQUIP_EARS)
+ UnregisterSignal(anything, COMSIG_MOVABLE_Z_CHANGED)
+ people_with_signals = list()
+
+/obj/machinery/cassette/dj_station/proc/start_broadcast()
+ var/choice = tgui_input_list(usr, "Choose which song to play.", "[src]", current_namelist)
+ if(!choice)
+ return
+ var/list_index = current_namelist.Find(choice)
+ if(!list_index)
+ return
+ GLOB.dj_broadcast = TRUE
+ pl_index = list_index
+
+ var/list/viable_z = SSmapping.levels_by_any_trait(list(ZTRAIT_STATION, ZTRAIT_MINING, ZTRAIT_CENTCOM, ZTRAIT_RESERVED))
+ for(var/mob/person as anything in GLOB.player_list)
+ if(issilicon(person) || isobserver(person) || isaicamera(person) || isbot(person))
+ active_listeners |= person.client
+ continue
+ if(iscarbon(person))
+ var/mob/living/carbon/anything = person
+ if(!(anything in people_with_signals))
+ if(!istype(anything))
+ continue
+
+ RegisterSignal(anything, COMSIG_CARBON_UNEQUIP_EARS, PROC_REF(stop_solo_broadcast))
+ RegisterSignal(anything, COMSIG_CARBON_EQUIP_EARS, PROC_REF(check_solo_broadcast))
+ RegisterSignal(anything, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(check_solo_broadcast))
+ people_with_signals |= anything
+
+ if(!(anything.client in active_listeners))
+ if(!(anything.z in viable_z))
+ continue
+
+ if(!anything.client)
+ continue
+
+ if(anything.client in GLOB.youtube_exempt["walkman"])
+ continue
+
+ var/obj/item/ear_slot = anything.get_item_by_slot(ITEM_SLOT_EARS)
+ if(istype(ear_slot, /obj/item/clothing/ears))
+ var/obj/item/clothing/ears/worn
+ if(!worn || !worn?.radio_compat)
+ continue
+ else if(!istype(ear_slot, /obj/item/radio/headset))
+ continue
+
+ if(!anything.client.prefs?.read_preference(/datum/preference/toggle/hear_music))
+ continue
+
+ active_listeners |= anything.client
+
+ if(!length(active_listeners))
+ return
+
+ start_playing(active_listeners)
+ START_PROCESSING(SSprocessing, src)
+
+
+/obj/machinery/cassette/dj_station/proc/check_solo_broadcast(mob/living/carbon/source, obj/item/clothing/ears/ear_item)
+ SIGNAL_HANDLER
+
+ if(!istype(source))
+ return
+
+ if(istype(ear_item, /obj/item/clothing/ears))
+ var/obj/item/clothing/ears/worn
+ if(!worn || !worn?.radio_compat)
+ return
+ else if(!istype(ear_item, /obj/item/radio/headset))
+ return
+
+ var/list/viable_z = SSmapping.levels_by_any_trait(list(ZTRAIT_STATION, ZTRAIT_MINING, ZTRAIT_CENTCOM))
+ if(!(source.z in viable_z) || !source.client)
+ return
+
+ if(!source.client.prefs?.read_preference(/datum/preference/toggle/hear_music))
+ return
+
+ active_listeners |= source.client
+ GLOB.youtube_exempt["dj-station"] |= source.client
+ INVOKE_ASYNC(src, PROC_REF(start_playing),list(source.client))
+
+/obj/machinery/cassette/dj_station/proc/stop_solo_broadcast(mob/living/carbon/source)
+ SIGNAL_HANDLER
+
+ if(!source.client || !(source.client in active_listeners))
+ return
+
+ active_listeners -= source.client
+ GLOB.youtube_exempt["dj-station"] -= source.client
+ source.client.tgui_panel?.stop_music()
+
+/obj/machinery/cassette/dj_station/proc/start_playing(list/clients)
+ if(!inserted_tape)
+ if(broadcasting)
+ stop_broadcast(TRUE)
+ return
+
+ waiting_for_yield = TRUE
+ if(is_http_protocol(current_playlist[pl_index]))
+ ///invoking youtube-dl
+ var/ytdl = CONFIG_GET(string/invoke_youtubedl)
+ ///the input for ytdl handled by the song list
+ var/web_sound_input
+ ///the url for youtube-dl
+ var/web_sound_url = ""
+ ///all extra data from the youtube-dl really want the name
+ var/list/music_extra_data = list()
+ web_sound_input = trim(current_playlist[pl_index])
+ if(!(web_sound_input in GLOB.parsed_audio))
+ ///scrubbing the input before putting it in the shell
+ var/shell_scrubbed_input = shell_url_scrub(web_sound_input)
+ ///putting it in the shell
+ var/list/output = world.shelleo("[ytdl] --geo-bypass --format \"bestaudio\[ext=mp3]/best\[ext=mp4]\[height <= 360]/bestaudio\[ext=m4a]/bestaudio\[ext=aac]\" --dump-single-json --no-playlist --extractor-args \"youtube:lang=en\" -- \"[shell_scrubbed_input]\"")
+ ///any errors
+ var/errorlevel = output[SHELLEO_ERRORLEVEL]
+ ///the standard output
+ var/stdout = output[SHELLEO_STDOUT]
+ if(!errorlevel)
+ ///list for all the output data to go to
+ var/list/data
+ try
+ data = json_decode(stdout)
+ catch(var/exception/error) ///catch errors here
+ to_chat(src, "Youtube-dl JSON parsing FAILED:", confidential = TRUE)
+ to_chat(src, "[error]: [stdout]", confidential = TRUE)
+ return
+
+ if (data["url"])
+ web_sound_url = data["url"]
+ music_extra_data["start"] = data["start_time"]
+ music_extra_data["end"] = data["end_time"]
+ music_extra_data["link"] = data["webpage_url"]
+ music_extra_data["title"] = data["title"]
+ if(music_extra_data["start"])
+ time_left = data["duration"] - music_extra_data["start"]
+ else
+ time_left = data["duration"]
+
+ current_song_duration = data["duration"]
+
+ GLOB.parsed_audio["[web_sound_input]"] = data
+ else
+ var/list/data = GLOB.parsed_audio["[web_sound_input]"]
+ web_sound_url = data["url"]
+ music_extra_data["start"] = data["start_time"]
+ music_extra_data["end"] = data["end_time"]
+ music_extra_data["link"] = data["webpage_url"]
+ music_extra_data["title"] = data["title"]
+ if(time_left <= 0)
+ if(music_extra_data["start"])
+ time_left = data["duration"] - music_extra_data["start"]
+ else
+ time_left = data["duration"]
+
+ current_song_duration = data["duration"]
+ music_extra_data["duration"] = data["duration"]
+
+ if(time_left > 0)
+ music_extra_data["start"] = music_extra_data["duration"] - time_left
+
+ for(var/client/anything as anything in clients)
+ if(!istype(anything))
+ continue
+ anything.tgui_panel?.play_music(web_sound_url, music_extra_data)
+ GLOB.youtube_exempt["dj-station"] |= anything
+ broadcasting = TRUE
+ waiting_for_yield = FALSE
+
+/obj/machinery/cassette/dj_station/proc/add_new_player(mob/living/carbon/new_player)
+ if(!(new_player in people_with_signals))
+ RegisterSignal(new_player, COMSIG_CARBON_UNEQUIP_EARS, PROC_REF(stop_solo_broadcast))
+ RegisterSignal(new_player, COMSIG_CARBON_EQUIP_EARS, PROC_REF(check_solo_broadcast))
+ RegisterSignal(new_player, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(check_solo_broadcast))
+ people_with_signals |= new_player
+
+ if(!broadcasting)
+ return
+
+ var/obj/item/ear_slot = new_player.get_item_by_slot(ITEM_SLOT_EARS)
+ if(istype(ear_slot, /obj/item/clothing/ears))
+ var/obj/item/clothing/ears/worn
+ if(!worn || !worn?.radio_compat)
+ return
+ else if(!istype(ear_slot, /obj/item/radio/headset))
+ return
+ var/list/viable_z = SSmapping.levels_by_any_trait(list(ZTRAIT_STATION, ZTRAIT_MINING, ZTRAIT_CENTCOM))
+ if(!(new_player.z in viable_z))
+ return
+
+ if(!(new_player.client in active_listeners))
+ active_listeners |= new_player.client
+ start_playing(list(new_player.client))
+
+/obj/machinery/cassette/dj_station/proc/next_song()
+ waiting_for_yield = TRUE
+ var/choice = tgui_input_number(usr, "Choose which song number to play.", "[src]", 1, length(current_playlist), 1)
+ if(!choice)
+ waiting_for_yield = FALSE
+ stop_broadcast()
+ return
+ GLOB.dj_broadcast = TRUE
+ pl_index = choice
+
+ pl_index++
+ start_playing(active_listeners)
diff --git a/monkestation/code/modules/cassettes/machines/portable_mixer.dm b/monkestation/code/modules/cassettes/machines/portable_mixer.dm
index 4f95b833c7da..a3f77b0639e4 100644
--- a/monkestation/code/modules/cassettes/machines/portable_mixer.dm
+++ b/monkestation/code/modules/cassettes/machines/portable_mixer.dm
@@ -1,3 +1,4 @@
+#warn TODO: cassette mixer
/obj/item/device/cassette_deck
name = "Dual Cassette Deck"
desc = "A Dual Cassette Deck, popular for its ability to copy songs from a cassette. A relic of the old times"
@@ -5,13 +6,13 @@
icon_state = "walkman"
w_class = WEIGHT_CLASS_SMALL
///The cassette that is being copied from
- var/obj/item/device/cassette_tape/send
+ var/obj/item/cassette_tape/send
///List of songs the sender has
var/list/sender_list
///List of names the Sender has
var/list/sender_names
///The cassette you are copying to
- var/obj/item/device/cassette_tape/recieve
+ var/obj/item/cassette_tape/recieve
///List of songs the Reciever has
var/list/reciever_list
///List of song names the Reciever has
@@ -23,7 +24,7 @@
/obj/item/device/cassette_deck/AltClick(mob/user)
if(recieve || send)
- eject_tape(user)
+ //eject_tape(user)
return
return ..()
@@ -32,15 +33,16 @@
removal = !removal
/obj/item/device/cassette_deck/attackby(obj/item/cassette, mob/user)
- if(!istype(cassette, /obj/item/device/cassette_tape))
+ if(!istype(cassette, /obj/item/cassette_tape))
return
if(!send || !recieve)
- insert_tape(cassette)
+ //insert_tape(cassette)
playsound(src,'sound/weapons/handcuffs.ogg',20,1)
to_chat(user,("You insert \the [cassette] into \the [src]"))
else
to_chat(user,("Remove a tape first!"))
+/*
/obj/item/device/cassette_deck/attack_self(mob/user)
. = ..()
if(!recieve)
@@ -76,7 +78,7 @@
reciever_list.Remove(reciever_list[num])
reciever_names.Remove(reciever_names[num])
-/obj/item/device/cassette_deck/proc/insert_tape(obj/item/device/cassette_tape/CTape)
+/obj/item/device/cassette_deck/proc/insert_tape(obj/item/cassette_tape/CTape)
if(send && recieve || !istype(CTape))
return
@@ -113,3 +115,4 @@
send = null
broke_approval = FALSE
playsound(src,'sound/weapons/handcuffs.ogg',20,1)
+*/
diff --git a/monkestation/code/modules/cassettes/machines/postbox.dm b/monkestation/code/modules/cassettes/machines/postbox.dm
index 3b60a4326667..86d3767ed60b 100644
--- a/monkestation/code/modules/cassettes/machines/postbox.dm
+++ b/monkestation/code/modules/cassettes/machines/postbox.dm
@@ -1,3 +1,4 @@
+#warn TODO: cassette submission postbox
/obj/machinery/cassette/mailbox
name = "Space Board of Music Postbox"
desc = "Has a slit specifically to fit cassettes into it."
@@ -14,12 +15,12 @@
. = ..()
REGISTER_REQUIRED_MAP_ITEM(1, INFINITY)
-
+/*
/obj/machinery/cassette/mailbox/attackby(obj/item/weapon, mob/user, params)
- if(!istype(weapon, /obj/item/device/cassette_tape) || !user.client)
+ if(!istype(weapon, /obj/item/cassette_tape) || !user.client)
return
- var/obj/item/device/cassette_tape/attacked_tape = weapon
+ var/obj/item/cassette_tape/attacked_tape = weapon
var/list/admin_count = get_admin_counts(R_FUN)
if(!length(admin_count["present"]))
@@ -58,3 +59,4 @@
attacked_tape.moveToNullspace()
submit_cassette_for_review(attacked_tape, user)
return TRUE
+*/
diff --git a/monkestation/code/modules/cassettes/machines/radio_mic.dm b/monkestation/code/modules/cassettes/machines/radio_mic.dm
index 7336728ff584..7863adb076bc 100644
--- a/monkestation/code/modules/cassettes/machines/radio_mic.dm
+++ b/monkestation/code/modules/cassettes/machines/radio_mic.dm
@@ -26,6 +26,8 @@
/// overlay when speaking a message (is displayed simultaniously with speaker_active)
overlay_mic_active = null
+ /// The proximity monitor used to allow people to hear DJ music while in hearing range.
+ var/datum/proximity_monitor/advanced/dj_music/music_field
/obj/item/radio/radio_mic/Initialize(mapload)
. = ..()
@@ -40,6 +42,12 @@
set_broadcasting(TRUE)
+ music_field = new(src, isnull(listening_range) ? canhear_range : listening_range)
+
+/obj/item/radio/radio_mic/Destroy()
+ QDEL_NULL(music_field)
+ return ..()
+
/obj/item/radio/radio_mic/ui_interact(mob/user, datum/tgui/ui, datum/ui_state/state)
return
diff --git a/monkestation/code/modules/cassettes/machines/stationary_mixer.dm b/monkestation/code/modules/cassettes/machines/stationary_mixer.dm
index 632e9426e8ae..ef901d9bcd6e 100644
--- a/monkestation/code/modules/cassettes/machines/stationary_mixer.dm
+++ b/monkestation/code/modules/cassettes/machines/stationary_mixer.dm
@@ -1,3 +1,4 @@
+#warn TODO: advanced cassette deck
/obj/machinery/cassette/adv_cassette_deck
name = "Advanced Cassette Deck"
desc = "A more advanced less portable Cassette Deck. Useful for recording songs from our generation, or customizing the style of your cassettes."
@@ -6,7 +7,7 @@
density = TRUE
pass_flags = PASSTABLE
///cassette tape used in adding songs or customizing
- var/obj/item/device/cassette_tape/tape
+ var/obj/item/cassette_tape/tape
///Selection used to remove songs
var/selection
@@ -20,7 +21,7 @@
return TRUE
/obj/machinery/cassette/adv_cassette_deck/attackby(obj/item/cassette, mob/user)
- if(!istype(cassette, /obj/item/device/cassette_tape))
+ if(!istype(cassette, /obj/item/cassette_tape))
return ..()
if(!tape)
insert_tape(cassette)
@@ -29,7 +30,7 @@
else
to_chat(user,"Remove a tape first!")
-/obj/machinery/cassette/adv_cassette_deck/proc/insert_tape(obj/item/device/cassette_tape/CTape)
+/obj/machinery/cassette/adv_cassette_deck/proc/insert_tape(obj/item/cassette_tape/CTape)
if(tape || !istype(CTape))
return
tape = CTape
@@ -57,6 +58,7 @@
ui = new(user, src, "CassetteDeck", name)
ui.open()
+/*
/obj/machinery/cassette/adv_cassette_deck/ui_data(mob/user)
///all data for the tgui
var/list/data = list()
@@ -193,3 +195,4 @@
else
tape.icon_state = design_path[design_names.Find(selection)]
tape.side2_icon = design_path[design_names.Find(selection)]
+*/
diff --git a/monkestation/code/modules/cassettes/random_cassette_selection.dm b/monkestation/code/modules/cassettes/random_cassette_selection.dm
index 19b27f0fdd81..0cc292301f09 100644
--- a/monkestation/code/modules/cassettes/random_cassette_selection.dm
+++ b/monkestation/code/modules/cassettes/random_cassette_selection.dm
@@ -17,7 +17,7 @@ GLOBAL_LIST_INIT(approved_ids, initialize_approved_ids())
return list()
return json_decode(file2text(ids_exist))
-/obj/item/device/cassette_tape/random
+/obj/item/cassette_tape/random
name = "Not Correctly Created Random Cassette"
desc = "How did this happen?"
random = TRUE
diff --git a/monkestation/code/modules/cassettes/walkman/_walkmen.dm b/monkestation/code/modules/cassettes/walkman/_walkmen.dm
index 6eee606c9ff9..cbf0c6c76f39 100644
--- a/monkestation/code/modules/cassettes/walkman/_walkmen.dm
+++ b/monkestation/code/modules/cassettes/walkman/_walkmen.dm
@@ -1,3 +1,4 @@
+#warn TODO: walkmen
GLOBAL_LIST_INIT(parsed_audio, list())
GLOBAL_LIST_INIT(youtube_exempt, list(
@@ -15,7 +16,7 @@ GLOBAL_LIST_INIT(youtube_exempt, list(
w_class = WEIGHT_CLASS_SMALL
actions_types = list(/datum/action/item_action/walkman/play_pause,/datum/action/item_action/walkman/next_song,/datum/action/item_action/walkman/restart_song)
///the cassette tape object
- var/obj/item/device/cassette_tape/tape
+ var/obj/item/cassette_tape/tape
///if the walkman is paused or not
var/paused = TRUE
///songs inside the current playlist
@@ -43,6 +44,7 @@ GLOBAL_LIST_INIT(youtube_exempt, list(
///cooldown used by the next song to stop overlapping sounds between url based songs and normal ones
COOLDOWN_DECLARE(next_song_use)
+/*
/obj/item/device/walkman/Initialize()
. = ..()
design = rand(1, 5)
@@ -58,7 +60,7 @@ GLOBAL_LIST_INIT(youtube_exempt, list(
. = ..()
/obj/item/device/walkman/attackby(obj/item/cassette, mob/user)
- if(!istype(cassette, /obj/item/device/cassette_tape))
+ if(!istype(cassette, /obj/item/cassette_tape))
return
if(!tape)
insert_tape(cassette)
@@ -142,7 +144,7 @@ GLOBAL_LIST_INIT(youtube_exempt, list(
/obj/item/device/walkman/proc/play()
if(!current_song)
if(current_playlist.len > 0)
- if(findtext(current_playlist[pl_index], GLOB.is_http_protocol))
+ if(is_http_protocol(current_playlist[pl_index]))
///invoking youtube-dl
var/ytdl = CONFIG_GET(string/invoke_youtubedl)
///the input for ytdl handled by the song list
@@ -232,9 +234,9 @@ GLOBAL_LIST_INIT(youtube_exempt, list(
/*Called when
- *Arguments: obj/item/device/cassette_tape/CT -> the cassette in question that you are inserting into the walkman
+ *Arguments: obj/item/cassette_tape/CT -> the cassette in question that you are inserting into the walkman
*/
-/obj/item/device/walkman/proc/insert_tape(obj/item/device/cassette_tape/CTape)
+/obj/item/device/walkman/proc/insert_tape(obj/item/cassette_tape/CTape)
if(tape || !istype(CTape))
return
@@ -285,7 +287,7 @@ GLOBAL_LIST_INIT(youtube_exempt, list(
break_sound()
pl_index = pl_index + 1 <= current_playlist.len ? (pl_index += 1) : 1
- link_play = findtext(current_playlist[pl_index], GLOB.is_http_protocol) ? TRUE : FALSE
+ link_play = is_http_protocol(current_playlist[pl_index])
if(!link_play)
@@ -359,6 +361,7 @@ GLOBAL_LIST_INIT(youtube_exempt, list(
return
update_song(current_song, current_listener, 0)
+*/
/*
ACTION BUTTONS
@@ -390,10 +393,12 @@ GLOBAL_LIST_INIT(youtube_exempt, list(
..()
name = "Next song"
+/*
/datum/action/item_action/walkman/next_song/Trigger(trigger_flags)
if(target)
var/obj/item/device/walkman/walkM = target
walkM.next_song(owner)
+*/
/datum/action/item_action/walkman/restart_song
button_icon_state = "walkman_restart"
@@ -402,10 +407,13 @@ GLOBAL_LIST_INIT(youtube_exempt, list(
..()
name = "Restart song"
+/*
/datum/action/item_action/walkman/restart_song/Trigger(trigger_flags)
if(target)
var/obj/item/device/walkman/walkM = target
walkM.restart_song(owner)
+*/
+
#undef sound_to
#undef NEXT_SONG_USE_TIMER
diff --git a/monkestation/code/modules/mob/dead/observer/observer.dm b/monkestation/code/modules/mob/dead/observer/observer.dm
new file mode 100644
index 000000000000..ab145f403285
--- /dev/null
+++ b/monkestation/code/modules/mob/dead/observer/observer.dm
@@ -0,0 +1,3 @@
+/mob/dead/observer/Initialize(mapload)
+ . = ..()
+ ADD_TRAIT(src, TRAIT_CAN_HEAR_MUSIC, INNATE_TRAIT)
diff --git a/monkestation/code/modules/mob/living/silicon/silicon.dm b/monkestation/code/modules/mob/living/silicon/silicon.dm
new file mode 100644
index 000000000000..6dab07329bf6
--- /dev/null
+++ b/monkestation/code/modules/mob/living/silicon/silicon.dm
@@ -0,0 +1,3 @@
+/mob/living/silicon/Initialize(mapload)
+ . = ..()
+ ADD_TRAIT(src, TRAIT_CAN_HEAR_MUSIC, INNATE_TRAIT)
diff --git a/tgstation.dme b/tgstation.dme
index 8dfe82c1869e..678f7535dba3 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -412,6 +412,7 @@
#include "code\__DEFINES\~monkestation\blueshift.dm"
#include "code\__DEFINES\~monkestation\botany.dm"
#include "code\__DEFINES\~monkestation\cargo.dm"
+#include "code\__DEFINES\~monkestation\cassettes.dm"
#include "code\__DEFINES\~monkestation\chat.dm"
#include "code\__DEFINES\~monkestation\chewin.dm"
#include "code\__DEFINES\~monkestation\clock_cult.dm"
@@ -610,6 +611,7 @@
#include "code\__HELPERS\~monkestation-helpers\mobs.dm"
#include "code\__HELPERS\~monkestation-helpers\records.dm"
#include "code\__HELPERS\~monkestation-helpers\roundend.dm"
+#include "code\__HELPERS\~monkestation-helpers\text.dm"
#include "code\__HELPERS\~monkestation-helpers\time.dm"
#include "code\__HELPERS\~monkestation-helpers\uwuify.dm"
#include "code\__HELPERS\~monkestation-helpers\virology.dm"
@@ -6170,6 +6172,7 @@
#include "monkestation\code\game\objects\items\guns\SRN.dm"
#include "monkestation\code\game\objects\items\guns\wt_ammo.dm"
#include "monkestation\code\game\objects\items\implants\hardlight.dm"
+#include "monkestation\code\game\objects\items\implants\implant_misc.dm"
#include "monkestation\code\game\objects\items\rayne_corp\rayne_lantern.dm"
#include "monkestation\code\game\objects\items\rayne_corp\rayne_mender.dm"
#include "monkestation\code\game\objects\items\robot\items\hypo.dm"
@@ -7122,7 +7125,10 @@
#include "monkestation\code\modules\cassettes\cassette_approval.dm"
#include "monkestation\code\modules\cassettes\random_cassette_selection.dm"
#include "monkestation\code\modules\cassettes\cassette_db\cassette_datum.dm"
-#include "monkestation\code\modules\cassettes\cassette_db\subsystem.dm"
+#include "monkestation\code\modules\cassettes\cassette_db\cassette_manager.dm"
+#include "monkestation\code\modules\cassettes\dj\dj_music_field.dm"
+#include "monkestation\code\modules\cassettes\dj\intercom.dm"
+#include "monkestation\code\modules\cassettes\dj\mob_can_hear.dm"
#include "monkestation\code\modules\cassettes\machines\cassette_rack.dm"
#include "monkestation\code\modules\cassettes\machines\dj_station.dm"
#include "monkestation\code\modules\cassettes\machines\portable_mixer.dm"
@@ -7643,6 +7649,7 @@
#include "monkestation\code\modules\mob\dead\new_player\sprite_accessories\multi_part.dm"
#include "monkestation\code\modules\mob\dead\new_player\sprite_accessories\sock_color.dm"
#include "monkestation\code\modules\mob\dead\new_player\sprite_accessories\underwear.dm"
+#include "monkestation\code\modules\mob\dead\observer\observer.dm"
#include "monkestation\code\modules\mob\living\emote.dm"
#include "monkestation\code\modules\mob\living\init_signals.dm"
#include "monkestation\code\modules\mob\living\living.dm"
@@ -7710,6 +7717,7 @@
#include "monkestation\code\modules\mob\living\carbon\human\species_type\tundra_moths\mothaccessories.dm"
#include "monkestation\code\modules\mob\living\carbon\human\species_type\tundra_moths\tundramoths.dm"
#include "monkestation\code\modules\mob\living\silicon\death.dm"
+#include "monkestation\code\modules\mob\living\silicon\silicon.dm"
#include "monkestation\code\modules\mob\living\simple_animal\megafauna\wendigo.dm"
#include "monkestation\code\modules\mob\living\simple_animal\pets\bees.dm"
#include "monkestation\code\modules\mob_spawn\ghost_roles\space_roles\oldchef.dm"