Skip to content

Commit

Permalink
[MIRROR] Refactors Object Possession into a Component (moar modular, …
Browse files Browse the repository at this point in the history
…less `/mob` vars) [MDB IGNORE] (#25534)

* Refactors Object Possession into a Component (moar modular, less `/mob` vars) (#80160)

## About The Pull Request

We have two verbs that allow any given mob to take control of an object
and move it ephemerally, `/proc/possess()` and `/proc/release()`. These
ones leveraged two vars present on every `/mob`: `name_archive` and
`control_object`. I don't like having vars clog up my VV and this just
injected snowflake behavior in a lot of spots - let's just make it a
component that'll clean everything else up.

This also opens up the ability to have more objects be under mob control
without giving someone verbs that spit out to the blackbox as an admin
verb + logs + message admins but that's a later thing. This just subs in
the behavior in a nice way.

Also, since it's a component, I added a small QoL that we can support
now: A screen alert that allows you to get out of the possession early
without navigating the stat panel for the specific verb. I think it's
neat. You can also trigger the aghost keybind if that's something you
want as well.

Also also, nothing actually ever cleaned up `control_object` by setting
it to null. This means that in the old framework, if a mob got qdelled
during a possession, that would have triggered a hung ref harddel. That
won't happen anymore.
## Why It's Good For The Game

Two less variables taking up crud space in the VSC debugger + view
variables panel. Better behavior injection that is far more reusable.
Component handling this behavior allows for better extensibility of this
function in the future.

![image](https://github.com/tgstation/tgstation/assets/34697715/a84238af-e014-4cff-9b4b-6cbaa36c44fd)
## Changelog
:cl:
admin: Object Possession has been reworked, please report any potential
bugs.
qol: Object Possession should now throw a screen alert for you to
unpossess the object instead of you having to search the stat-panel for
the "release obj" verb. You can still use the verb but it's a lot nicer
now. Aghosting will also work now.
/:cl:

* Refactors Object Possession into a Component (moar modular, less `/mob` vars)

* Refactor hydra quirk

The name_archive var is gone, let's store it in the quirk instead

---------

Co-authored-by: san7890 <[email protected]>
Co-authored-by: Giz <[email protected]>
  • Loading branch information
3 people authored and FFMirrorBot committed Dec 10, 2023
1 parent 4bec96b commit d3958af
Show file tree
Hide file tree
Showing 10 changed files with 173 additions and 81 deletions.
1 change: 1 addition & 0 deletions code/__DEFINES/alerts.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
5 changes: 5 additions & 0 deletions code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
12 changes: 12 additions & 0 deletions code/_onclick/hud/alert.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
127 changes: 127 additions & 0 deletions code/datums/components/object_possession.dm
Original file line number Diff line number Diff line change
@@ -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)
52 changes: 14 additions & 38 deletions code/modules/admin/verbs/possess.dm
Original file line number Diff line number Diff line change
@@ -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")
4 changes: 0 additions & 4 deletions code/modules/mob/mob.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 0 additions & 11 deletions code/modules/mob/mob_defines.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
23 changes: 2 additions & 21 deletions code/modules/mob/mob_movement.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down Expand Up @@ -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()
Expand Down
18 changes: 11 additions & 7 deletions modular_skyrat/modules/hydra/code/neutral.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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."), \
Expand Down
1 change: 1 addition & 0 deletions tgstation.dme
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit d3958af

Please sign in to comment.