Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Soulstones into possessed items #27980

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions code/__DEFINES/dcs/datum_signals.dm
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@
///from base of /datum/local_powernet/proc/power_change()
#define COMSIG_POWERNET_POWER_CHANGE "powernet_power_change"

/// Sent when bodies transfer between shades/shards and constructs
/// Sent when bodies transfer between shades/shards/constructs/possessed items.
/// from base of /datum/component/construct_held_body/proc/transfer_held_body()
#define COMSIG_SHADE_TO_CONSTRUCT_TRANSFER "shade_to_construct_transfer"
#define COMSIG_TRANSFER_HELD_BODY "transfer_held_body"


/// /datum/component/label
Expand Down
3 changes: 3 additions & 0 deletions code/__HELPERS/trait_helpers.dm
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
/// Prevents seeing this item on examine when on a mob, or seeing it in the strip menu. It's like ABSTRACT, without making the item fail to interact in several ways. The item can still be stripped however, combine with no_strip unless you have a reason not to.
#define TRAIT_SKIP_EXAMINE "skip_examine"

///An object that should not be able to be possessed by a player.
#define TRAIT_DO_NOT_POSSESS "do_not_possess"

//****** OBJ TRAITS *****//

///An /obj that should not increase the "depth" of the search for adjacency,
Expand Down
3 changes: 2 additions & 1 deletion code/_globalvars/traits.dm
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_HYPOSPRAY_IMMUNE" = TRAIT_HYPOSPRAY_IMMUNE,
"TRAIT_ITEM_ACTIVE" = TRAIT_ITEM_ACTIVE,
"TRAIT_NO_STRIP" = TRAIT_NO_STRIP,
"TRAIT_SKIP_EXAMINE" = TRAIT_SKIP_EXAMINE
"TRAIT_SKIP_EXAMINE" = TRAIT_SKIP_EXAMINE,
"TRAIT_DO_NOT_POSSESS" = TRAIT_DO_NOT_POSSESS
),

/turf = list(
Expand Down
6 changes: 3 additions & 3 deletions code/datums/components/cult_held_body.dm
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@
/datum/component/construct_held_body/RegisterWithParent()
RegisterSignal(parent, COMSIG_MOB_DEATH, PROC_REF(drop_body))
RegisterSignal(parent, COMSIG_PARENT_QDELETING, PROC_REF(drop_body))
RegisterSignal(parent, COMSIG_SHADE_TO_CONSTRUCT_TRANSFER, PROC_REF(transfer_held_body))
RegisterSignal(parent, COMSIG_TRANSFER_HELD_BODY, PROC_REF(transfer_held_body))

/datum/component/construct_held_body/UnregisterFromParent()
UnregisterSignal(parent, COMSIG_MOB_DEATH)
UnregisterSignal(parent, COMSIG_PARENT_QDELETING)
UnregisterSignal(parent, COMSIG_SHADE_TO_CONSTRUCT_TRANSFER)
UnregisterSignal(parent, COMSIG_TRANSFER_HELD_BODY)

/datum/component/construct_held_body/proc/add_body(atom/movable/new_body)
held_body = new_body
Expand All @@ -41,7 +41,7 @@
held_body = null

/datum/component/construct_held_body/proc/transfer_held_body(mob/living/current_parent, mob/living/new_body_holder)
SIGNAL_HANDLER // COMSIG_SHADE_TO_CONSTRUCT_TRANSFER
SIGNAL_HANDLER // COMSIG_TRANSFER_HELD_BODY
new_body_holder.TakeComponent(src)

/datum/component/construct_held_body/proc/drop_body()
Expand Down
152 changes: 97 additions & 55 deletions code/game/gamemodes/wizard/soulstone.dm
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,19 @@
w_class = WEIGHT_CLASS_TINY
slot_flags = ITEM_SLOT_BELT
origin_tech = "bluespace=4;materials=5"
new_attack_chain = TRUE

/// 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
Expand All @@ -36,14 +32,6 @@

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

Expand All @@ -55,7 +43,6 @@

/obj/item/soulstone/anybody/purified/chaplain
name = "mysterious old shard"
reusable = FALSE

/obj/item/soulstone/pickup(mob/living/user)
. = ..()
Expand Down Expand Up @@ -87,69 +74,69 @@
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
/obj/item/soulstone/pre_attack(atom/target, mob/living/user, params)
if(..())
return FINISH_ATTACK

if(target == user)
return FINISH_ATTACK

if(!can_use(user))
user.Weaken(10 SECONDS)
user.emote("scream")
to_chat(user, "<span class='userdanger'>Your body is wracked with debilitating pain!</span>")
return
return FINISH_ATTACK

if(spent)
to_chat(user, "<span class='warning'>There is no power left in the shard.</span>")
return

if(!ishuman(M)) //If target is not a human
if(!ishuman(target)) //If target is not a human
return ..()

if(!M.mind)
/obj/item/soulstone/attack(mob/living/target, mob/living/carbon/human/user)
if(!target.mind)
to_chat(user, "<span class='warning'>This being has no soul!</span>")
return ..()

if(jobban_isbanned(M, ROLE_CULTIST) || jobban_isbanned(M, ROLE_SYNDICATE))
if(jobban_isbanned(target, ROLE_CULTIST) || jobban_isbanned(target, ROLE_SYNDICATE))
to_chat(user, "<span class='warning'>A mysterious force prevents you from trapping this being's soul.</span>")
return ..()

if(IS_CULTIST(user) && IS_CULTIST(M))
if(IS_CULTIST(user) && IS_CULTIST(target))
to_chat(user, "<span class='cultlarge'>\"Come now, do not capture your fellow's soul.\"</span>")
return ..()

if((M.mind.offstation_role && M.mind.special_role != SPECIAL_ROLE_ERT) || HAS_MIND_TRAIT(M, TRAIT_XENOBIO_SPAWNED_HUMAN))
if((target.mind.offstation_role && target.mind.special_role != SPECIAL_ROLE_ERT) || HAS_MIND_TRAIT(target, TRAIT_XENOBIO_SPAWNED_HUMAN))
to_chat(user, "<span class='warning'>This being's soul seems worthless. Not even the stone will absorb it.</span>")
return ..()

if(optional)
if(!M.ckey)
if(!target.ckey)
to_chat(user, "<span class='warning'>They have no soul!</span>")
return

to_chat(user, "<span class='warning'>You attempt to channel [M]'s soul into [src]. You must give the soul some time to react and stand still...</span>")
to_chat(user, "<span class='warning'>You attempt to channel [target]'s soul into [src]. You must give the soul some time to react and stand still...</span>")

var/mob/player_mob = M
var/ghost = M.get_ghost()
var/mob/living/player_mob = target
var/ghost = target.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 class='warning'>[user] is trying to capture your soul into [src]! Click the button in the top right of the game window to respond.</span>")
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)
var/atom/movable/screen/alert/notify_soulstone/alert = 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)
alert.icon = ui_style2icon(player_client.prefs.UI_style)

