From 3ce34703a4df1ed7bd01d9df91916641f39cf660 Mon Sep 17 00:00:00 2001 From: Ava Date: Sun, 5 Mar 2023 02:08:25 -0500 Subject: [PATCH 01/35] agony --- code/game/gamemodes/cult/runes/_rune.dm | 118 ++++++++++++++++++ code/game/gamemodes/cult/runes/communicate.dm | 15 +++ polaris.dme | 2 + 3 files changed, 135 insertions(+) create mode 100644 code/game/gamemodes/cult/runes/_rune.dm create mode 100644 code/game/gamemodes/cult/runes/communicate.dm diff --git a/code/game/gamemodes/cult/runes/_rune.dm b/code/game/gamemodes/cult/runes/_rune.dm new file mode 100644 index 00000000000..75bba54f855 --- /dev/null +++ b/code/game/gamemodes/cult/runes/_rune.dm @@ -0,0 +1,118 @@ +/obj/effect/newrune + name = "rune" + desc = "A strange collection of symbols drawn in what looks to be blood." + icon = 'icons/obj/rune.dmi' + icon_state = "1" + anchored = TRUE + unacidable = TRUE + layer = TURF_LAYER + + /// The actual invocation spoken by each cultist activating this rune. + var/invocation + /// If true, cultists invoking this rune will whisper, instead of speaking normally. + var/whispered + /// AIs see runes as blood splatters. This variable tracks the image shown in the rune's place. + var/image/blood_image + /// How many cultists need to be adjacent to this rune and able to speak in order to activate it. + var/required_invokers = 1 + /// The actual name of this rune (like "Sacrifice", "Convert", or so on), shown to cultists or ghosts that examine it. + var/rune_name + /// As `rune_name`, but for a description of what the rune actually does. + var/rune_desc + +/obj/effect/newrune/Initialize() + . = ..() + blood_image = image(loc = src) + blood_image.override = TRUE + for (var/mob/living/silicon/ai/AI in player_list) + AI.client?.images += blood_image + update_icon() + +/obj/effect/newrune/Destroy() + for (var/mob/living/silicon/ai/AI in player_list) + AI.client?.images -= blood_image + QDEL_NULL(blood_image) + ..() + +/obj/effect/newrune/examine(mob/user) + . = ..() + if (iscultist(user) || isobserver(user)) + if (rune_name) + . += SPAN_OCCULT("This is a [rune_name] rune.") + if (rune_desc) + . += SPAN_OCCULT(rune_desc) + +/obj/effect/newrune/attackby(obj/item/I, mob/user) + if (istype(I, /obj/item/book/tome) && iscultist(user)) + to_chat(user, SPAN_NOTICE("You retrace your steps, carefully undoing the lines of \the [src].")) + qdel(src) + return + else if (istype(I, /obj/item/nullrod)) + to_chat(user, SPAN_NOTICE("The scratchings lose coherence and dissolve into puddles of blood that quickly sizzle and disappear.")) + qdel(src) + return + else if (istype(I, /obj/item/soap) || istype(I, /obj/item/mop)) + to_chat(user, SPAN_WARNING("No matter how hard you try, the scratchings just won't seem to come out.")) + return + +/obj/effect/newrune/attack_hand(mob/living/user) + if (can_contribute(user, FALSE)) + do_invocation(user) + +/// Type-specific checks to check if this rune can be invoked or not. Always returns `TRUE` unless overridden. +/obj/effect/newrune/proc/can_invoke(mob/living/invoker) + return TRUE + +/** + * Checks if a given mob can participate as an invoker for this rune. + * + * By default, a mob must be a human, a cultist, and able to speak. + * Arguments: + * * `invoker` - The mob being checked as a possible contributor + * * `silent` - If non-true, shows an error message to the mob being checked. Defaults to `TRUE` + */ +/obj/effect/newrune/proc/can_contribute(mob/living/invoker, silent = TRUE) + var/fail_message + if (!ishuman(invoker)) + return + var/mob/living/carbon/human/H = invoker + if (!iscultist(H)) + fail_message = "You can't mouth the arcane scratchings without fumbling over them." + else if (H.is_muzzled() || H.silent || (H.sdisabilities & MUTE)) + fail_message = "You can't speak the words of \the [src]." + if (fail_message) + if (!silent) + to_chat(H, SPAN_WARNING(fail_message)) + return + return TRUE + +/// This proc holds the logic through which invocation is attempted and handled, and shouldn't be overridden. +/// If `can_invoke()` is `TRUE` and the rune has enough possible contributors, the rune's `invoke()` is called. +/obj/effect/newrune/proc/do_invocation(mob/living/user) + if (!can_invoke(user)) + fizzle() + return + var/list/invokers = list() + invokers += user + for (var/mob/living/L in range(1, src) - user) + if (invokers.len >= required_invokers) + break + else if (can_contribute(L)) + invokers.Add(L) + if (invokers.len < required_invokers) + fizzle() + return + if (invocation) + for (var/mob/living/L in invokers) + !whispered ? L.say(invocation) : L.whisper(invocation) + invoke(invokers) + +/// Short message shown to all observers representing a failed attempt to use the rune. +/// Clarifying messages describing *why* an attempt failed should go in `can_invoke()` or `invoke()` if possible, and not here. +/obj/effect/newrune/proc/fizzle() + visible_message(SPAN_WARNING("The markings pulse with a small burst of light, then fall dark.")) + +/// This is what you want to override for each type. The actual effects of a rune (converting someone, teleporting the invoker, etc) should be handled here. +/// To reference the person who triggered the activation of the rune, use `invokers[1]`. +/obj/effect/newrune/proc/invoke(list/invokers) + fizzle() diff --git a/code/game/gamemodes/cult/runes/communicate.dm b/code/game/gamemodes/cult/runes/communicate.dm new file mode 100644 index 00000000000..6fe57a1aa9c --- /dev/null +++ b/code/game/gamemodes/cult/runes/communicate.dm @@ -0,0 +1,15 @@ +/obj/effect/newrune/communicate + rune_name = "Communicate" + rune_desc = "Allows you to communicate with other cultists." + invocation = "O bidai nabora se'sma!" + +/obj/effect/newrune/communicate/invoke(list/invokers) + var/mob/living/L = invokers[1] + var/input = input(L, "Please choose a message to tell to the other acolytes.", "Voice of Blood", "") as null|text + if (!input || !CanInteract(L, physical_state)) + return + input = sanitize(input) + log_and_message_admins("used a communicate rune to say '[input]'", usr) + for (var/mob/M in player_list) + if (iscultist(M) || isobserver(M)) + to_chat(M, SPAN_OCCULT(input)) diff --git a/polaris.dme b/polaris.dme index 69b9e621505..6b9d8e14aad 100644 --- a/polaris.dme +++ b/polaris.dme @@ -537,6 +537,8 @@ #include "code\game\gamemodes\cult\cultify\mob.dm" #include "code\game\gamemodes\cult\cultify\obj.dm" #include "code\game\gamemodes\cult\cultify\turf.dm" +#include "code\game\gamemodes\cult\runes\_rune.dm" +#include "code\game\gamemodes\cult\runes\communicate.dm" #include "code\game\gamemodes\endgame\endgame.dm" #include "code\game\gamemodes\endgame\supermatter_cascade\blob.dm" #include "code\game\gamemodes\endgame\supermatter_cascade\portal.dm" From 1fee53a643132edcff2b5ee74c64244ae10f1049 Mon Sep 17 00:00:00 2001 From: Ava Date: Sun, 5 Mar 2023 04:09:49 -0500 Subject: [PATCH 02/35] a --- code/__defines/gamemode.dm | 28 +++++- code/game/gamemodes/cult/runes/_rune.dm | 14 +-- code/game/gamemodes/cult/runes/_rune_icon.dm | 8 ++ code/game/gamemodes/cult/runes/communicate.dm | 1 + code/game/gamemodes/cult/runes/convert.dm | 85 +++++++++++++++++++ code/game/gamemodes/cult/runes/sacrifice.dm | 3 + code/game/gamemodes/cult/runes/summon_tome.dm | 10 +++ code/modules/antagonist/antagonist_create.dm | 2 +- code/modules/antagonist/station/cultist.dm | 18 ++++ polaris.dme | 4 + 10 files changed, 163 insertions(+), 10 deletions(-) create mode 100644 code/game/gamemodes/cult/runes/_rune_icon.dm create mode 100644 code/game/gamemodes/cult/runes/convert.dm create mode 100644 code/game/gamemodes/cult/runes/sacrifice.dm create mode 100644 code/game/gamemodes/cult/runes/summon_tome.dm diff --git a/code/__defines/gamemode.dm b/code/__defines/gamemode.dm index 6d1755f27a8..491d8fd5d56 100644 --- a/code/__defines/gamemode.dm +++ b/code/__defines/gamemode.dm @@ -166,11 +166,35 @@ var/global/list/be_special_flags = list( /* Changeling Defines -*/ +*/ #define CHANGELING_POWER_INHERENT "Inherent" #define CHANGELING_POWER_ARMOR "Armor" #define CHANGELING_POWER_STINGS "Stings" #define CHANGELING_POWER_SHRIEKS "Shrieks" #define CHANGELING_POWER_HEALTH "Health" #define CHANGELING_POWER_ENHANCEMENTS "Enhancements" -#define CHANGELING_POWER_WEAPONS "Weapons" \ No newline at end of file +#define CHANGELING_POWER_WEAPONS "Weapons" + +// English cult words. Each one should represent a concept. +#define CULT_WORD_BLOOD "blood" +#define CULT_WORD_DESTROY "destroy" +#define CULT_WORD_HELL "hell" +#define CULT_WORD_HIDE "hide" +#define CULT_WORD_JOIN "join" +#define CULT_WORD_OTHER "other" +#define CULT_WORD_SELF "self" +#define CULT_WORD_SEE "see" +#define CULT_WORD_TECHNOLOGY "technology" +#define CULT_WORD_TRAVEL "travel" + +// Culty cult words. These are gibberish and can basically be whatever. +#define CULT_WORD_BALAQ "balaq" +#define CULT_WORD_CERTUM "certum" +#define CULT_WORD_EGO "ego" +#define CULT_WORD_GEERI "geeri" +#define CULT_WORD_IRE "ire" +#define CULT_WORD_KARAZET "karazet" +#define CULT_WORD_JATKAA "jatkaa" +#define CULT_WORD_MGAR "mgar" +#define CULT_WORD_NAHLIZET "nahlizet" +#define CULT_WORD_VERI "veri" diff --git a/code/game/gamemodes/cult/runes/_rune.dm b/code/game/gamemodes/cult/runes/_rune.dm index 75bba54f855..0017ecbaaf2 100644 --- a/code/game/gamemodes/cult/runes/_rune.dm +++ b/code/game/gamemodes/cult/runes/_rune.dm @@ -11,6 +11,8 @@ var/invocation /// If true, cultists invoking this rune will whisper, instead of speaking normally. var/whispered + /// A list of "words" used to form this rune. + var/list/circle_words /// AIs see runes as blood splatters. This variable tracks the image shown in the rune's place. var/image/blood_image /// How many cultists need to be adjacent to this rune and able to speak in order to activate it. @@ -34,13 +36,11 @@ QDEL_NULL(blood_image) ..() -/obj/effect/newrune/examine(mob/user) - . = ..() - if (iscultist(user) || isobserver(user)) - if (rune_name) - . += SPAN_OCCULT("This is a [rune_name] rune.") - if (rune_desc) - . += SPAN_OCCULT(rune_desc) +/obj/effect/newrune/get_examine_desc() + if ((isobserver(usr) || iscultist(usr)) && rune_name && rune_desc) + return SPAN_OCCULT("This is a [rune_name] rune.
[rune_desc]") + else + return desc /obj/effect/newrune/attackby(obj/item/I, mob/user) if (istype(I, /obj/item/book/tome) && iscultist(user)) diff --git a/code/game/gamemodes/cult/runes/_rune_icon.dm b/code/game/gamemodes/cult/runes/_rune_icon.dm new file mode 100644 index 00000000000..6cfad067fcc --- /dev/null +++ b/code/game/gamemodes/cult/runes/_rune_icon.dm @@ -0,0 +1,8 @@ +/obj/effect/newrune/update_icon() + assemble_icon() + . = ..() + +/obj/effect/newrune/proc/assemble_icon() + var/vocab = cult?.vocabulary + if (circle_words.len == 3 && vocab) + icon = get_uristrune_cult(vocab[circle_words[1]], vocab[circle_words[2]], vocab[circle_words[3]]) diff --git a/code/game/gamemodes/cult/runes/communicate.dm b/code/game/gamemodes/cult/runes/communicate.dm index 6fe57a1aa9c..d11013f7c0d 100644 --- a/code/game/gamemodes/cult/runes/communicate.dm +++ b/code/game/gamemodes/cult/runes/communicate.dm @@ -1,6 +1,7 @@ /obj/effect/newrune/communicate rune_name = "Communicate" rune_desc = "Allows you to communicate with other cultists." + circle_words = list(CULT_WORD_SELF, CULT_WORD_OTHER, CULT_WORD_TECHNOLOGY) invocation = "O bidai nabora se'sma!" /obj/effect/newrune/communicate/invoke(list/invokers) diff --git a/code/game/gamemodes/cult/runes/convert.dm b/code/game/gamemodes/cult/runes/convert.dm new file mode 100644 index 00000000000..144200a71e9 --- /dev/null +++ b/code/game/gamemodes/cult/runes/convert.dm @@ -0,0 +1,85 @@ +/obj/effect/newrune/convert + rune_name = "Convert" + rune_desc = "A ubiquitous incantation, necessary to educate the innocent. Exposing a nonbeliever's mind to Nar-Sie will typically convert them, but stubborn or resilient individuals may be able to resist Its influence until they succumb or are overwhelmed by the revelation. Some people - typically those possessed of high authority - are able to resist Nar-Sie's influence and will entirely refuse to abandon their old beliefs." + circle_words = list(CULT_WORD_JOIN, CULT_WORD_BLOOD, CULT_WORD_SELF) + invocation = "Mah'weyh pleggh at e'ntrath!" + var/mob/living/converting + var/waiting_for_input + var/impudence_timer + +/obj/effect/newrune/convert/proc/can_convert(mob/living/victim) + return !iscultist(victim) && victim.client && victim.stat != DEAD && victim.mind + +/obj/effect/newrune/convert/can_invoke(mob/living/invoker) + for (var/mob/living/L in get_turf(src)) + if (can_convert(L)) + return TRUE + return + +/obj/effect/newrune/convert/invoke(list/invokers) + var/mob/living/user = invokers[1] + if (converting) + to_chat(user, SPAN_WARNING("You sense that the Dark One's power is already working away at [converting].")) + return + for (var/mob/living/L in get_turf(src)) + if (can_convert(L)) + converting = L + break + var/datum/gender/G = gender_datums[converting.get_visible_gender()] + converting.visible_message( + SPAN_DANGER("[converting] writhes in agony as the markings below [G.him] glow a sullen, bloody red."), + SPAN_DANGER("AAAAAAHHHH-") + ) + converting.emote("scream") + to_chat(converting, SPAN_OCCULT(FONT_LARGE("Agony invades every corner of your body. Your senses dim, brighten, and dim again. The air swims as if on fire. And as boiling scarlet light bathes your face, you discover that you are not alone in your head."))) + if (!cult.can_become_antag(converting.mind)) // We check this in here instead of can_convert() so that they can be shown to visibly resist the rune's influence + converting = null + converting.visible_message( + SPAN_DANGER("[converting] seems to push away the rune's influence!"), + SPAN_DANGER(FONT_LARGE("...and you're able to force it out of your mind. You need to get away from here as fast as you can!")) + ) + return + punish() + +/obj/effect/newrune/convert/proc/punish(impudence = 0) + if (!converting || !can_convert(converting) || !cult.can_become_antag(converting) || get_turf(converting) != get_turf(src)) + if (converting) + to_chat(converting, SPAN_DANGER("And then, just like that, it was gone. The blackness recedes, and you are yourself again.")) + converting = null + waiting_for_input = FALSE + return + if (!waiting_for_input) + spawn() + waiting_for_input = TRUE + var/choice = alert(converting, "Submit to the presence invading your head?", "Submit to Nar-Sie", "Submit!", "Resist!") + waiting_for_input = FALSE + if (choice == "Submit!") + cult.add_antagonist(converting.mind) + converting = null + converting.hallucination = 0 + to_chat(converting, SPAN_OCCULT("Your blood pulses. Your head throbs. The world goes red. All at once you are aware of a horrible, horrible truth. The veil of reality has been ripped away and in the festering wound left behind something sinister takes root.")) + to_chat(converting, SPAN_OCCULT("Assist your new compatriots in their dark dealings. Their goal is yours, and yours is theirs. You serve the Dark One above all else. Bring It back.")) + if (impudence_timer) + deltimer(impudence_timer) + impudence_timer = null + if (impudence) + converting.take_overall_damage(0, min(5 * impudence, 20)) + switch (converting.getFireLoss()) + if (0 to 25) + to_chat(converting, SPAN_DANGER("You feel like every part of you is on fire as you force yourself to resist the corruption invading every corner of your mind.")) + if(25 to 45) + to_chat(converting, SPAN_DANGER("Everything is so, so hot. It feels like it's inside your body. Inside your soul.")) + if(45 to 75) + to_chat(converting, SPAN_DANGER("Flickering images of a vast, vast, dark thing engulf your vision. Your will is crumbling.")) + converting.apply_effect(rand(1, 10), STUTTER) + if(75 to 100) + to_chat(converting, SPAN_DANGER("You feel like you're being cremated. Images of unspeakable horrors are bombarding the last remnants of your mental resistance.")) + converting.hallucination = min(converting.hallucination + 100, 500) + converting.apply_effect(10, STUTTER) + converting.adjustBrainLoss(1) + if(100 to INFINITY) + to_chat(converting, SPAN_DANGER("You are broken. Everything is on fire. You feel yourself slipping away, drawn towards inexorable nothingness.")) + converting.hallucination = min(converting.hallucination + 100, 500) + converting.apply_effect(15, STUTTER) + converting.adjustBrainLoss(1) + impudence_timer = addtimer(CALLBACK(src, .proc/punish, impudence + 1), 10 SECONDS, TIMER_STOPPABLE) diff --git a/code/game/gamemodes/cult/runes/sacrifice.dm b/code/game/gamemodes/cult/runes/sacrifice.dm new file mode 100644 index 00000000000..77573d1c020 --- /dev/null +++ b/code/game/gamemodes/cult/runes/sacrifice.dm @@ -0,0 +1,3 @@ +/obj/effect/newrune/sacrifice + rune_name = "Sacrifice" + rune_desc = "Offer a living thing or a body to the Geometer of Blood. Living beings require three invokers to sacrifice, while dead ones require only one." diff --git a/code/game/gamemodes/cult/runes/summon_tome.dm b/code/game/gamemodes/cult/runes/summon_tome.dm new file mode 100644 index 00000000000..4c46327741d --- /dev/null +++ b/code/game/gamemodes/cult/runes/summon_tome.dm @@ -0,0 +1,10 @@ +/obj/effect/newrune/summon_tome + rune_name = "Summon Tome" + rune_desc = "Manifests another copy of the Geometer's scripture." + circle_words = list(CULT_WORD_SEE, CULT_WORD_BLOOD, CULT_WORD_HELL) + invocation = "N'ath reth sh'yro eth d'raggathnor!" + +/obj/effect/newrune/summon_tome/invoke(list/invokers) + visible_message(SPAN_WARNING("Space is congealed into a blank book. The runes slither into the pages and drag the cover shut with a hollow thud.")) + new /obj/item/book/tome(get_turf(src)) + qdel(src) diff --git a/code/modules/antagonist/antagonist_create.dm b/code/modules/antagonist/antagonist_create.dm index ae4dc1a671c..df83c8ed4fb 100644 --- a/code/modules/antagonist/antagonist_create.dm +++ b/code/modules/antagonist/antagonist_create.dm @@ -101,7 +101,7 @@ /datum/antagonist/proc/greet(var/datum/mind/player) // Makes it harder to miss if you're alt-tabbed or not paying attention. if(antag_sound) - sound_to(player.current, sound(antag_sound)) + player.current.playsound_local(player.current.loc, antag_sound, 50, is_global = TRUE) window_flash(player.current.client) // Basic intro text. diff --git a/code/modules/antagonist/station/cultist.dm b/code/modules/antagonist/station/cultist.dm index e111513023f..f47677b58cf 100644 --- a/code/modules/antagonist/station/cultist.dm +++ b/code/modules/antagonist/station/cultist.dm @@ -38,9 +38,27 @@ var/global/datum/antagonist/cultist/cult var/list/sacrificed = list() var/list/harvested = list() + /** + * So here's how the cult vocabulary works: + * * There are two lists of words: one contains English words representing concepts ("blood", "other", "technology", etc) while the other are culty gibberish. Both of these lists have the same amount of total words in them. + * * At runtime, each cult word is correlated to a random English word representing its meaning. On one round the word "ego" might mean "technology", but on another it might mean "hell", and so on. + * * This list is populated as an associative list with each English word associated with its cult word counterpart. + * + * The word lists are found in `english_words` and `cult_words` on `/datum/antagonist/cultist`, and are populated from defines to avoid string copy-paste. + */ + var/list/vocabulary + var/list/english_words = list(CULT_WORD_BLOOD, CULT_WORD_DESTROY, CULT_WORD_HELL, CULT_WORD_HIDE, CULT_WORD_JOIN, CULT_WORD_OTHER, CULT_WORD_SELF, CULT_WORD_SEE, CULT_WORD_TECHNOLOGY, CULT_WORD_TRAVEL) + var/list/cult_words = list(CULT_WORD_BALAQ, CULT_WORD_CERTUM, CULT_WORD_EGO, CULT_WORD_GEERI, CULT_WORD_IRE, CULT_WORD_KARAZET, CULT_WORD_JATKAA, CULT_WORD_MGAR, CULT_WORD_NAHLIZET, CULT_WORD_VERI) + /datum/antagonist/cultist/New() ..() cult = src + if (!LAZYLEN(vocabulary)) + var/list/gibberish = cult_words + for (var/eng in english_words) + var/culty = pick(gibberish) + LAZYSET(vocabulary, eng, culty) + gibberish -= culty /datum/antagonist/cultist/create_global_objectives() diff --git a/polaris.dme b/polaris.dme index 6b9d8e14aad..b595e94be21 100644 --- a/polaris.dme +++ b/polaris.dme @@ -538,7 +538,11 @@ #include "code\game\gamemodes\cult\cultify\obj.dm" #include "code\game\gamemodes\cult\cultify\turf.dm" #include "code\game\gamemodes\cult\runes\_rune.dm" +#include "code\game\gamemodes\cult\runes\_rune_icon.dm" #include "code\game\gamemodes\cult\runes\communicate.dm" +#include "code\game\gamemodes\cult\runes\convert.dm" +#include "code\game\gamemodes\cult\runes\sacrifice.dm" +#include "code\game\gamemodes\cult\runes\summon_tome.dm" #include "code\game\gamemodes\endgame\endgame.dm" #include "code\game\gamemodes\endgame\supermatter_cascade\blob.dm" #include "code\game\gamemodes\endgame\supermatter_cascade\portal.dm" From cf1e2f1c089388e55116a68e968789bf14c3ee65 Mon Sep 17 00:00:00 2001 From: Ava Date: Sun, 5 Mar 2023 17:03:01 -0500 Subject: [PATCH 03/35] moar runes --- code/game/gamemodes/cult/runes/_rune.dm | 12 +- code/game/gamemodes/cult/runes/sacrifice.dm | 119 +++++++++++++++++++- code/game/gamemodes/cult/runes/wall.dm | 11 ++ polaris.dme | 1 + 4 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 code/game/gamemodes/cult/runes/wall.dm diff --git a/code/game/gamemodes/cult/runes/_rune.dm b/code/game/gamemodes/cult/runes/_rune.dm index 0017ecbaaf2..a24f0074e5a 100644 --- a/code/game/gamemodes/cult/runes/_rune.dm +++ b/code/game/gamemodes/cult/runes/_rune.dm @@ -59,10 +59,15 @@ if (can_contribute(user, FALSE)) do_invocation(user) -/// Type-specific checks to check if this rune can be invoked or not. Always returns `TRUE` unless overridden. +/// Type-specific check for if this rune can be invoked or not. Always returns `TRUE` unless overridden. /obj/effect/newrune/proc/can_invoke(mob/living/invoker) return TRUE +/// Type-specific check to get required invokers. This lets runes require different invokers under certain conditions. +/// If it's a rune with dynamic requirements, you might benefit from giving that invoker a message explaining why they need more people. +/obj/effect/newrune/proc/get_required_invokers(mob/living/invoker) + return required_invokers + /** * Checks if a given mob can participate as an invoker for this rune. * @@ -94,12 +99,13 @@ return var/list/invokers = list() invokers += user + var/req_invokers = get_required_invokers(user) for (var/mob/living/L in range(1, src) - user) - if (invokers.len >= required_invokers) + if (invokers.len >= req_invokers) break else if (can_contribute(L)) invokers.Add(L) - if (invokers.len < required_invokers) + if (invokers.len < req_invokers) fizzle() return if (invocation) diff --git a/code/game/gamemodes/cult/runes/sacrifice.dm b/code/game/gamemodes/cult/runes/sacrifice.dm index 77573d1c020..4a5f7e345c1 100644 --- a/code/game/gamemodes/cult/runes/sacrifice.dm +++ b/code/game/gamemodes/cult/runes/sacrifice.dm @@ -1,3 +1,120 @@ /obj/effect/newrune/sacrifice rune_name = "Sacrifice" - rune_desc = "Offer a living thing or a body to the Geometer of Blood. Living beings require three invokers to sacrifice, while dead ones require only one." + rune_desc = "Offer a living thing or a body to the Geometer of Blood. Living beings require three invokers to sacrifice, while dead beings require only one." + circle_words = list(CULT_WORD_HELL, CULT_WORD_BLOOD, CULT_WORD_JOIN) + invocation = "Barhah hra zar'garis!" + var/mob/living/sacrificing + +/// Fetches a sacrifice on top of this rune, aiming for the most "valuable" one (by way of species rarity, role, objective, and so on). +/obj/effect/newrune/sacrifice/proc/get_sacrifical_lamb() + var/list/sacrifices + for (var/mob/living/L in get_turf(src)) + var/datum/mind/M = L.mind + if (!iscultist(L) || cult?.sacrifice_target == M) + var/worth = 0 + if (ishuman(L)) + var/mob/living/carbon/human/H = L + if (H.species.rarity_value > 3) + worth++ + if (M) + if (M.assigned_role == "Chaplain") + worth++ + if (cult?.sacrifice_target == M) + worth = 99 // humgry..... + LAZYSET(sacrifices, L, worth) + if (LAZYLEN(sacrifices)) + var/mob/living/worthiest_sacrifice + var/highest_value = -1 + for (var/mob/living/L in shuffle(sacrifices)) + if (sacrifices[L] > highest_value) + worthiest_sacrifice = L + highest_value = sacrifices[L] + return worthiest_sacrifice + return + +/obj/effect/newrune/sacrifice/get_required_invokers(mob/living/invoker) + var/mob/living/L = get_sacrifical_lamb() + if (L?.mind && cult?.sacrifice_target == L.mind) + to_chat(invoker, SPAN_WARNING("This sacrifice's earthly bonds are too strong. You need more invokers to succeed in in presenting them to the Geometer.")) + return 3 + else if (L?.stat != DEAD) + to_chat(invoker, SPAN_WARNING("This sacrifice yet lives. You need more invokers to succeed in in presenting them to the Geometer.")) + return 3 + +/obj/effect/newrune/sacrifice/can_invoke(mob/living/invoker) + if (sacrificing) + to_chat(invoker, SPAN_WARNING("The Geometer is already receiving a sacrifice.")) + return sacrificing == null + +/obj/effect/newrune/sacrifice/invoke(list/invokers) + var/mob/living/L = get_sacrifical_lamb() + if (!L) + return fizzle() + var/datum/gender/G = gender_datums[L.get_visible_gender()] + L.visible_message( + SPAN_DANGER("The runes beneath [L] widen into a gaping void. Something yanks [G.him] in before they snap shut."), + !iscultist(L) ? SPAN_DANGER("You are dragged into the runes as they widen into a gaping maw!") : SPAN_OCCULT("Yes! Yes! The Geometer accepts you!") + ) + if (sacrificing.mind && cult.sacrifice_target == sacrificing.mind) + for (var/mob/living/C in invokers) + to_chat(C, SPAN_OCCULT("The Geometer of Blood is sated. Your objective is now complete.")) + sacrificed += sacrificing + else + for (var/mob/living/C in invokers) + to_chat(C, SPAN_OCCULT("The Geometer of Blood feasts on your sacrifice. You have pleased It.")) + if (isrobot(sacrificing)) // Prevent the MMI from surviving + sacrificing.dust() + else + sacrificing.gib() + + + + /*// let's get proper ghoulish + sacrificing.forceMove(src) + for (var/i = 0; i < 10; i++) + if (!sacrificing) + break + sacrificing.take_overall_damage(rand(30, 40), used_weapon = "massive bite marks") + for (var/obj/item/organ/external/E in sacrificing.organs) + if (prob(5)) + E.droplimb(FALSE, pick(DROPLIMB_BLUNT)) + playsound(src, 'sound/effects/squelch1.ogg', 50, TRUE) + sleep (0.5 SECONDS) + if (sacrificing) + var/turf/T = get_turf(src) + if (destroy_body) + visible_message(SPAN_WARNING("Ragged remains and fluids seep from the sigils. There is no sign of a body.")) + sacrificing.gib() + for (var/obj/O in src) + if (isorgan(O)) + var/obj/item/organ/OR = O + if (OR.vital) + qdel(OR) + continue + else + OR.take_damage(round(rand(OR.health / 1.5, OR.health)), TRUE) + O.forceMove(T) + if (prob(75)) + step(O, pick(cardinal)) + else + sacrificing.forceMove(get_turf(src)) + sacrificing.set_dir(NORTH) // facedown + sacrificing.adjustBruteLoss(666) + gibs(T, sacrificing.dna) + visible_message(SPAN_WARNING("[sacrificing] is slowly pushed through the sigils. There is little left of [G.his] body.")) + for (var/obj/item/organ/O in sacrificing.organs) + if (prob(25) || O == sacrificing.get_organ(BP_HEAD)) + if (istype(O, /obj/item/organ/external)) + var/obj/item/organ/external/E = O + E.droplimb(FALSE, DROPLIMB_EDGE) + else + sacrificing.rip_out_internal_organ(O) + if (prob(50)) + qdel(O) + else if (prob(75)) + step(O, pick(cardinal)) + playsound(src, 'sound/effects/splat.ogg', 75, TRUE, frequency = 20000) + for (var/mob/M in view(src)) + shake_camera(M, 3, 1) + sacrificing = null + qdel(src)*/ diff --git a/code/game/gamemodes/cult/runes/wall.dm b/code/game/gamemodes/cult/runes/wall.dm new file mode 100644 index 00000000000..a0cbe048a04 --- /dev/null +++ b/code/game/gamemodes/cult/runes/wall.dm @@ -0,0 +1,11 @@ +/obj/effect/newrune/wall + rune_name = "Wall" + rune_desc = "Invoking this rune solidifies the air above it, creating an an invisible wall. Invoke the rune again to bring the barrier down." + circle_words = list(CULT_WORD_DESTROY, CULT_WORD_TRAVEL, CULT_WORD_SELF) + invocation = "Khari'd! Eske'te tannin!" + +/obj/effect/newrune/wall/invoke(list/invokers) + var/mob/living/L = invokers[1] + density = !density + L.take_organ_damage(2, 0) + to_chat(L, SPAN_DANGER("Your blood flows into the rune, and you feel [density ? "the very space above it thicken" : "it release its grasp on space"].")) diff --git a/polaris.dme b/polaris.dme index b595e94be21..b58819d85cf 100644 --- a/polaris.dme +++ b/polaris.dme @@ -543,6 +543,7 @@ #include "code\game\gamemodes\cult\runes\convert.dm" #include "code\game\gamemodes\cult\runes\sacrifice.dm" #include "code\game\gamemodes\cult\runes\summon_tome.dm" +#include "code\game\gamemodes\cult\runes\wall.dm" #include "code\game\gamemodes\endgame\endgame.dm" #include "code\game\gamemodes\endgame\supermatter_cascade\blob.dm" #include "code\game\gamemodes\endgame\supermatter_cascade\portal.dm" From 0724b6f1c65000dbad7a4f046e8d7ab3d7e80b59 Mon Sep 17 00:00:00 2001 From: Ava Date: Sun, 5 Mar 2023 19:06:02 -0500 Subject: [PATCH 04/35] tome --- code/game/gamemodes/cult/arcane_tome.dm | 100 ++++++++++++++++++ code/game/gamemodes/cult/runes/_rune.dm | 4 +- code/game/gamemodes/cult/runes/communicate.dm | 1 + code/game/gamemodes/cult/runes/convert.dm | 1 + code/game/gamemodes/cult/runes/sacrifice.dm | 2 + code/game/gamemodes/cult/runes/summon_tome.dm | 3 +- code/game/gamemodes/cult/runes/wall.dm | 1 + polaris.dme | 1 + tgui/packages/tgui/interfaces/ArcaneTome.js | 56 ++++++++++ 9 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 code/game/gamemodes/cult/arcane_tome.dm create mode 100644 tgui/packages/tgui/interfaces/ArcaneTome.js diff --git a/code/game/gamemodes/cult/arcane_tome.dm b/code/game/gamemodes/cult/arcane_tome.dm new file mode 100644 index 00000000000..89ca7c9900b --- /dev/null +++ b/code/game/gamemodes/cult/arcane_tome.dm @@ -0,0 +1,100 @@ +/obj/item/arcane_tome + name = "arcane tome" + desc = "An old, dusty tome with frayed edges and a sinister-looking cover." + icon = 'icons/obj/weapons.dmi' + item_icons = list( + icon_l_hand = 'icons/mob/items/lefthand_books.dmi', + icon_r_hand = 'icons/mob/items/righthand_books.dmi', + ) + icon_state = "tome" + item_state = "tome" + throw_speed = 1 + throw_range = 5 + w_class = ITEMSIZE_SMALL + drop_sound = 'sound/items/drop/book.ogg' + pickup_sound = 'sound/items/pickup/book.ogg' + +/obj/item/arcane_tome/get_examine_desc() + if (iscultist(usr) || isobserver(usr)) + return "The scriptures of Nar-Sie, The One Who Sees, The Geometer of Blood. Contains the details of every ritual its followers could think of." + else + return desc + +/obj/item/arcane_tome/attack_self(mob/user) + if (!iscultist(user)) + to_chat(user, "The book is full of illegible scribbles and crudely-drawn shapes. Is this a joke...?") + tgui_interact(user) + +/obj/item/arcane_tome/tgui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui) + if (!iscultist(user)) + return + ui = SStgui.try_update_ui(user, src, ui) + if (!ui) + ui = new(user, src, "ArcaneTome", name) + ui.open() + playsound(user, 'sound/bureaucracy/bookopen.ogg', 25, TRUE) + +/obj/item/arcane_tome/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state) + var/list/data = list() + + var/list/rune_data = list() + for (var/V in subtypesof(/obj/effect/newrune)) + var/obj/effect/newrune/NR = V + rune_data += list(list( + "name" = initial(NR.rune_name), + "shorthand" = initial(NR.rune_shorthand), + "typepath" = NR + )) + data["runes"] = rune_data + + return data + +/obj/item/arcane_tome/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state) + if (..()) + return TRUE + switch (action) + if ("turnPage") + playsound(src, "pageturn", 25, TRUE) + if ("writeRune") + var/obj/effect/newrune/R = text2path(params["runePath"]) + if (!ispath(R, /obj/effect/newrune)) + return + scribe_rune(usr, R) + return + +/obj/item/arcane_tome/attack(mob/living/M, mob/living/user, target_zone, attack_modifier) + // This is basically a reimplementation of weapon logic for cultists only + // It is far from ideal, but it's what we got. Having it always do damage but only letting cultists attack with it + // would mean that it could be used to smash lights and windows etc, and we only want it to work on living things + if (iscultist(user) && !iscultist(M) && user.a_intent == I_HURT) + user.break_cloak() + user.setClickCooldown(user.get_attack_speed(src)) + user.do_attack_animation(M) + var/hit_zone = M.resolve_item_attack(src, user, target_zone) + if(hit_zone) + user.visible_message( + SPAN_DANGER("You burn \the [M] with \the [src]!"), + SPAN_DANGER("\The [user] bathes \the [M] in red light from \the [src]'s cover!") + ) + to_chat(M, SPAN_DANGER("You feel a searing heat inside!")) + playsound(src, 'sound/weapons/sear.ogg', 50, TRUE, -1) + M.apply_damage(rand(5, 20), BURN, hit_zone, used_weapon = "internal burns") + return + . = ..() + +/obj/item/arcane_tome/proc/scribe_rune(mob/living/user, obj/effect/rune_type) + var/datum/gender/G = gender_datums[user.get_visible_gender()] + user.visible_message( + SPAN_WARNING("\The [user] slices open [G.his] skin and begins painting on symbols on the floor with [G.his] own blood!"), + SPAN_DANGER("You slice open your skin and begin drawing a rune on the floor whilst invoking the ritual that binds your life essence with the dark arcane energies flowing through the surrounding world."), + SPAN_WARNING("You hear droplets softly splattering on the ground."), + range = 3) + user.apply_damage(1, BRUTE) + if (!do_after(user, 5 SECONDS)) + return + user.visible_message( + SPAN_WARNING("\The [user] paints arcane markings with [G.his] own blood!"), + SPAN_DANGER("You finish drawing the arcane markings of the Geometer."), + range = 3 + ) + new rune_type (get_turf(src)) diff --git a/code/game/gamemodes/cult/runes/_rune.dm b/code/game/gamemodes/cult/runes/_rune.dm index a24f0074e5a..4e80bc93f00 100644 --- a/code/game/gamemodes/cult/runes/_rune.dm +++ b/code/game/gamemodes/cult/runes/_rune.dm @@ -19,6 +19,8 @@ var/required_invokers = 1 /// The actual name of this rune (like "Sacrifice", "Convert", or so on), shown to cultists or ghosts that examine it. var/rune_name + /// Very short description of the rune's functionality, to be shown as a tooltip in the tome. + var/rune_shorthand /// As `rune_name`, but for a description of what the rune actually does. var/rune_desc @@ -43,7 +45,7 @@ return desc /obj/effect/newrune/attackby(obj/item/I, mob/user) - if (istype(I, /obj/item/book/tome) && iscultist(user)) + if (istype(I, /obj/item/arcane_tome) && iscultist(user)) to_chat(user, SPAN_NOTICE("You retrace your steps, carefully undoing the lines of \the [src].")) qdel(src) return diff --git a/code/game/gamemodes/cult/runes/communicate.dm b/code/game/gamemodes/cult/runes/communicate.dm index d11013f7c0d..86425b12597 100644 --- a/code/game/gamemodes/cult/runes/communicate.dm +++ b/code/game/gamemodes/cult/runes/communicate.dm @@ -1,6 +1,7 @@ /obj/effect/newrune/communicate rune_name = "Communicate" rune_desc = "Allows you to communicate with other cultists." + rune_shorthand = "Allows silent communication with other followers." circle_words = list(CULT_WORD_SELF, CULT_WORD_OTHER, CULT_WORD_TECHNOLOGY) invocation = "O bidai nabora se'sma!" diff --git a/code/game/gamemodes/cult/runes/convert.dm b/code/game/gamemodes/cult/runes/convert.dm index 144200a71e9..113dd785262 100644 --- a/code/game/gamemodes/cult/runes/convert.dm +++ b/code/game/gamemodes/cult/runes/convert.dm @@ -1,6 +1,7 @@ /obj/effect/newrune/convert rune_name = "Convert" rune_desc = "A ubiquitous incantation, necessary to educate the innocent. Exposing a nonbeliever's mind to Nar-Sie will typically convert them, but stubborn or resilient individuals may be able to resist Its influence until they succumb or are overwhelmed by the revelation. Some people - typically those possessed of high authority - are able to resist Nar-Sie's influence and will entirely refuse to abandon their old beliefs." + rune_shorthand = "Attempts to convert a nonbeliever to the fold." circle_words = list(CULT_WORD_JOIN, CULT_WORD_BLOOD, CULT_WORD_SELF) invocation = "Mah'weyh pleggh at e'ntrath!" var/mob/living/converting diff --git a/code/game/gamemodes/cult/runes/sacrifice.dm b/code/game/gamemodes/cult/runes/sacrifice.dm index 4a5f7e345c1..6b031388bff 100644 --- a/code/game/gamemodes/cult/runes/sacrifice.dm +++ b/code/game/gamemodes/cult/runes/sacrifice.dm @@ -1,6 +1,7 @@ /obj/effect/newrune/sacrifice rune_name = "Sacrifice" rune_desc = "Offer a living thing or a body to the Geometer of Blood. Living beings require three invokers to sacrifice, while dead beings require only one." + rune_shorthand = "Offers a creature to the Geometer for consumption." circle_words = list(CULT_WORD_HELL, CULT_WORD_BLOOD, CULT_WORD_JOIN) invocation = "Barhah hra zar'garis!" var/mob/living/sacrificing @@ -55,6 +56,7 @@ SPAN_DANGER("The runes beneath [L] widen into a gaping void. Something yanks [G.him] in before they snap shut."), !iscultist(L) ? SPAN_DANGER("You are dragged into the runes as they widen into a gaping maw!") : SPAN_OCCULT("Yes! Yes! The Geometer accepts you!") ) + sacrificing = L if (sacrificing.mind && cult.sacrifice_target == sacrificing.mind) for (var/mob/living/C in invokers) to_chat(C, SPAN_OCCULT("The Geometer of Blood is sated. Your objective is now complete.")) diff --git a/code/game/gamemodes/cult/runes/summon_tome.dm b/code/game/gamemodes/cult/runes/summon_tome.dm index 4c46327741d..db85c04189e 100644 --- a/code/game/gamemodes/cult/runes/summon_tome.dm +++ b/code/game/gamemodes/cult/runes/summon_tome.dm @@ -1,10 +1,11 @@ /obj/effect/newrune/summon_tome rune_name = "Summon Tome" rune_desc = "Manifests another copy of the Geometer's scripture." + rune_shorthand = "Creates a new arcane tome." circle_words = list(CULT_WORD_SEE, CULT_WORD_BLOOD, CULT_WORD_HELL) invocation = "N'ath reth sh'yro eth d'raggathnor!" /obj/effect/newrune/summon_tome/invoke(list/invokers) visible_message(SPAN_WARNING("Space is congealed into a blank book. The runes slither into the pages and drag the cover shut with a hollow thud.")) - new /obj/item/book/tome(get_turf(src)) + new /obj/item/arcane_tome(get_turf(src)) qdel(src) diff --git a/code/game/gamemodes/cult/runes/wall.dm b/code/game/gamemodes/cult/runes/wall.dm index a0cbe048a04..6c542b8623b 100644 --- a/code/game/gamemodes/cult/runes/wall.dm +++ b/code/game/gamemodes/cult/runes/wall.dm @@ -1,6 +1,7 @@ /obj/effect/newrune/wall rune_name = "Wall" rune_desc = "Invoking this rune solidifies the air above it, creating an an invisible wall. Invoke the rune again to bring the barrier down." + rune_shorthand = "Forms a reversible solid barrier when invoked." circle_words = list(CULT_WORD_DESTROY, CULT_WORD_TRAVEL, CULT_WORD_SELF) invocation = "Khari'd! Eske'te tannin!" diff --git a/polaris.dme b/polaris.dme index b58819d85cf..c62a372b584 100644 --- a/polaris.dme +++ b/polaris.dme @@ -524,6 +524,7 @@ #include "code\game\gamemodes\changeling\powers\silence_sting.dm" #include "code\game\gamemodes\changeling\powers\transform.dm" #include "code\game\gamemodes\changeling\powers\visible_camouflage.dm" +#include "code\game\gamemodes\cult\arcane_tome.dm" #include "code\game\gamemodes\cult\construct_spells.dm" #include "code\game\gamemodes\cult\cult.dm" #include "code\game\gamemodes\cult\cult_items.dm" diff --git a/tgui/packages/tgui/interfaces/ArcaneTome.js b/tgui/packages/tgui/interfaces/ArcaneTome.js new file mode 100644 index 00000000000..bb44a1e150e --- /dev/null +++ b/tgui/packages/tgui/interfaces/ArcaneTome.js @@ -0,0 +1,56 @@ +import { Fragment } from 'inferno'; +import { useBackend, useLocalState } from '../backend'; +import { Box, Button, Dropdown, Flex, Icon, Input, Modal, Section, Tabs } from '../components'; +import { Window } from '../layouts'; + +export const ArcaneTome = (props, context) => { + const { act, data } = useBackend(context); + const [tabIndex, setTabIndex] = useLocalState(context, 'tabIndex', 0); + + return ( + + + + { act("turnPage"), setTabIndex(0) }}> + Archives + + { act("turnPage"), setTabIndex(1) }}> + Runes + + +
+ { + tabIndex === 0 && + ("TBD") + || + tabIndex === 1 && + ( + + {data.runes.map(entry => ( + +
+
+
+ ); +}; From ab0f10da253fb9ee5eea6ae84e096fedf76c07be Mon Sep 17 00:00:00 2001 From: Ava Date: Sun, 5 Mar 2023 21:10:14 -0500 Subject: [PATCH 05/35] SURELY I'll work on this later --- code/game/gamemodes/cult/runes/deafen.dm | 14 +++ code/game/gamemodes/cult/runes/emp.dm | 14 +++ code/game/gamemodes/cult/runes/raise_dead.dm | 102 +++++++++++++++++++ code/game/gamemodes/cult/runes/stun.dm | 18 ++++ polaris.dme | 3 + tgui/packages/tgui/interfaces/ArcaneTome.js | 15 ++- tgui/packages/tgui/public/tgui.bundle.js | 4 +- 7 files changed, 160 insertions(+), 10 deletions(-) create mode 100644 code/game/gamemodes/cult/runes/deafen.dm create mode 100644 code/game/gamemodes/cult/runes/emp.dm create mode 100644 code/game/gamemodes/cult/runes/raise_dead.dm create mode 100644 code/game/gamemodes/cult/runes/stun.dm diff --git a/code/game/gamemodes/cult/runes/deafen.dm b/code/game/gamemodes/cult/runes/deafen.dm new file mode 100644 index 00000000000..793bc63f6a9 --- /dev/null +++ b/code/game/gamemodes/cult/runes/deafen.dm @@ -0,0 +1,14 @@ +/obj/effect/newrune/deafen + rune_name = "Deafen" + rune_desc = "Deafens all non-cultists near the rune." + rune_shorthand = "Deafens all non-cultists near the rune. Functions similarly in talisman form." + circle_words = list(CULT_WORD_HIDE, CULT_WORD_OTHER, CULT_WORD_SEE) + invocation = "Sti'kaliedir!" + +/obj/effect/newrune/deafen/invoke(list/invokers) + visible_message(SPAN_DANGER("The runes dissipate into fine dust.")) + for (var/mob/living/L in hearers(7, src)) + if (iscultist(L) || findNullRod(L)) + continue + L.ear_deaf = max(50, ear_deaf) + to_chat(L, SPAN_DANGER("Your ears pop and the world goes quiet.")) diff --git a/code/game/gamemodes/cult/runes/emp.dm b/code/game/gamemodes/cult/runes/emp.dm new file mode 100644 index 00000000000..6a7226e3795 --- /dev/null +++ b/code/game/gamemodes/cult/runes/emp.dm @@ -0,0 +1,14 @@ +/obj/effect/newrune/emp + rune_name = "Disable Technology" + rune_desc = "Emits a strong electromagnetic pulse in a short radius, disabling or harming nearby electronics." + rune_shorthand = "Emits a strong, short-ranged EMP." + circle_words = list(CULT_WORD_DESTROY, CULT_WORD_SEE, CULT_WORD_TECHNOLOGY) + invocation = "Ta'gh fara'qha fel d'amar det!" + +/obj/effect/newrune/emp/invoke(list/invokers) + var/turf/T = get_turf(src) + if (T) + T.hotspot_expose(700, 125) + visible_message(SPAN_DANGER("A wave of heat emanates outwards from the runes as they shimmer and vanish.")) + empulse(T, 2, 3, 4, 5) + qdel(src) diff --git a/code/game/gamemodes/cult/runes/raise_dead.dm b/code/game/gamemodes/cult/runes/raise_dead.dm new file mode 100644 index 00000000000..9ab5ab1e51b --- /dev/null +++ b/code/game/gamemodes/cult/runes/raise_dead.dm @@ -0,0 +1,102 @@ +/obj/effect/newrune/raise_dead + rune_name = "Raise Dead" + rune_desc = "This rune allows for the resurrection of any dead person. You will need a dead human body and a living human sacrifice. Make 2 raise dead runes. Put a living, awake human on top of one, and a dead body on the other one. When you invoke the rune, the life force of the living human will be transferred into the dead body, allowing a ghost standing on top of the dead body to enter it, instantly and fully healing it. Use other runes to ensure there is a ghost ready to be resurrected." + rune_shorthand = "Brings a dead body to life using the sacrifice of a living human on another copy of the rune." + circle_words = list(CULT_WORD_BLOOD, CULT_WORD_JOIN, CULT_WORD_SELF) + invocation = "Pasnar val'keriam usinar. Savrae ines amutan. Yam'toth remium il'tarat!" + +/obj/effect/newrune/raise_dead/proc/get_targets() + var/to_raise = null + var/to_sacrifice = null + for (var/mob/living/carbon/human/H in get_turf(src)) + if (H.stat == DEAD) + to_raise = H + break + for (var/obj/effect/newrune/raise_dead/R in orange(1, src)) + for (var/mob/living/carbon/human/H in get_turf(R)) + if (H.stat != DEAD) + to_sacrifice = H + break + return list(to_raise, to_sacrifice) + +/obj/effect/newrune/raise_dead/can_invoke(mob/living/invoker) + var/list/targets = get_targets() + if (!targets[1]) + return + else if (!targets[2]) + to_chat(invoker, SPAN_WARNING("You must position a living human sacrifice on an adjacent copy of the rune.")) + return + var/mob/living/L = targets[1] + if (!L.mind) + to_chat(invoker, SPAN_WARNING("This body is mindless and cannot hold life.")) + return + else if (cult.sacrifice_target == L.mind) + to_chat(invoker, SPAN_WARNING("This body cannot be raised, for the Geometer requires it as sacrifice.")) + return + else if (!cult.can_become_antag(L.mind)) + to_chat(invoker, SPAN_WARNING("The Geometer refuses to touch this body.")) + return + return TRUE + +/obj/effect/newrune/raise_dead/invoke(list/invokers) + var/list/targets = get_targets() + var/mob/living/L = invokers[1] + var/mob/living/carbon/human/shears = targets[1] + var/mob/living/carbon/human/lamb = targets[2] + to_chat(L, SPAN_OCCULT("The ritual is begun. Both bodies must remain in place...")) + shears.visible_message(SPAN_WARNING("\The [shears] is yanked upwards by invisible strings, dangling in the air like a puppet.")) + lamb.visible_message( + SPAN_WARNING("\The [lamb] is yanked upwards by invisible strings, dangling in the air like a puppet."), + SPAN_DANGER("An invisible force yanks you in the air and holds you there!") + ) + var/mob/observer/dead/ghost = shears.get_ghost() + if (ghost) + ghost.notify_revive("The cultist [L.real_name] is attempting to raise you from the dead. Return to your body if you wish to be risen into the service of Nar-Sie!", 'sound/effects/genetics.ogg', source = src) + if (do_after(shears, 5 SECONDS, lamb, FALSE, incapacitation_flags = INCAPACITATION_NONE)) + resurrect(shears, lamb) + return + to_chat(L, SPAN_OCCULT("The ritual's participants must remain stationary!")) + if (shears) + shears.visible_message(SPAN_WARNING("\The [shears] drops unceremoniously to the ground.")) + playsound(shears, "bodyfall", 50, TRUE) + if (lamb) + lamb.visible_message( + SPAN_WARNING("\The [lamb] drops unceremoniously to the ground."), + SPAN_DANGER("The force releases its hold on you, and you fall back to the ground!") + ) + playsound(lamb, "bodyfall", 50, TRUE) + +/obj/effect/newrune/raise_dead/proc/resurrect(mob/living/carbon/human/shears, mob/living/carbon/human/lamb, mob/living/invoker) + var/list/targets = get_targets() + if (targets[1] != shears || targets[2] != lamb) + to_chat(invoker, SPAN_DANGER("The ritual's subjects were moved before it could complete.")) + return + if (!shears.client || !shears.mind) + shears.visible_message(SPAN_WARNING("\The [shears] drops unceremoniously to the ground.")) + lamb.visible_message( + SPAN_WARNING("\The [lamb] drops unceremoniously to the ground."), + SPAN_DANGER("THe force releases its hold on you, and you fall back to the ground!") + ) + playsound(shears, "bodyfall", 50, TRUE) + playsound(lamb, "bodyfall", 50, TRUE) + to_chat(invoker, SPAN_DANGER("The deceased's spirit did not return to its body. It may if you try again, or it may not.")) + return + var/datum/gender/GS = gender_datums[shears.get_visible_gender()] + lamb.visible_message( + SPAN_DANGER(FONT_LARGE("[lamb]'s body is violently wrenched apart into bloody pieces!")), + SPAN_DANGER(FONT_LARGE("A vast, dark thing reaches inside you and plucks out something precious. Your body ripped into bloody pieces like wet paper.")) + ) + playsound(lamb, 'sound/effects/splat.ogg', 80, TRUE) + for (var/obj/item/organ/external/E in lamb.organs) + E.droplimb(FALSE, DROPLIMB_EDGE) + shears.revive() + shears.visible_message( + SPAN_DANGER("\The [shears] convulses violently as [GS.he] suddenly comes back to life!"), + SPAN_DANGER("Life... you're alive again...") + ) + if (!iscultist(shears)) + cult.add_antagonist(shears.mind) + to_chat(shears, SPAN_OCCULT("Your blood pulses. Your head throbs. The world goes red. All at once you are aware of a horrible, horrible truth. The veil of reality has been ripped away and in the festering wound left behind something sinister takes root.")) + to_chat(shears, SPAN_OCCULT("Assist your new compatriots in their dark dealings. Their goal is yours, and yours is theirs. You serve the Dark One above all else. Bring It back.")) + shears.flash_eyes(override_blindness_check = TRUE) + shears.Paralyse(10) diff --git a/code/game/gamemodes/cult/runes/stun.dm b/code/game/gamemodes/cult/runes/stun.dm new file mode 100644 index 00000000000..45b621ec547 --- /dev/null +++ b/code/game/gamemodes/cult/runes/stun.dm @@ -0,0 +1,18 @@ +/obj/effect/newrune/stun + rune_name = "Stun" + rune_desc = "This rune is specialized for use in talismans; invoked on its own, its only effect is to disorient nearby beings. As a talisman, all of its energy is instead forced into the talisman's target, immediately knocking them to the ground and preventing them from speaking." + rune_shorthand = "Releases a small burst of undirected stunning force. Much more effective in talisman form." + circle_words = list(CULT_WORD_JOIN, CULT_WORD_HIDE, CULT_WORD_TECHNOLOGY) + invocation = "Fuu ma'jin!" + +/obj/effect/newrune/stun/invoke(list/invokers) + visible_message(SPAN_DANGER("The runes explode in a bright flash of light!")) + for (var/mob/living/L in viewers(src)) + if (silicon(L)) + L.Weaken(5) + else + L.flash_eyes() + L.stuttering = min(1, stuttering) + L.Weaken(1) + L.Stun(1) + add_attack_logs(invokers[1], L, "stun rune") diff --git a/polaris.dme b/polaris.dme index c62a372b584..d182e26b3bc 100644 --- a/polaris.dme +++ b/polaris.dme @@ -542,7 +542,10 @@ #include "code\game\gamemodes\cult\runes\_rune_icon.dm" #include "code\game\gamemodes\cult\runes\communicate.dm" #include "code\game\gamemodes\cult\runes\convert.dm" +#include "code\game\gamemodes\cult\runes\deafen.dm" +#include "code\game\gamemodes\cult\runes\raise_dead.dm" #include "code\game\gamemodes\cult\runes\sacrifice.dm" +#include "code\game\gamemodes\cult\runes\stun.dm" #include "code\game\gamemodes\cult\runes\summon_tome.dm" #include "code\game\gamemodes\cult\runes\wall.dm" #include "code\game\gamemodes\endgame\endgame.dm" diff --git a/tgui/packages/tgui/interfaces/ArcaneTome.js b/tgui/packages/tgui/interfaces/ArcaneTome.js index bb44a1e150e..501265d3283 100644 --- a/tgui/packages/tgui/interfaces/ArcaneTome.js +++ b/tgui/packages/tgui/interfaces/ArcaneTome.js @@ -16,27 +16,26 @@ export const ArcaneTome = (props, context) => { { act("turnPage"), setTabIndex(0) }}> + onClick={() => { act("turnPage"); setTabIndex(0); }}> Archives { act("turnPage"), setTabIndex(1) }}> + onClick={() => { act("turnPage"); setTabIndex(1); }}> Runes
{ - tabIndex === 0 && - ("TBD") - || - tabIndex === 1 && - ( + tabIndex === 0 + && ("TBD") + || tabIndex === 1 + && ( {data.runes.map(entry => ( - +
+ + ))} || + + Entries marked with an asterisk (*) can be made into a talisman.

+ {data.runes.map(entry => ( + +