Media tracks subsystem, jukebox UI genres

This commit is contained in:
Aronai Sieyes
2021-06-01 14:48:06 -04:00
parent 44e2d88e30
commit 7ccda9bae0
12 changed files with 145 additions and 106 deletions
+1
View File
@@ -54,6 +54,7 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G
// The numbers just define the ordering, they are meaningless otherwise.
#define INIT_ORDER_WEBHOOKS 50
#define INIT_ORDER_SQLITE 40
#define INIT_ORDER_MEDIA_TRACKS 38 // Gotta get that lobby music up, yo
#define INIT_ORDER_CHEMISTRY 35
#define INIT_ORDER_SKYBOX 30
#define INIT_ORDER_MAPPING 25
+4
View File
@@ -84,3 +84,7 @@
/proc/cmp_text_asc(a,b)
return sorttext(b,a)
/proc/cmp_media_track_asc(datum/track/A, datum/track/B)
var/genre_sort = sorttext(B.genre || "Uncategorized", A.genre || "Uncategorized")
return genre_sort || sorttext(B.title, A.title)
+5
View File
@@ -294,6 +294,8 @@ var/list/gamemode_cache = list()
var/disable_webhook_embeds = FALSE
var/static/list/jukebox_track_files
/datum/configuration/New()
var/list/L = typesof(/datum/game_mode) - /datum/game_mode
for (var/T in L)
@@ -939,6 +941,9 @@ var/list/gamemode_cache = list()
if("enable_night_shifts")
config.enable_night_shifts = TRUE
if("jukebox_track_files")
config.jukebox_track_files = splittext(value, ";")
// VOREStation Edit Start - Can't be in _vr file because it is loaded too late.
if("vgs_access_identifier")
@@ -0,0 +1,67 @@
SUBSYSTEM_DEF(media_tracks)
name = "Media Tracks"
flags = SS_NO_FIRE
init_order = INIT_ORDER_MEDIA_TRACKS
/// Every track, including secret
var/list/all_tracks = list()
/// Non-secret jukebox tracks
var/list/jukebox_tracks = list()
/// Lobby music tracks
var/list/lobby_tracks = list()
/datum/controller/subsystem/media_tracks/Initialize()
load_tracks()
sort_tracks()
/datum/controller/subsystem/media_tracks/proc/load_tracks()
for(var/filename in config.jukebox_track_files)
report_progress("Loading jukebox track: [filename]")
if(!fexists(filename))
error("File not found: [filename]")
continue
var/list/jsonData = json_decode(file2text(filename))
if(!istype(jsonData))
error("Failed to read tracks from [filename], json_decode failed.")
continue
for(var/entry in jsonData)
// Critical problems that will prevent the track from working
if(!istext(entry["url"]))
error("Jukebox entry in [filename]: bad or missing 'url'. Tracks must have a URL.")
continue
if(!istext(entry["title"]))
error("Jukebox entry in [filename]: bad or missing 'title'. Tracks must have a title.")
continue
if(!isnum(entry["duration"]))
error("Jukebox entry in [filename]: bad or missing 'duration'. Tracks must have a duration (in deciseconds).")
continue
// Noncritical problems, we can keep going anyway, but warn so it can be fixed
if(!istext(entry["artist"]))
warning("Jukebox entry in [filename], [entry["title"]]: bad or missing 'artist'. Please consider crediting the artist.")
if(!istext(entry["genre"]))
warning("Jukebox entry in [filename], [entry["title"]]: bad or missing 'genre'. Please consider adding a genre.")
var/datum/track/T = new(entry["url"], entry["title"], entry["duration"], entry["artist"], entry["genre"])
T.secret = entry["secret"] ? 1 : 0
T.lobby = entry["lobby"] ? 1 : 0
all_tracks += T
/datum/controller/subsystem/media_tracks/proc/sort_tracks()
report_progress("Sorting media tracks...")
sortTim(all_tracks, /proc/cmp_media_track_asc)
jukebox_tracks.Cut()
for(var/datum/track/T in all_tracks)
if(!T.secret)
jukebox_tracks += T
if(T.lobby)
lobby_tracks += T
+17 -38
View File
@@ -33,26 +33,6 @@
var/list/queue = list()
//VOREStation Add End
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'),
new/datum/track("Stellar Transit", 'sound/ambience/space/space_serithi.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')
)
/obj/machinery/media/jukebox/Initialize()
. = ..()
@@ -68,16 +48,13 @@
// On initialization, copy our tracks from the global list
/obj/machinery/media/jukebox/Initialize()
. = ..()
if(LAZYLEN(all_jukebox_tracks)) //Global list has tracks
tracks.Cut()
secret_tracks.Cut()
for(var/datum/track/T in all_jukebox_tracks) //Load them
if(T.secret)
secret_tracks |= T
else
tracks |= T
else if(!LAZYLEN(tracks)) //We don't even have default tracks
stat |= BROKEN // No tracks configured this round!
if(!LAZYLEN(getTracksList()))
stat |= BROKEN
/obj/machinery/media/jukebox/proc/getTracksList()
PRIVATE_PROC(TRUE)
return hacked ? SSmedia_tracks.all_tracks : SSmedia_tracks.jukebox_tracks
/obj/machinery/media/jukebox/process()
if(!playing)
@@ -95,6 +72,7 @@
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
var/list/tracks = getTracksList()
switch(loop_mode)
if(JUKEMODE_NEXT)
var/curTrackIndex = max(1, tracks.Find(current_track))
@@ -119,19 +97,16 @@
if(current_track && playing)
media_url = current_track.url
media_start_time = world.time
visible_message("<span class='notice'>\The [src] begins to play [current_track.display()].</span>")
audible_message("<span class='notice'>\The [src] begins to play [current_track.display()].</span>", runemessage = "[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
if(hacked == newhacked)
return
hacked = newhacked
if (hacked)
tracks.Add(secret_tracks)
else
tracks.Remove(secret_tracks)
updateDialog()
/obj/machinery/media/jukebox/attackby(obj/item/W as obj, mob/user as mob)
@@ -217,13 +192,15 @@
data["volume"] = volume
data["current_track_ref"] = null
data["current_track"] = null
data["current_genre"] = null
if(current_track)
data["current_track_ref"] = "\ref[current_track]" // Convenient shortcut
data["current_track"] = current_track.toTguiList()
data["current_genre"] = current_track.genre
data["percent"] = playing ? min(100, round(world.time - media_start_time) / current_track.duration) : 0;
var/list/tgui_tracks = list()
for(var/datum/track/T in tracks)
for(var/datum/track/T in getTracksList())
tgui_tracks.Add(list(T.toTguiList()))
data["tracks"] = tgui_tracks
@@ -235,7 +212,7 @@
switch(action)
if("change_track")
var/datum/track/T = locate(params["change_track"]) in tracks
var/datum/track/T = locate(params["change_track"]) in getTracksList()
if(istype(T))
current_track = T
StartPlaying()
@@ -336,6 +313,7 @@
// Advance to the next track - Don't start playing it unless we were already playing
/obj/machinery/media/jukebox/proc/NextTrack()
var/list/tracks = getTracksList()
if(!tracks.len) return
var/curTrackIndex = max(1, tracks.Find(current_track))
var/newTrackIndex = (curTrackIndex % tracks.len) + 1 // Loop back around if past end
@@ -346,6 +324,7 @@
// Advance to the next track - Don't start playing it unless we were already playing
/obj/machinery/media/jukebox/proc/PrevTrack()
var/list/tracks = getTracksList()
if(!tracks.len) return
var/curTrackIndex = max(1, tracks.Find(current_track))
var/newTrackIndex = curTrackIndex == 1 ? tracks.len : curTrackIndex - 1
+2 -2
View File
@@ -122,9 +122,9 @@
return rand(32000, 55000) //Frequency stuff only works with 45kbps oggs.
/client/proc/playtitlemusic()
if(!ticker || !all_lobby_tracks.len || !media) return
if(!ticker || !SSmedia_tracks.lobby_tracks.len || !media) return
if(is_preference_enabled(/datum/client_preference/play_lobby_music))
var/datum/track/T = pick(all_lobby_tracks)
var/datum/track/T = pick(SSmedia_tracks.lobby_tracks)
media.push_music(T.url, world.time, 0.85)
to_chat(src,"<span class='notice'>Lobby music: <b>[T.title]</b> by <b>[T.artist]</b>.</span>")
+5 -52
View File
@@ -6,15 +6,17 @@
/datum/track
var/url // URL to load song from
var/title // Song title
var/artist // Song's creator
var/duration // Song length in deciseconds
var/artist // Song's creator
var/genre // Musical genre
var/secret // Show up in regular playlist or secret playlist?
var/lobby // Be one of the choices for lobby music?
/datum/track/New(var/url, var/title, var/duration, var/artist = "", var/secret = 0, var/lobby = 0)
/datum/track/New(var/url, var/title, var/duration, var/artist = "", var/genre = "", var/secret = 0, var/lobby = 0)
src.url = url
src.title = title
src.artist = artist
src.genre = genre
src.duration = duration
src.secret = secret
src.lobby = lobby
@@ -26,53 +28,4 @@
return str
/datum/track/proc/toTguiList()
return list("ref" = "\ref[src]", "title" = title, "artist" = artist, "duration" = duration)
// Global list holding all configured jukebox tracks
var/global/list/all_jukebox_tracks = list()
var/global/list/all_lobby_tracks = list()
// Read the jukebox configuration file on system startup.
/hook/startup/proc/load_jukebox_tracks()
var/jukebox_track_file = "config/jukebox.json"
var/jukebox_track_file_private = "config/jukebox_private.json" // Uncommitted
if(!fexists(jukebox_track_file))
warning("File not found: [jukebox_track_file]")
return 1
var/list/jsonData = json_decode(file2text(jukebox_track_file))
if(!istype(jsonData))
warning("Failed to read tracks from [jukebox_track_file], json_decode failed.")
return 1
// Optional
if(fexists(jukebox_track_file_private))
var/list/jsonData_private = json_decode(file2text(jukebox_track_file_private))
if(istype(jsonData_private))
jsonData += jsonData_private // Pack more tracks in
else
warning("Failed to read tracks from [jukebox_track_file_private], json_decode failed.")
//return 1 // Not worth failing over, as this file is optional
for(var/entry in jsonData)
if(!istext(entry["url"]))
warning("Jukebox entry [entry]: bad or missing 'url'")
continue
if(!istext(entry["title"]))
warning("Jukebox entry [entry]: bad or missing 'title'")
continue
if(!isnum(entry["duration"]))
warning("Jukebox entry [entry]: bad or missing 'duration'")
continue
var/datum/track/T = new(entry["url"], entry["title"], entry["duration"])
if(istext(entry["artist"]))
T.artist = entry["artist"]
T.secret = entry["secret"] ? 1 : 0
T.lobby = entry["lobby"] ? 1 : 0
all_jukebox_tracks += T
if(T.lobby)
all_lobby_tracks += T
return 1
return list("ref" = "\ref[src]", "title" = title, "artist" = artist, "genre" = genre, "duration" = duration)
+7
View File
@@ -579,3 +579,10 @@ LOADOUT_WHITELIST 1
# Allowed time from death to defib in minutes
DEFIB_TIMER 60
# Jukebox track files to load, JSON format. Keys include:
# REQUIRED: url (str), title (str), duration (num, in DS)
# SUGGESTED: artist (str), genre (str)
# OPTIONAL: secret (bool), lobby (bool)
#JUKEBOX_TRACK_FILES config/jukebox.json;config/jukebox_private.json
JUKEBOX_TRACK_FILES config/jukebox.json
+2 -1
View File
@@ -17,6 +17,7 @@ export class Collapsible extends Component {
const {
children,
color = 'default',
child_mt = 1,
title,
buttons,
...rest
@@ -41,7 +42,7 @@ export class Collapsible extends Component {
)}
</div>
{open && (
<Box mt={1}>
<Box mt={child_mt}>
{children}
</Box>
)}
+33 -12
View File
@@ -1,7 +1,6 @@
import { round } from 'common/math';
import { sortBy } from 'common/collections';
import { useBackend } from "../backend";
import { Box, Button, LabeledList, ProgressBar, Section, Slider } from "../components";
import { Box, Button, Collapsible, LabeledList, ProgressBar, Section, Slider } from "../components";
import { Window } from "../layouts";
export const Jukebox = (props, context) => {
@@ -13,10 +12,22 @@ export const Jukebox = (props, context) => {
volume,
current_track_ref,
current_track,
current_genre,
percent,
tracks,
} = data;
let genre_songs = tracks.length && tracks.reduce((acc, obj) => {
let key = obj.genre || "Uncategorized";
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(obj);
return acc;
}, {});
let true_genre = playing && (current_genre || "Uncategorized");
return (
<Window width={450} height={600} resizable>
<Window.Content scrollable>
@@ -96,15 +107,25 @@ export const Jukebox = (props, context) => {
</LabeledList>
</Section>
<Section title="Available Tracks">
{tracks.length && sortBy(a => a.title)(tracks).map(track => (
<Button
fluid
icon="play"
key={track.ref}
selected={current_track_ref === track.ref}
onClick={() => act("change_track", { change_track: track.ref })}>
{track.title}
</Button>
{tracks.length && Object.keys(genre_songs).sort().map(genre => (
<Collapsible
title={genre}
key={genre}
color={true_genre === genre ? "green" : "default"}
child_mt={0}>
<div style={{ "margin-left": "1em" }}>
{genre_songs[genre].map(track => (
<Button
fluid
icon="play"
key={track.ref}
selected={current_track_ref === track.ref}
onClick={() => act("change_track", { change_track: track.ref })}>
{track.title}
</Button>
))}
</div>
</Collapsible>
)) || (
<Box color="bad">
Error: No songs loaded.
@@ -114,4 +135,4 @@ export const Jukebox = (props, context) => {
</Window.Content>
</Window>
);
};
};
File diff suppressed because one or more lines are too long
+1
View File
@@ -272,6 +272,7 @@
#include "code\controllers\subsystems\lighting.dm"
#include "code\controllers\subsystems\machines.dm"
#include "code\controllers\subsystems\mapping.dm"
#include "code\controllers\subsystems\media_tracks.dm"
#include "code\controllers\subsystems\mobs.dm"
#include "code\controllers\subsystems\nightshift.dm"
#include "code\controllers\subsystems\open_space.dm"