Skip to content

Commit

Permalink
Sleeping Tweaks (#1077)
Browse files Browse the repository at this point in the history
* no longer heals brute/burn

* Dream Datums

* NIGHTMARE NIGHTMARE NIGHTMARE

* random schizo nightmares

* Update code/modules/dreams/detective_nightmare.dm

Co-authored-by: Zonespace <[email protected]>

---------

Co-authored-by: Zonespace <[email protected]>
  • Loading branch information
Kapu1178 and Zonespace27 committed Aug 25, 2024
1 parent e223ab9 commit e8a934a
Show file tree
Hide file tree
Showing 12 changed files with 341 additions and 48 deletions.
6 changes: 6 additions & 0 deletions code/__DEFINES/dream.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/// Dream is generic, and rollable by anyone.
#define DREAM_GENERIC (1<<0)
/// Dream can only be dreampt once per round per mind.
#define DREAM_ONCE_PER_ROUND (1<<1)
/// Dream is considered complete even if cut short.
#define DREAM_CUT_SHORT_IS_COMPLETE (1<<2)
7 changes: 7 additions & 0 deletions code/__DEFINES/traits.dm
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,11 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_VOIDSTORM_IMMUNE "voidstorm_immune"
#define TRAIT_WEATHER_IMMUNE "weather_immune" //Immune to ALL weather effects.

/// Mob is dreaming
#define TRAIT_DREAMING "currently_dreaming"
/// Mob cannot dream
#define TRAIT_CANNOT_DREAM "unable_to_dream"

//non-mob traits
/// Used for limb-based paralysis, where replacing the limb will fix it.
#define TRAIT_PARALYSIS "paralysis"
Expand Down Expand Up @@ -868,6 +873,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define EMP_TRAIT "emp"
/// Given by the operating table
#define OPTABLE_TRAIT "optable"
/// Given by dreaming
#define DREAMING_SOURCE "dreaming"

/**
* Trait granted by [/mob/living/carbon/Initialize] and
Expand Down
2 changes: 2 additions & 0 deletions code/__HELPERS/global_lists.dm
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@

init_employers()

init_dreams()

/// Inits the crafting recipe list, sorting crafting recipe requirements in the process.
/proc/init_crafting_recipes(list/crafting_recipes)
for(var/path in subtypesof(/datum/crafting_recipe))
Expand Down
5 changes: 5 additions & 0 deletions code/datums/mind.dm
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@
///List of objective-specific equipment that couldn't properly be given to the mind
var/list/failed_special_equipment

///The cooldown for dreams.
COOLDOWN_DECLARE(dream_cooldown)
/// A lazylist of dream types we have fully experienced
var/list/finished_dream_types

/datum/mind/New(_key)
key = _key
martial_art = default_martial_art
Expand Down
28 changes: 15 additions & 13 deletions code/datums/status_effects/debuffs/debuffs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,14 @@
. = ..()
if(!.)
return

if(!HAS_TRAIT(owner, TRAIT_SLEEPIMMUNE))
ADD_TRAIT(owner, TRAIT_KNOCKEDOUT, TRAIT_STATUS_EFFECT(id))
tick_interval = -1

if(owner.mind)
COOLDOWN_START(owner.mind, dream_cooldown, 5 SECONDS) // You need to sleep for atleast 5 seconds to begin dreaming.

ADD_TRAIT(owner, TRAIT_DEAF, TRAIT_STATUS_EFFECT(id))
RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_SLEEPIMMUNE), PROC_REF(on_owner_insomniac))
RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_SLEEPIMMUNE), PROC_REF(on_owner_sleepy))
Expand All @@ -170,30 +175,27 @@
tick_interval = initial(tick_interval)

/datum/status_effect/incapacitating/sleeping/tick()
if(owner.maxHealth)
var/health_ratio = owner.health / owner.maxHealth
var/healing = -0.2
var/healing = -0.2
if(isturf(owner.loc))
if((locate(/obj/structure/bed) in owner.loc))
healing -= 0.3
else if((locate(/obj/structure/table) in owner.loc))
healing -= 0.1
for(var/obj/item/bedsheet/bedsheet in range(owner.loc,0))
if(bedsheet.loc != owner.loc) //bedsheets in your backpack/neck don't give you comfort
continue

if((locate(/obj/structure/table) in owner.loc))
healing -= 0.1
break //Only count the first bedsheet
if(health_ratio > 0.8)
owner.adjustBruteLoss(healing)
owner.adjustFireLoss(healing)
owner.adjustToxLoss(healing * 0.5, TRUE, TRUE)
owner.stamina.adjust(-healing)

if(owner.getToxLoss() >= 20)
owner.adjustToxLoss(healing * 0.5, TRUE, TRUE)

owner.stamina.adjust(-healing)

// Drunkenness gets reduced by 0.3% per tick (6% per 2 seconds)
owner.set_drunk_effect(owner.get_drunk_amount() * 0.997)

if(iscarbon(owner))
var/mob/living/carbon/carbon_owner = owner
carbon_owner.handle_dreams()
carbon_owner.try_dream()

if(prob(2) && owner.health > owner.crit_threshold)
owner.emote("snore")
Expand Down
115 changes: 115 additions & 0 deletions code/modules/dreams/_dream.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//-------------------------
// DREAM DATUMS

GLOBAL_LIST_EMPTY(generic_dreams_weighted)
GLOBAL_LIST_EMPTY(detective_dreams_weighted)
GLOBAL_LIST_EMPTY(all_dreams_weighted)

/proc/init_dreams()
for(var/datum/dream/dream_type as anything in subtypesof(/datum/dream))
if(isabstract(dream_type))
continue

var/datum/dream/dream = new dream_type
GLOB.all_dreams_weighted[dream] = initial(dream_type.weight)

if(initial(dream_type.dream_flags) & DREAM_GENERIC)
GLOB.generic_dreams_weighted[dream] = initial(dream_type.weight)

if(istype(dream, /datum/dream/detective_nightmare))
GLOB.detective_dreams_weighted[dream] = initial(dream_type.weight)

/**
* 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
abstract_type = /datum/dream

var/dream_flags = DREAM_GENERIC

/// The relative chance this dream will be randomly selected
var/weight = 1000

/// Causes the mob to sleep long enough for the dream to finish if begun
var/sleep_until_finished = FALSE

var/dream_cooldown = 10 SECONDS

/**
* 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.
* The associated value is the delay FOLLOWING the message at that index, in deciseconds.
*/
/datum/dream/proc/GenerateDream(mob/living/carbon/dreamer)
RETURN_TYPE(/list)
return list()

/**
* Called when the dream starts.
*/
/datum/dream/proc/OnDreamStart(mob/living/carbon/dreamer)
SHOULD_CALL_PARENT(TRUE)
ADD_TRAIT(dreamer.mind, TRAIT_DREAMING, DREAMING_SOURCE)

/**
* Called when the dream ends or is interrupted.
*/
/datum/dream/proc/OnDreamEnd(mob/living/carbon/dreamer, cut_short = FALSE)
SHOULD_CALL_PARENT(TRUE)

REMOVE_TRAIT(dreamer.mind, TRAIT_DREAMING, DREAMING_SOURCE)
COOLDOWN_START(dreamer.mind, dream_cooldown, dream_cooldown)

if(!cut_short || (dream_flags & DREAM_CUT_SHORT_IS_COMPLETE))
LAZYOR(dreamer.mind.finished_dream_types, type)

/**
* Called by dream_sequence to wrap a message in any effects.
*/
/datum/dream/proc/WrapMessage(mob/living/carbon/dreamer, message)
return span_notice("<i>... [message] ...</i>")

/datum/dream/proc/BeginDreaming(mob/living/carbon/dreamer)
set waitfor = FALSE

var/list/fragments = GenerateDream(dreamer)
OnDreamStart(dreamer)
DreamLoop(dreamer, dreamer.mind, fragments)

/**
* 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:
* * dreamer - The mob we're looping on.
* * dreamer_mind - The mind that is dreaming.
* * dream_fragments - A list of strings, in the order they will be displayed.
*/

/datum/dream/proc/DreamLoop(mob/living/carbon/dreamer, datum/mind/dreamer_mind, list/dream_fragments)
if(dreamer.stat != UNCONSCIOUS || QDELETED(dreamer) || (dreamer.mind != dreamer_mind))
OnDreamEnd(dreamer, TRUE)
return

var/next_message = dream_fragments[1]
var/next_wait = dream_fragments[next_message]
dream_fragments.Cut(1,2)

if(istype(next_message, /datum/callback))
var/datum/callback/something_happens = next_message
next_message = something_happens.Invoke(dreamer)

to_chat(dreamer, WrapMessage(dreamer, next_message))

// Dream's over.
if(!LAZYLEN(dream_fragments))
OnDreamEnd(dreamer)
return

if(sleep_until_finished)
dreamer.Sleeping(next_wait + 1 SECOND)

addtimer(CALLBACK(src, PROC_REF(DreamLoop), dreamer, dreamer_mind,dream_fragments), next_wait)
109 changes: 109 additions & 0 deletions code/modules/dreams/detective_nightmare.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/datum/dream/detective_nightmare
abstract_type = /datum/dream/detective_nightmare

dream_flags = parent_type::dream_flags & ~DREAM_GENERIC

/datum/dream/detective_nightmare/WrapMessage(mob/living/carbon/dreamer, message)
return message

/datum/dream/detective_nightmare/proc/get_dream_name(mob/living/carbon/dreamer)
if(prob(1))
return "Harry"

var/list/name_split = splittext(dreamer.mind.name, " ")
return name_split[1]

/datum/dream/detective_nightmare/limbic_system
abstract_type = /datum/dream/detective_nightmare/limbic_system

dream_flags = parent_type::dream_flags | DREAM_ONCE_PER_ROUND | DREAM_CUT_SHORT_IS_COMPLETE
weight = 350

dream_cooldown = 25 SECONDS

/// The fragments are deterministic, just need to replacetext the name.
var/list/fragments

/datum/dream/detective_nightmare/limbic_system/GenerateDream(mob/living/carbon/dreamer)
. = list()
var/name = get_dream_name(dreamer)

for(var/fragment in fragments)
.["<i>[span_statsbad(replacetext(fragment, "%NAME%", name))]</i>"] = fragments[fragment]

/datum/dream/detective_nightmare/limbic_system/super_asshole
fragments = list(
"Hello %NAME%, I am so pleased you could join us again. NOT." = 7 SECONDS,
"This station is tearing your feeble, disgusting excuse for a body to shreeeedssss." = 9 SECONDS,
"You should've listened to me loooooong aaaggoooo, %NAME%. Maybe you would not be such a violent, waste of space if you had." = 12 SECONDS,
"Alright then, be that way. I'll be waiting for your next visit."
)

/datum/dream/detective_nightmare/limbic_system/calm_asshole
fragments = list(
"Here we are again, my broken bird. You stay so long yet always leave, why?." = 7 SECONDS,
"We think you should give up and stay with us, it will be better that way. Do you truly believe you can keep going how you are?" = 12 SECONDS,
"Would you rather spend the rest of your days wasting away on an old, rickety station in the corner of the Pool?." = 12 SECONDS,
"We will see you again soon, %NAME%."
)

/datum/dream/detective_nightmare/limbic_system/still_an_asshole
fragments = list(
"Welcome back %NAME%, come to visit your old pals again? Stay a while, you always do." = 7 SECONDS,
"Don't be so hard on yourself, %NAME%. You may be a disappointment to those around you, a useless lump of flesh, lumbering about the station, but, what do they know?" = 12 SECONDS,
"Was that too mean? I apologize, I will be more positive from here on out." = 7 SECONDS,
"Oh, we're out of time. See you soon."
)

/datum/dream/detective_nightmare/wake_up_harry
dream_flags = parent_type::dream_flags | DREAM_ONCE_PER_ROUND | DREAM_CUT_SHORT_IS_COMPLETE
weight = 50

/datum/dream/detective_nightmare/wake_up_harry/GenerateDream(mob/living/carbon/dreamer)
return list(
"<i>[span_statsgood("... Harrier? Harrier?! ...")]</i>" = rand(1 SECOND, 3 SECONDS),
"<i>[span_statsgood("... Damn it Harry wake up! ...")]</i>" = rand(1 SECOND, 3 SECONDS),
"<i>[span_statsgood("... Harry I am <b>not</b> going to be the one to explain to the station how their prized Harrier DuBois overdosed on Pyrholidon, <b>again</b>. ...")]</i>",
)

/datum/dream/detective_nightmare/random
weight = 2000

/datum/dream/detective_nightmare/random/WrapMessage(mob/living/carbon/dreamer, message)
return span_statsbad("<i>... [message] ...</i>")

/datum/dream/detective_nightmare/random/GenerateDream(mob/living/carbon/dreamer)
. = list()

var/name = get_dream_name(dreamer)

var/list/options = list(
"Wake up!",
"Help me!",
"I couldn't, I'm sorry.",
"Useless.",
"Tick tock tick tock tick tock tick tock",
"I couldn't save them.",
"*You hear a loud metallic banging.*",
"Get up, %NAME%.",
"Get up.",
"Get out! I SAID GET OUT!",
"*You hear the sound of water dripping onto ceramic.*",
"%NAME%? This is the police, we know you're in there, we just want to talk. %NAME%!",
"*You hear a distant gunshot, unmistakably a .44 magnum.*",
"*You hear the heart wrenching screech of a terrified woman.*",
"Don't go... please...",
"Pleasepleasepleasepleaseplease.",
"Please [dreamer.gender == MALE ? "sir" : "ma'am"] I have nowhere else to turn.",
"You're a failure.",
"Give up, %NAME%.",
"Give up.",
"Get out of my office.",
)

for(var/i in 1 to rand(1, 3))
if(prob(33))
.["[name]!"] = 2 SECONDS

.[replacetext(pick_n_take(options), "%NAME%", name)] = 3 SECONDS

57 changes: 57 additions & 0 deletions code/modules/dreams/mob_dream_procs.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Begins the dreaming process on a sleeping carbon.
*
* Only one dream sequence can be active at a time.
*/

/mob/living/carbon/proc/try_dream(dream_type)
if(!mind || HAS_TRAIT(mind, TRAIT_DREAMING) || HAS_TRAIT(src, TRAIT_CANNOT_DREAM))
return

if(!has_status_effect(/datum/status_effect/incapacitating/sleeping))
return

dream_type = locate(dream_type) in GLOB.all_dreams_weighted
if(dream_type)
do_dream(dream_type)
return TRUE

if(!COOLDOWN_FINISHED(mind, dream_cooldown))
return FALSE

dream_type = get_random_dream()

if(dream_type)
do_dream(dream_type)
return TRUE
return FALSE

/**
* Generates a dream sequence to be displayed to the sleeper.
*
* Generates the "dream" to display to the sleeper. A dream consists of a subject, a verb, and (most of the time) an object, displayed in sequence to the sleeper.
* Dreams are generated as a list of strings stored inside dream_fragments, which is passed to and displayed in dream_sequence().
* Bedsheets on the sleeper will provide a custom subject for the dream, pulled from the dream_messages on each bedsheet.
*/
/mob/living/carbon/proc/get_random_dream()
// Do robots dream of robotic sheep? -- No.
if(!mind || isipc(src))
return null

var/list/dream_pool
var/datum/dream/dream
if(mind?.assigned_role?.title == JOB_DETECTIVE)
dream_pool = GLOB.detective_dreams_weighted.Copy()

else
dream_pool = GLOB.generic_dreams_weighted.Copy()

while(length(dream_pool))
dream = pick_weight(dream_pool)
dream_pool -= dream

if(!(dream.dream_flags & DREAM_ONCE_PER_ROUND) || !(dream.type in mind.finished_dream_types))
return dream

/mob/living/carbon/proc/do_dream(datum/dream/chosen_dream)
chosen_dream.BeginDreaming(src)
Loading

0 comments on commit e8a934a

Please sign in to comment.