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) . = ..()