// Pass the stuff to the alert itself
A.stone = src
A.stoner = user.real_name
alert.stone = src
alert.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
alert.overlays += src
layer = old_layer
plane = old_plane

Expand All @@ -162,17 +149,14 @@

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)
add_attack_logs(user, target, "Stolestone'd with [name]")
transfer_soul("VICTIM", target, 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))
/obj/item/soulstone/item_interaction(mob/living/user, obj/item/I, list/modifiers)
if(istype(I, /obj/item/storage/bible) && !IS_CULTIST(user) && HAS_MIND_TRAIT(user, TRAIT_HOLY))
if(purified)
return
return ITEM_INTERACT_COMPLETE
to_chat(user, "<span class='notice'>You begin to exorcise [src].</span>")
playsound(src, 'sound/hallucinations/veryfar_noise.ogg', 40, TRUE)
if(do_after(user, 40, target = src))
Expand All @@ -187,7 +171,7 @@
icon_state = "purified_soulstone2"
if(IS_CULTIST(M))
M.mind.remove_antag_datum(/datum/antagonist/cultist, silent_removal = TRUE)
to_chat(M, "<span class='userdanger'>An unfamiliar white light flashes through your mind, cleansing the taint of [GET_CULT_DATA(entity_title1, "Nar'Sie")] \
to_chat(M, "<span class='userdanger'>An unfamiliar white light flashes through your mind, cleansing the taint of [GET_CULT_DATA(entity_title1, "that eldritch horror")] \
and the memories of your time as their servant with it.</span>")
to_chat(M, "<span class='danger'>Assist [user], your saviour, and get vengeance on those who enslaved you!</span>")
else
Expand All @@ -197,10 +181,10 @@
EX.holy = TRUE
EX.icon_state = "shade_angelic"
user.visible_message("<span class='notice'>[user] purifies [src]!</span>", "<span class='notice'>You purify [src]!</span>")

else if(istype(O, /obj/item/melee/cultblade/dagger) && IS_CULTIST(user))
return ITEM_INTERACT_COMPLETE
else if(istype(I, /obj/item/melee/cultblade/dagger) && IS_CULTIST(user))
if(!purified)
return
return ITEM_INTERACT_COMPLETE
to_chat(user, "<span class='notice'>You begin to cleanse [src] of holy magic.</span>")
if(do_after(user, 40, target = src))
remove_filter("ray")
Expand All @@ -218,12 +202,58 @@
EX.holy = FALSE
EX.icon_state = GET_CULT_DATA(shade_icon_state, "shade")
to_chat(user, "<span class='notice'>You have cleansed [src] of holy magic.</span>")
else
..()
return ITEM_INTERACT_COMPLETE
return ..()

/obj/item/soulstone/interact_with_atom(atom/A, mob/living/user, list/modifiers)
if(!isitem(A))
return ..()
if(!can_use(user))
return ..()
var/mob/living/simple_animal/shade/shade = locate(/mob/living/simple_animal/shade) in contents
if(!shade)
to_chat(user, "<span class='warning'>The soulstone contains no souls!</span>")
return ..()
if(HAS_TRAIT(A, TRAIT_DO_NOT_POSSESS))
to_chat(user, "<span class='warning'>Try as you might, you can't seem to force the spirit into [A].</span>")
return ..()
user.visible_message("[user] holds [src] to [A] and begins chanting.")
if(!do_after(user, 4 SECONDS, target = A))
return ..()

var/mob/living/simple_animal/possessed_object/possession = new(A)
possession.was_shade = TRUE
possession.shade_type = shade.type
possession.shade_name = shade.real_name
possession.ckey = shade.ckey
if(shade.mind)
shade.mind.transfer_to(possession)
possession.del_on_death = FALSE
possession.cancel_camera()
SEND_SIGNAL(shade, COMSIG_TRANSFER_HELD_BODY, possession)
qdel(shade)

name = "soulstone"
icon_state = initial(icon_state)
remove_filter("ray")
STOP_PROCESSING(SSobj, src)
animate_rays = FALSE

/obj/item/soulstone/activate_self(mob/living/user)
if(..() || !in_range(src, user))
return

/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)
if(!S)
if(!ishuman(user))
return
var/response = tgui_alert(user, "Sacrifice yourself to [src]? This will kill you and put you at the mercy of whoever holds the stone.", "Sacrifice yourself?", list("Sacrifice me!", "Stay alive"))
if(response != "Sacrifice me!")
return
if(src != user.get_active_hand())
return

