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"