diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm index 4f56b07fe7..f95ff93ac3 100644 --- a/code/__DEFINES/components.dm +++ b/code/__DEFINES/components.dm @@ -36,8 +36,10 @@ ////////////////////////////////////////////////////////////////// // /datum signals -#define COMSIG_COMPONENT_ADDED "component_added" //when a component is added to a datum: (/datum/component) -#define COMSIG_COMPONENT_REMOVING "component_removing" //before a component is removed from a datum because of RemoveComponent: (/datum/component) +#define COMSIG_COMPONENT_ADDED "component_added" //sent to the new datum parent when a component is added to them: (/datum/component) +#define COMSIG_COMPONENT_REMOVING "component_removing" //sent to the datum parent before a component is removed from them because of RemoveComponent: (/datum/component) +#define COMSIG_COMPONENT_UNREGISTER_PARENT "component_unregister_parent" //sent to the component itself when unregistered from a parent +#define COMSIG_COMPONENT_REGISTER_PARENT "component_register_parent" //sent to the component itself when registered to a parent #define COMSIG_PARENT_PREQDELETED "parent_preqdeleted" //before a datum's Destroy() is called: (force), returning a nonzero value will cancel the qdel operation #define COMSIG_PARENT_QDELETING "parent_qdeleting" //just before a datum's Destroy() is called: (force), at this point none of the other components chose to interrupt qdel and Destroy will be called @@ -140,19 +142,22 @@ #define HEARING_RAW_MESSAGE 4 /* #define HEARING_RADIO_FREQ 5 #define HEARING_SPANS 6 - #define HEARING_MESSAGE_MODE 7 + #define HEARING_MESSAGE_MODE 7 #define HEARING_SOURCE 8*/ #define COMSIG_MOVABLE_DISPOSING "movable_disposing" //called when the movable is added to a disposal holder object for disposal movement: (obj/structure/disposalholder/holder, obj/machinery/disposal/source) #define COMSIG_MOVABLE_TELEPORTED "movable_teleported" //from base of do_teleport(): (channel, turf/origin, turf/destination) // /mind signals -#define COMSIG_MIND_TRANSFER "mind_transfer" //from base of mind/transfer_to(): (new_character, old_character) +#define COMSIG_PRE_MIND_TRANSFER "pre_mind_transfer" //from base of mind/transfer_to() before it's done: (new_character, old_character) + #define COMPONENT_STOP_MIND_TRANSFER 1 //stops the mind transfer from happening. +#define COMSIG_MIND_TRANSFER "mind_transfer" //from base of mind/transfer_to() when it's done: (new_character, old_character) // /mob signals #define COMSIG_MOB_EXAMINATE "mob_examinate" //from base of /mob/verb/examinate(): (atom/A) #define COMPONENT_ALLOW_EXAMINE 1 #define COMSIG_MOB_DEATH "mob_death" //from base of mob/death(): (gibbed) -#define COMSIG_MOB_GHOSTIZE "mob_ghostize" //from base of mob/Ghostize(): (can_reenter_corpse) + #define COMPONENT_BLOCK_DEATH_BROADCAST 1 //stops the death from being broadcasted in deadchat. +#define COMSIG_MOB_GHOSTIZE "mob_ghostize" //from base of mob/Ghostize(): (can_reenter_corpse, special, penalize) #define COMPONENT_BLOCK_GHOSTING 1 #define COMSIG_MOB_ALLOWED "mob_allowed" //from base of obj/allowed(mob/M): (/obj) returns bool, if TRUE the mob has id access to the obj #define COMSIG_MOB_RECEIVE_MAGIC "mob_receive_magic" //from base of mob/anti_magic_check(): (mob/user, magic, holy, tinfoil, chargecost, self, protection_sources) @@ -164,7 +169,9 @@ #define COMSIG_MOB_ITEM_AFTERATTACK "mob_item_afterattack" //from base of obj/item/afterattack(): (atom/target, mob/user, proximity_flag, click_parameters) #define COMSIG_MOB_ATTACK_RANGED "mob_attack_ranged" //from base of mob/RangedAttack(): (atom/A, params) #define COMSIG_MOB_THROW "mob_throw" //from base of /mob/throw_item(): (atom/target) -#define COMSIG_MOB_KEY_CHANGE "mob_key_change" //from base of /mob/transfer_ckey() +#define COMSIG_MOB_KEY_CHANGE "mob_key_change" //from base of /mob/transfer_ckey(): (new_character, old_character) +#define COMSIG_MOB_PRE_PLAYER_CHANGE "mob_pre_player_change" //sent to the target mob from base of /mob/transfer_ckey() and /mind/transfer_to(): (our_character, their_character) +// #define COMPONENT_STOP_MIND_TRANSFER 1 #define COMSIG_MOB_UPDATE_SIGHT "mob_update_sight" //from base of /mob/update_sight(): () #define COMSIG_MOB_SAY "mob_say" // from /mob/living/say(): (proc args list) #define COMPONENT_UPPERCASE_SPEECH 1 diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm index f938a70308..72b9681f91 100644 --- a/code/datums/components/_component.dm +++ b/code/datums/components/_component.dm @@ -53,7 +53,7 @@ // If you want/expect to be moving the component around between parents, use this to register on the parent for signals /datum/component/proc/RegisterWithParent() - return + SEND_SIGNAL(src, COMSIG_COMPONENT_REGISTER_PARENT) //CITADEL EDIT /datum/component/proc/Initialize(...) return @@ -85,7 +85,7 @@ UnregisterFromParent() /datum/component/proc/UnregisterFromParent() - return + SEND_SIGNAL(src, COMSIG_COMPONENT_UNREGISTER_PARENT) //CITADEL EDIT /datum/proc/RegisterSignal(datum/target, sig_type_or_types, proctype, override = FALSE) if(QDELETED(src) || QDELETED(target)) diff --git a/code/datums/components/bane.dm b/code/datums/components/bane.dm index 84f8010270..bdfcfed517 100644 --- a/code/datums/components/bane.dm +++ b/code/datums/components/bane.dm @@ -19,12 +19,14 @@ src.damage_multiplier = damage_multiplier /datum/component/bane/RegisterWithParent() + . = ..() if(speciestype) RegisterSignal(parent, COMSIG_ITEM_AFTERATTACK, .proc/speciesCheck) else RegisterSignal(parent, COMSIG_ITEM_AFTERATTACK, .proc/mobCheck) /datum/component/bane/UnregisterFromParent() + . = ..() UnregisterSignal(parent, COMSIG_ITEM_AFTERATTACK) /datum/component/bane/proc/speciesCheck(obj/item/source, atom/target, mob/user, proximity_flag, click_parameters) diff --git a/code/datums/components/bouncy.dm b/code/datums/components/bouncy.dm index f6a2a89195..c7ca85455b 100644 --- a/code/datums/components/bouncy.dm +++ b/code/datums/components/bouncy.dm @@ -21,9 +21,11 @@ RegisterSignal(parent, bounce, .proc/bounce_up) /datum/component/bouncy/RegisterWithParent() + . = ..() RegisterSignal(parent, bounce_signals, .proc/bounce_up) /datum/component/bouncy/UnregisterFromParent() + . = ..() UnregisterSignal(parent, bounce_signals) /datum/component/bouncy/proc/bounce_up(datum/source) diff --git a/code/datums/components/decal.dm b/code/datums/components/decal.dm index 641dbdb1cf..60317797a7 100644 --- a/code/datums/components/decal.dm +++ b/code/datums/components/decal.dm @@ -17,6 +17,7 @@ apply() /datum/component/decal/RegisterWithParent() + . = ..() if(first_dir) RegisterSignal(parent, COMSIG_ATOM_DIR_CHANGE, .proc/rotate_react) if(cleanable) @@ -25,6 +26,7 @@ RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/examine) /datum/component/decal/UnregisterFromParent() + . = ..() UnregisterSignal(parent, list(COMSIG_ATOM_DIR_CHANGE, COMSIG_COMPONENT_CLEAN_ACT, COMSIG_PARENT_EXAMINE)) /datum/component/decal/Destroy() diff --git a/code/datums/components/fantasy/_fantasy.dm b/code/datums/components/fantasy/_fantasy.dm index 86e016784a..9e8493b6f4 100644 --- a/code/datums/components/fantasy/_fantasy.dm +++ b/code/datums/components/fantasy/_fantasy.dm @@ -30,11 +30,13 @@ return ..() /datum/component/fantasy/RegisterWithParent() + . = ..() var/obj/item/master = parent originalName = master.name modify() /datum/component/fantasy/UnregisterFromParent() + . = ..() unmodify() /datum/component/fantasy/InheritComponent(datum/component/fantasy/newComp, original, list/arguments) diff --git a/code/datums/components/igniter.dm b/code/datums/components/igniter.dm index b40383e828..13944b1200 100644 --- a/code/datums/components/igniter.dm +++ b/code/datums/components/igniter.dm @@ -9,6 +9,7 @@ src.fire_stacks = fire_stacks /datum/component/igniter/RegisterWithParent() + . = ..() if(ismachinery(parent) || isstructure(parent) || isgun(parent)) // turrets, etc RegisterSignal(parent, COMSIG_PROJECTILE_ON_HIT, .proc/projectile_hit) else if(isitem(parent)) @@ -17,6 +18,7 @@ RegisterSignal(parent, COMSIG_HOSTILE_ATTACKINGTARGET, .proc/hostile_attackingtarget) /datum/component/igniter/UnregisterFromParent() + . = ..() UnregisterSignal(parent, list(COMSIG_ITEM_AFTERATTACK, COMSIG_HOSTILE_ATTACKINGTARGET, COMSIG_PROJECTILE_ON_HIT)) /datum/component/igniter/proc/item_afterattack(obj/item/source, atom/target, mob/user, proximity_flag, click_parameters) diff --git a/code/datums/components/knockback.dm b/code/datums/components/knockback.dm index b4fcaa2dd8..988a0e575e 100644 --- a/code/datums/components/knockback.dm +++ b/code/datums/components/knockback.dm @@ -10,6 +10,7 @@ src.throw_anchored = throw_anchored /datum/component/knockback/RegisterWithParent() + . = ..() if(ismachinery(parent) || isstructure(parent) || isgun(parent)) // turrets, etc RegisterSignal(parent, COMSIG_PROJECTILE_ON_HIT, .proc/projectile_hit) else if(isitem(parent)) @@ -18,6 +19,7 @@ RegisterSignal(parent, COMSIG_HOSTILE_ATTACKINGTARGET, .proc/hostile_attackingtarget) /datum/component/knockback/UnregisterFromParent() + . = ..() UnregisterSignal(parent, list(COMSIG_ITEM_AFTERATTACK, COMSIG_HOSTILE_ATTACKINGTARGET, COMSIG_PROJECTILE_ON_HIT)) /datum/component/knockback/proc/item_afterattack(obj/item/source, atom/target, mob/user, proximity_flag, click_parameters) diff --git a/code/datums/components/lifesteal.dm b/code/datums/components/lifesteal.dm index c7a78e10a3..9d62d32866 100644 --- a/code/datums/components/lifesteal.dm +++ b/code/datums/components/lifesteal.dm @@ -10,6 +10,7 @@ src.flat_heal = flat_heal /datum/component/lifesteal/RegisterWithParent() + . = ..() if(isgun(parent)) RegisterSignal(parent, COMSIG_PROJECTILE_ON_HIT, .proc/projectile_hit) else if(isitem(parent)) @@ -18,6 +19,7 @@ RegisterSignal(parent, COMSIG_HOSTILE_ATTACKINGTARGET, .proc/hostile_attackingtarget) /datum/component/lifesteal/UnregisterFromParent() + . = ..() UnregisterSignal(parent, list(COMSIG_ITEM_AFTERATTACK, COMSIG_HOSTILE_ATTACKINGTARGET, COMSIG_PROJECTILE_ON_HIT)) /datum/component/lifesteal/proc/item_afterattack(obj/item/source, atom/target, mob/user, proximity_flag, click_parameters) diff --git a/code/datums/components/nanites.dm b/code/datums/components/nanites.dm index 362961a24f..0ef13b514b 100644 --- a/code/datums/components/nanites.dm +++ b/code/datums/components/nanites.dm @@ -34,6 +34,7 @@ cloud_sync() /datum/component/nanites/RegisterWithParent() + . = ..() RegisterSignal(parent, COMSIG_HAS_NANITES, .proc/confirm_nanites) RegisterSignal(parent, COMSIG_NANITE_UI_DATA, .proc/nanite_ui_data) RegisterSignal(parent, COMSIG_NANITE_GET_PROGRAMS, .proc/get_programs) @@ -57,6 +58,7 @@ RegisterSignal(parent, COMSIG_NANITE_SIGNAL, .proc/receive_signal) /datum/component/nanites/UnregisterFromParent() + . = ..() UnregisterSignal(parent, list(COMSIG_HAS_NANITES, COMSIG_NANITE_UI_DATA, COMSIG_NANITE_GET_PROGRAMS, diff --git a/code/datums/components/orbiter.dm b/code/datums/components/orbiter.dm index efa0fd14d5..05174c196b 100644 --- a/code/datums/components/orbiter.dm +++ b/code/datums/components/orbiter.dm @@ -20,12 +20,14 @@ begin_orbit(orbiter, radius, clockwise, rotation_speed, rotation_segments, pre_rotation) /datum/component/orbiter/RegisterWithParent() + . = ..() var/atom/target = parent while(ismovableatom(target)) RegisterSignal(target, COMSIG_MOVABLE_MOVED, .proc/move_react) target = target.loc /datum/component/orbiter/UnregisterFromParent() + . = ..() var/atom/target = parent while(ismovableatom(target)) UnregisterSignal(target, COMSIG_MOVABLE_MOVED) diff --git a/code/datums/components/shrapnel.dm b/code/datums/components/shrapnel.dm index a911221f26..4d1fe21b95 100644 --- a/code/datums/components/shrapnel.dm +++ b/code/datums/components/shrapnel.dm @@ -13,10 +13,12 @@ src.override_projectile_range = override_projectile_range /datum/component/shrapnel/RegisterWithParent() + . = ..() if(ismachinery(parent) || isstructure(parent) || isgun(parent)) // turrets, etc RegisterSignal(parent, COMSIG_PROJECTILE_ON_HIT, .proc/projectile_hit) /datum/component/shrapnel/UnregisterFromParent() + . = ..() UnregisterSignal(parent, list(COMSIG_PROJECTILE_ON_HIT)) /datum/component/shrapnel/proc/projectile_hit(atom/fired_from, atom/movable/firer, atom/target, Angle) diff --git a/code/datums/components/summoning.dm b/code/datums/components/summoning.dm index 552959603d..61718301b3 100644 --- a/code/datums/components/summoning.dm +++ b/code/datums/components/summoning.dm @@ -24,6 +24,7 @@ src.faction = faction /datum/component/summoning/RegisterWithParent() + . = ..() if(ismachinery(parent) || isstructure(parent) || isgun(parent)) // turrets, etc RegisterSignal(parent, COMSIG_PROJECTILE_ON_HIT, .proc/projectile_hit) else if(isitem(parent)) @@ -32,6 +33,7 @@ RegisterSignal(parent, COMSIG_HOSTILE_ATTACKINGTARGET, .proc/hostile_attackingtarget) /datum/component/summoning/UnregisterFromParent() + . = ..() UnregisterSignal(parent, list(COMSIG_ITEM_AFTERATTACK, COMSIG_HOSTILE_ATTACKINGTARGET, COMSIG_PROJECTILE_ON_HIT)) /datum/component/summoning/proc/item_afterattack(obj/item/source, atom/target, mob/user, proximity_flag, click_parameters) diff --git a/code/datums/components/tactical.dm b/code/datums/components/tactical.dm index 5917fc3009..ba028e2fd5 100644 --- a/code/datums/components/tactical.dm +++ b/code/datums/components/tactical.dm @@ -9,10 +9,12 @@ src.allowed_slot = allowed_slot /datum/component/tactical/RegisterWithParent() + . = ..() RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/modify) RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/unmodify) /datum/component/tactical/UnregisterFromParent() + . = ..() UnregisterSignal(parent, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED)) unmodify() diff --git a/code/datums/components/virtual_reality.dm b/code/datums/components/virtual_reality.dm index 7bad836e47..2f0405af2e 100644 --- a/code/datums/components/virtual_reality.dm +++ b/code/datums/components/virtual_reality.dm @@ -1,128 +1,245 @@ +/** + * The virtual reality turned component. + * Originally created to overcome issues of mob polymorphing locking the player inside virtual reality + * and allow for a more "immersive" virtual reality in a virtual reality experience. + * It relies on comically complex order of logic, expect things to break if procs such as mind/transfer_to() are revamped. + * In short, a barebone not so hardcoded VR framework. + * If you plan to add more devices that make use of this component, remember to isolate their code outta here where possible. + */ /datum/component/virtual_reality can_transfer = TRUE - var/datum/mind/mastermind // where is my mind t. pixies + //the player's mind (not the parent's), should something happen to them or to their mob. + var/datum/mind/mastermind + //the current mob's mind, which we need to keep track for mind transfer. var/datum/mind/current_mind - var/obj/machinery/vr_sleeper/vr_sleeper + //the action datum used by the mob to quit the vr session. var/datum/action/quit_vr/quit_action + //This one's name should be self explainatory, currently used for emags. var/you_die_in_the_game_you_die_for_real = FALSE - var/datum/component/virtual_reality/inception //The component works on a very fragile link betwixt mind, ckey and death. + //Used to allow people to play recursively playing vr while playing vr without many issues. + var/datum/component/virtual_reality/level_below + var/datum/component/virtual_reality/level_above + //Used to stop the component from executing certain functions that'd cause us some issues otherwise. + //FALSE if there is a connected player, otherwise TRUE. + var/session_paused = TRUE + //Used to stop unwarranted behaviour from happening in cases where the master mind transference is unsupported. Set on Initialize(). + var/allow_mastermind_transfer = FALSE -/datum/component/virtual_reality/Initialize(mob/M, obj/machinery/vr_sleeper/gaming_pod, yolo = FALSE, new_char = TRUE) - if(!ismob(parent) || !istype(M)) +/datum/component/virtual_reality/Initialize(yolo = FALSE, _allow_mastermind_transfer = FALSE) + var/mob/M = parent + if(!istype(M) || !M.mind) return COMPONENT_INCOMPATIBLE - var/mob/vr_M = parent - mastermind = M.mind - RegisterSignal(M, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING), .proc/game_over) - RegisterSignal(M, COMSIG_MOB_KEY_CHANGE, .proc/switch_player) - RegisterSignal(mastermind, COMSIG_MIND_TRANSFER, .proc/switch_player) you_die_in_the_game_you_die_for_real = yolo - quit_action = new() - if(gaming_pod) - vr_sleeper = gaming_pod - RegisterSignal(vr_sleeper, COMSIG_ATOM_EMAG_ACT, .proc/you_only_live_once) - RegisterSignal(vr_sleeper, COMSIG_MACHINE_EJECT_OCCUPANT, .proc/revert_to_reality) - vr_M.ckey = M.ckey - var/datum/component/virtual_reality/clusterfk = M.GetComponent(/datum/component/virtual_reality) - if(clusterfk && !clusterfk.inception) - clusterfk.inception = src - SStgui.close_user_uis(M, src) + allow_mastermind_transfer = _allow_mastermind_transfer + quit_action = new + +/datum/component/virtual_reality/Destroy() + QDEL_NULL(quit_action) + if(level_above) + level_above.level_below = null + level_above = null + if(level_below) + level_below.level_above = null + level_below = null + return ..() /datum/component/virtual_reality/RegisterWithParent() + . = ..() var/mob/M = parent current_mind = M.mind + if(!quit_action) + quit_action = new quit_action.Grant(M) - RegisterSignal(quit_action, COMSIG_ACTION_TRIGGER, .proc/revert_to_reality) + RegisterSignal(quit_action, COMSIG_ACTION_TRIGGER, .proc/action_trigger) RegisterSignal(M, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING), .proc/game_over) RegisterSignal(M, COMSIG_MOB_GHOSTIZE, .proc/be_a_quitter) - RegisterSignal(M, COMSIG_MOB_KEY_CHANGE, .proc/pass_me_the_remote) - RegisterSignal(current_mind, COMSIG_MIND_TRANSFER, .proc/pass_me_the_remote) - mastermind.current.audiovisual_redirect = M - if(vr_sleeper) - vr_sleeper.vr_mob = M + RegisterSignal(M, COMSIG_MOB_KEY_CHANGE, .proc/on_player_transfer) + RegisterSignal(current_mind, COMSIG_MIND_TRANSFER, .proc/on_player_transfer) + RegisterSignal(current_mind, COMSIG_PRE_MIND_TRANSFER, .proc/pre_player_transfer) + if(mastermind?.current) + mastermind.current.audiovisual_redirect = M /datum/component/virtual_reality/UnregisterFromParent() - quit_action.Remove(parent) + . = ..() + if(quit_action) + quit_action.Remove(parent) + UnregisterSignal(quit_action, COMSIG_ACTION_TRIGGER) UnregisterSignal(parent, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING, COMSIG_MOB_KEY_CHANGE, COMSIG_MOB_GHOSTIZE)) - UnregisterSignal(current_mind, COMSIG_MIND_TRANSFER) - UnregisterSignal(quit_action, COMSIG_ACTION_TRIGGER) + UnregisterSignal(current_mind, list(COMSIG_MIND_TRANSFER, COMSIG_PRE_MIND_TRANSFER)) current_mind = null - mastermind.current.audiovisual_redirect = null + if(mastermind?.current) + mastermind.current.audiovisual_redirect = null -/datum/component/virtual_reality/proc/switch_player(datum/source, mob/new_mob, mob/old_mob) - if(vr_sleeper || !new_mob.mind) - // Machineries currently don't deal up with the occupant being polymorphed et similar... Or did something fuck up? - revert_to_reality() - return - old_mob.audiovisual_redirect = null - new_mob.audiovisual_redirect = parent - -/datum/component/virtual_reality/proc/action_trigger(datum/signal_source, datum/action/source) - if(source != quit_action) - return COMPONENT_ACTION_BLOCK_TRIGGER - revert_to_reality(signal_source) +/** + * Called when attempting to connect a mob to a virtual reality mob. + * This will return FALSE if the mob is without player or dead. TRUE otherwise + */ +/datum/component/virtual_reality/proc/connect(mob/M) + var/mob/vr_M = parent + if(!M.mind || M.stat == DEAD || !vr_M.mind || vr_M.stat == DEAD) + return FALSE + var/datum/component/virtual_reality/VR = M.GetComponent(/datum/component/virtual_reality) + if(VR) + VR.level_below = src + level_above = VR + M.transfer_ckey(vr_M, FALSE) + mastermind = M.mind + mastermind.current.audiovisual_redirect = parent + RegisterSignal(mastermind, COMSIG_PRE_MIND_TRANSFER, .proc/switch_player) + RegisterSignal(M, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING), .proc/game_over) + RegisterSignal(M, COMSIG_MOB_PRE_PLAYER_CHANGE, .proc/player_hijacked) + SStgui.close_user_uis(vr_M, src) + session_paused = FALSE + return TRUE +/** + * emag_act() hook. Makes the game deadlier, killing the mastermind mob too should the parent die. + */ /datum/component/virtual_reality/proc/you_only_live_once() - if(you_die_in_the_game_you_die_for_real || vr_sleeper?.only_current_user_can_interact) + if(you_die_in_the_game_you_die_for_real) return FALSE you_die_in_the_game_you_die_for_real = TRUE return TRUE -/datum/component/virtual_reality/proc/pass_me_the_remote(datum/source, mob/new_mob) - if(new_mob == mastermind.current) - revert_to_reality(source) - return TRUE - new_mob.TakeComponent(src) - return TRUE +/** + * Called when the mastermind mind is transferred to another mob. + * This is pretty much just going to simply quit the session until machineries support polymorphed occupants etcetera. + */ +/datum/component/virtual_reality/proc/switch_player(datum/source, mob/new_mob, mob/old_mob) + if(session_paused) + return + if(!allow_mastermind_transfer) + quit() + return COMPONENT_STOP_MIND_TRANSFER + UnregisterSignal(old_mob, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING, COMSIG_MOB_PRE_PLAYER_CHANGE)) + RegisterSignal(new_mob, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING), .proc/game_over) + RegisterSignal(new_mob, COMSIG_MOB_PRE_PLAYER_CHANGE, .proc/player_hijacked) + old_mob.audiovisual_redirect = null + new_mob.audiovisual_redirect = parent +/** + * Called to stop the player mind from being transferred should the new mob happen to be one of our masterminds'. + * Since the target's mind.current is going to be null'd in the mind transfer process, + * This has to be done in a different signal proc than on_player_transfer(), by then the mastermind.current will be null. + */ +/datum/component/virtual_reality/proc/pre_player_transfer(datum/source, mob/new_mob, mob/old_mob) + if(!mastermind || session_paused) + return + if(new_mob == mastermind.current) + quit() + return COMPONENT_STOP_MIND_TRANSFER + if(!level_above) + return + var/datum/component/virtual_reality/VR = level_above + while(VR) + if(VR.mastermind.current == new_mob) + VR.quit() //this will revert the ckey back to new_mob. + return COMPONENT_STOP_MIND_TRANSFER + VR = VR.level_above + +/** + * Called when someone or something else is somewhat about to replace the mastermind's mob key somehow. + * And potentially lock the player in a broken virtual reality plot. Not really something to be proud of. + */ +/datum/component/virtual_reality/proc/player_hijacked(datum/source, mob/our_character, mob/their_character) + if(session_paused) + return + if(!their_character) + quit(cleanup = TRUE) + return + var/will_it_be_handled_in_their_pre_player_transfer = FALSE + var/datum/component/virtual_reality/VR = src + while(VR) + if(VR.parent == their_character) + will_it_be_handled_in_their_pre_player_transfer = TRUE + break + VR = VR.level_below + if(!will_it_be_handled_in_their_pre_player_transfer) //it's not the player playing shenanigeans, abandon all ships. + quit(cleanup = TRUE) + +/** + * Takes care of moving the component from a mob to another when their mind or ckey is transferred. + * The very reason this component even exists (else one would be stuck playing as a monky if monkyified) + */ +/datum/component/virtual_reality/proc/on_player_transfer(datum/source, mob/new_mob, mob/old_mob) + new_mob.TakeComponent(src) + +/** + * Required for the component to be transferable from mob to mob. + */ /datum/component/virtual_reality/PostTransfer() if(!ismob(parent)) return COMPONENT_INCOMPATIBLE +/** + *The following procs simply acts as hooks for quit(), since components do not use callbacks anymore + */ +/datum/component/virtual_reality/proc/action_trigger(datum/signal_source, datum/action/source) + quit() + return COMPONENT_ACTION_BLOCK_TRIGGER + /datum/component/virtual_reality/proc/revert_to_reality(datum/source) - quit_it() + quit() /datum/component/virtual_reality/proc/game_over(datum/source) - quit_it(TRUE, TRUE) + quit(you_die_in_the_game_you_die_for_real, TRUE) + return COMPONENT_BLOCK_DEATH_BROADCAST -/datum/component/virtual_reality/proc/be_a_quitter(datum/source, can_reenter_corpse) - quit_it() - return COMPONENT_BLOCK_GHOSTING +/datum/component/virtual_reality/proc/be_a_quitter(datum/source, can_reenter_corpse, special = FALSE, penalize = FALSE) + if(!special) + quit() + return COMPONENT_BLOCK_GHOSTING -/datum/component/virtual_reality/proc/virtual_reality_in_a_virtual_reality(mob/player, killme = FALSE, datum/component/virtual_reality/yo_dawg) +/datum/component/virtual_reality/proc/machine_destroyed(datum/source) + quit(cleanup = TRUE) + +/** + * Takes care of deleting itself, moving the player back to the mastermind's current and queueing the parent for deletion. + * It supports nested virtual realities by recursively calling vr_in_a_vr(), which in turns calls quit(), + * up to the deepest level, where the ckey will be transferred back to our mastermind's mob instead. + * The above operation is skipped when session_paused is TRUE (ergo no player in control of the current mob). + * vars: + * * deathcheck is used to kill the master, you want this FALSE unless for stuff that doesn't involve emagging. + * * cleanup is used to queue the parent for the next vr_clean_master's run, where they'll be deleted should they be dead. + * * mob/override is used for the recursive virtual reality explained above and shouldn't be used outside of vr_in_a_vr(). + */ +/datum/component/virtual_reality/proc/quit(deathcheck = FALSE, cleanup = FALSE, mob/override) var/mob/M = parent - quit_it(FALSE, killme, player, yo_dawg) - yo_dawg.inception = null - if(killme) - M.death(FALSE) - -/datum/component/virtual_reality/proc/quit_it(deathcheck = FALSE, cleanup = FALSE, mob/override) - var/mob/M = parent - var/mob/dreamer = override ? override : mastermind.current - if(!mastermind) - to_chat(M, "You feel a dreadful sensation, something terrible happened. You try to wake up, but you find yourself unable to...") - else - var/key_transfer = FALSE - if(inception?.parent) - inception.virtual_reality_in_a_virtual_reality(dreamer, cleanup, src) + if(!session_paused) + session_paused = TRUE + var/mob/dreamer = override || mastermind.current + if(!dreamer) //This shouldn't happen. + stack_trace("virtual reality component quit() called without a mob to transfer the parent ckey to.") + to_chat(M, "You feel a dreadful sensation, something terrible happened. You try to wake up, but you find yourself unable to...") + qdel(src) + return + if(level_below?.parent) + level_below.vr_in_a_vr(dreamer, deathcheck, (deathcheck && cleanup)) else - key_transfer = TRUE - if(key_transfer) M.transfer_ckey(dreamer, FALSE) - dreamer.stop_sound_channel(CHANNEL_HEARTBEAT) - dreamer.audiovisual_redirect = null - if(deathcheck && you_die_in_the_game_you_die_for_real) - to_chat(mastermind, "You feel everything fading away...") - dreamer.death(FALSE) - if(cleanup) - var/obj/effect/vr_clean_master/cleanbot = locate() in get_area(M) - if(cleanbot) - LAZYADD(cleanbot.corpse_party, M) - if(vr_sleeper) - vr_sleeper.vr_mob = null - vr_sleeper = null - qdel(src) + if(deathcheck) + to_chat(dreamer, "You feel everything fading away...") + dreamer.death(FALSE) + mastermind.current.audiovisual_redirect = null + if(!cleanup) + if(level_above) + level_above.level_below = null + level_above = null + UnregisterSignal(mastermind.current, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING, COMSIG_MOB_PRE_PLAYER_CHANGE)) + UnregisterSignal(mastermind, COMSIG_PRE_MIND_TRANSFER) + mastermind = null + if(cleanup) + var/obj/effect/vr_clean_master/cleanbot = locate() in get_area(M) + if(cleanbot) + LAZYOR(cleanbot.corpse_party, M) + qdel(src) -/datum/component/virtual_reality/Destroy() - var/datum/action/quit_vr/delet_me = quit_action - . = ..() - qdel(delet_me) \ No newline at end of file +/** + * Used for recursive virtual realities shenanigeans and should be called only through the above proc. + */ +/datum/component/virtual_reality/proc/vr_in_a_vr(mob/player, deathcheck = FALSE, lethal_cleanup = FALSE) + var/mob/M = parent + quit(deathcheck, lethal_cleanup, player) + M.audiovisual_redirect = null + if(lethal_cleanup) + M.death(FALSE) diff --git a/code/datums/components/wet_floor.dm b/code/datums/components/wet_floor.dm index 38b17993d8..d6c5c0bf83 100644 --- a/code/datums/components/wet_floor.dm +++ b/code/datums/components/wet_floor.dm @@ -34,10 +34,12 @@ last_process = world.time /datum/component/wet_floor/RegisterWithParent() + . = ..() RegisterSignal(parent, COMSIG_TURF_IS_WET, .proc/is_wet) RegisterSignal(parent, COMSIG_TURF_MAKE_DRY, .proc/dry) /datum/component/wet_floor/UnregisterFromParent() + . = ..() UnregisterSignal(parent, list(COMSIG_TURF_IS_WET, COMSIG_TURF_MAKE_DRY)) /datum/component/wet_floor/Destroy() diff --git a/code/datums/mind.dm b/code/datums/mind.dm index 0ded5f9912..d1b4e51a7d 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -88,6 +88,9 @@ /datum/mind/proc/transfer_to(mob/new_character, var/force_key_move = 0) var/old_character = current + var/signals = SEND_SIGNAL(new_character, COMSIG_MOB_PRE_PLAYER_CHANGE, new_character, old_character) | SEND_SIGNAL(src, COMSIG_PRE_MIND_TRANSFER, new_character, old_character) + if(signals & COMPONENT_STOP_MIND_TRANSFER) + return if(current) // remove ourself from our old body's mind variable current.mind = null SStgui.on_transfer(current, new_character) @@ -125,7 +128,6 @@ transfer_martial_arts(new_character) if(active || force_key_move) new_character.key = key //now transfer the key to link the client to our new body - SEND_SIGNAL(src, COMSIG_MIND_TRANSFER, new_character, old_character) //CIT CHANGE - makes arousal update when transfering bodies if(isliving(new_character)) //New humans and such are by default enabled arousal. Let's always use the new mind's prefs. @@ -134,6 +136,8 @@ L.canbearoused = L.client.prefs.arousable //Technically this should make taking over a character mean the body gain the new minds setting... L.update_arousal_hud() //Removes the old icon + SEND_SIGNAL(src, COMSIG_MIND_TRANSFER, new_character, old_character) + /datum/mind/proc/store_memory(new_text) if((length(memory) + length(new_text)) <= MAX_MESSAGE_LEN) memory += "[new_text]
" diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm index ff7383cd9e..5f44fccdac 100644 --- a/code/game/machinery/_machinery.dm +++ b/code/game/machinery/_machinery.dm @@ -181,8 +181,9 @@ Class Procs: if(isliving(A)) var/mob/living/L = A L.update_canmove() - SEND_SIGNAL(src, COMSIG_MACHINE_EJECT_OCCUPANT, occupant) - occupant = null + if(occupant) + SEND_SIGNAL(src, COMSIG_MACHINE_EJECT_OCCUPANT, occupant) + occupant = null /obj/machinery/proc/can_be_occupant(atom/movable/am) return occupant_typecache ? is_type_in_typecache(am, occupant_typecache) : isliving(am) diff --git a/code/modules/VR/vr_mob.dm b/code/modules/VR/vr_mob.dm index 5c0cea9f60..5f2caffc76 100644 --- a/code/modules/VR/vr_mob.dm +++ b/code/modules/VR/vr_mob.dm @@ -40,5 +40,5 @@ /datum/action/quit_vr/Trigger() //this merely a trigger for /datum/component/virtual_reality . = ..() - if(!.) + if(.) //The component was not there to block the trigger. Remove(owner) diff --git a/code/modules/VR/vr_sleeper.dm b/code/modules/VR/vr_sleeper.dm index 72cbdc1409..7cda24d98b 100644 --- a/code/modules/VR/vr_sleeper.dm +++ b/code/modules/VR/vr_sleeper.dm @@ -52,18 +52,16 @@ flags_1 = NODECONSTRUCT_1 only_current_user_can_interact = TRUE -/obj/machinery/vr_sleeper/hugbox/emag_act(mob/user) - return SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT) - /obj/machinery/vr_sleeper/emag_act(mob/user) . = ..() if(!(obj_flags & EMAGGED)) return - obj_flags |= EMAGGED - you_die_in_the_game_you_die_for_real = TRUE - sparks.start() - addtimer(CALLBACK(src, .proc/emagNotify), 150) - return TRUE + if(!only_current_user_can_interact) + obj_flags |= EMAGGED + you_die_in_the_game_you_die_for_real = TRUE + sparks.start() + addtimer(CALLBACK(src, .proc/emagNotify), 150) + return TRUE /obj/machinery/vr_sleeper/update_icon() icon_state = "[initial(icon_state)][state_open ? "-open" : ""]" @@ -76,7 +74,7 @@ return ..() /obj/machinery/vr_sleeper/MouseDrop_T(mob/target, mob/user) - if(user.stat || user.lying || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser()) + if(user.lying || !iscarbon(target) || !Adjacent(target) || !user.canUseTopic(src, BE_CLOSE, TRUE, NO_TK)) return close_machine(target) @@ -91,26 +89,25 @@ return switch(action) if("vr_connect") - var/mob/living/carbon/human/human_occupant = occupant - if(human_occupant && human_occupant.mind && usr == occupant) - - to_chat(occupant, "Transferring to virtual reality...") - if(vr_mob && (!istype(vr_mob) || !vr_mob.InCritical()) && !vr_mob.GetComponent(/datum/component/virtual_reality)) - vr_mob.AddComponent(/datum/component/virtual_reality, human_occupant, src, you_die_in_the_game_you_die_for_real) - to_chat(vr_mob, "Transfer successful! You are now playing as [vr_mob] in VR!") - else + var/mob/M = occupant + if(M?.mind && M == usr) + to_chat(M, "Transferring to virtual reality...") + var/datum/component/virtual_reality/VR + if(vr_mob) + VR = vr_mob.GetComponent(/datum/component/virtual_reality) + if(!(VR?.connect(M))) if(allow_creating_vr_mobs) - to_chat(occupant, "Virtual avatar not found, attempting to create one...") + to_chat(occupant, "Virtual avatar [vr_mob ? "corrupted" : "missing"], attempting to create one...") var/obj/effect/landmark/vr_spawn/V = get_vr_spawnpoint() var/turf/T = get_turf(V) if(T) - SStgui.close_user_uis(occupant, src) new_player(occupant, T, V.vr_outfit) - to_chat(vr_mob, "Transfer successful! You are now playing as [vr_mob] in VR!") else to_chat(occupant, "Virtual world misconfigured, aborting transfer") else to_chat(occupant, "The virtual world does not support the creation of new virtual avatars, aborting transfer") + else + to_chat(vr_mob, "Transfer successful! You are now playing as [vr_mob] in VR!") . = TRUE if("delete_avatar") if(!occupant || usr == occupant) @@ -157,17 +154,31 @@ for(var/obj/effect/landmark/vr_spawn/V in GLOB.landmarks_list) GLOB.vr_spawnpoints[V.vr_category] = V -/obj/machinery/vr_sleeper/proc/new_player(mob/living/carbon/human/H, location, datum/outfit/outfit, transfer = TRUE) - if(!H) +/obj/machinery/vr_sleeper/proc/new_player(mob/M, location, datum/outfit/outfit, transfer = TRUE) + if(!M) return cleanup_vr_mob() vr_mob = new virtual_mob_type(location) - if(vr_mob.build_virtual_character(H, outfit)) - var/mob/living/carbon/human/vr_H = vr_mob - vr_H.updateappearance(TRUE, TRUE, TRUE) - if(!transfer || !H.mind) - return - vr_mob.AddComponent(/datum/component/virtual_reality, H, src, you_die_in_the_game_you_die_for_real) + if(vr_mob.build_virtual_character(M, outfit) && iscarbon(vr_mob)) + var/mob/living/carbon/C = vr_mob + C.updateappearance(TRUE, TRUE, TRUE) + var/datum/component/virtual_reality/VR = vr_mob.AddComponent(/datum/component/virtual_reality, you_die_in_the_game_you_die_for_real) + if(VR.connect(M)) + RegisterSignal(VR, COMSIG_COMPONENT_UNREGISTER_PARENT, .proc/unset_vr_mob) + RegisterSignal(VR, COMSIG_COMPONENT_REGISTER_PARENT, .proc/set_vr_mob) + if(!only_current_user_can_interact) + VR.RegisterSignal(src, COMSIG_ATOM_EMAG_ACT, /datum/component/virtual_reality.proc/you_only_live_once) + VR.RegisterSignal(src, COMSIG_MACHINE_EJECT_OCCUPANT, /datum/component/virtual_reality.proc/revert_to_reality) + VR.RegisterSignal(src, COMSIG_PARENT_QDELETING, /datum/component/virtual_reality.proc/machine_destroyed) + to_chat(vr_mob, "Transfer successful! You are now playing as [vr_mob] in VR!") + else + to_chat(M, "Transfer failed! virtual reality data likely corrupted!") + +/obj/machinery/vr_sleeper/proc/unset_vr_mob(datum/component/virtual_reality/VR) + vr_mob = null + +/obj/machinery/vr_sleeper/proc/set_vr_mob(datum/component/virtual_reality/VR) + vr_mob = VR.parent /obj/machinery/vr_sleeper/proc/cleanup_vr_mob() if(vr_mob) @@ -222,6 +233,7 @@ qdel(C) for (var/A in corpse_party) var/mob/M = A - if(get_area(M) == vr_area && M.stat == DEAD) + if(M && M.stat == DEAD && get_area(M) == vr_area) qdel(M) + corpse_party -= M addtimer(CALLBACK(src, .proc/clean_up), 3 MINUTES) diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index fbbd43bbe1..5bdfd174b5 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -265,7 +265,7 @@ Works together with spawning an observer, noted above. /mob/proc/ghostize(can_reenter_corpse = TRUE, special = FALSE, penalize = FALSE) penalize = suiciding || penalize // suicide squad. - if(!key || cmptext(copytext(key,1,2),"@") || (!special && SEND_SIGNAL(src, COMSIG_MOB_GHOSTIZE, can_reenter_corpse) & COMPONENT_BLOCK_GHOSTING)) + if(!key || cmptext(copytext(key,1,2),"@") || (SEND_SIGNAL(src, COMSIG_MOB_GHOSTIZE, can_reenter_corpse, special, penalize) & COMPONENT_BLOCK_GHOSTING)) return //mob has no key, is an aghost or some component hijacked. stop_sound_channel(CHANNEL_HEARTBEAT) //Stop heartbeat sounds because You Are A Ghost Now var/mob/dead/observer/ghost = new(src) // Transfer safety to observer spawning proc. diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm index 0ff418d628..8345ef916d 100644 --- a/code/modules/mob/living/death.dm +++ b/code/modules/mob/living/death.dm @@ -62,12 +62,8 @@ unset_machine() timeofdeath = world.time tod = STATION_TIME_TIMESTAMP("hh:mm:ss") - var/turf/T = get_turf(src) for(var/obj/item/I in contents) I.on_mob_death(src, gibbed) - if(mind && mind.name && mind.active && !istype(T.loc, /area/ctf)) - var/rendered = "[mind.name] has died at [get_area_name(T)]." - deadchat_broadcast(rendered, follow_target = src, turf_target = T, message_type=DEADCHAT_DEATHRATTLE) if(mind) mind.store_memory("Time of death: [tod]", 0) GLOB.alive_mob_list -= src @@ -89,7 +85,12 @@ addtimer(CALLBACK(src, .proc/med_hud_set_status), (DEFIB_TIME_LIMIT * 10) + 1) stop_pulling() - SEND_SIGNAL(src, COMSIG_MOB_DEATH, gibbed) + var/signal = SEND_SIGNAL(src, COMSIG_MOB_DEATH, gibbed) + + var/turf/T = get_turf(src) + if(mind && mind.name && mind.active && !istype(T.loc, /area/ctf) && !(signal & COMPONENT_BLOCK_DEATH_BROADCAST)) + var/rendered = "[mind.name] has died at [get_area_name(T)]." + deadchat_broadcast(rendered, follow_target = src, turf_target = T, message_type=DEADCHAT_DEATHRATTLE) if (client) client.move_delay = initial(client.move_delay) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index d4528fb91f..a47ea9d5a0 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -465,9 +465,10 @@ mob/visible_message(message, self_message, blind_message, vision_distance = DEFA /mob/proc/transfer_ckey(mob/new_mob, send_signal = TRUE) if(!ckey || !new_mob) CRASH("transfer_ckey() called [ckey ? "" : "on a ckey-less mob[new_mob ? "" : " and "]"][new_mob ? "" : "without a valid mob target"]!") + SEND_SIGNAL(new_mob, COMSIG_MOB_PRE_PLAYER_CHANGE, new_mob, src) + new_mob.ckey = ckey if(send_signal) SEND_SIGNAL(src, COMSIG_MOB_KEY_CHANGE, new_mob, src) - new_mob.ckey = ckey return TRUE /mob/verb/cancel_camera()