From a7156dce1689ee7a00d8a8bf9bb1131919d261c4 Mon Sep 17 00:00:00 2001
From: Octus <62493359+OctusGit@users.noreply.github.com>
Date: Tue, 9 May 2023 19:41:40 -0500
Subject: [PATCH] Reworks cult sharding to not perma kill mindshielded victims
[Needs TM] (#20402)
* fucking cult
* WERE STILL WORKING ON ITTTT
* d
* Revert "d"
This reverts commit 8a4bf06f5ce1bfa57df62dcb4a37db88ee8e1171.
* MOREEEE
* update
* Revert "update"
This reverts commit 65d85eae877930f907a553ef687603224feb1f08.
* only chap can do it now lmao
* WHOOPS
* OK NOW
* Update runes.dm
* Apply suggestions from code review
Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
* PLEASE TESTMERGE AAA
* fixes some stuff, improves it
* fixes the code from merge conflict
* Update code/game/gamemodes/wizard/soulstone.dm
Co-authored-by: Ryan <80364400+Sirryan2002@users.noreply.github.com>
* fixes a bug with shit not dying due to brains
* Adds some GC stuff, Sirr's code reviews.
* finishes the last changes and reviews
* missed 1
---------
Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Co-authored-by: Ryan <80364400+Sirryan2002@users.noreply.github.com>
---
code/__HELPERS/traits.dm | 1 +
code/_globalvars/traits.dm | 1 +
code/game/gamemodes/cult/cult_mode.dm | 7 ++
code/game/gamemodes/cult/runes.dm | 23 ++++---
code/game/gamemodes/wizard/soulstone.dm | 69 +++++++++++++++----
.../mob/living/simple_animal/constructs.dm | 29 +++++++-
6 files changed, 107 insertions(+), 23 deletions(-)
diff --git a/code/__HELPERS/traits.dm b/code/__HELPERS/traits.dm
index a10884a1c40..6d6863a0278 100644
--- a/code/__HELPERS/traits.dm
+++ b/code/__HELPERS/traits.dm
@@ -199,6 +199,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_ELITE_CHALLENGER "elite_challenger"
#define TRAIT_SOAPY_MOUTH "soapy_mouth"
#define TRAIT_UNREVIVABLE "unrevivable" // Prevents changeling revival
+#define TRAIT_CULT_IMMUNITY "cult_immunity"
#define TRAIT_FLATTENED "flattened"
//***** ITEM AND MOB TRAITS *****//
diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm
index 392c584c283..89d6fb61890 100644
--- a/code/_globalvars/traits.dm
+++ b/code/_globalvars/traits.dm
@@ -70,6 +70,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_ELITE_CHALLENGER" = TRAIT_ELITE_CHALLENGER,
"TRAIT_SOAPY_MOUTH" = TRAIT_SOAPY_MOUTH,
"TRAIT_UNREVIVABLE" = TRAIT_UNREVIVABLE,
+ "TRAIT_CULT_IMMUNITY" = TRAIT_CULT_IMMUNITY,
"TRAIT_SHOW_WIRE_INFO" = TRAIT_SHOW_WIRE_INFO,
"TRAIT_CAN_BE_EATEN_BY_LIZARDS" = TRAIT_EDIBLE_BUG,
"TRAIT_FLATTENED" = TRAIT_FLATTENED
diff --git a/code/game/gamemodes/cult/cult_mode.dm b/code/game/gamemodes/cult/cult_mode.dm
index ffe18da4592..0f2711018b5 100644
--- a/code/game/gamemodes/cult/cult_mode.dm
+++ b/code/game/gamemodes/cult/cult_mode.dm
@@ -211,6 +211,13 @@ GLOBAL_LIST_EMPTY(all_cults)
cultist.visible_message("[cultist] looks like [cultist.p_they()] just reverted to [cultist.p_their()] old faith!",
"An unfamiliar white light flashes through your mind, cleansing the taint of [SSticker.cultdat ? SSticker.cultdat.entity_title1 : "Nar'Sie"] and the memories of your time as their servant with it.")
+/datum/game_mode/proc/add_cult_immunity(mob/living/target)
+ ADD_TRAIT(target, TRAIT_CULT_IMMUNITY, CULT_TRAIT)
+ addtimer(CALLBACK(src, PROC_REF(remove_cult_immunity), target), 1 MINUTES)
+
+/datum/game_mode/proc/remove_cult_immunity(mob/living/target)
+ REMOVE_TRAIT(target, TRAIT_CULT_IMMUNITY, CULT_TRAIT)
+
/**
* Decides at the start of the round how many conversions are needed to rise/ascend.
diff --git a/code/game/gamemodes/cult/runes.dm b/code/game/gamemodes/cult/runes.dm
index d6293e5d463..3f54785e31d 100644
--- a/code/game/gamemodes/cult/runes.dm
+++ b/code/game/gamemodes/cult/runes.dm
@@ -229,9 +229,9 @@ structure_check() searches for nearby cultist structures required for the invoca
animate(src, transform = matrix() * 2, alpha = 0, time = 5) // Fade out
animate(transform = oldtransform, alpha = 255, time = 0)
-/obj/effect/rune/proc/fail_invoke()
+/obj/effect/rune/proc/fail_invoke(show_message = TRUE)
//This proc contains the effects of a rune if it is not invoked correctly, through either invalid wording or not enough cultists. By default, it's just a basic fizzle.
- if(!invisibility) // No visible messages if not visible
+ if(!invisibility && show_message) // No visible messages if not visible
visible_message("The markings pulse with a small flash of red light, then fall dark.")
animate(src, color = rgb(255, 0, 0), time = 0)
animate(src, color = rune_blood_color, time = 5)
@@ -295,7 +295,6 @@ structure_check() searches for nearby cultist structures required for the invoca
for(var/obj/item/organ/internal/brain/brain in H.contents)
b_mob = brain.brainmob
brain.forceMove(T)
- O = brain // Convoluted way of making the brain disappear
else if(istype(O, /obj/item/organ/internal/brain)) // Offering a brain
var/obj/item/organ/internal/brain/brain = O
@@ -303,7 +302,6 @@ structure_check() searches for nearby cultist structures required for the invoca
if(b_mob && b_mob.mind && (!iscultist(b_mob) || is_sacrifice_target(b_mob.mind)))
offer_targets += b_mob
- O.invisibility = INVISIBILITY_MAXIMUM // So that it can't be moved around. This gets qdeleted later
if(!length(offer_targets))
fail_invoke()
@@ -313,8 +311,10 @@ structure_check() searches for nearby cultist structures required for the invoca
rune_in_use = TRUE
var/mob/living/L = pick(offer_targets)
- if(L.mind in GLOB.sacrificed)
- fail_invoke()
+ if(HAS_TRAIT(L, TRAIT_CULT_IMMUNITY))
+ fail_invoke(FALSE)
+ for(var/I in invokers)
+ to_chat(I, "This sacrifice was already converted recently. Wait a minute before trying again!")
rune_in_use = FALSE
return
@@ -325,8 +325,6 @@ structure_check() searches for nearby cultist structures required for the invoca
invocation = "Barhah hra zar'garis!"
..()
do_sacrifice(L, invokers)
- if(isbrain(L))
- qdel(L.loc) // Don't need this anymore!
rune_in_use = FALSE
/obj/effect/rune/convert/proc/do_convert(mob/living/convertee, list/invokers)
@@ -389,6 +387,15 @@ structure_check() searches for nearby cultist structures required for the invoca
var/sacrifice_fulfilled
var/worthless = FALSE
var/datum/game_mode/gamemode = SSticker.mode
+
+ if(isliving(offering) && !isbrain(offering))
+ var/mob/living/L = offering
+ if(isrobot(L) || ismachineperson(L))
+ L.adjustBruteLoss(250)
+ else
+ L.adjustCloneLoss(120)
+ L.death(FALSE)
+
if(offering.mind)
GLOB.sacrificed += offering.mind
if(is_sacrifice_target(offering.mind))
diff --git a/code/game/gamemodes/wizard/soulstone.dm b/code/game/gamemodes/wizard/soulstone.dm
index d46d77b0066..978b9d47bad 100644
--- a/code/game/gamemodes/wizard/soulstone.dm
+++ b/code/game/gamemodes/wizard/soulstone.dm
@@ -11,6 +11,8 @@
slot_flags = SLOT_BELT
origin_tech = "bluespace=4;materials=5"
+ /// The body/brain of the player inside this construct, transferred over from the soulstone.
+ var/atom/movable/held_body
/// Does this soulstone ask the victim whether they want to be turned into a shade
var/optional = FALSE
/// Can this soul stone be used by anyone, or only cultists/wizards?
@@ -24,11 +26,19 @@
var/opt_in = FALSE
var/purified = FALSE
+/obj/item/soulstone/proc/add_held_body(atom/movable/body)
+ held_body = body
+ RegisterSignal(body, COMSIG_PARENT_QDELETING, PROC_REF(remove_held_body))
+
+/obj/item/soulstone/proc/remove_held_body()
+ SIGNAL_HANDLER
+ held_body = null
+
/obj/item/soulstone/proc/can_use(mob/living/user)
if(iscultist(user) && purified && !iswizard(user))
return FALSE
- if(iscultist(user) || iswizard(user) || usability)
+ if(iscultist(user) || iswizard(user) || (user.mind?.isholy && purified))
return TRUE
return FALSE
@@ -58,12 +68,17 @@
. = ..()
if(iscultist(user) && purified && !iswizard(user))
to_chat(user, "[src] reeks of holy magic. You will need to cleanse it with a ritual dagger before anything can be done with it.")
+ return
+ if(user.mind?.isholy)
+ to_chat(user, "An overwhelming feeling of dread comes over you as you pick up [src]. It looks fragile enough to break with your hands.")
+ return
if(!can_use(user))
to_chat(user, "An overwhelming feeling of dread comes over you as you pick up [src].")
/obj/item/soulstone/Destroy() //Stops the shade from being qdel'd immediately and their ghost being sent back to the arrival shuttle.
for(var/mob/living/simple_animal/shade/A in src)
A.death()
+ remove_held_body()
return ..()
//////////////////////////////Capturing////////////////////////////////////////////////////////
@@ -200,17 +215,35 @@
..()
/obj/item/soulstone/attack_self(mob/living/user)
- if(!in_range(src, user))
+ var/mob/living/simple_animal/shade/S = locate(/mob/living/simple_animal/shade) in contents
+ if(!in_range(src, user) || !S)
return
- if(!can_use(user))
- user.Weaken(10 SECONDS)
- user.emote("scream")
- to_chat(user, "Your body is wracked with debilitating pain!")
+ if(can_use(user))
+ release_shades(user)
return
- release_shades(user)
- return
+ if(!user.mind.isholy)
+ to_chat(user, "The shard feels too tough to shatter, you are not holy enough to free its captive!")
+ return
+
+ if(!do_after_once(user, 10 SECONDS, FALSE, src) || !held_body)
+ return
+
+ user.visible_message("[user] shatters the soulstone apart! Releasing [held_body] from their prison!", "You shatter the soulstone holding [held_body], binding them free!", "You hear something shatter with a ghastly crack.")
+ if(ismob(held_body))
+ var/mob/M = held_body
+ M.key = S.key
+ else if(istype(held_body, /obj/item/organ/internal/brain))
+ var/obj/item/organ/internal/brain/B = held_body
+ B.brainmob.key = S.key
+ S.cancel_camera()
+ held_body.forceMove(get_turf(src))
+ SSticker.mode.add_cult_immunity(held_body)
+ remove_held_body()
+ new /obj/effect/temp_visual/cult/sparks(get_turf(src))
+ playsound(src, 'sound/effects/pylon_shatter.ogg', 40, TRUE)
+ qdel(src)
/obj/item/soulstone/proc/release_shades(mob/user)
for(var/mob/living/simple_animal/shade/A in src)
@@ -270,7 +303,8 @@
to_chat(user, "Capture failed! The soul has already fled its mortal frame. You attempt to bring it back...")
T.Paralyse(40 SECONDS)
if(!get_cult_ghost(T, user, TRUE))
- T.dust() //If we can't get a ghost, kill the sacrifice anyway.
+ add_held_body(T)
+ T.forceMove(src) //If we can't get a ghost, shard the body anyways.
if("VICTIM")
var/mob/living/carbon/human/T = target
@@ -300,7 +334,7 @@
if((iscultist(T) && purified) || (T.holy && !purified))
to_chat(user, "Capture failed! The shade recoils away from [src]!")
else
- if(length(contents))
+ if(locate(/mob/living/simple_animal/shade) in contents)
to_chat(user, "Capture failed!: The soul stone is full! Use or free an existing soul to make room.")
else
T.forceMove(src) // Put the shade into the stone.
@@ -360,6 +394,9 @@
to_chat(src, "You are still bound to serve the cult, follow their orders and help them complete their goals at all costs.")
else
to_chat(src, "You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.")
+ SS.held_body.forceMove(src)
+ add_held_body(SS.held_body)
+ SS.remove_held_body()
cancel_camera()
qdel(shell)
qdel(shade)
@@ -405,12 +442,16 @@
to_chat(S, "Your soul has been captured! You are now bound to [user.real_name]'s will. Help them succeed in their goals at all costs.")
if(forced && user)
to_chat(user, "Capture successful!: [M.real_name]'s soul has been ripped from [user.p_their()] body and stored within the soul stone.")
- if(isrobot(M))//Robots have to dust or else they spill out an empty robot brain, and unequiping them spills robot components that shouldn't spawn.
- M.dust()
- else
+ if(!isrobot(M))
for(var/obj/item/I in M)
M.unEquip(I)
- M.dust()
+ if(isbrain(M))
+ var/obj/item/organ/internal/brain/brain_obj = M.loc
+ add_held_body(brain_obj)
+ brain_obj.forceMove(src)
+ else
+ add_held_body(M)
+ M.forceMove(src)
/obj/item/soulstone/proc/get_shade_type()
if(purified)
diff --git a/code/modules/mob/living/simple_animal/constructs.dm b/code/modules/mob/living/simple_animal/constructs.dm
index 4a25931091b..464bb1e5e51 100644
--- a/code/modules/mob/living/simple_animal/constructs.dm
+++ b/code/modules/mob/living/simple_animal/constructs.dm
@@ -26,6 +26,8 @@
del_on_death = TRUE
deathmessage = "collapses in a shattered heap."
var/construct_type = "shade"
+ /// The body/brain of the player inside this construct, transferred over from the soulstone.
+ var/mob/living/held_body
var/list/construct_spells = list()
/// Is this a holy/purified construct?
var/holy = FALSE
@@ -50,9 +52,34 @@
set_light(2, 3, l_color = SSticker.cultdat ? SSticker.cultdat.construct_glow : LIGHT_COLOR_BLOOD_MAGIC)
+/mob/living/simple_animal/hostile/construct/Destroy()
+ remove_held_body()
+ return ..()
+
/mob/living/simple_animal/hostile/construct/death(gibbed)
- . = ..()
SSticker.mode.remove_cultist(show_message = FALSE, target_mob = src)
+ if(held_body) // Null check for empty bodies
+ held_body.forceMove(get_turf(src))
+ SSticker.mode.add_cult_immunity(held_body)
+ if(ismob(held_body)) // Check if the held_body is a mob
+ held_body.key = key
+ else if(istype(held_body, /obj/item/organ/internal/brain)) // Check if the held_body is a brain
+ var/obj/item/organ/internal/brain/brain = held_body
+ if(brain.brainmob) // Check if the brain has a brainmob
+ brain.brainmob.key = key // Set the key to the brainmob
+ brain.brainmob.mind.transfer_to(brain.brainmob) // Transfer the mind to the brainmob
+ held_body.cancel_camera()
+ new /obj/effect/temp_visual/cult/sparks(get_turf(src))
+ playsound(src, 'sound/effects/pylon_shatter.ogg', 40, TRUE)
+ . = ..()
+
+/mob/living/simple_animal/hostile/construct/proc/add_held_body(atom/movable/body)
+ held_body = body
+ RegisterSignal(body, COMSIG_PARENT_QDELETING, PROC_REF(remove_held_body))
+
+/mob/living/simple_animal/hostile/construct/proc/remove_held_body()
+ SIGNAL_HANDLER
+ held_body = null
/mob/living/simple_animal/hostile/construct/examine(mob/user)
. = ..()