user.do_suicide()
return

if(can_use(user))
Expand Down Expand Up @@ -264,11 +294,24 @@
to_chat(A, "<span class='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.</span>")
else
to_chat(A, "<span class='userdanger'>You have been released from your prison, but you are still bound to your [purified ? "saviour" : "creator"]'s will.</span>")
was_used()
remove_filter("ray")
STOP_PROCESSING(SSobj, src)
animate_rays = FALSE

/obj/item/soulstone/suicide_act(mob/living/user)
user.visible_message("<span class='suicide'>[user] kneels down and chants to [src]. It looks like [user.p_theyre()] trying to sacrifice [user.p_themselves()] to [src]!</span>")

if(!use_tool(user, user, 3 SECONDS))
return SHAME

if(!ishuman(user))
return BRUTELOSS

forceMove(user.loc)
init_shade(user, user)
user.death()
return OXYLOSS

///////////////////////////Transferring to constructs/////////////////////////////////////////////////////
/obj/structure/constructshell
name = "empty shell"
Expand All @@ -295,7 +338,6 @@
user.Confused(20 SECONDS)
return
SS.transfer_soul("CONSTRUCT", src, user)
SS.was_used()
else
return ..()

Expand Down Expand Up @@ -410,7 +452,7 @@
else
to_chat(src, "<span class='userdanger'>You are still bound to serve your creator, follow their orders and help them complete their goals at all costs.</span>")

SEND_SIGNAL(shade, COMSIG_SHADE_TO_CONSTRUCT_TRANSFER, src)
SEND_SIGNAL(shade, COMSIG_TRANSFER_HELD_BODY, src)
cancel_camera()
qdel(shell)
qdel(shade)
Expand Down
4 changes: 4 additions & 0 deletions code/game/objects/items/weapons/handcuffs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
else
forceMove(user.drop_location())

/obj/item/restraints/Initialize(mapload)
. = ..()
ADD_TRAIT(src, TRAIT_DO_NOT_POSSESS, ROUNDSTART_TRAIT)

//////////////////////////////
// MARK: HANDCUFFS
//////////////////////////////
Expand Down
10 changes: 8 additions & 2 deletions code/modules/admin/misc_admin_procs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -927,14 +927,20 @@ GLOBAL_VAR_INIT(gamma_ship_location, 1) // 0 = station , 1 = space
var/ask = alert("Are you sure you want to allow [frommob.name]([frommob.key]) to possess [toitem.name]?", "Place ghost in control of item?", "Yes", "No")
if(ask != "Yes")
return TRUE
var/has_do_not_possess = ""
if(HAS_TRAIT(tothing, TRAIT_DO_NOT_POSSESS))
var/double_confirm = alert("[tothing] has TRAIT_DO_NOT_POSSSESS. Things could break if you possess it. Are you REALLY sure?", "Override TRAIT_DO_NOT_POSSESS?", "Yes, break stuff!", "No, play it safe.")
if(double_confirm != "Yes, break stuff!")
return TRUE
has_do_not_possess = " (TRAIT_DO_NOT_POSSESS)"

if(!frommob || !toitem) //make sure the mobs don't go away while we waited for a response
return TRUE

var/mob/living/simple_animal/possessed_object/tomob = new(toitem)

message_admins("<span class='adminnotice'>[key_name_admin(usr)] has put [frommob.ckey] in control of [tomob.name].</span>")
log_admin("[key_name(usr)] stuffed [frommob.ckey] into [tomob.name].")
message_admins("<span class='adminnotice'>[key_name_admin(usr)] has put [frommob.ckey] in control of [tomob.name][has_do_not_possess].</span>")
log_admin("[key_name(usr)] stuffed [frommob.ckey] into [tomob.name][has_do_not_possess].")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Ghost Drag")

tomob.ckey = frommob.ckey
Expand Down
Loading
Loading