mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-25 00:51:55 +00:00
Implements Instrument UI Interaction When Laying Down (#72926) ## About The Pull Request Adds floor_okay = TRUE tag to the song editor dm. Basically a small QoL to allow you to initiate your song if you were say, slipped by a 100 potency banana and wanted to continue playing your song while waiting to get back up. The change allows you to actually interact with instrument UI while laying down. ## Why It's Good For The Game Took a bit of searching but I find that this QoL feature really made a big impact to me at least. You can play your instrument on the floor while laying down. It makes sense to me that you should be physically able to strum a guitar or press keys when you're on the floor as you already are able to play anyway by starting the instrument standing up and then laying down. This just cuts out the need to have to stand up to re-interact with the UI if you say, got slipped or lost both your legs in a tragic accident. Also allows you to chill on a bed and play without having to get up, I guess. This might be a minor gameplay change to some but for those who play instruments the entire round for others to enjoy, this is a huge QoL. ## Changelog 🆑 qol: Musicians rejoice! You can now interact with the instrument UI while laying down. /🆑 Co-authored-by: ophaq <44999764+ophaq@users.noreply.github.com>
245 lines
9.7 KiB
Plaintext
245 lines
9.7 KiB
Plaintext
/**
|
|
* Returns the HTML for the status UI for this song datum.
|
|
*/
|
|
/datum/song/proc/instrument_status_ui()
|
|
. = list()
|
|
. += "<div class='statusDisplay'>"
|
|
. += "<b><a href='?src=[REF(src)];switchinstrument=1'>Current instrument</a>:</b> "
|
|
if(!using_instrument)
|
|
. += "[span_danger("No instrument loaded!")]<br>"
|
|
else
|
|
. += "[using_instrument.name]<br>"
|
|
. += "Playback Settings:<br>"
|
|
if(can_noteshift)
|
|
. += "<a href='?src=[REF(src)];setnoteshift=1'>Note Shift/Note Transpose</a>: [note_shift] keys / [round(note_shift / 12, 0.01)] octaves<br>"
|
|
var/smt
|
|
var/modetext = ""
|
|
switch(sustain_mode)
|
|
if(SUSTAIN_LINEAR)
|
|
smt = "Linear"
|
|
modetext = "<a href='?src=[REF(src)];setlinearfalloff=1'>Linear Sustain Duration</a>: [sustain_linear_duration / 10] seconds<br>"
|
|
if(SUSTAIN_EXPONENTIAL)
|
|
smt = "Exponential"
|
|
modetext = "<a href='?src=[REF(src)];setexpfalloff=1'>Exponential Falloff Factor</a>: [sustain_exponential_dropoff]% per decisecond<br>"
|
|
. += "<a href='?src=[REF(src)];setsustainmode=1'>Sustain Mode</a>: [smt]<br>"
|
|
. += modetext
|
|
. += using_instrument?.ready()? "Status: <span class='good'>Ready</span><br>" : "Status: <span class='bad'>!Instrument Definition Error!</span><br>"
|
|
. += "Instrument Type: [legacy? "Legacy" : "Synthesized"]<br>"
|
|
. += "<a href='?src=[REF(src)];setvolume=1'>Volume</a>: [volume]<br>"
|
|
. += "<a href='?src=[REF(src)];setdropoffvolume=1'>Volume Dropoff Threshold</a>: [sustain_dropoff_volume]<br>"
|
|
. += "<a href='?src=[REF(src)];togglesustainhold=1'>Sustain indefinitely last held note</a>: [full_sustain_held_note? "Enabled" : "Disabled"].<br>"
|
|
. += "</div>"
|
|
|
|
/datum/song/ui_interact(mob/user)
|
|
var/list/dat = list()
|
|
|
|
dat += instrument_status_ui()
|
|
|
|
if(lines.len > 0)
|
|
dat += "<H3>Playback</H3>"
|
|
if(!playing)
|
|
dat += "<A href='?src=[REF(src)];play=1'>Play</A> <span class='linkOn'>Stop</span><BR><BR>"
|
|
dat += "Repeat Song: "
|
|
dat += repeat > 0 ? "<A href='?src=[REF(src)];repeat=-10'>-</A><A href='?src=[REF(src)];repeat=-1'>-</A>" : "<span class='linkOff'>-</SPAN><SPAN CLASS='linkOff'>-</span>"
|
|
dat += " [repeat] times "
|
|
dat += repeat < max_repeats ? "<A href='?src=[REF(src)];repeat=1'>+</A><A href='?src=[REF(src)];repeat=10'>+</A>" : "<span class='linkOff'>+</SPAN><SPAN CLASS='linkOff'>+</span>"
|
|
dat += "<BR>"
|
|
else
|
|
dat += "<span class='linkOn'>Play</span> <A href='?src=[REF(src)];stop=1'>Stop</A><BR>"
|
|
dat += "Repeats left: <B>[repeat]</B><BR>"
|
|
if(!editing)
|
|
dat += "<BR><B><A href='?src=[REF(src)];edit=2'>Show Editor</A></B><BR>"
|
|
else
|
|
dat += "<H3>Editing</H3>"
|
|
dat += "<B><A href='?src=[REF(src)];edit=1'>Hide Editor</A></B>"
|
|
dat += " <A href='?src=[REF(src)];newsong=1'>Start a New Song</A>"
|
|
dat += " <A href='?src=[REF(src)];import=1'>Import a Song</A><BR><BR>"
|
|
var/bpm = round(600 / tempo)
|
|
dat += "Tempo: <A href='?src=[REF(src)];tempo=[world.tick_lag]'>-</A> [bpm] BPM <A href='?src=[REF(src)];tempo=-[world.tick_lag]'>+</A><BR><BR>"
|
|
var/linecount = 0
|
|
for(var/line in lines)
|
|
linecount += 1
|
|
dat += "Line [linecount]: <A href='?src=[REF(src)];modifyline=[linecount]'>Edit</A> <A href='?src=[REF(src)];deleteline=[linecount]'>X</A> [line]<BR>"
|
|
dat += "<A href='?src=[REF(src)];newline=1'>Add Line</A><BR><BR>"
|
|
if(help)
|
|
dat += "<B><A href='?src=[REF(src)];help=1'>Hide Help</A></B><BR>"
|
|
dat += {"
|
|
Lines are a series of chords, separated by commas (,), each with notes separated by hyphens (-).<br>
|
|
Every note in a chord will play together, with chord timed by the tempo.<br>
|
|
<br>
|
|
Notes are played by the names of the note, and optionally, the accidental, and/or the octave number.<br>
|
|
By default, every note is natural and in octave 3. Defining otherwise is remembered for each note.<br>
|
|
Example: <i>C,D,E,F,G,A,B</i> will play a C major scale.<br>
|
|
After a note has an accidental placed, it will be remembered: <i>C,C4,C,C3</i> is <i>C3,C4,C4,C3</i><br>
|
|
Chords can be played simply by seperating each note with a hyphon: <i>A-C#,Cn-E,E-G#,Gn-B</i><br>
|
|
A pause may be denoted by an empty chord: <i>C,E,,C,G</i><br>
|
|
To make a chord be a different time, end it with /x, where the chord length will be length<br>
|
|
defined by tempo / x: <i>C,G/2,E/4</i><br>
|
|
Combined, an example is: <i>E-E4/4,F#/2,G#/8,B/8,E3-E4/4</i>
|
|
<br>
|
|
Lines may be up to [MUSIC_MAXLINECHARS] characters.<br>
|
|
A song may only contain up to [MUSIC_MAXLINES] lines.<br>
|
|
"}
|
|
else
|
|
dat += "<B><A href='?src=[REF(src)];help=2'>Show Help</A></B><BR>"
|
|
|
|
var/datum/browser/popup = new(user, "instrument", parent?.name || "instrument", 700, 500)
|
|
popup.set_content(dat.Join(""))
|
|
popup.open()
|
|
|
|
/**
|
|
* Parses a song the user has input into lines and stores them.
|
|
*/
|
|
/datum/song/proc/ParseSong(new_song)
|
|
set waitfor = FALSE
|
|
//split into lines
|
|
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(BPM_TO_TEMPO_SETTING(divisor))
|
|
lines.Cut(1, 2)
|
|
else
|
|
tempo = sanitize_tempo(5) // default 120 BPM
|
|
if(lines.len > MUSIC_MAXLINES)
|
|
to_chat(usr, "Too many lines!")
|
|
lines.Cut(MUSIC_MAXLINES + 1)
|
|
var/linenum = 1
|
|
for(var/l in lines)
|
|
if(length_char(l) > MUSIC_MAXLINECHARS)
|
|
to_chat(usr, "Line [linenum] too long!")
|
|
lines.Remove(l)
|
|
else
|
|
linenum++
|
|
updateDialog(usr) // make sure updates when complete
|
|
|
|
/datum/song/Topic(href, href_list)
|
|
if(!usr.canUseTopic(parent, be_close = TRUE, no_dexterity = FALSE, no_tk = FALSE, need_hands = FALSE, floor_okay = TRUE))
|
|
usr << browse(null, "window=instrument")
|
|
usr.unset_machine()
|
|
return
|
|
|
|
parent.add_fingerprint(usr)
|
|
|
|
if(href_list["newsong"])
|
|
lines = new()
|
|
tempo = sanitize_tempo(5) // default 120 BPM
|
|
name = ""
|
|
|
|
else if(href_list["import"])
|
|
var/t = ""
|
|
do
|
|
t = html_encode(input(usr, "Please paste the entire song, formatted:", text("[]", name), t) as message)
|
|
if(!in_range(parent, usr))
|
|
return
|
|
|
|
if(length_char(t) >= MUSIC_MAXLINES * MUSIC_MAXLINECHARS)
|
|
var/cont = tgui_alert(usr, "Your message is too long! Would you like to continue editing it?", "Warning", list("Yes", "No"))
|
|
if(cont != "Yes")
|
|
break
|
|
while(length_char(t) > MUSIC_MAXLINES * MUSIC_MAXLINECHARS)
|
|
ParseSong(t)
|
|
|
|
else if(href_list["help"])
|
|
help = text2num(href_list["help"]) - 1
|
|
|
|
else if(href_list["edit"])
|
|
editing = text2num(href_list["edit"]) - 1
|
|
|
|
if(href_list["repeat"]) //Changing this from a toggle to a number of repeats to avoid infinite loops.
|
|
set_repeats(repeat + text2num(href_list["repeat"]))
|
|
|
|
else if(href_list["tempo"])
|
|
tempo = sanitize_tempo(tempo + text2num(href_list["tempo"]))
|
|
|
|
else if(href_list["play"])
|
|
INVOKE_ASYNC(src, PROC_REF(start_playing), usr)
|
|
|
|
else if(href_list["newline"])
|
|
var/newline = tgui_input_text(usr, "Enter your line ", parent.name)
|
|
if(!newline || !in_range(parent, usr))
|
|
return
|
|
if(lines.len > MUSIC_MAXLINES)
|
|
return
|
|
if(length(newline) > MUSIC_MAXLINECHARS)
|
|
newline = copytext(newline, 1, MUSIC_MAXLINECHARS)
|
|
lines.Add(newline)
|
|
|
|
else if(href_list["deleteline"])
|
|
var/num = round(text2num(href_list["deleteline"]))
|
|
if(num > lines.len || num < 1)
|
|
return
|
|
lines.Cut(num, num+1)
|
|
|
|
else if(href_list["modifyline"])
|
|
var/num = round(text2num(href_list["modifyline"]),1)
|
|
var/content = tgui_input_text(usr, "Enter your line ", parent.name, lines[num], MUSIC_MAXLINECHARS)
|
|
if(!content || !in_range(parent, usr))
|
|
return
|
|
if(num > lines.len || num < 1)
|
|
return
|
|
lines[num] = content
|
|
|
|
else if(href_list["stop"])
|
|
stop_playing()
|
|
|
|
else if(href_list["setlinearfalloff"])
|
|
var/amount = tgui_input_number(usr, "Set linear sustain duration in seconds", "Linear Sustain Duration", 0.1, INSTRUMENT_MAX_TOTAL_SUSTAIN, 0.1, round_value = FALSE)
|
|
if(!isnull(amount))
|
|
set_linear_falloff_duration(amount)
|
|
|
|
else if(href_list["setexpfalloff"])
|
|
var/amount = tgui_input_number(usr, "Set exponential sustain factor", "Exponential sustain factor", INSTRUMENT_EXP_FALLOFF_MIN, INSTRUMENT_EXP_FALLOFF_MAX, INSTRUMENT_EXP_FALLOFF_MIN, round_value = FALSE)
|
|
if(!isnull(amount))
|
|
set_exponential_drop_rate(amount)
|
|
|
|
else if(href_list["setvolume"])
|
|
var/amount = tgui_input_number(usr, "Set volume", "Volume", 1, 75, 1)
|
|
if(!isnull(amount))
|
|
set_volume(amount)
|
|
|
|
else if(href_list["setdropoffvolume"])
|
|
var/amount = tgui_input_number(usr, "Set dropoff threshold", "Dropoff Volume", max_value = 100)
|
|
if(!isnull(amount))
|
|
set_dropoff_volume(amount)
|
|
|
|
else if(href_list["switchinstrument"])
|
|
if(!length(allowed_instrument_ids))
|
|
return
|
|
else if(length(allowed_instrument_ids) == 1)
|
|
set_instrument(allowed_instrument_ids[1])
|
|
return
|
|
var/list/categories = list()
|
|
for(var/i in allowed_instrument_ids)
|
|
var/datum/instrument/I = SSinstruments.get_instrument(i)
|
|
if(I)
|
|
LAZYSET(categories[I.category || "ERROR CATEGORY"], I.name, I.id)
|
|
var/cat = tgui_input_list(usr, "Select Category", "Instrument Category", categories)
|
|
if(isnull(cat))
|
|
return
|
|
var/list/instruments = categories[cat]
|
|
var/choice = tgui_input_list(usr, "Select Instrument", "Instrument Selection", instruments)
|
|
if(isnull(choice))
|
|
return
|
|
if(isnull(instruments[choice]))
|
|
return
|
|
choice = instruments[choice] //get id
|
|
if(choice)
|
|
set_instrument(choice)
|
|
|
|
else if(href_list["setnoteshift"])
|
|
var/amount = input(usr, "Set note shift", "Note Shift") as null|num
|
|
if(!isnull(amount))
|
|
note_shift = clamp(amount, note_shift_min, note_shift_max)
|
|
|
|
else if(href_list["setsustainmode"])
|
|
var/choice = tgui_input_list(usr, "Choose a sustain mode", "Sustain Mode", 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
|
|
|
|
updateDialog(usr)
|