mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-29 16:18:01 +01:00
[MIRROR] Refactor jukebox, jukebox datum, now jukebox audio is positional. And it fully respects deaf people. (#26301)
* Refactor jukebox, jukebox datum, now jukebox audio is positional. And it fully respects deaf people. * fix our stuff --------- Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Co-authored-by: jjpark-kb <mccorvey.norman@gmail.com>
This commit is contained in:
co-authored by
MrMelbert
jjpark-kb
parent
972e517cfe
commit
3d28268ea0
@@ -150,7 +150,6 @@
|
||||
},
|
||||
/obj/effect/turf_decal/siding/brown,
|
||||
/obj/machinery/jukebox{
|
||||
active = 1;
|
||||
req_access = null
|
||||
},
|
||||
/turf/open/floor/iron,
|
||||
|
||||
@@ -0,0 +1,406 @@
|
||||
/* SKYRAT EDIT: See modular_skyrat/modules/jukebox /// Checks if the mob has jukebox muted in their preferences
|
||||
#define IS_PREF_MUTED(mob) (!isnull(mob.client) && !mob.client.prefs.read_preference(/datum/preference/toggle/sound_jukebox))
|
||||
|
||||
// Reasons for appling STATUS_MUTE to a mob's sound status
|
||||
/// The mob is deaf
|
||||
#define MUTE_DEAF (1<<0)
|
||||
/// The mob has disabled jukeboxes in their preferences
|
||||
#define MUTE_PREF (1<<1)
|
||||
/// The mob is out of range of the jukebox
|
||||
#define MUTE_RANGE (1<<2)
|
||||
|
||||
/**
|
||||
* ## Jukebox datum
|
||||
*
|
||||
* Plays music to nearby mobs when hosted in a movable or a turf.
|
||||
*/
|
||||
/datum/jukebox
|
||||
/// Atom that hosts the jukebox. Can be a turf or a movable.
|
||||
VAR_FINAL/atom/parent
|
||||
/// List of /datum/tracks we can play. Set via get_songs().
|
||||
VAR_FINAL/list/songs = list()
|
||||
/// Current song track selected
|
||||
VAR_FINAL/datum/track/selection
|
||||
/// Current song datum playing
|
||||
VAR_FINAL/sound/active_song_sound
|
||||
/// Whether the jukebox requires a connect_range component to check for new listeners
|
||||
VAR_PROTECTED/requires_range_check = TRUE
|
||||
|
||||
/// Assoc list of all mobs listening to the jukebox to their sound status.
|
||||
VAR_PRIVATE/list/mob/listeners = list()
|
||||
|
||||
/// Volume of the songs played. Also serves as the max volume.
|
||||
/// Do not set directly, use set_new_volume() instead.
|
||||
VAR_PROTECTED/volume = 50
|
||||
|
||||
/// Range at which the sound plays to players, can also be a view "XxY" string
|
||||
VAR_PROTECTED/sound_range
|
||||
/// How far away horizontally from the jukebox can you be before you stop hearing it
|
||||
VAR_PRIVATE/x_cutoff
|
||||
/// How far away vertically from the jukebox can you be before you stop hearing it
|
||||
VAR_PRIVATE/z_cutoff
|
||||
/// Whether the music loops when done.
|
||||
/// If FALSE, you must handle ending music yourself.
|
||||
var/sound_loops = FALSE
|
||||
|
||||
/datum/jukebox/New(atom/new_parent)
|
||||
if(!ismovable(new_parent) && !isturf(new_parent))
|
||||
stack_trace("[type] created on non-turf or non-movable: [new_parent ? "[new_parent] ([new_parent.type])" : "null"])")
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
parent = new_parent
|
||||
|
||||
if(isnull(sound_range))
|
||||
sound_range = world.view
|
||||
var/list/worldviewsize = getviewsize(sound_range)
|
||||
x_cutoff = ceil(worldviewsize[1] * 1.25 / 2) // * 1.25 gives us some extra range to fade out with
|
||||
z_cutoff = ceil(worldviewsize[2] * 1.25 / 2) // and / 2 is because world view is the whole screen, and we want the centre
|
||||
|
||||
if(requires_range_check)
|
||||
var/static/list/connections = list(COMSIG_ATOM_ENTERED = PROC_REF(check_new_listener))
|
||||
AddComponent(/datum/component/connect_range, parent, connections, max(x_cutoff, z_cutoff))
|
||||
|
||||
songs = init_songs()
|
||||
if(length(songs))
|
||||
selection = songs[pick(songs)]
|
||||
|
||||
RegisterSignal(parent, COMSIG_ENTER_AREA, PROC_REF(on_enter_area))
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
|
||||
RegisterSignal(parent, COMSIG_QDELETING, PROC_REF(parent_delete))
|
||||
|
||||
/datum/jukebox/Destroy()
|
||||
unlisten_all()
|
||||
parent = null
|
||||
selection = null
|
||||
songs.Cut()
|
||||
active_song_sound = null
|
||||
return ..()
|
||||
|
||||
/// When our parent is deleted, we should go too.
|
||||
/datum/jukebox/proc/parent_delete(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
qdel(src)
|
||||
|
||||
/**
|
||||
* Initializes the track list.
|
||||
*
|
||||
* By default, this loads all tracks from the config datum.
|
||||
*
|
||||
* Returns
|
||||
* * An assoc list of track names to /datum/track. Track names must be unique.
|
||||
*/
|
||||
/datum/jukebox/proc/init_songs()
|
||||
return load_songs_from_config()
|
||||
|
||||
/// Loads the config sounds once, and returns a copy of them.
|
||||
/datum/jukebox/proc/load_songs_from_config()
|
||||
var/static/list/config_songs
|
||||
if(isnull(config_songs))
|
||||
config_songs = list()
|
||||
var/list/tracks = flist("[global.config.directory]/jukebox_music/sounds/")
|
||||
for(var/track_file in tracks)
|
||||
var/datum/track/new_track = new()
|
||||
new_track.song_path = file("[global.config.directory]/jukebox_music/sounds/[track_file]")
|
||||
var/list/track_data = splittext(track_file, "+")
|
||||
if(length(track_data) != 3)
|
||||
continue
|
||||
new_track.song_name = track_data[1]
|
||||
new_track.song_length = text2num(track_data[2])
|
||||
new_track.song_beat = text2num(track_data[3])
|
||||
config_songs[new_track.song_name] = new_track
|
||||
|
||||
if(!length(config_songs))
|
||||
var/datum/track/default/default_track = new()
|
||||
config_songs[default_track.song_name] = default_track
|
||||
|
||||
// returns a copy so it can mutate if desired.
|
||||
return config_songs.Copy()
|
||||
|
||||
/**
|
||||
* Returns a set of general data relating to the jukebox for use in TGUI.
|
||||
*
|
||||
* Returns
|
||||
* * A list of UI data
|
||||
*/
|
||||
/datum/jukebox/proc/get_ui_data()
|
||||
var/list/data = list()
|
||||
var/list/songs_data = list()
|
||||
for(var/song_name in songs)
|
||||
var/datum/track/one_song = songs[song_name]
|
||||
UNTYPED_LIST_ADD(songs_data, list( \
|
||||
"name" = song_name, \
|
||||
"length" = DisplayTimeText(one_song.song_length), \
|
||||
"beat" = one_song.song_beat, \
|
||||
))
|
||||
|
||||
data["active"] = !!active_song_sound
|
||||
data["songs"] = songs_data
|
||||
data["track_selected"] = selection?.song_name
|
||||
data["looping"] = sound_loops
|
||||
data["volume"] = volume
|
||||
return data
|
||||
|
||||
/**
|
||||
* Sets the sound's range to a new value. This can be a number or a view size string "XxY".
|
||||
* Then updates any mobs listening to it.
|
||||
*/
|
||||
/datum/jukebox/proc/set_sound_range(new_range)
|
||||
if(sound_range == new_range)
|
||||
return
|
||||
sound_range = new_range
|
||||
var/list/worldviewsize = getviewsize(sound_range)
|
||||
x_cutoff = ceil(worldviewsize[1] / 2)
|
||||
z_cutoff = ceil(worldviewsize[2] / 2)
|
||||
update_all()
|
||||
|
||||
/**
|
||||
* Sets the sound's volume to a new value.
|
||||
* Then updates any mobs listening to it.
|
||||
*/
|
||||
/datum/jukebox/proc/set_new_volume(new_vol)
|
||||
new_vol = clamp(new_vol, 0, initial(volume))
|
||||
if(volume == new_vol)
|
||||
return
|
||||
volume = new_vol
|
||||
if(!active_song_sound)
|
||||
return
|
||||
active_song_sound.volume = volume
|
||||
update_all()
|
||||
|
||||
/// Sets volume to the maximum possible value, the initial volume value.
|
||||
/datum/jukebox/proc/set_volume_to_max()
|
||||
set_new_volume(initial(volume))
|
||||
|
||||
/**
|
||||
* Sets the sound's environment to a new value.
|
||||
* Then updates any mobs listening to it.
|
||||
*/
|
||||
/datum/jukebox/proc/set_new_environment(new_env)
|
||||
if(!active_song_sound || active_song_sound.environment == new_env)
|
||||
return
|
||||
active_song_sound.environment = new_env
|
||||
update_all()
|
||||
|
||||
/// Helper to stop the music for all mobs listening to the music.
|
||||
/datum/jukebox/proc/unlisten_all()
|
||||
for(var/mob/listening as anything in listeners)
|
||||
deregister_listener(listening)
|
||||
active_song_sound = null
|
||||
|
||||
/// Helper to update all mobs currently listening to the music.
|
||||
/datum/jukebox/proc/update_all()
|
||||
for(var/mob/listening as anything in listeners)
|
||||
update_listener(listening)
|
||||
|
||||
/// Helper to kickstart the music for all mobs in hearing range of the jukebox.
|
||||
/datum/jukebox/proc/start_music()
|
||||
for(var/mob/nearby in hearers(sound_range, parent))
|
||||
register_listener(nearby)
|
||||
|
||||
/// Helper to get all mobs currently, ACTIVELY listening to the jukebox.
|
||||
/datum/jukebox/proc/get_active_listeners()
|
||||
var/list/all_listeners = list()
|
||||
for(var/mob/listener as anything in listeners)
|
||||
if(listeners[listener] & SOUND_MUTE)
|
||||
continue
|
||||
all_listeners += listener
|
||||
return all_listeners
|
||||
|
||||
/// Registers the passed mob as a new listener to the jukebox.
|
||||
/datum/jukebox/proc/register_listener(mob/new_listener)
|
||||
PROTECTED_PROC(TRUE)
|
||||
|
||||
listeners[new_listener] = NONE
|
||||
RegisterSignal(new_listener, COMSIG_QDELETING, PROC_REF(listener_deleted))
|
||||
|
||||
if(isnull(new_listener.client))
|
||||
RegisterSignal(new_listener, COMSIG_MOB_LOGIN, PROC_REF(listener_login))
|
||||
return
|
||||
|
||||
RegisterSignal(new_listener, COMSIG_MOVABLE_MOVED, PROC_REF(listener_moved))
|
||||
RegisterSignals(new_listener, list(SIGNAL_ADDTRAIT(TRAIT_DEAF), SIGNAL_REMOVETRAIT(TRAIT_DEAF)), PROC_REF(listener_deaf))
|
||||
|
||||
if(HAS_TRAIT(new_listener, TRAIT_DEAF) || IS_PREF_MUTED(new_listener))
|
||||
listeners[new_listener] |= SOUND_MUTE
|
||||
|
||||
if(isnull(active_song_sound))
|
||||
var/area/juke_area = get_area(parent)
|
||||
active_song_sound = sound(selection.song_path)
|
||||
active_song_sound.channel = CHANNEL_JUKEBOX
|
||||
active_song_sound.priority = 255
|
||||
active_song_sound.falloff = 2
|
||||
active_song_sound.volume = volume
|
||||
active_song_sound.y = 1
|
||||
active_song_sound.environment = juke_area.sound_environment || SOUND_ENVIRONMENT_NONE
|
||||
active_song_sound.repeat = sound_loops
|
||||
|
||||
update_listener(new_listener)
|
||||
// if you have a sound with status SOUND_UPDATE,
|
||||
// and try to play it to a client who is not listening to the sound already,
|
||||
// it will not work.
|
||||
// so we only add this status AFTER the first update, which plays the first sound.
|
||||
// and after that it's fine to keep it on the sound so it updates as the x/z does.
|
||||
listeners[new_listener] |= SOUND_UPDATE
|
||||
|
||||
/// Deregisters mobs on deletion.
|
||||
/datum/jukebox/proc/listener_deleted(mob/source)
|
||||
SIGNAL_HANDLER
|
||||
deregister_listener(source)
|
||||
|
||||
/// Updates the sound's position on mob movement.
|
||||
/datum/jukebox/proc/listener_moved(mob/source)
|
||||
SIGNAL_HANDLER
|
||||
update_listener(source)
|
||||
|
||||
/// Allows mobs who are clientless when the music starts to hear it when they log in.
|
||||
/datum/jukebox/proc/listener_login(mob/source)
|
||||
SIGNAL_HANDLER
|
||||
deregister_listener(source)
|
||||
register_listener(source)
|
||||
|
||||
/// Updates the sound's mute status when the mob's deafness updates.
|
||||
/datum/jukebox/proc/listener_deaf(mob/source)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(HAS_TRAIT(source, TRAIT_DEAF))
|
||||
listeners[source] |= SOUND_MUTE
|
||||
else if(!unmute_listener(source, MUTE_DEAF))
|
||||
return
|
||||
update_listener(source)
|
||||
|
||||
/**
|
||||
* Unmutes the passed mob's sound from the passed reason.
|
||||
*
|
||||
* Arguments
|
||||
* * mob/listener - The mob to unmute.
|
||||
* * reason - The reason to unmute them for. Can be a combination of MUTE_DEAF, MUTE_PREF, MUTE_RANGE.
|
||||
*/
|
||||
/datum/jukebox/proc/unmute_listener(mob/listener, reason)
|
||||
// We need to check everything BUT the reason we're unmuting for
|
||||
// Because if we're muted for a different reason we don't wanna touch it
|
||||
reason = ~reason
|
||||
|
||||
if((reason & MUTE_DEAF) && HAS_TRAIT(listener, TRAIT_DEAF))
|
||||
return FALSE
|
||||
|
||||
if((reason & MUTE_PREF) && IS_PREF_MUTED(listener))
|
||||
return FALSE
|
||||
|
||||
if(reason & MUTE_RANGE)
|
||||
var/turf/sound_turf = get_turf(parent)
|
||||
var/turf/listener_turf = get_turf(listener)
|
||||
if(isnull(sound_turf) || isnull(listener_turf))
|
||||
return FALSE
|
||||
if(sound_turf.z != listener_turf.z)
|
||||
return FALSE
|
||||
if(abs(sound_turf.x - listener_turf.x) > x_cutoff)
|
||||
return FALSE
|
||||
if(abs(sound_turf.y - listener_turf.y) > z_cutoff)
|
||||
return FALSE
|
||||
|
||||
listeners[listener] &= ~SOUND_MUTE
|
||||
return TRUE
|
||||
|
||||
/// Deregisters the passed mob as a listener to the jukebox, stopping the music.
|
||||
/datum/jukebox/proc/deregister_listener(mob/no_longer_listening)
|
||||
PROTECTED_PROC(TRUE)
|
||||
|
||||
listeners -= no_longer_listening
|
||||
no_longer_listening.stop_sound_channel(CHANNEL_JUKEBOX)
|
||||
UnregisterSignal(no_longer_listening, list(
|
||||
COMSIG_MOB_LOGIN,
|
||||
COMSIG_QDELETING,
|
||||
COMSIG_MOVABLE_MOVED,
|
||||
SIGNAL_ADDTRAIT(TRAIT_DEAF),
|
||||
SIGNAL_REMOVETRAIT(TRAIT_DEAF),
|
||||
))
|
||||
|
||||
/// Updates the passed mob's sound in according to their position and status.
|
||||
/datum/jukebox/proc/update_listener(mob/listener)
|
||||
PROTECTED_PROC(TRUE)
|
||||
|
||||
active_song_sound.status = listeners[listener] || NONE
|
||||
|
||||
var/turf/sound_turf = get_turf(parent)
|
||||
var/turf/listener_turf = get_turf(listener)
|
||||
if(isnull(sound_turf) || isnull(listener_turf)) // ??
|
||||
active_song_sound.x = 0
|
||||
active_song_sound.z = 0
|
||||
|
||||
else if(sound_turf.z != listener_turf.z) // Could MAYBE model multi-z jukeboxes but that's too complex for now
|
||||
listeners[listener] |= SOUND_MUTE
|
||||
|
||||
else
|
||||
// keep in mind sound XYZ is different to world XYZ. sound +-z = world +-y
|
||||
var/new_x = sound_turf.x - listener_turf.x
|
||||
var/new_z = sound_turf.y - listener_turf.y
|
||||
|
||||
if((abs(new_x) > x_cutoff || abs(new_z) > z_cutoff))
|
||||
listeners[listener] |= SOUND_MUTE
|
||||
|
||||
else if(listeners[listener] & SOUND_MUTE)
|
||||
unmute_listener(listener, MUTE_RANGE)
|
||||
|
||||
active_song_sound.x = new_x
|
||||
active_song_sound.z = new_z
|
||||
|
||||
SEND_SOUND(listener, active_song_sound)
|
||||
|
||||
/// When the jukebox moves, we need to update all listeners.
|
||||
/datum/jukebox/proc/on_moved(datum/source, ...)
|
||||
SIGNAL_HANDLER
|
||||
update_all()
|
||||
|
||||
/// When the jukebox enters a new area entirely, we need to update the environment to the new area's.
|
||||
/datum/jukebox/proc/on_enter_area(datum/source, area/area_to_register)
|
||||
SIGNAL_HANDLER
|
||||
set_new_environment(area_to_register.sound_environment || SOUND_ENVIRONMENT_NONE)
|
||||
|
||||
/// Check for new mobs entering the jukebox's range.
|
||||
/datum/jukebox/proc/check_new_listener(datum/source, atom/movable/entered)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(isnull(active_song_sound))
|
||||
return
|
||||
if(!ismob(entered))
|
||||
return
|
||||
if(entered in listeners)
|
||||
return
|
||||
register_listener(entered)
|
||||
|
||||
/**
|
||||
* Subtype which only plays the music to the mob you pass in via start_music().
|
||||
*
|
||||
* Multiple mobs can still listen at once, but you must register them all manually via start_music().
|
||||
*/
|
||||
/datum/jukebox/single_mob
|
||||
requires_range_check = FALSE
|
||||
|
||||
/datum/jukebox/single_mob/start_music(mob/solo_listener)
|
||||
register_listener(solo_listener)
|
||||
|
||||
#undef IS_PREF_MUTED
|
||||
|
||||
#undef MUTE_DEAF
|
||||
#undef MUTE_PREF
|
||||
#undef MUTE_RANGE
|
||||
|
||||
/// Track datums, used in jukeboxes
|
||||
/datum/track
|
||||
/// Readable name, used in the jukebox menu
|
||||
var/song_name = "generic"
|
||||
/// Filepath of the song
|
||||
var/song_path = null
|
||||
/// How long is the song in deciseconds
|
||||
var/song_length = 0
|
||||
/// How long is a beat of the song in decisconds
|
||||
/// Used to determine time between effects when played
|
||||
var/song_beat = 0
|
||||
|
||||
// Default track supplied for testing and also because it's a banger
|
||||
/datum/track/default
|
||||
song_path = 'sound/ambience/title3.ogg'
|
||||
song_name = "Tintin on the Moon"
|
||||
song_length = 3 MINUTES + 52 SECONDS
|
||||
song_beat = 1 SECONDS SKYRAT EDIT END*/
|
||||
@@ -1,122 +1,61 @@
|
||||
/* SKYRAT EDIT REMOVAL BEGIN - JUKEBOX - MOVED TO 'modular_skyrat/modules/jukebox/code/dance_machine.dm'
|
||||
/// Helper macro to check if the passed mob has jukebox sound preference enabled
|
||||
#define HAS_JUKEBOX_PREF(mob) (!QDELETED(mob) && !isnull(mob.client) && mob.client.prefs.read_preference(/datum/preference/toggle/sound_jukebox))
|
||||
|
||||
/* SKYRAT EDIT: See modular_skyrat/modules/jukebox
|
||||
/obj/machinery/jukebox
|
||||
name = "jukebox"
|
||||
desc = "A classic music player."
|
||||
icon = 'icons/obj/machines/music.dmi'
|
||||
icon_state = "jukebox"
|
||||
base_icon_state = "jukebox"
|
||||
verb_say = "states"
|
||||
density = TRUE
|
||||
req_access = list(ACCESS_BAR)
|
||||
/// Whether we're actively playing music
|
||||
var/active = FALSE
|
||||
/// List of weakrefs to mobs listening to the current song
|
||||
var/list/datum/weakref/rangers = list()
|
||||
/// World.time when the current song will stop playing, but also a cooldown between activations
|
||||
var/stop = 0
|
||||
/// List of /datum/tracks we can play
|
||||
/// Inited from config every time a jukebox is instantiated
|
||||
var/list/songs = list()
|
||||
/// Current song selected
|
||||
var/datum/track/selection = null
|
||||
/// Volume of the songs played
|
||||
var/volume = 50
|
||||
processing_flags = START_PROCESSING_MANUALLY
|
||||
/// Cooldown between "Error" sound effects being played
|
||||
COOLDOWN_DECLARE(jukebox_error_cd)
|
||||
|
||||
/obj/machinery/jukebox/disco
|
||||
name = "radiant dance machine mark IV"
|
||||
desc = "The first three prototypes were discontinued after mass casualty incidents."
|
||||
icon_state = "disco"
|
||||
req_access = list(ACCESS_ENGINEERING)
|
||||
anchored = FALSE
|
||||
var/list/spotlights = list()
|
||||
var/list/sparkles = list()
|
||||
|
||||
/obj/machinery/jukebox/disco/indestructible
|
||||
name = "radiant dance machine mark V"
|
||||
desc = "Now redesigned with data gathered from the extensive disco and plasma research."
|
||||
req_access = null
|
||||
anchored = TRUE
|
||||
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
|
||||
obj_flags = parent_type::obj_flags | NO_DECONSTRUCTION
|
||||
|
||||
/datum/track
|
||||
var/song_name = "generic"
|
||||
var/song_path = null
|
||||
var/song_length = 0
|
||||
var/song_beat = 0
|
||||
|
||||
/datum/track/default
|
||||
song_path = 'sound/ambience/title3.ogg'
|
||||
song_name = "Tintin on the Moon"
|
||||
song_length = 3 MINUTES + 52 SECONDS
|
||||
song_beat = 1 SECONDS
|
||||
/// Cooldown between being allowed to play another song
|
||||
COOLDOWN_DECLARE(jukebox_song_cd)
|
||||
/// TimerID to when the current song ends
|
||||
var/song_timerid
|
||||
/// The actual music player datum that handles the music
|
||||
var/datum/jukebox/music_player
|
||||
|
||||
/obj/machinery/jukebox/Initialize(mapload)
|
||||
. = ..()
|
||||
songs = load_songs_from_config()
|
||||
if(length(songs))
|
||||
selection = pick(songs)
|
||||
|
||||
/// Loads the config sounds once, and returns a copy of them.
|
||||
/obj/machinery/jukebox/proc/load_songs_from_config()
|
||||
var/static/list/config_songs
|
||||
if(isnull(config_songs))
|
||||
config_songs = list()
|
||||
var/list/tracks = flist("[global.config.directory]/jukebox_music/sounds/")
|
||||
for(var/track_file in tracks)
|
||||
var/datum/track/new_track = new()
|
||||
new_track.song_path = file("[global.config.directory]/jukebox_music/sounds/[track_file]")
|
||||
var/list/track_data = splittext(track_file, "+")
|
||||
if(length(track_data) != 3)
|
||||
continue
|
||||
new_track.song_name = track_data[1]
|
||||
new_track.song_length = text2num(track_data[2])
|
||||
new_track.song_beat = text2num(track_data[3])
|
||||
config_songs += new_track
|
||||
|
||||
if(!length(config_songs))
|
||||
// Includes title3 as a default for testing / "no config" support, also because it's a banger
|
||||
config_songs += new /datum/track/default()
|
||||
|
||||
// returns a copy so it can mutate if desired.
|
||||
return config_songs.Copy()
|
||||
music_player = new(src)
|
||||
|
||||
/obj/machinery/jukebox/Destroy()
|
||||
dance_over()
|
||||
selection = null
|
||||
songs.Cut()
|
||||
stop_music()
|
||||
QDEL_NULL(music_player)
|
||||
return ..()
|
||||
|
||||
/obj/machinery/jukebox/attackby(obj/item/O, mob/user, params)
|
||||
if(!active && !(obj_flags & NO_DECONSTRUCTION))
|
||||
if(O.tool_behaviour == TOOL_WRENCH)
|
||||
if(!anchored && !isinspace())
|
||||
to_chat(user,span_notice("You secure [src] to the floor."))
|
||||
set_anchored(TRUE)
|
||||
else if(anchored)
|
||||
to_chat(user,span_notice("You unsecure and disconnect [src]."))
|
||||
set_anchored(FALSE)
|
||||
playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE)
|
||||
return
|
||||
return ..()
|
||||
/obj/machinery/jukebox/no_access
|
||||
req_access = null
|
||||
|
||||
/obj/machinery/jukebox/wrench_act(mob/living/user, obj/item/tool)
|
||||
if(!isnull(music_player.active_song_sound))
|
||||
return NONE
|
||||
if(obj_flags & NO_DECONSTRUCTION)
|
||||
return NONE
|
||||
|
||||
if(default_unfasten_wrench(user, tool) == SUCCESSFUL_UNFASTEN)
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
|
||||
/obj/machinery/jukebox/update_icon_state()
|
||||
icon_state = "[initial(icon_state)][active ? "-active" : null]"
|
||||
icon_state = "[base_icon_state][music_player.active_song_sound ? "-active" : null]"
|
||||
return ..()
|
||||
|
||||
/obj/machinery/jukebox/ui_status(mob/user)
|
||||
if(isobserver(user))
|
||||
return ..()
|
||||
if(!anchored)
|
||||
to_chat(user,span_warning("This device must be anchored by a wrench!"))
|
||||
return UI_CLOSE
|
||||
if(!allowed(user) && !isobserver(user))
|
||||
if(!allowed(user))
|
||||
to_chat(user,span_warning("Error: Access Denied."))
|
||||
user.playsound_local(src, 'sound/misc/compiler-failure.ogg', 25, TRUE)
|
||||
return UI_CLOSE
|
||||
if(!songs.len && !isobserver(user))
|
||||
if(!length(music_player.songs))
|
||||
to_chat(user,span_warning("Error: No music tracks have been authorized for your station. Petition Central Command to resolve this issue."))
|
||||
user.playsound_local(src, 'sound/misc/compiler-failure.ogg', 25, TRUE)
|
||||
return UI_CLOSE
|
||||
@@ -129,23 +68,7 @@
|
||||
ui.open()
|
||||
|
||||
/obj/machinery/jukebox/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["active"] = active
|
||||
data["songs"] = list()
|
||||
for(var/datum/track/S in songs)
|
||||
var/list/track_data = list(
|
||||
name = S.song_name
|
||||
)
|
||||
data["songs"] += list(track_data)
|
||||
data["track_selected"] = null
|
||||
data["track_length"] = null
|
||||
data["track_beat"] = null
|
||||
if(selection)
|
||||
data["track_selected"] = selection.song_name
|
||||
data["track_length"] = DisplayTimeText(selection.song_length)
|
||||
data["track_beat"] = selection.song_beat
|
||||
data["volume"] = volume
|
||||
return data
|
||||
return music_player.get_ui_data()
|
||||
|
||||
/obj/machinery/jukebox/ui_act(action, list/params)
|
||||
. = ..()
|
||||
@@ -154,60 +77,120 @@
|
||||
|
||||
switch(action)
|
||||
if("toggle")
|
||||
if(QDELETED(src))
|
||||
return
|
||||
if(!active)
|
||||
if(stop > world.time)
|
||||
to_chat(usr, span_warning("Error: The device is still resetting from the last activation, it will be ready again in [DisplayTimeText(stop-world.time)]."))
|
||||
if(!COOLDOWN_FINISHED(src, jukebox_error_cd))
|
||||
return
|
||||
playsound(src, 'sound/misc/compiler-failure.ogg', 50, TRUE)
|
||||
COOLDOWN_START(src, jukebox_error_cd, 15 SECONDS)
|
||||
return
|
||||
if(isnull(music_player.active_song_sound))
|
||||
if(!COOLDOWN_FINISHED(src, jukebox_song_cd))
|
||||
to_chat(usr, span_warning("Error: The device is still resetting from the last activation, \
|
||||
it will be ready again in [DisplayTimeText(COOLDOWN_TIMELEFT(src, jukebox_song_cd))]."))
|
||||
if(COOLDOWN_FINISHED(src, jukebox_error_cd))
|
||||
playsound(src, 'sound/misc/compiler-failure.ogg', 33, TRUE)
|
||||
COOLDOWN_START(src, jukebox_error_cd, 15 SECONDS)
|
||||
return TRUE
|
||||
|
||||
activate_music()
|
||||
START_PROCESSING(SSobj, src)
|
||||
return TRUE
|
||||
else
|
||||
stop = 0
|
||||
return TRUE
|
||||
if("select_track")
|
||||
if(active)
|
||||
to_chat(usr, span_warning("Error: You cannot change the song until the current one is over."))
|
||||
return
|
||||
var/list/available = list()
|
||||
for(var/datum/track/S in songs)
|
||||
available[S.song_name] = S
|
||||
var/selected = params["track"]
|
||||
if(QDELETED(src) || !selected || !istype(available[selected], /datum/track))
|
||||
return
|
||||
selection = available[selected]
|
||||
stop_music()
|
||||
|
||||
return TRUE
|
||||
|
||||
if("select_track")
|
||||
if(!isnull(music_player.active_song_sound))
|
||||
to_chat(usr, span_warning("Error: You cannot change the song until the current one is over."))
|
||||
return TRUE
|
||||
|
||||
var/datum/track/new_song = music_player.songs[params["track"]]
|
||||
if(QDELETED(src) || !istype(new_song, /datum/track))
|
||||
return TRUE
|
||||
|
||||
music_player.selection = new_song
|
||||
return TRUE
|
||||
|
||||
if("set_volume")
|
||||
var/new_volume = params["volume"]
|
||||
if(new_volume == "reset")
|
||||
volume = initial(volume)
|
||||
return TRUE
|
||||
if(new_volume == "reset" || new_volume == "max")
|
||||
music_player.set_volume_to_max()
|
||||
else if(new_volume == "min")
|
||||
volume = 0
|
||||
return TRUE
|
||||
else if(new_volume == "max")
|
||||
volume = initial(volume)
|
||||
return TRUE
|
||||
else if(text2num(new_volume) != null)
|
||||
volume = text2num(new_volume)
|
||||
return TRUE
|
||||
music_player.set_new_volume(0)
|
||||
else if(isnum(text2num(new_volume)))
|
||||
music_player.set_new_volume(text2num(new_volume))
|
||||
return TRUE
|
||||
|
||||
if("loop")
|
||||
music_player.sound_loops = !!params["looping"]
|
||||
return TRUE
|
||||
|
||||
/obj/machinery/jukebox/proc/activate_music()
|
||||
active = TRUE
|
||||
if(!isnull(music_player.active_song_sound))
|
||||
return FALSE
|
||||
|
||||
music_player.start_music()
|
||||
update_use_power(ACTIVE_POWER_USE)
|
||||
update_appearance(UPDATE_ICON_STATE)
|
||||
START_PROCESSING(SSobj, src)
|
||||
stop = world.time + selection.song_length
|
||||
if(!music_player.sound_loops)
|
||||
song_timerid = addtimer(CALLBACK(src, PROC_REF(stop_music)), music_player.selection.song_length, TIMER_UNIQUE|TIMER_STOPPABLE|TIMER_DELETE_ME)
|
||||
return TRUE
|
||||
|
||||
/obj/machinery/jukebox/proc/stop_music()
|
||||
if(!isnull(song_timerid))
|
||||
deltimer(song_timerid)
|
||||
|
||||
music_player.unlisten_all()
|
||||
|
||||
if(!QDELING(src))
|
||||
COOLDOWN_START(src, jukebox_song_cd, 10 SECONDS)
|
||||
playsound(src,'sound/machines/terminal_off.ogg',50,TRUE)
|
||||
update_use_power(IDLE_POWER_USE)
|
||||
update_appearance(UPDATE_ICON_STATE)
|
||||
return TRUE
|
||||
|
||||
/obj/machinery/jukebox/on_set_is_operational(old_value)
|
||||
if(!is_operational)
|
||||
stop_music()
|
||||
|
||||
/obj/machinery/jukebox/disco
|
||||
name = "radiant dance machine mark IV"
|
||||
desc = "The first three prototypes were discontinued after mass casualty incidents."
|
||||
icon_state = "disco"
|
||||
base_icon_state = "disco"
|
||||
req_access = list(ACCESS_ENGINEERING)
|
||||
anchored = FALSE
|
||||
|
||||
/// Spotlight effects being played
|
||||
VAR_PRIVATE/list/obj/item/flashlight/spotlight/spotlights = list()
|
||||
/// Sparkle effects being played
|
||||
VAR_PRIVATE/list/obj/effect/overlay/sparkles/sparkles = list()
|
||||
|
||||
/obj/machinery/jukebox/disco/indestructible
|
||||
name = "radiant dance machine mark V"
|
||||
desc = "Now redesigned with data gathered from the extensive disco and plasma research."
|
||||
req_access = null
|
||||
anchored = TRUE
|
||||
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
|
||||
obj_flags = parent_type::obj_flags | NO_DECONSTRUCTION
|
||||
|
||||
/obj/machinery/jukebox/disco/activate_music()
|
||||
..()
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
dance_setup()
|
||||
lights_spin()
|
||||
begin_processing()
|
||||
|
||||
/obj/machinery/jukebox/disco/stop_music()
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
QDEL_LIST(spotlights)
|
||||
QDEL_LIST(sparkles)
|
||||
end_processing()
|
||||
|
||||
/obj/machinery/jukebox/disco/process()
|
||||
var/dance_num = rand(1, 4) //all will do the same dance
|
||||
for(var/mob/living/dancer in music_player.get_active_listeners())
|
||||
if(!(dancer.mobility_flags & MOBILITY_MOVE))
|
||||
continue
|
||||
if(HAS_TRAIT(dancer, TRAIT_DISCO_DANCER))
|
||||
continue
|
||||
dance(dancer, dance_num)
|
||||
|
||||
/obj/machinery/jukebox/disco/proc/dance_setup()
|
||||
var/turf/cen = get_turf(src)
|
||||
@@ -248,7 +231,7 @@
|
||||
|
||||
/obj/machinery/jukebox/disco/proc/lights_spin()
|
||||
for(var/i in 1 to 25)
|
||||
if(QDELETED(src) || !active)
|
||||
if(QDELETED(src) || isnull(music_player.active_song_sound))
|
||||
return
|
||||
var/obj/effect/overlay/sparkles/S = new /obj/effect/overlay/sparkles(src)
|
||||
S.alpha = 0
|
||||
@@ -267,7 +250,7 @@
|
||||
for(var/s in sparkles)
|
||||
var/obj/effect/overlay/sparkles/reveal = s
|
||||
reveal.alpha = 255
|
||||
while(active)
|
||||
while(!isnull(music_player.active_song_sound))
|
||||
for(var/g in spotlights) // The multiples reflects custom adjustments to each colors after dozens of tests
|
||||
var/obj/item/flashlight/spotlight/glow = g
|
||||
if(QDELETED(glow))
|
||||
@@ -335,7 +318,7 @@
|
||||
glow.even_cycle = !glow.even_cycle
|
||||
if(prob(2)) // Unique effects for the dance floor that show up randomly to mix things up
|
||||
INVOKE_ASYNC(src, PROC_REF(hierofunk))
|
||||
sleep(selection.song_beat)
|
||||
sleep(music_player.selection.song_beat)
|
||||
if(QDELETED(src))
|
||||
return
|
||||
|
||||
@@ -404,63 +387,4 @@
|
||||
/obj/machinery/jukebox/disco/proc/dance4_revert(mob/living/dancer, matrix/starting_matrix)
|
||||
animate(dancer, transform = starting_matrix, time = 5, loop = 0)
|
||||
REMOVE_TRAIT(dancer, TRAIT_DISCO_DANCER, REF(src))
|
||||
|
||||
/obj/machinery/jukebox/proc/dance_over()
|
||||
for(var/datum/weakref/weak_to_hide_from as anything in rangers)
|
||||
var/mob/to_hide_from = weak_to_hide_from?.resolve()
|
||||
to_hide_from?.stop_sound_channel(CHANNEL_JUKEBOX)
|
||||
|
||||
rangers.Cut()
|
||||
|
||||
/obj/machinery/jukebox/disco/dance_over()
|
||||
..()
|
||||
QDEL_LIST(spotlights)
|
||||
QDEL_LIST(sparkles)
|
||||
|
||||
/obj/machinery/jukebox/process()
|
||||
if(world.time < stop && active)
|
||||
var/sound/song_played = sound(selection.song_path)
|
||||
|
||||
// Goes through existing mobs in rangers to determine if they should not be played to
|
||||
for(var/datum/weakref/weak_to_hide_from as anything in rangers)
|
||||
var/mob/to_hide_from = weak_to_hide_from?.resolve()
|
||||
if(!HAS_JUKEBOX_PREF(to_hide_from) || get_dist(src, get_turf(to_hide_from)) > 10)
|
||||
rangers -= weak_to_hide_from
|
||||
to_hide_from?.stop_sound_channel(CHANNEL_JUKEBOX)
|
||||
|
||||
// Collect mobs to play the song to, stores weakrefs of them in rangers
|
||||
for(var/mob/to_play_to in range(world.view, src))
|
||||
if(!HAS_JUKEBOX_PREF(to_play_to))
|
||||
continue
|
||||
var/datum/weakref/weak_playing_to = WEAKREF(to_play_to)
|
||||
if(rangers[weak_playing_to])
|
||||
continue
|
||||
rangers[weak_playing_to] = TRUE
|
||||
// This plays the sound directly underneath the mob because otherwise it'd get stuck in their left ear or whatever
|
||||
// Would be neat if it sourced from the box itself though
|
||||
to_play_to.playsound_local(get_turf(to_play_to), null, volume, channel = CHANNEL_JUKEBOX, sound_to_use = song_played, use_reverb = FALSE)
|
||||
|
||||
else if(active)
|
||||
active = FALSE
|
||||
update_use_power(IDLE_POWER_USE)
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
dance_over()
|
||||
playsound(src,'sound/machines/terminal_off.ogg',50,TRUE)
|
||||
update_appearance(UPDATE_ICON_STATE)
|
||||
stop = world.time + 100
|
||||
|
||||
/obj/machinery/jukebox/disco/process()
|
||||
. = ..()
|
||||
if(!active)
|
||||
return
|
||||
|
||||
var/dance_num = rand(1,4) //all will do the same dance
|
||||
for(var/datum/weakref/weak_dancer as anything in rangers)
|
||||
var/mob/living/to_dance = weak_dancer.resolve()
|
||||
if(!istype(to_dance) || !(to_dance.mobility_flags & MOBILITY_MOVE))
|
||||
continue
|
||||
if(!HAS_TRAIT(to_dance, TRAIT_DISCO_DANCER))
|
||||
dance(to_dance, dance_num)
|
||||
|
||||
#undef HAS_JUKEBOX_PREF
|
||||
*/ //SKYRAT EDIT REMOVAL END
|
||||
SKYRAT EDIT END*/
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
mod.wearer.investigate_log("has been killed by [src].", INVESTIGATE_DEATHS)
|
||||
mod.wearer.death() //just in case, for some reason, they're still alive
|
||||
flash_color(mod.wearer, flash_color = "#FF0000", flash_time = 10 SECONDS)
|
||||
|
||||
/* SKYRAT EDIT: See skyrat_modular/modules/jukebox
|
||||
///Rave Visor - Gives you a rainbow visor and plays jukebox music to you.
|
||||
/obj/item/mod/module/visor/rave
|
||||
name = "MOD rave visor module"
|
||||
@@ -67,10 +67,6 @@
|
||||
var/datum/client_colour/rave_screen
|
||||
/// The current element in the rainbow_order list we are on.
|
||||
var/rave_number = 1
|
||||
/// The track we selected to play.
|
||||
var/datum/track/selection
|
||||
/// A list of all the songs we can play.
|
||||
var/list/songs = list()
|
||||
/// A list of the colors the module can take.
|
||||
var/static/list/rainbow_order = list(
|
||||
list(1,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0),
|
||||
@@ -80,23 +76,18 @@
|
||||
list(0,0,0,0, 0,0.5,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,0),
|
||||
list(1,0,0,0, 0,0,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,0),
|
||||
)
|
||||
/// What actually plays music to us
|
||||
var/datum/jukebox/single_mob/music_player
|
||||
|
||||
/obj/item/mod/module/visor/rave/Initialize(mapload)
|
||||
. = ..()
|
||||
var/list/tracks = flist("[global.config.directory]/jukebox_music/sounds/")
|
||||
for(var/sound in tracks)
|
||||
var/datum/track/track = new()
|
||||
track.song_path = file("[global.config.directory]/jukebox_music/sounds/[sound]")
|
||||
var/list/sound_params = splittext(sound,"+")
|
||||
if(length(sound_params) != 3)
|
||||
continue
|
||||
track.song_name = sound_params[1]
|
||||
track.song_length = text2num(sound_params[2])
|
||||
track.song_beat = text2num(sound_params[3])
|
||||
songs[track.song_name] = track
|
||||
if(length(songs))
|
||||
var/song_name = pick(songs)
|
||||
selection = songs[song_name]
|
||||
music_player = new(src)
|
||||
music_player.sound_loops = TRUE
|
||||
|
||||
/obj/item/mod/module/visor/rave/Destroy()
|
||||
QDEL_NULL(music_player)
|
||||
QDEL_NULL(rave_screen)
|
||||
return ..()
|
||||
|
||||
/obj/item/mod/module/visor/rave/on_activation()
|
||||
. = ..()
|
||||
@@ -104,24 +95,26 @@
|
||||
return
|
||||
rave_screen = mod.wearer.add_client_colour(/datum/client_colour/rave)
|
||||
rave_screen.update_colour(rainbow_order[rave_number])
|
||||
if(selection)
|
||||
mod.wearer.playsound_local(get_turf(src), null, 50, channel = CHANNEL_JUKEBOX, sound_to_use = sound(selection.song_path), use_reverb = FALSE)
|
||||
music_player.start_music(mod.wearer)
|
||||
|
||||
/obj/item/mod/module/visor/rave/on_deactivation(display_message = TRUE, deleting = FALSE)
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
QDEL_NULL(rave_screen)
|
||||
if(selection)
|
||||
mod.wearer.stop_sound_channel(CHANNEL_JUKEBOX)
|
||||
if(deleting)
|
||||
return
|
||||
SEND_SOUND(mod.wearer, sound('sound/machines/terminal_off.ogg', volume = 50, channel = CHANNEL_JUKEBOX))
|
||||
if(isnull(music_player.active_song_sound))
|
||||
return
|
||||
|
||||
music_player.unlisten_all()
|
||||
QDEL_NULL(music_player)
|
||||
if(deleting)
|
||||
return
|
||||
SEND_SOUND(mod.wearer, sound('sound/machines/terminal_off.ogg', volume = 50, channel = CHANNEL_JUKEBOX))
|
||||
|
||||
/obj/item/mod/module/visor/rave/generate_worn_overlay(mutable_appearance/standing)
|
||||
. = ..()
|
||||
for(var/mutable_appearance/appearance as anything in .)
|
||||
appearance.color = active ? rainbow_order[rave_number] : null
|
||||
appearance.color = isnull(music_player.active_song_sound) ? null : rainbow_order[rave_number]
|
||||
|
||||
/obj/item/mod/module/visor/rave/on_active_process(seconds_per_tick)
|
||||
rave_number++
|
||||
@@ -132,21 +125,21 @@
|
||||
|
||||
/obj/item/mod/module/visor/rave/get_configuration()
|
||||
. = ..()
|
||||
if(length(songs))
|
||||
.["selection"] = add_ui_configuration("Song", "list", selection.song_name, clean_songs())
|
||||
if(length(music_player.songs))
|
||||
.["selection"] = add_ui_configuration("Song", "list", music_player.selection.song_name, music_player.songs)
|
||||
|
||||
/obj/item/mod/module/visor/rave/configure_edit(key, value)
|
||||
switch(key)
|
||||
if("selection")
|
||||
if(active)
|
||||
if(!isnull(music_player.active_song_sound))
|
||||
return
|
||||
selection = songs[value]
|
||||
|
||||
/obj/item/mod/module/visor/rave/proc/clean_songs()
|
||||
. = list()
|
||||
for(var/track in songs)
|
||||
. += track
|
||||
var/datum/track/new_song = music_player.songs[value]
|
||||
if(QDELETED(src) || !istype(new_song, /datum/track))
|
||||
return
|
||||
|
||||
music_player.selection = new_song
|
||||
SKYRAT EDIT END */
|
||||
///Tanner - Tans you with spraytan.
|
||||
/obj/item/mod/module/tanner
|
||||
name = "MOD tanning module"
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
///Rave Visor - Gives you a rainbow visor and plays jukebox music to you.
|
||||
/obj/item/mod/module/visor/rave
|
||||
name = "MOD rave visor module"
|
||||
desc = "A Super Cool Awesome Visor (SCAV), intended for modular suits."
|
||||
icon_state = "rave_visor"
|
||||
complexity = 1
|
||||
overlay_state_inactive = "module_rave"
|
||||
/// The client colors applied to the wearer.
|
||||
var/datum/client_colour/rave_screen
|
||||
/// The current element in the rainbow_order list we are on.
|
||||
var/rave_number = 1
|
||||
/// The track we selected to play.
|
||||
var/datum/track/selection
|
||||
/// A list of all the songs we can play.
|
||||
var/list/songs = list()
|
||||
/// A list of the colors the module can take.
|
||||
var/static/list/rainbow_order = list(
|
||||
list(1,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0),
|
||||
list(1,0,0,0, 0,0.5,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0),
|
||||
list(1,0,0,0, 0,1,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0),
|
||||
list(0,0,0,0, 0,1,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0),
|
||||
list(0,0,0,0, 0,0.5,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,0),
|
||||
list(1,0,0,0, 0,0,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,0),
|
||||
)
|
||||
|
||||
/obj/item/mod/module/visor/rave/Initialize(mapload)
|
||||
. = ..()
|
||||
var/list/tracks = flist("[global.config.directory]/jukebox_music/sounds/")
|
||||
for(var/sound in tracks)
|
||||
var/datum/track/track = new()
|
||||
track.song_path = file("[global.config.directory]/jukebox_music/sounds/[sound]")
|
||||
var/list/sound_params = splittext(sound,"+")
|
||||
if(length(sound_params) != 3)
|
||||
continue
|
||||
track.song_name = sound_params[1]
|
||||
track.song_length = text2num(sound_params[2])
|
||||
track.song_beat = text2num(sound_params[3])
|
||||
songs[track.song_name] = track
|
||||
if(length(songs))
|
||||
var/song_name = pick(songs)
|
||||
selection = songs[song_name]
|
||||
|
||||
/obj/item/mod/module/visor/rave/on_activation()
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
rave_screen = mod.wearer.add_client_colour(/datum/client_colour/rave)
|
||||
rave_screen.update_colour(rainbow_order[rave_number])
|
||||
if(selection)
|
||||
mod.wearer.playsound_local(get_turf(src), null, 50, channel = CHANNEL_JUKEBOX, sound_to_use = sound(selection.song_path), use_reverb = FALSE)
|
||||
|
||||
/obj/item/mod/module/visor/rave/on_deactivation(display_message = TRUE, deleting = FALSE)
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
QDEL_NULL(rave_screen)
|
||||
if(selection)
|
||||
mod.wearer.stop_sound_channel(CHANNEL_JUKEBOX)
|
||||
if(deleting)
|
||||
return
|
||||
SEND_SOUND(mod.wearer, sound('sound/machines/terminal_off.ogg', volume = 50, channel = CHANNEL_JUKEBOX))
|
||||
|
||||
/obj/item/mod/module/visor/rave/generate_worn_overlay(mutable_appearance/standing)
|
||||
. = ..()
|
||||
for(var/mutable_appearance/appearance as anything in .)
|
||||
appearance.color = active ? rainbow_order[rave_number] : null
|
||||
|
||||
/obj/item/mod/module/visor/rave/on_active_process(seconds_per_tick)
|
||||
rave_number++
|
||||
if(rave_number > length(rainbow_order))
|
||||
rave_number = 1
|
||||
mod.wearer.update_clothing(mod.slot_flags)
|
||||
rave_screen.update_colour(rainbow_order[rave_number])
|
||||
|
||||
/obj/item/mod/module/visor/rave/get_configuration()
|
||||
. = ..()
|
||||
if(length(songs))
|
||||
.["selection"] = add_ui_configuration("Song", "list", selection.song_name, clean_songs())
|
||||
|
||||
/obj/item/mod/module/visor/rave/configure_edit(key, value)
|
||||
switch(key)
|
||||
if("selection")
|
||||
if(active)
|
||||
return
|
||||
selection = songs[value]
|
||||
|
||||
/obj/item/mod/module/visor/rave/proc/clean_songs()
|
||||
. = list()
|
||||
for(var/track in songs)
|
||||
. += track
|
||||
@@ -1189,6 +1189,7 @@
|
||||
#include "code\datums\components\jetpack.dm"
|
||||
#include "code\datums\components\joint_damage.dm"
|
||||
#include "code\datums\components\jousting.dm"
|
||||
#include "code\datums\components\jukebox.dm"
|
||||
#include "code\datums\components\keep_me_secure.dm"
|
||||
#include "code\datums\components\knockoff.dm"
|
||||
#include "code\datums\components\label.dm"
|
||||
@@ -7335,6 +7336,7 @@
|
||||
#include "modular_skyrat\modules\interaction_menu\code\interaction_datum.dm"
|
||||
#include "modular_skyrat\modules\jukebox\code\dance_machine.dm"
|
||||
#include "modular_skyrat\modules\jukebox\code\jukebox_subsystem.dm"
|
||||
#include "modular_skyrat\modules\jukebox\code\ravemod.dm"
|
||||
#include "modular_skyrat\modules\jungle\code\flora.dm"
|
||||
#include "modular_skyrat\modules\knives\knives.dm"
|
||||
#include "modular_skyrat\modules\layer_shift\code\mob_movement.dm"
|
||||
|
||||
+45
-16
@@ -1,6 +1,7 @@
|
||||
import { sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
|
||||
import { BooleanLike } from '../../common/react';
|
||||
import { useBackend } from '../backend';
|
||||
import {
|
||||
Box,
|
||||
@@ -13,31 +14,59 @@ import {
|
||||
} from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const Jukebox = (props) => {
|
||||
const { act, data } = useBackend();
|
||||
const { active, track_selected, track_length, track_beat, volume } = data;
|
||||
const songs = flow([sortBy((song) => song.name)])(data.songs || []);
|
||||
type Song = {
|
||||
name: string;
|
||||
length: number;
|
||||
beat: number;
|
||||
};
|
||||
|
||||
type Data = {
|
||||
active: BooleanLike;
|
||||
looping: BooleanLike;
|
||||
volume: number;
|
||||
track_selected: string | null;
|
||||
songs: Song[];
|
||||
};
|
||||
|
||||
export const Jukebox = () => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
const { active, looping, track_selected, volume, songs } = data;
|
||||
|
||||
const songs_sorted: Song[] = flow([sortBy((song: Song) => song.name)])(songs);
|
||||
const song_selected: Song | undefined = songs.find(
|
||||
(song) => song.name === track_selected,
|
||||
);
|
||||
|
||||
return (
|
||||
<Window width={370} height={313}>
|
||||
<Window.Content>
|
||||
<Section
|
||||
title="Song Player"
|
||||
buttons={
|
||||
<Button
|
||||
icon={active ? 'pause' : 'play'}
|
||||
content={active ? 'Stop' : 'Play'}
|
||||
selected={active}
|
||||
onClick={() => act('toggle')}
|
||||
/>
|
||||
<>
|
||||
<Button
|
||||
icon={active ? 'pause' : 'play'}
|
||||
content={active ? 'Stop' : 'Play'}
|
||||
selected={active}
|
||||
onClick={() => act('toggle')}
|
||||
/>
|
||||
<Button.Checkbox
|
||||
icon={'arrow-rotate-left'}
|
||||
content="Repeat"
|
||||
disabled={active}
|
||||
checked={looping}
|
||||
onClick={() => act('loop', { looping: !looping })}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Track Selected">
|
||||
<Dropdown
|
||||
width="240px"
|
||||
options={songs.map((song) => song.name)}
|
||||
disabled={active}
|
||||
selected={track_selected || 'Select a Track'}
|
||||
options={songs_sorted.map((song) => song.name)}
|
||||
disabled={!!active}
|
||||
selected={song_selected?.name || 'Select a Track'}
|
||||
onSelected={(value) =>
|
||||
act('select_track', {
|
||||
track: value,
|
||||
@@ -46,11 +75,11 @@ export const Jukebox = (props) => {
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Track Length">
|
||||
{track_selected ? track_length : 'No Track Selected'}
|
||||
{song_selected?.length || 'No Track Selected'}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Track Beat">
|
||||
{track_selected ? track_beat : 'No Track Selected'}
|
||||
{track_beat === 1 ? ' beat' : ' beats'}
|
||||
{song_selected?.beat || 'No Track Selected'}
|
||||
{song_selected?.beat === 1 ? ' beat' : ' beats'}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
Reference in New Issue
Block a user