From 4b540e7206ab38f9997be893cbef3d7c247a1a66 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Mon, 11 Aug 2025 03:18:37 -0500 Subject: [PATCH 01/20] Blood hallucination --- .../modules/hallucination/everywhere_blood.dm | 107 ++++++++++++++++++ maplestation.dme | 1 + 2 files changed, 108 insertions(+) create mode 100644 code/modules/hallucination/everywhere_blood.dm diff --git a/code/modules/hallucination/everywhere_blood.dm b/code/modules/hallucination/everywhere_blood.dm new file mode 100644 index 000000000000..d9fde07409f3 --- /dev/null +++ b/code/modules/hallucination/everywhere_blood.dm @@ -0,0 +1,107 @@ +/datum/hallucination/everywhere_blood + hallucination_tier = HALLUCINATION_TIER_RARE + /// List of images created + VAR_PRIVATE/list/blood_images + /// X coord of where the hallucination started + VAR_PRIVATE/start_x = -1 + /// Y coord of where the hallucination started + VAR_PRIVATE/start_y = -1 + /// Radius of the effect + var/radius = 12 + +/datum/hallucination/everywhere_blood/Destroy() + hallucinator.client.images -= blood_images + return ..() + +/datum/hallucination/everywhere_blood/start() + if(!hallucinator.client) + return FALSE + + var/list/splattered_atoms = list() + for(var/atom/nearby in urange(radius, hallucinator)) + if(prob(5)) + continue + var/dist = get_dist(nearby, hallucinator) + if(prob((dist - 10) * 25)) + continue + if(!splatterable_atom(nearby)) + continue + splattered_atoms |= HAS_TRAIT(nearby, TRAIT_WALLMOUNTED) ? get_step(nearby, nearby.dir) : nearby + + if(!length(splattered_atoms)) + return FALSE + + start_x = hallucinator.x + start_y = hallucinator.y + var/obj/effect/decal/cleanable/blood/pre_dna/copy_from = new() + + blood_images = list() + for(var/atom/to_splatter as anything in splattered_atoms) + var/image/blood_image = image( + icon = copy_from.icon, + icon_state = pick(copy_from.random_icon_states), + loc = to_splatter, + ) + blood_image.color = copy_from.get_blood_dna_color() + // blood_image.plane = SET_PLANE_EXPLICIT(blood_image, copy_from.plane, to_splatter) + blood_image.plane = to_splatter.plane + blood_image.layer = to_splatter.layer + 0.1 + blood_image.alpha = 0 + animate(blood_image, alpha = 50, time = (rand(5, 8) SECONDS)) + animate(alpha = 200, time = 0.5 SECONDS, easing = ELASTIC_EASING) + animate(alpha = 50, time = 0.2 SECONDS) + animate(alpha = 100, time = (rand(5, 8) SECONDS)) + animate(alpha = 200, time = 0.5 SECONDS, easing = ELASTIC_EASING) + animate(alpha = 100, time = 0.2 SECONDS) + animate(alpha = 150, time = (rand(5, 8) SECONDS)) + animate(alpha = 200, time = 0.5 SECONDS, easing = ELASTIC_EASING) + animate(alpha = 150, time = 0.2 SECONDS) + animate(alpha = 250, time = (rand(5, 8) SECONDS)) + blood_images += blood_image + + qdel(copy_from) + hallucinator.client.images += blood_images + addtimer(CALLBACK(src, PROC_REF(stop)), rand(60, 90) SECONDS, TIMER_DELETE_ME) + RegisterSignal(hallucinator, COMSIG_MOVABLE_MOVED, PROC_REF(check_distance)) + return TRUE + +/datum/hallucination/everywhere_blood/proc/stop() + SIGNAL_HANDLER + for(var/image/blood_image as anything in blood_images) + animate(blood_image, alpha = 0, time = 7 SECONDS) + QDEL_IN(src, 8 SECONDS) + +/datum/hallucination/everywhere_blood/proc/check_distance(datum/source, atom/movable/moved_atom) + SIGNAL_HANDLER + if(hallucinator_too_far()) + qdel(src) + +/// Checks if we leave range of the furthest point of the hallucination, cancels it if so. +/datum/hallucination/everywhere_blood/proc/hallucinator_too_far() + if(hallucinator.x > start_x + (radius + 10)) + return TRUE + if(hallucinator.x < start_x - (radius + 10)) + return TRUE + if(hallucinator.y > start_y + (radius + 8)) + return TRUE + if(hallucinator.y < start_y - (radius + 8)) + return TRUE + return FALSE + +/datum/hallucination/everywhere_blood/proc/splatterable_atom(atom/what) + if(isfloorturf(what)) + return TRUE + if(!isobj(what)) + return FALSE + if(what.invisibility > hallucinator.see_invisible || HAS_TRAIT(what, TRAIT_UNDERFLOOR)) + return FALSE + var/obj/objwhat = what + if(istype(objwhat, /obj/structure/window)) + return TRUE + if(!objwhat.anchored) + return FALSE + if(!objwhat.density) + return TRUE + if(objwhat.pass_flags_self & PASSMACHINE) + return TRUE + return FALSE diff --git a/maplestation.dme b/maplestation.dme index fbf3b53350a3..7b4f9a22f04a 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -4080,6 +4080,7 @@ #include "code\modules\hallucination\bolted_airlocks.dm" #include "code\modules\hallucination\bubblegum_attack.dm" #include "code\modules\hallucination\delusions.dm" +#include "code\modules\hallucination\everywhere_blood.dm" #include "code\modules\hallucination\fake_alert.dm" #include "code\modules\hallucination\fake_chat.dm" #include "code\modules\hallucination\fake_death.dm" From 27799259b4fb1ce10f0082683a59ffcab81fb276 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Fri, 23 Jan 2026 00:51:48 -0600 Subject: [PATCH 02/20] Sanity overlay --- code/datums/mood.dm | 311 ++++++++++++++++++++++++++++++++------------ 1 file changed, 230 insertions(+), 81 deletions(-) diff --git a/code/datums/mood.dm b/code/datums/mood.dm index fc2fb453a908..0961d055c9ce 100644 --- a/code/datums/mood.dm +++ b/code/datums/mood.dm @@ -42,10 +42,6 @@ /// List of mood events currently active on this datum var/list/mood_events = list() - /// Tracks the last mob stat, updates on change - /// Used to stop processing SSmood - var/last_stat = CONSCIOUS - /datum/mood/New(mob/living/mob_to_make_moody) if (!istype(mob_to_make_moody)) stack_trace("Tried to apply mood to a non-living atom!") @@ -59,7 +55,7 @@ RegisterSignal(mob_to_make_moody, COMSIG_MOB_HUD_CREATED, PROC_REF(modify_hud)) RegisterSignal(mob_to_make_moody, COMSIG_ENTER_AREA, PROC_REF(check_area_mood)) RegisterSignal(mob_to_make_moody, COMSIG_EXIT_AREA, PROC_REF(exit_area)) - RegisterSignal(mob_to_make_moody, COMSIG_LIVING_REVIVE, PROC_REF(on_revive)) + RegisterSignal(mob_to_make_moody, COMSIG_LIVING_POST_FULLY_HEAL, PROC_REF(on_aheal)) RegisterSignal(mob_to_make_moody, COMSIG_MOB_STATCHANGE, PROC_REF(handle_mob_death)) RegisterSignal(mob_to_make_moody, COMSIG_QDELETING, PROC_REF(clear_parent_ref)) @@ -78,7 +74,7 @@ unmodify_hud() mob_parent.lose_area_sensitivity(MOOD_DATUM_TRAIT) - UnregisterSignal(mob_parent, list(COMSIG_MOB_HUD_CREATED, COMSIG_ENTER_AREA, COMSIG_EXIT_AREA, COMSIG_LIVING_REVIVE, COMSIG_MOB_STATCHANGE, COMSIG_QDELETING)) + UnregisterSignal(mob_parent, list(COMSIG_MOB_HUD_CREATED, COMSIG_ENTER_AREA, COMSIG_EXIT_AREA, COMSIG_LIVING_POST_FULLY_HEAL, COMSIG_MOB_STATCHANGE, COMSIG_QDELETING)) var/area/our_area = get_area(mob_parent) if(our_area) UnregisterSignal(our_area, COMSIG_AREA_BEAUTY_UPDATED) @@ -91,34 +87,54 @@ return ..() /datum/mood/process(seconds_per_tick) + var/change = 0 + var/new_min = SANITY_INSANE + var/new_max = SANITY_GREAT switch(mood_level) if(MOOD_LEVEL_SAD4) - set_sanity(sanity - 0.3 * seconds_per_tick, SANITY_INSANE) + change = -0.3 if(MOOD_LEVEL_SAD3) - set_sanity(sanity - 0.15 * seconds_per_tick, SANITY_CRAZY) + change = -0.15 + new_min = SANITY_CRAZY if(MOOD_LEVEL_SAD2) - set_sanity(sanity - 0.1 * seconds_per_tick, SANITY_UNSTABLE) + change = -0.1 + new_min = SANITY_UNSTABLE if(MOOD_LEVEL_SAD1) - set_sanity(sanity - 0.05 * seconds_per_tick, SANITY_UNSTABLE) + change = -0.05 + new_min = SANITY_UNSTABLE if(MOOD_LEVEL_NEUTRAL) - set_sanity(sanity, SANITY_UNSTABLE) //This makes sure that mood gets increased should you be below the minimum. + new_min = SANITY_UNSTABLE if(MOOD_LEVEL_HAPPY1) - set_sanity(sanity + 0.2 * seconds_per_tick, SANITY_UNSTABLE) + change = 0.2 + new_min = SANITY_UNSTABLE if(MOOD_LEVEL_HAPPY2) - set_sanity(sanity + 0.3 * seconds_per_tick, SANITY_UNSTABLE) + change = 0.3 + new_min = SANITY_UNSTABLE if(MOOD_LEVEL_HAPPY3) - set_sanity(sanity + 0.4 * seconds_per_tick, SANITY_NEUTRAL, SANITY_MAXIMUM) + change = 0.4 + new_min = SANITY_NEUTRAL + new_max = SANITY_MAXIMUM if(MOOD_LEVEL_HAPPY4) - set_sanity(sanity + 0.6 * seconds_per_tick, SANITY_NEUTRAL, SANITY_MAXIMUM) + change = 0.6 + new_min = SANITY_NEUTRAL + new_max = SANITY_MAXIMUM + + // If our sanity is beneath our new lower threshold, + // a significant boost is added to help recover up to it + if(sanity < new_min) + change += 0.7 + new_min = SANITY_INSANE -/datum/mood/proc/handle_mob_death(datum/source) + if(change) + adjust_sanity(change * seconds_per_tick, new_min, new_max) + +/datum/mood/proc/handle_mob_death(datum/source, new_stat, old_stat) SIGNAL_HANDLER - if (last_stat == DEAD && mob_parent.stat != DEAD) + if (old_stat == DEAD && new_stat != DEAD) START_PROCESSING(SSmood, src) - else if (last_stat != DEAD && mob_parent.stat == DEAD) + else if (old_stat != DEAD && new_stat == DEAD) STOP_PROCESSING(SSmood, src) - last_stat = mob_parent.stat /// Handles mood given by nutrition /datum/mood/proc/update_nutrition_moodlets() @@ -126,6 +142,10 @@ clear_mood_event(MOOD_CATEGORY_NUTRITION) return FALSE + if(HAS_TRAIT(mob_parent, TRAIT_GLUTTON)) + add_mood_event(MOOD_CATEGORY_NUTRITION, /datum/mood_event/hungry) //you'll never get enough + return TRUE + if(HAS_TRAIT(mob_parent, TRAIT_FAT) && !HAS_TRAIT(mob_parent, TRAIT_VORACIOUS)) add_mood_event(MOOD_CATEGORY_NUTRITION, /datum/mood_event/fat) return TRUE @@ -153,11 +173,13 @@ * * Arguments: * * category - (text) category of the mood event - see /datum/mood_event for category explanation - * * type - (path) any /datum/mood_event + * * type - (path) any /datum/mood_event (besides /datum/mood_event/conditional) */ /datum/mood/proc/add_mood_event(category, new_type, ...) if (!ispath(new_type, /datum/mood_event)) CRASH("A non path ([new_type]), was used to add a mood event. This shouldn't be happening.") + if (ispath(new_type, /datum/mood_event/conditional)) + CRASH("A conditional mood event ([new_type]) was used in add_mood_event. Use add_conditional_mood_event instead.") if (!istext(category)) category = REF(category) @@ -167,6 +189,14 @@ qdel(new_event) return + add_mood_event_instance(new_event, params) + +/** + * Handles adding a mood event instance, including replacing or refreshing existing events + */ +/datum/mood/proc/add_mood_event_instance(datum/mood_event/new_event, list/params) + PRIVATE_PROC(TRUE) + var/category = new_event.category var/datum/mood_event/existing_event = mood_events[category] if(existing_event) var/continue_adding = FALSE @@ -196,6 +226,43 @@ /datum/personality/misanthropic = /datum/mood_event/misanthropic_happy ), range = 4) +/** + * Adds a conditional mood event to the mob + * + * Arguments: + * * category - (text) category of the mood event - see /datum/mood_event for category explanation + * * base_type - (path) any /datum/mood_event/conditional + */ +/datum/mood/proc/add_conditional_mood_event(category, datum/base_type, ...) + if (!ispath(base_type, /datum/mood_event/conditional)) + if (ispath(base_type, /datum/mood_event)) + CRASH("A non-conditional mood event ([base_type]) was used in add_conditional_mood_event. Use add_mood_event instead.") + CRASH("A non path ([base_type]), was used to add a mood event. This shouldn't be happening.") + if (!istext(category)) + category = REF(category) + + var/list/params = args.Copy(3) + var/list/datum/mood_event/conditional/all_valid_conditional_events = list() + for(var/event_subtype in valid_typesof(base_type)) + var/datum/mood_event/potential_event = new event_subtype(category) + if(!potential_event.can_effect_mob(arglist(list(src, mob_parent) + params))) + qdel(potential_event) + continue + + all_valid_conditional_events += potential_event + + if(!length(all_valid_conditional_events)) + return //no valid events to add + + var/datum/mood_event/conditional/highest_priority_event + for(var/datum/mood_event/conditional/checked_event as anything in all_valid_conditional_events) + if(!highest_priority_event || checked_event.priority > highest_priority_event.priority) + highest_priority_event = checked_event + + add_mood_event_instance(highest_priority_event, params) + all_valid_conditional_events -= highest_priority_event // you are the chosen one + QDEL_LIST(all_valid_conditional_events) // clean up the losers + /** * Removes a mood event from the mob * @@ -214,6 +281,9 @@ qdel(event) update_mood() +/datum/mood/proc/get_mood_event(category) + return mood_events[category] + /// Updates the mobs mood. /// Called after mood events have been added/removed. /datum/mood/proc/update_mood() @@ -222,18 +292,17 @@ mood = 0 shown_mood = 0 - SEND_SIGNAL(mob_parent, COMSIG_CARBON_MOOD_UPDATE) - - for(var/category in mood_events) - var/datum/mood_event/the_event = mood_events[category] - var/event_mood = the_event.mood_change - event_mood *= max((event_mood > 0) ? positive_mood_modifier : negative_mood_modifier, 0) - mood += event_mood - if (!the_event.hidden) - shown_mood += event_mood + if (!HAS_TRAIT(mob_parent, TRAIT_APATHETIC)) + for(var/category in mood_events) + var/datum/mood_event/the_event = mood_events[category] + var/event_mood = the_event.mood_change + event_mood *= max((event_mood > 0) ? positive_mood_modifier : negative_mood_modifier, 0) + mood += event_mood + if (!the_event.hidden) + shown_mood += event_mood - mood *= max(mood_modifier, 0) - shown_mood *= max(mood_modifier, 0) + mood *= max(mood_modifier, 0) + shown_mood *= max(mood_modifier, 0) switch(mood) if (-INFINITY to MOOD_SAD4) @@ -256,10 +325,11 @@ mood_level = MOOD_LEVEL_HAPPY4 update_mood_icon() + SEND_SIGNAL(mob_parent, COMSIG_CARBON_MOOD_UPDATE) /// Updates the mob's mood icon /datum/mood/proc/update_mood_icon() - if (!(mob_parent.client || mob_parent.hud_used)) + if (!(mob_parent.client || mob_parent.hud_used) || isnull(mood_screen_object)) return mood_screen_object.cut_overlays() @@ -293,7 +363,7 @@ if (SANITY_LEVEL_INSANE) mood_screen_object.color = "#f15d36" - if (!conflicting_moodies.len) // theres no special icons, use the normal icon states + if (!conflicting_moodies.len) // there's no special icons, use the normal icon states mood_screen_object.icon_state = "mood[mood_level]" return @@ -323,6 +393,7 @@ if(hud?.infodisplay) hud.infodisplay -= mood_screen_object QDEL_NULL(mood_screen_object) + UnregisterSignal(hud, COMSIG_QDELETING) /// Handles clicking on the mood HUD object /datum/mood/proc/hud_click(datum/source, location, control, params, mob/user) @@ -374,41 +445,49 @@ if(81 to INFINITY) msg += "[span_boldwarning("I'm completely wasted.")]
" - msg += span_notice("My current sanity: ") //Long term - switch(sanity) - if(SANITY_GREAT to INFINITY) - msg += "[span_boldnicegreen("My mind feels like a temple!")]
" - if(SANITY_NEUTRAL to SANITY_GREAT) - msg += "[span_nicegreen("I have been feeling great lately!")]
" - if(SANITY_DISTURBED to SANITY_NEUTRAL) - msg += "[span_nicegreen("I have felt quite decent lately.")]
" - if(SANITY_UNSTABLE to SANITY_DISTURBED) - msg += "[span_warning("I'm feeling a little bit unhinged...")]
" - if(SANITY_CRAZY to SANITY_UNSTABLE) - msg += "[span_warning("I'm freaking out!!")]
" - if(SANITY_INSANE to SANITY_CRAZY) - msg += "[span_boldwarning("AHAHAHAHAHAHAHAHAHAH!!")]
" - - msg += span_notice("My current mood: ") //Short term - switch(mood_level) - if(MOOD_LEVEL_SAD4) - msg += "[span_boldwarning("I wish I was dead!")]
" - if(MOOD_LEVEL_SAD3) - msg += "[span_boldwarning("I feel terrible...")]
" - if(MOOD_LEVEL_SAD2) - msg += "[span_boldwarning("I feel very upset.")]
" - if(MOOD_LEVEL_SAD1) - msg += "[span_warning("I'm a bit sad.")]
" - if(MOOD_LEVEL_NEUTRAL) - msg += "[span_grey("I'm alright.")]
" - if(MOOD_LEVEL_HAPPY1) - msg += "[span_nicegreen("I feel pretty okay.")]
" - if(MOOD_LEVEL_HAPPY2) - msg += "[span_boldnicegreen("I feel pretty good.")]
" - if(MOOD_LEVEL_HAPPY3) - msg += "[span_boldnicegreen("I feel amazing!")]
" - if(MOOD_LEVEL_HAPPY4) - msg += "[span_boldnicegreen("I love life!")]
" + if (HAS_TRAIT(mob_parent, TRAIT_APATHETIC)) + msg += span_notice("My mood: [span_grey("I don't feel anything.")]
") + else + msg += span_notice("My current sanity: ") //Long term + switch(sanity) + if(SANITY_GREAT to INFINITY) + msg += "[span_boldnicegreen("My mind feels like a temple!")]
" + if(SANITY_NEUTRAL to SANITY_GREAT) + msg += "[span_nicegreen("I have been feeling great lately!")]
" + if(SANITY_DISTURBED to SANITY_NEUTRAL) + msg += "[span_nicegreen("I have felt quite decent lately.")]
" + if(SANITY_UNSTABLE to SANITY_DISTURBED) + msg += "[span_warning("I'm feeling a little bit unhinged...")]
" + if(SANITY_CRAZY to SANITY_UNSTABLE) + msg += "[span_warning("I'm freaking out!!")]
" + if(SANITY_INSANE to SANITY_CRAZY) + msg += "[span_boldwarning("AHAHAHAHAHAHAHAHAHAH!!")]
" + + msg += span_notice("My current mood: ") //Short term + switch(mood_level) + if(MOOD_LEVEL_SAD4) + msg += "[span_boldwarning("I wish I was dead!")]
" + if(MOOD_LEVEL_SAD3) + msg += "[span_boldwarning("I feel terrible...")]
" + if(MOOD_LEVEL_SAD2) + msg += "[span_boldwarning("I feel very upset.")]
" + if(MOOD_LEVEL_SAD1) + msg += "[span_warning("I'm a bit sad.")]
" + if(MOOD_LEVEL_NEUTRAL) + msg += "[span_grey("I'm alright.")]
" + if(MOOD_LEVEL_HAPPY1) + msg += "[span_nicegreen("I feel pretty okay.")]
" + if(MOOD_LEVEL_HAPPY2) + msg += "[span_boldnicegreen("I feel pretty good.")]
" + if(MOOD_LEVEL_HAPPY3) + msg += "[span_boldnicegreen("I feel amazing!")]
" + if(MOOD_LEVEL_HAPPY4) + msg += "[span_boldnicegreen("I love life!")]
" + + var/list/additional_lines = list() + SEND_SIGNAL(user, COMSIG_CARBON_MOOD_CHECK, additional_lines) + if (length(additional_lines)) + msg += "[additional_lines.Join("
")]
" msg += "[span_notice("Moodlets:")]
"//All moodlets if(mood_events.len) @@ -434,7 +513,7 @@ if(LAZYLEN(mob_parent.quirks)) msg += span_notice("You have these quirks: [mob_parent.get_quirk_string(FALSE, CAT_QUIRK_ALL)].") - to_chat(user, examine_block(msg)) + to_chat(user, boxed_message(msg)) /// Updates the mob's moodies, if the area provides a mood bonus /datum/mood/proc/check_area_mood(datum/source, area/new_area) @@ -499,24 +578,30 @@ UnregisterSignal(old_area, COMSIG_AREA_BEAUTY_UPDATED) /// Called when parent is ahealed. -/datum/mood/proc/on_revive(datum/source, full_heal) +/datum/mood/proc/on_aheal(datum/source, heal_flags) SIGNAL_HANDLER - if (!full_heal) + if (!(heal_flags & HEAL_ADMIN)) return remove_temp_moods() set_sanity(initial(sanity), override = TRUE) /// Sets sanity to the specified amount and applies effects. -/datum/mood/proc/set_sanity(amount, minimum = SANITY_INSANE, maximum = SANITY_GREAT, override = FALSE) - // If we're out of the acceptable minimum-maximum range move back towards it in steps of 0.7 - // If the new amount would move towards the acceptable range faster then use it instead - if(amount < minimum) - amount += clamp(minimum - amount, 0, 0.7) - if((!override && HAS_TRAIT(mob_parent, TRAIT_UNSTABLE)) || amount > maximum) - amount = min(sanity, amount) +/datum/mood/proc/set_sanity(amount, minimum = SANITY_INSANE, maximum = SANITY_MAXIMUM, override = FALSE) + if(!override) + if(HAS_TRAIT(mob_parent, TRAIT_UNSTABLE)) + maximum = sanity + minimum = min(minimum, maximum) + if(HAS_TRAIT(mob_parent, TRAIT_APATHETIC)) + amount = SANITY_NEUTRAL + maximum = SANITY_NEUTRAL + minimum = SANITY_NEUTRAL + + amount = clamp(round(amount, 0.01), minimum, maximum) + if(amount == sanity) //Prevents stuff from flicking around. return + sanity = amount SEND_SIGNAL(mob_parent, COMSIG_CARBON_SANITY_UPDATE, amount) switch(sanity) @@ -558,16 +643,81 @@ mob_parent.remove_status_effect(/datum/status_effect/hallucination/sanity) update_mood_icon() + update_sanity_screen() + +/// Sets sanity to a specific amount, useful for callbacks +/datum/mood/proc/reset_sanity(amount) + set_sanity(amount, override = TRUE) /// Adjusts sanity by a value /datum/mood/proc/adjust_sanity(amount, minimum = SANITY_INSANE, maximum = SANITY_GREAT, override = FALSE) set_sanity(sanity + amount, minimum, maximum, override) +/datum/client_colour/sanity + fade_in = 2 SECONDS + fade_out = 2 SECONDS + var/desaturation = 1.0 + +/datum/client_colour/sanity/New(mob/owner) + . = ..() + src.color = color_matrix_saturation(desaturation) + +/datum/client_colour/sanity/tier4 + desaturation = 0.6 + +/datum/client_colour/sanity/tier3 + desaturation = 0.7 + +/datum/client_colour/sanity/tier2 + desaturation = 0.8 + +/datum/client_colour/sanity/tier1 + desaturation = 0.9 + +/datum/mood/proc/update_sanity_screen() + var/obj/old_screen = mob_parent.screens["sanity"] + var/old_state = old_screen?.icon_state + var/obj/new_screen + + var/esanity = sanity + // since you're stuck with your sanity while unstable, the effect is lessened + if(HAS_TRAIT(mob_parent, TRAIT_UNSTABLE)) + if(esanity <= 20) + esanity = 30 + else if(esanity <= 40) + esanity = 40 + + switch(sanity) + if (0 to 10) + new_screen = mob_parent.overlay_fullscreen("sanity", /atom/movable/screen/fullscreen/crit, 4) + mob_parent.add_client_colour(/datum/client_colour/sanity/tier4, "sanity") + if (10 to 20) + new_screen = mob_parent.overlay_fullscreen("sanity", /atom/movable/screen/fullscreen/crit, 3) + mob_parent.add_client_colour(/datum/client_colour/sanity/tier3, "sanity") + if (20 to 30) + new_screen = mob_parent.overlay_fullscreen("sanity", /atom/movable/screen/fullscreen/crit, 2) + mob_parent.add_client_colour(/datum/client_colour/sanity/tier2, "sanity") + if (30 to 40) + new_screen = mob_parent.overlay_fullscreen("sanity", /atom/movable/screen/fullscreen/crit, 1) + mob_parent.add_client_colour(/datum/client_colour/sanity/tier1, "sanity") + else + mob_parent.clear_fullscreen("sanity") + mob_parent.remove_client_colour("sanity") + + if(new_screen && old_state != new_screen.icon_state) + new_screen.color = "#270227" + new_screen.add_filter("sanity_filter", 1, outline_filter(1, "#270227")) + new_screen.add_filter("sanity_blur", 2, drop_shadow_filter(1, 1, 10, 0, "#270227")) + var/blur = new_screen.get_filter("sanity_blur") + // makes a pulsing effect - screen gets darker but the radius gets smaller + animate(blur, time = 10 SECONDS, size = 100, loop = -1) + animate(time = 10 SECONDS, size = 10, loop = -1) + /// Sets the insanity effect on the mob /datum/mood/proc/set_insanity_effect(newval) if (newval == insanity_effect) return - mob_parent.add_max_consciousness_value("sanity", UPPER_CONSCIOUSNESS_MAX - newval) + mob_parent.crit_threshold = (mob_parent.crit_threshold - insanity_effect) + newval insanity_effect = newval /// Removes all temporary moods @@ -582,11 +732,10 @@ /// Helper to forcefully drain sanity /datum/mood/proc/direct_sanity_drain(amount) - set_sanity(sanity + amount, override = TRUE) + adjust_sanity(amount, override = TRUE) /** * Returns true if you already have a mood from a provided category. - * You may think to yourself, why am I trying to get a boolean from a component? Well, this system probably should not be a component. * * Arguments * * category - Mood category to validate against. From d44881abae1e8f6080fa59117ecb118fd7b51e67 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Fri, 23 Jan 2026 15:44:51 -0600 Subject: [PATCH 03/20] Fixes --- .../signals/signals_mob/signals_mob_carbon.dm | 2 + code/datums/mood.dm | 108 +++---- code/datums/mood_events/_mood_event.dm | 27 ++ code/datums/mood_events/death.dm | 282 ++++++++++++++++++ code/modules/mob/living/death.dm | 23 +- code/modules/mob/living/living.dm | 6 +- code/modules/unit_tests/_unit_tests.dm | 1 + code/modules/unit_tests/death_moodlets.dm | 104 +++++++ maplestation.dme | 1 + 9 files changed, 482 insertions(+), 72 deletions(-) create mode 100644 code/datums/mood_events/death.dm create mode 100644 code/modules/unit_tests/death_moodlets.dm diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm index 01b8bcd576e8..6bdc139e5159 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm @@ -115,6 +115,8 @@ #define COMSIG_CARBON_APPLY_OVERLAY "carbon_apply_overlay" ///Called from remove_overlay(cache_index, overlay) #define COMSIG_CARBON_REMOVE_OVERLAY "carbon_remove_overlay" +///Called when a carbon checks their mood +#define COMSIG_CARBON_MOOD_CHECK "carbon_mod_check" // /mob/living/carbon/human signals diff --git a/code/datums/mood.dm b/code/datums/mood.dm index 0961d055c9ce..b753410b96d4 100644 --- a/code/datums/mood.dm +++ b/code/datums/mood.dm @@ -142,10 +142,6 @@ clear_mood_event(MOOD_CATEGORY_NUTRITION) return FALSE - if(HAS_TRAIT(mob_parent, TRAIT_GLUTTON)) - add_mood_event(MOOD_CATEGORY_NUTRITION, /datum/mood_event/hungry) //you'll never get enough - return TRUE - if(HAS_TRAIT(mob_parent, TRAIT_FAT) && !HAS_TRAIT(mob_parent, TRAIT_VORACIOUS)) add_mood_event(MOOD_CATEGORY_NUTRITION, /datum/mood_event/fat) return TRUE @@ -243,7 +239,7 @@ var/list/params = args.Copy(3) var/list/datum/mood_event/conditional/all_valid_conditional_events = list() - for(var/event_subtype in valid_typesof(base_type)) + for(var/event_subtype in subtypesof(base_type)) var/datum/mood_event/potential_event = new event_subtype(category) if(!potential_event.can_effect_mob(arglist(list(src, mob_parent) + params))) qdel(potential_event) @@ -292,17 +288,16 @@ mood = 0 shown_mood = 0 - if (!HAS_TRAIT(mob_parent, TRAIT_APATHETIC)) - for(var/category in mood_events) - var/datum/mood_event/the_event = mood_events[category] - var/event_mood = the_event.mood_change - event_mood *= max((event_mood > 0) ? positive_mood_modifier : negative_mood_modifier, 0) - mood += event_mood - if (!the_event.hidden) - shown_mood += event_mood + for(var/category in mood_events) + var/datum/mood_event/the_event = mood_events[category] + var/event_mood = the_event.mood_change + event_mood *= max((event_mood > 0) ? positive_mood_modifier : negative_mood_modifier, 0) + mood += event_mood + if (!the_event.hidden) + shown_mood += event_mood - mood *= max(mood_modifier, 0) - shown_mood *= max(mood_modifier, 0) + mood *= max(mood_modifier, 0) + shown_mood *= max(mood_modifier, 0) switch(mood) if (-INFINITY to MOOD_SAD4) @@ -445,44 +440,41 @@ if(81 to INFINITY) msg += "[span_boldwarning("I'm completely wasted.")]
" - if (HAS_TRAIT(mob_parent, TRAIT_APATHETIC)) - msg += span_notice("My mood: [span_grey("I don't feel anything.")]
") - else - msg += span_notice("My current sanity: ") //Long term - switch(sanity) - if(SANITY_GREAT to INFINITY) - msg += "[span_boldnicegreen("My mind feels like a temple!")]
" - if(SANITY_NEUTRAL to SANITY_GREAT) - msg += "[span_nicegreen("I have been feeling great lately!")]
" - if(SANITY_DISTURBED to SANITY_NEUTRAL) - msg += "[span_nicegreen("I have felt quite decent lately.")]
" - if(SANITY_UNSTABLE to SANITY_DISTURBED) - msg += "[span_warning("I'm feeling a little bit unhinged...")]
" - if(SANITY_CRAZY to SANITY_UNSTABLE) - msg += "[span_warning("I'm freaking out!!")]
" - if(SANITY_INSANE to SANITY_CRAZY) - msg += "[span_boldwarning("AHAHAHAHAHAHAHAHAHAH!!")]
" - - msg += span_notice("My current mood: ") //Short term - switch(mood_level) - if(MOOD_LEVEL_SAD4) - msg += "[span_boldwarning("I wish I was dead!")]
" - if(MOOD_LEVEL_SAD3) - msg += "[span_boldwarning("I feel terrible...")]
" - if(MOOD_LEVEL_SAD2) - msg += "[span_boldwarning("I feel very upset.")]
" - if(MOOD_LEVEL_SAD1) - msg += "[span_warning("I'm a bit sad.")]
" - if(MOOD_LEVEL_NEUTRAL) - msg += "[span_grey("I'm alright.")]
" - if(MOOD_LEVEL_HAPPY1) - msg += "[span_nicegreen("I feel pretty okay.")]
" - if(MOOD_LEVEL_HAPPY2) - msg += "[span_boldnicegreen("I feel pretty good.")]
" - if(MOOD_LEVEL_HAPPY3) - msg += "[span_boldnicegreen("I feel amazing!")]
" - if(MOOD_LEVEL_HAPPY4) - msg += "[span_boldnicegreen("I love life!")]
" + msg += span_notice("My current sanity: ") //Long term + switch(sanity) + if(SANITY_GREAT to INFINITY) + msg += "[span_boldnicegreen("My mind feels like a temple!")]
" + if(SANITY_NEUTRAL to SANITY_GREAT) + msg += "[span_nicegreen("I have been feeling great lately!")]
" + if(SANITY_DISTURBED to SANITY_NEUTRAL) + msg += "[span_nicegreen("I have felt quite decent lately.")]
" + if(SANITY_UNSTABLE to SANITY_DISTURBED) + msg += "[span_warning("I'm feeling a little bit unhinged...")]
" + if(SANITY_CRAZY to SANITY_UNSTABLE) + msg += "[span_warning("I'm freaking out!!")]
" + if(SANITY_INSANE to SANITY_CRAZY) + msg += "[span_boldwarning("AHAHAHAHAHAHAHAHAHAH!!")]
" + + msg += span_notice("My current mood: ") //Short term + switch(mood_level) + if(MOOD_LEVEL_SAD4) + msg += "[span_boldwarning("I wish I was dead!")]
" + if(MOOD_LEVEL_SAD3) + msg += "[span_boldwarning("I feel terrible...")]
" + if(MOOD_LEVEL_SAD2) + msg += "[span_boldwarning("I feel very upset.")]
" + if(MOOD_LEVEL_SAD1) + msg += "[span_warning("I'm a bit sad.")]
" + if(MOOD_LEVEL_NEUTRAL) + msg += "[span_grey("I'm alright.")]
" + if(MOOD_LEVEL_HAPPY1) + msg += "[span_nicegreen("I feel pretty okay.")]
" + if(MOOD_LEVEL_HAPPY2) + msg += "[span_boldnicegreen("I feel pretty good.")]
" + if(MOOD_LEVEL_HAPPY3) + msg += "[span_boldnicegreen("I feel amazing!")]
" + if(MOOD_LEVEL_HAPPY4) + msg += "[span_boldnicegreen("I love life!")]
" var/list/additional_lines = list() SEND_SIGNAL(user, COMSIG_CARBON_MOOD_CHECK, additional_lines) @@ -513,7 +505,7 @@ if(LAZYLEN(mob_parent.quirks)) msg += span_notice("You have these quirks: [mob_parent.get_quirk_string(FALSE, CAT_QUIRK_ALL)].") - to_chat(user, boxed_message(msg)) + to_chat(user, examine_block(msg)) /// Updates the mob's moodies, if the area provides a mood bonus /datum/mood/proc/check_area_mood(datum/source, area/new_area) @@ -592,10 +584,6 @@ if(HAS_TRAIT(mob_parent, TRAIT_UNSTABLE)) maximum = sanity minimum = min(minimum, maximum) - if(HAS_TRAIT(mob_parent, TRAIT_APATHETIC)) - amount = SANITY_NEUTRAL - maximum = SANITY_NEUTRAL - minimum = SANITY_NEUTRAL amount = clamp(round(amount, 0.01), minimum, maximum) @@ -660,7 +648,7 @@ /datum/client_colour/sanity/New(mob/owner) . = ..() - src.color = color_matrix_saturation(desaturation) + src.colour = color_matrix_saturation(desaturation) /datum/client_colour/sanity/tier4 desaturation = 0.6 @@ -717,7 +705,7 @@ /datum/mood/proc/set_insanity_effect(newval) if (newval == insanity_effect) return - mob_parent.crit_threshold = (mob_parent.crit_threshold - insanity_effect) + newval + mob_parent.add_max_consciousness_value("sanity", UPPER_CONSCIOUSNESS_MAX - newval) insanity_effect = newval /// Removes all temporary moods diff --git a/code/datums/mood_events/_mood_event.dm b/code/datums/mood_events/_mood_event.dm index 045636559af4..476886feb780 100644 --- a/code/datums/mood_events/_mood_event.dm +++ b/code/datums/mood_events/_mood_event.dm @@ -37,7 +37,9 @@ /** * Called when this datum is created, checks if the passed mob can experience this mood event * + * * home - the mood datum we are being added to * * who - the mob to check + * * ... - any other arguments that are passed to the mood event * * Return TRUE if the mob can experience this mood event * Return FALSE if the mob should be unaffected @@ -66,6 +68,10 @@ /** * Wrapper for the mood event being added to a mob + * + * * home - the mood datum we are being added to + * * who - the mob to add the mood event to + * * mood_args - any other arguments that are passed to the mood event */ /datum/mood_event/proc/on_add(datum/mood/home, mob/living/who, list/mood_args) SHOULD_NOT_OVERRIDE(TRUE) @@ -139,3 +145,24 @@ */ /datum/mood_event/proc/be_replaced(datum/mood/home, datum/mood_event/new_event, ...) return ALLOW_NEW_MOOD + +/// Subtype of mood event that iterates over all subtypes of itself to find a suitable one to apply +/datum/mood_event/conditional + /// Priority of this condition over other conditions. Higher = more priority. + /// If two priorities are the same, the first one found is used, which would be the one defined first in code. + var/priority = 0 + +/datum/mood_event/conditional/can_effect_mob(datum/mood/home, mob/living/who, ...) + return ..() && condition_fulfilled(arglist(args.Copy(2))) + +/** + * Is the condition for this mood event fulfilled for the given mob? + * + * * who - the mob to check + * * ... - any other arguments that are passed to the mood event + */ +/datum/mood_event/conditional/proc/condition_fulfilled(mob/living/who, ...) + return FALSE + +/datum/mood_event/conditional/be_replaced(datum/mood/home, datum/mood_event/conditional/new_event, ...) + return initial(new_event.priority) > initial(priority) ? ALLOW_NEW_MOOD : BLOCK_NEW_MOOD diff --git a/code/datums/mood_events/death.dm b/code/datums/mood_events/death.dm new file mode 100644 index 000000000000..5c05d1b00541 --- /dev/null +++ b/code/datums/mood_events/death.dm @@ -0,0 +1,282 @@ +// Defines for priority levels of various conditional death moodlets. +// These are defines so it's easier to get an overview of all priority levels in one place and tweak them all in some synchronous manner + +#define DESENSITIZED_PRIORITY 10 +#define PET_PRIORITY 30 +#define XENO_PRIORITY 35 +#define DONTCARE_PRIORITY 40 +#define GAMER_PRIORITY 80 +#define REVOLUTIONARY_PRIORITY 85 +#define CULT_PRIORITY 90 +#define NAIVE_PRIORITY 100 + +/datum/mood_event/conditional/see_death + mood_change = -8 + timeout = 5 MINUTES + +/datum/mood_event/conditional/see_death/can_effect_mob(datum/mood/home, mob/living/who, mob/dead_mob, dusted, gibbed) + if(isnull(dead_mob)) + stack_trace("Death mood event being applied with null dead_mob") + return FALSE + + return ..() + +/datum/mood_event/conditional/see_death/condition_fulfilled(mob/living/who, mob/dead_mob, dusted, gibbed) + return TRUE + +/datum/mood_event/conditional/see_death/add_effects(mob/dead_mob, dusted, gibbed) + update_effect(dead_mob, dusted, gibbed) + + if(HAS_TRAIT(dead_mob, TRAIT_SPAWNED_MOB)) + mood_change *= 0.25 + timeout *= 0.2 + + if(HAS_PERSONALITY(owner, /datum/personality/compassionate) && mood_change < 0) + mood_change *= 1.5 + timeout *= 1.5 + + if(gibbed || dusted) + mood_change *= 1.2 + timeout *= 1.5 + + if(!description) + if(gibbed) + description = "%DEAD_MOB% just exploded in front of me!!" + else if(dusted) + description = "%DEAD_MOB% was just vaporized in front of me!!" + else + description = "I just saw %DEAD_MOB% die. How horrible..." + + description = capitalize(replacetext(description, "%DEAD_MOB%", get_descriptor(dead_mob))) + +/// Blank proc which allows conditional effects to modify mood, timeout, or description before the main effect is applied +/datum/mood_event/conditional/see_death/proc/update_effect(mob/dead_mob, dusted, gibbed) + return + +/// Checks if the dead mob is a pet +/datum/mood_event/conditional/see_death/proc/is_pet(mob/dead_mob) + return istype(dead_mob, /mob/living/basic/pet) || ismonkey(dead_mob) + +/datum/mood_event/conditional/see_death/be_refreshed(datum/mood/home, mob/dead_mob, dusted, gibbed) + if(can_stack_effect(dead_mob)) + mood_change *= 1.5 + return ..() + +/datum/mood_event/conditional/see_death/be_replaced(datum/mood/home, datum/mood_event/new_event, mob/dead_mob, dusted, gibbed) + . = ..() + // when blocking a new mood event (because it's lower priority), refresh ourselves instead + if(. == BLOCK_NEW_MOOD) + return be_refreshed(home, dead_mob, dusted, gibbed) + +/// Checks if our mood can get worse by seeing another death (or better if we're weird like that) +/datum/mood_event/conditional/see_death/proc/can_stack_effect(mob/dead_mob) + // if we're desensitized, don't stack unless it's a buff + if(HAS_MIND_TRAIT(owner, TRAIT_DESENSITIZED) && mood_change > 0) + return FALSE + // if we're seeing a spawned mob die, don't stack + if(HAS_TRAIT(dead_mob, TRAIT_SPAWNED_MOB)) + return FALSE + return TRUE + +/// Changes "I saw Joe x" to "I saw the engineer x" +/datum/mood_event/conditional/see_death/proc/get_descriptor(mob/dead_mob) + if(is_pet(dead_mob)) + return "[dead_mob]" + if(dead_mob.name != "Unknown" && dead_mob.mind?.assigned_role?.job_flags & JOB_CREW_MEMBER) + return "the [LOWER_TEXT(dead_mob.mind?.assigned_role.title)]" + return "someone" + +/// Highest priority: Clown naivety about death +/datum/mood_event/conditional/see_death/naive + priority = NAIVE_PRIORITY + mood_change = 0 + +/datum/mood_event/conditional/see_death/naive/condition_fulfilled(mob/living/who, mob/dead_mob, dusted, gibbed) + return HAS_MIND_TRAIT(who, TRAIT_NAIVE) && !dusted && !gibbed + +/datum/mood_event/conditional/see_death/naive/update_effect(mob/dead_mob, dusted, gibbed) + description = "Have a good nap, [get_descriptor(dead_mob)]." + +/// Cultists are super brainwashed so they get buffs instead +/datum/mood_event/conditional/see_death/cult + priority = CULT_PRIORITY + description = "More souls for the Geometer!" + mood_change = parent_type::mood_change * -0.5 + +/datum/mood_event/conditional/see_death/cult/condition_fulfilled(mob/living/who, mob/dead_mob, dusted, gibbed) + if(!HAS_TRAIT(who, TRAIT_CULT_HALO)) + return FALSE + if(HAS_TRAIT(dead_mob, TRAIT_CULT_HALO)) + return FALSE + return TRUE + +/// Revs are also brainwashed but less so +/datum/mood_event/conditional/see_death/revolutionary + priority = REVOLUTIONARY_PRIORITY + mood_change = parent_type::mood_change * -0.5 + +/datum/mood_event/conditional/see_death/revolutionary/condition_fulfilled(mob/living/who, mob/dead_mob, dusted, gibbed) + return IS_REVOLUTIONARY(who) && (dead_mob.mind?.assigned_role.job_flags & JOB_HEAD_OF_STAFF) + +/datum/mood_event/conditional/see_death/revolutionary/update_effect(mob/dead_mob, dusted, gibbed) + var/datum/job/possible_head_job = dead_mob.mind?.assigned_role + description = "[possible_head_job.title ? "The [LOWER_TEXT(possible_head_job.title)]" : "Another head of staff"] is dead! Long live the revolution!" + +/// Then gamers +/datum/mood_event/conditional/see_death/gamer + priority = GAMER_PRIORITY + description = "Another one bites the dust!" + mood_change = parent_type::mood_change * -0.5 + +/datum/mood_event/conditional/see_death/gamer/condition_fulfilled(mob/living/who, mob/dead_mob, dusted, gibbed) + return istype(who.mind?.assigned_role, /datum/job/bitrunning_glitch) || istype(who.mind?.assigned_role, /datum/job/bit_avatar) + +/// People who just don't gaf +/datum/mood_event/conditional/see_death/dontcare + priority = DONTCARE_PRIORITY + mood_change = 0 + timeout = parent_type::timeout * 0.5 + +/datum/mood_event/conditional/see_death/dontcare/condition_fulfilled(mob/living/who, mob/dead_mob, dusted, gibbed) + if(HAS_PERSONALITY(who, /datum/personality/callous)) + return TRUE + if(HAS_PERSONALITY(who, /datum/personality/animal_disliker) && is_pet(dead_mob)) + return TRUE + return FALSE + +/datum/mood_event/conditional/see_death/dontcare/update_effect(mob/dead_mob, dusted, gibbed) + if(gibbed) + description = "Oh, %DEAD_MOB% exploded. Now I have to get the mop." + else if(dusted) + description = "Oh, %DEAD_MOB% was vaporized. Now I have to get the dustpan." + else + description = "Oh, %DEAD_MOB% died. Shame, I guess." + +/// Pets take priority over normal death moodlets +/datum/mood_event/conditional/see_death/pet + priority = PET_PRIORITY + +/datum/mood_event/conditional/see_death/pet/condition_fulfilled(mob/living/who, mob/dead_mob, dusted, gibbed) + return is_pet(dead_mob) + +/datum/mood_event/conditional/see_death/pet/update_effect(mob/dead_mob, dusted, gibbed) + if(gibbed) + description = "%DEAD_MOB% just exploded!!" + else if(dusted) + description = "%DEAD_MOB% just vaporized!!" + else + description = "%DEAD_MOB% just died!!" + + // future todo : make the hop care about ian, cmo runtime, etc. + if(HAS_PERSONALITY(owner, /datum/personality/animal_friend)) + mood_change *= 1.5 + timeout *= 1.25 + else if(!HAS_PERSONALITY(owner, /datum/personality/compassionate)) + mood_change *= 0.25 + timeout *= 0.5 + +/// Small boost if you see a xenomorph die +/datum/mood_event/conditional/see_death/xeno + priority = XENO_PRIORITY + +/// Check if the passed mob is any kind of xenomorph (covering for both carbon and basic types) +/datum/mood_event/conditional/see_death/xeno/proc/is_any_xenomorph(mob/target) + return isalien(target) || isalienadult(target) // latter proc should have coverage for basic mbos + +/datum/mood_event/conditional/see_death/xeno/condition_fulfilled(mob/living/who, mob/dead_mob, dusted, gibbed) + if(is_any_xenomorph(who)) + return FALSE + + if(HAS_TRAIT(who, TRAIT_XENO_HOST)) + return TRUE + + return is_any_xenomorph(dead_mob) + +// Give buffs based on the type of xenomorph dying +/datum/mood_event/conditional/see_death/xeno/update_effect(mob/dead_mob, dusted, gibbed) + // following values are in absolute value form, we make it have a positive effect later + var/change_modifier = 0 + var/timeout_modifier = 0 + + if(HAS_TRAIT(owner, TRAIT_XENO_HOST)) + handle_embryo_carrier(dead_mob) + return + + if(islarva(dead_mob)) + change_modifier = 0.1 + timeout_modifier = 0.1 + description = "Fine day whenever those slugs get squished." + + if(isalienadult(dead_mob)) + change_modifier = 0.25 + timeout_modifier = 0.25 + description = "That xenomorph bit the dust! Hell yeah!" + if(gibbed || dusted) + change_modifier += 0.1 + timeout_modifier += 0.1 + description = "It warms my heart to see xenomorphs get blown to bits!" + + if(isalienroyal(dead_mob) || istype(dead_mob, /mob/living/simple_animal/hostile/alien/queen)) + change_modifier = 0.5 + timeout_modifier = 0.5 + description = "The queen has fallen! The galaxy lives another day! I hope those bastards all rot in hell!" + if(gibbed || dusted) + change_modifier += 0.25 + timeout_modifier += 0.25 + description = "Seeing the xenomorph queen blown to bits fills me with extreme joy!" + + + mood_change = initial(mood_change) * -change_modifier + timeout = initial(timeout) * timeout_modifier + +/// Separate proc that handles cases where the viewer is carrying a xenomorph embryo +/datum/mood_event/conditional/see_death/xeno/proc/handle_embryo_carrier(mob/dead_mob) + if(!HAS_TRAIT(owner, TRAIT_XENO_HOST)) + return + + var/obj/item/organ/body_egg/alien_embryo/embryo = owner.get_organ_by_type(/obj/item/organ/body_egg/alien_embryo) + if(isnull(embryo)) + stack_trace("Xeno Host [owner] missing embryo organ despite having XENO_HOST trait. What the fuck?") + return + + if(owner.stat != CONSCIOUS) // if the carrier is sleeping then presumably the embryo's hivemind isn't affected + return + + // You feel a lot worse if you're conscious and see a xenomorph die while implanted because the hivemind feels the loss of their sister + var/embryo_stage_multiplier = 1 + (embryo.stage / 10) + mood_change *= embryo_stage_multiplier + timeout *= embryo_stage_multiplier + description = "There's something inside of me churning after I saw that xenomorph die." + RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_XENO_HOST), PROC_REF(on_embryo_removal)) + +/// Handles cleanup once the embryo carrier dies +/datum/mood_event/conditional/see_death/xeno/proc/on_embryo_removal(datum/source) + SIGNAL_HANDLER + qdel(src) + +/// Desensitized brings up the rear +/datum/mood_event/conditional/see_death/desensitized + priority = DESENSITIZED_PRIORITY + mood_change = parent_type::mood_change * 0.5 + timeout = parent_type::timeout * 0.5 + +/datum/mood_event/conditional/see_death/desensitized/condition_fulfilled(mob/living/who, mob/dead_mob, dusted, gibbed) + return HAS_MIND_TRAIT(who, TRAIT_DESENSITIZED) + +/datum/mood_event/conditional/see_death/desensitized/update_effect(mob/dead_mob, dusted, gibbed) + if(gibbed) + description = "I saw %DEAD_MOB% explode." + else if(dusted) + description = "I saw %DEAD_MOB% get vaporized." + else + description = "I saw %DEAD_MOB% die." + + +#undef DESENSITIZED_PRIORITY +#undef PET_PRIORITY +#undef XENO_PRIORITY +#undef DONTCARE_PRIORITY +#undef GAMER_PRIORITY +#undef REVOLUTIONARY_PRIORITY +#undef CULT_PRIORITY +#undef NAIVE_PRIORITY diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm index 1c0c56661ff5..fe1caa6ffb87 100644 --- a/code/modules/mob/living/death.dm +++ b/code/modules/mob/living/death.dm @@ -148,38 +148,39 @@ * Note: If the mob has a death moodlet, and a worse moodlet is applied, the worse moodlet will take priority. * * Arguments: - * * moodlet - The type of moodlet to send. Defaults to [/datum/mood_event/see_death] + * * dusted - Was the mob dusted? + * * gibbed - Was the mob gibbed? */ -/mob/living/proc/send_death_moodlets(datum/mood_event/moodlet = /datum/mood_event/see_death) +/mob/living/proc/send_death_moodlets(dusted = FALSE, gibbed = FALSE) if(flags_1 & HOLOGRAM_1) return for(var/mob/living/nearby in viewers(src)) if(nearby.stat >= UNCONSCIOUS || nearby.is_blind()) continue - nearby.add_mood_event("saw_death", moodlet, src) + nearby.add_mood_event("saw_death", /datum/mood_event/conditional/see_death, src, dusted, gibbed) -/mob/living/silicon/send_death_moodlets(datum/mood_event/moodlet) - return // You are a machine +/mob/living/silicon/send_death_moodlets(dusted = FALSE, gibbed = FALSE) + return // You are a machine (Future todo, roboticists feel sad though) -/mob/living/basic/send_death_moodlets(datum/mood_event/moodlet) +/mob/living/basic/send_death_moodlets(dusted = FALSE, gibbed = FALSE) if(!(basic_mob_flags & SENDS_DEATH_MOODLETS)) return . = ..() - add_memory_in_range(src, 7, /datum/memory/pet_died, deuteragonist = src) //Protagonist is the person memorizing it + add_memory_in_range(src, 7, /datum/memory/pet_died, deuteragonist = src) -/mob/living/simple_animal/send_death_moodlets(datum/mood_event/moodlet) +/mob/living/simple_animal/send_death_moodlets(dusted = FALSE, gibbed = FALSE) return // I don't care about you anymore -/mob/living/carbon/human/send_death_moodlets(datum/mood_event/moodlet) +/mob/living/carbon/human/send_death_moodlets(dusted = FALSE, gibbed = FALSE) for(var/datum/surgery/organ_manipulation/manipulation in surgeries) // This check exists so debraining someone doesn't make the surgeon super sad if(manipulation.location == BODY_ZONE_HEAD && body_position == LYING_DOWN) return . = ..() - var/memory_type = ispath(moodlet, /datum/mood_event/see_death/gibbed) ? /datum/memory/witness_gib : /datum/memory/witnessed_death - add_memory_in_range(src, 7, memory_type, protagonist = src) + add_memory_in_range(src, 7, (gibbed ? /datum/memory/witness_gib : /datum/memory/witnessed_death), protagonist = src) + /* * Called when the mob dies. Can also be called manually to kill a mob. diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index cb16e05ba5dd..870cb2da1787 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -2634,7 +2634,11 @@ GLOBAL_LIST_EMPTY(fire_appearances) /mob/living/proc/add_mood_event(category, type, ...) if(QDELETED(mob_mood)) return - mob_mood.add_mood_event(arglist(args)) + + if(ispath(type, /datum/mood_event/conditional)) + mob_mood.add_conditional_mood_event(arglist(args)) + else + mob_mood.add_mood_event(arglist(args)) /// Clears a mood event from the mob /mob/living/proc/clear_mood_event(category) diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index 1f7e96231d68..a5e3d5fe95a0 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -138,6 +138,7 @@ #include "damp_rag.dm" #include "dcs_check_list_arguments.dm" #include "dcs_get_id_from_elements.dm" +#include "death_moodlets.dm" #include "designs.dm" #include "dismemberment.dm" #include "door_access.dm" diff --git a/code/modules/unit_tests/death_moodlets.dm b/code/modules/unit_tests/death_moodlets.dm new file mode 100644 index 000000000000..ded468abbb1d --- /dev/null +++ b/code/modules/unit_tests/death_moodlets.dm @@ -0,0 +1,104 @@ +/// Tests death moodlets given various traits and personalities +/datum/unit_test/death_moodlets + abstract_type = /datum/unit_test/death_moodlets + /// What moodlet type we expect the test to give + var/desired_moodlet = /datum/mood_event/conditional/see_death + +/datum/unit_test/death_moodlets/Run() + var/mob/living/carbon/human/consistent/dummy = allocate(__IMPLIED_TYPE__) + prepare_dummy(dummy) + + var/mob/living/dying = get_dying_mob() + prepare_dying_mob(dying) + dying.death() + + var/datum/mood_event/moodlet = dummy.mob_mood.mood_events["saw_death"] + TEST_ASSERT_EQUAL(moodlet?.type, desired_moodlet, "Dummy did not receive the correct moodlet upon witnessing a death.") + +/// Override to prepare the dummy as needed +/datum/unit_test/death_moodlets/proc/prepare_dummy(mob/living/carbon/human/consistent/dummy) + return + +/// Override to provide the mob that will die +/datum/unit_test/death_moodlets/proc/get_dying_mob() + return allocate(/mob/living/carbon/human/consistent) + +/// Override to prepare the dying mob as needed +/datum/unit_test/death_moodlets/proc/prepare_dying_mob(mob/living/dying) + return + +/// Base type for human death moodlets +/datum/unit_test/death_moodlets/human + abstract_type = /datum/unit_test/death_moodlets/human + +/// Base type for pet death moodlets +/datum/unit_test/death_moodlets/pet + abstract_type = /datum/unit_test/death_moodlets/pet + +/datum/unit_test/death_moodlets/pet/get_dying_mob() + return allocate(/mob/living/basic/pet/cat/_proc) + +/// Test the normal ol default moodlet +/datum/unit_test/death_moodlets/human/normal + desired_moodlet = /datum/mood_event/conditional/see_death + +/// Test desensitized moodlet +/datum/unit_test/death_moodlets/human/desensitized + desired_moodlet = /datum/mood_event/conditional/see_death/desensitized + +/datum/unit_test/death_moodlets/human/desensitized/prepare_dummy(mob/living/carbon/human/consistent/dummy) + ADD_TRAIT(dummy, TRAIT_DESENSITIZED, TRAIT_SOURCE_UNIT_TESTS) + +/// Test callous moodlet +/datum/unit_test/death_moodlets/human/callous + desired_moodlet = /datum/mood_event/conditional/see_death/dontcare + +/datum/unit_test/death_moodlets/human/callous/prepare_dummy(mob/living/carbon/human/consistent/dummy) + dummy.add_personality(/datum/personality/callous) + +/// Tests that callous is prioritized over desensitized +/datum/unit_test/death_moodlets/human/desensitized_and_callous + desired_moodlet = /datum/mood_event/conditional/see_death/dontcare + +/datum/unit_test/death_moodlets/human/desensitized_and_callous/prepare_dummy(mob/living/carbon/human/consistent/dummy) + ADD_TRAIT(dummy, TRAIT_DESENSITIZED, TRAIT_SOURCE_UNIT_TESTS) + dummy.add_personality(/datum/personality/callous) + +/// Test cultist positive moodlet +/datum/unit_test/death_moodlets/human/cultist + desired_moodlet = /datum/mood_event/conditional/see_death/cult + +/datum/unit_test/death_moodlets/human/cultist/prepare_dummy(mob/living/carbon/human/consistent/dummy) + ADD_TRAIT(dummy, TRAIT_CULT_HALO, TRAIT_SOURCE_UNIT_TESTS) + +/// Tests cultists are still sad when other cultists die +/datum/unit_test/death_moodlets/human/cultist/friendly_fire + desired_moodlet = /datum/mood_event/conditional/see_death + +/datum/unit_test/death_moodlets/human/cultist/friendly_fire/prepare_dying_mob(mob/living/dying) + ADD_TRAIT(dying, TRAIT_CULT_HALO, TRAIT_SOURCE_UNIT_TESTS) + +/// Tests animal moodlet +/datum/unit_test/death_moodlets/pet/animal_moodlet + desired_moodlet = /datum/mood_event/conditional/see_death/pet + +/// Tests desensitized moodlet when a pet dies +/datum/unit_test/death_moodlets/pet/desensitized_to_pet + desired_moodlet = /datum/mood_event/conditional/see_death/pet + +/datum/unit_test/death_moodlets/pet/desensitized_to_pet/prepare_dummy(mob/living/carbon/human/consistent/dummy) + ADD_TRAIT(dummy, TRAIT_DESENSITIZED, TRAIT_SOURCE_UNIT_TESTS) + +/// Tests callous moodlet when a pet dies +/datum/unit_test/death_moodlets/pet/callous_to_pet + desired_moodlet = /datum/mood_event/conditional/see_death/dontcare + +/datum/unit_test/death_moodlets/pet/callous_to_pet/prepare_dummy(mob/living/carbon/human/consistent/dummy) + dummy.add_personality(/datum/personality/callous) + +/// Tests animal disliker moodlet when a pet dies +/datum/unit_test/death_moodlets/pet/animal_disliker_to_pet + desired_moodlet = /datum/mood_event/conditional/see_death/dontcare + +/datum/unit_test/death_moodlets/pet/animal_disliker_to_pet/prepare_dummy(mob/living/carbon/human/consistent/dummy) + dummy.add_personality(/datum/personality/animal_disliker) diff --git a/maplestation.dme b/maplestation.dme index 94fc3a30277b..564bd178e27d 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -1647,6 +1647,7 @@ #include "code\datums\mood_events\_mood_event.dm" #include "code\datums\mood_events\area_events.dm" #include "code\datums\mood_events\beauty_events.dm" +#include "code\datums\mood_events\death.dm" #include "code\datums\mood_events\dna_infuser_events.dm" #include "code\datums\mood_events\drink_events.dm" #include "code\datums\mood_events\drug_events.dm" From 0f899e830090b67133bc8e1f2e642ed87b8570b4 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Fri, 23 Jan 2026 15:47:23 -0600 Subject: [PATCH 04/20] typesof --- code/datums/mood.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/datums/mood.dm b/code/datums/mood.dm index b753410b96d4..28e8e130493a 100644 --- a/code/datums/mood.dm +++ b/code/datums/mood.dm @@ -239,7 +239,7 @@ var/list/params = args.Copy(3) var/list/datum/mood_event/conditional/all_valid_conditional_events = list() - for(var/event_subtype in subtypesof(base_type)) + for(var/event_subtype in typesof(base_type)) var/datum/mood_event/potential_event = new event_subtype(category) if(!potential_event.can_effect_mob(arglist(list(src, mob_parent) + params))) qdel(potential_event) From bc33c6305ce22fa9948629eb6c44ce585c03a0ce Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Fri, 23 Jan 2026 16:34:40 -0600 Subject: [PATCH 05/20] Insanity stuff --- code/datums/mood.dm | 11 +- code/datums/mood_events/_mood_event.dm | 5 + code/datums/mood_events/death.dm | 11 ++ code/datums/mood_events/drug_events.dm | 4 + .../mood_events/generic_negative_events.dm | 159 ++++++------------ code/modules/mob/living/death.dm | 6 +- code/modules/mob/living/status_procs.dm | 2 +- 7 files changed, 86 insertions(+), 112 deletions(-) diff --git a/code/datums/mood.dm b/code/datums/mood.dm index 28e8e130493a..4c37f706bf2b 100644 --- a/code/datums/mood.dm +++ b/code/datums/mood.dm @@ -128,6 +128,15 @@ if(change) adjust_sanity(change * seconds_per_tick, new_min, new_max) + if(sanity_level >= SANITY_LEVEL_CRAZY && SPT_PROB(sanity_level == SANITY_LEVEL_CRAZY ? 2 : 5, seconds_per_tick)) + mob_parent.set_jitter_if_lower(3 SECONDS) + if(prob(50)) + mob_parent.cause_hallucination(/datum/hallucination/fake_sound/weird/creepy, "low sanity") + for(var/mood_cat in shuffle(mood_events)) + var/datum/mood_event/event = mood_events[mood_cat] + if(event.insanity_message(sanity)) + break + /datum/mood/proc/handle_mob_death(datum/source, new_stat, old_stat) SIGNAL_HANDLER @@ -675,7 +684,7 @@ else if(esanity <= 40) esanity = 40 - switch(sanity) + switch(esanity) if (0 to 10) new_screen = mob_parent.overlay_fullscreen("sanity", /atom/movable/screen/fullscreen/crit, 4) mob_parent.add_client_colour(/datum/client_colour/sanity/tier4, "sanity") diff --git a/code/datums/mood_events/_mood_event.dm b/code/datums/mood_events/_mood_event.dm index 476886feb780..93f93e40e8b1 100644 --- a/code/datums/mood_events/_mood_event.dm +++ b/code/datums/mood_events/_mood_event.dm @@ -104,6 +104,11 @@ if(timeout) addtimer(CALLBACK(home, TYPE_PROC_REF(/datum/mood, clear_mood_event), category), timeout, (TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_NO_HASH_WAIT)) +/// Called when someone is suffering from this mood event and is crazy or insane +/// Return TRUE to handle the message, ie no other mood events are checked +/datum/mood_event/proc/insanity_message(sanity) + return FALSE + /** * Called when added to a mob * diff --git a/code/datums/mood_events/death.dm b/code/datums/mood_events/death.dm index 5c05d1b00541..9e8d0de1be3b 100644 --- a/code/datums/mood_events/death.dm +++ b/code/datums/mood_events/death.dm @@ -24,6 +24,17 @@ /datum/mood_event/conditional/see_death/condition_fulfilled(mob/living/who, mob/dead_mob, dusted, gibbed) return TRUE +/datum/mood_event/conditional/see_death/insanity_message(sanity) + if(mood_change <= -25 && sanity <= SANITY_CRAZY) + to_chat(owner, span_boldwarning("So many deaths... When will it end...?")) + owner.cause_hallucination(/datum/hallucination/everywhere_blood, "overwhelmed by death") + return TRUE + + if(mood_change <= -8) + to_chat(owner, span_warning("Could I have done anything to save them...?")) + return TRUE + return FALSE + /datum/mood_event/conditional/see_death/add_effects(mob/dead_mob, dusted, gibbed) update_effect(dead_mob, dusted, gibbed) diff --git a/code/datums/mood_events/drug_events.dm b/code/datums/mood_events/drug_events.dm index 8ac323dda7a7..a422c86d0348 100644 --- a/code/datums/mood_events/drug_events.dm +++ b/code/datums/mood_events/drug_events.dm @@ -81,6 +81,10 @@ timeout = 30 SECONDS special_screen_obj = "mood_happiness_bad" +/datum/mood_event/happiness_drug_bad_od/insanity_message(sanity) + to_chat(owner, span_userdanger(description)) + return TRUE + /datum/mood_event/narcotic_medium description = "I feel comfortably numb." mood_change = 4 diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm index 16c4f8d51785..927baf660a94 100644 --- a/code/datums/mood_events/generic_negative_events.dm +++ b/code/datums/mood_events/generic_negative_events.dm @@ -12,11 +12,19 @@ mood_change = -12 event_flags = MOOD_EVENT_FEAR +/datum/mood_event/on_fire/insanity_message(sanity) + to_chat(owner, span_userdanger(pick("PUT IT OUT! PUT IT OUT!!", description))) + return TRUE + /datum/mood_event/suffocation description = "CAN'T... BREATHE..." mood_change = -12 event_flags = MOOD_EVENT_FEAR +/datum/mood_event/suffocation/insanity_message(sanity) + to_chat(owner, span_userdanger(pick("AIR! I NEED AIR...", description))) + return TRUE + /datum/mood_event/burnt_thumb description = "I shouldn't play with lighters..." mood_change = -1 @@ -87,6 +95,10 @@ home.add_mood_event(new_event.category, /datum/mood_event/depression_mild) return BLOCK_NEW_MOOD +/datum/mood_event/depression_minimal/insanity_message(sanity) + to_chat(owner, span_warning("Things aren't doing so good...")) + return TRUE + /datum/mood_event/depression_mild description = "I feel sad for no particular reason." mood_change = -12 @@ -96,6 +108,10 @@ home.add_mood_event(new_event.category, /datum/mood_event/depression_moderate) return BLOCK_NEW_MOOD +/datum/mood_event/depression_mild/insanity_message(sanity) + to_chat(owner, span_warning("I feel so empty.")) + return TRUE + /datum/mood_event/depression_moderate description = "I feel miserable." mood_change = -14 @@ -105,16 +121,28 @@ home.add_mood_event(new_event.category, /datum/mood_event/depression_severe) return BLOCK_NEW_MOOD +/datum/mood_event/depression_moderate/insanity_message(sanity) + to_chat(owner, span_boldwarning("What's the point of anything anymore...?")) + return TRUE + /datum/mood_event/depression_severe description = "I've lost all hope." mood_change = -16 timeout = 2 MINUTES +/datum/mood_event/depression_severe/insanity_message(sanity) + to_chat(owner, span_boldwarning(description)) + return TRUE + /datum/mood_event/shameful_suicide //suicide_acts that return SHAME, like sord description = "I can't even end it all!" mood_change = -15 timeout = 60 SECONDS +/datum/mood_event/shameful_suicide/insanity_message(sanity) + to_chat(owner, span_boldwarning(description)) + return TRUE + /datum/mood_event/dismembered description = "AHH! MY LIMB! I WAS USING THAT!" mood_change = -10 @@ -124,6 +152,10 @@ if(limb) description = "AHH! MY [uppertext(limb.plaintext_zone)]! I WAS USING THAT!" +/datum/mood_event/dismembered/insanity_message(sanity) + to_chat(owner, span_boldwarning(description)) + return TRUE + /datum/mood_event/reattachment description = "Ouch! My limb feels like I fell asleep on it." mood_change = -3 @@ -194,6 +226,10 @@ description = "I hate it in the light...I need to find a darker place..." mood_change = -12 +/datum/mood_event/bright_light/insanity_message(sanity) + to_chat(owner, span_boldwarning("The light, it burns!!")) + return TRUE + /datum/mood_event/family_heirloom_missing description = "I'm missing my family heirloom..." mood_change = -4 @@ -219,6 +255,10 @@ mood_change = -10 event_flags = MOOD_EVENT_FEAR +/datum/mood_event/choke/insanity_message(sanity) + to_chat(owner, span_boldwarning(description)) + return TRUE + /datum/mood_event/vomit description = "I just threw up. Gross." mood_change = -2 @@ -276,6 +316,10 @@ var/unhinged = uppertext(unstable.Join(""))//example Tinea Luxor > TINEA LUXORRRR (with randomness in how long that slur is) description = "THEY NEEEEEEED [unhinged]!!" +/datum/mood_event/notcreepingsevere/insanity_message(sanity) + to_chat(owner, span_boldwarning(description)) + return TRUE + /datum/mood_event/tower_of_babel description = "My ability to communicate is an incoherent babel..." mood_change = -1 @@ -320,6 +364,10 @@ description = "This is it... I'm really going to die." mood_change = -20 +/datum/mood_event/deaths_door/insanity_message(sanity) + to_chat(owner, span_userdanger(description)) + return TRUE + /datum/mood_event/gunpoint description = "This guy is insane! I better be careful..." mood_change = -10 @@ -341,6 +389,10 @@ timeout = 4 MINUTES event_flags = MOOD_EVENT_FEAR +/datum/mood_event/gates_of_mansus/insanity_message(sanity) + to_chat(owner, span_boldwarning(description)) + return TRUE + /datum/mood_event/high_five_alone description = "I tried getting a high-five with no one around, how embarassing!" mood_change = -2 @@ -602,113 +654,6 @@ description = "Blowing smoke in my face, really?" mood_change = 0 -/datum/mood_event/see_death - description = "I just saw someone die. How horrible..." - mood_change = -8 - timeout = 5 MINUTES - /// Message variant for callous people - var/dont_care_message = "Oh, %DEAD_MOB% died. Shame, I guess." - /// Message variant for people who care about animals - var/pet_message = "%DEAD_MOB% just died!!" - /// Message variant for desensitized people (security, medical, cult with halo, etc) - var/desensitized_message = "I saw %DEAD_MOB% die." - /// Standard message variant - var/normal_message = "I just saw %DEAD_MOB% die. How horrible..." - /// Naive mobs are immune to the effect - var/naive_immune = TRUE - -/datum/mood_event/see_death/add_effects(mob/dead_mob) - if(isnull(dead_mob)) - return - if(HAS_TRAIT(owner, TRAIT_NAIVE) && naive_immune) - description = "Have a good nap, [dead_mob.name]." - mood_change = 0 - timeout *= 0.2 - return - if(HAS_TRAIT(dead_mob, TRAIT_SPAWNED_MOB)) - mood_change *= 0.25 - timeout *= 0.2 - if(istype(owner.mind?.assigned_role, /datum/job/bitrunning_glitch) || istype(owner.mind?.assigned_role, /datum/job/bit_avatar)) - // Digital beings shouldn't care about death it's just gaming - mood_change *= -0.25 - description = "Another one bites the dust!" - return - if(HAS_TRAIT(owner, TRAIT_CULT_HALO) && !HAS_TRAIT(dead_mob, TRAIT_CULT_HALO)) - // When cultists get halos, they stop caring about death - mood_change *= -0.5 - description = "More souls for the Geometer!" - return - - var/ispet = istype(dead_mob, /mob/living/basic/pet) || ismonkey(dead_mob) - if(HAS_PERSONALITY(owner, /datum/personality/callous) || (ispet && HAS_PERSONALITY(owner, /datum/personality/animal_disliker))) - description = replacetext(dont_care_message, "%DEAD_MOB%", get_descriptor(dead_mob)) - mood_change = 0 - timeout *= 0.5 - return - // future todo : make the hop care about ian, cmo runtime, etc. - if(ispet) - description = replacetext(pet_message, "%DEAD_MOB%", capitalize(dead_mob.name)) // doesn't use a descriptor, so it says "Ian died" - if(HAS_PERSONALITY(owner, /datum/personality/animal_friend)) - mood_change *= 1.5 - timeout *= 1.25 - else if(!HAS_PERSONALITY(owner, /datum/personality/compassionate)) - mood_change *= 0.25 - timeout *= 0.5 - return - if(HAS_PERSONALITY(owner, /datum/personality/compassionate)) - mood_change *= 1.5 - timeout *= 1.5 - if(HAS_TRAIT(owner, TRAIT_DESENSITIZED)) - mood_change *= 0.5 - timeout *= 0.5 - description = replacetext(desensitized_message, "%DEAD_MOB%", get_descriptor(dead_mob)) - return - - description = replacetext(normal_message, "%DEAD_MOB%", get_descriptor(dead_mob)) - -/datum/mood_event/see_death/be_refreshed(datum/mood/home, mob/dead_mob, ...) - // Every time we get refreshed we get worse if not desensitized - if(!HAS_TRAIT(owner, TRAIT_DESENSITIZED) && !HAS_TRAIT(dead_mob, TRAIT_SPAWNED_MOB)) - mood_change *= 1.5 - return ..() - -/datum/mood_event/see_death/be_replaced(datum/mood/home, datum/mood_event/new_event, ...) - // Only be replaced if the incoming event's base mood is worse than our base mood - // (IE: replace normal death events with gib events, but not the other way around) - if(initial(new_event.mood_change) > initial(mood_change)) - new_event.mood_change = max(new_event.mood_change, mood_change * 1.5) - return ..() - // Otherwise if it's equivalent or worse, refresh it instead - return be_refreshed(home) - -/// Changes "I saw Joe x" to "I saw the engineer x" -/datum/mood_event/see_death/proc/get_descriptor(mob/dead_mob) - if(isnull(dead_mob)) - return "something" - if(dead_mob.name != "Unknown" && dead_mob.mind?.assigned_role?.job_flags & JOB_CREW_MEMBER) - return "the [LOWER_TEXT(dead_mob.mind?.assigned_role.title)]" - return "someone" - -/datum/mood_event/see_death/gibbed - description = "Someone just exploded in front of me!!" - mood_change = -12 - timeout = 10 MINUTES - dont_care_message = "Oh, %DEAD_MOB% exploded. Now I have to get the mop." - pet_message = "%DEAD_MOB% just exploded!!" - desensitized_message = "I saw %DEAD_MOB% explode." - normal_message = "%DEAD_MOB% just exploded in front of me!!" - naive_immune = FALSE - -/datum/mood_event/see_death/dusted - description = "Someone was just vaporized in front of me!! I don't feel so good..." - mood_change = -12 - timeout = 10 MINUTES - dont_care_message = "Oh, %DEAD_MOB% was vaporized. Now I have to get the dustpan." - pet_message = "%DEAD_MOB% just vaporized!!" - desensitized_message = "I saw %DEAD_MOB% get vaporized." - normal_message = "%DEAD_MOB% was just vaporized in front of me!!" - naive_immune = FALSE - /datum/mood_event/slots/loss description = "Aww dang it!" mood_change = -2 diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm index fe1caa6ffb87..4fc7c1b11054 100644 --- a/code/modules/mob/living/death.dm +++ b/code/modules/mob/living/death.dm @@ -17,7 +17,7 @@ if(stat != DEAD) death(TRUE, "being torn apart") - send_death_moodlets(/datum/mood_event/see_death) + send_death_moodlets(gibbed = TRUE) ghostize() spill_organs(drop_bitflags) @@ -81,7 +81,7 @@ ADD_TRAIT(src, TRAIT_FORCED_STANDING, TRAIT_GENERIC) death(TRUE, "being vaporized") - send_death_moodlets(/datum/mood_event/see_death/dusted) + send_death_moodlets(dusted = TRUE) if(drop_items) unequip_everything() @@ -195,7 +195,7 @@ if(!gibbed) if(death_sound || death_message || (living_flags & ALWAYS_DEATHGASP)) INVOKE_ASYNC(src, TYPE_PROC_REF(/mob, emote), "deathgasp") - send_death_moodlets(/datum/mood_event/see_death) + send_death_moodlets() set_stat(DEAD) SShealth_updates.queue_update(src, UPDATE_MEDHUD) // This is just for weird case where death is called out of nowhere diff --git a/code/modules/mob/living/status_procs.dm b/code/modules/mob/living/status_procs.dm index 52a4963509f2..11786978a1ee 100644 --- a/code/modules/mob/living/status_procs.dm +++ b/code/modules/mob/living/status_procs.dm @@ -553,7 +553,7 @@ station_timestamp_timeofdeath = station_time_timestamp() if(!HAS_TRAIT(src, TRAIT_FAKEDEATH) && !silent) - send_death_moodlets(/datum/mood_event/see_death) + send_death_moodlets() add_traits(list(TRAIT_FAKEDEATH, TRAIT_DEATHCOMA), source) ///Unignores all slowdowns that lack the IGNORE_NOSLOW flag. From 466881459718a998b4b83ca22354a465032e5caf Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Sun, 25 Jan 2026 13:23:58 -0600 Subject: [PATCH 06/20] Static? --- code/datums/mood.dm | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/code/datums/mood.dm b/code/datums/mood.dm index 4c37f706bf2b..2b7eb84bf9e2 100644 --- a/code/datums/mood.dm +++ b/code/datums/mood.dm @@ -671,6 +671,18 @@ /datum/client_colour/sanity/tier1 desaturation = 0.9 +/atom/movable/screen/fullscreen/sanity + show_when_dead = FALSE + icon_state = "passage" + layer = UI_DAMAGE_LAYER - 0.1 + plane = FULLSCREEN_PLANE + color = "#270227" + +/atom/movable/screen/fullscreen/static_vision/sanity + show_when_dead = FALSE + color = "#e0e0e0" + alpha = 25 + /datum/mood/proc/update_sanity_screen() var/obj/old_screen = mob_parent.screens["sanity"] var/old_state = old_screen?.icon_state @@ -686,23 +698,38 @@ switch(esanity) if (0 to 10) - new_screen = mob_parent.overlay_fullscreen("sanity", /atom/movable/screen/fullscreen/crit, 4) + new_screen = mob_parent.overlay_fullscreen("sanity", /atom/movable/screen/fullscreen/sanity, 4) mob_parent.add_client_colour(/datum/client_colour/sanity/tier4, "sanity") if (10 to 20) - new_screen = mob_parent.overlay_fullscreen("sanity", /atom/movable/screen/fullscreen/crit, 3) + new_screen = mob_parent.overlay_fullscreen("sanity", /atom/movable/screen/fullscreen/sanity, 3) mob_parent.add_client_colour(/datum/client_colour/sanity/tier3, "sanity") if (20 to 30) - new_screen = mob_parent.overlay_fullscreen("sanity", /atom/movable/screen/fullscreen/crit, 2) + new_screen = mob_parent.overlay_fullscreen("sanity", /atom/movable/screen/fullscreen/sanity, 2) mob_parent.add_client_colour(/datum/client_colour/sanity/tier2, "sanity") if (30 to 40) - new_screen = mob_parent.overlay_fullscreen("sanity", /atom/movable/screen/fullscreen/crit, 1) + new_screen = mob_parent.overlay_fullscreen("sanity", /atom/movable/screen/fullscreen/sanity, 1) mob_parent.add_client_colour(/datum/client_colour/sanity/tier1, "sanity") else mob_parent.clear_fullscreen("sanity") + mob_parent.clear_fullscreen("sanity_static") mob_parent.remove_client_colour("sanity") if(new_screen && old_state != new_screen.icon_state) - new_screen.color = "#270227" + // updating static effect for new sanity level + var/had_effect = !!mob_parent.screens["sanity_static"] + mob_parent.clear_fullscreen("sanity_static", animated = FALSE) + var/obj/staticystuff = mob_parent.overlay_fullscreen("sanity_static", /atom/movable/screen/fullscreen/static_vision/sanity) + var/new_alpha = staticystuff.alpha - (esanity * 0.5) + if(had_effect) + animate(staticystuff, time = 2.5 MINUTES, alpha = 0, loop = -1) + animate(time = 2.5 MINUTES, alpha = new_alpha, loop = -1) + else + staticystuff.alpha = 0 + animate(staticystuff, time = 0.5 MINUTES, alpha = new_alpha) + animate(time = 2.5 MINUTES, alpha = 0, loop = -1) + animate(time = 2.5 MINUTES, alpha = new_alpha, loop = -1) + + // resetting filter stuff new_screen.add_filter("sanity_filter", 1, outline_filter(1, "#270227")) new_screen.add_filter("sanity_blur", 2, drop_shadow_filter(1, 1, 10, 0, "#270227")) var/blur = new_screen.get_filter("sanity_blur") From 95ce0da9b9b5083a7d62ac75158b63abe4e4e1da Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Tue, 17 Feb 2026 17:16:20 -0600 Subject: [PATCH 07/20] Mood notifications --- code/datums/mood.dm | 75 ++++++++++++++++++- code/datums/mood_events/_mood_event.dm | 3 + code/datums/mood_events/drug_events.dm | 14 +++- .../mood_events/generic_negative_events.dm | 6 ++ maplestation_modules/code/datums/pain/pain.dm | 8 +- .../code/datums/pain/pain_effects.dm | 5 -- .../datums/pain/pain_reagents/painkillers.dm | 6 +- 7 files changed, 102 insertions(+), 15 deletions(-) diff --git a/code/datums/mood.dm b/code/datums/mood.dm index 0db295f3a386..0cd1f83f0cd0 100644 --- a/code/datums/mood.dm +++ b/code/datums/mood.dm @@ -38,7 +38,6 @@ var/insanity_effect = 0 /// The screen object for the current mood level var/atom/movable/screen/mood/mood_screen_object - /// List of mood events currently active on this datum var/list/mood_events = list() @@ -183,6 +182,12 @@ new_event.on_add(src, mob_parent, params) mood_events[category] = new_event update_mood() + if(mob_parent.client) + var/atom/movable/screen/mood_maptext/new_maptext = new(null, null, new_event, active_mood_maptexts) + active_mood_maptexts += 1 + mob_parent.client.screen += new_maptext + addtimer(CALLBACK(src, PROC_REF(fade_mood_maptext), new_maptext), new_maptext.running_time_length + 1 SECONDS, TIMER_DELETE_ME) + if(new_event.mood_change == 0 || new_event.hidden) return if(new_event.mood_change > 0) @@ -196,6 +201,74 @@ /datum/personality/misanthropic = /datum/mood_event/misanthropic_happy ), range = 4) +/datum/mood + /// Counts + var/active_mood_maptexts = 0 + + var/debug_fadeless = FALSE + +/datum/mood/proc/fade_mood_maptext(atom/movable/screen/mood_maptext/maptext_to_fade) + if(QDELETED(maptext_to_fade) || debug_fadeless) + return + animate(maptext_to_fade, time = 1 SECONDS, alpha = 0) + addtimer(CALLBACK(src, PROC_REF(clear_mood_maptext), maptext_to_fade), 2 SECONDS) + +/datum/mood/proc/clear_mood_maptext(atom/movable/screen/mood_maptext/maptext_to_clear) + active_mood_maptexts -= 1 + mob_parent.client?.screen -= maptext_to_clear + qdel(maptext_to_clear) + +/atom/movable/screen/mood_maptext + alpha = 200 + maptext_width = 200 + maptext_height = 40 + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + var/maptext_color + var/running_time_length = 0 + +/atom/movable/screen/mood_maptext/Initialize(mapload, datum/hud/hud_owner, datum/mood_event/base, offset = 0) + . = ..() + if(isnull(base)) + return INITIALIZE_HINT_QDEL + + maptext_color = base.screentext_color + if(isnull(maptext_color)) + if(base.mood_change > 0) + maptext_color = COLOR_ETHIOPIA_GREEN + else if(base.mood_change < 0) + maptext_color = COLOR_ETHIOPIA_RED + + var/shown_text = base.description + var/list/shown_words = splittext(shown_text, " ") + if(length(shown_words) == 1) + maptext = "
[MAPTEXT(shown_text)]
" + else + var/list/words = list(shown_words[1]) + maptext = construct_maptext(words) + for(var/i in 2 to length(shown_words)) + var/new_time_length = max(1, floor(length_char(shown_words[i - 1]) * 0.75)) + words += shown_words[i] + animate(src, time = new_time_length, maptext = construct_maptext(words), flags = ANIMATION_CONTINUE) + running_time_length += new_time_length + + screen_loc = "CENTER-3:16,SOUTH+3:[offset * 20]" + +/atom/movable/screen/mood_maptext/proc/construct_maptext(list/words) + var/output = "" + var/linebreaks = 0 + for(var/i in 1 to length(words)) + var/word = words[i] + if(i == length(words)) + output += "[word]" + + else if(linebreaks < 1 && findtext(word, GLOB.is_punctuation)) + output += "[word]
" + linebreaks += 1 + else + output += "[word] " + + return "
[MAPTEXT("[output]")]
" + /** * Removes a mood event from the mob * diff --git a/code/datums/mood_events/_mood_event.dm b/code/datums/mood_events/_mood_event.dm index 045636559af4..830ad3917632 100644 --- a/code/datums/mood_events/_mood_event.dm +++ b/code/datums/mood_events/_mood_event.dm @@ -24,6 +24,9 @@ var/mob/living/owner /// List of required jobs for this mood event var/list/required_job + /// Color of the maptext for this mood event, if applicable + /// If null defaults to picking it based on intensity + var/screentext_color /datum/mood_event/New(category) src.category = category diff --git a/code/datums/mood_events/drug_events.dm b/code/datums/mood_events/drug_events.dm index 0f78678cbc54..4ef47c3792ba 100644 --- a/code/datums/mood_events/drug_events.dm +++ b/code/datums/mood_events/drug_events.dm @@ -83,12 +83,22 @@ timeout = 30 SECONDS special_screen_obj = "mood_happiness_bad" -/datum/mood_event/narcotic_medium +/datum/mood_event/narcotic + +/datum/mood_event/narcotic/be_replaced(datum/mood/home, datum/mood_event/new_event, ...) + return (new_event.mood_change < mood_change) ? BLOCK_NEW_MOOD : ALLOW_NEW_MOOD + +/datum/mood_event/narcotic/light + description = "I feel numb." + mood_change = 4 + timeout = 3 MINUTES + +/datum/mood_event/narcotic/medium description = "I feel comfortably numb." mood_change = 4 timeout = 3 MINUTES -/datum/mood_event/narcotic_heavy +/datum/mood_event/narcotic/heavy description = "I feel like I'm wrapped up in cotton!" mood_change = 9 timeout = 3 MINUTES diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm index c0778b6f33d2..119261d27319 100644 --- a/code/datums/mood_events/generic_negative_events.dm +++ b/code/datums/mood_events/generic_negative_events.dm @@ -25,26 +25,32 @@ /datum/mood_event/chilly description = "I'm feeling a bit chilly." mood_change = -2 + screentext_color = COLOR_CYAN /datum/mood_event/cold description = "It's way too cold." mood_change = -3 + screentext_color = COLOR_CYAN /datum/mood_event/freezing description = "It's freezing cold!" mood_change = -6 + screentext_color = COLOR_CYAN /datum/mood_event/warm description = "I'm feeling a bit warm." mood_change = -2 + screentext_color = COLOR_ORANGE /datum/mood_event/hot description = "It's way too hot." mood_change = -3 + screentext_color = COLOR_ORANGE /datum/mood_event/overhot description = "It's scorching hot!" mood_change = -6 + screentext_color = COLOR_ORANGE /datum/mood_event/creampie description = "I've been creamed. Tastes like pie flavor." diff --git a/maplestation_modules/code/datums/pain/pain.dm b/maplestation_modules/code/datums/pain/pain.dm index 055e35c92726..4d734f2ec71d 100644 --- a/maplestation_modules/code/datums/pain/pain.dm +++ b/maplestation_modules/code/datums/pain/pain.dm @@ -309,7 +309,7 @@ */ /datum/pain/proc/on_pain_gain(obj/item/bodypart/affected_part, amount, dam_type) affected_part.on_gain_pain_effects(amount, dam_type) - refresh_pain_attributes() + addtimer(CALLBACK(src, PROC_REF(refresh_pain_attributes)), 1, TIMER_UNIQUE) SEND_SIGNAL(parent, COMSIG_CARBON_PAIN_GAINED, affected_part, amount, dam_type) COOLDOWN_START(src, time_since_last_pain_loss, 60 SECONDS) if(amount > 20) @@ -332,7 +332,7 @@ */ /datum/pain/proc/on_pain_loss(obj/item/bodypart/affected_part, amount, type) affected_part.on_lose_pain_effects(amount) - refresh_pain_attributes() + addtimer(CALLBACK(src, PROC_REF(refresh_pain_attributes)), 1, TIMER_UNIQUE) SEND_SIGNAL(parent, COMSIG_CARBON_PAIN_LOST, affected_part, amount, type) /// Hooks into [apply_damage] to apply pain to the parent based on incoming damage. @@ -630,13 +630,13 @@ parent.add_movespeed_modifier(/datum/movespeed_modifier/pain/medium) parent.add_actionspeed_modifier(/datum/actionspeed_modifier/pain/medium) parent.add_mood_event(PAIN, /datum/mood_event/med_pain) - if(125 to 175) + if(125 to 200) parent.add_surgery_speed_mod(PAIN, 1.4) parent.outgoing_damage_mod = 0.6 parent.add_movespeed_modifier(/datum/movespeed_modifier/pain/heavy) parent.add_actionspeed_modifier(/datum/actionspeed_modifier/pain/heavy) parent.add_mood_event(PAIN, /datum/mood_event/heavy_pain) - if(225 to INFINITY) + if(200 to INFINITY) parent.add_surgery_speed_mod(PAIN, 1.5) parent.outgoing_damage_mod = 0.5 parent.add_movespeed_modifier(/datum/movespeed_modifier/pain/crippling) diff --git a/maplestation_modules/code/datums/pain/pain_effects.dm b/maplestation_modules/code/datums/pain/pain_effects.dm index 459648c7bfb1..600782fa601c 100644 --- a/maplestation_modules/code/datums/pain/pain_effects.dm +++ b/maplestation_modules/code/datums/pain/pain_effects.dm @@ -92,11 +92,6 @@ mood_change = -10 timeout = 6 MINUTES -/datum/mood_event/narcotic_light - description = "I feel numb." - mood_change = 4 - timeout = 3 MINUTES - /** * Obviousnly not all ailments of a mob are treatable while dead, * so we need to apply a "buffer" status effect post-revival diff --git a/maplestation_modules/code/datums/pain/pain_reagents/painkillers.dm b/maplestation_modules/code/datums/pain/pain_reagents/painkillers.dm index 5eb7810e5b1b..3bede65f7d32 100644 --- a/maplestation_modules/code/datums/pain/pain_reagents/painkillers.dm +++ b/maplestation_modules/code/datums/pain/pain_reagents/painkillers.dm @@ -9,11 +9,11 @@ if(current_cycle >= 5) switch(pain_modifier) if(0 to 0.45) - M.add_mood_event("numb", /datum/mood_event/narcotic_heavy, name) + M.add_mood_event("narcotic-numb", /datum/mood_event/narcotic/heavy, name) if(0.45 to 0.55) - M.add_mood_event("numb", /datum/mood_event/narcotic_medium, name) + M.add_mood_event("narcotic-numb", /datum/mood_event/narcotic/medium, name) else - M.add_mood_event("numb", /datum/mood_event/narcotic_light, name) + M.add_mood_event("narcotic-numb", /datum/mood_event/narcotic/light, name) // However, drinking with painkillers is toxic. var/highest_boozepwr = 0 From 46eb5da349296a7877ea9ca8389d3ae1cae7af73 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Tue, 17 Feb 2026 18:30:03 -0600 Subject: [PATCH 08/20] Tweaks --- code/datums/mood.dm | 43 +++++++++++-------- .../mood_events/generic_negative_events.dm | 4 +- code/datums/mood_events/needs_events.dm | 9 ++-- .../reagents/drinks/alcohol_reagents.dm | 2 +- .../chemistry/reagents/drug_reagents.dm | 2 +- .../chemistry/reagents/toxin_reagents.dm | 2 +- maplestation.dme | 1 + .../client/preferences/toggle_mood_text.dm | 14 ++++++ .../features/game_preferences/_mood_text.tsx | 9 ++++ 9 files changed, 61 insertions(+), 25 deletions(-) create mode 100644 maplestation_modules/code/modules/client/preferences/toggle_mood_text.dm create mode 100644 tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/_mood_text.tsx diff --git a/code/datums/mood.dm b/code/datums/mood.dm index 0cd1f83f0cd0..2bc277412c75 100644 --- a/code/datums/mood.dm +++ b/code/datums/mood.dm @@ -40,6 +40,10 @@ var/atom/movable/screen/mood/mood_screen_object /// List of mood events currently active on this datum var/list/mood_events = list() + /// Counts number of maptexts currently active so we can avoid stacking them on top of each other + var/active_mood_maptexts = 0 + /// Assoc lazylist of mood event types to the next world.time where we show that mood text again, to prevent spam + var/list/mood_maptext_cooldowns /// Tracks the last mob stat, updates on change /// Used to stop processing SSmood @@ -182,11 +186,15 @@ new_event.on_add(src, mob_parent, params) mood_events[category] = new_event update_mood() - if(mob_parent.client) + + var/mood_text_setting = mob_parent.client?.prefs.read_preference(/datum/preference/numeric/mood_text_alpha) || 0 + if(mood_text_setting > 0 && LAZYACCESS(mood_maptext_cooldowns, new_event.type) <= world.time) var/atom/movable/screen/mood_maptext/new_maptext = new(null, null, new_event, active_mood_maptexts) + new_maptext.alpha = 255 * mood_text_setting active_mood_maptexts += 1 mob_parent.client.screen += new_maptext addtimer(CALLBACK(src, PROC_REF(fade_mood_maptext), new_maptext), new_maptext.running_time_length + 1 SECONDS, TIMER_DELETE_ME) + LAZYSET(mood_maptext_cooldowns, new_event.type, world.time + 30 SECONDS) // arbitrary 30s cd if(new_event.mood_change == 0 || new_event.hidden) return @@ -201,15 +209,7 @@ /datum/personality/misanthropic = /datum/mood_event/misanthropic_happy ), range = 4) -/datum/mood - /// Counts - var/active_mood_maptexts = 0 - - var/debug_fadeless = FALSE - /datum/mood/proc/fade_mood_maptext(atom/movable/screen/mood_maptext/maptext_to_fade) - if(QDELETED(maptext_to_fade) || debug_fadeless) - return animate(maptext_to_fade, time = 1 SECONDS, alpha = 0) addtimer(CALLBACK(src, PROC_REF(clear_mood_maptext), maptext_to_fade), 2 SECONDS) @@ -219,12 +219,12 @@ qdel(maptext_to_clear) /atom/movable/screen/mood_maptext - alpha = 200 maptext_width = 200 maptext_height = 40 mouse_opacity = MOUSE_OPACITY_TRANSPARENT - var/maptext_color - var/running_time_length = 0 + layer = BELOW_OBJ_LAYER // render underneath storage + VAR_FINAL/maptext_color + VAR_FINAL/running_time_length = 0 /atom/movable/screen/mood_maptext/Initialize(mapload, datum/hud/hud_owner, datum/mood_event/base, offset = 0) . = ..() @@ -232,11 +232,20 @@ return INITIALIZE_HINT_QDEL maptext_color = base.screentext_color - if(isnull(maptext_color)) - if(base.mood_change > 0) - maptext_color = COLOR_ETHIOPIA_GREEN - else if(base.mood_change < 0) - maptext_color = COLOR_ETHIOPIA_RED + if(isnull(maptext_color) && base.mood_change != 0) + switch(base.mood_change) + if(-INFINITY to MOOD_SAD2) + maptext_color = COLOR_DARK_RED + if(MOOD_SAD2 to MOOD_SAD1) + maptext_color = COLOR_RED + if(MOOD_SAD1 to MOOD_NEUTRAL) + maptext_color = COLOR_GRAY + if(MOOD_NEUTRAL to MOOD_HAPPY1) + maptext_color = COLOR_WHITE + if(MOOD_HAPPY1 to MOOD_HAPPY2) + maptext_color = COLOR_LIME + if(MOOD_HAPPY2 to INFINITY) + maptext_color = COLOR_DARK_LIME var/shown_text = base.description var/list/shown_words = splittext(shown_text, " ") diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm index 119261d27319..d9f228b42f7f 100644 --- a/code/datums/mood_events/generic_negative_events.dm +++ b/code/datums/mood_events/generic_negative_events.dm @@ -226,12 +226,12 @@ event_flags = MOOD_EVENT_FEAR /datum/mood_event/vomit - description = "I just threw up. Gross." + description = "I just threw up. Gross..." mood_change = -2 timeout = 2 MINUTES /datum/mood_event/vomitself - description = "I just threw up all over myself. This is disgusting." + description = "I just threw up all over myself. This is disgusting..." mood_change = -4 timeout = 3 MINUTES diff --git a/code/datums/mood_events/needs_events.dm b/code/datums/mood_events/needs_events.dm index 31b34585035e..69c4dad3908d 100644 --- a/code/datums/mood_events/needs_events.dm +++ b/code/datums/mood_events/needs_events.dm @@ -50,16 +50,19 @@ //Disgust /datum/mood_event/gross - description = "I saw something gross." + description = "I don't feel so good..." mood_change = -4 + screentext_color = COLOR_OLIVE /datum/mood_event/verygross - description = "I think I'm going to puke..." + description = "I think I'm going to puke..!" mood_change = -6 + screentext_color = COLOR_OLIVE /datum/mood_event/disgusted - description = "Oh god, that's disgusting..." + description = "I definitely going to puke!" mood_change = -8 + screentext_color = COLOR_OLIVE /datum/mood_event/disgust/bad_smell description = "I can smell something horribly decayed inside this room." diff --git a/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm index 65289f623499..1f1a9345639d 100644 --- a/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm @@ -2416,7 +2416,7 @@ /datum/reagent/consumable/ethanol/drunken_espatier/on_mob_life(mob/living/carbon/drinker, seconds_per_tick, times_fired) . = ..() - drinker.add_mood_event("numb", /datum/mood_event/narcotic_medium, name) //comfortably numb + drinker.add_mood_event("numb", /datum/mood_event/narcotic/medium, name) //comfortably numb /datum/reagent/consumable/ethanol/drunken_espatier/on_mob_metabolize(mob/living/drinker) . = ..() diff --git a/code/modules/reagents/chemistry/reagents/drug_reagents.dm b/code/modules/reagents/chemistry/reagents/drug_reagents.dm index e531518b2c99..793d86bfdc37 100644 --- a/code/modules/reagents/chemistry/reagents/drug_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drug_reagents.dm @@ -132,7 +132,7 @@ var/high_message = pick("You feel calm.", "You feel collected.", "You feel like you need to relax.") if(SPT_PROB(2.5, seconds_per_tick)) to_chat(affected_mob, span_notice("[high_message]")) - affected_mob.add_mood_event("smacked out", /datum/mood_event/narcotic_heavy) + affected_mob.add_mood_event("smacked out", /datum/mood_event/narcotic/heavy) if(current_cycle == 36 && creation_purity <= 0.6) if(!istype(affected_mob.dna.species, /datum/species/human/krokodil_addict)) to_chat(affected_mob, span_userdanger("Your skin falls off easily!")) diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm index ce7e484a04b4..a2d422707a93 100644 --- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm @@ -728,7 +728,7 @@ if(affected_mob.toxloss <= 60) need_mob_update += affected_mob.adjustToxLoss(1 * REM * normalise_creation_purity() * seconds_per_tick, updating_health = FALSE, required_biotype = affected_biotype) if(current_cycle > 4) - affected_mob.add_mood_event("smacked out", /datum/mood_event/narcotic_heavy, name) + affected_mob.add_mood_event("smacked out", /datum/mood_event/narcotic/heavy, name) if(current_cycle > 18) affected_mob.Sleeping(40 * REM * normalise_creation_purity() * seconds_per_tick) if(need_mob_update) diff --git a/maplestation.dme b/maplestation.dme index c021c4fd9077..ad58f9afb557 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -6506,6 +6506,7 @@ #include "maplestation_modules\code\modules\client\preferences\ready_anominity.dm" #include "maplestation_modules\code\modules\client\preferences\runechat_color.dm" #include "maplestation_modules\code\modules\client\preferences\sound_frequency.dm" +#include "maplestation_modules\code\modules\client\preferences\toggle_mood_text.dm" #include "maplestation_modules\code\modules\client\preferences\toggle_radio.dm" #include "maplestation_modules\code\modules\client\preferences\toggle_speech.dm" #include "maplestation_modules\code\modules\client\preferences\species\lizard.dm" diff --git a/maplestation_modules/code/modules/client/preferences/toggle_mood_text.dm b/maplestation_modules/code/modules/client/preferences/toggle_mood_text.dm new file mode 100644 index 000000000000..b4139276339a --- /dev/null +++ b/maplestation_modules/code/modules/client/preferences/toggle_mood_text.dm @@ -0,0 +1,14 @@ +/datum/preference/numeric/mood_text_alpha + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "mood_text_alpha" + savefile_identifier = PREFERENCE_PLAYER + minimum = 0 + maximum = 1 + step = 0.01 + +/datum/preference/numeric/mood_text_alpha/create_default_value() + return 0.75 + +/datum/preference/numeric/mood_text_alpha/apply_to_client_updated(client/client, value) + for(var/atom/movable/screen/mood_maptext/maptext in client.screen) + maptext.alpha = 255 * value diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/_mood_text.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/_mood_text.tsx new file mode 100644 index 000000000000..8e4dc9374530 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/_mood_text.tsx @@ -0,0 +1,9 @@ +import { type FeatureNumeric, FeatureSliderInput } from '../base'; + +export const mood_text_alpha: FeatureNumeric = { + name: 'Mood Text Transparency', + category: 'GAMEPLAY', + description: `Controls how transparent the text displayed on your screen + when your character is affected by moodlet is.`, + component: FeatureSliderInput, +}; From 94a81c4eb30c4058163afb63cbb19bf832c904ed Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 18 Feb 2026 03:20:43 -0600 Subject: [PATCH 09/20] Bonus --- .../mood_events/generic_negative_events.dm | 26 ++++++++++++++++++- code/modules/mob/living/carbon/carbon.dm | 2 +- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm index d9f228b42f7f..d27863892419 100644 --- a/code/datums/mood_events/generic_negative_events.dm +++ b/code/datums/mood_events/generic_negative_events.dm @@ -229,12 +229,36 @@ description = "I just threw up. Gross..." mood_change = -2 timeout = 2 MINUTES + /// Replaces the description with this + taste of blood if it was blood vomid + var/bloody_description = "I just threw up..." + /// Was it vomitting blood + var/was_bloody = FALSE -/datum/mood_event/vomitself +/datum/mood_event/vomit/add_effects(blood = FALSE) + was_bloody = blood + + var/datum/blood_type/owner_blood = owner.get_blood_type() + if(!was_bloody || isnull(owner_blood)) + return // probably shouldn't happen but who knows + + description = "[bloody_description] Why do I taste [owner_blood.reagent_type::taste_description]..?" + mood_change *= 2 + timeout *= 1.5 + +/datum/mood_event/vomit/be_refreshed(datum/mood/home, blood = FALSE) + if(!was_bloody && blood) + return ALLOW_NEW_MOOD // re-apply with new description + return ..() // normal refresh + +/datum/mood_event/vomit/self description = "I just threw up all over myself. This is disgusting..." + bloody_description = "I just threw up all over myself..." mood_change = -4 timeout = 3 MINUTES +/datum/mood_event/vomit/self/be_replaced(datum/mood/home, datum/mood_event/new_event, blood) + return be_refreshed(home, blood) // vomit self won't be replaced with vomit other, just refreshed + /datum/mood_event/painful_medicine description = "Medicine may be good for me but right now it stings like hell." mood_change = -5 diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 47bd0c95ae10..2f97aa15a7ad 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -335,7 +335,7 @@ span_danger("[src] throws up all over [p_them()]self!"), span_userdanger("You throw up all over yourself!"), ) - add_mood_event("vomit", /datum/mood_event/vomitself) + add_mood_event("vomit", /datum/mood_event/vomit/self) distance = 0 else if(message) From ab35d46798a7c598cabbc726cc400673f95552e2 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 18 Feb 2026 14:45:46 -0600 Subject: [PATCH 10/20] More updates --- code/__HELPERS/_lists.dm | 2 + code/datums/mood.dm | 38 ++++++---- code/datums/mood_events/_mood_event.dm | 2 + .../mood_events/generic_negative_events.dm | 1 + code/datums/status_effects/wound_effects.dm | 4 +- code/datums/wounds/bones.dm | 5 ++ code/modules/mob/living/blood.dm | 57 -------------- .../mob/living/carbon/carbon_defines.dm | 2 - .../modules/projectiles/projectile/bullets.dm | 10 +-- code/modules/surgery/bodyparts/_bodyparts.dm | 3 +- .../code/datums/pain/pain_effects.dm | 75 +++++++++++++++++++ .../client/preferences/toggle_mood_text.dm | 19 +++++ .../features/game_preferences/_mood_text.tsx | 17 ++++- 13 files changed, 150 insertions(+), 85 deletions(-) diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm index d6bea0a3aee9..d9a81101b34f 100644 --- a/code/__HELPERS/_lists.dm +++ b/code/__HELPERS/_lists.dm @@ -50,6 +50,8 @@ ///Initialize the lazylist #define LAZYINITLIST(L) if (!L) { L = list(); } +///Initialize the lazylist and set it lengths if the list was not already initialized - does not change the length of existing lists +#define LAZYINITLISTLEN(L, V) if (!L) { L = list(); L.len = V; } ///If the provided list is empty, set it to null #define UNSETEMPTY(L) if (L && !length(L)) L = null ///If the provided key -> list is empty, remove it from the list diff --git a/code/datums/mood.dm b/code/datums/mood.dm index 2bc277412c75..e501fec166c6 100644 --- a/code/datums/mood.dm +++ b/code/datums/mood.dm @@ -40,8 +40,8 @@ var/atom/movable/screen/mood/mood_screen_object /// List of mood events currently active on this datum var/list/mood_events = list() - /// Counts number of maptexts currently active so we can avoid stacking them on top of each other - var/active_mood_maptexts = 0 + /// Lazylist that maps mood text slots currently active so we can avoid stacking them on top of each other + var/list/active_mood_maptexts /// Assoc lazylist of mood event types to the next world.time where we show that mood text again, to prevent spam var/list/mood_maptext_cooldowns @@ -186,15 +186,7 @@ new_event.on_add(src, mob_parent, params) mood_events[category] = new_event update_mood() - - var/mood_text_setting = mob_parent.client?.prefs.read_preference(/datum/preference/numeric/mood_text_alpha) || 0 - if(mood_text_setting > 0 && LAZYACCESS(mood_maptext_cooldowns, new_event.type) <= world.time) - var/atom/movable/screen/mood_maptext/new_maptext = new(null, null, new_event, active_mood_maptexts) - new_maptext.alpha = 255 * mood_text_setting - active_mood_maptexts += 1 - mob_parent.client.screen += new_maptext - addtimer(CALLBACK(src, PROC_REF(fade_mood_maptext), new_maptext), new_maptext.running_time_length + 1 SECONDS, TIMER_DELETE_ME) - LAZYSET(mood_maptext_cooldowns, new_event.type, world.time + 30 SECONDS) // arbitrary 30s cd + show_mood_maptext(new_event) if(new_event.mood_change == 0 || new_event.hidden) return @@ -209,12 +201,28 @@ /datum/personality/misanthropic = /datum/mood_event/misanthropic_happy ), range = 4) -/datum/mood/proc/fade_mood_maptext(atom/movable/screen/mood_maptext/maptext_to_fade) +/datum/mood/proc/show_mood_maptext(datum/mood_event/event) + if(isnull(mob_parent.client) || mob_parent.stat >= UNCONSCIOUS || LAZYACCESS(mood_maptext_cooldowns, event.type) > world.time) + return + LAZYINITLISTLEN(active_mood_maptexts, mob_parent.client.prefs.read_preference(/datum/preference/numeric/mood_text_cap)) + var/first_open_index = 1 + while(LAZYACCESS(active_mood_maptexts, first_open_index)) + first_open_index += 1 + if(first_open_index > LAZYLEN(active_mood_maptexts)) + return + var/atom/movable/screen/mood_maptext/new_maptext = new(null, null, event, first_open_index) + new_maptext.alpha = 255 * mob_parent.client.prefs.read_preference(/datum/preference/numeric/mood_text_alpha) + mob_parent.client.screen += new_maptext + addtimer(CALLBACK(src, PROC_REF(fade_mood_maptext), new_maptext, first_open_index), new_maptext.running_time_length + 1 SECONDS, TIMER_DELETE_ME) + LAZYSET(active_mood_maptexts, first_open_index, REF(new_maptext)) + LAZYSET(mood_maptext_cooldowns, event.type, world.time + event.screentext_cooldown) + +/datum/mood/proc/fade_mood_maptext(atom/movable/screen/mood_maptext/maptext_to_fade, index) animate(maptext_to_fade, time = 1 SECONDS, alpha = 0) - addtimer(CALLBACK(src, PROC_REF(clear_mood_maptext), maptext_to_fade), 2 SECONDS) + addtimer(CALLBACK(src, PROC_REF(clear_mood_maptext), maptext_to_fade, index), 2 SECONDS) -/datum/mood/proc/clear_mood_maptext(atom/movable/screen/mood_maptext/maptext_to_clear) - active_mood_maptexts -= 1 +/datum/mood/proc/clear_mood_maptext(atom/movable/screen/mood_maptext/maptext_to_clear, index) + LAZYSET(active_mood_maptexts, index, null) mob_parent.client?.screen -= maptext_to_clear qdel(maptext_to_clear) diff --git a/code/datums/mood_events/_mood_event.dm b/code/datums/mood_events/_mood_event.dm index 830ad3917632..a3c07e84121c 100644 --- a/code/datums/mood_events/_mood_event.dm +++ b/code/datums/mood_events/_mood_event.dm @@ -27,6 +27,8 @@ /// Color of the maptext for this mood event, if applicable /// If null defaults to picking it based on intensity var/screentext_color + /// Cooldown between showing text for this mood event if the mood event is applied multiple times + var/screentext_cooldown = 20 SECONDS /datum/mood_event/New(category) src.category = category diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm index d27863892419..99ac2a0270ab 100644 --- a/code/datums/mood_events/generic_negative_events.dm +++ b/code/datums/mood_events/generic_negative_events.dm @@ -215,6 +215,7 @@ /datum/mood_event/jittery description = "I'm nervous and on edge and I can't stand still!!" mood_change = -2 + screentext_cooldown = 1 MINUTES /datum/mood_event/jittery/add_effects(...) if(HAS_PERSONALITY(owner, /datum/personality/paranoid)) diff --git a/code/datums/status_effects/wound_effects.dm b/code/datums/status_effects/wound_effects.dm index 616d7f542b83..94467b9edde7 100644 --- a/code/datums/status_effects/wound_effects.dm +++ b/code/datums/status_effects/wound_effects.dm @@ -81,8 +81,8 @@ return FALSE owner.visible_message( - span_danger("[owner]'s body slackens noticeably!"), - span_boldwarning("Your adrenaline rush dies off, and the pain from your wounds come aching back in..."), + span_warning("[owner]'s body slackens noticeably!"), + span_grey("Your adrenaline rush dies off, and the pain from your wounds come aching back in..."), vision_distance = COMBAT_MESSAGE_RANGE, ) owner.add_movespeed_modifier(/datum/movespeed_modifier/determination_crash) diff --git a/code/datums/wounds/bones.dm b/code/datums/wounds/bones.dm index bbcc59a2995c..19fba1ac07ff 100644 --- a/code/datums/wounds/bones.dm +++ b/code/datums/wounds/bones.dm @@ -550,7 +550,12 @@ if(L.body_zone == BODY_ZONE_HEAD) occur_text = "splits open, exposing a bare, cracked skull through the flesh and blood" examine_desc = "has an unsettling indent, with bits of skull poking out" + return ..() + +/datum/wound/blunt/bone/critical/set_victim(new_victim) + victim?.clear_mood_event("compound_fracture") . = ..() + victim?.add_mood_event("compound_fracture", /datum/mood_event/compound_fracture) /// if someone is using bone gel on our wound /datum/wound/blunt/bone/proc/gel(obj/item/stack/medical/bone_gel/I, mob/user) diff --git a/code/modules/mob/living/blood.dm b/code/modules/mob/living/blood.dm index 5e9871c51ac0..a0cf8d6d2d57 100644 --- a/code/modules/mob/living/blood.dm +++ b/code/modules/mob/living/blood.dm @@ -111,7 +111,6 @@ if(temp_bleed) bleed(temp_bleed) - bleed_warn(temp_bleed) /// Has each bodypart update its bleed/wound overlay icon states /mob/living/carbon/proc/update_bodypart_bleed_overlays() @@ -149,62 +148,6 @@ . = ..() . *= physiology.bleed_mod -/** - * bleed_warn() is used to for carbons with an active client to occasionally receive messages warning them about their bleeding status (if applicable) - * - * Arguments: - * * bleed_amt- When we run this from [/mob/living/carbon/human/proc/handle_blood] we already know how much blood we're losing this tick, so we can skip tallying it again with this - * * forced- - */ -/mob/living/carbon/proc/bleed_warn(bleed_amt = get_bleed_rate(), forced = FALSE) - // NON-MODULE CHANGE for blood - if(!client || HAS_TRAIT(src, TRAIT_NOBLOOD)) - return - if((!COOLDOWN_FINISHED(src, bleeding_message_cd) || HAS_TRAIT(src, TRAIT_KNOCKEDOUT)) && !forced) - return - - var/bleeding_severity = "" - var/next_cooldown = BLEEDING_MESSAGE_BASE_CD - - switch(bleed_amt) - if(-INFINITY to 0) - return - if(0 to 1) - bleeding_severity = "You feel light trickles of blood across your skin" - next_cooldown *= 2.5 - if(1 to 3) - bleeding_severity = "You feel a small stream of blood running across your body" - next_cooldown *= 2 - if(3 to 5) - bleeding_severity = "You skin feels clammy from the flow of blood leaving your body" - next_cooldown *= 1.7 - if(5 to 7) - bleeding_severity = "Your body grows more and more numb as blood streams out" - next_cooldown *= 1.5 - if(7 to INFINITY) - bleeding_severity = "Your heartbeat thrashes wildly trying to keep up with your bloodloss" - - var/rate_of_change = ", but it's getting better." // if there's no wounds actively getting bloodier or maintaining the same flow, we must be getting better! - if(HAS_TRAIT(src, TRAIT_COAGULATING)) // if we have coagulant, we're getting better quick - rate_of_change = ", but it's clotting up quickly!" - else - // flick through our wounds to see if there are any bleeding ones getting worse or holding flow (maybe move this to handle_blood and cache it so we don't need to cycle through the wounds so much) - for(var/datum/wound/iter_wound as anything in all_wounds) - if(!iter_wound.blood_flow) - continue - var/iter_wound_roc = iter_wound.get_bleed_rate_of_change() - switch(iter_wound_roc) - if(BLOOD_FLOW_INCREASING) // assume the worst, if one wound is getting bloodier, we focus on that - rate_of_change = ", and it's getting worse!" - break - if(BLOOD_FLOW_STEADY) // our best case now is that our bleeding isn't getting worse - rate_of_change = ", and it's holding steady." - if(BLOOD_FLOW_DECREASING) // this only matters if none of the wounds fit the above two cases, included here for completeness - continue - - to_chat(src, span_warning("[bleeding_severity][rate_of_change]")) - COOLDOWN_START(src, bleeding_message_cd, next_cooldown) - /mob/living/proc/restore_blood() blood_volume = initial(blood_volume) diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm index 2ef6199cfc38..919b86756628 100644 --- a/code/modules/mob/living/carbon/carbon_defines.dm +++ b/code/modules/mob/living/carbon/carbon_defines.dm @@ -120,8 +120,6 @@ /// A bitfield of "bodyshapes", updated by /obj/item/bodypart/proc/synchronize_bodyshapes() var/bodyshape = BODYSHAPE_HUMANOID - COOLDOWN_DECLARE(bleeding_message_cd) - /// Obscured hide flags (hideflags that can't be seen AND can't be interacted with) var/obscured_slots = NONE /// Covered hide flags (hideflags that can be seen, BUT can't be interacted with) diff --git a/code/modules/projectiles/projectile/bullets.dm b/code/modules/projectiles/projectile/bullets.dm index 88e1273a4fd1..44a7deaa6f64 100644 --- a/code/modules/projectiles/projectile/bullets.dm +++ b/code/modules/projectiles/projectile/bullets.dm @@ -35,7 +35,7 @@ obj/item/weapon, harmful = weapon?.is_embed_harmless(), ) - addtimer(CALLBACK(src, PROC_REF(embed_msg), victim, limb, weapon, harmful), 0.2 SECONDS) + // addtimer(CALLBACK(src, PROC_REF(embed_msg), victim, limb, weapon, harmful), 0.2 SECONDS) if(harmful) playsound(victim, pick( 'maplestation_modules/sound/items/bullets/bullet_meat1.ogg', @@ -44,10 +44,10 @@ 'maplestation_modules/sound/items/bullets/bullet_meat4.ogg', ), 40) -/datum/embed_data/bullet/proc/embed_msg(mob/living/carbon/victim, obj/item/bodypart/limb, obj/item/weapon, harmful = TRUE) - if(QDELETED(victim) || QDELETED(limb) || QDELETED(weapon) || limb.owner != victim || victim.stat == DEAD) - return - to_chat(victim, span_userdanger("You feel a [(CAN_FEEL_PAIN(victim) && harmful) ? "sharp pain" : "dull force"] as [victim.is_blind() ? "something" : weapon] [harmful ? "embeds" : "sticks"] itself in your [limb.plaintext_zone]!")) +// /datum/embed_data/bullet/proc/embed_msg(mob/living/carbon/victim, obj/item/bodypart/limb, obj/item/weapon, harmful = TRUE) +// if(QDELETED(victim) || QDELETED(limb) || QDELETED(weapon) || limb.owner != victim || victim.stat == DEAD) +// return +// to_chat(victim, span_danger("You feel a [(CAN_FEEL_PAIN(victim) && harmful) ? "sharp pain" : "dull force"] as [victim.is_blind() ? "something" : weapon] [harmful ? "embeds" : "sticks"] itself in your [limb.plaintext_zone]!")) /datum/embed_data/bullet/pellet blood_loss = 0.01 diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index 6b4350c452f2..41a98b8f904f 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -1335,7 +1335,7 @@ var/old_bleed_rate = cached_bleed_rate cached_bleed_rate = 0 - if(!owner) + if(isnull(owner)) return if(!can_bleed()) @@ -1388,6 +1388,7 @@ // Our bleed overlay is based directly off bleed_rate, so go aheead and update that would you? if(cached_bleed_rate != old_bleed_rate) update_part_wound_overlay() + addtimer(CALLBACK(owner, TYPE_PROC_REF(/mob/living/carbon, bleed_rate_changed)), 1, TIMER_UNIQUE|TIMER_DELETE_ME) return cached_bleed_rate diff --git a/maplestation_modules/code/datums/pain/pain_effects.dm b/maplestation_modules/code/datums/pain/pain_effects.dm index 600782fa601c..4a7cc034c678 100644 --- a/maplestation_modules/code/datums/pain/pain_effects.dm +++ b/maplestation_modules/code/datums/pain/pain_effects.dm @@ -181,3 +181,78 @@ /datum/status_effect/revival_sickess/proc/died_again(...) SIGNAL_HANDLER qdel(src) + +/datum/mood_event/compound_fracture + description = "I'm pretty sure that bone is not supposed to be there!" + mood_change = -8 + +/mob/living/carbon/proc/bleed_rate_changed() + var/sum_blood_flow = get_total_bleed_rate() + if(HAS_MIND_TRAIT(src, TRAIT_MORBID)) + sum_blood_flow *= 0.25 + if(HAS_PERSONALITY(src, /datum/personality/apathetic) || HAS_PERSONALITY(src, /datum/personality/brave)) + sum_blood_flow *= 0.5 + if(HAS_PERSONALITY(src, /datum/personality/paranoid) || HAS_PERSONALITY(src, /datum/personality/cowardly)) + sum_blood_flow *= 2 + + switch(sum_blood_flow) + if(-INFINITY to 0) + clear_mood_event("bleeding") + if(0 to 1) + mob_mood?.add_mood_event("bleeding", /datum/mood_event/bleeding/low) + if(1 to 3) + mob_mood?.add_mood_event("bleeding", /datum/mood_event/bleeding/med) + if(3 to 5) + mob_mood?.add_mood_event("bleeding", /datum/mood_event/bleeding/high) + if(5 to 7) + mob_mood?.add_mood_event("bleeding", /datum/mood_event/bleeding/vhigh) + if(7 to INFINITY) + mob_mood?.add_mood_event("bleeding", /datum/mood_event/bleeding/critical) + +/datum/mood_event/bleeding + var/downgrade_description = "" + var/upgrade_description = "" + +/datum/mood_event/bleeding/be_replaced(datum/mood/home, datum/mood_event/new_event, ...) + if(istype(new_event, /datum/mood_event/bleeding)) + var/datum/mood_event/bleeding/new_bleeding_event = new_event + new_bleeding_event.replacing(src) + return ALLOW_NEW_MOOD + return BLOCK_NEW_MOOD + +/datum/mood_event/bleeding/proc/replacing(datum/mood_event/bleeding/existing_event) + if(existing_event.mood_change < mood_change) // inverted because more negative = worse + description = downgrade_description + if(existing_event.mood_change > mood_change) // inverted because less negative = better + description = upgrade_description + if(!description) + stack_trace("[existing_event.type] to [type] has no description set!") + description = initial(description) + +/datum/mood_event/bleeding/low + description = "Just a scrape." + downgrade_description = "Phew, bleeding's almost stopped." + mood_change = -1 + +/datum/mood_event/bleeding/med + description = "I'm bleeding..." + downgrade_description = "I think the bleeding's slowing down." + upgrade_description = "I think i'm bleeding more than I thought." + mood_change = -3 + +/datum/mood_event/bleeding/high + description = "I'm bleeding a lot!" + downgrade_description = "The bleeding is getting better, but I'm still losing a lot of blood." + upgrade_description = "The bleeding is getting worse, I need to stop it!" + mood_change = -6 + +/datum/mood_event/bleeding/vhigh + description = "The blood won't stop!" + downgrade_description = "The bleeding is getting better, but I'm still losing a dangerous amount of blood." + upgrade_description = "The bleeding is getting worse, I need to stop it fast!" + mood_change = -8 + +/datum/mood_event/bleeding/critical + description = "I'm losing so much blood!!" + upgrade_description = "The bleeding can't get any worse, I need to stop it immediately!" + mood_change = -12 diff --git a/maplestation_modules/code/modules/client/preferences/toggle_mood_text.dm b/maplestation_modules/code/modules/client/preferences/toggle_mood_text.dm index b4139276339a..b52b026e0757 100644 --- a/maplestation_modules/code/modules/client/preferences/toggle_mood_text.dm +++ b/maplestation_modules/code/modules/client/preferences/toggle_mood_text.dm @@ -12,3 +12,22 @@ /datum/preference/numeric/mood_text_alpha/apply_to_client_updated(client/client, value) for(var/atom/movable/screen/mood_maptext/maptext in client.screen) maptext.alpha = 255 * value + +/datum/preference/numeric/mood_text_cap + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "mood_text_cap" + savefile_identifier = PREFERENCE_PLAYER + minimum = 0 + maximum = 5 + step = 1 + +/datum/preference/numeric/mood_text_cap/create_default_value() + return 3 + +/datum/preference/numeric/mood_text_cap/apply_to_client_updated(client/client, value) + if(!isliving(client?.mob)) + return + var/mob/living/playermob = client.mob + if(isnull(playermob.mob_mood)) + return + LAZYSETLEN(playermob.mob_mood.active_mood_maptexts, value) diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/_mood_text.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/_mood_text.tsx index 8e4dc9374530..7c144fdfd002 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/_mood_text.tsx +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/_mood_text.tsx @@ -1,9 +1,20 @@ -import { type FeatureNumeric, FeatureSliderInput } from '../base'; +import { + FeatureNumberInput, + type FeatureNumeric, + FeatureSliderInput, +} from '../base'; export const mood_text_alpha: FeatureNumeric = { name: 'Mood Text Transparency', category: 'GAMEPLAY', - description: `Controls how transparent the text displayed on your screen - when your character is affected by moodlet is.`, + description: `Controls the transparency of moodlet screen text popups.`, component: FeatureSliderInput, }; + +export const mood_text_cap: FeatureNumeric = { + name: 'Mood Text Cap', + category: 'GAMEPLAY', + description: `Controls how many moodlet screen text popups can be visible at once. + Setting this to 0 will disable moodlet text popups entirely.`, + component: FeatureNumberInput, +}; From c1f3a99eda22afb9a0244129c0840db64ab76594 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 18 Feb 2026 14:46:52 -0600 Subject: [PATCH 11/20] Tweak --- code/modules/surgery/bodyparts/dismemberment.dm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm index 62aa98d51175..23c15db8e71d 100644 --- a/code/modules/surgery/bodyparts/dismemberment.dm +++ b/code/modules/surgery/bodyparts/dismemberment.dm @@ -308,7 +308,8 @@ if(new_limb_owner.mob_mood?.has_mood_of_category("dismembered_[body_zone]")) new_limb_owner.clear_mood_event("dismembered_[body_zone]") - new_limb_owner.add_mood_event("phantom_pain_[body_zone]", /datum/mood_event/reattachment, src) + if(!special) + new_limb_owner.add_mood_event("phantom_pain_[body_zone]", /datum/mood_event/reattachment, src) update_bodypart_damage_state() if(can_be_disabled) From 0c2811adb3099162a9c8f484429072d343080d4d Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 18 Feb 2026 15:07:11 -0600 Subject: [PATCH 12/20] Sanity tweaks --- code/datums/mood.dm | 4 +- code/datums/mood_events/_mood_event.dm | 17 +++--- code/datums/mood_events/death.dm | 9 +--- code/datums/mood_events/drug_events.dm | 4 -- .../mood_events/generic_negative_events.dm | 52 ------------------- code/modules/mob/living/death.dm | 2 +- 6 files changed, 12 insertions(+), 76 deletions(-) diff --git a/code/datums/mood.dm b/code/datums/mood.dm index a02777336bea..3d875b951335 100644 --- a/code/datums/mood.dm +++ b/code/datums/mood.dm @@ -133,11 +133,9 @@ if(sanity_level >= SANITY_LEVEL_CRAZY && SPT_PROB(sanity_level == SANITY_LEVEL_CRAZY ? 2 : 5, seconds_per_tick)) mob_parent.set_jitter_if_lower(3 SECONDS) - if(prob(50)) - mob_parent.cause_hallucination(/datum/hallucination/fake_sound/weird/creepy, "low sanity") for(var/mood_cat in shuffle(mood_events)) var/datum/mood_event/event = mood_events[mood_cat] - if(event.insanity_message(sanity)) + if(event.insanity_effect(sanity)) break /datum/mood/proc/handle_mob_death(datum/source, new_stat, old_stat) diff --git a/code/datums/mood_events/_mood_event.dm b/code/datums/mood_events/_mood_event.dm index 1bf382a79675..0dafe222a967 100644 --- a/code/datums/mood_events/_mood_event.dm +++ b/code/datums/mood_events/_mood_event.dm @@ -24,11 +24,6 @@ var/mob/living/owner /// List of required jobs for this mood event var/list/required_job - /// Color of the maptext for this mood event, if applicable - /// If null defaults to picking it based on intensity - var/screentext_color - /// Cooldown between showing text for this mood event if the mood event is applied multiple times - var/screentext_cooldown = 20 SECONDS /datum/mood_event/New(category) src.category = category @@ -109,10 +104,14 @@ if(timeout) addtimer(CALLBACK(home, TYPE_PROC_REF(/datum/mood, clear_mood_event), category), timeout, (TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_NO_HASH_WAIT)) -/// Called when someone is suffering from this mood event and is crazy or insane -/// Return TRUE to handle the message, ie no other mood events are checked -/datum/mood_event/proc/insanity_message(sanity) - return FALSE +/// Called when someone affected by this mood event has an insanity event happen to them +/// Returning TRUE will prevent other events from applying their own insanity effects +/datum/mood_event/proc/insanity_effect(sanity) + if(mood_change >= 0) + return FALSE + if(prob(10)) + mob_parent.cause_hallucination(/datum/hallucination/fake_sound/weird/creepy, "low sanity") + return TRUE /** * Called when added to a mob diff --git a/code/datums/mood_events/death.dm b/code/datums/mood_events/death.dm index 9e8d0de1be3b..c6902c8de39d 100644 --- a/code/datums/mood_events/death.dm +++ b/code/datums/mood_events/death.dm @@ -24,15 +24,10 @@ /datum/mood_event/conditional/see_death/condition_fulfilled(mob/living/who, mob/dead_mob, dusted, gibbed) return TRUE -/datum/mood_event/conditional/see_death/insanity_message(sanity) - if(mood_change <= -25 && sanity <= SANITY_CRAZY) - to_chat(owner, span_boldwarning("So many deaths... When will it end...?")) +/datum/mood_event/conditional/see_death/insanity_effect(sanity) + if(mood_change <= -25 && sanity <= SANITY_CRAZY && prob(25)) owner.cause_hallucination(/datum/hallucination/everywhere_blood, "overwhelmed by death") return TRUE - - if(mood_change <= -8) - to_chat(owner, span_warning("Could I have done anything to save them...?")) - return TRUE return FALSE /datum/mood_event/conditional/see_death/add_effects(mob/dead_mob, dusted, gibbed) diff --git a/code/datums/mood_events/drug_events.dm b/code/datums/mood_events/drug_events.dm index 966dd082ebb2..4ef47c3792ba 100644 --- a/code/datums/mood_events/drug_events.dm +++ b/code/datums/mood_events/drug_events.dm @@ -83,10 +83,6 @@ timeout = 30 SECONDS special_screen_obj = "mood_happiness_bad" -/datum/mood_event/happiness_drug_bad_od/insanity_message(sanity) - to_chat(owner, span_userdanger(description)) - return TRUE - /datum/mood_event/narcotic /datum/mood_event/narcotic/be_replaced(datum/mood/home, datum/mood_event/new_event, ...) diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm index 604ffc400bf5..4ded0dfa31e6 100644 --- a/code/datums/mood_events/generic_negative_events.dm +++ b/code/datums/mood_events/generic_negative_events.dm @@ -12,19 +12,11 @@ mood_change = -12 event_flags = MOOD_EVENT_FEAR -/datum/mood_event/on_fire/insanity_message(sanity) - to_chat(owner, span_userdanger(pick("PUT IT OUT! PUT IT OUT!!", description))) - return TRUE - /datum/mood_event/suffocation description = "CAN'T... BREATHE..." mood_change = -12 event_flags = MOOD_EVENT_FEAR -/datum/mood_event/suffocation/insanity_message(sanity) - to_chat(owner, span_userdanger(pick("AIR! I NEED AIR...", description))) - return TRUE - /datum/mood_event/burnt_thumb description = "I shouldn't play with lighters..." mood_change = -1 @@ -101,10 +93,6 @@ home.add_mood_event(new_event.category, /datum/mood_event/depression_mild) return BLOCK_NEW_MOOD -/datum/mood_event/depression_minimal/insanity_message(sanity) - to_chat(owner, span_warning("Things aren't doing so good...")) - return TRUE - /datum/mood_event/depression_mild description = "I feel sad for no particular reason." mood_change = -12 @@ -114,10 +102,6 @@ home.add_mood_event(new_event.category, /datum/mood_event/depression_moderate) return BLOCK_NEW_MOOD -/datum/mood_event/depression_mild/insanity_message(sanity) - to_chat(owner, span_warning("I feel so empty.")) - return TRUE - /datum/mood_event/depression_moderate description = "I feel miserable." mood_change = -14 @@ -127,28 +111,16 @@ home.add_mood_event(new_event.category, /datum/mood_event/depression_severe) return BLOCK_NEW_MOOD -/datum/mood_event/depression_moderate/insanity_message(sanity) - to_chat(owner, span_boldwarning("What's the point of anything anymore...?")) - return TRUE - /datum/mood_event/depression_severe description = "I've lost all hope." mood_change = -16 timeout = 2 MINUTES -/datum/mood_event/depression_severe/insanity_message(sanity) - to_chat(owner, span_boldwarning(description)) - return TRUE - /datum/mood_event/shameful_suicide //suicide_acts that return SHAME, like sord description = "I can't even end it all!" mood_change = -15 timeout = 60 SECONDS -/datum/mood_event/shameful_suicide/insanity_message(sanity) - to_chat(owner, span_boldwarning(description)) - return TRUE - /datum/mood_event/dismembered description = "AHH! MY LIMB! I WAS USING THAT!" mood_change = -10 @@ -158,10 +130,6 @@ if(limb) description = "AHH! MY [uppertext(limb.plaintext_zone)]! I WAS USING THAT!" -/datum/mood_event/dismembered/insanity_message(sanity) - to_chat(owner, span_boldwarning(description)) - return TRUE - /datum/mood_event/reattachment description = "Ouch! My limb feels like I fell asleep on it." mood_change = -3 @@ -232,10 +200,6 @@ description = "I hate it in the light...I need to find a darker place..." mood_change = -12 -/datum/mood_event/bright_light/insanity_message(sanity) - to_chat(owner, span_boldwarning("The light, it burns!!")) - return TRUE - /datum/mood_event/family_heirloom_missing description = "I'm missing my family heirloom..." mood_change = -4 @@ -262,10 +226,6 @@ mood_change = -10 event_flags = MOOD_EVENT_FEAR -/datum/mood_event/choke/insanity_message(sanity) - to_chat(owner, span_boldwarning(description)) - return TRUE - /datum/mood_event/vomit description = "I just threw up. Gross..." mood_change = -2 @@ -347,10 +307,6 @@ var/unhinged = uppertext(unstable.Join(""))//example Tinea Luxor > TINEA LUXORRRR (with randomness in how long that slur is) description = "THEY NEEEEEEED [unhinged]!!" -/datum/mood_event/notcreepingsevere/insanity_message(sanity) - to_chat(owner, span_boldwarning(description)) - return TRUE - /datum/mood_event/tower_of_babel description = "My ability to communicate is an incoherent babel..." mood_change = -1 @@ -395,10 +351,6 @@ description = "This is it... I'm really going to die." mood_change = -20 -/datum/mood_event/deaths_door/insanity_message(sanity) - to_chat(owner, span_userdanger(description)) - return TRUE - /datum/mood_event/gunpoint description = "This guy is insane! I better be careful..." mood_change = -10 @@ -420,10 +372,6 @@ timeout = 4 MINUTES event_flags = MOOD_EVENT_FEAR -/datum/mood_event/gates_of_mansus/insanity_message(sanity) - to_chat(owner, span_boldwarning(description)) - return TRUE - /datum/mood_event/high_five_alone description = "I tried getting a high-five with no one around, how embarassing!" mood_change = -2 diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm index a01aee2edc3e..5a6221a0f707 100644 --- a/code/modules/mob/living/death.dm +++ b/code/modules/mob/living/death.dm @@ -172,7 +172,7 @@ /mob/living/simple_animal/send_death_moodlets(dusted = FALSE, gibbed = FALSE) return // I don't care about you anymore -/mob/living/carbon/human/send_death_moodlets(datum/mood_event/moodlet) +/mob/living/carbon/human/send_death_moodlets(dusted = FALSE, gibbed = FALSE) // Deaths of people undergoing surgery don't count // otherwise surgeons would be depressed and that would be too realistic if(HAS_TRAIT(src, TRAIT_READY_TO_OPERATE)) From 7fec48c3392ab37eb65d7b4fadf25a29207e4afd Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 18 Feb 2026 16:00:43 -0600 Subject: [PATCH 13/20] Oops --- code/datums/mood.dm | 4 ---- code/datums/mood_events/_mood_event.dm | 7 ++++++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/code/datums/mood.dm b/code/datums/mood.dm index 3d875b951335..43981f62c66b 100644 --- a/code/datums/mood.dm +++ b/code/datums/mood.dm @@ -734,10 +734,6 @@ /datum/mood/proc/reset_sanity(amount) set_sanity(amount, override = TRUE) -/// Sets sanity to a specific amount, useful for callbacks -/datum/mood/proc/reset_sanity(amount) - set_sanity(amount, override = TRUE) - /// Adjusts sanity by a value /datum/mood/proc/adjust_sanity(amount, minimum = SANITY_INSANE, maximum = SANITY_GREAT, override = FALSE) set_sanity(sanity + amount, minimum, maximum, override) diff --git a/code/datums/mood_events/_mood_event.dm b/code/datums/mood_events/_mood_event.dm index 0dafe222a967..9f7c322e962c 100644 --- a/code/datums/mood_events/_mood_event.dm +++ b/code/datums/mood_events/_mood_event.dm @@ -24,6 +24,11 @@ var/mob/living/owner /// List of required jobs for this mood event var/list/required_job + /// Color of the moodlet's screen text + /// If null/unset is auto-picked based on mood intensity + var/screentext_color + /// Cooldown between allowing the same type of mood event from showing screentext + var/screentext_cooldown = 20 SECONDS /datum/mood_event/New(category) src.category = category @@ -110,7 +115,7 @@ if(mood_change >= 0) return FALSE if(prob(10)) - mob_parent.cause_hallucination(/datum/hallucination/fake_sound/weird/creepy, "low sanity") + owner.cause_hallucination(/datum/hallucination/fake_sound/weird/creepy, "low sanity") return TRUE /** From 01f9f0a7f04571e5cd0ea74efa406674220db31b Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 18 Feb 2026 16:19:11 -0600 Subject: [PATCH 14/20] Pain --- tgstation.dme | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tgstation.dme b/tgstation.dme index ba05b767f975..f2d364e4d469 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1651,6 +1651,7 @@ #include "code\datums\mood_events\_mood_event.dm" #include "code\datums\mood_events\area_events.dm" #include "code\datums\mood_events\beauty_events.dm" +#include "code\datums\mood_events\death.dm" #include "code\datums\mood_events\dna_infuser_events.dm" #include "code\datums\mood_events\drink_events.dm" #include "code\datums\mood_events\drug_events.dm" @@ -4195,6 +4196,7 @@ #include "code\modules\hallucination\bolted_airlocks.dm" #include "code\modules\hallucination\bubblegum_attack.dm" #include "code\modules\hallucination\delusions.dm" +#include "code\modules\hallucination\everywhere_blood.dm" #include "code\modules\hallucination\eyes_in_dark.dm" #include "code\modules\hallucination\fake_alert.dm" #include "code\modules\hallucination\fake_chat.dm" From b45af5c30d6b52cc92a499c521ad0d5647c05350 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Fri, 20 Feb 2026 17:03:32 -0600 Subject: [PATCH 15/20] Roundstart mood --- code/datums/mood_events/roundstart.dm | 129 ++++++++++++++++++++++++++ code/modules/jobs/job_types/_job.dm | 6 ++ maplestation.dme | 1 + 3 files changed, 136 insertions(+) create mode 100644 code/datums/mood_events/roundstart.dm diff --git a/code/datums/mood_events/roundstart.dm b/code/datums/mood_events/roundstart.dm new file mode 100644 index 000000000000..590b5ad33888 --- /dev/null +++ b/code/datums/mood_events/roundstart.dm @@ -0,0 +1,129 @@ +/datum/mood_event/conditional/roundstart + timeout = 5 MINUTES + mood_change = 1 + description = "Another day, another dollar." + +/datum/mood_event/conditional/roundstart/condition_fulfilled(mob/living/who, ...) + return TRUE + +/datum/mood_event/conditional/roundstart/stowaway + mood_change = -1 + priority = 70 + +/datum/mood_event/conditional/roundstart/stowaway/add_effects(...) + description = pick("Where am I..?", "I shouldn't be here...", "How did I even get here..?") + +/datum/mood_event/conditional/roundstart/stowaway/condition_fulfilled(mob/living/who) + return istype(who.mind?.assigned_role, /datum/job/stowaway) + +// /datum/mood_event/conditional/roundstart/all_nighter +// mood_change = -2 +// description = "Ugh, stayed up all night..." +// priority = 60 + +// /datum/mood_event/conditional/roundstart/all_nighter/condition_fulfilled(mob/living/who) +// return who.has_quirk(/datum/quirk/all_nighter) + +/datum/mood_event/conditional/roundstart/caffeine_addict + mood_change = -1 + description = "I need my morning coffee..." + priority = 59 + +/datum/mood_event/conditional/roundstart/caffeine_addict/condition_fulfilled(mob/living/who) + return who.has_quirk(/datum/quirk/caffeinated) + +/datum/mood_event/conditional/roundstart/nt_fanatic + mood_change = 2 + description = "I love working for Nanotrasen." + priority = 52 + +/datum/mood_event/conditional/roundstart/nt_fanatic/condition_fulfilled(mob/living/who) + return HAS_PERSONALITY(who, /datum/personality/nt/loyalist) + +/datum/mood_event/conditional/roundstart/nt_hater + mood_change = -2 + description = "Another day working for the company that ruined my life..." + priority = 51 + +/datum/mood_event/conditional/roundstart/nt_hater/condition_fulfilled(mob/living/who) + return HAS_PERSONALITY(who, /datum/personality/nt/disillusioned) + +/datum/mood_event/conditional/roundstart/diligent + mood_change = 2 + description = "Ready to get to work." + priority = 50 + +/datum/mood_event/conditional/roundstart/diligent/condition_fulfilled(mob/living/who) + return HAS_PERSONALITY(who, /datum/personality/slacking/diligent) || HAS_PERSONALITY(who, /datum/personality/industrious) + +/datum/mood_event/conditional/roundstart/paranoid + mood_change = 0 + description = "I hope nothing bad happens today..." + priority = 49 + +/datum/mood_event/conditional/roundstart/paranoid/condition_fulfilled(mob/living/who) + return HAS_PERSONALITY(who, /datum/personality/paranoid) + +/datum/mood_event/conditional/roundstart/lazy + mood_change = -1 + priority = 48 + +/datum/mood_event/conditional/roundstart/lazy/condition_fulfilled(mob/living/who) + return HAS_PERSONALITY(who, /datum/personality/slacking/lazy) + +// +// +// +// +// +// + +/datum/mood_event/conditional/latejoin + timeout = 2 MINUTES + mood_change = 1 + description = "Late to the party." + +/datum/mood_event/conditional/latejoin/condition_fulfilled(mob/living/who, ...) + return TRUE + +// /datum/mood_event/conditional/latejoin/all_nighter +// mood_change = -2 +// description = "Ugh, stayed up all night... Missed my alarm..." +// priority = 60 + +// /datum/mood_event/conditional/latejoin/all_nighter/condition_fulfilled(mob/living/who) +// return who.has_quirk(/datum/quirk/all_nighter) + +/datum/mood_event/conditional/latejoin/caffeine_addict + mood_change = -1 + description = "Missed my alarm... I need my morning coffee." + priority = 59 + +/datum/mood_event/conditional/latejoin/nt_fanatic + mood_change = 2 + description = "I hope they forgive me for being late." + priority = 52 + +/datum/mood_event/conditional/latejoin/nt_fanatic/condition_fulfilled(mob/living/who) + return HAS_PERSONALITY(who, /datum/personality/nt/loyalist) + +/datum/mood_event/conditional/latejoin/nt_hater + mood_change = -2 + description = "I hope they don't fire me for being late..." + priority = 51 + +/datum/mood_event/conditional/latejoin/nt_hater/condition_fulfilled(mob/living/who) + return HAS_PERSONALITY(who, /datum/personality/nt/disillusioned) + +/datum/mood_event/conditional/latejoin/diligent + mood_change = -1 + description = "Can't believe I was late!" + priority = 50 + +/datum/mood_event/conditional/latejoin/diligent/condition_fulfilled(mob/living/who) + return HAS_PERSONALITY(who, /datum/personality/slacking/diligent) || HAS_PERSONALITY(who, /datum/personality/industrious) + +/datum/mood_event/conditional/latejoin/paranoid + mood_change = -1 + description = "I hope the station was safe while I was gone..." + priority = 49 diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm index 7a9f503ccd87..f7b8fc79fdd9 100644 --- a/code/modules/jobs/job_types/_job.dm +++ b/code/modules/jobs/job_types/_job.dm @@ -185,6 +185,12 @@ for(var/password_id, password_info in GLOB.important_passwords[type]) spawned.add_mob_memory(/datum/memory/key/important_password, location = password_info[PASSWORD_LOCATION], password = password_info[PASSWORD_CODE]) + if(job_flags & JOB_ASSIGN_QUIRKS) + if(SSticker.current_state == GAME_STATE_SETTING_UP) + addtimer(CALLBACK(spawned, TYPE_PROC_REF(/mob/living, add_mood_event), "roundstart", /datum/mood_event/conditional/roundstart), 5 SECONDS) + else + addtimer(CALLBACK(spawned, TYPE_PROC_REF(/mob/living, add_mood_event), "latejoin", /datum/mood_event/conditional/latejoin), 5 SECONDS) + /// Return the outfit to use /datum/job/proc/get_outfit(consistent, title) return title_options[title] || base_outfit diff --git a/maplestation.dme b/maplestation.dme index f5961bca4fbb..dd725b0f13d1 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -1661,6 +1661,7 @@ #include "code\datums\mood_events\generic_positive_events.dm" #include "code\datums\mood_events\morbid_events.dm" #include "code\datums\mood_events\needs_events.dm" +#include "code\datums\mood_events\roundstart.dm" #include "code\datums\mutations\_combined.dm" #include "code\datums\mutations\_mutations.dm" #include "code\datums\mutations\active.dm" From 7e02b87bc868fcf11d86180cff5d5aece8043b0b Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Fri, 20 Feb 2026 17:04:12 -0600 Subject: [PATCH 16/20] y --- tgstation.dme | 1 + 1 file changed, 1 insertion(+) diff --git a/tgstation.dme b/tgstation.dme index f2d364e4d469..cfe21a72fd41 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1661,6 +1661,7 @@ #include "code\datums\mood_events\generic_positive_events.dm" #include "code\datums\mood_events\morbid_events.dm" #include "code\datums\mood_events\needs_events.dm" +#include "code\datums\mood_events\roundstart.dm" #include "code\datums\mutations\_combined.dm" #include "code\datums\mutations\_mutations.dm" #include "code\datums\mutations\active.dm" From ea6090fd45263e512453e17990b3dabc2e38337b Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Sun, 22 Feb 2026 19:32:56 -0600 Subject: [PATCH 17/20] CD tweaks --- code/datums/mood_events/generic_negative_events.dm | 6 +++++- code/datums/mood_events/generic_positive_events.dm | 9 +++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm index 4ded0dfa31e6..6dc09dae9f54 100644 --- a/code/datums/mood_events/generic_negative_events.dm +++ b/code/datums/mood_events/generic_negative_events.dm @@ -215,7 +215,7 @@ /datum/mood_event/jittery description = "I'm nervous and on edge and I can't stand still!!" mood_change = -2 - screentext_cooldown = 1 MINUTES + screentext_cooldown = 2 MINUTES /datum/mood_event/jittery/add_effects(...) if(HAS_PERSONALITY(owner, /datum/personality/paranoid)) @@ -665,11 +665,13 @@ description = "I'm alone with someone - what if they want to kill me?" mood_change = -3 event_flags = MOOD_EVENT_FEAR + screentext_cooldown = 3 MINUTES /datum/mood_event/paranoid/large_group description = "There are so many people around - any one of them could be out to get me!" mood_change = -3 event_flags = MOOD_EVENT_FEAR + screentext_cooldown = 3 MINUTES /datum/mood_event/nt_disillusioned description = "I hate the company, and everything it stands for." @@ -688,6 +690,7 @@ /datum/mood_event/slacking_off_diligent description = "I should get back to work." mood_change = -1 + screentext_cooldown = 10 MINUTES /datum/mood_event/unimaginative_patronage description = "That felt like a waste of money." @@ -708,6 +711,7 @@ description = "Eugh, I just got coated in blood!" mood_change = -4 timeout = 4 MINUTES + screentext_cooldown = 1 MINUTES /datum/mood_event/splattered_with_blood/can_effect_mob(datum/mood/home, mob/living/who, ...) if(isvampire(who)) diff --git a/code/datums/mood_events/generic_positive_events.dm b/code/datums/mood_events/generic_positive_events.dm index a3d1a41ed05c..fb3e3cdf8407 100644 --- a/code/datums/mood_events/generic_positive_events.dm +++ b/code/datums/mood_events/generic_positive_events.dm @@ -628,23 +628,28 @@ description = "Seeing happy people makes me happy." mood_change = 2 timeout = 2 MINUTES + screentext_cooldown = 6 MINUTES /datum/mood_event/misanthropic_happy description = "Seeing sad people makes me glad." mood_change = 2 timeout = 2 MINUTES + screentext_cooldown = 6 MINUTES /datum/mood_event/paranoid/alone description = "Peace and quiet, no one around to threaten me." mood_change = 1 + screentext_cooldown = 3 MINUTES /datum/mood_event/paranoid/small_group description = "I feel safer in this small group. We've got each other's backs." mood_change = 2 + screentext_cooldown = 3 MINUTES /datum/mood_event/nt_loyalist description = "I feel proud to be part of the NTâ„¢ family!" mood_change = 2 + screentext_cooldown = 12 MINUTES /datum/mood_event/loyalist_revs_lost description = "The revolution was defeated! Long live the Nanotrasen!" @@ -659,14 +664,17 @@ /datum/mood_event/enjoying_department_area description = "I love my job." mood_change = 1 + screentext_cooldown = 12 MINUTES /datum/mood_event/slacking_off_lazy description = "Boss makes a dollar, I make a dime. That's why I slack on job time." mood_change = 1 + screentext_cooldown = 12 MINUTES /datum/mood_event/working_diligent description = "Working hard is its own reward." mood_change = 1 + screentext_cooldown = 12 MINUTES /datum/mood_event/creative_patronage description = "Support artists!" @@ -688,3 +696,4 @@ mood_change = 3 timeout = 2 MINUTES event_flags = MOOD_EVENT_WHIMSY + screentext_cooldown = 3 MINUTES From fc45352979f820bf8b51b63dbd78a9ec3818a9c5 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Sun, 22 Feb 2026 19:39:10 -0600 Subject: [PATCH 18/20] These too --- maplestation_modules/code/modules/smells/smell_mood.dm | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/maplestation_modules/code/modules/smells/smell_mood.dm b/maplestation_modules/code/modules/smells/smell_mood.dm index 20256e67fba2..31e45bc614b9 100644 --- a/maplestation_modules/code/modules/smells/smell_mood.dm +++ b/maplestation_modules/code/modules/smells/smell_mood.dm @@ -2,6 +2,7 @@ description = "The metallic scent of blood fills the air." mood_change = -2 timeout = 30 SECONDS + screentext_cooldown = 1 MINUTES /datum/mood_event/blood_smell/add_effects(...) if(HAS_TRAIT(owner, TRAIT_MORBID) || isvampire(owner)) @@ -11,11 +12,13 @@ description = "The pungent odor of oil fills the air." mood_change = -1 timeout = 10 SECONDS + screentext_cooldown = 1 MINUTES /datum/mood_event/cigarette_smoke description = "The acrid smell of cigarette smoke lingers in the air." mood_change = -1 timeout = 10 SECONDS + screentext_cooldown = 1 MINUTES /datum/mood_event/cigarette_smoke/add_effects(...) if(HAS_TRAIT(owner, TRAIT_SMOKER)) @@ -25,28 +28,34 @@ description = "There's an unpleasant smell in the air." mood_change = -1 timeout = 1 MINUTES + screentext_cooldown = 1 MINUTES /datum/mood_event/disgust/bad_smell description = "I think something must have died in here." mood_change = -3 timeout = 1 MINUTES + screentext_cooldown = 1 MINUTES /datum/mood_event/disgust/really_bad_smell description = "Something horribly decayed is in this room." mood_change = -6 timeout = 1 MINUTES + screentext_cooldown = 1 MINUTES /datum/mood_event/disgust/nauseating_stench description = "The stench of rot is unbearable!" mood_change = -12 timeout = 1 MINUTES + screentext_cooldown = 1 MINUTES /datum/mood_event/good_food_aroma description = "That smells delicious!" mood_change = 2 timeout = 2 MINUTES + screentext_cooldown = 1 MINUTES /datum/mood_event/burnt_food_aroma description = "Did someone leave something burning?" mood_change = -1 timeout = 2 MINUTES + screentext_cooldown = 1 MINUTES From 7b1898cc0daaddb84d83fdfde6c0f95fb6e82f62 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Sun, 22 Feb 2026 21:10:06 -0600 Subject: [PATCH 19/20] Sanity tweak --- code/datums/mood.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/datums/mood.dm b/code/datums/mood.dm index 43981f62c66b..68fea520a7ff 100644 --- a/code/datums/mood.dm +++ b/code/datums/mood.dm @@ -131,7 +131,7 @@ if(change) adjust_sanity(change * seconds_per_tick, new_min, new_max) - if(sanity_level >= SANITY_LEVEL_CRAZY && SPT_PROB(sanity_level == SANITY_LEVEL_CRAZY ? 2 : 5, seconds_per_tick)) + if(sanity_level >= SANITY_LEVEL_CRAZY && mood_level <= MOOD_LEVEL_SAD2 && SPT_PROB(sanity_level == SANITY_LEVEL_CRAZY ? 2 : 5, seconds_per_tick)) mob_parent.set_jitter_if_lower(3 SECONDS) for(var/mood_cat in shuffle(mood_events)) var/datum/mood_event/event = mood_events[mood_cat] From e8b9eef1cd3f8f6f1351bf27e363b3600ce2a183 Mon Sep 17 00:00:00 2001 From: MrMelbert Date: Wed, 25 Feb 2026 17:45:21 -0600 Subject: [PATCH 20/20] Location pref --- code/__DEFINES/mood.dm | 3 +++ code/datums/mood.dm | 11 ++++++++--- .../modules/client/preferences/toggle_mood_text.dm | 14 ++++++++++++++ .../features/game_preferences/_mood_text.tsx | 9 +++++++++ 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/code/__DEFINES/mood.dm b/code/__DEFINES/mood.dm index 53c5247fb521..6fbaf2277fa1 100644 --- a/code/__DEFINES/mood.dm +++ b/code/__DEFINES/mood.dm @@ -93,3 +93,6 @@ #define BLOCK_NEW_MOOD FALSE /// Return from /be_replaced or /be_refreshed to actually go through and allow the new mood event to be added #define ALLOW_NEW_MOOD TRUE + +#define MOOD_TEXT_ABOVE_CHARACTER "Above Character" +#define MOOD_TEXT_BELOW_CHARACTER "Below Character" diff --git a/code/datums/mood.dm b/code/datums/mood.dm index 68fea520a7ff..32e97b6ec433 100644 --- a/code/datums/mood.dm +++ b/code/datums/mood.dm @@ -243,7 +243,8 @@ first_open_index += 1 if(first_open_index > LAZYLEN(active_mood_maptexts)) return - var/atom/movable/screen/mood_maptext/new_maptext = new(null, null, event, first_open_index) + var/maptext_location = mob_parent.client.prefs.read_preference(/datum/preference/choiced/mood_text_location) + var/atom/movable/screen/mood_maptext/new_maptext = new(null, null, event, first_open_index, maptext_location) new_maptext.alpha = 255 * mob_parent.client.prefs.read_preference(/datum/preference/numeric/mood_text_alpha) mob_parent.client.screen += new_maptext addtimer(CALLBACK(src, PROC_REF(fade_mood_maptext), new_maptext, first_open_index), new_maptext.running_time_length + 1 SECONDS, TIMER_DELETE_ME) @@ -267,7 +268,7 @@ VAR_FINAL/maptext_color VAR_FINAL/running_time_length = 0 -/atom/movable/screen/mood_maptext/Initialize(mapload, datum/hud/hud_owner, datum/mood_event/base, offset = 0) +/atom/movable/screen/mood_maptext/Initialize(mapload, datum/hud/hud_owner, datum/mood_event/base, offset = 0, maptext_location = MOOD_TEXT_ABOVE_CHARACTER) . = ..() if(isnull(base)) return INITIALIZE_HINT_QDEL @@ -301,7 +302,11 @@ animate(src, time = new_time_length, maptext = construct_maptext(words), flags = ANIMATION_CONTINUE) running_time_length += new_time_length - screen_loc = "CENTER-3:16,SOUTH+3:[offset * 20]" + switch(maptext_location) + if(MOOD_TEXT_ABOVE_CHARACTER) + screen_loc = "CENTER-3:16,NORTH-1:[(offset - 1) * -20]" + if(MOOD_TEXT_BELOW_CHARACTER) + screen_loc = "CENTER-3:16,SOUTH+3:[(offset - 1) * 20]" /atom/movable/screen/mood_maptext/proc/construct_maptext(list/words) var/output = "" diff --git a/maplestation_modules/code/modules/client/preferences/toggle_mood_text.dm b/maplestation_modules/code/modules/client/preferences/toggle_mood_text.dm index b52b026e0757..70cf11540089 100644 --- a/maplestation_modules/code/modules/client/preferences/toggle_mood_text.dm +++ b/maplestation_modules/code/modules/client/preferences/toggle_mood_text.dm @@ -31,3 +31,17 @@ if(isnull(playermob.mob_mood)) return LAZYSETLEN(playermob.mob_mood.active_mood_maptexts, value) + +/datum/preference/choiced/mood_text_location + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "mood_text_location" + savefile_identifier = PREFERENCE_PLAYER + +/datum/preference/choiced/mood_text_location/init_possible_values() + return list( + MOOD_TEXT_ABOVE_CHARACTER, + MOOD_TEXT_BELOW_CHARACTER, + ) + +/datum/preference/choiced/mood_text_location/create_default_value() + return get_choices()[1] diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/_mood_text.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/_mood_text.tsx index 7c144fdfd002..d7da9dabf2c5 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/_mood_text.tsx +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/_mood_text.tsx @@ -1,8 +1,10 @@ import { + type FeatureChoiced, FeatureNumberInput, type FeatureNumeric, FeatureSliderInput, } from '../base'; +import { FeatureDropdownInput } from '../dropdowns'; export const mood_text_alpha: FeatureNumeric = { name: 'Mood Text Transparency', @@ -18,3 +20,10 @@ export const mood_text_cap: FeatureNumeric = { Setting this to 0 will disable moodlet text popups entirely.`, component: FeatureNumberInput, }; + +export const mood_text_location: FeatureChoiced = { + name: 'Mood Text Location', + category: 'GAMEPLAY', + description: `Controls where moodlet screen text popups appear on the screen.`, + component: FeatureDropdownInput, +};