// This whole file really needs reorganising at some point, or at the very least the construct stuff should be moved somewhere else. /obj/item/soulstone name = "soul stone" icon = 'icons/obj/wizard.dmi' icon_state = "soulstone" worn_icon_state = "electronic" inhand_icon_state = "electronic" belt_icon = "soulstone" var/icon_state_full = "soulstone2" desc = "A weighty shard of dark crystal, flickering with eldritch energy. A soul can be bound within, forced to obey the commands of the owner." w_class = WEIGHT_CLASS_TINY slot_flags = ITEM_SLOT_BELT origin_tech = "bluespace=4;materials=5" /// Should we show rays? Triggered by a held body var/animate_rays = FALSE /// 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? var/usability = FALSE /// Can this soul stone be used more than once? var/reusable = TRUE /// If the soul stone can only be used once, has it been used? var/spent = FALSE /// For tracking during the 'optional' bit var/opt_in = FALSE var/purified = FALSE /obj/item/soulstone/proc/can_use(mob/living/user) if(IS_CULTIST(user) && purified && !iswizard(user)) return FALSE if(IS_CULTIST(user) || iswizard(user) || (HAS_MIND_TRAIT(user, TRAIT_HOLY) && purified) || usability) return TRUE return FALSE /obj/item/soulstone/proc/was_used() if(!reusable) spent = TRUE name = "dull [initial(name)]" desc = "A fragment of the legendary treasure known simply as \ the 'Soul Stone'. The shard lies still, dull and lifeless; \ whatever spark it once held long extinguished." /obj/item/soulstone/anybody usability = TRUE /obj/item/soulstone/anybody/purified icon_state = "purified_soulstone" icon_state_full = "purified_soulstone2" purified = TRUE optional = TRUE /obj/item/soulstone/anybody/purified/chaplain name = "mysterious old shard" reusable = FALSE /obj/item/soulstone/pickup(mob/living/user) . = ..() if(IS_CULTIST(user) && purified && !iswizard(user)) to_chat(user, SPAN_DANGER("[src] reeks of holy magic. You will need to cleanse it with a ritual dagger before anything can be done with it.")) return if(HAS_MIND_TRAIT(user, TRAIT_HOLY)) to_chat(user, SPAN_DANGER("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, SPAN_DANGER("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() STOP_PROCESSING(SSobj, src) return ..() /obj/item/soulstone/process() . = ..() if(animate_rays) var/new_filter = isnull(get_filter("ray")) if(!purified) ray_filter_helper(1, 40,"#c926ae", 6, 20) else ray_filter_helper(1, 40,"#268dc9", 6, 20) if(new_filter) animate(get_filter("ray"), offset = 10, time = 10 SECONDS, loop = -1) animate(offset = 0, time = 10 SECONDS) //////////////////////////////Capturing//////////////////////////////////////////////////////// /obj/item/soulstone/attack__legacy__attackchain(mob/living/carbon/human/M, mob/living/user) if(M == user) return if(!can_use(user)) user.Weaken(10 SECONDS) user.emote("scream") to_chat(user, SPAN_USERDANGER("Your body is wracked with debilitating pain!")) return if(spent) to_chat(user, SPAN_WARNING("There is no power left in the shard.")) return if(!ishuman(M)) //If target is not a human return ..() if(!M.mind) to_chat(user, SPAN_WARNING("This being has no soul!")) return ..() if(jobban_isbanned(M, ROLE_CULTIST) || jobban_isbanned(M, ROLE_SYNDICATE)) to_chat(user, SPAN_WARNING("A mysterious force prevents you from trapping this being's soul.")) return ..() if(IS_CULTIST(user) && IS_CULTIST(M)) to_chat(user, SPAN_CULTLARGE("\"Come now, do not capture your fellow's soul.\"")) return ..() if((M.mind.offstation_role && M.mind.special_role != SPECIAL_ROLE_ERT) || HAS_MIND_TRAIT(M, TRAIT_XENOBIO_SPAWNED_HUMAN)) to_chat(user, SPAN_WARNING("This being's soul seems worthless. Not even the stone will absorb it.")) return ..() if(optional) if(!M.ckey) to_chat(user, SPAN_WARNING("They have no soul!")) return to_chat(user, SPAN_WARNING("You attempt to channel [M]'s soul into [src]. You must give the soul some time to react and stand still...")) var/mob/player_mob = M var/ghost = M.get_ghost() if(ghost) // In case our player ghosted and we need to throw the alert at their ghost instead player_mob = ghost var/client/player_client = player_mob.client to_chat(player_mob, SPAN_WARNING("[user] is trying to capture your soul into [src]! Click the button in the top right of the game window to respond.")) SEND_SOUND(player_client, sound('sound/misc/notice2.ogg')) window_flash(player_client) var/atom/movable/screen/alert/notify_soulstone/A = player_mob.throw_alert("\ref[src]_soulstone_thingy", /atom/movable/screen/alert/notify_soulstone) if(player_client.prefs && player_client.prefs.UI_style) A.icon = ui_style2icon(player_client.prefs.UI_style) // Pass the stuff to the alert itself A.stone = src A.stoner = user.real_name // Layer shenanigans to make the alert display the soulstone var/old_layer = layer var/old_plane = plane layer = FLOAT_LAYER plane = FLOAT_PLANE A.overlays += src layer = old_layer plane = old_plane // Give the victim 10 seconds to respond sleep(10 SECONDS) if(!opt_in) to_chat(user, SPAN_WARNING("The soul resists your attempts at capturing it!")) return opt_in = FALSE if(spent)//checking one more time against shenanigans return add_attack_logs(user, M, "Stolestone'd with [name]") transfer_soul("VICTIM", M, user) return /obj/item/soulstone/attackby__legacy__attackchain(obj/item/O, mob/user) if(istype(O, /obj/item/storage/bible) && !IS_CULTIST(user) && HAS_MIND_TRAIT(user, TRAIT_HOLY)) if(purified) return to_chat(user, SPAN_NOTICE("You begin to exorcise [src].")) playsound(src, 'sound/hallucinations/veryfar_noise.ogg', 40, TRUE) if(do_after(user, 40, target = src)) remove_filter("ray") usability = TRUE purified = TRUE optional = TRUE icon_state = "purified_soulstone" icon_state_full = "purified_soulstone2" for(var/mob/M in contents) if(M.mind) icon_state = "purified_soulstone2" if(IS_CULTIST(M)) M.mind.remove_antag_datum(/datum/antagonist/cultist, silent_removal = TRUE) to_chat(M, "An unfamiliar white light flashes through your mind, cleansing the taint of [GET_CULT_DATA(entity_title1, "Nar'Sie")] \ and the memories of your time as their servant with it.") to_chat(M, SPAN_DANGER("Assist [user], your saviour, and get vengeance on those who enslaved you!")) else to_chat(M, SPAN_DANGER("Your soulstone has been exorcised, and you are now bound to obey [user].")) for(var/mob/living/simple_animal/shade/EX in src) EX.holy = TRUE EX.icon_state = "shade_angelic" user.visible_message(SPAN_NOTICE("[user] purifies [src]!"), SPAN_NOTICE("You purify [src]!")) else if(istype(O, /obj/item/melee/cultblade/dagger) && IS_CULTIST(user)) if(!purified) return to_chat(user, SPAN_NOTICE("You begin to cleanse [src] of holy magic.")) if(do_after(user, 40, target = src)) remove_filter("ray") usability = FALSE purified = FALSE optional = FALSE icon_state = "soulstone" icon_state_full = "soulstone2" for(var/mob/M in contents) if(M.mind) icon_state = "soulstone2" M.mind.add_antag_datum(/datum/antagonist/cultist) to_chat(M, SPAN_CULT("Your shard has been cleansed of holy magic, and you are now bound to the cult's will. Obey them and assist in their goals.")) for(var/mob/living/simple_animal/shade/EX in src) EX.holy = FALSE EX.icon_state = GET_CULT_DATA(shade_icon_state, "shade") to_chat(user, SPAN_NOTICE("You have cleansed [src] of holy magic.")) else ..() /obj/item/soulstone/attack_self__legacy__attackchain(mob/living/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)) release_shades(user) return if(!HAS_MIND_TRAIT(user, TRAIT_HOLY)) to_chat(user, SPAN_NOTICE("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)) return if(!S) return var/datum/component/construct_held_body/body_holder = S.GetComponent(/datum/component/construct_held_body) var/atom/movable/dropped_body = body_holder.held_body body_holder.drop_body() if(!dropped_body) return user.visible_message("[user] shatters the soulstone apart! Releasing [dropped_body] from their prison!", "You shatter the soulstone holding [dropped_body], binding them free!", "You hear something shatter with a ghastly crack.") 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) A.forceMove(get_turf(user)) A.cancel_camera() if(purified) icon_state = "purified_soulstone" else icon_state = "soulstone" name = initial(name) if(IS_CULTIST(A)) to_chat(A, SPAN_USERDANGER("You have been released from your prison, but you are still bound to the cult's will. Help them succeed in their goals at all costs.")) else to_chat(A, SPAN_USERDANGER("You have been released from your prison, but you are still bound to your [purified ? "saviour" : "creator"]'s will.")) was_used() remove_filter("ray") STOP_PROCESSING(SSobj, src) animate_rays = FALSE ///////////////////////////Transferring to constructs///////////////////////////////////////////////////// /obj/structure/constructshell name = "empty shell" icon = 'icons/obj/wizard.dmi' icon_state = "construct-cult" desc = "A wicked machine used by those skilled in magical arts. It is inactive" /// Is someone currently placing a soulstone into the shell var/active = FALSE /obj/structure/constructshell/examine(mob/user) . = ..() if(in_range(user, src) && (IS_CULTIST(user) || iswizard(user) || user.stat == DEAD)) . += SPAN_CULT("A construct shell, used to house bound souls from a soulstone.") . += SPAN_CULT("Placing a soulstone with a soul into this shell allows you to produce your choice of the following:") . += SPAN_CULTITALIC("An Artificer, which can produce more shells and soulstones, as well as fortifications.") . += SPAN_CULTITALIC("A Wraith, which does high damage and can jaunt through walls, though it is quite fragile.") . += SPAN_CULTITALIC("A Juggernaut, which is very hard to kill and can produce temporary walls, but is slow.") /obj/structure/constructshell/item_interaction(mob/living/user, obj/item/I, list/modifiers) if(istype(I, /obj/item/soulstone)) var/obj/item/soulstone/SS = I if(!SS.can_use(user)) to_chat(user, SPAN_DANGER("An overwhelming feeling of dread comes over you as you attempt to place the soulstone into the shell.")) user.Confused(20 SECONDS) return ITEM_INTERACT_COMPLETE SS.transfer_soul("CONSTRUCT", src, user) SS.was_used() else return ..() ////////////////////////////Proc for moving soul in and out off stone////////////////////////////////////// // this whole proc is pain /obj/item/soulstone/proc/transfer_soul(choice, target, mob/living/user) switch(choice) if("FORCE") var/mob/living/T = target T.grab_ghost(FALSE) // If they haven't DC'd or ahudded, put them back in their body if(T.client) // If there's someone in the body init_shade(T, user) else // Poll ghosts to_chat(user, "[SPAN_USERDANGER("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)) // no luck, dont shard them. to_chat(user, SPAN_USERDANGER("No soul responds to the soul stone.")) if("VICTIM") var/mob/living/carbon/human/T = target if(T.stat == CONSCIOUS) to_chat(user, "[SPAN_DANGER("Capture failed!")] Kill or maim the victim first!") else if(!length(T.client_mobs_in_contents)) to_chat(user, SPAN_WARNING("They have no soul!")) else if(T.client == null) to_chat(user, "[SPAN_USERDANGER("Capture failed!")] The soul has already fled its mortal frame. You attempt to bring it back...") get_cult_ghost(T, user, get_new_player = !T.ghost_can_reenter()) else if(length(contents)) to_chat(user, "[SPAN_DANGER("Capture failed!")] The soul stone is full! Use or free an existing soul to make room.") else init_shade(T, user, TRUE) if("SHADE") var/mob/living/simple_animal/shade/T = target if(!can_use(user)) user.Weaken(10 SECONDS) to_chat(user, SPAN_USERDANGER("Your body is wracked with debilitating pain!")) return if(T.stat == DEAD) to_chat(user, "[SPAN_DANGER("Capture failed!")] The shade has already been banished!") if((IS_CULTIST(T) && purified) || (T.holy && !purified)) to_chat(user, "[SPAN_DANGER("Capture failed!")] The shade recoils away from [src]!") else if(locate(/mob/living/simple_animal/shade) in contents) to_chat(user, "[SPAN_DANGER("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. T.health = T.maxHealth icon_state = icon_state_full name = "soulstone : [T.name]" to_chat(T, SPAN_NOTICE("Your soul has been recaptured by the soul stone, its arcane energies are reknitting your ethereal form")) to_chat(user, "[SPAN_NOTICE("Capture successful!")] [T.name]'s has been recaptured and stored within the soul stone.") animate_rays = TRUE START_PROCESSING(SSobj, src) if("CONSTRUCT") var/obj/structure/constructshell/shell = target var/mob/living/simple_animal/shade/shade = locate() in src var/list/construct_types = list("Juggernaut" = /mob/living/simple_animal/hostile/construct/armoured, "Wraith" = /mob/living/simple_animal/hostile/construct/wraith, "Artificer" = /mob/living/simple_animal/hostile/construct/builder) /// Custom construct icons for different cults var/list/construct_icons = list("Juggernaut" = image(icon = 'icons/mob/cult.dmi', icon_state = GET_CULT_DATA(get_icon("juggernaut"), "behemoth")), "Wraith" = image(icon = 'icons/mob/cult.dmi', icon_state = GET_CULT_DATA(get_icon("wraith"), "floating")), "Artificer" = image(icon = 'icons/mob/cult.dmi', icon_state = GET_CULT_DATA(get_icon("builder"), "artificer"))) if(shade) var/construct_choice = show_radial_menu(user, shell, construct_icons, custom_check = CALLBACK(src, PROC_REF(radial_check), user), require_near = TRUE) var/picked_class = construct_types[construct_choice] if((picked_class && !QDELETED(shell) && !QDELETED(src)) && user.Adjacent(shell) && !user.incapacitated() && radial_check(user)) var/mob/living/simple_animal/hostile/construct/C = new picked_class(shell.loc) C.init_construct(shade, src, shell) to_chat(C, C.playstyle_string) to_chat(C, SPAN_MOTD("For more information, check the wiki page: ([GLOB.configuration.url.wiki_url]/index.php/Construct)")) else to_chat(user, "[SPAN_DANGER("Creation failed!")]: The soul stone is empty! Go kill someone!") /obj/item/soulstone/proc/radial_check(mob/user) if(!ishuman(user)) // Should never happen, but just in case return FALSE var/mob/living/carbon/human/H = user if(!H.is_in_hands(src)) // Not holding the soulstone return FALSE return TRUE /mob/living/simple_animal/hostile/construct/proc/init_construct(mob/living/simple_animal/shade/shade, obj/item/soulstone/SS, obj/structure/constructshell/shell) if(shade.mind) shade.mind.transfer_to(src) if(SS.usability) // Replace regular soulstone summoning with anyuse soulstones if(is_type_in_list(/datum/spell/aoe/conjure/build/soulstone, mob_spell_list)) RemoveSpell(/datum/spell/aoe/conjure/build/soulstone) AddSpell(new /datum/spell/aoe/conjure/build/soulstone/any) if(SS.purified) make_holy() // Replace regular soulstone summoning with purified soulstones if(is_type_in_list(/datum/spell/aoe/conjure/build/soulstone, mob_spell_list)) RemoveSpell(/datum/spell/aoe/conjure/build/soulstone) AddSpell(new /datum/spell/aoe/conjure/build/soulstone/holy) else if(mind.has_antag_datum(/datum/antagonist/cultist)) // Re-grant cult actions, lost in the transfer var/datum/action/innate/cult/comm/CC = new var/datum/action/innate/cult/check_progress/D = new CC.Grant(src) D.Grant(src) SSticker.mode.cult_team.study_objectives(src) // Display objectives again to_chat(src, SPAN_USERDANGER("You are still bound to serve the cult, follow their orders and help them complete their goals at all costs.")) else to_chat(src, SPAN_USERDANGER("You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.")) SEND_SIGNAL(shade, COMSIG_SHADE_TO_CONSTRUCT_TRANSFER, src) cancel_camera() qdel(shell) qdel(shade) qdel(SS) /proc/make_new_construct(mob/living/simple_animal/hostile/construct/c_type, mob/target, mob/user, cult_override = FALSE, create_smoke = FALSE) if(jobban_isbanned(target, ROLE_CULTIST)) return var/mob/living/simple_animal/hostile/construct/C = new c_type(get_turf(target)) if(create_smoke) new /obj/effect/particle_effect/smoke/sleeping(target.loc) C.faction |= "\ref[user]" C.key = target.key if(user && IS_CULTIST(user) || cult_override) C.mind.add_antag_datum(/datum/antagonist/cultist) if(user && IS_CULTIST(user)) to_chat(C, "You are still bound to serve the cult, follow their orders and help them complete their goals at all costs.") else to_chat(C, "You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.") C.cancel_camera() /obj/item/soulstone/proc/init_shade(mob/living/M, mob/user, forced = FALSE) START_PROCESSING(SSobj, src) var/type = get_shade_type() var/mob/living/simple_animal/shade/S = new type(src) S.name = "Shade of [M.real_name]" S.real_name = "Shade of [M.real_name]" S.key = M.key S.cancel_camera() name = "soulstone: [S.name]" icon_state = icon_state_full if(user) S.faction |= "\ref[user]" //Add the master as a faction, allowing inter-mob cooperation if(iswizard(user)) var/datum/antagonist/wizard/construct/construct = new /datum/antagonist/wizard/construct() construct.my_creator = user S.mind.add_antag_datum(construct) if(IS_CULTIST(user)) S.mind.add_antag_datum(/datum/antagonist/cultist) to_chat(S, SPAN_USERDANGER("Your soul has been captured! You are now bound to the cult's will. Help them succeed in their goals at all costs.")) else S.mind.store_memory("Serve [user.real_name], your creator.") to_chat(S, SPAN_USERDANGER("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, "[SPAN_NOTICE("Capture successful!:")] [M.real_name]'s soul has been ripped from [user.p_their()] body and stored within the soul stone.") if(!isrobot(M)) for(var/obj/item/I in M) M.drop_item_to_ground(I) var/target_body = M if(isbrain(M)) target_body = M.loc // get the brain organ instead of the brain mob S.AddComponent(/datum/component/construct_held_body, target_body) animate_rays = TRUE /obj/item/soulstone/proc/get_shade_type() if(purified) return /mob/living/simple_animal/shade/holy return /mob/living/simple_animal/shade/cult /obj/item/soulstone/proc/get_cult_ghost(mob/living/M, mob/user, get_new_player = FALSE) var/mob/dead/observer/chosen_ghost if(!get_new_player) for(var/mob/dead/observer/ghost in GLOB.player_list) // We put them back in their body if(ghost.mind && ghost.mind.current == M && ghost.client) chosen_ghost = ghost break if(!chosen_ghost) // Failing that, we grab a ghost var/list/consenting_candidates if(purified) consenting_candidates = SSghost_spawns.poll_candidates("Would you like to play as a Holy Shade?", ROLE_SENTIENT, FALSE, poll_time = 10 SECONDS, source = /mob/living/simple_animal/shade/holy, role_cleanname = "holy shade") else consenting_candidates = SSghost_spawns.poll_candidates("Would you like to play as a Shade?", ROLE_SENTIENT, FALSE, poll_time = 10 SECONDS, source = /mob/living/simple_animal/shade, role_cleanname = "shade") if(length(consenting_candidates)) chosen_ghost = pick(consenting_candidates) if(!M) return FALSE if(!chosen_ghost) to_chat(user, SPAN_DANGER("There were no spirits willing to become a shade.")) return FALSE if(length(contents)) //If they used the soulstone on someone else in the meantime return FALSE M.ckey = chosen_ghost.ckey dust_if_respawnable(chosen_ghost) init_shade(M, user) return TRUE