diff --git a/code/__HELPERS/files.dm b/code/__HELPERS/files.dm index c773b8e9b76..f8cb06fd762 100644 --- a/code/__HELPERS/files.dm +++ b/code/__HELPERS/files.dm @@ -72,7 +72,12 @@ GLOBAL_VAR_INIT(fileaccess_timer, 0) #undef FTPDELAY #undef ADMIN_FTPDELAY_MODIFIER -/proc/pathwalk(path) +/** + * Takes a directory and returns every file within every sub directory. + * If extensions_filter is provided then only files that end in that extension are given back. + * If extensions_filter is a list, any file that matches at least one entry is given back. + */ +/proc/pathwalk(path, extensions_filter) var/list/jobs = list(path) var/list/filenames = list() @@ -82,9 +87,19 @@ GLOBAL_VAR_INIT(fileaccess_timer, 0) for(var/new_filename in new_filenames) // if filename ends in / it is a directory, append to currdir if(findtext(new_filename, "/", -1)) - jobs += current_dir + new_filename + jobs += "[current_dir][new_filename]" + continue + // filename extension filtering + if(extensions_filter) + if(islist(extensions_filter)) + for(var/allowed_extension in extensions_filter) + if(endswith(new_filename, allowed_extension)) + filenames += "[current_dir][new_filename]" + break + else if(endswith(new_filename, extensions_filter)) + filenames += "[current_dir][new_filename]" else - filenames += current_dir + new_filename + filenames += "[current_dir][new_filename]" return filenames /proc/pathflatten(path) diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm index 91406650d20..1e39cd6eaf3 100644 --- a/code/__HELPERS/text.dm +++ b/code/__HELPERS/text.dm @@ -1191,3 +1191,8 @@ GLOBAL_LIST_INIT(binary, list("0","1")) text2num(semver_regex.group[2]), text2num(semver_regex.group[3]), ) + +/// Returns TRUE if the input_text ends with the ending +/proc/endswith(input_text, ending) + var/input_length = LAZYLEN(ending) + return !!findtext(input_text, ending, -input_length) diff --git a/code/controllers/subsystem/sounds.dm b/code/controllers/subsystem/sounds.dm index 9067c077da7..2bc253c5af7 100644 --- a/code/controllers/subsystem/sounds.dm +++ b/code/controllers/subsystem/sounds.dm @@ -23,8 +23,12 @@ SUBSYSTEM_DEF(sounds) /// higher reserve position - decremented and incremented to reserve sound channels, anything above this is reserved. The channel at this index is the highest unreserved channel. var/channel_reserve_high + /// All valid sound files in the sound directory + var/list/all_sounds + /datum/controller/subsystem/sounds/Initialize() setup_available_channels() + find_all_available_sounds() return SS_INIT_SUCCESS /datum/controller/subsystem/sounds/proc/setup_available_channels() @@ -37,6 +41,26 @@ SUBSYSTEM_DEF(sounds) channel_random_low = 1 channel_reserve_high = length(channel_list) +/datum/controller/subsystem/sounds/proc/find_all_available_sounds() + all_sounds = list() + // Put more common extensions first to speed this up a bit + var/static/list/valid_file_extensions = list( + ".ogg", + ".wav", + ".mid", + ".midi", + ".mod", + ".it", + ".s3m", + ".xm", + ".oxm", + ".raw", + ".wma", + ".aiff", + ) + + all_sounds = pathwalk("sound/", valid_file_extensions) + /// Removes a channel from using list. /datum/controller/subsystem/sounds/proc/free_sound_channel(channel) var/text_channel = num2text(channel) @@ -78,7 +102,7 @@ SUBSYSTEM_DEF(sounds) CRASH("Attempted to reserve sound channel without datum using the managed proc.") .= reserve_channel() if(!.) - return FALSE + CRASH("No more sound channels can be reserved") var/text_channel = num2text(.) using_channels[text_channel] = D LAZYINITLIST(using_channels_by_datum[D]) diff --git a/code/modules/flufftext/Dreaming.dm b/code/modules/flufftext/Dreaming.dm index 9c0ca9d33c5..06472f84be6 100644 --- a/code/modules/flufftext/Dreaming.dm +++ b/code/modules/flufftext/Dreaming.dm @@ -19,14 +19,94 @@ /mob/living/carbon/proc/dream() set waitfor = FALSE - var/list/dream_fragments = list() + + var/datum/dream/chosen_dream = pick_weight(GLOB.dreams) + + dreaming = TRUE + dream_sequence(chosen_dream.GenerateDream(src), chosen_dream) + +/** + * Displays the passed list of dream fragments to a sleeping carbon. + * + * Displays the first string of the passed dream fragments, then either ends the dream sequence + * or performs a callback on itself depending on if there are any remaining dream fragments to display. + * + * Arguments: + * * dream_fragments - A list of strings, in the order they will be displayed. + * * current_dream - The dream datum used for the current dream + */ + +/mob/living/carbon/proc/dream_sequence(list/dream_fragments, datum/dream/current_dream) + if(stat != UNCONSCIOUS || HAS_TRAIT(src, TRAIT_CRITICAL_CONDITION)) + dreaming = FALSE + current_dream.OnDreamEnd(src) + return + var/next_message = dream_fragments[1] + dream_fragments.Cut(1,2) + + if(istype(next_message, /datum/callback)) + var/datum/callback/something_happens = next_message + next_message = something_happens.InvokeAsync(src) + + to_chat(src, span_notice("... [next_message] ...")) + + if(LAZYLEN(dream_fragments)) + var/next_wait = rand(10, 30) + if(current_dream.sleep_until_finished) + AdjustSleeping(next_wait) + addtimer(CALLBACK(src, PROC_REF(dream_sequence), dream_fragments, current_dream), next_wait) + else + dreaming = FALSE + current_dream.OnDreamEnd(src) + +//------------------------- +// DREAM DATUMS + +GLOBAL_LIST_INIT(dreams, populate_dream_list()) + +/proc/populate_dream_list() + var/list/output = list() + for(var/datum/dream/dream_type as anything in subtypesof(/datum/dream)) + output[new dream_type] = initial(dream_type.weight) + return output + +/** + * Contains all the behavior needed to play a kind of dream. + * All dream types get randomly selected from based on weight when an appropriate mobs dreams. + */ +/datum/dream + /// The relative chance this dream will be randomly selected + var/weight = 0 + + /// Causes the mob to sleep long enough for the dream to finish if begun + var/sleep_until_finished = FALSE + +/** + * Called when beginning a new dream for the dreamer. + * Gives back a list of dream events. Events can be text or callbacks that return text. + */ +/datum/dream/proc/GenerateDream(mob/living/carbon/dreamer) + return list() + +/** + * Called when the dream ends or is interrupted. + */ +/datum/dream/proc/OnDreamEnd(mob/living/carbon/dreamer) + return + +/// The classic random dream of various words that might form a cohesive narrative, but usually wont +/datum/dream/random + weight = 1000 + +/datum/dream/random/GenerateDream(mob/living/carbon/dreamer) var/list/custom_dream_nouns = list() var/fragment = "" - for(var/obj/item/bedsheet/sheet in loc) + for(var/obj/item/bedsheet/sheet in dreamer.loc) custom_dream_nouns += sheet.dream_messages - dream_fragments += "you see" + . = list() + . += "you see" //Subject if(custom_dream_nouns.len && prob(90)) @@ -40,7 +120,7 @@ fragment = replacetext(fragment, "%ADJECTIVE% ", "") if(findtext(fragment, "%A% ")) fragment = "\a [replacetext(fragment, "%A% ", "")]" - dream_fragments += fragment + . += fragment //Verb fragment = "" @@ -51,10 +131,9 @@ else fragment += "will " fragment += pick(GLOB.verbs) - dream_fragments += fragment + . += fragment if(prob(25)) - dream_sequence(dream_fragments) return //Object @@ -66,29 +145,38 @@ fragment = replacetext(fragment, "%ADJECTIVE% ", "") if(findtext(fragment, "%A% ")) fragment = "\a [replacetext(fragment, "%A% ", "")]" - dream_fragments += fragment + . += fragment - dreaming = TRUE - dream_sequence(dream_fragments) +/// Dream plays a random sound at you, chosen from all sounds in the folder +/datum/dream/hear_something + weight = 500 -/** - * Displays the passed list of dream fragments to a sleeping carbon. - * - * Displays the first string of the passed dream fragments, then either ends the dream sequence - * or performs a callback on itself depending on if there are any remaining dream fragments to display. - * - * Arguments: - * * dream_fragments - A list of strings, in the order they will be displayed. - */ + var/reserved_sound_channel -/mob/living/carbon/proc/dream_sequence(list/dream_fragments) - if(stat != UNCONSCIOUS || HAS_TRAIT(src, TRAIT_CRITICAL_CONDITION)) - dreaming = FALSE - return - var/next_message = dream_fragments[1] - dream_fragments.Cut(1,2) - to_chat(src, span_notice("... [next_message] ...")) - if(LAZYLEN(dream_fragments)) - addtimer(CALLBACK(src, PROC_REF(dream_sequence), dream_fragments), rand(10,30)) - else - dreaming = FALSE +/datum/dream/hear_something/New() + . = ..() + RegisterSignal(SSsounds, COMSIG_SUBSYSTEM_POST_INITIALIZE, PROC_REF(ReserveSoundChannel)) + +/datum/dream/hear_something/GenerateDream(mob/living/carbon/dreamer) + . = ..() + . += pick("you wind up a toy", "you hear something strange", "you pick out a record to play", "you hit shuffle on your music player") + . += CALLBACK(src, PROC_REF(PlayRandomSound)) + . += "it reminds you of something" + +/datum/dream/hear_something/OnDreamEnd(mob/living/carbon/dreamer) + . = ..() + // In case we play some long ass music track + addtimer(CALLBACK(src, PROC_REF(StopSound), dreamer), 5 SECONDS) + +/datum/dream/hear_something/proc/ReserveSoundChannel() + reserved_sound_channel = SSsounds.reserve_sound_channel(src) + UnregisterSignal(SSsounds, COMSIG_SUBSYSTEM_POST_INITIALIZE) + +/datum/dream/hear_something/proc/PlayRandomSound(mob/living/carbon/dreamer) + var/sound/random_sound = sound(pick(SSsounds.all_sounds), channel=reserved_sound_channel) + random_sound.status = SOUND_STREAM + SEND_SOUND(dreamer, random_sound) + return "you hear something you weren't expecting!" + +/datum/dream/hear_something/proc/StopSound(mob/living/carbon/dreamer) + SEND_SOUND(dreamer, sound(channel=reserved_sound_channel))