Synthesizers and headphones can now have circuits! (#62825)

About The Pull Request

A circuit and shell components have been added to Synthesizers (headphones and spacepods included, though with a reduced capacity because of their size), so they can now be used for wiremod. Just like for instant cameras, no shell design here. They are meant to be found in dorms or maybe ordered from cargo.
Why It's Good For The Game

The station outside the sci department has plenty of USB ports stuff but is lacking when it comes to circuits shell. This is another small step toward a better and more applicable wiremod.
Changelog

cl
expansion: Synthesizers and headphones can now have circuits installed.
/cl
This commit is contained in:
Ghom
2021-12-01 23:18:36 +01:00
committed by GitHub
parent a49da2531f
commit 2fbd7b20bb
12 changed files with 267 additions and 110 deletions
+6 -59
View File
@@ -23,8 +23,12 @@
QDEL_NULL(song)
return ..()
/obj/item/instrument/proc/should_stop_playing(mob/user)
return user.incapacitated() || !((loc == user) || (isturf(loc) && Adjacent(user))) // sorry, no more TK playing.
/obj/item/instrument/proc/should_stop_playing(atom/music_player)
if(!ismob(music_player))
return STOP_PLAYING
var/mob/user = music_player
if(user.incapacitated() || !((loc == user) || (isturf(loc) && Adjacent(user)))) // sorry, no more TK playing.
return STOP_PLAYING
/obj/item/instrument/suicide_act(mob/user)
user.visible_message(span_suicide("[user] begins to play 'Gloomy Sunday'! It looks like [user.p_theyre()] trying to commit suicide!"))
@@ -61,63 +65,6 @@
inhand_icon_state = "golden_violin"
resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
/obj/item/instrument/piano_synth
name = "synthesizer"
desc = "An advanced electronic synthesizer that can be used as various instruments."
icon_state = "synth"
inhand_icon_state = "synth"
allowed_instrument_ids = "piano"
/obj/item/instrument/piano_synth/Initialize(mapload)
. = ..()
song.allowed_instrument_ids = SSinstruments.synthesizer_instrument_ids
/obj/item/instrument/piano_synth/headphones
name = "headphones"
desc = "Unce unce unce unce. Boop!"
icon = 'icons/obj/clothing/accessories.dmi'
lefthand_file = 'icons/mob/inhands/clothing_lefthand.dmi'
righthand_file = 'icons/mob/inhands/clothing_righthand.dmi'
icon_state = "headphones"
inhand_icon_state = "headphones"
slot_flags = ITEM_SLOT_EARS | ITEM_SLOT_HEAD
force = 0
w_class = WEIGHT_CLASS_SMALL
custom_price = PAYCHECK_ASSISTANT * 2.5
instrument_range = 1
/obj/item/instrument/piano_synth/headphones/ComponentInitialize()
. = ..()
AddElement(/datum/element/update_icon_updates_onmob)
RegisterSignal(src, COMSIG_SONG_START, .proc/start_playing)
RegisterSignal(src, COMSIG_SONG_END, .proc/stop_playing)
/**
* Called by a component signal when our song starts playing.
*/
/obj/item/instrument/piano_synth/headphones/proc/start_playing()
SIGNAL_HANDLER
icon_state = "[initial(icon_state)]_on"
update_appearance()
/**
* Called by a component signal when our song stops playing.
*/
/obj/item/instrument/piano_synth/headphones/proc/stop_playing()
SIGNAL_HANDLER
icon_state = "[initial(icon_state)]"
update_appearance()
/obj/item/instrument/piano_synth/headphones/spacepods
name = "\improper Nanotrasen space pods"
desc = "Flex your money, AND ignore what everyone else says, all at once!"
icon_state = "spacepods"
inhand_icon_state = "spacepods"
slot_flags = ITEM_SLOT_EARS
strip_delay = 100 //air pods don't fall out
instrument_range = 0 //you're paying for quality here
custom_premium_price = PAYCHECK_ASSISTANT * 36 //Save up 5 shifts worth of pay just to lose it down a drainpipe on the sidewalk
/obj/item/instrument/banjo
name = "banjo"
desc = "A 'Mura' brand banjo. It's pretty much just a drum with a neck and strings."
+191
View File
@@ -0,0 +1,191 @@
/obj/item/instrument/piano_synth
name = "synthesizer"
desc = "An advanced electronic synthesizer that can be used as various instruments."
icon_state = "synth"
inhand_icon_state = "synth"
allowed_instrument_ids = "piano"
var/circuit_type = /obj/item/circuit_component/synth
var/shell_capacity = SHELL_CAPACITY_SMALL
/obj/item/instrument/piano_synth/Initialize(mapload)
. = ..()
song.allowed_instrument_ids = SSinstruments.synthesizer_instrument_ids
AddComponent(/datum/component/shell, list(new circuit_type), shell_capacity)
/obj/item/instrument/piano_synth/headphones
name = "headphones"
desc = "Unce unce unce unce. Boop!"
icon = 'icons/obj/clothing/accessories.dmi'
lefthand_file = 'icons/mob/inhands/clothing_lefthand.dmi'
righthand_file = 'icons/mob/inhands/clothing_righthand.dmi'
icon_state = "headphones"
inhand_icon_state = "headphones"
slot_flags = ITEM_SLOT_EARS | ITEM_SLOT_HEAD
force = 0
w_class = WEIGHT_CLASS_SMALL
custom_price = PAYCHECK_ASSISTANT * 2.5
instrument_range = 1
circuit_type = /obj/item/circuit_component/synth/headphones
shell_capacity = SHELL_CAPACITY_TINY
/obj/item/instrument/piano_synth/headphones/ComponentInitialize()
. = ..()
AddElement(/datum/element/update_icon_updates_onmob)
RegisterSignal(src, COMSIG_SONG_START, .proc/start_playing)
RegisterSignal(src, COMSIG_SONG_END, .proc/stop_playing)
/**
* Called by a component signal when our song starts playing.
*/
/obj/item/instrument/piano_synth/headphones/proc/start_playing()
SIGNAL_HANDLER
icon_state = "[initial(icon_state)]_on"
update_appearance()
/**
* Called by a component signal when our song stops playing.
*/
/obj/item/instrument/piano_synth/headphones/proc/stop_playing()
SIGNAL_HANDLER
icon_state = "[initial(icon_state)]"
update_appearance()
/obj/item/instrument/piano_synth/headphones/spacepods
name = "\improper Nanotrasen space pods"
desc = "Flex your money, AND ignore what everyone else says, all at once!"
icon_state = "spacepods"
inhand_icon_state = "spacepods"
slot_flags = ITEM_SLOT_EARS
strip_delay = 100 //air pods don't fall out
instrument_range = 0 //you're paying for quality here
custom_premium_price = PAYCHECK_ASSISTANT * 36 //Save up 5 shifts worth of pay just to lose it down a drainpipe on the sidewalk
/obj/item/circuit_component/synth
display_name = "Synthesizer"
desc = "An advanced electronic synthesizer that can be used as various instruments."
/// The song, represented in latin alphabet A to G, that'll be played when play is triggered.
var/datum/port/input/song
/// Starts playing the song.
var/datum/port/input/play
/// Stop playing the song.
var/datum/port/input/stop
/// How many times the song will be played.
var/datum/port/input/repetitions
/// The beats per minute of the song
var/datum/port/input/beats_per_min
/// The volume of the song
var/datum/port/input/volume
/// Notes with volume below this threshold will be dead
var/datum/port/input/volume_dropoff
/// Note shift
var/datum/port/input/note_shift
/// Sustain Mode
var/datum/port/input/sustain_mode
/// The value of the above
var/datum/port/input/sustain_value
/// If set the last held note will decay
var/datum/port/input/note_decay
/// The list of instruments which sound can be synthesized.
var/datum/port/input/option/selected_instrument
/// Whether a song is currently playing
var/datum/port/output/is_playing
/// Sent when a new song has started playing
var/datum/port/output/started_playing
/// Sent when a song has finished playing
var/datum/port/output/stopped_playing
/// The synthesizer this circut is attached to.
var/obj/item/instrument/piano_synth/synth
/obj/item/circuit_component/synth/populate_ports()
song = add_input_port("Song", PORT_TYPE_LIST(PORT_TYPE_STRING), trigger = .proc/import_song)
play = add_input_port("Play", PORT_TYPE_SIGNAL, trigger = .proc/start_playing)
stop = add_input_port("Stop", PORT_TYPE_SIGNAL, trigger = .proc/stop_playing)
repetitions = add_input_port("Repetitions", PORT_TYPE_NUMBER, trigger = .proc/set_repetitions)
beats_per_min = add_input_port("BPM", PORT_TYPE_NUMBER, trigger = .proc/set_bpm)
selected_instrument = add_option_port("Selected Instrument", SSinstruments.synthesizer_instrument_ids, trigger = .proc/set_instrument)
volume = add_input_port("Volume", PORT_TYPE_NUMBER, trigger = .proc/set_volume)
volume_dropoff = add_input_port("Volume Dropoff Threshold", PORT_TYPE_NUMBER, trigger = .proc/set_dropoff)
note_shift = add_input_port("Note Shift", PORT_TYPE_NUMBER, trigger = .proc/set_note_shift)
sustain_mode = add_option_port("Note Sustain Mode", SSinstruments.note_sustain_modes, trigger = .proc/set_sustain_mode)
sustain_value = add_input_port("Note Sustain Value", PORT_TYPE_NUMBER, trigger = .proc/set_sustain_value)
note_decay = add_input_port("Held Note Decay", PORT_TYPE_NUMBER, trigger = .proc/set_sustain_decay)
is_playing = add_output_port("Currently Playing", PORT_TYPE_NUMBER)
started_playing = add_output_port("Started Playing", PORT_TYPE_SIGNAL)
stopped_playing = add_output_port("Stopped Playing", PORT_TYPE_SIGNAL)
/obj/item/circuit_component/synth/register_shell(atom/movable/shell)
. = ..()
synth = shell
RegisterSignal(synth, COMSIG_SONG_START, .proc/on_song_start)
RegisterSignal(synth, COMSIG_SONG_END, .proc/on_song_end)
RegisterSignal(synth, COMSIG_SONG_SHOULD_STOP_PLAYING, .proc/continue_if_autoplaying)
/obj/item/circuit_component/synth/unregister_shell(atom/movable/shell)
if(synth.song.music_player == src)
synth.song.stop_playing()
synth = null
UnregisterSignal(synth, list(COMSIG_SONG_START, COMSIG_SONG_END, COMSIG_SONG_SHOULD_STOP_PLAYING))
return ..()
/obj/item/circuit_component/synth/proc/start_playing(datum/port/input/port)
synth.song.start_playing(src)
/obj/item/circuit_component/synth/proc/on_song_start()
SIGNAL_HANDLER
is_playing.set_output(TRUE)
started_playing.set_output(COMPONENT_SIGNAL)
/obj/item/circuit_component/synth/proc/continue_if_autoplaying(datum/source, atom/music_player)
SIGNAL_HANDLER
if(music_player == src)
return IGNORE_INSTRUMENT_CHECKS
/obj/item/circuit_component/synth/proc/stop_playing(datum/port/input/port)
synth.song.stop_playing()
/obj/item/circuit_component/synth/proc/on_song_end()
SIGNAL_HANDLER
is_playing.set_output(FALSE)
stopped_playing.set_output(COMPONENT_SIGNAL)
/obj/item/circuit_component/synth/proc/import_song()
synth.song.ParseSong(song.value)
/obj/item/circuit_component/synth/proc/set_repetitions()
synth.song.set_repeats(repetitions.value)
/obj/item/circuit_component/synth/proc/set_bpm()
synth.song.sanitize_tempo(BPM_TO_TEMPO_SETTING(beats_per_min.value))
/obj/item/circuit_component/synth/proc/set_instrument()
synth.song.set_instrument(selected_instrument.value)
/obj/item/circuit_component/synth/proc/set_volume()
synth.song.set_volume(volume.value)
/obj/item/circuit_component/synth/proc/set_dropoff()
synth.song.set_dropoff_volume(volume_dropoff.value)
/obj/item/circuit_component/synth/proc/set_note_shift()
synth.song.note_shift = clamp(note_shift.value, synth.song.note_shift_min, synth.song.note_shift_max)
/obj/item/circuit_component/synth/proc/set_sustain_mode()
synth.song.sustain_mode = SSinstruments.note_sustain_modes[sustain_mode.value]
/obj/item/circuit_component/synth/proc/set_sustain_value()
switch(synth.song.sustain_mode)
if(SUSTAIN_LINEAR)
synth.song.set_linear_falloff_duration(sustain_value.value)
if(SUSTAIN_EXPONENTIAL)
synth.song.set_exponential_drop_rate(sustain_value.value)
/obj/item/circuit_component/synth/proc/set_sustain_decay()
synth.song.full_sustain_held_note = !!synth.song.full_sustain_held_note
/obj/item/circuit_component/synth/headphones
display_name = "Headphones"
desc = "An advanced electronic device that plays music into your ears."
+35 -21
View File
@@ -74,8 +74,8 @@
var/list/channels_playing = list()
/// List of channels that aren't being used, as text. This is to prevent unnecessary freeing and reallocations from SSsounds/SSinstruments.
var/list/channels_idle = list()
/// Person playing us
var/mob/user_playing
/// Who or what's playing us
var/atom/music_player
//////////////////////////////////////////////////////
/// Last world.time we checked for who can hear us
@@ -197,7 +197,7 @@
/**
* Attempts to start playing our song.
*/
/datum/song/proc/start_playing(mob/user)
/datum/song/proc/start_playing(atom/user)
if(playing)
return
if(!using_instrument?.ready())
@@ -208,7 +208,6 @@
to_chat(user, span_warning("Song is empty."))
return
playing = TRUE
updateDialog(user_playing)
//we can not afford to runtime, since we are going to be doing sound channel reservations and if we runtime it means we have a channel allocation leak.
//wrap the rest of the stuff to ensure stop_playing() is called.
do_hearcheck()
@@ -216,7 +215,9 @@
elapsed_delay = 0
delay_by = 0
current_chord = 1
user_playing = user
music_player = user
if(ismob(music_player))
updateDialog(music_player)
START_PROCESSING(SSinstruments, src)
/**
@@ -232,13 +233,13 @@
SEND_SIGNAL(parent, COMSIG_SONG_END)
terminate_all_sounds(TRUE)
hearing_mobs.len = 0
user_playing = null
music_player = null
/**
* Processes our song.
*/
/datum/song/proc/process_song(wait)
if(!length(compiled_chords) || should_stop_playing(user_playing))
if(!length(compiled_chords) || should_stop_playing(music_player) == STOP_PLAYING)
stop_playing()
return
var/list/chord = compiled_chords[current_chord]
@@ -276,13 +277,26 @@
/datum/song/proc/play_chord(list/chord)
// last value is timing information
for(var/i in 1 to (length(chord) - 1))
legacy? playkey_legacy(chord[i][1], chord[i][2], chord[i][3], user_playing) : playkey_synth(chord[i], user_playing)
legacy? playkey_legacy(chord[i][1], chord[i][2], chord[i][3], music_player) : playkey_synth(chord[i], music_player)
/**
* Checks if we should halt playback.
*/
/datum/song/proc/should_stop_playing(mob/user)
return QDELETED(parent) || !using_instrument || !playing
/datum/song/proc/should_stop_playing(atom/player)
if(QDELETED(player) || !using_instrument || !playing)
return STOP_PLAYING
return SEND_SIGNAL(parent, COMSIG_SONG_SHOULD_STOP_PLAYING, player)
/// Sets and sanitizes the repeats variable.
/datum/song/proc/set_repeats(new_repeats_value)
if(playing)
return //So that people cant keep adding to repeat. If the do it intentionally, it could result in the server crashing.
repeat = round(new_repeats_value)
if(repeat < 0)
repeat = 0
if(repeat > max_repeats)
repeat = max_repeats
/**
* Sanitizes tempo to a value that makes sense and fits the current world.tick_lag.
@@ -332,7 +346,7 @@
* Setter for setting output volume.
*/
/datum/song/proc/set_volume(volume)
src.volume = clamp(volume, max(0, min_volume), min(100, max_volume))
src.volume = clamp(round(volume, 1), max(0, min_volume), min(100, max_volume))
update_sustain()
updateDialog()
@@ -340,7 +354,7 @@
* Setter for setting how low the volume has to get before a note is considered "dead" and dropped
*/
/datum/song/proc/set_dropoff_volume(volume)
sustain_dropoff_volume = clamp(volume, INSTRUMENT_MIN_SUSTAIN_DROPOFF, 100)
sustain_dropoff_volume = clamp(round(volume, 0.01), INSTRUMENT_MIN_SUSTAIN_DROPOFF, 100)
update_sustain()
updateDialog()
@@ -348,7 +362,7 @@
* Setter for setting exponential falloff factor.
*/
/datum/song/proc/set_exponential_drop_rate(drop)
sustain_exponential_dropoff = clamp(drop, INSTRUMENT_EXP_FALLOFF_MIN, INSTRUMENT_EXP_FALLOFF_MAX)
sustain_exponential_dropoff = clamp(round(drop, 0.00001), INSTRUMENT_EXP_FALLOFF_MIN, INSTRUMENT_EXP_FALLOFF_MAX)
update_sustain()
updateDialog()
@@ -356,7 +370,7 @@
* Setter for setting linear falloff duration.
*/
/datum/song/proc/set_linear_falloff_duration(duration)
sustain_linear_duration = clamp(duration, 0.1, INSTRUMENT_MAX_TOTAL_SUSTAIN)
sustain_linear_duration = clamp(round(duration * 10, world.tick_lag), 0.1, INSTRUMENT_MAX_TOTAL_SUSTAIN)
update_sustain()
updateDialog()
@@ -379,12 +393,12 @@
/datum/song/handheld/updateDialog(mob/user)
parent.ui_interact(user || usr)
/datum/song/handheld/should_stop_playing(mob/user)
/datum/song/handheld/should_stop_playing(atom/player)
. = ..()
if(.)
return TRUE
if(. == STOP_PLAYING || . == IGNORE_INSTRUMENT_CHECKS)
return
var/obj/item/instrument/I = parent
return I.should_stop_playing(user)
return I.should_stop_playing(player)
// subtype for stationary structures, like pianos
/datum/song/stationary
@@ -392,9 +406,9 @@
/datum/song/stationary/updateDialog(mob/user)
parent.ui_interact(user || usr)
/datum/song/stationary/should_stop_playing(mob/user)
/datum/song/stationary/should_stop_playing(atom/player)
. = ..()
if(.)
if(. == STOP_PLAYING || . == IGNORE_INSTRUMENT_CHECKS)
return TRUE
var/obj/structure/musician/M = parent
return M.should_stop_playing(user)
return M.should_stop_playing(player)
+11 -20
View File
@@ -90,15 +90,15 @@
/**
* Parses a song the user has input into lines and stores them.
*/
/datum/song/proc/ParseSong(text)
/datum/song/proc/ParseSong(new_song)
set waitfor = FALSE
//split into lines
lines = splittext(text, "\n")
lines = islist(new_song) ? new_song : splittext(new_song, "\n")
if(lines.len)
var/bpm_string = "BPM: "
if(findtext(lines[1], bpm_string, 1, length(bpm_string) + 1))
var/divisor = text2num(copytext(lines[1], length(bpm_string) + 1)) || 120 // default
tempo = sanitize_tempo(600 / round(divisor, 1))
tempo = sanitize_tempo(BPM_TO_TEMPO_SETTING(divisor))
lines.Cut(1, 2)
else
tempo = sanitize_tempo(5) // default 120 BPM
@@ -148,13 +148,7 @@
editing = text2num(href_list["edit"]) - 1
if(href_list["repeat"]) //Changing this from a toggle to a number of repeats to avoid infinite loops.
if(playing)
return //So that people cant keep adding to repeat. If the do it intentionally, it could result in the server crashing.
repeat += round(text2num(href_list["repeat"]))
if(repeat < 0)
repeat = 0
if(repeat > max_repeats)
repeat = max_repeats
set_repeats(repeat + text2num(href_list["repeat"]))
else if(href_list["tempo"])
tempo = sanitize_tempo(tempo + text2num(href_list["tempo"]))
@@ -193,22 +187,22 @@
else if(href_list["setlinearfalloff"])
var/amount = input(usr, "Set linear sustain duration in seconds", "Linear Sustain Duration") as null|num
if(!isnull(amount))
set_linear_falloff_duration(round(amount * 10, world.tick_lag))
set_linear_falloff_duration(amount)
else if(href_list["setexpfalloff"])
var/amount = input(usr, "Set exponential sustain factor", "Exponential sustain factor") as null|num
if(!isnull(amount))
set_exponential_drop_rate(round(amount, 0.00001))
set_exponential_drop_rate(amount)
else if(href_list["setvolume"])
var/amount = input(usr, "Set volume", "Volume") as null|num
if(!isnull(amount))
set_volume(round(amount, 1))
set_volume(amount)
else if(href_list["setdropoffvolume"])
var/amount = input(usr, "Set dropoff threshold", "Dropoff Threshold Volume") as null|num
if(!isnull(amount))
set_dropoff_volume(round(amount, 0.01))
set_dropoff_volume(amount)
else if(href_list["switchinstrument"])
if(!length(allowed_instrument_ids))
@@ -238,12 +232,9 @@
note_shift = clamp(amount, note_shift_min, note_shift_max)
else if(href_list["setsustainmode"])
var/choice = input(usr, "Choose a sustain mode", "Sustain Mode") as null|anything in list("Linear", "Exponential")
switch(choice)
if("Linear")
sustain_mode = SUSTAIN_LINEAR
if("Exponential")
sustain_mode = SUSTAIN_EXPONENTIAL
var/choice = input(usr, "Choose a sustain mode", "Sustain Mode") as null|anything in SSinstruments.note_sustain_modes
if(choice)
sustain_mode = SSinstruments.note_sustain_modes[choice]
else if(href_list["togglesustainhold"])
full_sustain_held_note = !full_sustain_held_note
@@ -46,7 +46,7 @@
* * acc is either "b", "n", or "#"
* * oct is 1-8 (or 9 for C)
*/
/datum/song/proc/playkey_legacy(note, acc as text, oct, mob/user)
/datum/song/proc/playkey_legacy(note, acc as text, oct, atom/player)
// handle accidental -> B<>C of E<>F
if(acc == "b" && (note == 3 || note == 6)) // C or F
if(note == 3)
@@ -82,7 +82,7 @@
var/sound/music_played = sound(soundfile)
for(var/i in hearing_mobs)
var/mob/M = i
if(user && HAS_TRAIT(user, TRAIT_MUSICIAN) && isliving(M))
if(player && HAS_TRAIT(player, TRAIT_MUSICIAN) && isliving(M))
var/mob/living/L = M
L.apply_status_effect(STATUS_EFFECT_GOOD_MUSIC)
if(!(M?.client?.prefs?.toggles & SOUND_INSTRUMENTS))
@@ -42,7 +42,7 @@
* Plays a specific numerical key from our instrument to anyone who can hear us.
* Does a hearing check if enough time has passed.
*/
/datum/song/proc/playkey_synth(key, mob/user)
/datum/song/proc/playkey_synth(key, atom/player)
if(can_noteshift)
key = clamp(key + note_shift, key_min, key_max)
if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck)
@@ -62,7 +62,7 @@
last_channel_played = channel_text
for(var/i in hearing_mobs)
var/mob/M = i
if(user && HAS_TRAIT(user, TRAIT_MUSICIAN) && isliving(M))
if(player && HAS_TRAIT(player, TRAIT_MUSICIAN) && isliving(M))
var/mob/living/L = M
L.apply_status_effect(STATUS_EFFECT_GOOD_MUSIC)
if(!(M?.client?.prefs?.toggles & SOUND_INSTRUMENTS))
+6 -6
View File
@@ -15,12 +15,12 @@
QDEL_NULL(song)
return ..()
/obj/structure/musician/proc/should_stop_playing(mob/user)
if(!(anchored || can_play_unanchored))
return TRUE
if(!user)
return FALSE
return !user.canUseTopic(src, FALSE, TRUE, FALSE, FALSE) //can play with TK and while resting because fun.
/obj/structure/musician/proc/should_stop_playing(atom/music_player)
if(!(anchored || can_play_unanchored) || !ismob(music_player))
return STOP_PLAYING
var/mob/user = music_player
if(!user.canUseTopic(src, FALSE, TRUE, FALSE, FALSE)) //can play with TK and while resting because fun.
return STOP_PLAYING
/obj/structure/musician/ui_interact(mob/user)
. = ..()