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 82cec3c519f6..bf66eb14fa5d 100644
--- a/_maps/map_files/BoxStation/BoxStation.dmm
+++ b/_maps/map_files/BoxStation/BoxStation.dmm
@@ -21346,7 +21346,7 @@
pixel_y = -32
},
/obj/structure/table/wood,
-/obj/item/device/cassette_tape/friday{
+/obj/item/cassette_tape/friday{
pixel_y = 2;
pixel_x = 9
},
diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm
index 580dbe6c6205..32647bd0032a 100644
--- a/_maps/map_files/MetaStation/MetaStation.dmm
+++ b/_maps/map_files/MetaStation/MetaStation.dmm
@@ -51163,15 +51163,15 @@
pixel_y = 4;
pixel_x = -6
},
-/obj/item/device/cassette_tape/blank{
+/obj/item/cassette_tape/blank{
pixel_y = 3;
pixel_x = 5
},
-/obj/item/device/cassette_tape/blank{
+/obj/item/cassette_tape/blank{
pixel_y = 3;
pixel_x = 5
},
-/obj/item/device/cassette_tape/blank{
+/obj/item/cassette_tape/blank{
pixel_y = 3;
pixel_x = 5
},
diff --git a/_maps/map_files/Voidraptor/VoidRaptor.dmm b/_maps/map_files/Voidraptor/VoidRaptor.dmm
index dbae07341eae..c1d858bd99d0 100644
--- a/_maps/map_files/Voidraptor/VoidRaptor.dmm
+++ b/_maps/map_files/Voidraptor/VoidRaptor.dmm
@@ -10015,7 +10015,7 @@
/obj/item/radio/radio_mic{
pixel_y = 7
},
-/obj/item/device/cassette_tape/friday{
+/obj/item/cassette_tape/friday{
pixel_y = -6;
pixel_x = -8
},
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 dce781859162..dfa2d3f4857c 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
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 7518b42b14c8..f461f0a49bbe 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"
@@ -608,6 +609,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"
@@ -7030,7 +7032,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"