From 54f8dbb8482c6932de6638e8128e579ec5f2c458 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Sun, 22 Oct 2023 00:19:51 +0200 Subject: [PATCH] [MIRROR] Light dreaming refactor + new dream [MDB IGNORE] (#24499) * Light dreaming refactor + new dream (#78996) ## About The Pull Request Makes it so new dream types can be more easily added to the game with effects within them beyond just some text being displayed if you want. I added a dream that plays a random sound file available in the game files to demonstrate how it's used. Mainly I wanted this out of the way for another thing I'm working on. I also made it so if the sound subsystem fails to reserve a channel it throws a runtime since this happening is likely always going to result in a bug, like it did while I was testing this. ## Changelog :cl: add: New dream that plays sound at you /:cl: * Light dreaming refactor + new dream --------- Co-authored-by: Emmett Gaines --- code/__HELPERS/files.dm | 21 +++- code/__HELPERS/text.dm | 5 + code/controllers/subsystem/sounds.dm | 26 ++++- code/modules/flufftext/Dreaming.dm | 146 +++++++++++++++++++++------ 4 files changed, 165 insertions(+), 33 deletions(-) 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))