diff --git a/code/__DEFINES/~darkpack/auras.dm b/code/__DEFINES/~darkpack/auras.dm
index 514c44e06e60..50b05b5baded 100644
--- a/code/__DEFINES/~darkpack/auras.dm
+++ b/code/__DEFINES/~darkpack/auras.dm
@@ -40,29 +40,59 @@
#define AURA_FAERIE (FALSE) // Rainbow highlights in aura -- Not implemented. Changelings.
GLOBAL_LIST_INIT(aura_list, sort_list(list(
-"Afraid" = AURA_AFRAID,
-"Aggressive" = AURA_AGGRESSIVE,
-"Angry" = AURA_ANGRY,
-"Bitter" = AURA_BITTER,
-"Calm" = AURA_CALM,
-"Compassionate" = AURA_COMPASSIONATE,
-"Conservative" = AURA_CONSERVATIVE,
-"Depressed" = AURA_DEPRESSED,
-"Desirous" = AURA_DESIROUS,
-"Distrustful" = AURA_DISTRUSTFUL,
-"Envious" = AURA_ENVIOUS,
-"Excited" = AURA_EXCITED,
-"Generous" = AURA_GENEROUS,
-"Happy" = AURA_HAPPY,
-"Hateful" = AURA_HATEFUL,
-"Idealistic" = AURA_IDEALISTIC,
-"Innocent" = AURA_INNOCENT,
-"Lovestruck" = AURA_LOVESTRUCK,
-"Obsessed" = AURA_OBSESSED,
-"Sad" = AURA_SAD,
-"Spiritual" = AURA_SPIRITUAL,
-"Suspicious" = AURA_SUSPICIOUS,
-"Anxious" = AURA_ANXIOUS,
-"Confused" = AURA_CONFUSED,
-"Daydreaming" = AURA_DAYDREAMING,
-"Psychotic" = AURA_PSYCHOTIC)))
+ "Afraid" = AURA_AFRAID,
+ "Aggressive" = AURA_AGGRESSIVE,
+ "Angry" = AURA_ANGRY,
+ "Bitter" = AURA_BITTER,
+ "Calm" = AURA_CALM,
+ "Compassionate" = AURA_COMPASSIONATE,
+ "Conservative" = AURA_CONSERVATIVE,
+ "Depressed" = AURA_DEPRESSED,
+ "Desirous" = AURA_DESIROUS,
+ "Distrustful" = AURA_DISTRUSTFUL,
+ "Envious" = AURA_ENVIOUS,
+ "Excited" = AURA_EXCITED,
+ "Generous" = AURA_GENEROUS,
+ "Happy" = AURA_HAPPY,
+ "Hateful" = AURA_HATEFUL,
+ "Idealistic" = AURA_IDEALISTIC,
+ "Innocent" = AURA_INNOCENT,
+ "Lovestruck" = AURA_LOVESTRUCK,
+ "Obsessed" = AURA_OBSESSED,
+ "Sad" = AURA_SAD,
+ "Spiritual" = AURA_SPIRITUAL,
+ "Suspicious" = AURA_SUSPICIOUS,
+ "Anxious" = AURA_ANXIOUS,
+ "Confused" = AURA_CONFUSED,
+ "Daydreaming" = AURA_DAYDREAMING,
+ "Psychotic" = AURA_PSYCHOTIC
+)))
+
+GLOBAL_LIST_INIT(emotion_to_quality, sort_list(list(
+ "Afraid" = "fear",
+ "Aggressive" = "aggressiveness",
+ "Angry" = "anger",
+ "Bitter" = "bitterness",
+ "Calm" = "calmness",
+ "Compassionate" = "compassion",
+ "Conservative" = "conservativeness",
+ "Depressed" = "depression",
+ "Desirous" = "desire",
+ "Distrustful" = "distrust",
+ "Envious" = "envy",
+ "Excited" = "excitement",
+ "Generous" = "generosity",
+ "Happy" = "happiness",
+ "Hateful" = "hate",
+ "Idealistic" = "idealism",
+ "Innocent" = "innocence",
+ "Lovestruck" = "love",
+ "Obsessed" = "obsessiveness",
+ "Sad" = "sadness",
+ "Spiritual" = "spirituality",
+ "Suspicious" = "suspicion",
+ "Anxious" = "anxiety",
+ "Confused" = "confusion",
+ "Daydreaming" = "absentmindedness",
+ "Psychotic" = "psychosis"
+)))
diff --git a/code/__DEFINES/~darkpack/traits/declarations.dm b/code/__DEFINES/~darkpack/traits/declarations.dm
index 5291c3c51d00..3784ae79c833 100644
--- a/code/__DEFINES/~darkpack/traits/declarations.dm
+++ b/code/__DEFINES/~darkpack/traits/declarations.dm
@@ -112,4 +112,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
// Is the Vampire currently hungry? Hunger is defined at a bloodpool rating of 7 - self control (if humanity alignment) or instincts (enlightenment alignment)
// its called this because theres apparently already a defined quirk called 'hungry' which appears to lower your blood drawn from biting by half.
#define TRAIT_NEEDS_BLOOD "vampire_hungry"
+
+// Is the character's emotion currently forced? Blocks emotion panel usage - Melpominee
+#define TRAIT_FORCED_EMOTION "forced_emotion"
+// Are we under the effects of Melpominee 5?
+#define TRAIT_VIRTUOSA "virtuosa"
// END TRAIT DEFINES
diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm
index 4b30fcfba89b..5ec5f3d8d0be 100644
--- a/code/_globalvars/traits/_traits.dm
+++ b/code/_globalvars/traits/_traits.dm
@@ -725,6 +725,8 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_WEAK_TO_DOMINATE" = TRAIT_WEAK_TO_DOMINATE, // DARKPACK EDIT ADD - Dominate
"TRAIT_ILLEGAL_IDENTITY" = TRAIT_ILLEGAL_IDENTITY, // DARKPACK EDIT ADD - GOVERMENT
"TRAIT_NECROPOLIS_WORSHIP" = TRAIT_NECROPOLIS_WORSHIP,
+ "TRAIT_FORCED_EMOTION" = TRAIT_FORCED_EMOTION, // DARKPACK EDIT ADD - Melpominee
+ "TRAIT_VIRTUOSA" = TRAIT_VIRTUOSA, // DARKPACK EDIT ADD - Melpominee
),
/mob/living/carbon = list(
"TRAIT_BRAINLESS_CARBON" = TRAIT_BRAINLESS_CARBON,
diff --git a/modular_darkpack/master_files/code/modules/mob/dead/observer/observer.dm b/modular_darkpack/master_files/code/modules/mob/dead/observer/observer.dm
index d09ca58068da..d4610f3fadc5 100644
--- a/modular_darkpack/master_files/code/modules/mob/dead/observer/observer.dm
+++ b/modular_darkpack/master_files/code/modules/mob/dead/observer/observer.dm
@@ -1,6 +1,3 @@
/mob/dead/observer
+ has_emotion = TRUE
var/soul_taken = FALSE //prevents necromancers from farming souls off one singular ghost
-
-/mob/dead/observer/Initialize(mapload)
- . = ..()
- AddComponent(/datum/component/aura)
diff --git a/modular_darkpack/master_files/code/modules/mob/living/living.dm b/modular_darkpack/master_files/code/modules/mob/living/living.dm
index 5bf01e8aff80..59bd200edda7 100644
--- a/modular_darkpack/master_files/code/modules/mob/living/living.dm
+++ b/modular_darkpack/master_files/code/modules/mob/living/living.dm
@@ -1,7 +1,6 @@
/mob/living/Initialize(mapload)
. = ..()
storyteller_stats = create_new_stat_prefs(storyteller_stats)
- AddComponent(/datum/component/aura)
become_area_sensitive("zone_hud")
update_zone_hud(src, get_area(src))
diff --git a/modular_darkpack/master_files/code/modules/mob/living/living_defines.dm b/modular_darkpack/master_files/code/modules/mob/living/living_defines.dm
index 318ed24e6997..3c729416bdc0 100644
--- a/modular_darkpack/master_files/code/modules/mob/living/living_defines.dm
+++ b/modular_darkpack/master_files/code/modules/mob/living/living_defines.dm
@@ -1,4 +1,6 @@
/mob/living
+ has_emotion = TRUE
+
var/mob/living/lastattacked
var/bloodquality = 1
diff --git a/modular_darkpack/master_files/code/modules/mob/mob_defines.dm b/modular_darkpack/master_files/code/modules/mob/mob_defines.dm
new file mode 100644
index 000000000000..f7c9a3d833be
--- /dev/null
+++ b/modular_darkpack/master_files/code/modules/mob/mob_defines.dm
@@ -0,0 +1,5 @@
+/mob
+ /// Emotion currently felt by the mob (for Auspex auras etc.) // (AS A COLOR... ≧ཀ≦)
+ var/current_emotion = "Innocent"
+ /// If the mob has emotions and therefore is granted a aura.
+ var/has_emotion = FALSE
diff --git a/modular_darkpack/master_files/code/modules/mob/mob_helpers.dm b/modular_darkpack/master_files/code/modules/mob/mob_helpers.dm
new file mode 100644
index 000000000000..d76f797398c7
--- /dev/null
+++ b/modular_darkpack/master_files/code/modules/mob/mob_helpers.dm
@@ -0,0 +1,4 @@
+/mob/Initialize(mapload)
+ . = ..()
+ if(has_emotion)
+ AddComponent(/datum/component/aura)
diff --git a/modular_darkpack/modules/deprecated/icons/particle_effects.dmi b/modular_darkpack/modules/deprecated/icons/particle_effects.dmi
index 8b8db2dcaeac..db73fa4d1a3b 100644
Binary files a/modular_darkpack/modules/deprecated/icons/particle_effects.dmi and b/modular_darkpack/modules/deprecated/icons/particle_effects.dmi differ
diff --git a/modular_darkpack/modules/powers/code/discipline/__discipline_power.dm b/modular_darkpack/modules/powers/code/discipline/__discipline_power.dm
index e61c6b3da141..7d69c7e302ac 100644
--- a/modular_darkpack/modules/powers/code/discipline/__discipline_power.dm
+++ b/modular_darkpack/modules/powers/code/discipline/__discipline_power.dm
@@ -717,6 +717,9 @@
* duration_timer expire without calling the relevant proc.
*/
/datum/discipline_power/proc/clear_duration_timer(to_clear = 1)
+ if(duration_override)
+ return
+
if (toggled && (duration_length == 0))
return
diff --git a/modular_darkpack/modules/powers/code/discipline/auspex/aura_component.dm b/modular_darkpack/modules/powers/code/discipline/auspex/aura_component.dm
index dbe6f5ed2ef3..b7065fc7a6ee 100644
--- a/modular_darkpack/modules/powers/code/discipline/auspex/aura_component.dm
+++ b/modular_darkpack/modules/powers/code/discipline/auspex/aura_component.dm
@@ -11,7 +11,6 @@
var/datum/atom_hud/data/auspex_aura/target_hud = GLOB.huds[DATA_HUD_AUSPEX_AURAS]
target_hud.add_atom_to_hud(parent_mob)
- add_verb(parent_mob, /mob/verb/emotion_panel)
RegisterSignal(parent_mob, COMSIG_MOB_EMOTION_CHANGED, PROC_REF(update_emotions))
RegisterSignal(parent_mob, COMSIG_MOB_UPDATE_AURA, PROC_REF(update_aura))
update_aura()
@@ -21,7 +20,6 @@
var/datum/atom_hud/data/auspex_aura/target_hud = GLOB.huds[DATA_HUD_AUSPEX_AURAS]
target_hud.remove_atom_from_hud(parent_mob)
- remove_verb(parent_mob, /mob/verb/emotion_panel)
UnregisterSignal(parent_mob, list(COMSIG_MOB_EMOTION_CHANGED, COMSIG_MOB_UPDATE_AURA))
return ..()
diff --git a/modular_darkpack/modules/powers/code/discipline/auspex/auspex.dm b/modular_darkpack/modules/powers/code/discipline/auspex/auspex.dm
index 48a88e37241c..c14400e6acb0 100644
--- a/modular_darkpack/modules/powers/code/discipline/auspex/auspex.dm
+++ b/modular_darkpack/modules/powers/code/discipline/auspex/auspex.dm
@@ -116,7 +116,8 @@
var/list/heard = orange(DEFAULT_MESSAGE_RANGE, owner)
for(var/mob/living/hearer in heard)
- hearer.apply_status_effect(/datum/status_effect/question_emotion)
+ if(!HAS_TRAIT(src, TRAIT_FORCED_EMOTION))
+ hearer.apply_status_effect(/datum/status_effect/question_emotion)
/datum/discipline_power/auspex/aura_perception/deactivate()
. = ..()
diff --git a/modular_darkpack/modules/powers/code/discipline/auspex/emotion_panel.dm b/modular_darkpack/modules/powers/code/discipline/auspex/emotion_panel.dm
index 7b56b784649d..4d8d02a2418e 100644
--- a/modular_darkpack/modules/powers/code/discipline/auspex/emotion_panel.dm
+++ b/modular_darkpack/modules/powers/code/discipline/auspex/emotion_panel.dm
@@ -4,9 +4,21 @@
set category = "IC"
set desc = "Change your character's emotions."
+ if(HAS_TRAIT(src, TRAIT_FORCED_EMOTION))
+ to_chat(src, span_warning("You cannot change emotions right now."))
+ return FALSE
+
+ // This really shouldnt be using aura here. it needs to be detached and made unrelenient on aura/auspex. - Fallcon
var/new_emotion = tgui_input_list(src, "What are you feeling?", "Feelings", GLOB.aura_list)
if(isnull(new_emotion))
return FALSE
+ set_emotion(new_emotion)
+
+/mob/proc/set_emotion(new_emotion)
+ if(current_emotion == new_emotion)
+ return
+
+ current_emotion = new_emotion
SEND_SIGNAL(src, COMSIG_MOB_EMOTION_CHANGED, new_emotion)
/datum/status_effect/question_emotion
@@ -34,3 +46,18 @@
attached_effect.owner.emotion_panel()
// Regardless if they acctually end up setting anything, clear the status effect
qdel(attached_effect)
+
+/datum/status_effect/forced_emotion
+ id = "question_emotion"
+ // Nothing says it needs to be a scene, I just like using our defines to create nice standards of time.
+ duration = 1 SCENES
+
+ status_type = STATUS_EFFECT_REPLACE
+
+ alert_type = /atom/movable/screen/alert/status_effect/forced_emotion
+
+/atom/movable/screen/alert/status_effect/forced_emotion
+ name = "Forced emotion"
+ desc = "Something is forcing your mind into a particular emotion."
+ icon = 'modular_darkpack/modules/deprecated/icons/hud/screen_alert.dmi'
+ icon_state = "in_love"
diff --git a/modular_darkpack/modules/powers/code/discipline/melpominee.dm b/modular_darkpack/modules/powers/code/discipline/melpominee.dm
index dc072a67ba7a..9f670b7d09d8 100644
--- a/modular_darkpack/modules/powers/code/discipline/melpominee.dm
+++ b/modular_darkpack/modules/powers/code/discipline/melpominee.dm
@@ -9,67 +9,88 @@
name = "Melpominee power name"
desc = "Melpominee power description"
- activate_sound = 'modular_darkpack/modules/deprecated/sounds/melpominee.ogg'
+ activate_sound = 'modular_darkpack/modules/powers/sounds/melpominee/melpominee.ogg'
+
+ vitae_cost = 1 // All Melpominee powers below 5 dots cost blood
+ var/obj/effect/abstract/particle_holder/particle_generator
+
+/datum/discipline_power/melpominee/proc/setup_particles()
+ if(!particle_generator)
+ particle_generator = new(owner, /particles/melpominee, PARTICLE_ATTACH_MOB)
+
+/particles/melpominee
+ icon = 'modular_darkpack/modules/phones/icons/phone.dmi'
+ icon_state = list("note" = 1)
+ width = 32
+ height = 48
+ count = 5
+ spawning = 0.5
+ lifespan = 5 SECONDS
+ fade = 1.5 SECONDS
+ gravity = list(0, 0.1)
+ position = generator(GEN_SPHERE, 0, 16, NORMAL_RAND)
+ spin = generator(GEN_NUM, -1, 1, NORMAL_RAND)
+
+/**
+ * • The Missing Voice - p453
+ *
+ * The character can “throw” her voice anywhere within her line of sight. This enables the Daughter to carry on surreptitious conversations,
+ * sing duets with herself, or cause any number of distractions. This power can also be combined with other Melpominee powers to
+ * disguise their source (and some Daughters use it to conceal the fact that Melpominee powers do not function through recorded media).
+ *
+ * The Daughter clicks on an object, mob, or turf and sends a message from that location.
+ *
+ */
+/obj/effect/the_missing_voice
+ name = "disembodied voice"
+ desc = "What are you, a ghost lip-reader?"
-//THE MISSING VOICE
/datum/discipline_power/melpominee/the_missing_voice
name = "The Missing Voice"
desc = "Throw your voice to any place you can see."
level = 1
check_flags = DISC_CHECK_CONSCIOUS | DISC_CHECK_CAPABLE | DISC_CHECK_SPEAK
- target_type = TARGET_OBJ | TARGET_LIVING
+ target_type = TARGET_MOB | TARGET_OBJ | TARGET_TURF
range = 7
- cooldown_length = 5 SECONDS
-
/datum/discipline_power/melpominee/the_missing_voice/activate(atom/movable/target)
. = ..()
- var/new_say = input(owner, "What will [target] say?") as null|text
+ var/new_say = tgui_input_text(owner, "What will you say?")
if(!new_say)
return
//prevent forceful emoting and whatnot
new_say = trim(copytext_char(sanitize(new_say), 1, MAX_MESSAGE_LEN))
- if (findtext(new_say, "*"))
- to_chat(owner, span_danger("You can't force others to perform emotes!"))
+ if(!owner.try_speak(new_say))
return
- if (CHAT_FILTER_CHECK(new_say))
- to_chat(owner, span_warning("That message contained a word prohibited in IC chat! Consider reviewing the server rules.\n\"[new_say]\""))
- SSblackbox.record_feedback("tally", "ic_blocked_words", 1, LOWER_TEXT(config.ic_filter_regex.match))
+ if(findtext(new_say, "*"))
+ to_chat(owner, span_danger("You can't emote with [name]!"))
return
- target.say(message = new_say, forced = "melpominee 1")
-
- if (!isliving(target))
+ var/obj/dummy = new /obj/effect/the_missing_voice(get_turf(target)) // snowflake code but it's more robust than engineering some evil to_chat mechanism
+ if(!(dummy in range(7, owner)))
+ to_chat(owner, span_warning("You need line of sight to the location your voice is coming from."))
return
- //viewers are able to detect if a person's words aren't their own
- var/base_difficulty = 5
- var/difficulty_malus = 0
- var/masked = FALSE
- if (ishuman(target)) //apply a malus and different text if victim's mouth isn't visible, and a malus if they're already typing
- var/mob/living/carbon/human/victim = target
- if (!victim.is_face_visible())
- masked = TRUE
- base_difficulty += 2
- if (victim.overlays_standing[SAY_LAYER]) //ugly way to check for if the victim is currently typing
- base_difficulty += 2
-
- for (var/mob/living/hearer in (oviewers(DEFAULT_SIGHT_DISTANCE, target) - owner))
- if (!hearer.client)
- continue
- difficulty_malus = 0
- if (get_dist(hearer, target) > 3)
- difficulty_malus += 1
- if (SSroll.storyteller_roll(hearer.st_get_stat(STAT_PERCEPTION), base_difficulty + difficulty_malus, roller = hearer) == ROLL_SUCCESS)
- if (masked)
- to_chat(hearer, span_warning("[target]'s jaw isn't moving to match [target.p_their()] words."))
- else
- to_chat(hearer, span_warning("[target]'s lips aren't moving to match [target.p_their()] words."))
-
-//PHANTOM SPEAKER
+ dummy.name = owner.get_generic_name(TRUE, TRUE) + "'s voice"
+ dummy.say(message = new_say, forced = "melpominee 1")
+ QDEL_IN(dummy, 2 TURNS)
+
+/**
+ * •• Phantom Speaker - p453
+ *
+ * The Daughter can project her voice to any individual she has personally met. Distance is no object,
+ * but it must be night wherever the target presently is. The vampire can sing, talk, or otherwise project her voice in
+ * any way she sees fit (including other uses of Melpominee), but she cannot hear what she is saying,
+ * and therefore suffers a +1 difficulty to any rolls accompanying her utterance. For instance, the vampire could
+ * project her voice to an enemy in an attempt to intimidate him, but would suffer a +1 to the difficulty of the Charisma + Intimidation roll.
+ *
+ * The Daughter selects a mob from their guestbook and sends a message to them, provided she succedes a Wits + Performance roll. The next N messages
+ * do not require a roll and do not expend blood
+ *
+ */
/datum/discipline_power/melpominee/phantom_speaker
name = "Phantom Speaker"
desc = "Project your voice to anyone you've met, speaking to them from afar."
@@ -78,64 +99,141 @@
check_flags = DISC_CHECK_CONSCIOUS | DISC_CHECK_SPEAK
cooldown_length = 5 SECONDS
+ // the guys we last talked to
+ var/list/free_speakers
/datum/discipline_power/melpominee/phantom_speaker/activate()
. = ..()
- var/mob/living/target = input(owner, "Who will you project your voice to?") as null|mob in (GLOB.player_list - owner)
- if(!target)
+ if(!owner.mind.guestbook.known_names)
+ to_chat(owner, span_warning("You don't seem to know anyone you can speak to right now..."))
+ return
+ // Guys we add to the input below
+ var/list/targets
+
+ for(var/mob/living/character in GLOB.player_list)
+ if(character == owner)
+ continue
+ if(owner.mind.guestbook.known_names[character.real_name] && character.client)
+ targets += character
+
+ var/list/mob/living/listener_list
+ var/mob/living/listener
+
+ if(!HAS_TRAIT_FROM(owner, TRAIT_VIRTUOSA, type))
+ listener = tgui_input_list(owner, "Who will you project your voice to?", "Phantom Speaker", targets)
+ if(!listener)
+ return
+ listener_list[WEAKREF(listener)] = 0
+ else
+ listener_list = tgui_input_checkboxes(owner, "Who will you project your voice to?", "Phantom Speaker", targets)
+
+ if(!length(listener_list))
return
- var/input_message = input(owner, "What message will you project to them?") as null|text
+ var/input_message = tgui_input_text(owner, "What message will you project to them?", title = "Phantom Speaker")
if (!input_message)
return
//sanitisation!
input_message = trim(copytext_char(sanitize(input_message), 1, MAX_MESSAGE_LEN))
- if(CHAT_FILTER_CHECK(input_message))
- to_chat(owner, span_warning("That message contained a word prohibited in IC chat! Consider reviewing the server rules.\n\"[input_message]\""))
- SSblackbox.record_feedback("tally", "ic_blocked_words", 1, LOWER_TEXT(config.ic_filter_regex.match))
+ if(!owner.try_speak(input_message))
return
- var/language = owner.get_selected_language()
- var/message = owner.compose_message(owner, language, input_message, , list())
- to_chat(target, span_purple("You hear someone's voice in your head..."))
- target.Hear(message, target, language, input_message, , , )
- to_chat(owner, span_notice("You project your voice to [target]'s ears."))
+ if(findtext(input_message, "*"))
+ to_chat(owner, span_danger("You can't emote with [name]!"))
+ return
-//MADRIGAL
+ var/language = owner.get_selected_language()
+ var/message = owner.compose_message(owner, language, input_message)
+
+ var/successes
+
+ if(listener_list ~= free_speakers)
+ owner.adjust_blood_pool(1)
+ else
+ var/list/unspoken_to = list()
+ for(var/mob/living/guy in listener_list)
+ if(!(guy in free_speakers))
+ unspoken_to += guy
+ successes = SSroll.storyteller_roll(owner.st_get_stat(STAT_WITS) + owner.st_get_stat(STAT_PERFORMANCE), 7, owner, numerical = TRUE)
+ for(var/mob/living/new_guy in unspoken_to)
+ if(successes)
+ listener_list[new_guy] = successes
+ else
+ listener_list[new_guy] = null
+ to_chat(owner, span_warning("[new_guy]'s ears are not reached by your song."))
+
+ var/bp_used = max(1, length(unspoken_to-6))
+ owner.adjust_blood_pool(bp_used)
+
+ var/those_who_hear = "[jointext(listener_list, ", ", 1, length(listener_list))], and [listener_list[length(listener_list)]]."
+ for(var/mob/living/final_listeners in listener_list)
+ to_chat(final_listeners, span_boldannounce("You hear a voice in your head..."))
+ final_listeners.Hear(owner, language, span_purple(message), message_mods = list(MODE_SING))
+ to_chat(owner, span_notice("Your voice reaches the ears of [those_who_hear]"))
+
+/**
+ * ••• Madrigal - p453-454
+ *
+ * Music has the power to sway the listener, engendering specific emotions through artful lyrics, pounding crescendo,
+ * or haunting melody. The Daughters of Cacophony can tap into music’s power, forcing listeners to feel whatever they wish. The emotion becomes so
+ * powerful that the listener must act, though what a listener does isn’t something the Siren can directly control.
+ *
+ * The Daughter chooses an emotion and anyone who fails a Wits + Awareness check against her roll will begin to feel that emotion
+ *
+ */
/datum/discipline_power/melpominee/madrigal
name = "Madrigal"
- desc = "Sing a siren song, calling all nearby to you."
+ desc = "Sing a siren song, swaying the emotions of all around you."
level = 3
check_flags = DISC_CHECK_CONSCIOUS | DISC_CHECK_CAPABLE | DISC_CHECK_IMMOBILE | DISC_CHECK_SPEAK
- cooldown_length = 5 SECONDS
- duration_length = 2 SECONDS
- duration_override = TRUE
+ cooldown_length = 1 SCENES
+ duration_length = 1 SCENES
+ var/list/audience = list()
/datum/discipline_power/melpominee/madrigal/activate()
. = ..()
- for(var/mob/living/carbon/human/listener in oviewers(DEFAULT_SIGHT_DISTANCE, owner))
- listener.create_walk_to(2 SECONDS, owner)
+ var/our_power = SSroll.storyteller_roll(owner.st_get_stat(STAT_WITS) + owner.st_get_stat(STAT_PERFORMANCE), 7, owner, numerical = TRUE)
+ var/emotion = tgui_input_list(owner, "What emotion do you wish to incite?", "Madrigal", GLOB.emotion_to_quality)
- listener.remove_overlay(MUTATIONS_LAYER)
- var/mutable_appearance/song_overlay = mutable_appearance('modular_darkpack/modules/deprecated/icons/icons.dmi', "song", -MUTATIONS_LAYER)
- listener.overlays_standing[MUTATIONS_LAYER] = song_overlay
- listener.apply_overlay(MUTATIONS_LAYER)
+ for(var/mob/living/carbon/member in ohearers(7, owner))
+ audience += member
+ var/their_power = SSroll.storyteller_roll(member.st_get_stat(STAT_WITS) + member.st_get_stat(STAT_AWARENESS), 7, member, numerical = TRUE)
+ if(our_power > their_power)
+ set_emotion(member, emotion)
- addtimer(CALLBACK(src, PROC_REF(deactivate), listener), 2 SECONDS)
+/datum/discipline_power/melpominee/madrigal/proc/set_emotion(mob/living/target, emotion)
+ target.set_emotion(emotion)
+ ADD_TRAIT(target, TRAIT_FORCED_EMOTION, type)
-/mob/living/carbon/human/proc/create_walk_to(duration, mob/living/walk_to)
- var/datum/cb = CALLBACK(src, TYPE_PROC_REF(/mob/living/carbon/human, walk_to_caster), walk_to)
- for(var/i in 1 to duration)
- addtimer(cb, (i - 1) * cached_multiplicative_slowdown)
+ to_chat(target, span_purple("You are overwhelmed with [GLOB.emotion_to_quality[emotion]]."))
+ target.apply_status_effect(/datum/status_effect/forced_emotion)
-/datum/discipline_power/melpominee/madrigal/deactivate(mob/living/carbon/human/target)
+/datum/discipline_power/melpominee/madrigal/deactivate()
. = ..()
- target.remove_overlay(MUTATIONS_LAYER)
-
-//SIREN'S BECKONING
+ for(var/mob/living/carbon/member in audience)
+ if(HAS_TRAIT_FROM(member, TRAIT_FORCED_EMOTION, type))
+ to_chat(member, span_nicegreen("You are no longer overwhelmed with [GLOB.emotion_to_quality[member.current_emotion]]."))
+ else
+ to_chat(member, span_nicegreen("You feel your [GLOB.emotion_to_quality[member.current_emotion]] weakening."))
+
+ REMOVE_TRAIT(member, TRAIT_FORCED_EMOTION, type)
+
+ audience = list()
+
+/**
+ * •••• Siren's Beckoning - p454
+ *
+ * The Daughters of Cacophony don’t spread madness as surely (or as visibly) as the Malkavians, but their songs are definitely
+ * detrimental to one’s sanity. With this power, the Daughter can drive any listener to madness. Most of the time, the victim is
+ * too fascinated to realize that he should leave the area and block out the music from his mind.
+ *
+ * The Daughter sings a haunting sound that causes the victim to remain and listen, provided they fail a willpower roll.
+ *
+ * TODO: When we add derangements, add the weird cumulative success effect this power has
+ */
/datum/discipline_power/melpominee/sirens_beckoning
name = "Siren's Beckoning"
desc = "Sing an unearthly song to stun those around you."
@@ -143,55 +241,186 @@
level = 4
check_flags = DISC_CHECK_CONSCIOUS | DISC_CHECK_CAPABLE | DISC_CHECK_IMMOBILE | DISC_CHECK_SPEAK
- effect_sound = 'modular_darkpack/modules/deprecated/sounds/killscream.ogg'
-
- duration_length = 2 SECONDS
- cooldown_length = 7.5 SECONDS
+ effect_sound = 'modular_darkpack/modules/powers/sounds/melpominee/melpominee.ogg'
+ range = 7
+ duration_length = 4 TURNS
+ cooldown_length = 1 MINUTES
duration_override = TRUE
+ target_type = TARGET_LIVING
+ var/list/listener_list = list()
+ var/list/listeners_failed = list()
+ var/channeling = FALSE
+ var/list/cumulative_list = list()
+ var/list/cumulative_our_power = list()
+ var/turns_left = 4
+
+/datum/discipline_power/melpominee/sirens_beckoning/can_activate(atom/target)
+ if(HAS_TRAIT(owner, TRAIT_VIRTUOSA))
+ target_type = NONE
+ else
+ target_type = TARGET_LIVING
-/datum/discipline_power/melpominee/sirens_beckoning/activate()
. = ..()
- for(var/mob/living/carbon/human/listener in oviewers(DEFAULT_SIGHT_DISTANCE, owner))
- listener.Stun(2 SECONDS)
+/datum/discipline_power/melpominee/sirens_beckoning/activate(mob/living/target) // TODO: sliding difficulty for willpower
+ . = ..()
+ setup_particles()
+ to_chat(owner, span_purple("You begin to sing a haunting melody."))
+
+ owner.Stun(1 TURNS)
+ channeling = TRUE
+
+ if(!HAS_TRAIT(owner, TRAIT_VIRTUOSA))
+ if(!target)
+ return
+ else
+ if(!length(ohearers(owner, 7)))
+ return
+
+ run_effect(target)
+
+/datum/discipline_power/melpominee/sirens_beckoning/proc/run_effect(mob/living/carbon/target)
+ if(turns_left > 0)
+ turns_left--
+ else
+ deactivate(target, TRUE)
+ return FALSE
+
+ if(!HAS_TRAIT(owner, TRAIT_VIRTUOSA))
+ listener_list = list(target)
+ else
+ listener_list = ohearers(owner, 7)
+
+ for(var/mob/living/carbon/listener in listener_list) // TODO: mark these as spammy rolls
+ var/our_power = SSroll.storyteller_roll((owner.st_get_stat(STAT_MANIPULATION) + owner.st_get_stat(STAT_PERFORMANCE)), listener.st_get_stat(STAT_TEMPORARY_WILLPOWER), owner, numerical = TRUE)
+ cumulative_our_power[listener] += our_power
+ var/their_power = SSroll.storyteller_roll(listener.st_get_stat(STAT_TEMPORARY_WILLPOWER), (owner.st_get_stat(STAT_APPEARANCE) + owner.st_get_stat(STAT_PERFORMANCE)), listener, numerical = TRUE)
+ cumulative_list[listener] += their_power
+ if(our_power > their_power && should_run_effect(listener))
+ effect(listener)
+ else
+ listener_list -= listener
+ listener.remove_overlay(MUTATIONS_LAYER)
+ cumulative_our_power[listener] = null
+ cumulative_list[listener] = null
+
+ if(do_after(owner, 1 TURNS, timed_action_flags = IGNORE_HELD_ITEM | IGNORE_INCAPACITATED | IGNORE_SLOWDOWNS) && channeling && length(listener_list))
+ run_effect(target)
+ else
+ deactivate(target, TRUE)
+
+/datum/discipline_power/melpominee/sirens_beckoning/proc/should_run_effect(mob/living/listener)
+ if(!owner.can_speak())
+ return FALSE
+ return TRUE
+
+/datum/discipline_power/melpominee/sirens_beckoning/proc/effect(mob/living/carbon/listener)
+ listener.Stun(1 TURNS)
+ listener.remove_overlay(MUTATIONS_LAYER)
+ var/mutable_appearance/song_overlay = mutable_appearance('modular_darkpack/modules/deprecated/icons/icons.dmi', "song", -MUTATIONS_LAYER)
+ listener.overlays_standing[MUTATIONS_LAYER] = song_overlay
+ listener.apply_overlay(MUTATIONS_LAYER)
+ if(cumulative_our_power[listener] >= 20)
+ listener.add_quirk(/datum/quirk/derangement)
+
+ if(cumulative_list[listener] <= cumulative_our_power[listener]-6)
+ if(listener.add_quirk(/datum/quirk/derangement))
+ addtimer(CALLBACK(src, PROC_REF(remove_derangement), listener), 1 SCENES)
+
+/datum/discipline_power/melpominee/sirens_beckoning/proc/remove_derangement(mob/living/carbon/listener)
+ listener.remove_quirk(/datum/quirk/derangement)
+
+/datum/discipline_power/melpominee/sirens_beckoning/deactivate(mob/living/carbon/target)
+ . = ..()
+ for(var/mob/living/carbon/listener in listener_list)
listener.remove_overlay(MUTATIONS_LAYER)
- var/mutable_appearance/song_overlay = mutable_appearance('modular_darkpack/modules/deprecated/icons/icons.dmi', "song", -MUTATIONS_LAYER)
- listener.overlays_standing[MUTATIONS_LAYER] = song_overlay
- listener.apply_overlay(MUTATIONS_LAYER)
- addtimer(CALLBACK(src, PROC_REF(deactivate), listener), 2 SECONDS)
+ owner.visible_message(span_purple("[owner]'s haunting melody ceases."), span_purple("You stop singing."))
+ channeling = FALSE
+ QDEL_NULL(particle_generator)
+ turns_left = 4
+
+ // These can still be a source of hardels if they happen mid ability.
+ // But it would need a bigger refactor so we only have to handle 1 weakref/1 list per guy to avoid nightmare code.
+ listener_list = list()
+ listeners_failed = list()
+ cumulative_list = list()
+ cumulative_our_power = list()
+
+/**
+ * ••••• Shattering Crescendo - p454
+ *
+ * Most of the low-level Melpominee powers can only be used on one target at a time.
+ * When the Daughter reaches this level of mastery in her Discipline, she can "entertain” a
+ * wider audience. Each member of the audience hears the same message.
+ *
+ * The Siren toggles the ability, augmenting the function of •• Phantom Speaker and •••• Siren's Beckoning
+ *
+ */
+/datum/discipline_power/melpominee/virtuosa
+ name = "Virtuosa"
+ desc = "Augment your abilities, allowing some powers to be used on multiple people."
-/datum/discipline_power/melpominee/sirens_beckoning/deactivate(mob/living/carbon/human/target)
+ level = 5
+ toggled = TRUE
+ check_flags = DISC_CHECK_CONSCIOUS | DISC_CHECK_CAPABLE | DISC_CHECK_IMMOBILE | DISC_CHECK_SPEAK
+
+ vitae_cost = 0
+
+/datum/discipline_power/melpominee/virtuosa/activate()
. = ..()
- target.remove_overlay(MUTATIONS_LAYER)
+ ADD_TRAIT(owner, TRAIT_VIRTUOSA, type)
-//SHATTERING CRESCENDO
-/datum/discipline_power/melpominee/shattering_crescendo
+/datum/discipline_power/melpominee/virtuosa/deactivate(atom/target, direct)
+ . = ..()
+ REMOVE_TRAIT(owner, TRAIT_VIRTUOSA, type)
+
+/**
+ * ••••• • Shattering Crescendo - p454
+ *
+ * The Daughter can sing powerfully enough to rend flesh, split skin, and crack bone. While some Kindred unfortunate enough to witness
+ * this power make reference to the fact that even mortal singers can shatter glass at the right frequency, others note that volume and
+ * intensity don’t seem to matter when a Daughter employs Shattering Crescendo. The Siren can sing a soothing lullaby and still kill a target.
+ *
+ * The Siren selects a target and deals a high amount of damage in brute and to the target's ears.
+ *
+ */
+/datum/discipline_power/melpominee/death_of_the_drum
name = "Shattering Crescendo"
desc = "Scream at an unnatural pitch, shattering the bodies of your enemies."
- level = 5
+ level = 6
check_flags = DISC_CHECK_CONSCIOUS | DISC_CHECK_CAPABLE | DISC_CHECK_IMMOBILE | DISC_CHECK_SPEAK
+ target_type = TARGET_MOB
- effect_sound = 'modular_darkpack/modules/deprecated/sounds/killscream.ogg'
+ effect_sound = 'modular_darkpack/modules/powers/sounds/melpominee/banshee.ogg'
- duration_length = 2 SECONDS
- cooldown_length = 7.5 SECONDS
- duration_override = TRUE
+ range = 7
+ duration_length = 1 TURNS
+ cooldown_length = 3 TURNS
-/datum/discipline_power/melpominee/shattering_crescendo/activate()
+/datum/discipline_power/melpominee/death_of_the_drum/activate()
. = ..()
for(var/mob/living/carbon/human/listener in oviewers(DEFAULT_SIGHT_DISTANCE, owner))
- listener.Stun(2 SECONDS)
- listener.apply_damage(50, BRUTE, BODY_ZONE_HEAD)
+ listener.Stun(1 TURNS)
+ switch(listener.get_ear_protection(TRUE))
+ if(0)
+ listener.apply_damage(50, AGGRAVATED, BODY_ZONE_HEAD)
+ listener.sound_damage(50, 3 TURNS)
+ if(1)
+ listener.apply_damage(25, AGGRAVATED, BODY_ZONE_HEAD)
+ listener.sound_damage(25, 10 TURNS)
+ if(2)
+ listener.apply_damage(15, AGGRAVATED, BODY_ZONE_HEAD)
+
listener.remove_overlay(MUTATIONS_LAYER)
var/mutable_appearance/song_overlay = mutable_appearance('modular_darkpack/modules/deprecated/icons/icons.dmi', "song", -MUTATIONS_LAYER)
listener.overlays_standing[MUTATIONS_LAYER] = song_overlay
listener.apply_overlay(MUTATIONS_LAYER)
- addtimer(CALLBACK(src, PROC_REF(deactivate), listener), 2 SECONDS)
+ addtimer(CALLBACK(src, PROC_REF(deactivate), listener), 1 TURNS)
-/datum/discipline_power/melpominee/shattering_crescendo/deactivate(mob/living/carbon/human/target)
+/datum/discipline_power/melpominee/death_of_the_drum/deactivate(mob/living/carbon/human/target)
. = ..()
target.remove_overlay(MUTATIONS_LAYER)
diff --git a/modular_darkpack/modules/powers/sounds/melpominee/banshee.ogg b/modular_darkpack/modules/powers/sounds/melpominee/banshee.ogg
new file mode 100644
index 000000000000..f3aea4800841
Binary files /dev/null and b/modular_darkpack/modules/powers/sounds/melpominee/banshee.ogg differ
diff --git a/modular_darkpack/modules/deprecated/sounds/melpominee.ogg b/modular_darkpack/modules/powers/sounds/melpominee/melpominee.ogg
similarity index 100%
rename from modular_darkpack/modules/deprecated/sounds/melpominee.ogg
rename to modular_darkpack/modules/powers/sounds/melpominee/melpominee.ogg
diff --git a/modular_darkpack/modules/quirks/code/negative_quirks/derangement.dm b/modular_darkpack/modules/quirks/code/negative_quirks/derangement.dm
index 494bea3a29eb..782c19b1e768 100644
--- a/modular_darkpack/modules/quirks/code/negative_quirks/derangement.dm
+++ b/modular_darkpack/modules/quirks/code/negative_quirks/derangement.dm
@@ -21,6 +21,8 @@
derangements = subtypesof(/datum/hallucination/malk)
/datum/quirk/derangement/process(seconds_per_tick)
+ if(!client)
+ return
if(!COOLDOWN_FINISHED(src, next_process))
return
if(SPT_PROB(2, seconds_per_tick))
diff --git a/modular_darkpack/modules/vampire_the_masquerade/code/vampire_clan/clans/doc.dm b/modular_darkpack/modules/vampire_the_masquerade/code/vampire_clan/clans/doc.dm
index 888cebebe4b5..7e12efdb195f 100644
--- a/modular_darkpack/modules/vampire_the_masquerade/code/vampire_clan/clans/doc.dm
+++ b/modular_darkpack/modules/vampire_the_masquerade/code/vampire_clan/clans/doc.dm
@@ -6,7 +6,7 @@
curse = "Hear more than should."
clan_disciplines = list(
/datum/discipline/fortitude,
- // /datum/discipline/melpominee,
+ /datum/discipline/melpominee,
/datum/discipline/presence
)
male_clothes = /obj/item/clothing/under/vampire/sexy
diff --git a/tgstation.dme b/tgstation.dme
index 367abcf91a06..f5290bccced9 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -6943,6 +6943,8 @@
#include "modular_darkpack\master_files\code\modules\fishing\sources\subtypes\structures.dm"
#include "modular_darkpack\master_files\code\modules\fishing\sources\subtypes\turfs.dm"
#include "modular_darkpack\master_files\code\modules\hydroponics\grown.dm"
+#include "modular_darkpack\master_files\code\modules\mob\mob_defines.dm"
+#include "modular_darkpack\master_files\code\modules\mob\mob_helpers.dm"
#include "modular_darkpack\master_files\code\modules\mob\dead\observer\observer.dm"
#include "modular_darkpack\master_files\code\modules\mob\living\init_signals.dm"
#include "modular_darkpack\master_files\code\modules\mob\living\living.dm"
@@ -7383,6 +7385,7 @@
#include "modular_darkpack\modules\powers\code\discipline\__discipline_power.dm"
#include "modular_darkpack\modules\powers\code\discipline\animalism.dm"
#include "modular_darkpack\modules\powers\code\discipline\dementation.dm"
+#include "modular_darkpack\modules\powers\code\discipline\melpominee.dm"
#include "modular_darkpack\modules\powers\code\discipline\necromancy.dm"
#include "modular_darkpack\modules\powers\code\discipline\obtenebration.dm"
#include "modular_darkpack\modules\powers\code\discipline\serpentis.dm"