diff --git a/code/__defines/dcs/signals.dm b/code/__defines/dcs/signals.dm index 71b4524ad5..5ad7a43718 100644 --- a/code/__defines/dcs/signals.dm +++ b/code/__defines/dcs/signals.dm @@ -322,6 +322,8 @@ #define COMSIG_MOB_ITEM_ATTACK_QDELETED "mob_item_attack_qdeleted" ///from base of mob/RangedAttack(): (atom/A, params) #define COMSIG_MOB_ATTACK_RANGED "mob_attack_ranged" +///from base of obj/item/dropped(): (obj/item) +#define COMSIG_MOB_DROPPED_ITEM "mob_dropped_item" ///from base of /mob/throw_item(): (atom/target) #define COMSIG_MOB_THROW "mob_throw" ///from base of /mob/verb/examinate(): (atom/target) @@ -348,6 +350,10 @@ ///from base of mob/swap_hand(): (obj/item) #define COMSIG_MOB_SWAP_HANDS "mob_swap_hands" #define COMPONENT_BLOCK_SWAP (1<<0) +/// From base of /mob/proc/reset_perspective() : () +#define COMSIG_MOB_RESET_PERSPECTIVE "mob_reset_perspective" +/// from base of /client/proc/set_eye() : (atom/old_eye, atom/new_eye) +#define COMSIG_CLIENT_SET_EYE "client_set_eye" // /mob/living signals @@ -685,6 +691,8 @@ #define COMSIG_JOB_RECEIVED "job_received" ///When the mob's dna and species have been fully applied #define COMSIG_HUMAN_DNA_FINALIZED "human_dna_finished" +///from /proc/domutcheck(): () +#define COMSIG_MOB_DNA_MUTATION "mob_dna_mutation" // Organ specific signals @@ -841,6 +849,10 @@ ///from base of datum/component/two_handed/proc/unwield(mob/living/carbon/user): (/mob/user) #define COMSIG_TWOHANDED_UNWIELD "twohanded_unwield" +// /datum/component/remote_view signals +/// Signal that can be sent from the mob remote viewing, the viewed mob, or object being used to view to forcibly end all related remote viewing components +#define COMSIG_REMOTE_VIEW_CLEAR "remote_view_clear_viewers" + // /datum/action signals ///from base of datum/action/proc/Trigger(): (datum/action) diff --git a/code/__defines/flags.dm b/code/__defines/flags.dm index 0f07cef74a..6a3e6e1ce3 100644 --- a/code/__defines/flags.dm +++ b/code/__defines/flags.dm @@ -35,6 +35,7 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204 #define NOREACT (1<<6) // Reagents don't react inside this container. #define OVERLAY_QUEUED (1<<7)// Atom queued to SSoverlay for COMPILE_OVERLAYS #define IS_BUSY (1<<8) // Atom has a TASK_TARGET_EXCLUSIVE do_after with it as the target. +#define REMOTEVIEW_ON_ENTER (1<<9) // Object starts a remoteview of itself for any mob that enters it with a client. Items will automatically handle their own remoteview, and ignore this. #define ATOM_INITIALIZED (1<<23) // Atom has been initialized. Using a flag instead of a variable saves ~25mb total. //Flags for items (equipment) - Used in /obj/item/var/item_flags diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index 06bb3b966a..1a9572350d 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -265,7 +265,7 @@ A.ShiftClick(src) return /atom/proc/ShiftClick(var/mob/user) - if(user.client && user.client.eye == user) + if(user.client && !user.is_remote_viewing()) user.examinate(src) return diff --git a/code/_onclick/hud/ai.dm b/code/_onclick/hud/ai.dm index 00b772abac..546dbee8ef 100644 --- a/code/_onclick/hud/ai.dm +++ b/code/_onclick/hud/ai.dm @@ -1,23 +1,3 @@ -/atom/movable/screen/ai/multicam/Click() - if(..()) - return - var/mob/living/silicon/ai/ai_user = usr - ai_user.toggle_multicam() - -/atom/movable/screen/ai/add_multicam/Click() - if(..()) - return - var/mob/living/silicon/ai/ai_user = usr - ai_user.drop_new_multicam() - -/atom/movable/screen/ai/up/Click() - var/mob/living/silicon/ai/ai_user = usr - ai_user.zMove(UP) - -/atom/movable/screen/ai/down/Click() - var/mob/living/silicon/ai/ai_user = usr - ai_user.zMove(DOWN) - /mob/living/silicon/ai/create_mob_hud(datum/hud/HUD, apply_to_client = TRUE) ..() @@ -146,7 +126,8 @@ HUD.adding += using //Multicamera mode - using = new /atom/movable/screen/ai/multicam() // special + + using = new /atom/movable/screen() using.name = "Multicamera Mode" using.icon = HUD.ui_style using.icon_state = "multicam" @@ -155,7 +136,7 @@ HUD.adding += using //Add multicamera camera - using = new /atom/movable/screen/ai/add_multicam() // special + using = new /atom/movable/screen() using.name = "New Camera" using.icon = HUD.ui_style using.icon_state = "new_cam" @@ -164,16 +145,16 @@ HUD.adding += using //Up and Down - using = new /atom/movable/screen/ai/up() // special - using.name = "Move Upwards" + using = new /atom/movable/screen() + using.name = "Move Up" using.icon = HUD.ui_style using.icon_state = "up" using.screen_loc = ui_ai_updown using.layer = SCREEN_LAYER HUD.adding += using - using = new /atom/movable/screen/ai/down() // special - using.name = "Move Downwards" + using = new /atom/movable/screen() + using.name = "Move Down" using.icon = HUD.ui_style using.icon_state = "down" using.screen_loc = ui_ai_updown diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index 059ae39c88..77722d250d 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -495,10 +495,16 @@ else if (issilicon(usr)) var/mob/living/silicon/u = usr u.pose() + if("move upwards") usr.up() + if("Move Up") // AI version + usr.zMove(UP) + if("move downwards") usr.down() + if("Move Down") // AI version + usr.zMove(DOWN) if("use held item on self") var/atom/movable/screen/useself/s = src @@ -622,6 +628,16 @@ var/mob/living/silicon/ai/ai_user = usr ai_user.view_images() + if("Multicamera Mode") + if(isAI(usr)) + var/mob/living/silicon/ai/ai_user = usr + ai_user.toggle_multicam() + + if("New Camera") + if(isAI(usr)) + var/mob/living/silicon/ai/ai_user = usr + ai_user.drop_new_multicam() + if("shadekin status") var/turf/T = get_turf(usr) if(T) diff --git a/code/_onclick/other_mobs.dm b/code/_onclick/other_mobs.dm index af3718ada2..d510b2a9f4 100644 --- a/code/_onclick/other_mobs.dm +++ b/code/_onclick/other_mobs.dm @@ -57,7 +57,7 @@ if(istype(gloves,/obj/item/clothing/gloves/telekinetic)) var/obj/item/clothing/gloves/telekinetic/TKG = gloves TKG.use_grip_power(src,TRUE) - if(client.eye != src) // Extremely bad exploits if allowed to TK while remote viewing + if(is_remote_viewing()) // Extremely bad exploits if allowed to TK while remote viewing to_chat(src, TK_DENIED_MESSAGE) else if(get_dist(src, A) > TK_MAXRANGE) to_chat(src, TK_OUTRANGED_MESSAGE) diff --git a/code/_onclick/telekinesis.dm b/code/_onclick/telekinesis.dm index 7fadb38a0a..0402e65d9f 100644 --- a/code/_onclick/telekinesis.dm +++ b/code/_onclick/telekinesis.dm @@ -97,7 +97,7 @@ if(isobj(target) && !isturf(target.loc)) return - if(user.client.eye != user) // Extremely bad exploits if allowed to TK while remote viewing + if(user.is_remote_viewing()) // Extremely bad exploits if allowed to TK while remote viewing to_chat(user, TK_DENIED_MESSAGE) return diff --git a/code/datums/components/machinery/disposal_connection.dm b/code/datums/components/machinery/disposal_connection.dm index 35b5e2f3c5..2283eea2ac 100644 --- a/code/datums/components/machinery/disposal_connection.dm +++ b/code/datums/components/machinery/disposal_connection.dm @@ -79,11 +79,6 @@ for(var/atom/movable/AM in packet) expelled_items += AM AM.forceMove(disposal_owner) - // Handle client eye we get a black screen between trunk and eject if we have delay on ejection! - if(ismob(AM)) - var/mob/M = AM - if(M.client) - M.client.eye = disposal_owner var/datum/gas_mixture/gas = new() gas.copy_from(packet.gas) qdel(packet) diff --git a/code/datums/components/recursive_move.dm b/code/datums/components/recursive_move.dm index e76a0cdc4f..494a533a6f 100644 --- a/code/datums/components/recursive_move.dm +++ b/code/datums/components/recursive_move.dm @@ -4,7 +4,7 @@ * Previously there was a system where COMSIG_OBSERVER_MOVE was always recursively propogated, but that was unnecessary bloat. */ /datum/component/recursive_move - dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS //This makes it so pretty much nothing happens when a duplicate component is created since we don't actually override InheritComponent + dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS //This makes it so pretty much nothing happens when a duplicate component is created since we only use it to regenerate our parent list var/atom/movable/holder var/list/parents = list() var/noparents = FALSE @@ -17,6 +17,12 @@ if(!QDELETED(src)) setup_parents() +/datum/component/recursive_move/InheritComponent(datum/component/recursive_move/C, i_am_original) + if(!i_am_original) + return + reset_parents() + setup_parents() + /datum/component/recursive_move/proc/setup_parents() SIGNAL_HANDLER if(length(parents)) // safety check just incase this was called without clearing diff --git a/code/datums/components/remote_view.dm b/code/datums/components/remote_view.dm new file mode 100644 index 0000000000..dbb8cd4455 --- /dev/null +++ b/code/datums/components/remote_view.dm @@ -0,0 +1,352 @@ +/** + * Use this if you need to remote view something. Remote view will end if you move or the remote view target is deleted. Cleared automatically if another remote view begins. + */ +/datum/component/remote_view + VAR_PROTECTED/mob/host_mob + VAR_PROTECTED/atom/remote_view_target + VAR_PROTECTED/forbid_movement = TRUE + +/datum/component/remote_view/allow_moving + forbid_movement = FALSE + +/datum/component/remote_view/Initialize(atom/focused_on) + . = ..() + if(!ismob(parent)) + return COMPONENT_INCOMPATIBLE + // Safety check, focus on ourselves if the target is deleted, and flag any movement to end the view. + host_mob = parent + if(QDELETED(focused_on)) + focused_on = host_mob + forbid_movement = TRUE + // Begin remoteview + host_mob.reset_perspective(focused_on) // Must be done before registering the signals + if(forbid_movement) + RegisterSignal(host_mob, COMSIG_MOVABLE_MOVED, PROC_REF(handle_hostmob_moved)) + else + RegisterSignal(host_mob, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(handle_hostmob_moved)) + RegisterSignal(host_mob, COMSIG_MOB_RESET_PERSPECTIVE, PROC_REF(on_reset_perspective)) + RegisterSignal(host_mob, COMSIG_MOB_LOGOUT, PROC_REF(handle_endview)) + RegisterSignal(host_mob, COMSIG_MOB_DEATH, PROC_REF(handle_endview)) + RegisterSignal(host_mob, COMSIG_REMOTE_VIEW_CLEAR, PROC_REF(handle_endview)) + // Recursive move component fires this, we only want it to handle stuff like being inside a paicard when releasing turf lock + if(isturf(focused_on)) + RegisterSignal(host_mob, COMSIG_OBSERVER_MOVED, PROC_REF(handle_recursive_moved)) + // Focus on remote view + remote_view_target = focused_on + if(host_mob != remote_view_target) // Some items just offset our view, so we set ourselves as the view target, don't double dip if so! + RegisterSignal(remote_view_target, COMSIG_QDELETING, PROC_REF(handle_endview)) + RegisterSignal(remote_view_target, COMSIG_MOB_RESET_PERSPECTIVE, PROC_REF(on_remotetarget_reset_perspective)) + RegisterSignal(remote_view_target, COMSIG_REMOTE_VIEW_CLEAR, PROC_REF(handle_endview)) + +/datum/component/remote_view/Destroy(force) + . = ..() + if(forbid_movement) + UnregisterSignal(host_mob, COMSIG_MOVABLE_MOVED) + else + UnregisterSignal(host_mob, COMSIG_MOVABLE_Z_CHANGED) + UnregisterSignal(host_mob, COMSIG_MOB_RESET_PERSPECTIVE) + UnregisterSignal(host_mob, COMSIG_MOB_LOGOUT) + UnregisterSignal(host_mob, COMSIG_MOB_DEATH) + UnregisterSignal(host_mob, COMSIG_REMOTE_VIEW_CLEAR) + if(isturf(remote_view_target)) + UnregisterSignal(host_mob, COMSIG_OBSERVER_MOVED) + // Cleanup remote view + if(host_mob != remote_view_target) + UnregisterSignal(remote_view_target, COMSIG_QDELETING) + UnregisterSignal(remote_view_target, COMSIG_MOB_RESET_PERSPECTIVE) + UnregisterSignal(remote_view_target, COMSIG_REMOTE_VIEW_CLEAR) + host_mob = null + remote_view_target = null + +/datum/component/remote_view/proc/handle_hostmob_moved(atom/source, atom/oldloc, direction, forced, movetime) + SIGNAL_HANDLER + PROTECTED_PROC(TRUE) + if(!host_mob) + return + end_view() + qdel(src) + +/datum/component/remote_view/proc/handle_recursive_moved(atom/source, atom/oldloc, atom/new_loc) + SIGNAL_HANDLER + PROTECTED_PROC(TRUE) + ASSERT(isturf(remote_view_target)) + // This signal handler is for recursive move decoupling us from /datum/component/remote_view/mob_holding_item's turf focusing when dropped in an item like a paicard + // This signal is only hooked when we focus on a turf. Check the subtype for more info, this horrorshow took several days to make consistently behave. + if(!host_mob) + return + end_view() + qdel(src) + +/datum/component/remote_view/proc/handle_endview(datum/source) + SIGNAL_HANDLER + PRIVATE_PROC(TRUE) + if(!host_mob) + return + end_view() + qdel(src) + +/datum/component/remote_view/proc/on_reset_perspective(datum/source) + SIGNAL_HANDLER + PRIVATE_PROC(TRUE) + if(!host_mob) + return + // Check if we're still remote viewing the SAME target! + if(host_mob.client.eye == remote_view_target) + return + // The object already changed it's view, lets not interupt it like the others + qdel(src) + +/datum/component/remote_view/proc/on_remotetarget_reset_perspective(datum/source) + SIGNAL_HANDLER + PRIVATE_PROC(TRUE) + // Non-mobs can't do this anyway + if(!host_mob) + return + if(!ismob(remote_view_target)) + return + var/mob/remote_view_mob = remote_view_target + // This is an ugly one, but if we want to follow the other object properly we need to copy its state! + if(!remote_view_mob.client || !host_mob.client) + end_view() + qdel(src) + return + // Only continue to observe if their view location is the same as their turf. otherwise they are doing their own ACTUALLY-REMOTE viewing + // we just won't update it if you're trying to look at the remote view target of another mob as they remote view someone else! + if(get_turf(remote_view_mob) != get_turf(remote_view_mob.client.eye)) + host_mob.client.eye = remote_view_mob + host_mob.client.perspective = MOB_PERSPECTIVE + return + // Copy the view, do not use reset_perspective, because it will call our signal and end our view! + host_mob.client.eye = remote_view_mob.client.eye + host_mob.client.perspective = remote_view_mob.client.perspective + +/datum/component/remote_view/proc/end_view() + PROTECTED_PROC(TRUE) + host_mob.reset_perspective() + +/datum/component/remote_view/proc/get_host() + return host_mob + +/datum/component/remote_view/proc/get_target() + return remote_view_target + +/datum/component/remote_view/proc/looking_at_target_already(atom/target) + return (remote_view_target == target) + + +/** + * Remote view subtype where if the item used with it is moved or dropped the view ends too + */ +/datum/component/remote_view/item_zoom + VAR_PRIVATE/obj/item/host_item + VAR_PRIVATE/show_message + +/datum/component/remote_view/item_zoom/allow_moving + forbid_movement = FALSE + + +/datum/component/remote_view/item_zoom/Initialize(atom/focused_on, obj/item/our_item, viewsize, tileoffset, show_visible_messages) + . = ..() + host_item = our_item + RegisterSignal(host_item, COMSIG_QDELETING, PROC_REF(handle_endview)) + RegisterSignal(host_item, COMSIG_MOVABLE_MOVED, PROC_REF(handle_endview)) + RegisterSignal(host_item, COMSIG_ITEM_DROPPED, PROC_REF(handle_endview)) + RegisterSignal(host_item, COMSIG_REMOTE_VIEW_CLEAR, PROC_REF(handle_endview)) + // If the user has already limited their HUD this avoids them having a HUD when they zoom in + if(host_mob.hud_used.hud_shown) + host_mob.toggle_zoom_hud() + host_mob.set_viewsize(viewsize) + // Unfortunately too many things read this to control item state for me to remove this. + // Oh well! better than GetComponent() everywhere. Lets just manage item/zoom in this component though... + our_item.zoom = TRUE + // Offset view + var/tilesize = 32 + var/viewoffset = tilesize * tileoffset + switch(host_mob.dir) + if (NORTH) + host_mob.client.pixel_x = 0 + host_mob.client.pixel_y = viewoffset + if (SOUTH) + host_mob.client.pixel_x = 0 + host_mob.client.pixel_y = -viewoffset + if (EAST) + host_mob.client.pixel_x = viewoffset + host_mob.client.pixel_y = 0 + if (WEST) + host_mob.client.pixel_x = -viewoffset + host_mob.client.pixel_y = 0 + // Feedback + show_message = show_visible_messages + if(show_message) + host_mob.visible_message(span_filter_notice("[host_mob] peers through the [host_item.zoomdevicename ? "[host_item.zoomdevicename] of the [host_item.name]" : "[host_item.name]"].")) + host_mob.handle_vision() + +/datum/component/remote_view/item_zoom/Destroy(force) + // Feedback + if(show_message) + host_mob.visible_message(span_filter_notice("[host_item.zoomdevicename ? "[host_mob] looks up from the [host_item.name]" : "[host_mob] lowers the [host_item.name]"].")) + // Reset to default + host_mob.set_viewsize() + if(!host_mob.hud_used.hud_shown) + host_mob.toggle_zoom_hud() + host_item.zoom = FALSE + // return view offset + host_mob.client.pixel_x = 0 + host_mob.client.pixel_y = 0 + host_mob.handle_vision() + // decouple + UnregisterSignal(host_item, COMSIG_QDELETING) + UnregisterSignal(host_item, COMSIG_MOVABLE_MOVED) + UnregisterSignal(host_item, COMSIG_ITEM_DROPPED) + UnregisterSignal(host_item, COMSIG_REMOTE_VIEW_CLEAR) + host_item = null + . = ..() + + +/** + * Remote view subtype that stops if the remote view target is dead, or you lose access to the mremote mutation + */ +/datum/component/remote_view/mremote_mutation + +/datum/component/remote_view/mremote_mutation/allow_moving + forbid_movement = FALSE + +/datum/component/remote_view/mremote_mutation/Initialize(atom/focused_on) + if(!ismob(focused_on)) // What are you doing? This gene only works on mob targets, if you adminbus this I will personally eat your face. + return COMPONENT_INCOMPATIBLE + . = ..() + // Remote view mutation stops viewing when mobs die or if we lose the mutation/gene + RegisterSignal(host_mob, COMSIG_MOB_DNA_MUTATION, PROC_REF(on_mutation)) + if(host_mob != remote_view_target) + RegisterSignal(remote_view_target, COMSIG_MOB_DEATH, PROC_REF(handle_endview)) + +/datum/component/remote_view/mremote_mutation/Destroy(force) + UnregisterSignal(host_mob, COMSIG_MOB_DNA_MUTATION) + if(host_mob != remote_view_target) + UnregisterSignal(remote_view_target, COMSIG_MOB_DEATH) + . = ..() + +/datum/component/remote_view/mremote_mutation/proc/on_mutation(datum/source) + SIGNAL_HANDLER + PRIVATE_PROC(TRUE) + if(!host_mob) + return + var/mob/remote_mob = remote_view_target + if(host_mob.stat == CONSCIOUS && (mRemote in host_mob.mutations) && remote_mob && remote_mob.stat == CONSCIOUS) + return + end_view() + qdel(src) + + +/** + * Remote view subtype that handles look() and unlook() procs while managing a list of viewers. Expects a viewer list stored by the object itself, passed in with AddComponent(). Ensure the list exists before passing it to the component or pass by reference will fail. + */ +/datum/component/remote_view/viewer_managed + VAR_PRIVATE/datum/view_coordinator // The object containing the viewer_list, with look() and unlook() logic + VAR_PRIVATE/list/viewers // list from the view_coordinator, lists in byond are pass by reference, so this is the SAME list as on the coordinator! If you pass a null this will explode. + +/datum/component/remote_view/viewer_managed/allow_moving + forbid_movement = FALSE + +/datum/component/remote_view/viewer_managed/Initialize(atom/focused_on, datum/coordinator, list/viewer_list) + . = ..() + if(!islist(viewer_list)) // BAD BAD BAD NO + CRASH("Passed a viewer_list that was not a list, or was null, to /datum/component/remote_view/viewer_managed component. Ensure the viewer_list exists before passing it into AddComponent.") + viewers = viewer_list + view_coordinator = coordinator + view_coordinator.look(host_mob) + LAZYDISTINCTADD(viewers, WEAKREF(host_mob)) + RegisterSignal(view_coordinator, COMSIG_REMOTE_VIEW_CLEAR, PROC_REF(handle_endview)) + +/datum/component/remote_view/viewer_managed/Destroy(force) + UnregisterSignal(view_coordinator, COMSIG_REMOTE_VIEW_CLEAR) + view_coordinator.unlook(host_mob, FALSE) + LAZYREMOVE(viewers, WEAKREF(host_mob)) + view_coordinator = null + viewers = null + . = ..() + + +/** + * Remote view subtype that is handling a byond bug where mobs changing their client eye from inside of + * and object will not have their eye change, and instead focus on any mob currently holding the item, + * and only be released once we move ourselves to a new turf. This subtype does some loc witchcraft + * to put us on a turf, change our view, and put us back without calling signals or move/enter. + * Hopefully this will not be needed someday in the future - Willbird + */ +#define MAX_RECURSIVE 64 +/datum/component/remote_view/mob_holding_item + var/needs_to_decouple = FALSE // if the current top level atom is a mob + +/datum/component/remote_view/mob_holding_item/Initialize(atom/focused_on) + if(!isobj(focused_on)) // You shouldn't be using this if so. + return COMPONENT_INCOMPATIBLE + . = ..() + // Items can be nested deeply, so we need to update on any parent reorganization or actual move. + host_mob.AddComponent(/datum/component/recursive_move) + RegisterSignal(host_mob, COMSIG_OBSERVER_MOVED, PROC_REF(handle_recursive_moved)) // Doesn't need override, basetype only ever registers this signal if we're looking at a turf + // Check our inmob state + if(ismob(find_topmost_atom())) + needs_to_decouple = TRUE + +/datum/component/remote_view/mob_holding_item/Destroy(force) + UnregisterSignal(host_mob, COMSIG_OBSERVER_MOVED) + . = ..() + +/datum/component/remote_view/mob_holding_item/handle_hostmob_moved(atom/source, atom/oldloc) + // We handle this in recursive move + if(!host_mob) + return + if(isturf(host_mob.loc)) + decouple_view_to_turf( host_mob, host_mob.loc) + return + +/datum/component/remote_view/mob_holding_item/handle_recursive_moved(atom/source, atom/oldloc, atom/new_loc) + if(!host_mob) + return + // default moved signal will handle this + if(isturf(host_mob.loc)) + return + // This only triggers when we are deeper in than our mob. See who is in charge of this clowncar... + // Loop upward until we find a mob or a turf. Mobs will hold our current view, turfs mean our bag-stack was dropped. + var/atom/top_most = find_topmost_atom() + if(isturf(top_most)) + if(needs_to_decouple) // Only need to do this if we were held by a mob prior, otherwise this triggers every move and is expensive for no reason + decouple_view_to_turf( host_mob, top_most) + return + if(ismob(top_most) || ismecha(top_most)) // Mobs and mechas both do this + host_mob.AddComponent(/datum/component/recursive_move) // Will rebuild parent chain. + needs_to_decouple = TRUE + return + +/// Get our topmost atom state, if it's a mob or a turf +/datum/component/remote_view/mob_holding_item/proc/find_topmost_atom() + var/atom/cur_parent = remote_view_target?.loc // first loc could be null + var/recursion = 0 // safety check - max iterations + while(!isnull(cur_parent) && (recursion < MAX_RECURSIVE)) + if(cur_parent == cur_parent.loc) //safety check incase a thing is somehow inside itself, cancel + log_runtime("REMOTE_VIEW: Parent is inside itself. ([host_mob]) ([host_mob.type]) : [MAX_RECURSIVE - recursion]") + return null + if(ismob(cur_parent) || ismecha(cur_parent) || isturf(cur_parent)) + return cur_parent + recursion++ + cur_parent = cur_parent.loc + + if(recursion >= MAX_RECURSIVE) // If we escaped due to iteration limit, cancel + log_runtime("REMOTE_VIEW: Turf search hit recursion limit. ([host_mob]) ([host_mob.type])") + return null + +/// Makes a new remote view focused on the release_turf argument. This remote view ends as soon as any movement happens. Even if we are inside many levels of objects due to our recursive_move listener +/datum/component/remote_view/mob_holding_item/proc/decouple_view_to_turf(mob/cache_mob, turf/release_turf) + if(needs_to_decouple) + // Yes this spawn is needed, yes I wish it wasn't. + spawn(0) + // Decouple the view to the turf on drop, or we'll be stuck on the mob that dropped us forever + cache_mob.AddComponent(/datum/component/remote_view, release_turf) + cache_mob.client.eye = release_turf // Yes-- + cache_mob.client.perspective = EYE_PERSPECTIVE // --this is required too. + if(!isturf(cache_mob.loc)) // For stuff like paicards + cache_mob.AddComponent(/datum/component/recursive_move) // Will rebuild parent chain. + qdel(src) + +#undef MAX_RECURSIVE diff --git a/code/datums/datum.dm b/code/datums/datum.dm index 8a2f0d7b72..b7362deaa9 100644 --- a/code/datums/datum.dm +++ b/code/datums/datum.dm @@ -372,3 +372,16 @@ return harddel_deets_dumped = TRUE return "Image icon: [icon] - icon_state: [icon_state] [loc ? "loc: [loc] ([loc.x],[loc.y],[loc.z])" : ""]" + +/// Begin coordinated remote viewing, this will call look() when the view begins, and unlook() when it ends. +/datum/proc/start_coordinated_remoteview(mob/user, atom/target, list/viewer_managed_list) + ASSERT(islist(viewer_managed_list)) + user.AddComponent(/datum/component/remote_view/viewer_managed, focused_on = target, coordinator = src, viewer_list = viewer_managed_list) + +/// Called from /datum/component/remote_view/viewer_managed during Initilize(). +/datum/proc/look(mob/user) + return + +/// Called from /datum/component/remote_view/viewer_managed during Destroy() +/datum/proc/unlook(mob/user) + return diff --git a/code/defines/obj/weapon.dm b/code/defines/obj/weapon.dm index 738fe8802a..f6d1893a93 100644 --- a/code/defines/obj/weapon.dm +++ b/code/defines/obj/weapon.dm @@ -216,6 +216,9 @@ throw_range = 20 /obj/item/camera_bug/attack_self(mob/user as mob) + if(in_use) + return + var/list/cameras = new/list() for (var/obj/machinery/camera/C in cameranet.cameras) if (C.bugged && C.status) @@ -229,7 +232,10 @@ for (var/obj/machinery/camera/C in cameras) friendly_cameras.Add(C.c_tag) + in_use = TRUE var/target = tgui_input_list(user, "Select the camera to observe", "Select Camera", friendly_cameras) + in_use = FALSE + if (!target) return for (var/obj/machinery/camera/C in cameras) @@ -238,7 +244,7 @@ break if (user.stat == 2) return - user.client.eye = target + user.AddComponent(/datum/component/remote_view/item_zoom, focused_on = target, our_item = src, viewsize = null, tileoffset = 0, show_visible_messages = FALSE) /* /obj/item/cigarpacket diff --git a/code/game/atom/_atom.dm b/code/game/atom/_atom.dm index 18898a8210..835e2d2797 100644 --- a/code/game/atom/_atom.dm +++ b/code/game/atom/_atom.dm @@ -95,8 +95,7 @@ else return null -//return flags that should be added to the viewer's sight var. -//Otherwise return a negative number to indicate that the view should be cancelled. +/// return flags that should be added to the viewer's sight var. Otherwise return a negative number to indicate that the view should be cancelled. /atom/proc/check_eye(user as mob) if (isAI(user)) // WHYYYY return 0 diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 7104cf073e..3cf5b8023e 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -239,7 +239,7 @@ last_move = direct // The direction you last moved // set_dir(direct) //Don't think this is necessary -//Called after a successful Move(). By this point, we've already moved +///Called after a successful Move(). By this point, we've already moved /atom/movable/proc/Moved(atom/old_loc, direction, forced = FALSE, movetime) SEND_SIGNAL(src, COMSIG_MOVABLE_MOVED, old_loc, direction, forced, movetime) // Handle any buckled mobs on this movable @@ -250,9 +250,14 @@ riding_datum.handle_vehicle_offsets() for (var/datum/light_source/light as anything in light_sources) // Cycle through the light sources on this atom and tell them to update. light.source_atom.update_light() - return TRUE +/mob/Moved(atom/old_loc, direction, forced, movetime) + . = ..() + //If we return focus to our own mob, but we are still inside something with an inherent remote view. Restart it. + if(client) + restore_remote_views() + /atom/movable/set_dir(newdir) . = ..(newdir) if(riding_datum) diff --git a/code/game/dna/dna2_domutcheck.dm b/code/game/dna/dna2_domutcheck.dm index d803394325..c7a3063909 100644 --- a/code/game/dna/dna2_domutcheck.dm +++ b/code/game/dna/dna2_domutcheck.dm @@ -68,3 +68,5 @@ M.active_genes -= gene.name // Traitgenes Use name instead, cannot use type with dynamically setup traitgenes M.update_icon = 1 M.update_mutations() + // Inform anything attached of our mutation + SEND_SIGNAL(M, COMSIG_MOB_DNA_MUTATION) diff --git a/code/game/dna/dna_modifier.dm b/code/game/dna/dna_modifier.dm index bba6e22c80..4917842c6e 100644 --- a/code/game/dna/dna_modifier.dm +++ b/code/game/dna/dna_modifier.dm @@ -172,8 +172,6 @@ to_chat(usr, span_warning("There is already something inside.")) return usr.stop_pulling() - usr.client.perspective = EYE_PERSPECTIVE - usr.client.eye = src usr.forceMove(src) set_occupant(usr) icon_state = "scanner_1" @@ -250,9 +248,6 @@ . = ..() /obj/machinery/dna_scannernew/proc/put_in(var/mob/M) - if(M.client) - M.client.perspective = EYE_PERSPECTIVE - M.client.eye = src M.forceMove(src) set_occupant(M) icon_state = "scanner_1" @@ -274,9 +269,6 @@ var/mob/living/carbon/WC = get_occupant() if((!WC || locked)) return - if(WC.client) - WC.client.eye = WC.client.mob - WC.client.perspective = MOB_PERSPECTIVE if(istype(WC,/mob/living/carbon/brain)) for(var/obj/O in src) if(istype(O,/obj/item/organ/internal/brain)) diff --git a/code/game/machinery/OpTable.dm b/code/game/machinery/OpTable.dm index 8e73d1f849..51f0fd1170 100644 --- a/code/game/machinery/OpTable.dm +++ b/code/game/machinery/OpTable.dm @@ -78,13 +78,10 @@ user.visible_message("[user] climbs on \the [src].","You climb on \the [src].") else visible_message(span_notice("\The [C] has been laid on \the [src] by [user].")) - if(C.client) - C.client.perspective = EYE_PERSPECTIVE - C.client.eye = src if(C.pulledby) C.pulledby.stop_pulling() C.resting = 1 - C.loc = src.loc + C.forceMove(get_turf(src)) for(var/obj/O in src) O.loc = src.loc add_fingerprint(user) diff --git a/code/game/machinery/Sleeper.dm b/code/game/machinery/Sleeper.dm index 10ce91cc74..5383f34894 100644 --- a/code/game/machinery/Sleeper.dm +++ b/code/game/machinery/Sleeper.dm @@ -479,9 +479,6 @@ to_chat(user, span_warning("\The [src] is already occupied.")) return M.stop_pulling() - if(M.client) - M.client.perspective = EYE_PERSPECTIVE - M.client.eye = src M.forceMove(src) update_use_power(USE_POWER_ACTIVE) occupant = M @@ -493,9 +490,6 @@ occupant.cozyloop.stop() // CHOMPStation Add: Cozy Music occupant = null // JUST IN CASE return - if(occupant.client) - occupant.client.eye = occupant.client.mob - occupant.client.perspective = MOB_PERSPECTIVE occupant.Stasis(0) occupant.forceMove(get_turf(src)) occupant.cozyloop.stop() // CHOMPStation Add: Cozy Music diff --git a/code/game/machinery/adv_med.dm b/code/game/machinery/adv_med.dm index 63c4f288fe..1af296f0f2 100644 --- a/code/game/machinery/adv_med.dm +++ b/code/game/machinery/adv_med.dm @@ -124,10 +124,7 @@ /obj/machinery/bodyscanner/proc/go_out() if ((!(occupant) || src.locked)) return - if (occupant.client) - occupant.client.eye = occupant.client.mob - occupant.client.perspective = MOB_PERSPECTIVE - occupant.forceMove(src.loc) // was occupant.loc = src.loc, but that doesn't trigger exit(), and thus recursive radio listeners forwarded messages to the occupant as if they were still inside it for the rest of the round! OP21 #5f88307 Port + occupant.forceMove(get_turf(src)) occupant = null update_icon() //icon_state = "body_scanner_1" //VOREStation Edit - Health display for consoles with light and such. SStgui.update_uis(src) @@ -137,7 +134,7 @@ switch(severity) if(1.0) for(var/atom/movable/A as mob|obj in src) - A.forceMove(src.loc) + A.forceMove(get_turf(src)) ex_act(severity) //Foreach goto(35) //SN src = null @@ -146,7 +143,7 @@ if(2.0) if (prob(50)) for(var/atom/movable/A as mob|obj in src) - A.forceMove(src.loc) + A.forceMove(get_turf(src)) ex_act(severity) //Foreach goto(108) //SN src = null @@ -155,7 +152,7 @@ if(3.0) if (prob(25)) for(var/atom/movable/A as mob|obj in src) - A.forceMove(src.loc) + A.forceMove(get_turf(src)) ex_act(severity) //Foreach goto(181) //SN src = null diff --git a/code/game/machinery/cloning.dm b/code/game/machinery/cloning.dm index b35286efc5..4e3eb5b332 100644 --- a/code/game/machinery/cloning.dm +++ b/code/game/machinery/cloning.dm @@ -319,9 +319,6 @@ if(!(occupant)) return - if(occupant.client) - occupant.client.eye = occupant.client.mob - occupant.client.perspective = MOB_PERSPECTIVE occupant.forceMove(get_turf(src)) eject_wait = 0 //If it's still set somehow. if(ishuman(occupant)) //Need to be safe. diff --git a/code/game/machinery/cryo.dm b/code/game/machinery/cryo.dm index cd89a98c9d..ec20dd5f37 100644 --- a/code/game/machinery/cryo.dm +++ b/code/game/machinery/cryo.dm @@ -153,7 +153,7 @@ update_icon() if("ejectBeaker") if(beaker) - beaker.loc = get_step(src.loc, SOUTH) + beaker.forceMove(get_step(src.loc, SOUTH)) beaker = null update_icon() if("ejectOccupant") @@ -173,7 +173,7 @@ beaker = G user.drop_item() - G.loc = src + G.forceMove(src) user.visible_message("[user] adds \a [G] to \the [src]!", "You add \a [G] to \the [src]!") SStgui.update_uis(src) update_icon() @@ -261,15 +261,10 @@ /obj/machinery/atmospherics/unary/cryo_cell/proc/go_out() if(!(occupant)) return - //for(var/obj/O in src) - // O.loc = src.loc - if(occupant.client) - occupant.client.eye = occupant.client.mob - occupant.client.perspective = MOB_PERSPECTIVE vis_contents -= occupant occupant.pixel_x = occupant.default_pixel_x occupant.pixel_y = occupant.default_pixel_y - occupant.loc = get_step(src.loc, SOUTH) //this doesn't account for walls or anything, but i don't forsee that being a problem. + occupant.forceMove(get_step(src.loc, SOUTH)) //this doesn't account for walls or anything, but i don't forsee that being a problem. if(occupant.bodytemperature < 261 && occupant.bodytemperature >= 70) //Patch by Aranclanos to stop people from taking burn damage after being ejected occupant.bodytemperature = 261 // Changed to 70 from 140 by Zuhayr due to reoccurance of bug. unbuckle_mob(occupant, force = TRUE) @@ -296,11 +291,8 @@ if(!node) to_chat(usr, span_warning("The cell is not correctly connected to its pipe network!")) return - if(M.client) - M.client.perspective = EYE_PERSPECTIVE - M.client.eye = src M.stop_pulling() - M.loc = src + M.forceMove(src) M.extinguish_mob() if(M.health > -100 && (M.health < 0 || M.sleeping)) to_chat(M, span_boldnotice("You feel a cold liquid surround you. Your skin starts to freeze up.")) diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm index b78fc28f52..be38fa844e 100644 --- a/code/game/machinery/cryopod.dm +++ b/code/game/machinery/cryopod.dm @@ -639,8 +639,6 @@ return usr.stop_pulling() - usr.client.perspective = EYE_PERSPECTIVE - usr.client.eye = src usr.forceMove(src) set_occupant(usr) if(ishuman(usr) && applies_stasis) @@ -676,9 +674,6 @@ if(!occupant) return - if(occupant.client) - occupant.client.eye = occupant.client.mob - occupant.client.perspective = MOB_PERSPECTIVE if(!skip_move) occupant.forceMove(get_turf(src)) if(ishuman(occupant) && applies_stasis) @@ -731,10 +726,6 @@ to_chat(user, span_warning("\The [src] is already occupied.")) return M.forceMove(src) - - if(M.client) - M.client.perspective = EYE_PERSPECTIVE - M.client.eye = src else return icon_state = occupied_icon_state diff --git a/code/game/machinery/machinery.dm b/code/game/machinery/machinery.dm index d924b38884..13a9226af4 100644 --- a/code/game/machinery/machinery.dm +++ b/code/game/machinery/machinery.dm @@ -153,9 +153,8 @@ Class Procs: for(var/atom/A in contents) if(ishuman(A)) var/mob/living/carbon/human/H = A - H.client.eye = H.client.mob - H.client.perspective = MOB_PERSPECTIVE - H.loc = src.loc + H.forceMove(loc) + H.reset_perspective() else qdel(A) return ..() @@ -239,7 +238,7 @@ Class Procs: if(isrobot(user)) // For some reason attack_robot doesn't work // This is to stop robots from using cameras to remotely control machines. - if(user.client && user.client.eye == user) + if(user.client && !user.is_remote_viewing()) return attack_hand(user) else return attack_hand(user) diff --git a/code/game/machinery/rechargestation.dm b/code/game/machinery/rechargestation.dm index 4df6d8802a..da35b39df0 100644 --- a/code/game/machinery/rechargestation.dm +++ b/code/game/machinery/rechargestation.dm @@ -266,7 +266,6 @@ return add_fingerprint(R) - R.reset_view(src) R.forceMove(src) occupant = R update_icon() @@ -280,7 +279,6 @@ return add_fingerprint(P) - P.reset_view(src) P.forceMove(src) occupant = P update_icon() @@ -291,7 +289,6 @@ var/mob/living/carbon/human/H = L if(H.isSynthetic() || H.wearing_rig) add_fingerprint(H) - H.reset_view(src) H.forceMove(src) occupant = H update_icon() @@ -302,9 +299,7 @@ /obj/machinery/recharge_station/proc/go_out() if(!occupant) return - - occupant.forceMove(src.loc) - occupant.reset_view() + occupant.forceMove(get_turf(src)) occupant = null update_icon() diff --git a/code/game/machinery/suit_storage/suit_cycler.dm b/code/game/machinery/suit_storage/suit_cycler.dm index 71e4d435cb..fdf68a69c3 100644 --- a/code/game/machinery/suit_storage/suit_cycler.dm +++ b/code/game/machinery/suit_storage/suit_cycler.dm @@ -165,10 +165,7 @@ GLOBAL_LIST_EMPTY(suit_cycler_typecache) if(do_after(user, 2 SECONDS, target = src)) if(!G || !G.affecting) return var/mob/M = G.affecting - if(M.client) - M.client.perspective = EYE_PERSPECTIVE - M.client.eye = src - M.loc = src + M.forceMove(src) occupant = M add_fingerprint(user) @@ -213,7 +210,7 @@ GLOBAL_LIST_EMPTY(suit_cycler_typecache) to_chat(user, "You fit \the [I] into the suit cycler.") user.drop_item() - I.loc = src + I.forceMove(src) helmet = I update_icon() @@ -252,7 +249,7 @@ GLOBAL_LIST_EMPTY(suit_cycler_typecache) to_chat(user, "You fit \the [I] into the suit cycler.") user.drop_item() - I.loc = src + I.forceMove(src) suit = I update_icon() @@ -502,11 +499,7 @@ GLOBAL_LIST_EMPTY(suit_cycler_typecache) if(!occupant) return - if(occupant.client) - occupant.client.eye = occupant.client.mob - occupant.client.perspective = MOB_PERSPECTIVE - - occupant.loc = get_turf(occupant) + occupant.forceMove(get_turf(src)) occupant = null add_fingerprint(user) diff --git a/code/game/machinery/suit_storage/suit_storage.dm b/code/game/machinery/suit_storage/suit_storage.dm index dfc6042343..cc891d0110 100644 --- a/code/game/machinery/suit_storage/suit_storage.dm +++ b/code/game/machinery/suit_storage/suit_storage.dm @@ -188,7 +188,7 @@ if(!HELMET) return //Do I even need this sanity check? Nyoro~n else - HELMET.loc = src.loc + HELMET.forceMove(get_turf(src)) HELMET = null return @@ -197,7 +197,7 @@ if(!SUIT) return else - SUIT.loc = src.loc + SUIT.forceMove(get_turf(src)) SUIT = null return @@ -206,7 +206,7 @@ if(!MASK) return else - MASK.loc = src.loc + MASK.forceMove(get_turf(src)) MASK = null return @@ -214,13 +214,13 @@ /obj/machinery/suit_storage_unit/proc/dump_everything() islocked = 0 //locks go free if(SUIT) - SUIT.loc = src.loc + SUIT.forceMove(get_turf(src)) SUIT = null if(HELMET) - HELMET.loc = src.loc + HELMET.forceMove(get_turf(src)) HELMET = null if(MASK) - MASK.loc = src.loc + MASK.forceMove(get_turf(src)) MASK = null if(OCCUPANT) eject_occupant(OCCUPANT) @@ -321,10 +321,7 @@ to_chat(OCCUPANT, span_notice("The machine kicks you out!")) if(user.loc != src.loc) to_chat(OCCUPANT, span_notice("You leave the not-so-cozy confines of the SSU.")) - - OCCUPANT.client.eye = OCCUPANT.client.mob - OCCUPANT.client.perspective = MOB_PERSPECTIVE - OCCUPANT.loc = src.loc + OCCUPANT.forceMove(get_turf(src)) OCCUPANT = null if(!isopen) isopen = 1 @@ -364,9 +361,7 @@ visible_message(span_info("[usr] starts squeezing into the suit storage unit!"), 3) if(do_after(usr, 1 SECOND, target = src)) usr.stop_pulling() - usr.client.perspective = EYE_PERSPECTIVE - usr.client.eye = src - usr.loc = src + usr.forceMove(src) OCCUPANT = usr isopen = 0 //Close the thing after the guy gets inside update_icon() @@ -403,10 +398,7 @@ if(do_after(user, 2 SECONDS, target = src)) if(!G || !G.affecting) return //derpcheck var/mob/M = G.affecting - if(M.client) - M.client.perspective = EYE_PERSPECTIVE - M.client.eye = src - M.loc = src + M.forceMove(src) OCCUPANT = M isopen = 0 //close ittt @@ -424,7 +416,7 @@ return to_chat(user, span_info("You load the [S.name] into the storage compartment.")) user.drop_item() - S.loc = src + S.forceMove(src) SUIT = S update_icon() return @@ -437,7 +429,7 @@ return to_chat(user, span_info("You load the [H.name] into the storage compartment.")) user.drop_item() - H.loc = src + H.forceMove(src) HELMET = H update_icon() return @@ -450,7 +442,7 @@ return to_chat(user, span_info("You load the [M.name] into the storage compartment.")) user.drop_item() - M.loc = src + M.forceMove(src) MASK = M update_icon() return diff --git a/code/game/machinery/transportpod.dm b/code/game/machinery/transportpod.dm index fb43333603..c502fb7019 100644 --- a/code/game/machinery/transportpod.dm +++ b/code/game/machinery/transportpod.dm @@ -58,7 +58,6 @@ return add_fingerprint(O) - O.reset_view(src) O.forceMove(src) occupant = O update_icon() @@ -72,9 +71,7 @@ /obj/machinery/transportpod/proc/go_out() if(!occupant) return - occupant.forceMove(src.loc) - occupant.reset_view() occupant = null update_icon() diff --git a/code/game/machinery/virtual_reality/ar_console.dm b/code/game/machinery/virtual_reality/ar_console.dm index 8eddb866ee..04f9196e73 100644 --- a/code/game/machinery/virtual_reality/ar_console.dm +++ b/code/game/machinery/virtual_reality/ar_console.dm @@ -65,9 +65,6 @@ avatar.exit_vr() //We don't poof! We're a actual, living entity that isn't restrained by VR zones! if(!occupant) //This whole thing needs cleaned up later, but this works for now. return - if(occupant && occupant.client) - occupant.client.eye = occupant.client.mob - occupant.client.perspective = MOB_PERSPECTIVE occupant.forceMove(get_turf(src)) occupant.vr_link = null //The machine remembers the avatar. 1 avatar per machine. So the vr_link isn't needed anymore. occupant = null diff --git a/code/game/machinery/virtual_reality/vr_console.dm b/code/game/machinery/virtual_reality/vr_console.dm index e8072a952f..a687f8f030 100644 --- a/code/game/machinery/virtual_reality/vr_console.dm +++ b/code/game/machinery/virtual_reality/vr_console.dm @@ -3,6 +3,7 @@ desc = "A fancy bed with built-in sensory I/O ports and connectors to interface users' minds with their bodies in virtual reality." icon = 'icons/obj/Cryogenic2.dmi' icon_state = "body_scanner_0" + flags = REMOTEVIEW_ON_ENTER var/base_state = "body_scanner_" @@ -186,10 +187,7 @@ to_chat(user, span_warning("\The [src] is already occupied.")) return M.stop_pulling() - if(M.client) - M.client.perspective = EYE_PERSPECTIVE - M.client.eye = src - M.loc = src + M.forceMove(src) occupant = M update_icon() @@ -221,9 +219,7 @@ if(occupant.vr_link) occupant.vr_link.exit_vr(FALSE) - if(occupant.client) - occupant.client.eye = occupant.client.mob - occupant.client.perspective = MOB_PERSPECTIVE + occupant.reset_perspective() // Needed for returning from VR occupant.forceMove(get_turf(src)) occupant = null for(var/atom/movable/A in src) // In case an object was dropped inside or something @@ -231,7 +227,7 @@ continue if(A in component_parts) continue - A.loc = src.loc + A.forceMove(get_turf(src)) update_use_power(USE_POWER_IDLE) update_icon() diff --git a/code/game/mecha/equipment/tools/passenger.dm b/code/game/mecha/equipment/tools/passenger.dm index 3245615895..0c3496f0fa 100644 --- a/code/game/mecha/equipment/tools/passenger.dm +++ b/code/game/mecha/equipment/tools/passenger.dm @@ -10,7 +10,6 @@ var/door_locked = 1 salvageable = 0 allow_duplicate = TRUE - equip_type = EQUIP_HULL /obj/item/mecha_parts/mecha_equipment/tool/passenger/destroy() @@ -69,12 +68,6 @@ if(!occupant) return occupant.forceMove(get_turf(src)) - occupant.reset_view() - /* - if(occupant.client) - occupant.client.eye = occupant.client.mob - occupant.client.perspective = MOB_PERSPECTIVE - */ occupant = null return diff --git a/code/game/mecha/equipment/tools/sleeper.dm b/code/game/mecha/equipment/tools/sleeper.dm index 10c0f4cee5..ea2964306c 100644 --- a/code/game/mecha/equipment/tools/sleeper.dm +++ b/code/game/mecha/equipment/tools/sleeper.dm @@ -48,13 +48,7 @@ return target.forceMove(src) occupant = target - target.reset_view(src) occupant.Stasis(3) - /* - if(target.client) - target.client.perspective = EYE_PERSPECTIVE - target.client.eye = chassis - */ set_ready_state(FALSE) START_PROCESSING(SSprocessing, src) occupant_message(span_notice("[target] successfully loaded into [src]. Life support functions engaged.")) @@ -68,12 +62,6 @@ occupant.forceMove(get_turf(src)) occupant_message(span_infoplain("[occupant] ejected. Life support functions disabled.")) src.mecha_log_message("[occupant] ejected. Life support functions disabled.") - occupant.reset_view() - /* - if(occupant.client) - occupant.client.eye = occupant.client.mob - occupant.client.perspective = MOB_PERSPECTIVE - */ occupant.Stasis(0) occupant = null STOP_PROCESSING(SSprocessing, src) diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm index f8bc868a86..6d1286c709 100644 --- a/code/game/mecha/mecha.dm +++ b/code/game/mecha/mecha.dm @@ -13,6 +13,7 @@ opacity = 1 //Opaque. Menacing. anchored = TRUE //No pulling around. unacidable = TRUE //And no deleting hoomans inside + flags = REMOTEVIEW_ON_ENTER layer = MOB_LAYER //Icon draw layer infra_luminosity = 15 //Byond implementation is bugged. var/initial_icon = null //Mech type for resetting icon. Only used for reskinning kits (see custom items) @@ -1679,11 +1680,6 @@ return 0 user.drop_from_inventory(mmi_as_oc) var/mob/brainmob = mmi_as_oc.brainmob - brainmob.reset_view(src) - /* - brainmob.client.eye = src - brainmob.client.perspective = EYE_PERSPECTIVE - */ occupant = brainmob brainmob.loc = src //should allow relaymove brainmob.canmove = 1 @@ -1988,23 +1984,15 @@ /obj/mecha/proc/moved_inside(var/mob/living/carbon/human/H) if(H && H.client && (H in range(1))) - H.reset_view(src) - /* - H.client.perspective = EYE_PERSPECTIVE - H.client.eye = src - */ H.stop_pulling() H.forceMove(src) src.occupant = H src.add_fingerprint(H) - src.forceMove(src.loc) src.verbs += /obj/mecha/verb/eject src.log_append_to_last("[H] moved in as pilot.") update_icon() - //VOREStation Edit Add if(occupant.hud_used) minihud = new (occupant.hud_used, src) - //VOREStation Edit Add End //This part removes all the verbs if you don't have them the _possible on your mech. This is a little clunky, but it lets you just add that to any mech. //And it's not like this 10yo code wasn't clunky before. @@ -2106,14 +2094,13 @@ return if(mob_container.forceMove(src.loc))//ejecting mob container src.mecha_log_message("[mob_container] moved out.") - occupant.reset_view() occupant << browse(null, "window=exosuit") if(occupant.client && cloaked_selfimage) occupant.client.images -= cloaked_selfimage if(istype(mob_container, /obj/item/mmi)) var/obj/item/mmi/mmi = mob_container if(mmi.brainmob) - occupant.loc = mmi + occupant.forceMove(mmi) mmi.mecha = null occupant.canmove = 0 occupant.clear_alert("charge") diff --git a/code/game/mecha/medical/odysseus.dm b/code/game/mecha/medical/odysseus.dm index 16ade2a469..7b288ae53c 100644 --- a/code/game/mecha/medical/odysseus.dm +++ b/code/game/mecha/medical/odysseus.dm @@ -43,31 +43,7 @@ H.recalculate_vis() ..() return -/* - verb/set_perspective() - set name = "Set client perspective." - set category = "Exosuit Interface" - set src = usr.loc - var/perspective = tgui_input_list(usr, "Select a perspective type.", - "Client perspective", - list(MOB_PERSPECTIVE,EYE_PERSPECTIVE), occupant.client.perspective) - to_world("[perspective]") - occupant.client.perspective = perspective - return - verb/toggle_eye() - set name = "Toggle eye." - set category = "Exosuit Interface" - set src = usr.loc - if(occupant.client.eye == occupant) - occupant.client.eye = src - else - occupant.client.eye = occupant - to_world("[occupant.client.eye]") - return -*/ - -//TODO - Check documentation for client.eye and client.perspective... /obj/item/clothing/glasses/hud/health/mech name = "Integrated Medical Hud" diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index c7aaae1e11..cb6190b47f 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -422,8 +422,6 @@ // apparently called whenever an item is removed from a slot, container, or anything else. /obj/item/proc/dropped(mob/user) SHOULD_CALL_PARENT(TRUE) - if(zoom) - zoom() //binoculars, scope, etc appearance_flags &= ~NO_CLIENT_COLOR // Remove any item actions we temporary gave out. for(var/datum/action/action_item_has as anything in actions) @@ -433,6 +431,7 @@ qdel(src) SEND_SIGNAL(src, COMSIG_ITEM_DROPPED, user) + SEND_SIGNAL(user, COMSIG_MOB_DROPPED_ITEM, src) if(my_augment && !QDELETED(src)) forceMove(my_augment) @@ -821,91 +820,42 @@ GLOBAL_LIST_EMPTY(blood_overlays_by_type) if(I && !I.abstract) I.showoff(src) -/* -For zooming with scope or binoculars. This is called from -modules/mob/mob_movement.dm if you move you will be zoomed out -modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out. -*/ -//Looking through a scope or binoculars should /not/ improve your periphereal vision. Still, increase viewsize a tiny bit so that sniping isn't as restricted to NSEW -/obj/item/var/ignore_visor_zoom_restriction = FALSE - +/// For zooming with scope or binoculars. Uses remote_view/item component for disabling when you move or drop the item /obj/item/proc/zoom(var/mob/living/M, var/tileoffset = 14,var/viewsize = 9) //tileoffset is client view offset in the direction the user is facing. viewsize is how far out this thing zooms. 7 is normal view SIGNAL_HANDLER if(isliving(usr)) //Always prefer usr if set M = usr - + if(!M.client) + return FALSE if(!isliving(M)) - return 0 - + return FALSE + if(isbelly(M.loc) || istype(M.loc,/obj/item/dogborg/sleeper)) + return FALSE + if(M.is_remote_viewing()) + to_chat(M, span_warning("You are too distracted to do that.")) + return FALSE var/devicename - if(zoomdevicename) devicename = zoomdevicename else devicename = src.name - var/cannotzoom - + var/can_zoom = TRUE if((M.stat && !zoom) || !(ishuman(M))) to_chat(M, span_filter_notice("You are unable to focus through the [devicename].")) - cannotzoom = 1 + can_zoom = FALSE else if(!zoom && (GLOB.global_hud.darkMask[1] in M.client.screen)) to_chat(M, span_filter_notice("Your visor gets in the way of looking through the [devicename].")) - cannotzoom = 1 + can_zoom = FALSE else if(!zoom && M.get_active_hand() != src) to_chat(M, span_filter_notice("You are too distracted to look through the [devicename], perhaps if it was in your active hand this might work better.")) - cannotzoom = 1 + can_zoom = FALSE - //We checked above if they are a human and returned already if they weren't. - var/mob/living/carbon/human/H = M - - if(!zoom && !cannotzoom) - if(H.hud_used.hud_shown) - H.toggle_zoom_hud() // If the user has already limited their HUD this avoids them having a HUD when they zoom in - H.set_viewsize(viewsize) - zoom = 1 - H.AddComponent(/datum/component/recursive_move) - RegisterSignal(H, COMSIG_OBSERVER_MOVED, PROC_REF(zoom), override = TRUE) - - var/tilesize = 32 - var/viewoffset = tilesize * tileoffset - - switch(H.dir) - if (NORTH) - H.client.pixel_x = 0 - H.client.pixel_y = viewoffset - if (SOUTH) - H.client.pixel_x = 0 - H.client.pixel_y = -viewoffset - if (EAST) - H.client.pixel_x = viewoffset - H.client.pixel_y = 0 - if (WEST) - H.client.pixel_x = -viewoffset - H.client.pixel_y = 0 - - H.visible_message(span_filter_notice("[M] peers through the [zoomdevicename ? "[zoomdevicename] of the [src.name]" : "[src.name]"].")) - if(!ignore_visor_zoom_restriction) - H.looking_elsewhere = TRUE - H.handle_vision() - - else - H.set_viewsize() // Reset to default - if(!H.hud_used.hud_shown) - H.toggle_zoom_hud() - zoom = 0 - UnregisterSignal(H, COMSIG_OBSERVER_MOVED) - - H.client.pixel_x = 0 - H.client.pixel_y = 0 - H.looking_elsewhere = FALSE - H.handle_vision() - - if(!cannotzoom) - M.visible_message(span_filter_notice("[zoomdevicename ? "[M] looks up from the [src.name]" : "[M] lowers the [src.name]"].")) - - return + if(!zoom && can_zoom) + M.AddComponent(/datum/component/remote_view/item_zoom/allow_moving, focused_on = M, our_item = src, viewsize = viewsize, tileoffset = tileoffset, show_visible_messages = TRUE) + return + SEND_SIGNAL(src,COMSIG_REMOTE_VIEW_CLEAR) /obj/item/proc/pwr_drain() return 0 // Process Kill diff --git a/code/game/objects/items/devices/chameleonproj.dm b/code/game/objects/items/devices/chameleonproj.dm index 7136c6edc7..9fc31b3830 100644 --- a/code/game/objects/items/devices/chameleonproj.dm +++ b/code/game/objects/items/devices/chameleonproj.dm @@ -87,16 +87,14 @@ /obj/item/chameleon/proc/eject_all() for(var/atom/movable/A in active_dummy) - A.loc = active_dummy.loc - if(ismob(A)) - var/mob/M = A - M.reset_view(null) + A.forceMove(get_turf(active_dummy)) /obj/effect/dummy/chameleon name = "" desc = "" density = FALSE anchored = TRUE + flags = REMOTEVIEW_ON_ENTER var/can_move = 1 var/obj/item/chameleon/master = null @@ -107,7 +105,7 @@ icon_state = new_iconstate overlays = new_overlays set_dir(O.dir) - M.loc = src + M.forceMove(src) master = C master.active_dummy = src diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm index b503288fcd..c6a0a3af2e 100644 --- a/code/game/objects/items/devices/paicard.dm +++ b/code/game/objects/items/devices/paicard.dm @@ -462,16 +462,16 @@ paicard = card user.unEquip(card) card.forceMove(src) - AI.client.eye = src + AI.reset_perspective(src) // focus this machine to_chat(AI, span_notice("Your location is [card.loc].")) // DEBUG. TODO: Make unfolding the chassis trigger an eject. name = AI.name to_chat(AI, span_notice("You feel a tingle in your circuits as your systems interface with \the [initial(src.name)].")) /obj/machinery/proc/ejectpai(mob/user) if(paicard) + paicard.forceMove(get_turf(src)) var/mob/living/silicon/pai/AI = paicard.pai - paicard.forceMove(src.loc) - AI.client.eye = AI + AI.reset_perspective() // return to the card paicard = null name = initial(src.name) to_chat(AI, span_notice("You feel a tad claustrophobic as your mind closes back into your card, ejecting from \the [initial(src.name)].")) diff --git a/code/game/objects/items/devices/spy_bug.dm b/code/game/objects/items/devices/spy_bug.dm index cca907c1be..936e97027c 100644 --- a/code/game/objects/items/devices/spy_bug.dm +++ b/code/game/objects/items/devices/spy_bug.dm @@ -33,7 +33,6 @@ spawn(0) qdel(src) /* else - user.set_machine(radio) radio.interact(user) */ /obj/item/camerabug/verb/reset() @@ -159,7 +158,6 @@ w_class = ITEMSIZE_SMALL origin_tech = list(TECH_DATA = 1, TECH_ENGINEERING = 1) - var/operating = 0 // var/obj/item/radio/bug/radio var/obj/machinery/camera/bug/selected_camera var/list/obj/machinery/camera/bug/cameras = new() @@ -172,17 +170,14 @@ radio = new(src) */ /obj/item/bug_monitor/attack_self(mob/user) - if(operating) - return - // radio.attack_self(user) view_cameras(user) /obj/item/bug_monitor/attackby(obj/item/W as obj, mob/living/user as mob) if(istype(W, /obj/item/camerabug)) W.attackby(src, user) - else - return ..() + return + . = ..() /obj/item/bug_monitor/proc/unpair(var/obj/item/camerabug/SB) if(SB.camera in cameras) @@ -192,46 +187,36 @@ cameras += SB.camera /obj/item/bug_monitor/proc/view_cameras(mob/user) + if(in_use) + return + if(!can_use_cam(user)) return - - selected_camera = cameras[1] - user.reset_view(selected_camera) + if(cameras.len == 1) + selected_camera = cameras[1] + else + in_use = TRUE // Don't allow spamming tgui menus + selected_camera = tgui_input_list(user, "Select camera to view.", "Camera Choice", cameras) + in_use = FALSE view_camera(user) - operating = 1 - while(selected_camera && Adjacent(user)) - selected_camera = tgui_input_list(user, "Select camera to view.", "Camera Choice", cameras) - selected_camera = null - operating = 0 - /obj/item/bug_monitor/proc/view_camera(mob/user) - spawn(0) - while(selected_camera && Adjacent(user)) - var/turf/T = get_turf(selected_camera) - if(!T || !is_on_same_plane_or_station(T.z, user.z) || !selected_camera.can_use()) - user.unset_machine() - user.reset_view(null) - to_chat(user, span_notice("Link to [selected_camera] has been lost.")) - src.unpair(selected_camera.loc) - sleep(90) - else - user.set_machine(selected_camera) - user.reset_view(selected_camera) - sleep(10) - user.unset_machine() - user.reset_view(null) + if(loc != user) // Nice try smartass, must be in your hand and not in a box in your inventory + return + var/turf/T = get_turf(selected_camera) + if(!T || !is_on_same_plane_or_station(T.z, user.z) || !selected_camera.can_use()) + to_chat(user, span_notice("Link to [selected_camera] has been lost.")) + unpair(selected_camera) + selected_camera = null + return + user.AddComponent(/datum/component/remote_view/item_zoom, focused_on = selected_camera, our_item = src, viewsize = null, tileoffset = 0, show_visible_messages = TRUE) /obj/item/bug_monitor/proc/can_use_cam(mob/user) - if(operating) - return - if(!cameras.len) to_chat(user, span_warning("No paired cameras detected!")) to_chat(user, span_warning("Bring a camera in contact with this device to pair the camera.")) - return - - return 1 + return FALSE + return TRUE /obj/item/bug_monitor/spy name = "\improper PDA" diff --git a/code/game/objects/items/weapons/implants/implantchair.dm b/code/game/objects/items/weapons/implants/implantchair.dm index 1fc0bbd0c2..dee1107999 100644 --- a/code/game/objects/items/weapons/implants/implantchair.dm +++ b/code/game/objects/items/weapons/implants/implantchair.dm @@ -88,10 +88,7 @@ return if(M == occupant) // so that the guy inside can't eject himself -Agouri return - if (src.occupant.client) - src.occupant.client.eye = src.occupant.client.mob - src.occupant.client.perspective = MOB_PERSPECTIVE - src.occupant.loc = src.loc + src.occupant.forceMove(get_turf(src)) if(injecting) implant(src.occupant) injecting = 0 @@ -107,11 +104,8 @@ if(src.occupant) to_chat(usr, span_warning("\The [src] is already occupied!")) return - if(M.client) - M.client.perspective = EYE_PERSPECTIVE - M.client.eye = src M.stop_pulling() - M.loc = src + M.forceMove(src) src.occupant = M src.add_fingerprint(usr) icon_state = "implantchair_on" diff --git a/code/game/objects/structures/crates_lockers/__closets.dm b/code/game/objects/structures/crates_lockers/__closets.dm index 5a475032ca..4e07479d17 100644 --- a/code/game/objects/structures/crates_lockers/__closets.dm +++ b/code/game/objects/structures/crates_lockers/__closets.dm @@ -128,9 +128,6 @@ for(var/mob/M in src) M.forceMove(loc) - if(M.client) - M.client.eye = M.client.mob - M.client.perspective = MOB_PERSPECTIVE /obj/structure/closet/proc/open() if(opened) @@ -201,9 +198,6 @@ continue if(stored_units + added_units + M.mob_size > storage_capacity) break - if(M.client) - M.client.perspective = EYE_PERSPECTIVE - M.client.eye = src M.forceMove(src) added_units += M.mob_size return added_units diff --git a/code/game/objects/structures/crates_lockers/closets/statue.dm b/code/game/objects/structures/crates_lockers/closets/statue.dm index f4926d1ed3..152056d62a 100644 --- a/code/game/objects/structures/crates_lockers/closets/statue.dm +++ b/code/game/objects/structures/crates_lockers/closets/statue.dm @@ -7,6 +7,7 @@ anchored = TRUE health = 0 //destroying the statue kills the mob within blocks_emissive = EMISSIVE_BLOCK_UNIQUE + closet_appearance = null var/intialTox = 0 //these are here to keep the mob from taking damage from things that logically wouldn't affect a rock var/intialFire = 0 //it's a little sloppy I know but it was this or the GODMODE flag. Lesser of two evils. var/intialBrute = 0 @@ -19,10 +20,7 @@ if(L.buckled) L.buckled = 0 L.anchored = FALSE - if(L.client) - L.client.perspective = EYE_PERSPECTIVE - L.client.eye = src - L.loc = src + L.forceMove(src) L.sdisabilities |= MUTE health = L.health + 100 //stoning damaged mobs will result in easier to shatter statues intialTox = L.getToxLoss() @@ -61,15 +59,13 @@ /obj/structure/closet/statue/dump_contents() for(var/obj/O in src) - O.loc = src.loc + O.forceMove(get_turf(src)) for(var/mob/living/M in src) - M.loc = src.loc + M.forceMove(loc) // Might be in a belly M.sdisabilities &= ~MUTE M.take_overall_damage((M.health - health - 100),0) //any new damage the statue incurred is transfered to the mob - if(M.client) - M.client.eye = M.client.mob - M.client.perspective = MOB_PERSPECTIVE + M.reset_perspective() // Fixes a blackscreen flicker /obj/structure/closet/statue/open() return diff --git a/code/game/objects/structures/watercloset_vr.dm b/code/game/objects/structures/watercloset_vr.dm index b50815872f..ece1ea8d8e 100644 --- a/code/game/objects/structures/watercloset_vr.dm +++ b/code/game/objects/structures/watercloset_vr.dm @@ -62,7 +62,7 @@ /obj/structure/toilet/attack_ai(mob/user as mob) if(isrobot(user)) - if(user.client && user.client.eye == user) + if(user.client && !user.is_remote_viewing()) return attack_hand(user) else return attack_hand(user) diff --git a/code/game/objects/stumble_into_vr.dm b/code/game/objects/stumble_into_vr.dm index 1d9a6bc54d..43206c743e 100644 --- a/code/game/objects/stumble_into_vr.dm +++ b/code/game/objects/stumble_into_vr.dm @@ -24,9 +24,6 @@ playsound(src, 'sound/effects/clang.ogg', 25, 1, -1) visible_message(span_warning("[M] [pick("tripped", "stumbled")] into \the [src]!")) log_and_message_admins("stumbled into \the [src]", M) - if(M.client) - M.client.perspective = EYE_PERSPECTIVE - M.client.eye = src M.apply_damage(5, BRUTE) M.Weaken(2) M.forceMove(src) @@ -126,9 +123,6 @@ return ..() playsound(src, 'sound/effects/clang.ogg', 25, 1, -1) visible_message(span_warning("[M] [pick("tripped", "stumbled")] into \the [src]!")) - if(M.client) - M.client.perspective = EYE_PERSPECTIVE - M.client.eye = src M.forceMove(src) OCCUPANT = M isopen = 0 diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index 054f3b918a..8ba97c5ae0 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -875,6 +875,20 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( holder.particle_test = new /datum/particle_editor(in_atom) holder.particle_test.tgui_interact(mob) +/client/proc/set_eye(new_eye) + if(new_eye == eye) + return + var/atom/old_eye = eye + eye = new_eye + SEND_SIGNAL(src, COMSIG_CLIENT_SET_EYE, old_eye, new_eye) + +/mob/proc/is_remote_viewing() + if(!client || !client.mob || !client.eye) + return FALSE + if(isturf(client.mob.loc) && get_turf(client.eye) == get_turf(client.mob)) + return FALSE + return (client.eye != client.mob) + #undef ADMINSWARNED_AT #undef CURRENT_MINUTE #undef CURRENT_SECOND diff --git a/code/modules/food/kitchen/gibber.dm b/code/modules/food/kitchen/gibber.dm index 6cf36285c7..42f24f2784 100644 --- a/code/modules/food/kitchen/gibber.dm +++ b/code/modules/food/kitchen/gibber.dm @@ -153,10 +153,7 @@ src.add_fingerprint(user) if(do_after(user, 3 SECONDS, target = src) && victim.Adjacent(src) && user.Adjacent(src) && victim.Adjacent(user) && !occupant) user.visible_message(span_danger("[user] stuffs [victim] into the gibber!")) - if(victim.client) - victim.client.perspective = EYE_PERSPECTIVE - victim.client.eye = src - victim.loc = src + victim.forceMove(src) src.occupant = victim update_icon() @@ -176,10 +173,7 @@ return for(var/obj/O in src) O.loc = src.loc - if (src.occupant.client) - src.occupant.client.eye = src.occupant.client.mob - src.occupant.client.perspective = MOB_PERSPECTIVE - src.occupant.loc = src.loc + src.occupant.forceMove(get_turf(src)) src.occupant = null update_icon() return diff --git a/code/modules/food/recipe.dm b/code/modules/food/recipe.dm index a43529618e..97a0426dbd 100644 --- a/code/modules/food/recipe.dm +++ b/code/modules/food/recipe.dm @@ -211,14 +211,12 @@ var/obj/item/I = locate(i) in container if (I && I.reagents) I.reagents.trans_to_holder(buffer,I.reagents.total_volume) - // Outpost 21 upport start - Handle holders dropping mobs on destruction. No more endless mice burgers if(istype(I,/obj/item/holder)) var/obj/item/holder/hol = I if(hol.held_mob?.client) hol.held_mob.ghostize() qdel(hol.held_mob) hol.held_mob = null - // Outpost 21 upport end qdel(I) //Find fruits diff --git a/code/modules/mob/freelook/ai/eye.dm b/code/modules/mob/freelook/ai/eye.dm index 8a3710aa13..37ac45c13f 100644 --- a/code/modules/mob/freelook/ai/eye.dm +++ b/code/modules/mob/freelook/ai/eye.dm @@ -33,7 +33,7 @@ ai.camera_visibility(src) if(ai.client && !ai.multicam_on) - ai.client.eye = src + ai.reset_perspective(src) if(ai.master_multicam) ai.master_multicam.refresh_view() @@ -57,8 +57,7 @@ new_eye = src qdel(eyeobj) // No AI, no Eye eyeobj = null - if(client) - client.eye = new_eye + reset_perspective(new_eye) /mob/living/silicon/ai/proc/create_eyeobj(var/newloc) if(eyeobj) @@ -69,8 +68,7 @@ all_eyes += eyeobj eyeobj.owner = src eyeobj.name = "[src.name] (AI Eye)" // Give it a name - if(client) - client.eye = eyeobj + reset_perspective(eyeobj) SetName(src.name) /atom/proc/move_camera_by_click() @@ -87,9 +85,9 @@ if(!src.eyeobj) return + if(client.eye) + reset_perspective(src) - if(client && client.eye) - client.eye = src for(var/datum/chunk/c in eyeobj.visibleChunks) c.remove(eyeobj) src.eyeobj.setLoc(src) diff --git a/code/modules/mob/freelook/eye.dm b/code/modules/mob/freelook/eye.dm index fc46c6a678..2b042b06cf 100644 --- a/code/modules/mob/freelook/eye.dm +++ b/code/modules/mob/freelook/eye.dm @@ -64,9 +64,7 @@ if(T != loc) loc = T - if(owner.client) - owner.client.eye = src - + owner.reset_perspective(src) if(owner_follows_eye) visualnet.updateVisibility(owner, 0) owner.loc = loc diff --git a/code/modules/mob/holder.dm b/code/modules/mob/holder.dm index c04cbad37d..36c234a2d6 100644 --- a/code/modules/mob/holder.dm +++ b/code/modules/mob/holder.dm @@ -30,7 +30,6 @@ var/list/holder_mob_icon_cache = list() ASSERT(ismob(held)) . = ..() held.forceMove(src) - held.reset_view(src) START_PROCESSING(SSobj, src) /mob/living/get_status_tab_items() @@ -68,10 +67,10 @@ var/list/holder_mob_icon_cache = list() . += "" . += "Location: [location]" +/// Loads the mob into the holder and sets several vis_flags /obj/item/holder/Entered(mob/held, atom/OldLoc) if(held_mob) held.forceMove(get_turf(src)) - held.reset_view(null) return ASSERT(ismob(held)) . = ..() @@ -83,38 +82,39 @@ var/list/holder_mob_icon_cache = list() original_transform = held.transform held.transform = null +/// Handles restoring the vis flags and scale of the mob, also makes the holder invisible now that it's empty. /obj/item/holder/Exited(atom/movable/thing, atom/OldLoc) if(thing == held_mob) held_mob.transform = original_transform - held_mob.update_transform() //VOREStation edit + held_mob.update_transform() held_mob.vis_flags = original_vis_flags held_mob = null + invisibility = INVISIBILITY_ABSTRACT ..() +/// Dumps the mob if we still hold one, and if we are held by a mob clears us from its inventory. /obj/item/holder/Destroy() STOP_PROCESSING(SSobj, src) if(held_mob) + var/mob/cached_mob = held_mob dump_mob() + cached_mob.reset_perspective() // This case cannot be handled gracefully, make sure the mob view is cleaned up. if(ismob(loc)) var/mob/M = loc - M.drop_from_inventory(src, get_turf(src)) - return ..() + M.drop_from_inventory(src, loc) + . = ..() +/// If the mob somehow leaves the holder, clean us up. /obj/item/holder/process() - if(held_mob?.loc != src || isturf(loc)) + if(held_mob?.loc != src || isturf(loc) || isbelly(loc)) qdel(src) +/// Releases the mob from inside the holder. Calls forceMove() which calls Exited(). Then does cleanup for the client's eye location. /obj/item/holder/proc/dump_mob() if(!held_mob) return if (held_mob.loc == src || isnull(held_mob.loc)) - held_mob.transform = original_transform - held_mob.update_transform() - held_mob.vis_flags = original_vis_flags - held_mob.reset_view(null) - held_mob.forceMove(get_turf(src)) - held_mob = null - invisibility = INVISIBILITY_ABSTRACT + held_mob.forceMove(loc) /obj/item/holder/throw_at(atom/target, range, speed, thrower) if(held_mob) @@ -146,12 +146,10 @@ var/list/holder_mob_icon_cache = list() holster.clear_holster() to_chat(held, span_warning("You extricate yourself from [holster].")) forceMove(get_turf(src)) - held.reset_view(null) else if(isitem(loc)) var/obj/item/I = loc to_chat(held, span_warning("You struggle free of [loc].")) forceMove(get_turf(src)) - held.reset_view(null) if(istype(I)) I.on_holder_escape(src) diff --git a/code/modules/mob/living/carbon/alien/life.dm b/code/modules/mob/living/carbon/alien/life.dm index f992f280d0..77f801351a 100644 --- a/code/modules/mob/living/carbon/alien/life.dm +++ b/code/modules/mob/living/carbon/alien/life.dm @@ -134,12 +134,8 @@ set_fullscreen(disabilities & NEARSIGHTED, "impaired", /atom/movable/screen/fullscreen/impaired, 1) set_fullscreen(eye_blurry, "blurry", /atom/movable/screen/fullscreen/blurry) set_fullscreen(druggy, "high", /atom/movable/screen/fullscreen/high) - if(machine) - if(machine.check_eye(src) < 0) - reset_view(null) - else - if(client && !client.adminobs) - reset_view(null) + if(machine && machine.check_eye(src) < 0) + reset_perspective() return 1 diff --git a/code/modules/mob/living/carbon/brain/life.dm b/code/modules/mob/living/carbon/brain/life.dm index 37fcfe971b..95fe889588 100644 --- a/code/modules/mob/living/carbon/brain/life.dm +++ b/code/modules/mob/living/carbon/brain/life.dm @@ -210,11 +210,7 @@ set_fullscreen(eye_blurry, "blurry", /atom/movable/screen/fullscreen/blurry) set_fullscreen(druggy, "high", /atom/movable/screen/fullscreen/high) - if (machine) - if (!( machine.check_eye(src) )) - reset_view(null) - else - if(client && !client.adminobs) - reset_view(null) + if (machine && machine.check_eye(src) < 0) + reset_perspective() return 1 diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index eb4bd4cd63..c78e207e60 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -890,8 +890,6 @@ set category = "Superpower" if(stat!=CONSCIOUS) - reset_view(0) - remoteview_target = null return if(!(mMorph in mutations)) @@ -964,9 +962,7 @@ set name = "Project mind" set category = "Abilities.Superpower" - if(stat!=CONSCIOUS) - reset_view(0) - remoteview_target = null + if(stat != CONSCIOUS) return if(!(mRemotetalk in src.mutations)) @@ -995,17 +991,11 @@ set name = "Remote View" set category = "Abilities.Superpower" - if(stat!=CONSCIOUS) - remoteview_target = null - reset_view(0) + if(stat != CONSCIOUS) return - if(!(mRemote in src.mutations)) - remoteview_target = null - reset_view(0) - remove_verb(src, /mob/living/carbon/human/proc/remoteobserve) - if(client.eye != client.mob) - reset_view(0) + if(is_remote_viewing()) + reset_perspective() return var/list/mob/creatures = list() @@ -1022,13 +1012,9 @@ creatures += h var/mob/target = input ("Who do you want to project your mind to?") as mob in creatures - - if (target) - remoteview_target = target - reset_view(target) - else - remoteview_target = null - reset_view(0) + if(target) + AddComponent(/datum/component/remote_view/mremote_mutation, target) + return /mob/living/carbon/human/get_visible_gender(mob/user, force) switch(force) @@ -1572,7 +1558,7 @@ return remove_from_mob(W, src.loc) return ..() -/mob/living/carbon/human/reset_view(atom/A, update_hud = 1) +/mob/living/carbon/human/reset_perspective(atom/A, update_hud = 1) ..() if(update_hud) handle_regular_hud_updates() diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index 87525e688f..61f1518757 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -87,7 +87,6 @@ var/xylophone = 0 //For the spoooooooky xylophone cooldown - var/mob/remoteview_target = null var/hand_blood_color var/list/flavor_texts = list() diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm index 25ae7e3f50..b41278833f 100644 --- a/code/modules/mob/living/carbon/human/inventory.dm +++ b/code/modules/mob/living/carbon/human/inventory.dm @@ -367,17 +367,11 @@ This saves us from having to call add_fingerprint() any time something is put in update_inv_r_hand() W.hud_layerise() - - if(W.zoom) - W.zoom() - W.in_inactive_hand(src) - //VOREStation Addition Start if(istype(W, /obj/item)) var/obj/item/I = W I.equip_special() - //VOREStation Addition End return 1 diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index c56d3f897f..d372caf68c 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -1674,7 +1674,7 @@ if(found_welder) client.screen |= GLOB.global_hud.darkMask -/mob/living/carbon/human/reset_view(atom/A) +/mob/living/carbon/human/reset_perspective(atom/A) ..() if(machine_visual && machine_visual != A) machine_visual.remove_visual(src) @@ -1683,13 +1683,6 @@ if(stat == DEAD) sight |= SEE_TURFS|SEE_MOBS|SEE_OBJS|SEE_SELF see_in_dark = 8 - if(client) - if(client.view != world.view) // If mob dies while zoomed in with device, unzoom them. - for(var/obj/item/item in contents) - if(item.zoom) - item.zoom() - break - else //We aren't dead sight &= ~(SEE_TURFS|SEE_MOBS|SEE_OBJS) @@ -1731,12 +1724,12 @@ var/glasses_processed = 0 var/obj/item/rig/rig = get_rig() - if(istype(rig) && rig.visor && !looking_elsewhere) + if(istype(rig) && rig.visor && !is_remote_viewing()) if(!rig.helmet || (head && rig.helmet == head)) if(rig.visor && rig.visor.vision && rig.visor.active && rig.visor.vision.glasses) glasses_processed = process_glasses(rig.visor.vision.glasses) - if(glasses && !glasses_processed && !looking_elsewhere) + if(glasses && !glasses_processed && !is_remote_viewing()) glasses_processed = process_glasses(glasses) if(XRAY in mutations) sight |= SEE_TURFS|SEE_MOBS|SEE_OBJS @@ -1765,23 +1758,13 @@ if(machine) var/viewflags = machine.check_eye(src) if(viewflags < 0) - reset_view(null, 0) - else if(viewflags && !looking_elsewhere) + reset_perspective() + else if(viewflags && !is_remote_viewing()) sight |= viewflags else machine.apply_visual(src) - else if(eyeobj) - if(eyeobj.owner != src) - - reset_view(null) - else - var/isRemoteObserve = 0 - if((mRemote in mutations) && remoteview_target) - if(remoteview_target.stat==CONSCIOUS) - isRemoteObserve = 1 - if(!isRemoteObserve && client && !client.adminobs) - remoteview_target = null - reset_view(null, 0) + else if(eyeobj && eyeobj.owner != src) + reset_perspective() return 1 /mob/living/carbon/human/proc/process_glasses(var/obj/item/clothing/glasses/G) diff --git a/code/modules/mob/living/carbon/human/species/station/protean/protean_powers.dm b/code/modules/mob/living/carbon/human/species/station/protean/protean_powers.dm index 7a4cc57ca9..9fda802866 100644 --- a/code/modules/mob/living/carbon/human/species/station/protean/protean_powers.dm +++ b/code/modules/mob/living/carbon/human/species/station/protean/protean_powers.dm @@ -365,7 +365,6 @@ src.drop_from_inventory(S.OurRig) P.forceMove(S.OurRig) S.OurRig.canremove = 1 - P.reset_view() else //Make one if not to_chat(temporary_form, span_warning("Somehow, your RIG got disconnected from your species. This may have been caused by an admin heal. A new one has been created for you, contact a coder.")) new /obj/item/rig/protean(src,src) diff --git a/code/modules/mob/living/carbon/human/species/station/protean/protean_rig.dm b/code/modules/mob/living/carbon/human/species/station/protean/protean_rig.dm index fe6bf1313c..11459b27fd 100644 --- a/code/modules/mob/living/carbon/human/species/station/protean/protean_rig.dm +++ b/code/modules/mob/living/carbon/human/species/station/protean/protean_rig.dm @@ -16,6 +16,7 @@ seal_delay = 0 var/mob/living/myprotean initial_modules = list(/obj/item/rig_module/protean/syphon, /obj/item/rig_module/protean/armor, /obj/item/rig_module/protean/healing) + flags = PHORONGUARD item_flags = NOSTRIP helm_type = /obj/item/clothing/head/helmet/space/rig/protean //These are important for sprite pointers diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index a12fee4d75..281ebb3df4 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1268,8 +1268,6 @@ // We just swapped hands, so the thing in our inactive hand will notice it's not the focus var/obj/item/I = get_inactive_hand() if(I) - if(I.zoom) - I.zoom() I.in_inactive_hand(src) //This'll do specific things, determined by the item return diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index e40f60544c..ed2cba6e6b 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -72,8 +72,6 @@ var/makes_dirt = TRUE //FALSE if the mob shouldn't be making dirt on the ground when it walks - var/looking_elsewhere = FALSE //If the mob's view has been relocated to somewhere else, like via a camera or with binocs - var/image/selected_image = null // Used for buildmode AI control stuff. var/allow_self_surgery = FALSE // Used to determine if the mob can perform surgery on itself. diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index adb8a1cf97..2f78a5a9ff 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -513,7 +513,7 @@ var/list/ai_verbs_default = list( if(.) end_multicam() -/mob/living/silicon/ai/reset_view(atom/A) +/mob/living/silicon/ai/reset_perspective(atom/A) if(camera) camera.set_light(0) if(istype(A,/obj/machinery/camera)) @@ -524,8 +524,7 @@ var/list/ai_verbs_default = list( if(.) if(!A && isturf(loc) && eyeobj) end_multicam() - client.eye = eyeobj - client.perspective = MOB_PERSPECTIVE + reset_perspective(eyeobj) if(istype(A,/obj/machinery/camera)) if(camera_light_on) A.set_light(AI_CAMERA_LUMINOSITY) else A.set_light(0) diff --git a/code/modules/mob/living/silicon/ai/life.dm b/code/modules/mob/living/silicon/ai/life.dm index 056511aee8..beadb686e5 100644 --- a/code/modules/mob/living/silicon/ai/life.dm +++ b/code/modules/mob/living/silicon/ai/life.dm @@ -7,7 +7,7 @@ if (src.stat != CONSCIOUS) src.cameraFollow = null - src.reset_view(null) + src.reset_perspective() disconnect_shell("Disconnecting from remote shell due to local system failure.") src.updatehealth() diff --git a/code/modules/mob/living/silicon/ai/logout.dm b/code/modules/mob/living/silicon/ai/logout.dm index 4cb5c1ebda..d9dac30887 100644 --- a/code/modules/mob/living/silicon/ai/logout.dm +++ b/code/modules/mob/living/silicon/ai/logout.dm @@ -2,9 +2,5 @@ ..() for(var/obj/machinery/ai_status_display/O in GLOB.machines) //change status O.mode = 0 - if(!isturf(loc)) - if (client) - client.eye = loc - client.perspective = EYE_PERSPECTIVE src.view_core() return diff --git a/code/modules/mob/living/silicon/ai/multicam.dm b/code/modules/mob/living/silicon/ai/multicam.dm index 98f0419cf4..16736bb6eb 100644 --- a/code/modules/mob/living/silicon/ai/multicam.dm +++ b/code/modules/mob/living/silicon/ai/multicam.dm @@ -246,7 +246,7 @@ GLOBAL_DATUM(ai_camera_room_landmark, /obj/effect/landmark/ai_multicam_room) to_chat(src, span_notice("Multiple-camera viewing mode activated.")) /mob/living/silicon/ai/proc/refresh_multicam() - reset_view(GLOB.ai_camera_room_landmark) + reset_perspective(GLOB.ai_camera_room_landmark) if(client) for(var/atom/movable/screen/movable/pic_in_pic/P as anything in multicam_screens) P.show_to(client) @@ -259,7 +259,7 @@ GLOBAL_DATUM(ai_camera_room_landmark, /obj/effect/landmark/ai_multicam_room) if(client) for(var/atom/movable/screen/movable/pic_in_pic/P as anything in multicam_screens) P.unshow_to(client) - reset_view() + reset_perspective(src) to_chat(src, span_notice("Multiple-camera viewing mode deactivated.")) diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm index 249524edd2..77c87e8211 100644 --- a/code/modules/mob/living/silicon/pai/pai.dm +++ b/code/modules/mob/living/silicon/pai/pai.dm @@ -178,7 +178,7 @@ /mob/living/silicon/pai/proc/switchCamera(var/obj/machinery/camera/C) if (!C) src.unset_machine() - src.reset_view(null) + src.reset_perspective() return 0 if (stat == 2 || !C.status || !(src.network in C.network)) return 0 @@ -186,7 +186,7 @@ src.set_machine(src) src.current = C - src.reset_view(C) + src.AddComponent(/datum/component/remote_view, C) return 1 /mob/living/silicon/pai/verb/reset_record_view() @@ -205,7 +205,7 @@ /mob/living/silicon/pai/cancel_camera() set category = "Abilities.pAI Commands" set name = "Cancel Camera View" - src.reset_view(null) + src.reset_perspective() src.unset_machine() src.cameraFollow = null @@ -220,7 +220,25 @@ if(stat || sleeping || paralysis || weakened) return - if(src.loc != card) + if(loc != card) + return + + // Lets not trap the pai forever. These are special cases we want to escape out of when in our card + if(istype(loc.loc, /obj/item/pda)) + var/obj/item/pda/ourpda = loc.loc + if(ourpda.pai == card) + ourpda.pai.forceMove(ourpda.loc) + ourpda.pai = null + visible_message(span_warning("\The [card] ejects itself from \the [ourpda].")) + return + if(istype(loc.loc, /obj/item/storage/vore_egg)) + var/obj/item/storage/vore_egg/ouregg = loc.loc + to_chat(src, span_notice("You craftily use your built in rumble function to break free of \the [ouregg]'s confines!")) + ouregg.hatch(src) + return + + if(is_folding_unsafe(loc.loc)) + to_chat(src, span_danger("It's not safe to unfold while inside a [loc.loc]!")) return if(card.projector != PP_FUNCTIONAL && card.emitter != PP_FUNCTIONAL) @@ -257,16 +275,15 @@ var/obj/item/pda/holder = card.loc holder.pai = null - src.client.perspective = EYE_PERSPECTIVE - src.client.eye = src - src.forceMove(get_turf(card)) - + src.forceMove(card.loc) card.forceMove(src) card.screen_loc = null canmove = TRUE - var/turf/T = get_turf(src) - if(istype(T)) T.visible_message(span_filter_notice(span_bold("[src]") + " folds outwards, expanding into a mobile form.")) + if(isturf(loc)) + var/turf/T = get_turf(src) + if(istype(T)) T.visible_message(span_filter_notice(span_bold("[src]") + " folds outwards, expanding into a mobile form.")) + add_verb(src, /mob/living/silicon/pai/proc/pai_nom) add_verb(src, /mob/living/proc/vertical_nom) update_icon() @@ -376,7 +393,12 @@ last_special = world.time + 100 - if(src.loc == card) + if(loc == card) + return + + // some snowflake locations where we really shouldn't fold up... + if(is_folding_unsafe(loc)) + to_chat(src, span_danger("It's not safe to fold up while inside a [loc]!")) return release_vore_contents(FALSE) //VOREStation Add @@ -384,40 +406,35 @@ var/turf/T = get_turf(src) if(istype(T) && !silent) T.visible_message(span_filter_notice(span_bold("[src]") + " neatly folds inwards, compacting down to a rectangular card.")) - if(client) - src.stop_pulling() - src.client.perspective = EYE_PERSPECTIVE - src.client.eye = card + stop_pulling() //stop resting resting = 0 // If we are being held, handle removing our holder from their inv. - var/obj/item/holder/H = loc - if(istype(H)) - var/mob/living/M = H.loc + var/obj/item/holder/our_holder = loc + if(istype(our_holder)) + var/turf/drop_turf = get_turf(our_holder) + var/mob/living/M = our_holder.loc if(istype(M)) - M.drop_from_inventory(H) - H.loc = get_turf(src) - src.loc = get_turf(H) + M.drop_from_inventory(our_holder) + src.forceMove(card) + card.forceMove(drop_turf) if(isbelly(loc)) //If in tumby, when fold up, card go into tumby var/obj/belly/B = loc src.forceMove(card) card.forceMove(B) - if(istype( src.loc,/obj/structure/disposalholder)) + if(istype(loc,/obj/structure/disposalholder)) var/obj/structure/disposalholder/hold = loc - src.loc = card - card.loc = hold src.forceMove(card) card.forceMove(hold) else //Otherwise go on floor - src.loc = card - card.loc = get_turf(card) + card.forceMove(get_turf(src)) src.forceMove(card) - card.forceMove(card.loc) + canmove = 1 resting = 0 icon_state = "[chassis]" @@ -426,6 +443,9 @@ remove_verb(src, /mob/living/silicon/pai/proc/pai_nom) remove_verb(src, /mob/living/proc/vertical_nom) +/mob/living/silicon/pai/proc/is_folding_unsafe(check_location) + return isbelly(check_location) || istype(check_location, /obj/machinery) || istype(check_location, /obj/item/storage/vore_egg || istype(check_location, /obj/item/pda)) + // No binary for pAIs. /mob/living/silicon/pai/binarycheck() return 0 diff --git a/code/modules/mob/living/silicon/pai/pai_hud.dm b/code/modules/mob/living/silicon/pai/pai_hud.dm index f52f26284d..5034400300 100644 --- a/code/modules/mob/living/silicon/pai/pai_hud.dm +++ b/code/modules/mob/living/silicon/pai/pai_hud.dm @@ -5,7 +5,7 @@ icon = 'icons/mob/pai_hud.dmi' var/base_state -/atom/movable/pai/Click(location, control, params) +/atom/movable/screen/pai/Click(location, control, params) . = ..() if(!ispAI(usr)) return diff --git a/code/modules/mob/living/silicon/robot/dogborg/dog_sleeper.dm b/code/modules/mob/living/silicon/robot/dogborg/dog_sleeper.dm index a99dae7fc0..b294bfd082 100644 --- a/code/modules/mob/living/silicon/robot/dogborg/dog_sleeper.dm +++ b/code/modules/mob/living/silicon/robot/dogborg/dog_sleeper.dm @@ -105,7 +105,6 @@ user.visible_message(span_warning("[hound.name] is ingesting [trashmouse] into their [src.name]."), span_notice("You start ingesting [trashmouse] into your [src.name]...")) if(do_after(user, 3 SECONDS, target = trashmouse) && length(contents) < max_item_count) trashmouse.forceMove(src) - trashmouse.reset_view(src) user.visible_message(span_warning("[hound.name]'s [src.name] groans lightly as [trashmouse] slips inside."), span_notice("Your [src.name] groans lightly as [trashmouse] slips inside.")) playsound(src, gulpsound, vol = 60, vary = 1, falloff = 0.1, preference = /datum/preference/toggle/eating_noises) if(delivery) @@ -125,7 +124,6 @@ user.visible_message(span_warning("[hound.name] is ingesting [trashman] into their [src.name]."), span_notice("You start ingesting [trashman] into your [src.name]...")) if(do_after(user, 3 SECONDS, target = trashman) && !patient && !trashman.buckled && length(contents) < max_item_count) trashman.forceMove(src) - trashman.reset_view(src) START_PROCESSING(SSobj, src) user.visible_message(span_warning("[hound.name]'s [src.name] groans lightly as [trashman] slips inside."), span_notice("Your [src.name] groans lightly as [trashman] slips inside.")) log_attack("[key_name(hound)] has eaten [key_name(patient)] with a cyborg belly. ([hound ? "JMP" : "null"])") @@ -155,7 +153,6 @@ return //If you try to eat two people at once, you can only eat one. else //If you don't have someone in you, proceed. H.forceMove(src) - H.reset_view(src) update_patient() START_PROCESSING(SSobj, src) user.visible_message(span_warning("[hound.name]'s [src.name] lights up as [H.name] slips inside."), span_notice("Your [src] lights up as [H] slips inside. Life support functions engaged.")) @@ -206,7 +203,6 @@ if(ishuman(C)) var/mob/living/carbon/human/person = C person.forceMove(get_turf(src)) - person.reset_view() else var/obj/T = C T.loc = hound.loc @@ -416,7 +412,6 @@ if(ishuman(C)) var/mob/living/carbon/human/person = C person.forceMove(get_turf(src)) - person.reset_view() else var/obj/T = C T.loc = hound.loc diff --git a/code/modules/mob/living/silicon/robot/life.dm b/code/modules/mob/living/silicon/robot/life.dm index c8189d941a..b97ff5264a 100644 --- a/code/modules/mob/living/silicon/robot/life.dm +++ b/code/modules/mob/living/silicon/robot/life.dm @@ -300,12 +300,8 @@ set_fullscreen(eye_blurry, "blurry", /atom/movable/screen/fullscreen/blurry) set_fullscreen(druggy, "high", /atom/movable/screen/fullscreen/high) - if (machine) - if (machine.check_eye(src) < 0) - reset_view(null) - else - if(client && !client.adminobs) - reset_view(null) + if (machine && machine.check_eye(src) < 0) + reset_perspective() if(emagged) throw_alert("hacked", /atom/movable/screen/alert/hacked) diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index ab07eb7d3f..a39063dc1f 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -395,7 +395,7 @@ /mob/living/silicon/setEarDamage() return -/mob/living/silicon/reset_view() +/mob/living/silicon/reset_perspective(atom/new_eye) . = ..() if(cameraFollow) cameraFollow = null diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm index f535c60cf6..55357fe97e 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm @@ -171,7 +171,6 @@ forceMove(get_turf(host)) - reset_view(null) machine = null if(ishuman(host)) @@ -180,7 +179,6 @@ if(head) head.implants -= src - host.reset_view(null) host.machine = null host = null diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/leech.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/leech.dm index ed5394e5b0..8ba2db4bba 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/sif/leech.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/leech.dm @@ -331,8 +331,6 @@ forceMove(get_turf(host)) - reset_view(null) - host = null /mob/living/simple_mob/animal/sif/leech/verb/inject_victim() diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm index 33d354f4fc..5e288ba376 100644 --- a/code/modules/mob/login.dm +++ b/code/modules/mob/login.dm @@ -52,12 +52,9 @@ ..() SEND_SIGNAL(src, COMSIG_MOB_LOGIN) - if(loc && !isturf(loc)) - client.eye = loc - client.perspective = EYE_PERSPECTIVE - else - client.eye = src + if(!restore_remote_views()) client.perspective = MOB_PERSPECTIVE + client.set_eye(src) add_click_catcher() update_client_color() diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 3b14f40788..8e9b65ea05 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -244,19 +244,69 @@ /mob/proc/restrained() return -/mob/proc/reset_view(atom/A) - if (client) - if (istype(A, /atom/movable)) - client.perspective = EYE_PERSPECTIVE - client.eye = A - else - if (isturf(loc)) - client.eye = client.mob - client.perspective = MOB_PERSPECTIVE - else +/** + * Reset the attached clients perspective (viewpoint) + * + * reset_perspective() set eye to common default : mob on turf, loc otherwise. If the client mob is inside an object with REMOTEVIEW_ON_ENTER, it will restart that object's remote view. + * reset_perspective(thing) set the eye to the thing (if it's equal to current default reset to mob perspective). This ignores REMOTEVIEW_ON_ENTER, and forces focus to the mob. + */ +/mob/proc/reset_perspective(atom/new_eye) + SHOULD_CALL_PARENT(TRUE) + if(!client) + return + + if(new_eye) + if(ismovable(new_eye)) + //Set the new eye unless it's us + if(new_eye != src) client.perspective = EYE_PERSPECTIVE - client.eye = loc + client.set_eye(new_eye) + else + client.set_eye(client.mob) + client.perspective = MOB_PERSPECTIVE + + else if(isturf(new_eye)) + //Set to the turf unless it's our current turf + if(new_eye != loc) + client.perspective = EYE_PERSPECTIVE + client.set_eye(new_eye) + else + client.set_eye(client.mob) + client.perspective = MOB_PERSPECTIVE + else + return TRUE //no setting eye to stupid things like areas or whatever + else + //If we return focus to our own mob, but we are still inside something with an inherent remote view. Restart it. + if(restore_remote_views()) + return TRUE + //Reset to common defaults: mob if on turf, otherwise current loc + if(isturf(loc)) + client.set_eye(client.mob) + client.perspective = MOB_PERSPECTIVE + else + client.perspective = EYE_PERSPECTIVE + client.set_eye(loc) + /// Signal sent after the eye has been successfully updated, with the client existing. + SEND_SIGNAL(src, COMSIG_MOB_RESET_PERSPECTIVE) + return TRUE + +/// Reapplies remote views based on object type and flags. Returns true if the view was assigned. +/mob/proc/restore_remote_views() + if(!loc) // Nullspace during respawn + return FALSE + if(isturf(loc)) // Cannot be remote if it was a turf, also obj and turf flags overlap so stepping into space triggers remoteview endlessly. + return FALSE + // Check if we actually need to drop our current remote view component, as this is expensive to do, and leads to more difficult to understand error prone logic + var/datum/component/remote_view/remote_comp = GetComponent(/datum/component/remote_view) + if(remote_comp?.looking_at_target_already(loc)) + return FALSE + if(isitem(loc) || isbelly(loc)) // Requires more careful handling than structures because they are held by mobs + AddComponent(/datum/component/remote_view/mob_holding_item, loc) return TRUE + if(loc.flags & REMOTEVIEW_ON_ENTER) // Handle atoms that begin a remote view upon entering them. + AddComponent(/datum/component/remote_view, loc) + return TRUE + return FALSE /mob/proc/ret_grab(list/L, flag) return @@ -509,17 +559,17 @@ var/mob/mob_eye = targets[eye_name] if(client && mob_eye) - client.eye = mob_eye - if (is_admin) - client.adminobs = 1 - if(mob_eye == client.mob || client.eye == client.mob) - client.adminobs = 0 + AddComponent(/datum/component/remote_view, focused_on = mob_eye) + if(is_admin) + client.adminobs = TRUE + if(mob_eye == client.mob || !is_remote_viewing()) + client.adminobs = FALSE /mob/verb/cancel_camera() set name = "Cancel Camera View" set category = "OOC.Game" unset_machine() - reset_view(null) + reset_perspective() /mob/Topic(href, href_list) if(href_list["mach_close"]) diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index ca0863db45..dc0ba83d9f 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -205,13 +205,6 @@ if(L.is_incorporeal())//Move though walls Process_Incorpmove(direct) return - /* TODO observer unzoom - if(view != world.view) // If mob moves while zoomed in with device, unzoom them. - for(var/obj/item/item in mob.contents) - if(item.zoom) - item.zoom() - break - */ if(Process_Grab()) return diff --git a/code/modules/multiz/movement.dm b/code/modules/multiz/movement.dm index 41f7c46fec..ab1316f80f 100644 --- a/code/modules/multiz/movement.dm +++ b/code/modules/multiz/movement.dm @@ -582,6 +582,8 @@ adjustBruteLoss(rand(damage_min, damage_max)) Weaken(4) updatehealth() + // There is really no situation where smacking into a floor and possibly dying horribly would NOT result in you dropping your remote view... It's also safer then assuming they should persist. + reset_perspective() /mob/living/carbon/human/fall_impact(atom/hit_atom, damage_min, damage_max, silent, planetary) if(!species?.handle_falling(src, hit_atom, damage_min, damage_max, silent, planetary)) diff --git a/code/modules/overmap/ships/computers/helm.dm b/code/modules/overmap/ships/computers/helm.dm index 86e738e925..8914dd3d74 100644 --- a/code/modules/overmap/ships/computers/helm.dm +++ b/code/modules/overmap/ships/computers/helm.dm @@ -106,9 +106,9 @@ GLOBAL_LIST_EMPTY(all_waypoints) /obj/machinery/computer/ship/helm/tgui_close(mob/user) . = ..() - // Unregister map objects user.client?.clear_map(linked?.map_name) + user.reset_perspective() /obj/machinery/computer/ship/helm/tgui_data(mob/user) var/list/data = ..() @@ -270,7 +270,11 @@ GLOBAL_LIST_EMPTY(all_waypoints) . = TRUE if("manual") - viewing_overmap(ui.user) ? unlook(ui.user) : look(ui.user) + if(!viewing_overmap(ui.user) && linked) + if(!viewers) viewers = list() // List must exist for pass by reference to work + start_coordinated_remoteview(ui.user, linked, viewers) + else + ui.user.reset_perspective() . = TRUE add_fingerprint(ui.user) diff --git a/code/modules/overmap/ships/computers/sensors.dm b/code/modules/overmap/ships/computers/sensors.dm index a6ee328427..93f7af14af 100644 --- a/code/modules/overmap/ships/computers/sensors.dm +++ b/code/modules/overmap/ships/computers/sensors.dm @@ -89,7 +89,11 @@ switch(action) if("viewing") if(ui.user && !isAI(ui.user)) - viewing_overmap(ui.user) ? unlook(ui.user) : look(ui.user) + if(!viewing_overmap(ui.user) && linked) + if(!viewers) viewers = list() // List must exist for pass by reference to work + start_coordinated_remoteview(ui.user, linked, viewers) + else + ui.user.reset_perspective() . = TRUE if("link") diff --git a/code/modules/overmap/ships/computers/ship.dm b/code/modules/overmap/ships/computers/ship.dm index f7a821954e..98527adc84 100644 --- a/code/modules/overmap/ships/computers/ship.dm +++ b/code/modules/overmap/ships/computers/ship.dm @@ -35,7 +35,7 @@ somewhere on that shuttle. Subtypes of these can be then used to perform ship ov /obj/machinery/computer/ship/proc/display_reconnect_dialog(var/mob/user, var/flavor) var/datum/browser/popup = new (user, "[src]", "[src]") if(viewing_overmap(user)) - unlook(user, TRUE) + user.reset_perspective() popup.set_content("