diff --git a/code/game/machinery/jukebox.dm b/code/game/machinery/jukebox.dm index 4eb0e7d408..9ee5a3f70b 100644 --- a/code/game/machinery/jukebox.dm +++ b/code/game/machinery/jukebox.dm @@ -1,10 +1,12 @@ -datum/track - var/title - var/sound +// +// Media Player Jukebox +// Rewritten by Leshana from existing Polaris code, merging in D2K5 and N3X15 work +// -datum/track/New(var/title_name, var/audio) - title = title_name - sound = audio +#define JUKEMODE_NEXT 1 // Advance to next song in the track list +#define JUKEMODE_RANDOM 2 // Not shuffle, randomly picks next each time. +#define JUKEMODE_REPEAT_SONG 3 // Play the same song over and over +#define JUKEMODE_PLAY_ONCE 4 // Play, then stop. /obj/machinery/media/jukebox/ name = "space jukebox" @@ -19,33 +21,17 @@ datum/track/New(var/title_name, var/audio) active_power_usage = 100 circuit = /obj/item/weapon/circuitboard/jukebox - var/playing = 0 - // Vars for hacking var/datum/wires/jukebox/wires = null var/hacked = 0 // Whether to show the hidden songs or not - var/freq = 0 + var/freq = 0 // Currently no effect, will return in phase II of mediamanager. - var/datum/track/current_track - var/list/datum/track/tracks = list( - new/datum/track("Beyond", 'sound/ambience/ambispace.ogg'), - new/datum/track("Clouds of Fire", 'sound/music/clouds.s3m'), - new/datum/track("D`Bert", 'sound/music/title2.ogg'), - new/datum/track("D`Fort", 'sound/ambience/song_game.ogg'), - new/datum/track("Floating", 'sound/music/main.ogg'), - new/datum/track("Endless Space", 'sound/music/space.ogg'), - new/datum/track("Part A", 'sound/misc/TestLoop1.ogg'), - new/datum/track("Scratch", 'sound/music/title1.ogg'), - new/datum/track("Trai`Tor", 'sound/music/traitor.ogg'), - ) - - // Only visible if hacked - var/list/datum/track/secret_tracks = list( - new/datum/track("Clown", 'sound/music/clown.ogg'), - new/datum/track("Space Asshole", 'sound/music/space_asshole.ogg'), - new/datum/track("Thunderdome", 'sound/music/THUNDERDOME.ogg'), - new/datum/track("Russkiy rep Diskoteka", 'sound/music/russianrapdisco.ogg') - ) + var/loop_mode = JUKEMODE_PLAY_ONCE // Behavior when finished playing a song + var/max_queue_len = 3 // How many songs are we allowed to queue up? + var/datum/track/current_track // Currently playing song + var/list/datum/track/queue = list() // Queued songs + var/list/datum/track/tracks = list() // Available tracks + var/list/datum/track/secret_tracks = list() // Only visible if hacked /obj/machinery/media/jukebox/New() ..() @@ -54,11 +40,70 @@ datum/track/New(var/title_name, var/audio) update_icon() /obj/machinery/media/jukebox/Destroy() - StopPlaying() qdel(wires) wires = null ..() +// On initialization, copy our tracks from the global list +/obj/machinery/media/jukebox/initialize() + ..() + if(all_jukebox_tracks.len < 1) + stat |= BROKEN // No tracks configured this round! + return + // Ootherwise load from the global list! + for(var/datum/track/T in all_jukebox_tracks) + if(T.secret) + secret_tracks |= T + else + tracks |= T + return + +/obj/machinery/media/jukebox/process() + if(!playing) + return + if(inoperable()) + disconnect_media_source() + playing = 0 + return + // If the current track isn't finished playing, let it keep going + if(current_track && world.time < media_start_time + current_track.duration) + return + // Otherwise time to pick a new one! + if(queue.len > 0) + current_track = queue[1] + queue.Cut(1, 2) // Remove the item we just took off the list + else + // Oh... nothing in queue? Well then pick next according to our rules + switch(loop_mode) + if(JUKEMODE_NEXT) + var/curTrackIndex = max(1, tracks.Find(current_track)) + var/newTrackIndex = (curTrackIndex % tracks.len) + 1 // Loop back around if past end + current_track = tracks[newTrackIndex] + if(JUKEMODE_RANDOM) + var/previous_track = current_track + do + current_track = pick(tracks) + while(current_track == previous_track && tracks.len > 1) + if(JUKEMODE_REPEAT_SONG) + current_track = current_track + if(JUKEMODE_PLAY_ONCE) + current_track = null + playing = 0 + update_icon() + updateDialog() + start_stop_song() + +// Tells the media manager to start or stop playing based on current settings. +/obj/machinery/media/jukebox/proc/start_stop_song() + if(current_track && playing) + media_url = current_track.url + media_start_time = world.time + visible_message("\The [src] begins to play [current_track.display()].") + else + media_url = "" + media_start_time = 0 + update_music() + /obj/machinery/media/jukebox/proc/set_hacked(var/newhacked) if (hacked == newhacked) return hacked = newhacked @@ -80,13 +125,16 @@ datum/track/New(var/title_name, var/audio) if(istype(W, /obj/item/device/multitool)) return wires.Interact(user) if(istype(W, /obj/item/weapon/wrench)) - if(playing) - StopPlaying() user.visible_message("[user] has [anchored ? "un" : ""]secured \the [src].", "You [anchored ? "un" : ""]secure \the [src].") anchored = !anchored playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1) power_change() update_icon() + if(!anchored) + playing = 0 + disconnect_media_source() + else + update_media_source() return return ..() @@ -125,16 +173,23 @@ datum/track/New(var/title_name, var/audio) usr << "You must secure \the [src] first." return - if(stat & (NOPOWER|BROKEN)) + if(inoperable()) usr << "\The [src] doesn't appear to function." return if(href_list["change_track"]) - for(var/datum/track/T in tracks) - if(T.title == href_list["title"]) - current_track = T - StartPlaying() - break + var/datum/track/T = locate(href_list["change_track"]) in tracks + if(istype(T)) + current_track = T + StartPlaying() + else if(href_list["loopmode"]) + var/newval = text2num(href_list["loopmode"]) + loop_mode = sanitize_inlist(newval, list(JUKEMODE_NEXT, JUKEMODE_RANDOM, JUKEMODE_REPEAT_SONG, JUKEMODE_PLAY_ONCE), loop_mode) + else if(href_list["volume"]) + var/newval = input("Choose Jukebox volume (0-100%)", "Jukebox volume", round(volume * 100.0)) + newval = sanitize_integer(text2num(newval), min = 0, max = 100, default = volume * 100.0) + volume = newval / 100.0 + update_music() // To broadcast volume change without restarting song else if(href_list["stop"]) StopPlaying() else if(href_list["play"]) @@ -162,36 +217,39 @@ datum/track/New(var/title_name, var/audio) return 1 /obj/machinery/media/jukebox/interact(mob/user) - if(stat & (NOPOWER|BROKEN)) + if(inoperable()) usr << "\The [src] doesn't appear to function." return - ui_interact(user) /obj/machinery/media/jukebox/ui_interact(mob/user, ui_key = "jukebox", var/datum/nanoui/ui = null, var/force_open = 1) var/title = "RetroBox - Space Style" var/data[0] - if(!(stat & (NOPOWER|BROKEN))) - data["current_track"] = current_track != null ? current_track.title : "" + if(operable()) data["playing"] = playing + data["hacked"] = hacked + data["max_queue_len"] = max_queue_len + data["media_start_time"] = media_start_time + data["loop_mode"] = loop_mode + data["volume"] = volume + if(current_track) + data["current_track_ref"] = "\ref[current_track]" // Convenient shortcut + data["current_track"] = current_track.toNanoList() + data["percent"] = playing ? min(100, round(world.time - media_start_time) / current_track.duration) : 0; var/list/nano_tracks = new for(var/datum/track/T in tracks) - nano_tracks[++nano_tracks.len] = list("track" = T.title) - + nano_tracks[++nano_tracks.len] = T.toNanoList() data["tracks"] = nano_tracks // update the ui if it exists, returns null if no ui is passed/found ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open) if (!ui) - // the ui does not exist, so we'll create a new() one - // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm ui = new(user, src, ui_key, "jukebox.tmpl", title, 450, 600) - // when the ui is first opened this is the data it will use ui.set_initial_data(data) - // open the new ui window ui.open() + ui.set_auto_update(playing) /obj/machinery/media/jukebox/attack_ai(mob/user as mob) return src.attack_hand(user) @@ -212,24 +270,6 @@ datum/track/New(var/title_name, var/audio) new /obj/effect/decal/cleanable/blood/oil(src.loc) qdel(src) -/obj/machinery/media/jukebox/attackby(obj/item/W as obj, mob/user as mob) - src.add_fingerprint(user) - - if(default_deconstruction_screwdriver(user, W)) - return - if(default_deconstruction_crowbar(user, W)) - return - if(istype(W, /obj/item/weapon/wrench)) - if(playing) - StopPlaying() - user.visible_message("[user] has [anchored ? "un" : ""]secured \the [src].", "You [anchored ? "un" : ""]secure \the [src].") - anchored = !anchored - playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1) - power_change() - update_icon() - return - return ..() - /obj/machinery/media/jukebox/emag_act(var/remaining_charges, var/mob/user) if(!emagged) emagged = 1 @@ -239,37 +279,19 @@ datum/track/New(var/title_name, var/audio) return 1 /obj/machinery/media/jukebox/proc/StopPlaying() - var/area/main_area = get_area(src) - // Always kill the current sound - for(var/mob/living/M in mobs_in_area(main_area)) - M << sound(null, channel = 1) - - main_area.forced_ambience = null playing = 0 update_use_power(1) update_icon() - + start_stop_song() /obj/machinery/media/jukebox/proc/StartPlaying() - StopPlaying() if(!current_track) return - - var/area/main_area = get_area(src) - if(freq) - var/sound/new_song = sound(current_track.sound, channel = 1, repeat = 1, volume = 25) - new_song.frequency = freq - main_area.forced_ambience = list(new_song) - else - main_area.forced_ambience = list(current_track.sound) - - for(var/mob/living/M in mobs_in_area(main_area)) - if(M.mind) - main_area.play_ambience(M) - playing = 1 update_use_power(2) update_icon() + start_stop_song() + updateDialog() // Advance to the next track - Don't start playing it unless we were already playing /obj/machinery/media/jukebox/proc/NextTrack() @@ -278,7 +300,7 @@ datum/track/New(var/title_name, var/audio) var/newTrackIndex = (curTrackIndex % tracks.len) + 1 // Loop back around if past end current_track = tracks[newTrackIndex] if(playing) - StartPlaying() + start_stop_song() updateDialog() // Advance to the next track - Don't start playing it unless we were already playing @@ -288,5 +310,5 @@ datum/track/New(var/title_name, var/audio) var/newTrackIndex = curTrackIndex == 1 ? tracks.len : curTrackIndex - 1 current_track = tracks[newTrackIndex] if(playing) - StartPlaying() + start_stop_song() updateDialog() diff --git a/code/game/machinery/jukebox_vr.dm b/code/game/machinery/jukebox_vr.dm deleted file mode 100644 index 290bb9688a..0000000000 --- a/code/game/machinery/jukebox_vr.dm +++ /dev/null @@ -1,45 +0,0 @@ -// -// VOREStation Custom - Configurable Jukebox! -// - -/datum/track - var/secret = 0 // Whether or not this is a SECRET TRACK OOOOOH - -// On initialization, copy our tracks from the global list -/obj/machinery/media/jukebox/initialize() - ..() - if(all_jukebox_tracks.len) - tracks.Cut() - secret_tracks.Cut() - for(var/datum/track/T in all_jukebox_tracks) - if(T.secret) - secret_tracks += T - else - tracks += T - return - -// Global list holding all configured jukebox tracks -var/global/list/all_jukebox_tracks = list() - -// Read the jukebox configuration file on system startup. -/hook/startup/proc/load_jukebox_tracks() - var/jukebox_track_file = "config/jukebox.txt" - if(!fexists(jukebox_track_file)) - warning("File not found: [jukebox_track_file]") - return - // Helpful regex that ignores comments and parses our file format - var/regex/lineSplitter = regex("^(?!#)(.+?)\\|(.+?)\\|(.+)$") - var/list/Lines = file2list(jukebox_track_file) - for(var/t in Lines) - if(!t) continue - if(!lineSplitter.Find(t)) continue - var/file = trim(lineSplitter.group[1]) - var/title = trim(lineSplitter.group[2]) - var/isSecret = text2num(trim(lineSplitter.group[3])) - if(!fexists(file)) - warning("In [jukebox_track_file], sound file file not found: [file]") - continue - var/datum/track/T = new(title, file(file)) - T.secret = isSecret ? 1 : 0 - all_jukebox_tracks += T - return 1 diff --git a/code/modules/client/preference_setup/global/06_media.dm b/code/modules/client/preference_setup/global/06_media.dm new file mode 100644 index 0000000000..cd6a12a6f9 --- /dev/null +++ b/code/modules/client/preference_setup/global/06_media.dm @@ -0,0 +1,53 @@ +/datum/preferences + var/media_volume = 1 + var/media_player = 2 // 0 = VLC, 1 = WMP, 2 = HTML5, 3+ = unassigned + +/datum/category_item/player_setup_item/player_global/media + name = "Media" + sort_order = 6 + +/datum/category_item/player_setup_item/player_global/media/load_preferences(var/savefile/S) + S["media_volume"] >> pref.media_volume + S["media_player"] >> pref.media_player + +/datum/category_item/player_setup_item/player_global/media/save_preferences(var/savefile/S) + S["media_volume"] << pref.media_volume + S["media_player"] << pref.media_player + +/datum/category_item/player_setup_item/player_global/media/sanitize_preferences() + pref.media_volume = isnum(pref.media_volume) ? Clamp(pref.media_volume, 0, 1) : initial(pref.media_volume) + pref.media_player = sanitize_inlist(pref.media_player, list(0, 1, 2), initial(pref.media_player)) + +/datum/category_item/player_setup_item/player_global/media/content(var/mob/user) + . += "Jukebox Volume:" + . += "[round(pref.media_volume * 100)]%
" + . += "Media Player Type: Depending on you operating system, one of these might work better. " + . += "Use HTML5 if it works for you. If neither HTML5 nor WMP work, you'll have to fall back to using VLC, " + . += "but this requires you have the VLC client installed on your comptuer." + . += "Try the others if you want but you'll probably just get no music.
" + . += (pref.media_player == 2) ? "HTML5 " : "HTML5 " + . += (pref.media_player == 1) ? "WMP " : "WMP " + . += (pref.media_player == 0) ? "VLC " : "VLC " + . += "
" + +/datum/category_item/player_setup_item/player_global/media/OnTopic(var/href, var/list/href_list, var/mob/user) + if(href_list["change_media_volume"]) + if(CanUseTopic(user)) + var/value = input("Choose your Jukebox volume (0-100%)", "Jukebox volume", round(pref.media_volume * 100)) + if(isnum(value)) + value = Clamp(value, 0, 100) + pref.media_volume = value/100.0 + if(user.client && user.client.media) + user.client.media.update_volume(pref.media_volume) + return TOPIC_REFRESH + else if(href_list["set_media_player"]) + if(CanUseTopic(user)) + var/newval = sanitize_inlist(text2num(href_list["set_media_player"]), list(0, 1, 2), pref.media_player) + if(newval != pref.media_player) + pref.media_player = newval + if(user.client && user.client.media) + user.client.media.open() + spawn(10) + user.update_music() + return TOPIC_REFRESH + return ..() diff --git a/code/modules/client/preference_setup/global/setting_datums.dm b/code/modules/client/preference_setup/global/setting_datums.dm index 71559fed0e..675cbdf7a7 100644 --- a/code/modules/client/preference_setup/global/setting_datums.dm +++ b/code/modules/client/preference_setup/global/setting_datums.dm @@ -74,6 +74,16 @@ var/list/_client_preferences_by_type preference_mob << sound(null, repeat = 0, wait = 0, volume = 0, channel = 1) preference_mob << sound(null, repeat = 0, wait = 0, volume = 0, channel = 2) +/datum/client_preference/play_jukebox + description ="Play jukebox music" + key = "SOUND_JUKEBOX" + +/datum/client_preference/play_jukebox/toggled(var/mob/preference_mob, var/enabled) + if(!enabled) + preference_mob.stop_all_music() + else + preference_mob.update_music() + /datum/client_preference/ghost_ears description ="Ghost ears" key = "CHAT_GHOSTEARS" diff --git a/code/modules/media/media_machinery.dm b/code/modules/media/media_machinery.dm new file mode 100644 index 0000000000..42816ca51a --- /dev/null +++ b/code/modules/media/media_machinery.dm @@ -0,0 +1,74 @@ +// Machinery serving as a media source. +/obj/machinery/media + var/playing = 0 // Am I playing right now? + var/media_url = "" // URL of media I am playing + var/media_start_time = 0 // world.time when it started playing + var/volume = 1 // 0 - 1 for ease of coding. + + var/area/master_area // My area + + // ~Leshana - Transmitters unimplemented + +// Notify everyone in the area of new music. +// YOU MUST SET MEDIA_URL AND MEDIA_START_TIME YOURSELF! +/obj/machinery/media/proc/update_music() + update_media_source() + // Bail if we lost connection to master. + if(!master_area) + return + // Send update to clients. + for(var/mob/M in mobs_in_area(master_area)) + if(M && M.client) + M.update_music() + +/obj/machinery/media/proc/update_media_source() + var/area/A = get_area_master(src) + if(!A) + return + // Check if there's a media source already. + if(A.media_source && A.media_source != src) // If it does, the new media source replaces it. basically, the last media source arrived gets played on top. + A.media_source.disconnect_media_source() // You can turn a media source off and on for it to come back on top. + A.media_source = src + master_area = A + return + else + A.media_source = src + master_area = A + +/obj/machinery/media/proc/disconnect_media_source() + var/area/A = get_area_master(src) + // Sanity + if(!A) + master_area = null + return + // Check if there's a media source already. + if(A && A.media_source && A.media_source != src) + master_area = null + return + // Update Media Source. + A.media_source = null + // Clients + for(var/mob/M in mobs_in_area(A)) + if(M && M.client) + M.update_music() + master_area = null + +/obj/machinery/media/Move() + ..() + if(anchored) + update_music() + +/obj/machinery/media/forceMove(var/atom/destination) + disconnect_media_source() + ..() + if(anchored) + update_music() + +/obj/machinery/media/initialize() + ..() + update_media_source() + +/obj/machinery/media/Destroy() + disconnect_media_source() + ..() + diff --git a/code/modules/media/media_player_html5.dm b/code/modules/media/media_player_html5.dm new file mode 100644 index 0000000000..b5093db567 --- /dev/null +++ b/code/modules/media/media_player_html5.dm @@ -0,0 +1,27 @@ +// IT IS FINALLY TIME. IT IS HERE. Converted to HTML5