From 59476a5db2b4f61e7b67fbb73a8cb0624feafab5 Mon Sep 17 00:00:00 2001 From: Lucy Date: Sun, 15 Dec 2024 02:54:00 -0500 Subject: [PATCH 01/11] Refactors cassettes and the cassette player --- SQL/tgstation_schema.sql | 17 + _maps/map_files/BoxStation/BoxStation.dmm | 126 +++--- _maps/map_files/MetaStation/MetaStation.dmm | 122 +++--- _maps/map_files/Voidraptor/VoidRaptor.dmm | 74 ++-- code/__DEFINES/subsystems.dm | 1 + code/__DEFINES/~monkestation/cassettes.dm | 13 + code/__HELPERS/~monkestation-helpers/text.dm | 6 + code/_globalvars/_regexes.dm | 3 - .../configuration/entries/monkestation.dm | 2 + code/modules/admin/verbs/playsound.dm | 4 +- code/modules/requests/request_manager.dm | 2 +- code/modules/unit_tests/unit_test.dm | 4 +- config/config.txt | 2 + .../code/modules/admin/verbs/spawn_mixtape.dm | 2 +- .../code/modules/cargo/crates/goodies.dm | 4 +- .../code/modules/cargo/crates/service.dm | 4 +- .../code/modules/cassettes/cassette.dm | 30 +- .../modules/cassettes/cassette_approval.dm | 6 +- .../cassettes/cassette_db/cassette_datum.dm | 237 ++++++++--- .../cassettes/cassette_db/cassette_manager.dm | 190 +++++++++ .../cassettes/cassette_db/subsystem.dm | 30 -- .../cassettes/machines/cassette_rack.dm | 26 +- .../modules/cassettes/machines/dj_station.dm | 359 +---------------- .../cassettes/machines/dj_station_old.dm | 377 ++++++++++++++++++ .../cassettes/machines/portable_mixer.dm | 8 +- .../modules/cassettes/machines/postbox.dm | 4 +- .../cassettes/machines/stationary_mixer.dm | 6 +- .../cassettes/random_cassette_selection.dm | 2 +- .../modules/cassettes/walkman/_walkmen.dm | 12 +- tgstation.dme | 4 +- 30 files changed, 1013 insertions(+), 664 deletions(-) create mode 100644 code/__DEFINES/~monkestation/cassettes.dm create mode 100644 code/__HELPERS/~monkestation-helpers/text.dm create mode 100644 monkestation/code/modules/cassettes/cassette_db/cassette_manager.dm delete mode 100644 monkestation/code/modules/cassettes/cassette_db/subsystem.dm create mode 100644 monkestation/code/modules/cassettes/machines/dj_station_old.dm 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 8be6a9fced01..54df9ba49254 100644 --- a/_maps/map_files/BoxStation/BoxStation.dmm +++ b/_maps/map_files/BoxStation/BoxStation.dmm @@ -21341,53 +21341,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) @@ -26093,20 +26046,6 @@ /obj/machinery/deepfryer, /turf/open/floor/iron/kitchen, /area/station/service/kitchen) -"iuI" = ( -/obj/effect/turf_decal/tile/red/fourcorners, -/obj/structure/rack, -/obj/item/gun/energy/disabler{ - pixel_x = -3; - pixel_y = 3 - }, -/obj/item/gun/energy/disabler, -/obj/item/gun/energy/disabler{ - pixel_x = 3; - pixel_y = -3 - }, -/turf/open/floor/iron/dark, -/area/station/ai_monitored/security/armory) "iuJ" = ( /obj/effect/turf_decal/tile/dark_blue/full, /obj/effect/turf_decal/bot_white, @@ -42564,6 +42503,53 @@ /obj/structure/cable, /turf/open/floor/iron, /area/station/cargo/warehouse) +"nOJ" = ( +/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) "nOK" = ( /obj/machinery/firealarm/directional/north, /turf/open/floor/iron/dark/side{ @@ -50234,6 +50220,20 @@ /obj/machinery/holopad, /turf/open/floor/carpet, /area/station/service/chapel) +"qrg" = ( +/obj/effect/turf_decal/tile/red/fourcorners, +/obj/structure/rack, +/obj/item/gun/energy/disabler{ + pixel_x = -3; + pixel_y = 3 + }, +/obj/item/gun/energy/disabler, +/obj/item/gun/energy/disabler{ + pixel_x = 3; + pixel_y = -3 + }, +/turf/open/floor/iron/dark, +/area/station/ai_monitored/security/armory) "qrn" = ( /obj/structure/cable, /obj/machinery/atmospherics/components/binary/pump/on{ @@ -103790,7 +103790,7 @@ oRs csZ gGx dAx -iuI +qrg lZV jZp jRr @@ -122881,7 +122881,7 @@ qaG gji dOL nVc -gSC +nOJ nBE nep mhc diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm index 427bd05d6564..22fa0bc161cc 100644 --- a/_maps/map_files/MetaStation/MetaStation.dmm +++ b/_maps/map_files/MetaStation/MetaStation.dmm @@ -9301,14 +9301,6 @@ }, /turf/open/floor/iron/dark/textured, /area/station/medical/pathology) -"dtx" = ( -/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, -/obj/structure/cable, -/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, -/obj/item/kirbyplants/random, -/obj/machinery/camera/autoname/directional/north, -/turf/open/floor/iron/white, -/area/station/science/explab) "dtB" = ( /obj/machinery/atmospherics/pipe/smart/manifold/purple/visible{ dir = 8 @@ -33386,28 +33378,6 @@ /obj/machinery/duct, /turf/open/floor/engine, /area/station/science/xenobiology) -"lFk" = ( -/obj/machinery/light_switch/directional/east, -/obj/effect/turf_decal/tile/red/half/contrasted{ - dir = 8 - }, -/obj/item/ammo_box/magazine/m35, -/obj/item/ammo_box/magazine/m35, -/obj/structure/rack, -/obj/item/ammo_box/magazine/m35, -/obj/item/ammo_box/magazine/m35, -/obj/item/ammo_box/magazine/m35, -/obj/item/ammo_box/magazine/m35, -/obj/item/ammo_box/magazine/m35, -/obj/item/ammo_box/magazine/m35, -/obj/item/ammo_box/magazine/m35, -/obj/item/ammo_box/magazine/m35, -/obj/item/gun/ballistic/automatic/pistol/paco/no_mag, -/obj/item/gun/ballistic/automatic/pistol/paco/no_mag{ - pixel_y = 6 - }, -/turf/open/floor/iron/dark, -/area/station/ai_monitored/security/armory) "lFo" = ( /obj/structure/disposalpipe/segment{ dir = 9 @@ -43960,6 +43930,14 @@ /obj/machinery/light/small/directional/west, /turf/open/floor/iron/freezer, /area/station/hallway/primary/aft) +"pjY" = ( +/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, +/obj/structure/cable, +/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, +/obj/item/kirbyplants/random, +/obj/machinery/camera/autoname/directional/north, +/turf/open/floor/iron/white, +/area/station/science/explab) "pke" = ( /obj/effect/turf_decal/tile/red, /obj/effect/turf_decal/tile/red{ @@ -46323,6 +46301,28 @@ /obj/machinery/station_map/engineering/directional/south, /turf/open/floor/iron, /area/station/hallway/primary/central) +"qcE" = ( +/obj/machinery/light_switch/directional/east, +/obj/effect/turf_decal/tile/red/half/contrasted{ + dir = 8 + }, +/obj/item/ammo_box/magazine/m35, +/obj/item/ammo_box/magazine/m35, +/obj/structure/rack, +/obj/item/ammo_box/magazine/m35, +/obj/item/ammo_box/magazine/m35, +/obj/item/ammo_box/magazine/m35, +/obj/item/ammo_box/magazine/m35, +/obj/item/ammo_box/magazine/m35, +/obj/item/ammo_box/magazine/m35, +/obj/item/ammo_box/magazine/m35, +/obj/item/ammo_box/magazine/m35, +/obj/item/gun/ballistic/automatic/pistol/paco/no_mag, +/obj/item/gun/ballistic/automatic/pistol/paco/no_mag{ + pixel_y = 6 + }, +/turf/open/floor/iron/dark, +/area/station/ai_monitored/security/armory) "qcP" = ( /obj/effect/landmark/blobstart, /obj/effect/mapping_helpers/burnt_floor, @@ -51147,34 +51147,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{ @@ -68703,6 +68675,34 @@ }, /turf/open/floor/iron, /area/station/hallway/secondary/command) +"xPT" = ( +/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) "xQb" = ( /obj/machinery/light/directional/east, /obj/structure/filingcabinet, @@ -89105,7 +89105,7 @@ nxF sVY cyk qzS -rIa +xPT ahD wki jIY @@ -97544,7 +97544,7 @@ sDw eew hxF daO -lFk +qcE anl vDh ewj @@ -109463,7 +109463,7 @@ tVv tVv tVv tVv -dtx +pjY aqv lHE pQf diff --git a/_maps/map_files/Voidraptor/VoidRaptor.dmm b/_maps/map_files/Voidraptor/VoidRaptor.dmm index 167607ecb8c0..d60acbb2e511 100644 --- a/_maps/map_files/Voidraptor/VoidRaptor.dmm +++ b/_maps/map_files/Voidraptor/VoidRaptor.dmm @@ -10005,22 +10005,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; @@ -27173,25 +27157,6 @@ /obj/machinery/electroplater, /turf/open/floor/wood/large, /area/station/service/forge) -"hPg" = ( -/obj/structure/table/reinforced, -/obj/item/clothing/suit/hooded/ablative{ - pixel_y = 7 - }, -/obj/item/gun/energy/temperature/security{ - pixel_y = 5 - }, -/obj/structure/window/reinforced/spawner/directional/south, -/obj/structure/window/reinforced/spawner/directional/east{ - layer = 2.9; - pixel_x = 4 - }, -/obj/effect/turf_decal/stripes/line{ - dir = 8 - }, -/obj/effect/turf_decal/delivery, -/turf/open/floor/iron/dark/textured_large, -/area/station/ai_monitored/security/armory) "hPi" = ( /turf/open/floor/iron/textured_large, /area/station/command/cc_dock) @@ -56344,6 +56309,22 @@ dir = 1 }, /area/station/security/checkpoint/science/research) +"pKs" = ( +/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) "pKD" = ( /obj/structure/extinguisher_cabinet/directional/north, /obj/effect/turf_decal/bot, @@ -72722,6 +72703,25 @@ }, /turf/open/floor/catwalk_floor, /area/station/maintenance/solars/starboard/fore) +"uja" = ( +/obj/structure/table/reinforced, +/obj/item/clothing/suit/hooded/ablative{ + pixel_y = 7 + }, +/obj/item/gun/energy/temperature/security{ + pixel_y = 5 + }, +/obj/structure/window/reinforced/spawner/directional/south, +/obj/structure/window/reinforced/spawner/directional/east{ + layer = 2.9; + pixel_x = 4 + }, +/obj/effect/turf_decal/stripes/line{ + dir = 8 + }, +/obj/effect/turf_decal/delivery, +/turf/open/floor/iron/dark/textured_large, +/area/station/ai_monitored/security/armory) "ujo" = ( /obj/structure/table/wood/fancy/orange, /obj/item/reagent_containers/pill/patch/libital{ @@ -125088,7 +125088,7 @@ jzQ nQl fLp dWg -cYI +pKs vZM uuO rCr @@ -127103,7 +127103,7 @@ vwn ewC ybu cDc -hPg +uja pKY nRV iqx diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 2409801b5e34..14a15eb92d27 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/~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/__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 c49237131fec..342063dd62a9 100644 --- a/code/controllers/configuration/entries/monkestation.dm +++ b/code/controllers/configuration/entries/monkestation.dm @@ -62,3 +62,5 @@ . = ..() if(.) config_entry_value *= 600 // documented as minutes + +/datum/config_entry/flag/cassettes_in_db 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/requests/request_manager.dm b/code/modules/requests/request_manager.dm index eb16a38fc3bc..408889b86eef 100644 --- a/code/modules/requests/request_manager.dm +++ b/code/modules/requests/request_manager.dm @@ -245,7 +245,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/unit_tests/unit_test.dm b/code/modules/unit_tests/unit_test.dm index e3446e24618a..1cc258a6efa0 100644 --- a/code/modules/unit_tests/unit_test.dm +++ b/code/modules/unit_tests/unit_test.dm @@ -326,8 +326,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 464b95aa5078..46f90548fe35 100644 --- a/config/config.txt +++ b/config/config.txt @@ -566,3 +566,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/modules/admin/verbs/spawn_mixtape.dm b/monkestation/code/modules/admin/verbs/spawn_mixtape.dm index bffca51317a0..026026c7e422 100644 --- a/monkestation/code/modules/admin/verbs/spawn_mixtape.dm +++ b/monkestation/code/modules/admin/verbs/spawn_mixtape.dm @@ -52,6 +52,6 @@ switch(action) if("spawn") if (params["id"]) - new/obj/item/device/cassette_tape(usr.loc, params["id"]) + new/obj/item/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].") 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..3fc36c21f793 100644 --- a/monkestation/code/modules/cassettes/cassette.dm +++ b/monkestation/code/modules/cassettes/cassette.dm @@ -1,5 +1,5 @@ -/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' @@ -28,7 +28,7 @@ var/random = FALSE var/cassette_desc_string = "Generic Desc" -/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() @@ -57,13 +57,13 @@ update_appearance() -/obj/item/device/cassette_tape/attack_self(mob/user) +/obj/item/cassette_tape/attack_self(mob/user) ..() icon_state = flipped ? side1_icon : side2_icon flipped = !flipped to_chat(user, span_notice("You flip [src].")) -/obj/item/device/cassette_tape/update_desc(updates) +/obj/item/cassette_tape/update_desc(updates) . = ..() desc = cassette_desc_string desc += "\n" @@ -72,7 +72,7 @@ if(author_name) desc += span_notice("Mixed by [author_name]\n") -/obj/item/device/cassette_tape/attackby(obj/item/item, mob/living/user) +/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")) @@ -106,24 +106,8 @@ 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()) - -/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 69f7306c8574..433d566d10af 100644 --- a/monkestation/code/modules/cassettes/cassette_approval.dm +++ b/monkestation/code/modules/cassettes/cassette_approval.dm @@ -1,7 +1,7 @@ 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 +25,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 +70,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" 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..0f8c26c6f58c --- /dev/null +++ b/monkestation/code/modules/cassettes/cassette_db/cassette_manager.dm @@ -0,0 +1,190 @@ +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) as /datum/cassette + RETURN_TYPE(/datum/cassette) + if(!id) + return null + 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) + +/// Gets all the cassettes authored by the given ckey. +/datum/controller/subsystem/cassettes/proc/get_cassettes_by_ckey(user_ckey) as /list + RETURN_TYPE(/list/datum/cassette) + . = list() + user_ckey = ckey(user_ckey) + for(var/id in cassettes) + var/datum/cassette/cassette = cassettes[id] + if(cassette.author.ckey == user_ckey) + . += cassette + +/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/machines/cassette_rack.dm b/monkestation/code/modules/cassettes/machines/cassette_rack.dm index 9abbeabb0371..3925273a2a06 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,20 +68,18 @@ 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)) + var/list/user_cassettes = SScassettes.get_cassettes_by_ckey(user_ckey) + if(!length(user_cassettes)) return FALSE - var/list/existing_tapes = list() - for(var/obj/item/device/cassette_tape/tape in src) + var/list/existing_cassettes = list() + for(var/obj/item/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]) + existing_cassettes[tape.id] = TRUE + for(var/iter in 1 to min(max_amt, length(user_cassettes))) + var/datum/cassette/cassette = pick_n_take(user_cassettes) + if(existing_cassettes[cassette.id]) continue - new /obj/item/device/cassette_tape(src, tape.cassette_id) + new /obj/item/cassette_tape(src, 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 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..332d22e5366a 100644 --- a/monkestation/code/modules/cassettes/machines/portable_mixer.dm +++ b/monkestation/code/modules/cassettes/machines/portable_mixer.dm @@ -5,13 +5,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 @@ -32,7 +32,7 @@ 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) @@ -76,7 +76,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 diff --git a/monkestation/code/modules/cassettes/machines/postbox.dm b/monkestation/code/modules/cassettes/machines/postbox.dm index 3b60a4326667..01cbc8161b29 100644 --- a/monkestation/code/modules/cassettes/machines/postbox.dm +++ b/monkestation/code/modules/cassettes/machines/postbox.dm @@ -16,10 +16,10 @@ /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"])) diff --git a/monkestation/code/modules/cassettes/machines/stationary_mixer.dm b/monkestation/code/modules/cassettes/machines/stationary_mixer.dm index 632e9426e8ae..f0f5958f2411 100644 --- a/monkestation/code/modules/cassettes/machines/stationary_mixer.dm +++ b/monkestation/code/modules/cassettes/machines/stationary_mixer.dm @@ -6,7 +6,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 +20,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 +29,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 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..a275fd3c8fed 100644 --- a/monkestation/code/modules/cassettes/walkman/_walkmen.dm +++ b/monkestation/code/modules/cassettes/walkman/_walkmen.dm @@ -15,7 +15,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 @@ -58,7 +58,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 +142,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 +232,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 +285,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) diff --git a/tgstation.dme b/tgstation.dme index 6d7052a6722a..a74e2f3488e8 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" @@ -609,6 +610,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\virology.dm" #include "code\__HELPERS\~monkestation-helpers\logging\attack.dm" @@ -7042,7 +7044,7 @@ #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\machines\cassette_rack.dm" #include "monkestation\code\modules\cassettes\machines\dj_station.dm" #include "monkestation\code\modules\cassettes\machines\portable_mixer.dm" From 47a8f42d14a5785d1135b4025aef7e7588b5fb35 Mon Sep 17 00:00:00 2001 From: Lucy Date: Tue, 31 Dec 2024 11:20:39 -0500 Subject: [PATCH 02/11] update usage of `GLOB.is_http_protocol` --- code/modules/tgui_panel/audio.dm | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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) From aa3d87a410e3cddb976806f99781f6e7b750c59e Mon Sep 17 00:00:00 2001 From: Lucy Date: Tue, 31 Dec 2024 11:20:47 -0500 Subject: [PATCH 03/11] fix mixtape spawner --- .../code/modules/admin/verbs/spawn_mixtape.dm | 66 +++++++++++-------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/monkestation/code/modules/admin/verbs/spawn_mixtape.dm b/monkestation/code/modules/admin/verbs/spawn_mixtape.dm index 026026c7e422..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/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 From 30b93536afb47b7d9cc92187de4c6cbebcd4125c Mon Sep 17 00:00:00 2001 From: Lucy Date: Tue, 31 Dec 2024 12:14:42 -0500 Subject: [PATCH 04/11] rework logic around whether a mob can hear dj music or not to use a trait --- .../traits/monkestation/declarations.dm | 4 ++ .../dcs/signals/signals_global.dm | 4 ++ .../objects/items/devices/radio/headset.dm | 9 +++++ .../objects/items/implants/implant_misc.dm | 9 +++++ .../modules/cassettes/dj/dj_music_field.dm | 40 +++++++++++++++++++ .../code/modules/cassettes/dj/intercom.dm | 13 ++++++ .../code/modules/cassettes/dj/mob_can_hear.dm | 25 ++++++++++++ .../modules/mob/dead/observer/observer.dm | 3 ++ .../modules/mob/living/silicon/silicon.dm | 3 ++ tgstation.dme | 6 +++ 10 files changed, 116 insertions(+) create mode 100644 monkestation/code/game/objects/items/implants/implant_misc.dm create mode 100644 monkestation/code/modules/cassettes/dj/dj_music_field.dm create mode 100644 monkestation/code/modules/cassettes/dj/intercom.dm create mode 100644 monkestation/code/modules/cassettes/dj/mob_can_hear.dm create mode 100644 monkestation/code/modules/mob/dead/observer/observer.dm create mode 100644 monkestation/code/modules/mob/living/silicon/silicon.dm diff --git a/code/__DEFINES/traits/monkestation/declarations.dm b/code/__DEFINES/traits/monkestation/declarations.dm index f43063a99422..98d4f955a2af 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/dcs/signals/signals_global.dm b/code/__DEFINES/~monkestation/dcs/signals/signals_global.dm index d090af76972c..335269043492 100644 --- a/code/__DEFINES/~monkestation/dcs/signals/signals_global.dm +++ b/code/__DEFINES/~monkestation/dcs/signals/signals_global.dm @@ -1,2 +1,6 @@ /// Sent whenever a new goldeneye key is spawned: (obj/item/goldeneye_key) #define COMSIG_GLOB_GOLDENEYE_KEY_CREATED "!goldeneye_key_created" +/// 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/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/cassettes/dj/dj_music_field.dm b/monkestation/code/modules/cassettes/dj/dj_music_field.dm new file mode 100644 index 000000000000..21bde8d71997 --- /dev/null +++ b/monkestation/code/modules/cassettes/dj/dj_music_field.dm @@ -0,0 +1,40 @@ +/// 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(mob/living/crosser, turf/old_location, turf/new_location) + if(isliving(crosser)) + add_mob(crosser) + +/datum/proximity_monitor/advanced/dj_music/field_turf_uncrossed(mob/living/crosser, turf/old_location, turf/new_location) + if(isliving(crosser)) + remove_mob(crosser) + +/datum/proximity_monitor/advanced/dj_music/setup_field_turf(turf/target) + for(var/mob/living/inner_mob as anything in target) + add_mob(inner_mob) + +/datum/proximity_monitor/advanced/dj_music/cleanup_field_turf(turf/target) + for(var/mob/living/inner_mob as anything in target) + remove_mob(inner_mob) + +/datum/proximity_monitor/advanced/dj_music/proc/add_mob(mob/living/target) + if(!isliving(target) || QDELING(target) || (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..0ce2c0e4ca4c --- /dev/null +++ b/monkestation/code/modules/cassettes/dj/mob_can_hear.dm @@ -0,0 +1,25 @@ +/// 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 + 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 + GLOB.music_listeners -= src + SEND_GLOBAL_SIGNAL(COMSIG_GLOB_REMOVE_MUSIC_LISTENER, src) 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 8ef7f65cb8f6..bb4f10d03cf5 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -6148,6 +6148,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" @@ -7077,6 +7078,9 @@ #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\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" @@ -7588,6 +7592,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" @@ -7654,6 +7659,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" From 94c40dc2d43d0b3978961cae5769a17043c49ca8 Mon Sep 17 00:00:00 2001 From: Lucy Date: Tue, 31 Dec 2024 12:16:00 -0500 Subject: [PATCH 05/11] add some sanity checks --- monkestation/code/modules/cassettes/dj/mob_can_hear.dm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/monkestation/code/modules/cassettes/dj/mob_can_hear.dm b/monkestation/code/modules/cassettes/dj/mob_can_hear.dm index 0ce2c0e4ca4c..4c09e1cd1406 100644 --- a/monkestation/code/modules/cassettes/dj/mob_can_hear.dm +++ b/monkestation/code/modules/cassettes/dj/mob_can_hear.dm @@ -16,10 +16,14 @@ GLOBAL_LIST_EMPTY_TYPED(music_listeners, /mob) /mob/proc/on_can_hear_music_trait_gain(datum/source) SIGNAL_HANDLER - GLOB.music_listeners |= src + 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) From 77f45abde4ebb7744b077f27d512fd81f7b86120 Mon Sep 17 00:00:00 2001 From: Lucy Date: Tue, 31 Dec 2024 12:19:17 -0500 Subject: [PATCH 06/11] radio mics also always allow hearing music --- monkestation/code/modules/cassettes/machines/radio_mic.dm | 8 ++++++++ 1 file changed, 8 insertions(+) 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 From a8697442bdd1cbe2cac1da0e5631bd2648e27b45 Mon Sep 17 00:00:00 2001 From: Lucy Date: Tue, 31 Dec 2024 12:20:48 -0500 Subject: [PATCH 07/11] additional sanity check in the dj_music field --- monkestation/code/modules/cassettes/dj/dj_music_field.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkestation/code/modules/cassettes/dj/dj_music_field.dm b/monkestation/code/modules/cassettes/dj/dj_music_field.dm index 21bde8d71997..24649b8499c5 100644 --- a/monkestation/code/modules/cassettes/dj/dj_music_field.dm +++ b/monkestation/code/modules/cassettes/dj/dj_music_field.dm @@ -26,7 +26,7 @@ remove_mob(inner_mob) /datum/proximity_monitor/advanced/dj_music/proc/add_mob(mob/living/target) - if(!isliving(target) || QDELING(target) || (target in listeners)) + if(QDELING(src) || !isliving(target) || QDELING(target) || (target in listeners)) return LAZYADD(listeners, target) ADD_TRAIT(target, TRAIT_CAN_HEAR_MUSIC, REF(src)) From 8f0999116bc76c9f5d060b43fcc459e9805fec24 Mon Sep 17 00:00:00 2001 From: Lucy Date: Tue, 31 Dec 2024 13:09:44 -0500 Subject: [PATCH 08/11] dj music field is now recursive --- .../modules/cassettes/dj/dj_music_field.dm | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/monkestation/code/modules/cassettes/dj/dj_music_field.dm b/monkestation/code/modules/cassettes/dj/dj_music_field.dm index 24649b8499c5..7ae01f489138 100644 --- a/monkestation/code/modules/cassettes/dj/dj_music_field.dm +++ b/monkestation/code/modules/cassettes/dj/dj_music_field.dm @@ -9,24 +9,32 @@ remove_mob(listener) return ..() -/datum/proximity_monitor/advanced/dj_music/field_turf_crossed(mob/living/crosser, turf/old_location, turf/new_location) +/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(mob/living/crosser, turf/old_location, turf/new_location) +/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/mob/living/inner_mob as anything in target) - add_mob(inner_mob) + 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/mob/living/inner_mob as anything in target) - remove_mob(inner_mob) + 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) || (target in listeners)) + 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)) From dcfbaff8cb513dd84c4616a3f0d5e244c7880296 Mon Sep 17 00:00:00 2001 From: Lucy Date: Tue, 31 Dec 2024 13:29:46 -0500 Subject: [PATCH 09/11] Begin refactoring the cassette tapes themselves. --- .../code/modules/cassettes/cassette.dm | 99 ++++++++----------- .../cassettes/cassette_db/cassette_manager.dm | 4 +- 2 files changed, 42 insertions(+), 61 deletions(-) diff --git a/monkestation/code/modules/cassettes/cassette.dm b/monkestation/code/modules/cassettes/cassette.dm index 3fc36c21f793..70bf94df0ef0 100644 --- a/monkestation/code/modules/cassettes/cassette.dm +++ b/monkestation/code/modules/cassettes/cassette.dm @@ -5,82 +5,63 @@ 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 + /// 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 + /// The data for this cassette. + var/datum/cassette/cassette_data ///are we random? var/random = FALSE - var/cassette_desc_string = "Generic Desc" /obj/item/cassette_tape/Initialize(mapload, spawned_id) . = ..() - if(!length(GLOB.approved_ids)) - GLOB.approved_ids = initialize_approved_ids() + if(!isnull(spawned_id)) + cassette_data = SScassettes.load_cassette(spawned_id) + cassette_data ||= new + update_appearance(UPDATE_DESC | UPDATE_ICON_STATE) - 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 - - 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"] - - update_appearance() +/obj/item/cassette_tape/Destroy(force) + cassette_data = null + return ..() /obj/item/cassette_tape/attack_self(mob/user) - ..() - icon_state = flipped ? side1_icon : side2_icon + . = ..() 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/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])") + + 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 +73,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,10 +82,8 @@ if(!newdesc) to_chat(user, span_warning("That description is invalid.")) return - cassette_desc_string = newdesc - update_appearance() - else - return + cassette_data.desc = newdesc + update_appearance(UPDATE_DESC) /obj/item/cassette_tape/blank id = "blank" diff --git a/monkestation/code/modules/cassettes/cassette_db/cassette_manager.dm b/monkestation/code/modules/cassettes/cassette_db/cassette_manager.dm index 0f8c26c6f58c..5ee1b55e5fd5 100644 --- a/monkestation/code/modules/cassettes/cassette_db/cassette_manager.dm +++ b/monkestation/code/modules/cassettes/cassette_db/cassette_manager.dm @@ -28,10 +28,12 @@ SUBSYSTEM_DEF(cassettes) /// 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) as /datum/cassette +/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)) From ababe6124ea28fcfd4f5d68fd1653ad2dd9a041f Mon Sep 17 00:00:00 2001 From: Lucy Date: Tue, 31 Dec 2024 16:11:39 -0500 Subject: [PATCH 10/11] more work --- .../code/modules/cassettes/cassette.dm | 9 ++++++- .../cassettes/cassette_db/cassette_manager.dm | 24 +++++++++++++++---- .../cassettes/machines/cassette_rack.dm | 22 +++++++---------- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/monkestation/code/modules/cassettes/cassette.dm b/monkestation/code/modules/cassettes/cassette.dm index 70bf94df0ef0..06aed098d961 100644 --- a/monkestation/code/modules/cassettes/cassette.dm +++ b/monkestation/code/modules/cassettes/cassette.dm @@ -9,13 +9,20 @@ var/flipped = FALSE /// The data for this cassette. var/datum/cassette/cassette_data - ///are we random? + /// Should we just spawn a random cassette? var/random = FALSE + /// ID of the cassette to spawn in as by default. + var/id /obj/item/cassette_tape/Initialize(mapload, spawned_id) . = ..() + 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) diff --git a/monkestation/code/modules/cassettes/cassette_db/cassette_manager.dm b/monkestation/code/modules/cassettes/cassette_db/cassette_manager.dm index 5ee1b55e5fd5..043786409120 100644 --- a/monkestation/code/modules/cassettes/cassette_db/cassette_manager.dm +++ b/monkestation/code/modules/cassettes/cassette_db/cassette_manager.dm @@ -156,15 +156,29 @@ SUBSYSTEM_DEF(cassettes) ids |= id rustg_file_write(ids, CASSETTE_ID_FILE) -/// Gets all the cassettes authored by the given ckey. -/datum/controller/subsystem/cassettes/proc/get_cassettes_by_ckey(user_ckey) as /list +/// 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() - user_ckey = ckey(user_ckey) + 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(cassette.author.ckey == user_ckey) - . += cassette + 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()) diff --git a/monkestation/code/modules/cassettes/machines/cassette_rack.dm b/monkestation/code/modules/cassettes/machines/cassette_rack.dm index 3925273a2a06..f6c683baadba 100644 --- a/monkestation/code/modules/cassettes/machines/cassette_rack.dm +++ b/monkestation/code/modules/cassettes/machines/cassette_rack.dm @@ -68,21 +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_cassettes = SScassettes.get_cassettes_by_ckey(user_ckey) - if(!length(user_cassettes)) - return FALSE var/list/existing_cassettes = list() for(var/obj/item/cassette_tape/tape in src) - if(tape.id) - existing_cassettes[tape.id] = TRUE - for(var/iter in 1 to min(max_amt, length(user_cassettes))) - var/datum/cassette/cassette = pick_n_take(user_cassettes) - if(existing_cassettes[cassette.id]) - continue - new /obj/item/cassette_tape(src, 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 + 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 From 0493d99eac5a4eca33b088a3574b373b7a0f850b Mon Sep 17 00:00:00 2001 From: Lucy Date: Fri, 3 Jan 2025 16:04:46 -0500 Subject: [PATCH 11/11] comment out some warnings and such --- code/__HELPERS/roundend.dm | 3 ++- code/__HELPERS/~monkestation-helpers/roundend.dm | 3 +++ code/modules/admin/admin_verbs.dm | 3 ++- code/modules/admin/topic.dm | 4 +++- code/modules/mob/dead/new_player/new_player.dm | 6 ------ monkestation/code/modules/cassettes/cassette_approval.dm | 3 +++ .../code/modules/cassettes/machines/portable_mixer.dm | 7 +++++-- monkestation/code/modules/cassettes/machines/postbox.dm | 4 +++- .../code/modules/cassettes/machines/stationary_mixer.dm | 3 +++ monkestation/code/modules/cassettes/walkman/_walkmen.dm | 8 ++++++++ 10 files changed, 32 insertions(+), 12 deletions(-) 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 b6bfb7fc59f0..8ad981919f8a 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 @@ -59,3 +61,4 @@ ) if(adjusted) qdel(review) +*/ diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 877a998ee651..d882ba9ba36e 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 88daf9cf16a2..e035b57447f7 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1794,6 +1794,8 @@ user_client.client_token_holder.reject_antag_token() log_admin("[user_client]'s token has been rejected by [owner].") +#warn TODO: cassette reviews +/* else if(href_list["open_music_review"]) if(!check_rights(R_ADMIN)) return @@ -1802,7 +1804,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/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/monkestation/code/modules/cassettes/cassette_approval.dm b/monkestation/code/modules/cassettes/cassette_approval.dm index 433d566d10af..e35b2500a382 100644 --- a/monkestation/code/modules/cassettes/cassette_approval.dm +++ b/monkestation/code/modules/cassettes/cassette_approval.dm @@ -1,3 +1,5 @@ +#warn TODO: cassette reviewing/approvals +/* GLOBAL_LIST_INIT(cassette_reviews, list()) #define ADMIN_OPEN_REVIEW(id) "(Open Review)" @@ -203,3 +205,4 @@ GLOBAL_LIST_INIT(cassette_reviews, list()) var/datum/cassette_review/cassette = GLOB.cassette_reviews[action] cassette.ui_interact(ui.user) +*/ diff --git a/monkestation/code/modules/cassettes/machines/portable_mixer.dm b/monkestation/code/modules/cassettes/machines/portable_mixer.dm index 332d22e5366a..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" @@ -23,7 +24,7 @@ /obj/item/device/cassette_deck/AltClick(mob/user) if(recieve || send) - eject_tape(user) + //eject_tape(user) return return ..() @@ -35,12 +36,13 @@ 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) @@ -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 01cbc8161b29..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,7 +15,7 @@ . = ..() REGISTER_REQUIRED_MAP_ITEM(1, INFINITY) - +/* /obj/machinery/cassette/mailbox/attackby(obj/item/weapon, mob/user, params) if(!istype(weapon, /obj/item/cassette_tape) || !user.client) return @@ -58,3 +59,4 @@ attacked_tape.moveToNullspace() submit_cassette_for_review(attacked_tape, user) return TRUE +*/ diff --git a/monkestation/code/modules/cassettes/machines/stationary_mixer.dm b/monkestation/code/modules/cassettes/machines/stationary_mixer.dm index f0f5958f2411..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." @@ -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/walkman/_walkmen.dm b/monkestation/code/modules/cassettes/walkman/_walkmen.dm index a275fd3c8fed..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( @@ -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) @@ -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