From e57b1c8335e2879009bb63a66be0a4c9cd31eeae Mon Sep 17 00:00:00 2001
From: Ghom <42542238+Ghommie@users.noreply.github.com>
Date: Thu, 2 Sep 2021 10:00:06 +0200
Subject: [PATCH] Using a soulstone on a construct shell no longer destroys it
and the shade if no option is taken. Plus code improvement (#60982)
I have split the multi-purpose transfer_soul proc into three smaller procs, each with their own purpose, renamed some variables accordingly to guidelines and added some checks such as CanReach where opportune.
---
code/modules/antagonists/cult/cult_items.dm | 28 ++--
code/modules/antagonists/cult/runes.dm | 4 +-
.../antagonists/wizard/equipment/soulstone.dm | 145 ++++++++----------
.../modules/mob/living/simple_animal/shade.dm | 8 +-
.../projectiles/guns/ballistic/revolver.dm | 8 +-
5 files changed, 89 insertions(+), 104 deletions(-)
diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm
index e5959de8571..29ffeb9df1b 100644
--- a/code/modules/antagonists/cult/cult_items.dm
+++ b/code/modules/antagonists/cult/cult_items.dm
@@ -212,19 +212,21 @@
if(dash_toggled && !proximity)
jaunt.Teleport(user, target)
return
- if(proximity)
- if(ishuman(target))
- var/mob/living/carbon/human/H = target
- if(H.stat != CONSCIOUS)
- var/obj/item/soulstone/SS = new /obj/item/soulstone(src)
- SS.attack(H, user)
- if(!LAZYLEN(SS.contents))
- qdel(SS)
- if(istype(target, /obj/structure/constructshell) && contents.len)
- var/obj/item/soulstone/SS = contents[1]
- if(istype(SS))
- SS.transfer_soul("CONSTRUCT",target,user)
- qdel(SS)
+ if(!proximity)
+ return
+ if(ishuman(target))
+ var/mob/living/carbon/human/human_target = target
+ if(human_target.stat != CONSCIOUS)
+ var/obj/item/soulstone/stone = new /obj/item/soulstone(src)
+ stone.attack(human_target, user)
+ if(!LAZYLEN(stone.contents))
+ qdel(stone)
+ if(istype(target, /obj/structure/constructshell) && contents.len)
+ var/obj/item/soulstone/stone = contents[1]
+ if(!istype(stone))
+ stone.forceMove(drop_location())
+ else if(!stone.transfer_to_construct(target, user) && !(locate(/mob/living/simple_animal/shade) in stone))
+ qdel(stone)
/datum/action/innate/dash/cult
name = "Rend the Veil"
diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm
index c88e5bbe341..6ee93bd0cbb 100644
--- a/code/modules/antagonists/cult/runes.dm
+++ b/code/modules/antagonists/cult/runes.dm
@@ -318,7 +318,7 @@ structure_check() searches for nearby cultist structures required for the invoca
if(iscyborg(sacrificial))
var/construct_class = show_radial_menu(first_invoker, sacrificial, GLOB.construct_radial_images, require_near = TRUE, tooltips = TRUE)
- if(QDELETED(sacrificial))
+ if(QDELETED(sacrificial) || !construct_class)
return FALSE
sacrificial.grab_ghost()
make_new_construct_from_class(construct_class, THEME_CULT, sacrificial, first_invoker, TRUE, get_turf(src))
@@ -329,7 +329,7 @@ structure_check() searches for nearby cultist structures required for the invoca
var/obj/item/soulstone/stone = new /obj/item/soulstone(get_turf(src))
if(sacrificial.mind && !sacrificial.suiciding)
stone.invisibility = INVISIBILITY_MAXIMUM //so it's not picked up during transfer_soul()
- stone.transfer_soul("FORCE", sacrificial, usr)
+ stone.capture_soul(sacrificial, first_invoker, TRUE)
stone.invisibility = 0
if(sacrificial)
diff --git a/code/modules/antagonists/wizard/equipment/soulstone.dm b/code/modules/antagonists/wizard/equipment/soulstone.dm
index d4d65a33d67..e3485445bb0 100644
--- a/code/modules/antagonists/wizard/equipment/soulstone.dm
+++ b/code/modules/antagonists/wizard/equipment/soulstone.dm
@@ -139,7 +139,7 @@
hot_potato(user)
return
log_combat(user, M, "captured [M.name]'s soul", src)
- transfer_soul("VICTIM", M, user)
+ capture_soul(M, user)
///////////////////Options for using captured souls///////////////////////////////////////
@@ -234,88 +234,72 @@
if(SS.theme == THEME_HOLY && IS_CULTIST(user))
SS.hot_potato(user)
return
- SS.transfer_soul("CONSTRUCT",src,user)
- SS.was_used()
+ SS.transfer_to_construct(src, user)
else
return ..()
-////////////////////////////Proc for moving soul in and out off stone//////////////////////////////////////
+/// Procs for moving soul in and out off stone
+/// transfer the mind of a carbon mob (which is then dusted) into a shade mob inside src. If forced, sacrifical and stat checks are skipped.
+/obj/item/soulstone/proc/capture_soul(mob/living/carbon/victim, mob/user, forced = FALSE)
+ if(!iscarbon(victim)) //TODO: Add sacrifice stoning for non-organics, just because you have no body doesnt mean you dont have a soul
+ return FALSE
+ if(!forced)
+ var/datum/antagonist/cult/C = user?.mind?.has_antag_datum(/datum/antagonist/cult,TRUE)
+ if(C?.cult_team.is_sacrifice_target(victim.mind))
+ to_chat(user, span_cult("\"This soul is mine. SACRIFICE THEM!\""))
+ return FALSE
+ if(contents.len)
+ return FALSE
+ if(!forced && (grab_sleeping ? victim.stat == CONSCIOUS : victim.stat != DEAD))
+ to_chat(user, "[span_userdanger("Capture failed!")]: Kill or maim the victim first!")
+ return FALSE
+ if(victim.client)
+ victim.unequip_everything()
+ init_shade(victim, user)
+ return TRUE
+ else
+ to_chat(user, "[span_userdanger("Capture failed!")]: The soul has already fled its mortal frame. You attempt to bring it back...")
+ return getCultGhost(victim,user)
-/obj/item/soulstone/proc/transfer_soul(choice as text, target, mob/user)
- switch(choice)
- if("FORCE")
- if(!iscarbon(target)) //TODO: Add sacrifice stoning for non-organics, just because you have no body doesnt mean you dont have a soul
- return FALSE
- if(contents.len)
- return FALSE
- var/mob/living/carbon/T = target
- if(T.client != null)
- for(var/obj/item/W in T)
- T.dropItemToGround(W)
- init_shade(T, user)
- return TRUE
- else
- to_chat(user, "[span_userdanger("Capture failed!")]: The soul has already fled its mortal frame. You attempt to bring it back...")
- return getCultGhost(T,user)
+///captures a shade that was previously released from a soulstone.
+/obj/item/soulstone/proc/capture_shade(mob/living/simple_animal/shade/shade, mob/user)
+ if(contents.len)
+ to_chat(user, "[span_userdanger("Capture failed!")]: [src] is full! Free an existing soul to make room.")
+ return FALSE
+ shade.AddComponent(/datum/component/soulstoned, src)
+ if(theme == THEME_HOLY)
+ icon_state = "purified_soulstone2"
+ shade.mind?.remove_antag_datum(/datum/antagonist/cult)
+ if(theme == THEME_WIZARD)
+ icon_state = "mystic_soulstone2"
+ if(theme == THEME_CULT)
+ icon_state = "soulstone2"
+ name = "soulstone: Shade of [shade.real_name]"
+ to_chat(shade, span_notice("Your soul has been captured by [src]. Its arcane energies are reknitting your ethereal form."))
+ if(user != shade)
+ to_chat(user, "[span_info("Capture successful!:")] [shade.real_name]'s soul has been captured and stored within [src].")
+ return TRUE
- if("VICTIM")
- var/mob/living/carbon/human/T = target
- var/datum/antagonist/cult/C = user.mind.has_antag_datum(/datum/antagonist/cult,TRUE)
- if(C?.cult_team.is_sacrifice_target(T.mind))
- to_chat(user, span_cult("\"This soul is mine. SACRIFICE THEM!\""))
- return FALSE
- if(contents.len)
- to_chat(user, "[span_userdanger("Capture failed!")]: [src] is full! Free an existing soul to make room.")
- else
- if((grab_sleeping && T.stat != CONSCIOUS) || (!grab_sleeping && T.stat == DEAD))
- 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...")
- getCultGhost(T,user)
- else
- for(var/obj/item/W in T)
- T.dropItemToGround(W)
- init_shade(T, user, message_user = 1)
- qdel(T)
- else
- to_chat(user, "[span_userdanger("Capture failed!")]: Kill or maim the victim first!")
+///transfer the mind of the shade to a construct mob selected by the user, then deletes both the shade and src.
+/obj/item/soulstone/proc/transfer_to_construct(obj/structure/constructshell/shell, mob/user)
+ var/mob/living/simple_animal/shade/shade = locate() in src
+ if(!shade)
+ to_chat(user, "[span_userdanger("Creation failed!")]: [src] is empty! Go kill someone!")
+ return FALSE
+ var/construct_class = show_radial_menu(user, src, GLOB.construct_radial_images, custom_check = CALLBACK(src, .proc/check_menu, user, shell), require_near = TRUE, tooltips = TRUE)
+ if(!shell || !construct_class)
+ return FALSE
+ make_new_construct_from_class(construct_class, theme, shade, user, FALSE, shell.loc)
+ shade.mind?.remove_antag_datum(/datum/antagonist/cult)
+ qdel(shell)
+ qdel(src)
+ return TRUE
- if("SHADE")
- var/mob/living/simple_animal/shade/T = target
- if(contents.len)
- to_chat(user, "[span_userdanger("Capture failed!")]: [src] is full! Free an existing soul to make room.")
- else
- T.AddComponent(/datum/component/soulstoned, src)
- if(theme == THEME_HOLY)
- icon_state = "purified_soulstone2"
- T.mind?.remove_antag_datum(/datum/antagonist/cult)
- if(theme == THEME_WIZARD)
- icon_state = "mystic_soulstone2"
- if(theme == THEME_CULT)
- icon_state = "soulstone2"
- name = "soulstone: Shade of [T.real_name]"
- to_chat(T, span_notice("Your soul has been captured by [src]. Its arcane energies are reknitting your ethereal form."))
- if(user != T)
- to_chat(user, "[span_info("Capture successful!:")] [T.real_name]'s soul has been captured and stored within [src].")
-
- if("CONSTRUCT")
- var/obj/structure/constructshell/T = target
- var/mob/living/simple_animal/shade/A = locate() in src
- if(A)
- var/construct_class = show_radial_menu(user, src, GLOB.construct_radial_images, custom_check = CALLBACK(src, .proc/check_menu, user), require_near = TRUE, tooltips = TRUE)
- if(!T || !T.loc)
- return
- make_new_construct_from_class(construct_class, theme, A, user, FALSE, T.loc)
- A.mind?.remove_antag_datum(/datum/antagonist/cult)
- qdel(T)
- qdel(src)
- else
- to_chat(user, "[span_userdanger("Creation failed!")]: [src] is empty! Go kill someone!")
-
-/obj/item/soulstone/proc/check_menu(mob/user)
+/obj/item/soulstone/proc/check_menu(mob/user, obj/structure/constructshell/shell)
if(!istype(user))
return FALSE
- if(user.incapacitated() || !user.Adjacent(src))
+ if(user.incapacitated() || !user.is_holding(src) || !user.CanReach(shell, src))
return FALSE
return TRUE
@@ -422,24 +406,23 @@
to_chat(user, "[span_info("Capture successful!:")] [dusted_victim.real_name]'s soul has been ripped from [dusted_victim.p_their()] body and stored within [src].")
-/obj/item/soulstone/proc/getCultGhost(mob/living/carbon/human/T, mob/user)
+/obj/item/soulstone/proc/getCultGhost(mob/living/carbon/victim, mob/user)
var/mob/dead/observer/chosen_ghost
- chosen_ghost = T.get_ghost(TRUE,TRUE) //Try to grab original owner's ghost first
+ chosen_ghost = victim.get_ghost(TRUE,TRUE) //Try to grab original owner's ghost first
if(!chosen_ghost || !chosen_ghost.client) //Failing that, we grab a ghosts
var/list/consenting_candidates = pollGhostCandidates("Would you like to play as a Shade?", "Cultist", ROLE_CULTIST, 50, POLL_IGNORE_SHADE)
if(consenting_candidates.len)
chosen_ghost = pick(consenting_candidates)
- if(!T)
+ if(!victim || user.incapacitated() || !user.is_holding(src) || !user.CanReach(victim, src))
return FALSE
if(!chosen_ghost || !chosen_ghost.client)
to_chat(user, span_danger("There were no spirits willing to become a shade."))
return FALSE
if(contents.len) //If they used the soulstone on someone else in the meantime
return FALSE
- for(var/obj/item/W in T)
- T.dropItemToGround(W)
- init_shade(T, user , shade_controller = chosen_ghost)
- qdel(T)
+ victim.unequip_everything()
+ init_shade(victim, user, shade_controller = chosen_ghost)
+ qdel(victim)
return TRUE
diff --git a/code/modules/mob/living/simple_animal/shade.dm b/code/modules/mob/living/simple_animal/shade.dm
index 8c9a1abecdf..90a162799cd 100644
--- a/code/modules/mob/living/simple_animal/shade.dm
+++ b/code/modules/mob/living/simple_animal/shade.dm
@@ -65,9 +65,9 @@
else if(src != user)
return ..()
-/mob/living/simple_animal/shade/attackby(obj/item/O, mob/user, params) //Marker -Agouri
- if(istype(O, /obj/item/soulstone))
- var/obj/item/soulstone/SS = O
- SS.transfer_soul("SHADE", src, user)
+/mob/living/simple_animal/shade/attackby(obj/item/item, mob/user, params) //Marker -Agouri
+ if(istype(item, /obj/item/soulstone))
+ var/obj/item/soulstone/stone = item
+ stone.capture_shade(src, user)
else
. = ..()
diff --git a/code/modules/projectiles/guns/ballistic/revolver.dm b/code/modules/projectiles/guns/ballistic/revolver.dm
index 48be4771801..77a63f165f8 100644
--- a/code/modules/projectiles/guns/ballistic/revolver.dm
+++ b/code/modules/projectiles/guns/ballistic/revolver.dm
@@ -217,10 +217,10 @@
desc = "To play with this revolver requires wagering your very soul."
/obj/item/gun/ballistic/revolver/russian/soul/shoot_self(mob/living/user)
- ..()
- var/obj/item/soulstone/anybody/revolver/SS = new /obj/item/soulstone/anybody/revolver(get_turf(src))
- if(!SS.transfer_soul("FORCE", user)) //Something went wrong
- qdel(SS)
+ . = ..()
+ var/obj/item/soulstone/anybody/revolver/stone = new /obj/item/soulstone/anybody/revolver(get_turf(src))
+ if(!stone.capture_soul(user, forced = TRUE)) //Something went wrong
+ qdel(stone)
return
user.visible_message(span_danger("[user.name]'s soul is captured by \the [src]!"), span_userdanger("You've lost the gamble! Your soul is forfeit!"))