From 76934c9f28a79010c7e47eedfe976ffd48332a36 Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Wed, 13 Aug 2025 20:04:43 -0500 Subject: [PATCH] Instruments can be synced by setting an ID in their UI (#92413) Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com> --- code/modules/instruments/items.dm | 13 +++-- code/modules/instruments/songs/_song.dm | 48 ++++++++++++++++++- code/modules/instruments/songs/editor.dm | 6 +++ code/modules/instruments/stationary.dm | 25 ++++++---- .../tgui/interfaces/InstrumentEditor.tsx | 29 ++++++++++- 5 files changed, 104 insertions(+), 17 deletions(-) diff --git a/code/modules/instruments/items.dm b/code/modules/instruments/items.dm index 2fe51b434e0..de6ae2d0bbf 100644 --- a/code/modules/instruments/items.dm +++ b/code/modules/instruments/items.dm @@ -23,19 +23,22 @@ QDEL_NULL(song) return ..() -/obj/item/instrument/proc/should_stop_playing(atom/music_player) +/obj/item/instrument/proc/can_play(atom/music_player) if(!ismob(music_player)) - return STOP_PLAYING + return FALSE var/mob/user = music_player - if(user.incapacitated || !((loc == user) || (isturf(loc) && Adjacent(user)))) // sorry, no more TK playing. - return STOP_PLAYING + if(user.incapacitated) + return FALSE + if(!Adjacent(user)) + return FALSE + return TRUE /obj/item/instrument/suicide_act(mob/living/user) user.visible_message(span_suicide("[user] begins to play 'Gloomy Sunday'! It looks like [user.p_theyre()] trying to commit suicide!")) return BRUTELOSS /obj/item/instrument/ui_interact(mob/user, datum/tgui/ui) - song.ui_interact(user) + return song.ui_interact(user) /obj/item/instrument/violin name = "space violin" diff --git a/code/modules/instruments/songs/_song.dm b/code/modules/instruments/songs/_song.dm index fb0e4f08744..f3f630cc7ec 100644 --- a/code/modules/instruments/songs/_song.dm +++ b/code/modules/instruments/songs/_song.dm @@ -8,6 +8,9 @@ /// Name of the song var/name = "Untitled" + /// ID for syncing songs together + var/id = "" + /// The atom we're attached to/playing from var/atom/parent @@ -212,6 +215,32 @@ current_chord = 1 music_player = user START_PROCESSING(SSinstruments, src) + if(id) + sync_play() + +/** + * Attempts to find other instruments with the same ID and syncs them to our song. + */ +/datum/song/proc/sync_play() + for(var/datum/song/other_instrument as anything in SSinstruments.songs) + if(other_instrument == src || other_instrument.id != id) + continue + if(other_instrument.playing) + continue + var/atom/other_player = other_instrument.find_sync_player() + if(isnull(other_player) || !(other_player in view(parent))) + continue + // copies the main song info to target songs + other_instrument.lines = lines.Copy() + other_instrument.max_repeats = max_repeats + other_instrument.tempo = tempo + other_instrument.start_playing(other_player) + +/** + * Finds a player which would reasonably be able to play this song. + */ +/datum/song/proc/find_sync_player() + return null /** * Stops playing, terminating all sounds if in synthesized mode. Clears hearing_mobs. @@ -388,7 +417,14 @@ if(. == STOP_PLAYING || . == IGNORE_INSTRUMENT_CHECKS) return var/obj/item/instrument/I = parent - return I.should_stop_playing(player) + return I.can_play(player) ? NONE : STOP_PLAYING + +/datum/song/handheld/find_sync_player() + var/obj/item/instrument/instrument = parent + var/mob/living/player = get(parent, /mob/living) + if(instrument.can_play(player)) + return player + return null // subtype for stationary structures, like pianos /datum/song/stationary @@ -398,4 +434,12 @@ if(. == STOP_PLAYING || . == IGNORE_INSTRUMENT_CHECKS) return TRUE var/obj/structure/musician/M = parent - return M.should_stop_playing(player) + return M.can_play(player) ? NONE : STOP_PLAYING + +/datum/song/stationary/find_sync_player() + var/obj/structure/musician/piano = parent + for(var/mob/living/player in view(parent, 1)) + if(piano.can_play(player)) + return player + + return null diff --git a/code/modules/instruments/songs/editor.dm b/code/modules/instruments/songs/editor.dm index 4029e5c3954..aef02858c03 100644 --- a/code/modules/instruments/songs/editor.dm +++ b/code/modules/instruments/songs/editor.dm @@ -9,6 +9,7 @@ /datum/song/ui_data(mob/user) var/list/data = ..() + data["id"] = id data["using_instrument"] = using_instrument?.name || "No instrument loaded!" data["note_shift"] = note_shift data["octaves"] = round(note_shift / 12, 0.01) @@ -71,6 +72,11 @@ else stop_playing() return TRUE + if("set_instrument_id") + var/new_id = reject_bad_name(LOWER_TEXT(params["id"]), max_length = 20, allow_numbers = TRUE, cap_after_symbols = FALSE) + if(new_id) + id = new_id + return TRUE if("change_instrument") var/new_instrument = params["new_instrument"] //only one instrument, so no need to bother changing it. diff --git a/code/modules/instruments/stationary.dm b/code/modules/instruments/stationary.dm index 621ecc664ed..e9413cfbe41 100644 --- a/code/modules/instruments/stationary.dm +++ b/code/modules/instruments/stationary.dm @@ -3,9 +3,12 @@ desc = "Something broke, contact coderbus." interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT | INTERACT_ATOM_REQUIRES_DEXTERITY integrity_failure = 0.25 + /// IF FALSE music stops when the piano is unanchored. var/can_play_unanchored = FALSE + /// Our allowed list of instrument ids. This is nulled on initialize. var/list/allowed_instrument_ids = list("r3grand","r3harpsi","crharpsi","crgrand1","crbright1", "crichugan", "crihamgan","piano") - var/datum/song/song + /// Our song datum. + var/datum/song/stationary/song /obj/structure/musician/Initialize(mapload) . = ..() @@ -16,18 +19,22 @@ QDEL_NULL(song) return ..() -/obj/structure/musician/proc/should_stop_playing(atom/music_player) - if(!(anchored || can_play_unanchored) || !ismob(music_player)) - return STOP_PLAYING +/obj/structure/musician/proc/can_play(atom/music_player) + if(!anchored && !can_play_unanchored) + return FALSE + if(!ismob(music_player)) + return FALSE var/mob/user = music_player - if(!ISADVANCEDTOOLUSER(user)) - to_chat(src, span_warning("You don't have the dexterity to do this!")) - return STOP_PLAYING + return FALSE + if(user.incapacitated) + return FALSE + if(!Adjacent(user)) + return FALSE + return TRUE /obj/structure/musician/ui_interact(mob/user) - . = ..() - song.ui_interact(user) + return song.ui_interact(user) /obj/structure/musician/wrench_act(mob/living/user, obj/item/tool) . = ..() diff --git a/tgui/packages/tgui/interfaces/InstrumentEditor.tsx b/tgui/packages/tgui/interfaces/InstrumentEditor.tsx index 9f68ab52e46..d1e7b4936cd 100644 --- a/tgui/packages/tgui/interfaces/InstrumentEditor.tsx +++ b/tgui/packages/tgui/interfaces/InstrumentEditor.tsx @@ -7,6 +7,8 @@ import { NumberInput, Section, Stack, + Input, + Tooltip, } from 'tgui-core/components'; import type { BooleanLike } from 'tgui-core/react'; @@ -14,6 +16,7 @@ import { useBackend } from '../backend'; import { Window } from '../layouts'; type Data = { + id: string; using_instrument: string; note_shift_min: number; note_shift_max: number; @@ -73,6 +76,7 @@ export const InstrumentEditor = (props) => { const InstrumentSettings = (props) => { const { act, data } = useBackend(); const { + id, playing, repeat, max_repeats, @@ -106,15 +110,37 @@ const InstrumentSettings = (props) => { return (
{lines.length > 0 && ( - + )} + + + + ID: + + + act('set_instrument_id', { id: value })} + /> + Repeats Left: { {sustain_mode_button}: