diff --git a/code/__DEFINES/alerts.dm b/code/__DEFINES/alerts.dm index cfb408898ec..1933b592d55 100644 --- a/code/__DEFINES/alerts.dm +++ b/code/__DEFINES/alerts.dm @@ -24,6 +24,7 @@ #define ALERT_EMBEDDED_OBJECT "embeddedobject" #define ALERT_SHOES_KNOT "shoealert" #define ALERT_RADIOACTIVE_AREA "radioactive_area" +#define ALERT_UNPOSSESS_OBJECT "unpossess_object" //antag related #define ALERT_HYPNOSIS "hypnosis" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm index 16c3861d8fa..2023493ac95 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm @@ -39,6 +39,11 @@ /// Should we stop the current living movement attempt #define COMSIG_MOB_CLIENT_BLOCK_PRE_LIVING_MOVE COMPONENT_MOVABLE_BLOCK_PRE_MOVE +/// From base of /client/Move(), invoked when a non-living mob is attempting to move: (list/move_args) +#define COMSIG_MOB_CLIENT_PRE_NON_LIVING_MOVE "mob_client_move_possessed_object" + /// Cancels the move attempt + #define COMSIG_MOB_CLIENT_BLOCK_PRE_NON_LIVING_MOVE COMPONENT_MOVABLE_BLOCK_PRE_MOVE + /// From base of /client/Move(): (new_loc, direction) #define COMSIG_MOB_CLIENT_PRE_MOVE "mob_client_pre_move" /// Should always match COMPONENT_MOVABLE_BLOCK_PRE_MOVE as these are interchangeable and used to block movement. diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm index 36c1bf8a799..2f3df52dfd3 100644 --- a/code/_onclick/hud/alert.dm +++ b/code/_onclick/hud/alert.dm @@ -869,6 +869,18 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." carbon_owner.changeNext_move(CLICK_CD_RESIST) carbon_owner.shoes.handle_tying(carbon_owner) +/atom/movable/screen/alert/unpossess_object + name = "Unpossess" + desc = "You are possessing an object. Click this alert to unpossess it." + icon_state = "buckled" + +/atom/movable/screen/alert/unpossess_object/Click() + . = ..() + if(!.) + return + + qdel(owner.GetComponent(/datum/component/object_possession)) + // PRIVATE = only edit, use, or override these if you're editing the system as a whole // Re-render all alerts - also called in /datum/hud/show_hud() because it's needed there diff --git a/code/datums/components/object_possession.dm b/code/datums/components/object_possession.dm new file mode 100644 index 00000000000..c62f0905068 --- /dev/null +++ b/code/datums/components/object_possession.dm @@ -0,0 +1,127 @@ +/// Component that allows a user to control any object as if it were a mob. Does give the user incorporeal movement. +/datum/component/object_possession + dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS + /// Stores a reference to the obj that we are currently possessing. + var/obj/possessed = null + /// Ref to the screen object that is currently being displayed. + var/datum/weakref/screen_alert_ref = null + /** + * back up of the real name during user possession + * + * When a user possesses an object it's real name is set to the user name and this + * stores whatever the real name was previously. When possession ends, the real name + * is reset to this value + */ + var/stashed_name = null + +/datum/component/object_possession/Initialize(obj/target) + . = ..() + if(!isobj(target) || !ismob(parent)) + return COMPONENT_INCOMPATIBLE + + if(!bind_to_new_object(target)) + return COMPONENT_INCOMPATIBLE + + var/mob/user = parent + screen_alert_ref = WEAKREF(user.throw_alert(ALERT_UNPOSSESS_OBJECT, /atom/movable/screen/alert/unpossess_object)) + + // we can expect to be possessed by either a nonliving or a living mob + RegisterSignals(parent, list(COMSIG_MOB_CLIENT_PRE_LIVING_MOVE, COMSIG_MOB_CLIENT_PRE_NON_LIVING_MOVE), PROC_REF(on_move)) + RegisterSignals(parent, list(COMSIG_MOB_GHOSTIZED, COMSIG_KB_ADMIN_AGHOST_DOWN), PROC_REF(end_possession)) + +/datum/component/object_possession/Destroy() + cleanup_object_binding() + UnregisterSignal(parent, list( + COMSIG_KB_ADMIN_AGHOST_DOWN, + COMSIG_MOB_CLIENT_PRE_LIVING_MOVE, + COMSIG_MOB_CLIENT_PRE_NON_LIVING_MOVE, + COMSIG_MOB_GHOSTIZED, + )) + + var/mob/user = parent + var/atom/movable/screen/alert/alert_to_clear = screen_alert_ref?.resolve() + if(!QDELETED(alert_to_clear)) + user.clear_alert(ALERT_UNPOSSESS_OBJECT) + + return ..() + +/datum/component/object_possession/InheritComponent(datum/component/object_possession/old_component, i_am_original, obj/target) + cleanup_object_binding() + if(!bind_to_new_object(target)) + qdel(src) + + stashed_name = old_component.stashed_name + +/// Binds the mob to the object and sets up the naming and everything. +/// Returns FALSE if we don't bind, TRUE if we succeed. +/datum/component/object_possession/proc/bind_to_new_object(obj/target) + if((target.obj_flags & DANGEROUS_POSSESSION) && CONFIG_GET(flag/forbid_singulo_possession)) + to_chat(parent, "[target] is too powerful for you to possess.", confidential = TRUE) + return FALSE + + var/mob/user = parent + + stashed_name = user.real_name + possessed = target + + user.forceMove(target) + user.real_name = target.name + user.name = target.name + user.reset_perspective(target) + + target.AddElement(/datum/element/weather_listener, /datum/weather/ash_storm, ZTRAIT_ASHSTORM, GLOB.ash_storm_sounds) + + RegisterSignal(target, COMSIG_QDELETING, PROC_REF(end_possession)) + return TRUE + +/// Cleans up everything pertinent to the current possessed object. +/datum/component/object_possession/proc/cleanup_object_binding() + if(QDELETED(possessed)) + return + + var/mob/poltergeist = parent + + possessed.RemoveElement(/datum/element/weather_listener, /datum/weather/ash_storm, ZTRAIT_ASHSTORM, GLOB.ash_storm_sounds) + UnregisterSignal(possessed, COMSIG_QDELETING) + + if(!isnull(stashed_name)) + poltergeist.real_name = stashed_name + poltergeist.name = stashed_name + if(ishuman(poltergeist)) + var/mob/living/carbon/human/human_user = poltergeist + human_user.name = human_user.get_visible_name() + + poltergeist.forceMove(get_turf(possessed)) + poltergeist.reset_perspective() + + possessed = null + +/** + * force move the parent object instead of the source mob. + * + * Has no sanity other than checking the possed obj's density. this means it effectively has incorporeal movement, making it only good for badminnery. + * + * We always want to return `COMPONENT_MOVABLE_BLOCK_PRE_MOVE` here regardless + */ +/datum/component/object_possession/proc/on_move(datum/source, new_loc, direct) + SIGNAL_HANDLER + . = COMPONENT_MOVABLE_BLOCK_PRE_MOVE // both signals that invoke this are explicitly tied to listen for this define as the return value + + if(QDELETED(possessed)) + return . + + if(!possessed.density) + possessed.forceMove(get_step(possessed, direct)) + else + step(possessed, direct) + + if(QDELETED(possessed)) + return . + + possessed.setDir(direct) + return . + +/// Just the overall "get me outta here" proc. +/datum/component/object_possession/proc/end_possession(datum/source) + SIGNAL_HANDLER + qdel(src) diff --git a/code/modules/admin/verbs/possess.dm b/code/modules/admin/verbs/possess.dm index 28d76abb101..63304104ab5 100644 --- a/code/modules/admin/verbs/possess.dm +++ b/code/modules/admin/verbs/possess.dm @@ -1,56 +1,32 @@ -/proc/possess(obj/O in world) +/proc/possess(obj/target in world) set name = "Possess Obj" set category = "Object" - if((O.obj_flags & DANGEROUS_POSSESSION) && CONFIG_GET(flag/forbid_singulo_possession)) - to_chat(usr, "[O] is too powerful for you to possess.", confidential = TRUE) - return - - var/turf/T = get_turf(O) + var/result = usr.AddComponent(/datum/component/object_possession, target) - if(T) - log_admin("[key_name(usr)] has possessed [O] ([O.type]) at [AREACOORD(T)]") - message_admins("[key_name(usr)] has possessed [O] ([O.type]) at [AREACOORD(T)]") - else - log_admin("[key_name(usr)] has possessed [O] ([O.type]) at an unknown location") - message_admins("[key_name(usr)] has possessed [O] ([O.type]) at an unknown location") + if(isnull(result)) // trigger a safety movement just in case we yonk + usr.forceMove(get_turf(usr)) + return - if(!usr.control_object) //If you're not already possessing something... - usr.name_archive = usr.real_name + var/turf/target_turf = get_turf(target) + var/message = "[key_name(usr)] has possessed [target] ([target.type]) at [AREACOORD(target_turf)]" + message_admins(message) + log_admin(message) - usr.forceMove(O) - usr.real_name = O.name - usr.name = O.name - usr.reset_perspective(O) - usr.control_object = O - O.AddElement(/datum/element/weather_listener, /datum/weather/ash_storm, ZTRAIT_ASHSTORM, GLOB.ash_storm_sounds) BLACKBOX_LOG_ADMIN_VERB("Possess Object") /proc/release() set name = "Release Obj" set category = "Object" - if(!usr.control_object) //lest we are banished to the nullspace realm. - return - - if(usr.name_archive) //if you have a name archived - usr.real_name = usr.name_archive - usr.name_archive = "" - usr.name = usr.real_name - if(ishuman(usr)) - var/mob/living/carbon/human/H = usr - H.name = H.get_visible_name() - - usr.control_object.RemoveElement(/datum/element/weather_listener, /datum/weather/ash_storm, ZTRAIT_ASHSTORM, GLOB.ash_storm_sounds) - usr.forceMove(get_turf(usr.control_object)) - usr.reset_perspective() - usr.control_object = null + qdel(usr.GetComponent(/datum/component/object_possession)) BLACKBOX_LOG_ADMIN_VERB("Release Object") -/proc/givetestverbs(mob/M in GLOB.mob_list) +/proc/give_possession_verbs(mob/dude in GLOB.mob_list) set desc = "Give this guy possess/release verbs" set category = "Debug" set name = "Give Possessing Verbs" - add_verb(M, GLOBAL_PROC_REF(possess)) - add_verb(M, GLOBAL_PROC_REF(release)) + + add_verb(dude, GLOBAL_PROC_REF(possess)) + add_verb(dude, GLOBAL_PROC_REF(release)) BLACKBOX_LOG_ADMIN_VERB("Give Possessing Verbs") diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index c9e41734f15..dc080a22535 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -1604,10 +1604,6 @@ /mob/vv_edit_var(var_name, var_value) switch(var_name) - if(NAMEOF(src, control_object)) - var/obj/O = var_value - if(!istype(O) || (O.obj_flags & DANGEROUS_POSSESSION)) - return FALSE if(NAMEOF(src, machine)) set_machine(var_value) . = TRUE diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index ffc343f491d..be218941aea 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -89,14 +89,6 @@ /// What is the mobs real name (name is overridden for disguises etc) var/real_name = null - /** - * back up of the real name during admin possession - * - * If an admin possesses an object it's real name is set to the admin name and this - * stores whatever the real name was previously. When possession ends, the real name - * is reset to this value - */ - var/name_archive //For admin things like possession /// Default body temperature var/bodytemperature = BODYTEMP_NORMAL //310.15K / 98.6F @@ -158,9 +150,6 @@ /// Can they interact with station electronics var/has_unlimited_silicon_privilege = FALSE - ///Used by admins to possess objects. All mobs should have this var - var/obj/control_object - ///Calls relay_move() to whatever this is set to when the mob tries to move var/atom/movable/remote_control diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index a5aa8c2c145..4189d08f3bd 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -8,25 +8,6 @@ if(!iscyborg(mob) && mob.stat == CONSCIOUS) mob.dropItemToGround(mob.get_active_held_item()) return - -/** - * force move the control_object of your client mob - * - * Used in admin possession and called from the client Move proc - * ensures the possessed object moves and not the admin mob - * - * Has no sanity other than checking density - */ -/client/proc/Move_object(direct) - if(mob?.control_object) - if(mob.control_object.density) - step(mob.control_object,direct) - if(!mob.control_object) - return - mob.control_object.setDir(direct) - else - mob.control_object.forceMove(get_step(mob.control_object,direct)) - /** * Move a client in a direction * @@ -76,9 +57,9 @@ return FALSE if(HAS_TRAIT(mob, TRAIT_NO_TRANSFORM)) return FALSE //This is sorta the goto stop mobs from moving trait - if(mob.control_object) - return Move_object(direct) if(!isliving(mob)) + if(SEND_SIGNAL(mob, COMSIG_MOB_CLIENT_PRE_NON_LIVING_MOVE, new_loc, direct) & COMSIG_MOB_CLIENT_BLOCK_PRE_NON_LIVING_MOVE) + return FALSE return mob.Move(new_loc, direct) if(mob.stat == DEAD) mob.ghostize() diff --git a/modular_skyrat/modules/hydra/code/neutral.dm b/modular_skyrat/modules/hydra/code/neutral.dm index fe4165a33bf..4c92eccc10d 100644 --- a/modular_skyrat/modules/hydra/code/neutral.dm +++ b/modular_skyrat/modules/hydra/code/neutral.dm @@ -7,6 +7,8 @@ lose_text = span_danger("All of your minds become singular.") medical_record_text = "There are multiple heads and personalities affixed to one body." icon = FA_ICON_HORSE_HEAD + // remember what the name was before activation + var/original_name /datum/quirk/hydra/add(client/client_source) var/mob/living/carbon/human/hydra = quirk_holder @@ -31,17 +33,19 @@ /datum/action/innate/hydrareset/Activate() var/mob/living/carbon/human/hydra = owner - if(!hydra.name_archive) // sets the archived 'real' name if not set. - hydra.name_archive = hydra.real_name - hydra.real_name = hydra.name_archive + var/datum/quirk/hydra/hydra_quirk = hydra.get_quirk(/datum/quirk/hydra) + if(!hydra_quirk.original_name) // sets the archived 'real' name if not set. + hydra_quirk.original_name = hydra.real_name + hydra.real_name = hydra_quirk.original_name hydra.visible_message(span_notice("[hydra.name] pushes all three heads forwards; they seem to be talking as a collective."), \ - span_notice("You are now talking as [hydra.name_archive]!"), ignored_mobs=owner) + span_notice("You are now talking as [hydra_quirk.original_name]!"), ignored_mobs=owner) /datum/action/innate/hydra/Activate() //Oops, all hydra! var/mob/living/carbon/human/hydra = owner - if(!hydra.name_archive) // sets the archived 'real' name if not set. - hydra.name_archive = hydra.real_name - var/list/names = splittext(hydra.name_archive,"-") + var/datum/quirk/hydra/hydra_quirk = hydra.get_quirk(/datum/quirk/hydra) + if(!hydra_quirk.original_name) // sets the archived 'real' name if not set. + hydra_quirk.original_name = hydra.real_name + var/list/names = splittext(hydra_quirk.original_name,"-") var/selhead = input("Who would you like to speak as?","Heads:") in names hydra.real_name = selhead hydra.visible_message(span_notice("[hydra.name] pulls the rest of their heads back; and puts [selhead]'s forward."), \ diff --git a/tgstation.dme b/tgstation.dme index 5878df2e0ec..58462295ce3 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1193,6 +1193,7 @@ #include "code\datums\components\multiple_lives.dm" #include "code\datums\components\mutant_hands.dm" #include "code\datums\components\nuclear_bomb_operator.dm" +#include "code\datums\components\object_possession.dm" #include "code\datums\components\omen.dm" #include "code\datums\components\on_hit_effect.dm" #include "code\datums\components\onwear_mood.dm"