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 dc2f80927a3e..aeb25824e054 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm
@@ -117,6 +117,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/__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/__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 0db295f3a386..32e97b6ec433 100644
--- a/code/datums/mood.dm
+++ b/code/datums/mood.dm
@@ -38,13 +38,12 @@
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()
-
- /// Tracks the last mob stat, updates on change
- /// Used to stop processing SSmood
- var/last_stat = CONSCIOUS
+ /// 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
/datum/mood/New(mob/living/mob_to_make_moody)
if (!istype(mob_to_make_moody))
@@ -59,7 +58,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 +77,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 +90,61 @@
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)
-
-/datum/mood/proc/handle_mob_death(datum/source)
+ 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
+
+ if(change)
+ adjust_sanity(change * seconds_per_tick, new_min, new_max)
+
+ 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]
+ if(event.insanity_effect(sanity))
+ break
+
+/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()
@@ -153,11 +179,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 +195,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
@@ -183,6 +219,8 @@
new_event.on_add(src, mob_parent, params)
mood_events[category] = new_event
update_mood()
+ show_mood_maptext(new_event)
+
if(new_event.mood_change == 0 || new_event.hidden)
return
if(new_event.mood_change > 0)
@@ -196,6 +234,133 @@
/datum/personality/misanthropic = /datum/mood_event/misanthropic_happy
), range = 4)
+/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/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)
+ 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, index), 2 SECONDS)
+
+/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)
+
+/atom/movable/screen/mood_maptext
+ maptext_width = 200
+ maptext_height = 40
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ 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, maptext_location = MOOD_TEXT_ABOVE_CHARACTER)
+ . = ..()
+ if(isnull(base))
+ return INITIALIZE_HINT_QDEL
+
+ maptext_color = base.screentext_color
+ 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, " ")
+ 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
+
+ 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 = ""
+ 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]")]"
+
+/**
+ * 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 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 +379,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,8 +390,6 @@
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
@@ -256,10 +422,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 +460,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 +490,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)
@@ -410,6 +578,11 @@
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)
for(var/category in mood_events)
@@ -499,24 +672,26 @@
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)
+
+ 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,6 +733,7 @@
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)
@@ -567,6 +743,93 @@
/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.colour = 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
+
+/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
+ 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(esanity)
+ if (0 to 10)
+ 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/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/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/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)
+ // 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")
+ // 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)
@@ -586,11 +849,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.
diff --git a/code/datums/mood_events/_mood_event.dm b/code/datums/mood_events/_mood_event.dm
index 045636559af4..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
@@ -37,7 +42,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 +73,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)
@@ -98,6 +109,15 @@
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 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))
+ owner.cause_hallucination(/datum/hallucination/fake_sound/weird/creepy, "low sanity")
+ return TRUE
+
/**
* Called when added to a mob
*
@@ -139,3 +159,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..c6902c8de39d
--- /dev/null
+++ b/code/datums/mood_events/death.dm
@@ -0,0 +1,288 @@
+// 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/insanity_effect(sanity)
+ if(mood_change <= -25 && sanity <= SANITY_CRAZY && prob(25))
+ owner.cause_hallucination(/datum/hallucination/everywhere_blood, "overwhelmed by death")
+ return TRUE
+ return FALSE
+
+/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/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 0d0ce26523a1..198215482dbb 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."
@@ -209,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 = 2 MINUTES
/datum/mood_event/jittery/add_effects(...)
if(HAS_PERSONALITY(owner, /datum/personality/paranoid))
@@ -220,15 +227,39 @@
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."
+ /// 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/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
@@ -602,113 +633,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
@@ -741,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."
@@ -764,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."
@@ -784,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
diff --git a/code/datums/mood_events/needs_events.dm b/code/datums/mood_events/needs_events.dm
index 8f988ac53672..4609b812f15a 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/dirty_food
description = "That was too dirty to eat..."
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/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/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/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm
index 163289c2e7f9..708e2fe0df1e 100644
--- a/code/modules/jobs/job_types/_job.dm
+++ b/code/modules/jobs/job_types/_job.dm
@@ -191,6 +191,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/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.dm b/code/modules/mob/living/carbon/carbon.dm
index 8af9da917e5d..6ee1617b7d8b 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)
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/mob/living/death.dm b/code/modules/mob/living/death.dm
index 49a0ab0c2991..5a6221a0f707 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()
@@ -148,37 +148,38 @@
* 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)
// 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))
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.
@@ -193,7 +194,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/living.dm b/code/modules/mob/living/living.dm
index bf11c7d9cf4b..b2cf847e38b8 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -2660,7 +2660,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/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.
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/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/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/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)
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 d5deef8a1fa2..d23463409bc5 100644
--- a/maplestation.dme
+++ b/maplestation.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"
@@ -1660,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"
@@ -4201,6 +4203,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"
@@ -6519,6 +6522,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/datums/pain/pain.dm b/maplestation_modules/code/datums/pain/pain.dm
index c4bf878a2321..dde35e24f1b2 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.
@@ -635,13 +635,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..4a7cc034c678 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
@@ -186,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/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
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..70cf11540089
--- /dev/null
+++ b/maplestation_modules/code/modules/client/preferences/toggle_mood_text.dm
@@ -0,0 +1,47 @@
+/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
+
+/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)
+
+/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/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
diff --git a/tgstation.dme b/tgstation.dme
index 67f598a8108c..8e44229d318f 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"
@@ -1660,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"
@@ -4209,6 +4211,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"
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..d7da9dabf2c5
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/_mood_text.tsx
@@ -0,0 +1,29 @@
+import {
+ type FeatureChoiced,
+ FeatureNumberInput,
+ type FeatureNumeric,
+ FeatureSliderInput,
+} from '../base';
+import { FeatureDropdownInput } from '../dropdowns';
+
+export const mood_text_alpha: FeatureNumeric = {
+ name: 'Mood Text Transparency',
+ category: 'GAMEPLAY',
+ 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,
+};
+
+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,
+};