diff --git a/code/__DEFINES/admin_defines.dm b/code/__DEFINES/admin_defines.dm index 1856a55e010..fd1ada925ef 100644 --- a/code/__DEFINES/admin_defines.dm +++ b/code/__DEFINES/admin_defines.dm @@ -46,12 +46,14 @@ #define R_HOST ((1<<18)-1) //17 bit bitmask, update me if we ever add more admin permissions. Sum of all permissions to allow easy setting. + #define ADMIN_QUE(user,display) "[display]" #define ADMIN_FLW(user,display) "[display]" #define ADMIN_PP(user,display) "[display]" #define ADMIN_VV(atom,display) "[display]" #define ADMIN_SM(user,display) "[display]" #define ADMIN_TP(user,display) "[display]" +#define ADMIN_OBS(user, display) "[display]" #define ADMIN_ALERT(user, display) "[display]" #define ADMIN_BSA(user,display) "[display]" #define ADMIN_CENTCOM_REPLY(user,display) "[display]" diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 3dd763ba3da..84491eb549b 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -181,6 +181,8 @@ #define COMSIG_ATOM_ORBIT_BEGIN "atom_orbit_begin" ///called when an atom stops orbiting another atom: (atom) #define COMSIG_ATOM_ORBIT_STOP "atom_orbit_stop" +/// called on an atom who has stopped orbiting another atom (atom/orbiter, atom/formerly_orbited) +#define COMSIG_ATOM_ORBITER_STOP "atom_orbiter_stop" ///from base of atom/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) #define COMSIG_ATOM_HITBY "atom_hitby" /// Called when an atom is sharpened or dulled. @@ -945,11 +947,26 @@ ///from base of datum/action/proc/Trigger(): (datum/action) #define COMSIG_ACTION_TRIGGER "action_trigger" #define COMPONENT_ACTION_BLOCK_TRIGGER (1<<0) +/// From /datum/action/Grant(): (mob/grant_to) +#define COMSIG_ACTION_GRANTED "action_grant" +/// From /datum/action/Grant(): (datum/action) +#define COMSIG_MOB_GRANTED_ACTION "mob_action_grant" +/// From /datum/action/Remove(): (mob/removed_from) +#define COMSIG_ACTION_REMOVED "action_removed" +/// From /datum/action/Remove(): (datum/action) +#define COMSIG_MOB_REMOVED_ACTION "mob_action_removed" // Note that this is only defined for actions because this could be a good bit expensive otherwise /// From base of /atom/movable/screen/movable/action_button/MouseWheel(src, delta_x, delta_y, location, control, params) #define COMSIG_ACTION_SCROLLED "action_scrolled" +// ghost signals + +/// from observer_base/do_observe(): (mob/now_followed) +#define COMSIG_GHOST_START_OBSERVING "ghost_start_observing" +/// from observer_base/do_observe(): (mob/no_longer_following) +#define COMSIG_GHOST_STOP_OBSERVING "ghost_stop_observing" + //Xenobio hotkeys ///from slime CtrlClickOn(): (/mob) diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm index 3e1275ba880..8f87cf4ec58 100644 --- a/code/__HELPERS/game.dm +++ b/code/__HELPERS/game.dm @@ -164,6 +164,8 @@ if(sight_check && !isInSight(A, O)) continue L |= M + for(var/mob/dead/observer/ghost in M.observers) + L |= ghost //log_world("[recursion_limit] = [M] - [get_turf(M)] - ([M.x], [M.y], [M.z])") if(isobj(A) || ismob(A)) diff --git a/code/__HELPERS/lists.dm b/code/__HELPERS/lists.dm index cd6d5d8820b..1f1af7032bd 100644 --- a/code/__HELPERS/lists.dm +++ b/code/__HELPERS/lists.dm @@ -676,6 +676,7 @@ #define UNSETEMPTY(L) if(L && !length(L)) L = null #define LAZYREMOVE(L, I) if(L) { L -= I; if(!length(L)) { L = null; } } #define LAZYADD(L, I) if(!L) { L = list(); } L += I; +/// Add an item to the list if not already present, if the list is null it will initialize it #define LAZYOR(L, I) if(!L) { L = list(); } L |= I; /// Adds I to L, initializing L if necessary, if I is not already in L #define LAZYDISTINCTADD(L, I) if(!L) { L = list(); } L |= I; diff --git a/code/__HELPERS/mob_helpers.dm b/code/__HELPERS/mob_helpers.dm index d4e50baa361..30c2693a643 100644 --- a/code/__HELPERS/mob_helpers.dm +++ b/code/__HELPERS/mob_helpers.dm @@ -554,7 +554,7 @@ GLOBAL_LIST_EMPTY(do_after_once_tracker) to_chat(user, "Name = [M.name]; Real_name = [M.real_name]; Mind_name = [M.mind?"[M.mind.name]":""]; Key = [M.key];") to_chat(user, "Location = [location_description];") to_chat(user, "[special_role_description]") - to_chat(user, "(PM) ([ADMIN_PP(M,"PP")]) ([ADMIN_VV(M,"VV")]) ([ADMIN_TP(M,"TP")]) ([ADMIN_SM(M,"SM")]) ([ADMIN_FLW(M,"FLW")])") + to_chat(user, "(PM) ([ADMIN_PP(M,"PP")]) ([ADMIN_VV(M,"VV")]) ([ADMIN_TP(M,"TP")]) ([ADMIN_SM(M,"SM")]) ([ADMIN_FLW(M,"FLW")]) ([ADMIN_OBS(M, "OBS")])") // Gets the first mob contained in an atom, and warns the user if there's not exactly one /proc/get_mob_in_atom_with_warning(atom/A, mob/user = usr) diff --git a/code/__HELPERS/trait_helpers.dm b/code/__HELPERS/trait_helpers.dm index e3778abe37f..112661491d7 100644 --- a/code/__HELPERS/trait_helpers.dm +++ b/code/__HELPERS/trait_helpers.dm @@ -235,6 +235,10 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_SLEIGHT_OF_HAND "sleight_of_hand" #define TRAIT_KNOWS_COOKING_RECIPES "knows_cooking_recipes" +/// used for dead mobs that are observing, but should not be afforded all the same platitudes as full ghosts. +/// This is a mind trait because ghosts can be frequently deleted and we want to be sure this sticks. +#define TRAIT_MENTOR_OBSERVING "mentor_observe" + //***** ITEM AND MOB TRAITS *****// /// Show what machine/door wires do when held. #define TRAIT_SHOW_WIRE_INFO "show_wire_info" @@ -321,6 +325,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define HOLO_CIGAR "holo_cigar" #define GLADIATOR "gladiator" #define PULSEDEMON_TRAIT "pulse_demon" +/// Mentor observing +#define MENTOR_OBSERVING "mobserving" //quirk traits #define TRAIT_ALCOHOL_TOLERANCE "alcohol_tolerance" diff --git a/code/_onclick/hud/action_button.dm b/code/_onclick/hud/action_button.dm index 205d4fe1dde..a8303d46fb8 100644 --- a/code/_onclick/hud/action_button.dm +++ b/code/_onclick/hud/action_button.dm @@ -16,6 +16,8 @@ var/id /// UID of the last thing we hovered over. Used for managing action button dragging. var/last_hovered_ref + /// Whether or not this should be shown to observers + var/shown_to_observers = FALSE /// Whether or not this button is locked, preventing it from being dragged. var/locked = FALSE @@ -32,8 +34,14 @@ return ..() /atom/movable/screen/movable/action_button/proc/can_use(mob/user) + if(isobserver(user)) + var/mob/dead/observer/dead_mob = user + if(dead_mob.mob_observed) // Observers can only click on action buttons if they're not observing something + return FALSE if(!linked_action) return TRUE + if(linked_action.owner != user) + return FALSE return !isnull(linked_action.viewers[user.hud_used]) // Entered and Exited won't fire while you're dragging something, because you're still "holding" it @@ -60,7 +68,8 @@ last_hovered.MouseExited(over_location, over_control, params) closeToolTip(usr) last_hovered_ref = UID(over_object) - over_object.MouseEntered(over_location, over_control, params) + if(!isnull(over_object)) + over_object.MouseEntered(over_location, over_control, params) /atom/movable/screen/movable/action_button/MouseDrop(over_object) last_hovered_ref = null @@ -113,6 +122,8 @@ animate(src, transform = matrix(), time = 0.4 SECONDS, alpha = 255) /atom/movable/screen/movable/action_button/Click(location, control, params) + if(!can_use(usr)) + return FALSE var/list/modifiers = params2list(params) if(modifiers["ctrl"] && modifiers["shift"]) INVOKE_ASYNC(src, PROC_REF(set_to_keybind), usr) @@ -578,4 +589,47 @@ GLOBAL_LIST_INIT(palette_removed_matrix, list(1.4,0,0,0, 0.7,0.4,0,0, 0.4,0,0.6, button = action.viewers[src] position_action(button, button.location) + +/** + * Show (most) of the another mob's action buttons to this mob + * + * Used for observers viewing another mob's screen + */ +/mob/proc/show_other_mob_action_buttons(mob/take_from) + if(!hud_used || !client) + return + + for(var/datum/action/action as anything in take_from.actions) + if(!action.show_to_observers) + continue + action.GiveAction(src) + RegisterSignal(take_from, COMSIG_MOB_GRANTED_ACTION, PROC_REF(on_observing_action_granted), override = TRUE) + RegisterSignal(take_from, COMSIG_MOB_REMOVED_ACTION, PROC_REF(on_observing_action_removed), override = TRUE) + +/** + * Hide another mob's action buttons from this mob + * + * Used for observers viewing another mob's screen + */ +/mob/proc/hide_other_mob_action_buttons(mob/take_from) + for(var/datum/action/action as anything in take_from.actions) + action.HideFrom(src) + UnregisterSignal(take_from, list(COMSIG_MOB_GRANTED_ACTION, COMSIG_MOB_REMOVED_ACTION)) + +/// Signal proc for [COMSIG_MOB_GRANTED_ACTION] - If we're viewing another mob's action buttons, +/// we need to update with any newly added buttons granted to the mob. +/mob/proc/on_observing_action_granted(mob/living/source, datum/action/action) + SIGNAL_HANDLER // COMSIG_MOB_GRANTED_ACTION + + if(!action.show_to_observers) + return + action.GiveAction(src) + +/// Signal proc for [COMSIG_MOB_REMOVED_ACTION] - If we're viewing another mob's action buttons, +/// we need to update with any removed buttons from the mob. +/mob/proc/on_observing_action_removed(mob/living/source, datum/action/action) + SIGNAL_HANDLER // COMSIG_MOB_REMOVED_ACTION + + action.HideFrom(src) + #undef AB_MAX_COLUMNS diff --git a/code/_onclick/hud/ai_hud.dm b/code/_onclick/hud/ai_hud.dm index d99b737cc1f..1f0cf58e237 100644 --- a/code/_onclick/hud/ai_hud.dm +++ b/code/_onclick/hud/ai_hud.dm @@ -1,20 +1,27 @@ /atom/movable/screen/ai icon = 'icons/mob/screen_ai.dmi' +/atom/movable/screen/ai/Click() + if(isobserver(usr) || usr.incapacitated()) + return TRUE + /atom/movable/screen/ai/aicore name = "AI core" icon_state = "ai_core" /atom/movable/screen/ai/aicore/Click() - if(isAI(usr)) - var/mob/living/silicon/ai/AI = usr - AI.view_core() + if(..()) + return + var/mob/living/silicon/ai/AI = usr + AI.view_core() /atom/movable/screen/ai/camera_list name = "Show Camera List" icon_state = "camera" /atom/movable/screen/ai/camera_list/Click() + if(..()) + return var/mob/living/silicon/ai/AI = usr var/camera = tgui_input_list(AI, "Choose which camera you want to view", "Cameras", AI.get_camera_list()) AI.ai_camera_list(camera) @@ -24,53 +31,60 @@ icon_state = "track" /atom/movable/screen/ai/camera_track/Click() - if(isAI(usr)) - var/mob/living/silicon/ai/AI = usr - var/target_name = tgui_input_list(AI, "Choose a target you want to track", "Tracking", AI.trackable_mobs()) - if(target_name) - AI.ai_camera_track(target_name) + if(..()) + return + var/mob/living/silicon/ai/AI = usr + var/target_name = tgui_input_list(AI, "Choose a target you want to track", "Tracking", AI.trackable_mobs()) + if(target_name) + AI.ai_camera_track(target_name) /atom/movable/screen/ai/camera_light name = "Toggle Camera Light" icon_state = "camera_light" /atom/movable/screen/ai/camera_light/Click() - if(isAI(usr)) - var/mob/living/silicon/ai/AI = usr - AI.toggle_camera_light() + if(..()) + return + var/mob/living/silicon/ai/AI = usr + AI.toggle_camera_light() /atom/movable/screen/ai/crew_monitor name = "Crew Monitoring Console" icon_state = "crew_monitor" /atom/movable/screen/ai/crew_monitor/Click() - if(isAI(usr)) - var/mob/living/silicon/ai/AI = usr - AI.subsystem_crew_monitor() + if(..()) + return + var/mob/living/silicon/ai/AI = usr + AI.subsystem_crew_monitor() /atom/movable/screen/ai/crew_manifest name = "Crew Manifest" icon_state = "manifest" /atom/movable/screen/ai/crew_manifest/Click() - if(isAI(usr)) - var/mob/living/silicon/ai/AI = usr - AI.ai_roster() + if(..()) + return + var/mob/living/silicon/ai/AI = usr + AI.ai_roster() /atom/movable/screen/ai/alerts name = "Show Alerts" icon_state = "alerts" /atom/movable/screen/ai/alerts/Click() - if(isAI(usr)) - var/mob/living/silicon/ai/AI = usr - AI.ai_alerts() + if(..()) + return + var/mob/living/silicon/ai/AI = usr + AI.ai_alerts() /atom/movable/screen/ai/announcement name = "Make Announcement" icon_state = "announcement" /atom/movable/screen/ai/announcement/Click() + if(..()) + return var/mob/living/silicon/ai/AI = usr AI.announcement() @@ -79,15 +93,18 @@ icon_state = "call_shuttle" /atom/movable/screen/ai/call_shuttle/Click() - if(isAI(usr)) - var/mob/living/silicon/ai/AI = usr - AI.ai_call_shuttle() + if(..()) + return + var/mob/living/silicon/ai/AI = usr + AI.ai_call_shuttle() /atom/movable/screen/ai/state_laws name = "Law Manager" icon_state = "state_laws" /atom/movable/screen/ai/state_laws/Click() + if(..()) + return if(isAI(usr)) var/mob/living/silicon/ai/AI = usr AI.subsystem_law_manager() @@ -97,42 +114,48 @@ icon_state = "pda_send" /atom/movable/screen/ai/pda_msg_send/Click() - if(isAI(usr)) - var/mob/living/silicon/ai/AI = usr - AI.aiPDA.cmd_send_pdamesg() + if(..()) + return + var/mob/living/silicon/ai/AI = usr + AI.aiPDA.cmd_send_pdamesg() /atom/movable/screen/ai/pda_msg_show name = "PDA - Show Message Log" icon_state = "pda_receive" /atom/movable/screen/ai/pda_msg_show/Click() - if(isAI(usr)) - var/mob/living/silicon/ai/AI = usr - AI.aiPDA.cmd_show_message_log() + if(..()) + return + var/mob/living/silicon/ai/AI = usr + AI.aiPDA.cmd_show_message_log() /atom/movable/screen/ai/image_take name = "Take Image" icon_state = "take_picture" /atom/movable/screen/ai/image_take/Click() - if(isAI(usr)) - var/mob/living/silicon/ai/AI = usr - AI.aiCamera.toggle_camera_mode() + if(..()) + return + var/mob/living/silicon/ai/AI = usr + AI.aiCamera.toggle_camera_mode() /atom/movable/screen/ai/image_view name = "View Images" icon_state = "view_images" /atom/movable/screen/ai/image_view/Click() - if(isAI(usr)) - var/mob/living/silicon/ai/AI = usr - AI.aiCamera.viewpictures() + if(..()) + return + var/mob/living/silicon/ai/AI = usr + AI.aiCamera.viewpictures() /atom/movable/screen/ai/sensors name = "Toggle Sensor Augmentation" icon_state = "ai_sensor" /atom/movable/screen/ai/sensors/Click() + if(..()) + return if(isAI(usr)) var/mob/living/silicon/ai/AI = usr AI.sensor_mode() diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm index 8af3de91da0..80abb2b6f26 100644 --- a/code/_onclick/hud/alert.dm +++ b/code/_onclick/hud/alert.dm @@ -48,6 +48,8 @@ if(override) alert.timeout = null + alert.attach_owner(src) + if(icon_override) alert.icon = icon_override @@ -100,10 +102,50 @@ name = "Alert" desc = "Something seems to have gone wrong with this alert, so report this bug please" mouse_opacity = MOUSE_OPACITY_ICON - var/timeout = 0 //If set to a number, this alert will clear itself after that many deciseconds + /// How long before this alert automatically clears itself (in deciseconds). If zero, remains until cleared. + var/timeout = 0 + /// Some alerts may have different icon states based on severity, this adjusts that. var/severity = 0 + /// Tool-tip for the alert. var/alerttooltipstyle = "" - var/override_alerts = FALSE //If it is overriding other alerts of the same type + /// If true, this should override any other alerts of the same type thrown. + var/override_alerts = FALSE + /// The mob that this alert was originally thrown to. + var/mob/owner + +/atom/movable/screen/alert/proc/attach_owner(mob/new_owner) + owner = new_owner + RegisterSignal(owner, COMSIG_PARENT_QDELETING, PROC_REF(remove_owner)) + +/atom/movable/screen/alert/proc/remove_owner(mob/source, force) + SIGNAL_HANDLER // COMSIG_PARENT_QDELETING + if(owner == source && !isnull(owner)) + UnregisterSignal(owner, COMSIG_PARENT_QDELETING) + owner = null + +/atom/movable/screen/alert/Destroy() + if(owner) + UnregisterSignal(owner, COMSIG_PARENT_QDELETING) + owner = null + severity = 0 + master = null + screen_loc = "" + return ..() + +/atom/movable/screen/alert/Click(location, control, params) + ..() + if(!usr || !usr.client) + return FALSE + if(usr != owner) + to_chat(usr, "Only [owner] can use that!") + return FALSE + var/paramslist = params2list(params) + if(paramslist["shift"]) // screen objects don't do the normal Click() stuff so we'll cheat + to_chat(usr, "[name] - [desc]") + return FALSE + if(master) + usr.client.Click(master, location, control, params) + return TRUE /atom/movable/screen/alert/MouseEntered(location, control, params) . = ..() @@ -310,7 +352,7 @@ or something covering your eyes." icon_state = "embeddedobject" /atom/movable/screen/alert/embeddedobject/Click() - if(isliving(usr)) + if(isliving(usr) && ..()) var/mob/living/carbon/human/M = usr return M.help_shake_act(M) @@ -333,7 +375,7 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." icon_state = "fire" /atom/movable/screen/alert/fire/Click() - if(isliving(usr)) + if(isliving(usr) && ..()) var/mob/living/L = usr return L.resist() @@ -343,7 +385,7 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." icon_state = "direction_lock" /atom/movable/screen/alert/direction_lock/Click() - if(isliving(usr)) + if(isliving(usr) && ..()) var/mob/living/L = usr return L.clear_forced_look() @@ -406,7 +448,7 @@ Recharging stations are available in robotics, the dormitory bathrooms, and the desc = "You have merged with a diona gestalt and are now part of it's biomass. You can still wiggle yourself free though." /atom/movable/screen/alert/nymph/Click() - if(!usr || !usr.client) + if(!..()) return if(isnymph(usr)) var/mob/living/simple_animal/diona/D = usr @@ -417,7 +459,7 @@ Recharging stations are available in robotics, the dormitory bathrooms, and the desc = "You have merged with one or more diona nymphs. Click here to drop it (or one of them)." /atom/movable/screen/alert/gestalt/Click() - if(!usr || !usr.client) + if(!..()) return var/list/nymphs = list() @@ -465,7 +507,7 @@ so as to remain in compliance with the most up-to-date laws." return ..() /atom/movable/screen/alert/hackingapc/Click() - if(!usr || !usr.client) + if(!..()) return if(!target) return @@ -491,7 +533,7 @@ so as to remain in compliance with the most up-to-date laws." return ..() /atom/movable/screen/alert/mech_port_available/Click() - if(!usr || !usr.client) + if(!..()) return if(!ismecha(usr.loc) || !target) return @@ -507,7 +549,7 @@ so as to remain in compliance with the most up-to-date laws." icon_state = "mech_port_x" /atom/movable/screen/alert/mech_port_disconnect/Click() - if(!usr || !usr.client) + if(!..()) return if(!ismecha(usr.loc)) return @@ -566,7 +608,7 @@ so as to remain in compliance with the most up-to-date laws." timeout = 300 /atom/movable/screen/alert/notify_cloning/Click() - if(!usr || !usr.client) + if(!..()) return var/mob/dead/observer/G = usr G.reenter_corpse() @@ -585,6 +627,8 @@ so as to remain in compliance with the most up-to-date laws." overlays += I /atom/movable/screen/alert/ghost/Click() + if(!..()) + return var/mob/living/carbon/human/infected_user = usr if(!istype(infected_user) || infected_user.stat == DEAD) infected_user.clear_alert("ghost_nest") @@ -640,7 +684,7 @@ so as to remain in compliance with the most up-to-date laws." return ..() /atom/movable/screen/alert/notify_action/Click() - if(!usr || !usr.client) + if(!..()) return var/mob/dead/observer/G = usr @@ -721,7 +765,7 @@ so as to remain in compliance with the most up-to-date laws." var/stoner = null /atom/movable/screen/alert/notify_soulstone/Click() - if(!usr || !usr.client) + if(!..()) return if(stone) if(tgui_alert(usr, "Do you want to be captured by [stoner]'s soul stone? This will destroy your corpse and make it \ @@ -738,6 +782,7 @@ so as to remain in compliance with the most up-to-date laws." icon_state = "map_vote" /atom/movable/screen/alert/notify_mapvote/Click() + // ehh sure let observers click on it if they really want, who cares usr.client.vote() //OBJECT-BASED @@ -756,11 +801,14 @@ so as to remain in compliance with the most up-to-date laws." desc = "You're legcuffed, which slows you down considerably. Click the alert to free yourself." /atom/movable/screen/alert/restrained/Click() - if(isliving(usr)) - var/mob/living/L = usr - return L.resist() + if(!isliving(usr) || !..()) + return + var/mob/living/L = usr + return L.resist() /atom/movable/screen/alert/restrained/buckled/Click() + if(!isliving(usr) || !..()) + return var/mob/living/L = usr if(!istype(L) || !L.can_resist()) return @@ -770,20 +818,23 @@ so as to remain in compliance with the most up-to-date laws." // PRIVATE = only edit, use, or override these if you're editing the system as a whole // Re-render all alerts - also called in /datum/hud/show_hud() because it's needed there -/datum/hud/proc/reorganize_alerts() +/datum/hud/proc/reorganize_alerts(mob/viewmob) + var/mob/screenmob = viewmob || mymob + if(!screenmob.client) + return var/list/alerts = mymob.alerts if(!alerts) return FALSE var/icon_pref if(!hud_shown) for(var/i in 1 to length(alerts)) - mymob.client.screen -= alerts[alerts[i]] + screenmob.client.screen -= alerts[alerts[i]] return TRUE for(var/i in 1 to length(alerts)) var/atom/movable/screen/alert/alert = alerts[alerts[i]] if(alert.icon_state == "template") if(!icon_pref) - icon_pref = ui_style2icon(mymob.client.prefs.UI_style) + icon_pref = ui_style2icon(screenmob.client.prefs.UI_style) alert.icon = icon_pref switch(i) if(1) @@ -799,24 +850,12 @@ so as to remain in compliance with the most up-to-date laws." else . = "" alert.screen_loc = . - mymob.client.screen |= alert + screenmob.client.screen |= alert + if(!viewmob) + for(var/viewer in mymob.observers) + reorganize_alerts(viewer) return TRUE -/atom/movable/screen/alert/Click(location, control, params) - if(!usr || !usr.client) - return - var/paramslist = params2list(params) - if(paramslist["shift"]) // screen objects don't do the normal Click() stuff so we'll cheat - to_chat(usr, "[name] - [desc]") - return - if(master) - return usr.client.Click(master, location, control, params) - -/atom/movable/screen/alert/Destroy() - severity = 0 - master = null - screen_loc = "" - return ..() /// Gives the player the option to succumb while in critical condition /atom/movable/screen/alert/succumb @@ -825,7 +864,7 @@ so as to remain in compliance with the most up-to-date laws." icon_state = "succumb" /atom/movable/screen/alert/succumb/Click() - if(!usr || !usr.client) + if(!..()) return var/mob/living/living_owner = usr if(!istype(usr)) diff --git a/code/_onclick/hud/alien_hud.dm b/code/_onclick/hud/alien_hud.dm index eff377796e0..ad4e8d2a05c 100644 --- a/code/_onclick/hud/alien_hud.dm +++ b/code/_onclick/hud/alien_hud.dm @@ -146,19 +146,22 @@ inv_slots[inv.slot_id] = inv inv.update_icon() -/datum/hud/alien/persistent_inventory_update() +/datum/hud/alien/persistent_inventory_update(mob/viewer) if(!mymob) return var/mob/living/carbon/alien/humanoid/H = mymob + var/mob/screenmob = viewer || H if(hud_version != HUD_STYLE_NOHUD) if(H.r_hand) H.r_hand.screen_loc = ui_rhand - H.client.screen += H.r_hand + screenmob.client.screen += H.r_hand if(H.l_hand) H.l_hand.screen_loc = ui_lhand - H.client.screen += H.l_hand + screenmob.client.screen += H.l_hand else if(H.r_hand) H.r_hand.screen_loc = null + screenmob.client.screen -= H.r_hand if(H.l_hand) H.l_hand.screen_loc = null + screenmob.client.screen -= H.l_hand diff --git a/code/_onclick/hud/fullscreen.dm b/code/_onclick/hud/fullscreen.dm index afc30ca3422..b085669097b 100644 --- a/code/_onclick/hud/fullscreen.dm +++ b/code/_onclick/hud/fullscreen.dm @@ -49,16 +49,17 @@ for(var/category in screens) clear_fullscreen(category) -/datum/hud/proc/reload_fullscreen() - if(mymob.client) - var/atom/movable/screen/fullscreen/screen - var/list/screens = mymob.screens - for(var/category in screens) - screen = screens[category] - if(screen.should_show_to(mymob)) - mymob.client.screen |= screen - continue - mymob.client.screen -= screen +/mob/proc/reload_fullscreen() + if(!client) + return + var/atom/movable/screen/fullscreen/screen + for(var/category in screens) + screen = screens[category] + if(screen.should_show_to(src)) + screen.update_for_view(client.view) + client.screen |= screen + else + client.screen -= screen /atom/movable/screen/fullscreen icon = 'icons/mob/screen_full.dmi' diff --git a/code/_onclick/hud/ghost_hud.dm b/code/_onclick/hud/ghost_hud.dm index f7994d9aa45..40c957b26c9 100644 --- a/code/_onclick/hud/ghost_hud.dm +++ b/code/_onclick/hud/ghost_hud.dm @@ -110,7 +110,24 @@ for(var/atom/movable/screen/S in (static_inventory + toggleable_inventory)) S.hud = src -/datum/hud/ghost/show_hud() - mymob.client.screen = list() - mymob.client.screen += static_inventory - ..() +/datum/hud/ghost/show_hud(version = 0, mob/viewmob) + // don't show this HUD if observing; show the HUD of the observee + var/mob/dead/observer/O = mymob + if(istype(O) && O.mob_observed) + plane_masters_update() + return FALSE + + . = ..() + if(!.) + return + var/mob/screenmob = viewmob || mymob + screenmob.client.screen += static_inventory + + +// We should only see observed mob alerts. +/datum/hud/ghost/reorganize_alerts(mob/viewmob) + var/mob/dead/observer/O = mymob + if(istype(O) && O.mob_observed) + return + return ..() + diff --git a/code/_onclick/hud/hud_datum.dm b/code/_onclick/hud/hud_datum.dm index 566e07c7bb9..4f11604ade2 100644 --- a/code/_onclick/hud/hud_datum.dm +++ b/code/_onclick/hud/hud_datum.dm @@ -11,7 +11,7 @@ var/hud_shown = TRUE /// Current displayed version of the HUD var/hud_version = 1 - /// Whether or not their toggleable inventory + /// Whether or not their toggleable inventory (generally their contents on the left) is expanded var/inventory_shown = TRUE /// This is to hide the buttons that can be used via hotkeys. (hotkeybuttons list of buttons) var/hotkey_ui_hidden = FALSE @@ -147,14 +147,22 @@ QDEL_NULL(screentip_text) return ..() -/datum/hud/proc/show_hud(version = 0) +/** + * Shows this hud's hud to some mob + * + * Arguments + * * version - denotes which style should be displayed. blank or 0 means "next version" + * * viewmob - what mob to show the hud to. Can be this hud's mob, can be another mob, can be null (will use this hud's mob if so) + */ +/datum/hud/proc/show_hud(version = 0, mob/viewmob) if(!ismob(mymob)) return FALSE - - if(!mymob.client) + var/mob/screenmob = viewmob || mymob + if(!screenmob.client) return FALSE - mymob.client.screen = list() + screenmob.client.clear_screen() + screenmob.client.apply_clickcatcher() var/display_hud_version = version if(!display_hud_version) //If 0 or blank, display the next hud version @@ -166,15 +174,15 @@ if(HUD_STYLE_STANDARD) //Default HUD hud_shown = TRUE //Governs behavior of other procs if(length(static_inventory)) - mymob.client.screen += static_inventory - if(length(toggleable_inventory) && inventory_shown) - mymob.client.screen += toggleable_inventory - if(length(hotkeybuttons) && !hotkey_ui_hidden) - mymob.client.screen += hotkeybuttons + screenmob.client.screen += static_inventory + if(length(toggleable_inventory) && screenmob.hud_used?.inventory_shown) + screenmob.client.screen += toggleable_inventory + if(length(hotkeybuttons) && !screenmob.hud_used?.hotkey_ui_hidden) + screenmob.client.screen += hotkeybuttons if(length(infodisplay)) - mymob.client.screen += infodisplay + screenmob.client.screen += infodisplay - mymob.client.screen += toggle_palette + screenmob.client.screen += toggle_palette if(action_intent) action_intent.screen_loc = initial(action_intent.screen_loc) //Restore intent selection to the original position @@ -182,33 +190,33 @@ if(HUD_STYLE_REDUCED) //Reduced HUD hud_shown = FALSE //Governs behavior of other procs if(length(static_inventory)) - mymob.client.screen -= static_inventory + screenmob.client.screen -= static_inventory if(length(toggleable_inventory)) - mymob.client.screen -= toggleable_inventory + screenmob.client.screen -= toggleable_inventory if(length(hotkeybuttons)) - mymob.client.screen -= hotkeybuttons + screenmob.client.screen -= hotkeybuttons if(length(infodisplay)) - mymob.client.screen += infodisplay + screenmob.client.screen += infodisplay //These ones are a part of 'static_inventory', 'toggleable_inventory' or 'hotkeybuttons' but we want them to stay if(inv_slots[SLOT_HUD_LEFT_HAND]) - mymob.client.screen += inv_slots[SLOT_HUD_LEFT_HAND] //we want the hands to be visible + screenmob.client.screen += inv_slots[SLOT_HUD_LEFT_HAND] //we want the hands to be visible if(inv_slots[SLOT_HUD_RIGHT_HAND]) - mymob.client.screen += inv_slots[SLOT_HUD_RIGHT_HAND] //we want the hands to be visible + screenmob.client.screen += inv_slots[SLOT_HUD_RIGHT_HAND] //we want the hands to be visible if(action_intent) - mymob.client.screen += action_intent //we want the intent switcher visible + screenmob.client.screen += action_intent //we want the intent switcher visible action_intent.screen_loc = ui_acti_alt //move this to the alternative position, where zone_select usually is. if(HUD_STYLE_NOHUD) //No HUD hud_shown = FALSE //Governs behavior of other procs if(length(static_inventory)) - mymob.client.screen -= static_inventory + screenmob.client.screen -= static_inventory if(length(toggleable_inventory)) - mymob.client.screen -= toggleable_inventory + screenmob.client.screen -= toggleable_inventory if(length(hotkeybuttons)) - mymob.client.screen -= hotkeybuttons + screenmob.client.screen -= hotkeybuttons if(length(infodisplay)) - mymob.client.screen -= infodisplay + screenmob.client.screen -= infodisplay if(HUD_STYLE_ACTIONHUD) //No HUD hud_shown = TRUE //Governs behavior of other procs @@ -220,36 +228,46 @@ mymob.client.screen -= infodisplay hud_version = display_hud_version - persistent_inventory_update() - mymob.update_action_buttons(1) - reorganize_alerts() - reload_fullscreen() - update_parallax_pref(mymob) + persistent_inventory_update(screenmob) + screenmob.update_action_buttons(TRUE) + reorganize_alerts(screenmob) + screenmob.reload_fullscreen() + update_parallax_pref(screenmob) + if(!viewmob) + // working off of mymob + plane_masters_update() + for(var/M in mymob.observers) + show_hud(hud_version, M) + else if(viewmob.hud_used) + viewmob.hud_used.plane_masters_update() + viewmob.show_other_mob_action_buttons(mymob) plane_masters_update() + return TRUE /datum/hud/proc/plane_masters_update() // Plane masters are always shown to OUR mob, never to observers for(var/thing in plane_masters) var/atom/movable/screen/plane_master/PM = plane_masters[thing] PM.backdrop(mymob) - mymob.client.screen += PM + mymob.client?.screen += PM -/datum/hud/human/show_hud(version = 0) +/datum/hud/human/show_hud(version = 0, mob/viewmob) . = ..() if(!.) return - hidden_inventory_update() + var/mob/screenmob = viewmob || mymob + hidden_inventory_update(screenmob) -/datum/hud/robot/show_hud(version = 0) +/datum/hud/robot/show_hud(version = 0, mob/viewmob) . = ..() if(!.) return - update_robot_modules_display() + update_robot_modules_display(viewmob) -/datum/hud/proc/hidden_inventory_update() +/datum/hud/proc/hidden_inventory_update(mob/viewer) return -/datum/hud/proc/persistent_inventory_update() +/datum/hud/proc/persistent_inventory_update(mob/viewer) return /mob/proc/hide_hud() diff --git a/code/_onclick/hud/human_hud.dm b/code/_onclick/hud/human_hud.dm index 68eb065069f..94c08989d00 100644 --- a/code/_onclick/hud/human_hud.dm +++ b/code/_onclick/hud/human_hud.dm @@ -6,14 +6,22 @@ icon_state = "toggle" /atom/movable/screen/human/toggle/Click() - if(usr.hud_used.inventory_shown) + + var/mob/targetmob = usr + + if(isobserver(usr)) + if(ishuman(usr.client.eye) && (usr.client.eye != usr)) + var/mob/M = usr.client.eye + targetmob = M + + if(usr.hud_used.inventory_shown && targetmob.hud_used) usr.hud_used.inventory_shown = FALSE - usr.client.screen -= usr.hud_used.toggleable_inventory + usr.client.screen -= targetmob.hud_used.toggleable_inventory else usr.hud_used.inventory_shown = TRUE - usr.client.screen += usr.hud_used.toggleable_inventory + usr.client.screen += targetmob.hud_used.toggleable_inventory - usr.hud_used.hidden_inventory_update() + targetmob.hud_used.hidden_inventory_update(usr) /atom/movable/screen/human/equip name = "equip" @@ -21,7 +29,7 @@ /atom/movable/screen/human/equip/Click() if(ismecha(usr.loc)) // stops inventory actions in a mech - return 1 + return TRUE var/mob/living/carbon/human/H = usr H.quick_equip() @@ -33,6 +41,8 @@ screen_loc = ui_lingstingdisplay /atom/movable/screen/ling/sting/Click() + if(isobserver(usr)) + return var/datum/antagonist/changeling/cling = usr.mind.has_antag_datum(/datum/antagonist/changeling) cling?.chosen_sting?.unset_sting() @@ -389,100 +399,118 @@ else crafting.invisibility = initial(crafting.invisibility) -/datum/hud/human/hidden_inventory_update() +/datum/hud/human/hidden_inventory_update(mob/viewer) if(!mymob) return var/mob/living/carbon/human/H = mymob - if(inventory_shown && hud_version == HUD_STYLE_STANDARD) + + var/mob/screenmob = viewer || H + if(screenmob.hud_used.inventory_shown && screenmob.hud_used.hud_shown && screenmob.hud_used.hud_version == HUD_STYLE_STANDARD) + if(H.shoes) H.shoes.screen_loc = ui_shoes - H.client.screen += H.shoes + screenmob.client.screen += H.shoes if(H.gloves) H.gloves.screen_loc = ui_gloves - H.client.screen += H.gloves + screenmob.client.screen += H.gloves if(H.l_ear) H.l_ear.screen_loc = ui_l_ear - H.client.screen += H.l_ear + screenmob.client.screen += H.l_ear if(H.r_ear) H.r_ear.screen_loc = ui_r_ear - H.client.screen += H.r_ear + screenmob.client.screen += H.r_ear if(H.glasses) H.glasses.screen_loc = ui_glasses - H.client.screen += H.glasses + screenmob.client.screen += H.glasses if(H.w_uniform) H.w_uniform.screen_loc = ui_iclothing - H.client.screen += H.w_uniform + screenmob.client.screen += H.w_uniform if(H.wear_suit) H.wear_suit.screen_loc = ui_oclothing - H.client.screen += H.wear_suit + screenmob.client.screen += H.wear_suit if(H.wear_mask) H.wear_mask.screen_loc = ui_mask - H.client.screen += H.wear_mask + screenmob.client.screen += H.wear_mask if(H.head) H.head.screen_loc = ui_head - H.client.screen += H.head + screenmob.client.screen += H.head else - if(H.shoes) H.shoes.screen_loc = null - if(H.gloves) H.gloves.screen_loc = null - if(H.l_ear) H.l_ear.screen_loc = null - if(H.r_ear) H.r_ear.screen_loc = null - if(H.glasses) H.glasses.screen_loc = null - if(H.w_uniform) H.w_uniform.screen_loc = null - if(H.wear_suit) H.wear_suit.screen_loc = null - if(H.wear_mask) H.wear_mask.screen_loc = null - if(H.head) H.head.screen_loc = null + if(H.shoes) + screenmob.client.screen -= H.shoes + if(H.gloves) + screenmob.client.screen -= H.gloves + if(H.l_ear) + screenmob.client.screen -= H.l_ear + if(H.r_ear) + screenmob.client.screen -= H.r_ear + if(H.glasses) + screenmob.client.screen -= H.glasses + if(H.w_uniform) + screenmob.client.screen -= H.w_uniform + if(H.wear_suit) + screenmob.client.screen -= H.wear_suit + if(H.wear_mask) + screenmob.client.screen -= H.wear_mask + if(H.head) + screenmob.client.screen -= H.head -/datum/hud/human/persistent_inventory_update() +/datum/hud/human/persistent_inventory_update(mob/viewer) if(!mymob) return + ..() var/mob/living/carbon/human/H = mymob - if(hud_version == HUD_STYLE_STANDARD) - if(H.s_store) - H.s_store.screen_loc = ui_sstore1 - H.client.screen += H.s_store - if(H.wear_id) - H.wear_id.screen_loc = ui_id - H.client.screen += H.wear_id - if(H.wear_pda) - H.wear_pda.screen_loc = ui_pda - H.client.screen += H.wear_pda - if(H.belt) - H.belt.screen_loc = ui_belt - H.client.screen += H.belt - if(H.back) - H.back.screen_loc = ui_back - H.client.screen += H.back - if(H.l_store) - H.l_store.screen_loc = ui_storage1 - H.client.screen += H.l_store - if(H.r_store) - H.r_store.screen_loc = ui_storage2 - H.client.screen += H.r_store - else - if(H.s_store) - H.s_store.screen_loc = null - if(H.wear_id) - H.wear_id.screen_loc = null - if(H.wear_pda) - H.wear_pda.screen_loc = null - if(H.belt) - H.belt.screen_loc = null - if(H.back) - H.back.screen_loc = null - if(H.l_store) - H.l_store.screen_loc = null - if(H.r_store) - H.r_store.screen_loc = null + var/mob/screenmob = viewer || H + + if(screenmob.hud_used) + if(screenmob.hud_used.hud_shown && screenmob.hud_used.hud_version == HUD_STYLE_STANDARD) + if(H.s_store) + H.s_store.screen_loc = ui_sstore1 + screenmob.client.screen += H.s_store + if(H.wear_id) + H.wear_id.screen_loc = ui_id + screenmob.client.screen += H.wear_id + if(H.wear_pda) + H.wear_pda.screen_loc = ui_pda + screenmob.client.screen += H.wear_pda + if(H.belt) + H.belt.screen_loc = ui_belt + screenmob.client.screen += H.belt + if(H.back) + H.back.screen_loc = ui_back + screenmob.client.screen += H.back + if(H.l_store) + H.l_store.screen_loc = ui_storage1 + screenmob.client.screen += H.l_store + if(H.r_store) + H.r_store.screen_loc = ui_storage2 + screenmob.client.screen += H.r_store + else + if(H.s_store) + screenmob.client.screen -= H.s_store + if(H.wear_id) + screenmob.client.screen -= H.wear_id + if(H.wear_pda) + screenmob.client.screen -= H.wear_pda + if(H.belt) + screenmob.client.screen -= H.belt + if(H.back) + screenmob.client.screen -= H.back + if(H.l_store) + screenmob.client.screen -= H.l_store + if(H.r_store) + screenmob.client.screen -= H.r_store if(hud_version != HUD_STYLE_NOHUD) if(H.r_hand) H.r_hand.screen_loc = ui_rhand - H.client.screen += H.r_hand + screenmob.client.screen += H.r_hand if(H.l_hand) H.l_hand.screen_loc = ui_lhand - H.client.screen += H.l_hand + screenmob.client.screen += H.l_hand else if(H.r_hand) - H.r_hand.screen_loc = null + screenmob.r_hand.screen_loc = null + screenmob.client.screen -= H.r_hand if(H.l_hand) - H.l_hand.screen_loc = null + screenmob.l_hand.screen_loc = null + screenmob.client.screen -= H.l_hand diff --git a/code/_onclick/hud/parallax.dm b/code/_onclick/hud/parallax.dm index 2dca02b6e6d..aec2360eded 100644 --- a/code/_onclick/hud/parallax.dm +++ b/code/_onclick/hud/parallax.dm @@ -1,6 +1,9 @@ -/datum/hud/proc/create_parallax() - var/client/C = mymob.client - if(!apply_parallax_pref()) +/datum/hud/proc/create_parallax(mob/viewmob) + var/mob/screenmob = viewmob || mymob + if(!screenmob.client) + return + var/client/C = screenmob.client + if(!apply_parallax_pref(screenmob)) return // this is needed so it blends properly with the space plane and blackness plane. var/atom/movable/screen/plane_master/space/S = plane_masters["[PLANE_SPACE]"] @@ -34,16 +37,20 @@ C.screen |= (C.parallax_layers + C.parallax_static_layers_tail) -/datum/hud/proc/remove_parallax() - var/client/C = mymob.client +/datum/hud/proc/remove_parallax(mob/viewmob) + var/mob/screenmob = viewmob || mymob + var/client/C = screenmob.client C.screen -= (C.parallax_layers_cached + C.parallax_static_layers_tail) C.parallax_layers = null var/atom/movable/screen/plane_master/space/S = plane_masters["[PLANE_SPACE]"] S.color = null S.appearance_flags &= ~NO_CLIENT_COLOR -/datum/hud/proc/apply_parallax_pref() - var/client/C = mymob.client +/datum/hud/proc/apply_parallax_pref(mob/viewmob) + var/mob/screen_mob = viewmob || mymob + var/client/C = screen_mob.client + if(!istype(C)) + return FALSE if(C.prefs) var/pref = C.prefs.parallax if(isnull(pref)) @@ -72,16 +79,22 @@ C.parallax_layers_max = 4 return TRUE -/datum/hud/proc/update_parallax_pref() - remove_parallax() - create_parallax() - update_parallax() +/datum/hud/proc/update_parallax_pref(mob/viewmob) + var/mob/screen_mob = viewmob || mymob + if(!screen_mob.client) + return + remove_parallax(screen_mob) + create_parallax(screen_mob) + update_parallax(screen_mob) // This sets which way the current shuttle is moving (returns true if the shuttle has stopped moving so the caller can append their animation) // Well, it would if our shuttle code had dynamic areas -/datum/hud/proc/set_parallax_movedir(new_parallax_movedir, skip_windups) +/datum/hud/proc/set_parallax_movedir(mob/viewmob, new_parallax_movedir, skip_windups) . = FALSE - var/client/C = mymob.client + var/mob/screen_mob = viewmob || mymob + var/client/C = screen_mob.client + if(!istype(C)) + return if(new_parallax_movedir == C.parallax_movedir) return var/animatedir = new_parallax_movedir @@ -157,8 +170,9 @@ animate(L, transform = L.transform, time = 0, loop = -1, flags = ANIMATION_END_NOW) animate(transform = matrix(), time = T) -/datum/hud/proc/update_parallax() - var/client/C = mymob.client +/datum/hud/proc/update_parallax(mob/viewmob) + var/mob/screenmob = viewmob || mymob + var/client/C = screenmob.client var/turf/posobj = get_turf(C.eye) if(!posobj) return @@ -167,9 +181,9 @@ // Update the movement direction of the parallax if necessary (for shuttles) var/area/shuttle/SA = areaobj if(!SA || !SA.moving) - set_parallax_movedir(0) + set_parallax_movedir(screenmob, 0) else - set_parallax_movedir(SA.parallax_move_direction) + set_parallax_movedir(screenmob, SA.parallax_move_direction) var/force if(!C.previous_turf || (C.previous_turf.z != posobj.z)) @@ -193,7 +207,7 @@ for(var/thing in C.parallax_layers) var/atom/movable/screen/parallax_layer/L = thing - L.update_status(mymob) + L.update_status(screenmob) if(L.view_sized != C.view) L.update_o(C.view) diff --git a/code/_onclick/hud/robot_hud.dm b/code/_onclick/hud/robot_hud.dm index 30fae75f816..60310611b71 100644 --- a/code/_onclick/hud/robot_hud.dm +++ b/code/_onclick/hud/robot_hud.dm @@ -1,23 +1,34 @@ /atom/movable/screen/robot icon = 'icons/mob/screen_robot.dmi' +/atom/movable/screen/robot/Click() + . = ..() + if(isobserver(usr)) + return TRUE + /atom/movable/screen/robot/module name = "cyborg module" icon_state = "nomod" /atom/movable/screen/robot/module/Click() - if(isrobot(usr)) + if(isrobot(usr) && !..()) var/mob/living/silicon/robot/R = usr - if(R.module) - R.hud_used.toggle_show_robot_modules() - return 1 - R.pick_module() + if(!R.module) + R.pick_module() + return + + // we can let ghosts mess with this one + usr.hud_used.toggle_show_robot_modules(usr) + return TRUE + /atom/movable/screen/robot/module1 name = "module1" icon_state = "inv1" /atom/movable/screen/robot/module1/Click() + if(..()) + return if(isrobot(usr)) var/mob/living/silicon/robot/R = usr R.toggle_module(1) @@ -27,6 +38,8 @@ icon_state = "inv2" /atom/movable/screen/robot/module2/Click() + if(..()) + return if(isrobot(usr)) var/mob/living/silicon/robot/R = usr R.toggle_module(2) @@ -36,6 +49,8 @@ icon_state = "inv3" /atom/movable/screen/robot/module3/Click() + if(..()) + return if(isrobot(usr)) var/mob/living/silicon/robot/R = usr R.toggle_module(3) @@ -46,6 +61,8 @@ icon_state = "radio" /atom/movable/screen/robot/radio/Click() + if(..()) + return if(issilicon(usr)) var/mob/living/silicon/robot/R = usr R.radio_menu() @@ -55,6 +72,8 @@ icon_state = "store" /atom/movable/screen/robot/store/Click() + if(..()) + return if(isrobot(usr)) var/mob/living/silicon/robot/R = usr R.uneq_active() @@ -66,6 +85,8 @@ screen_loc = ui_borg_lamp /atom/movable/screen/robot/lamp/Click() + if(..()) + return if(isrobot(usr)) var/mob/living/silicon/robot/R = usr R.control_headlamp() @@ -75,6 +96,8 @@ icon_state = "ionpulse0" /atom/movable/screen/robot/thrusters/Click() + if(..()) + return var/mob/living/silicon/robot/R = usr R.toggle_ionpulse() @@ -83,11 +106,23 @@ icon_state = "running" /atom/movable/screen/robot/mov_intent/Click() + if(..()) + return usr.toggle_move_intent() +/datum/hud/robot + var/shown_robot_modules = FALSE // Used to determine whether they have the module menu shown or not + var/atom/movable/screen/robot_modules_background + /datum/hud/robot/New(mob/user) ..() + + robot_modules_background = new() + robot_modules_background.icon_state = "block" + robot_modules_background.layer = HUD_LAYER // Objects that appear on screen are on layer 20, UI should be just below it. + robot_modules_background.plane = HUD_PLANE + var/atom/movable/screen/using var/mob/living/silicon/robot/mymobR = mymob @@ -184,41 +219,51 @@ return ..() -/datum/hud/proc/toggle_show_robot_modules() +/datum/hud/proc/toggle_show_robot_modules(mob/viewmob) + return + +/datum/hud/robot/toggle_show_robot_modules(mob/viewmob) + var/mob/screenmob = viewmob || mymob + if(istype(viewmob.hud_used, /datum/hud/robot)) + var/datum/hud/robot/robohud = screenmob.hud_used + robohud.shown_robot_modules = !robohud.shown_robot_modules + + update_robot_modules_display(viewmob) + +/datum/hud/proc/update_robot_modules_display(mob/viewer) + return + +/datum/hud/robot/update_robot_modules_display(mob/viewer) if(!isrobot(mymob)) return var/mob/living/silicon/robot/R = mymob - R.shown_robot_modules = !R.shown_robot_modules - update_robot_modules_display() - -/datum/hud/proc/update_robot_modules_display() - if(!isrobot(mymob)) - return - - var/mob/living/silicon/robot/R = mymob + var/mob/screenmob = viewer || R if(!R.client) return if(!R.module) + shown_robot_modules = FALSE + screenmob.client.screen -= robot_modules_background + screenmob.client?.screen -= screenmob.hud_used.module_store_icon return - if(R.shown_robot_modules && hud_version == HUD_STYLE_STANDARD) + if(shown_robot_modules && hud_shown && hud_version == HUD_STYLE_STANDARD) //Modules display is shown - R.client.screen += module_store_icon //"store" icon + screenmob.client.screen += module_store_icon //"store" icon if(!R.module.modules) to_chat(usr, "Selected module has no modules to select.") return - if(!R.robot_modules_background) + if(!robot_modules_background) return var/display_rows = CEILING(length(R.module.modules) / 8, 1) - R.robot_modules_background.screen_loc = "CENTER-4:16,SOUTH+1:7 to CENTER+3:16,SOUTH+[display_rows]:7" - R.client.screen += R.robot_modules_background + robot_modules_background.screen_loc = "CENTER-4:16,SOUTH+1:7 to CENTER+3:16,SOUTH+[display_rows]:7" + screenmob.client.screen += robot_modules_background var/x = -4 //Start at CENTER-4,SOUTH+1 var/y = 1 @@ -226,7 +271,7 @@ for(var/atom/movable/A in R.module.modules) if((A != R.module_state_1) && (A != R.module_state_2) && (A != R.module_state_3)) //Module is not currently active - R.client.screen += A + screenmob.client.screen += A if(x < 0) A.screen_loc = "CENTER[x]:16,SOUTH+[y]:7" else @@ -241,11 +286,39 @@ else //Modules display is hidden - R.client.screen -= module_store_icon + screenmob.client.screen -= module_store_icon for(var/atom/A in R.module.modules) if((A != R.module_state_1) && (A != R.module_state_2) && (A != R.module_state_3)) //Module is not currently active - R.client.screen -= A - R.shown_robot_modules = FALSE - R.client.screen -= R.robot_modules_background + screenmob.client.screen -= A + shown_robot_modules = FALSE + screenmob.client.screen -= robot_modules_background + +/datum/hud/robot/persistent_inventory_update(mob/viewer) + if(!mymob) + return + var/mob/living/silicon/robot/R = mymob + + var/mob/screenmob = viewer || R + + var/held_items = list(R.module_state_1, R.module_state_2, R.module_state_3) + if(!screenmob.hud_used) + return + if(screenmob.hud_used.hud_shown) + for(var/i in 1 to length(held_items)) + var/obj/item/I = held_items[i] + if(I) + switch(i) + if(1) + I.screen_loc = ui_inv1 + if(2) + I.screen_loc = ui_inv2 + if(3) + I.screen_loc = ui_inv3 + else + return + screenmob.client.screen += I + else + for(var/obj/item/I in held_items) + screenmob.client.screen -= I diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index b4897cd5df7..9216a62bbf3 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -367,6 +367,8 @@ screen_loc = ui_crafting /atom/movable/screen/craft/Click() + if(!isliving(usr)) + return var/mob/living/M = usr M.OpenCraftingMenu() @@ -396,24 +398,26 @@ object_overlays.Cut() /atom/movable/screen/inventory/proc/add_overlays() - var/mob/user = hud.mymob + var/mob/user = hud?.mymob - if(hud && user && slot_id) - var/obj/item/holding = user.get_active_hand() + if(!user || !slot_id || user != usr) + return - if(!holding || user.get_item_by_slot(slot_id)) - return + var/obj/item/holding = user.get_active_hand() - var/image/item_overlay = image(holding) - item_overlay.alpha = 92 + if(!holding || user.get_item_by_slot(slot_id)) + return - if(!user.can_equip(holding, slot_id, disable_warning = TRUE)) - item_overlay.color = "#ff0000" - else - item_overlay.color = "#00ff00" + var/image/item_overlay = image(holding) + item_overlay.alpha = 92 - object_overlays += item_overlay - add_overlay(object_overlays) + if(!user.can_equip(holding, slot_id, TRUE)) + item_overlay.color = "#ff0000" + else + item_overlay.color = "#00ff00" + + object_overlays += item_overlay + add_overlay(object_overlays) /atom/movable/screen/inventory/MouseDrop(atom/over) cut_overlay(object_overlays) diff --git a/code/datums/action.dm b/code/datums/action.dm index 0a099de316c..33c5681c9b6 100644 --- a/code/datums/action.dm +++ b/code/datums/action.dm @@ -16,6 +16,8 @@ var/default_button_position = SCRN_OBJ_IN_LIST /// Map of huds viewing a button with our action -> their button var/list/viewers = list() + /// Whether or not this will be shown to observers + var/show_to_observers = TRUE /datum/action/New(Target) @@ -49,6 +51,8 @@ Remove(owner) owner = M RegisterSignal(owner, COMSIG_PARENT_QDELETING, PROC_REF(clear_ref), override = TRUE) + SEND_SIGNAL(src, COMSIG_ACTION_GRANTED, owner) + SEND_SIGNAL(owner, COMSIG_MOB_GRANTED_ACTION, src) GiveAction(M) /datum/action/proc/Remove(mob/remove_from) @@ -64,6 +68,9 @@ if(isnull(owner)) return + SEND_SIGNAL(src, COMSIG_ACTION_REMOVED, owner) + SEND_SIGNAL(owner, COMSIG_MOB_REMOVED_ACTION, src) + if(target == owner) RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(clear_ref), override = TRUE) if(owner == remove_from) diff --git a/code/datums/components/orbiter.dm b/code/datums/components/orbiter.dm index 50ff05d4af1..d7a5a3a8ade 100644 --- a/code/datums/components/orbiter.dm +++ b/code/datums/components/orbiter.dm @@ -163,6 +163,7 @@ orbiter.animate_movement = SLIDE_STEPS if(!QDELETED(parent)) SEND_SIGNAL(parent, COMSIG_ATOM_ORBIT_STOP, orbiter) + SEND_SIGNAL(orbiter, COMSIG_ATOM_ORBITER_STOP, parent) orbiter.transform = get_cached_transform(orbiter) orbiter.layer = get_orbiter_layer(orbiter) diff --git a/code/game/objects/items/weapons/storage/storage_base.dm b/code/game/objects/items/weapons/storage/storage_base.dm index cfb1b6015dc..ea3e086b3d6 100644 --- a/code/game/objects/items/weapons/storage/storage_base.dm +++ b/code/game/objects/items/weapons/storage/storage_base.dm @@ -199,12 +199,9 @@ orient2hud(user) // this only needs to happen to make .contents show properly as screen objects. if(user.s_active) user.s_active.hide_from(user) // If there's already an interface open, close it. - user.client.screen -= boxes - user.client.screen -= closer - user.client.screen -= contents - user.client.screen += boxes - user.client.screen += closer - user.client.screen += contents + user.client.screen |= boxes + user.client.screen |= closer + user.client.screen |= contents user.s_active = src LAZYDISTINCTADD(mobs_viewing, user) @@ -245,6 +242,9 @@ update_viewers() /obj/item/storage/proc/open(mob/user) + if(isobserver(user)) + show_to(user) + return if(use_sound && isliving(user)) playsound(loc, use_sound, 50, TRUE, -5) @@ -459,6 +459,10 @@ if(user) if(user.client && user.s_active != src) user.client.screen -= I + if(length(user.observers)) + for(var/mob/observer in user.observers) + if(observer.client && observer.s_active != src) + observer.client.screen -= I I.dropped(user, TRUE) add_fingerprint(user) diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index baf89390823..f6dbf439894 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -12,6 +12,8 @@ GLOBAL_LIST_INIT(admin_verbs_admin, list( /client/proc/invisimin, /*allows our mob to go invisible/visible*/ /datum/admins/proc/announce, /*priority announce something to all clients.*/ /client/proc/admin_ghost, /*allows us to ghost/reenter body at will*/ + /client/proc/admin_observe, /*allows us to freely observe mobs */ + /client/proc/admin_observe_target, /*and gives it to us on right click*/ /client/proc/toggle_view_range, /*changes how far we can see*/ /client/proc/cmd_admin_pm_context, /*right-click adminPM interface*/ /client/proc/cmd_admin_pm_panel, /*admin-pm list*/ @@ -196,13 +198,17 @@ GLOBAL_LIST_INIT(admin_verbs_mod, list( /client/proc/dsay, /datum/admins/proc/show_player_panel, /client/proc/ban_panel, - /client/proc/debug_variables /*allows us to -see- the variables of any instance in the game. +VAREDIT needed to modify*/ + /client/proc/debug_variables, /*allows us to -see- the variables of any instance in the game. +VAREDIT needed to modify*/ + /client/proc/admin_observe, + /client/proc/admin_observe_target, )) GLOBAL_LIST_INIT(admin_verbs_mentor, list( /client/proc/cmd_admin_pm_context, /*right-click adminPM interface*/ /client/proc/cmd_admin_pm_panel, /*admin-pm list*/ /client/proc/cmd_admin_pm_by_key_panel, /*admin-pm list by key*/ /client/proc/openMentorTicketUI, + /client/proc/admin_observe, /* Allow mentors to observe as well, though they face some limitations */ + /client/proc/admin_observe_target, /client/proc/cmd_mentor_say /* mentor say*/ // cmd_mentor_say is added/removed by the toggle_mentor_chat verb )) @@ -339,13 +345,22 @@ GLOBAL_LIST_INIT(view_runtimes_verbs, list( to_chat(src, "All of your adminverbs are now visible.") SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Admin Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/client/proc/admin_ghost() - set category = "Admin" - set name = "Aghost" +/client/proc/mentor_ghost() + var/is_mentor = check_rights(R_MENTOR, FALSE) + var/is_full_admin = check_rights(R_ADMIN|R_MOD, FALSE) - if(!check_rights(R_ADMIN|R_MOD)) + if(!is_mentor && !is_full_admin) + to_chat(src, "You aren't allowed to use this!") return + // mentors are allowed only if they have the observe trait, which is given on observe. + // they should also not be given this proc. + if(!is_full_admin && (is_mentor && !HAS_MIND_TRAIT(mob, TRAIT_MENTOR_OBSERVING) || !is_mentor)) + return + + do_aghost() + +/client/proc/do_aghost() if(isobserver(mob)) //re-enter var/mob/dead/observer/ghost = mob @@ -372,6 +387,134 @@ GLOBAL_LIST_INIT(view_runtimes_verbs, list( // TODO: SStgui.on_transfer() to move windows from old and new SSblackbox.record_feedback("tally", "admin_verb", 1, "Aghost") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/client/proc/admin_ghost() + set category = "Admin" + set name = "Aghost" + + if(!check_rights(R_ADMIN|R_MOD)) + return + + do_aghost() + +/// Allow an admin to observe someone. +/// mentors are allowed to use this verb while living, but with some stipulations: +/// if they attempt to do anything that would stop their orbit, they will immediately be returned to their body. +/client/proc/admin_observe() + set name = "Aobserve" + set category = "Admin" + if(!check_rights(R_ADMIN|R_MOD|R_MENTOR)) + return + + if(isnewplayer(mob)) + to_chat(src, "You cannot aobserve while in the lobby. Please join or observe first.") + return + + var/mob/target + + target = tgui_input_list(mob, "Select a mob to observe", "Aobserve", GLOB.player_list) + if(isnull(target)) + return + if(target == src) + to_chat(src, "You can't observe yourself!") + return + + if(isobserver(target)) + to_chat(src, "[target] is a ghost, and cannot be observed.") + return + + if(isnewplayer(target)) + to_chat(src, "[target] is in the lobby, and cannot be observed.") + return + + admin_observe_target(target) + +/client/proc/cleanup_admin_observe(mob/dead/observer/ghost) + if(!istype(ghost) || !ghost.mob_observed) + return FALSE + + // un-follow them + ghost.cleanup_observe() + // if it's a mentor, make sure they go back to their body. + if(HAS_TRAIT(mob.mind, TRAIT_MENTOR_OBSERVING)) + // handler will handle removing the trait + mob.stop_orbit() + log_admin("[key_name(src)] has de-activated Aobserve") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Aobserve") + return TRUE + +/// targeted form of admin_observe: this should only appear in the right-click menu. +/client/proc/admin_observe_target(mob/target as mob in GLOB.mob_list) + set name = "\[Admin\] Aobserve" + set category = null + + if(!check_rights(R_ADMIN|R_MOD|R_MENTOR, mob)) + return + + var/full_admin = check_rights(R_ADMIN|R_MOD, FALSE, mob) + + if(isnewplayer(mob)) + to_chat(src, "You cannot aobserve while in the lobby. Please join or observe first.") + return + + if(isnewplayer(target)) + to_chat(src, "[target] is currently in the lobby.") + return + + if(isobserver(target)) + to_chat(src, "You can't observe a ghost.") + return + + if(cleanup_admin_observe(mob)) + return + + if(isnull(target) || target == src) + // let the default one find the target if there isn't one + admin_observe() + return + + // observers don't need to ghost, so we don't need to worry about adding any traits + if(isobserver(mob)) + var/mob/dead/observer/ghost = mob + SSblackbox.record_feedback("tally", "admin_verb", 1, "Aobserve") + ghost.do_observe(target) + return + + log_admin("[key_name(src)] has Aobserved out of their body to follow [target]") + do_aghost() + var/mob/dead/observer/ghost = mob + + if(!full_admin) + // if they're a me and they're alive, add the MENTOR_OBSERVINGtrait to ensure that they can only go back to their body. + // we need to handle this here because when you aghost, your mob gets set to the ghost. Oops! + ADD_TRAIT(mob.mind, TRAIT_MENTOR_OBSERVING, MENTOR_OBSERVING) + RegisterSignal(ghost, COMSIG_ATOM_ORBITER_STOP, PROC_REF(on_mentor_observe_end), override = TRUE) + to_chat(src, "You have temporarily observed [target], either move or observe again to un-observe.") + log_admin("[key_name(src)] has mobserved out of their body to follow [target].") + else + log_admin("[key_name(src)] is aobserving [target].") + + + ghost.do_observe(target) + +/client/proc/on_mentor_observe_end(atom/movable/us, atom/movable/orbited) + SIGNAL_HANDLER // COMSIG_ATOM_ORBITER_STOP + if(!isobserver(mob)) + log_and_message_admins("A mentor somehow managed to end observing while not being a ghost. Please investigate and notify coders.") + return + var/mob/dead/observer/ghost = mob + + // just to be safe + ghost.cleanup_observe() + + REMOVE_TRAIT(mob.mind, TRAIT_MENTOR_OBSERVING, MENTOR_OBSERVING) + UnregisterSignal(mob, COMSIG_ATOM_ORBITER_STOP) + + if(!ghost.reenter_corpse()) + // tell everyone since this is kinda nasty. + log_debug("Mentor [key_name_mentor(src)] was unable to re-enter their body after mentor observing.") + log_and_message_admins("[key_name_mentor(src)] was unable to re-enter their body after mentor observing.") + to_chat(src, "Unable to return you to your body after mentor ghosting. If your body still exists, please contact a coder, and you should probably ahelp.") + /client/proc/invisimin() set name = "Invisimin" set category = "Admin" diff --git a/code/modules/admin/player_panel.dm b/code/modules/admin/player_panel.dm index ca98106ea1b..3b33c728713 100644 --- a/code/modules/admin/player_panel.dm +++ b/code/modules/admin/player_panel.dm @@ -86,7 +86,8 @@ body += "PM - " body += "SM - " body += "FLW - " - body += "ALERT" + body += "ALERT - " + body += "OBS" if(eyeUID) body += "|EYE" body += "
" diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index cdbce0a63d8..ba2d9b6432e 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1613,6 +1613,17 @@ show_player_panel(H) //H.regenerate_icons() + else if(href_list["adminobserve"]) + if(!check_rights(R_ADMIN|R_MOD|R_MENTOR)) + return + var/client/C = usr.client + var/mob/M = locateUID(href_list["adminobserve"]) + + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob") + return + C.admin_observe_target(M) + else if(href_list["adminplayeropts"]) var/mob/M = locateUID(href_list["adminplayeropts"]) @@ -3586,3 +3597,5 @@ var/mob/dead/observer/O = target if(O.mind && O.mind.current) . += "|[ADMIN_FLW(O.mind.current,"BDY")]" + else if(ismob(target)) + . += "|[ADMIN_OBS(target, "OBS")]" diff --git a/code/modules/admin/verbs/adminpm.dm b/code/modules/admin/verbs/adminpm.dm index f094b4d9644..f45f123f57d 100644 --- a/code/modules/admin/verbs/adminpm.dm +++ b/code/modules/admin/verbs/adminpm.dm @@ -178,6 +178,7 @@ var/ping_link = check_rights(R_ADMIN, 0, mob) ? "(PING)" : "" var/ticket_link var/alert_link = check_rights(R_ADMIN, FALSE, mob) ? "(ALERT)" : "" + var/observe_link = check_rights(R_MENTOR, FALSE, mob) ? "([ADMIN_OBS(C, "OBS")])" : "" if(ticket_id != -1) if(message_type == MESSAGE_TYPE_MENTORPM) ticket_link = "(TICKET)" @@ -190,7 +191,7 @@ receive_window_link = ticket_link else if(message_type == MESSAGE_TYPE_ADMINPM && check_rights(R_ADMIN, 0, C.mob)) receive_window_link = ticket_link - receive_message = "[type] from-[receive_pm_type] [C.holder ? key_name(src, TRUE, type, ticket_id = ticket_id) : key_name_hidden(src, TRUE, type, ticket_id = ticket_id)]:

[emoji_msg][C.holder ? "
[ping_link] [receive_window_link] [alert_link]" : ""]
" + receive_message = "[type] from-[receive_pm_type] [C.holder ? key_name(src, TRUE, type, ticket_id = ticket_id) : key_name_hidden(src, TRUE, type, ticket_id = ticket_id)]:

[emoji_msg][C.holder ? "
[ping_link] [receive_window_link] [alert_link] [observe_link]" : ""]
" if(message_type == MESSAGE_TYPE_MENTORPM) receive_message = chat_box_mhelp(receive_message) else @@ -202,7 +203,7 @@ send_window_link = ticket_link else if(message_type == MESSAGE_TYPE_ADMINPM && check_rights(R_ADMIN, 0, mob)) send_window_link = ticket_link - var/send_message = "[send_pm_type][type] to-[holder ? key_name(C, TRUE, type, ticket_id = ticket_id) : key_name_hidden(C, TRUE, type, ticket_id = ticket_id)]:

[emoji_msg]

[ping_link] [send_window_link] [alert_link]" + var/send_message = "[send_pm_type][type] to-[holder ? key_name(C, TRUE, type, ticket_id = ticket_id) : key_name_hidden(C, TRUE, type, ticket_id = ticket_id)]:

[emoji_msg]

[ping_link] [send_window_link] [alert_link] [observe_link]" if(message_type == MESSAGE_TYPE_MENTORPM) send_message = chat_box_mhelp(send_message) else @@ -211,9 +212,9 @@ var/third_party_message if(message_type == MESSAGE_TYPE_MENTORPM) - third_party_message = chat_box_mhelp("[type]: [key_name(src, TRUE, type, ticket_id = ticket_id)]->[key_name(C, TRUE, type, ticket_id = ticket_id)]:

[emoji_msg]
[ping_link] [ticket_link] [alert_link]
") + third_party_message = chat_box_mhelp("[type]: [key_name(src, TRUE, type, ticket_id = ticket_id)]->[key_name(C, TRUE, type, ticket_id = ticket_id)]:

[emoji_msg]
[ping_link] [ticket_link] [alert_link] [observe_link]
") else - third_party_message = chat_box_ahelp("[type]: [key_name(src, TRUE, type, ticket_id = ticket_id)]->[key_name(C, TRUE, type, ticket_id = ticket_id)]:

[emoji_msg]
[ping_link] [ticket_link] [alert_link]
") + third_party_message = chat_box_ahelp("[type]: [key_name(src, TRUE, type, ticket_id = ticket_id)]->[key_name(C, TRUE, type, ticket_id = ticket_id)]:

[emoji_msg]
[ping_link] [ticket_link] [alert_link] [observe_link]
") //play the recieving admin the adminhelp sound (if they have them enabled) //non-admins always hear the sound, as they cannot toggle it diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 64b3c5045d5..73c0d726df4 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -1172,6 +1172,14 @@ else src << link(GLOB.configuration.system.region_map[choice]) +/client/proc/set_eye(new_eye) + if(new_eye == eye) + return + eye = new_eye + +/client/proc/clear_screen() + for(var/object in screen) + screen -= object /client/verb/reload_graphics() set category = "Special Verbs" diff --git a/code/modules/mob/dead/observer/observer_base.dm b/code/modules/mob/dead/observer/observer_base.dm index bc0624b2b86..9d1d9e9bb7a 100644 --- a/code/modules/mob/dead/observer/observer_base.dm +++ b/code/modules/mob/dead/observer/observer_base.dm @@ -20,6 +20,8 @@ GLOBAL_DATUM_INIT(ghost_crew_monitor, /datum/ui_module/crew_monitor/ghost, new) move_resist = INFINITY // don't get pushed around invisibility = INVISIBILITY_OBSERVER blocks_emissive = FALSE // Ghosts are transparent, duh + hud_type = /datum/hud/ghost + speaks_ooc = TRUE var/can_reenter_corpse var/bootime = FALSE var/started_as_observer //This variable is set to 1 when you enter the game as an observer. @@ -41,8 +43,8 @@ GLOBAL_DATUM_INIT(ghost_crew_monitor, /datum/ui_module/crew_monitor/ghost, new) var/datum/orbit_menu/orbit_menu /// The "color" their runechat would have had var/alive_runechat_color = "#FFFFFF" - hud_type = /datum/hud/ghost - speaks_ooc = TRUE + /// UID of the mob which we are currently observing + var/mob_observed /mob/dead/observer/New(mob/body=null, flags=1) set_invisibility(GLOB.observer_default_invisibility) @@ -120,6 +122,8 @@ GLOBAL_DATUM_INIT(ghost_crew_monitor, /datum/ui_module/crew_monitor/ghost, new) if(seerads) STOP_PROCESSING(SSobj, src) remove_observer_verbs() + if(mob_observed) + cleanup_observe() return ..() /mob/dead/observer/examine(mob/user) @@ -184,25 +188,37 @@ Works together with spawning an observer, noted above. return 1 /mob/proc/ghostize(flags = GHOST_CAN_REENTER, user_color, ghost_name) - if(key) - if(player_logged) //if they have disconnected we want to remove their SSD overlay - overlays -= image('icons/effects/effects.dmi', icon_state = "zzz_glow") - if(GLOB.non_respawnable_keys[ckey]) - flags &= ~GHOST_CAN_REENTER - var/mob/dead/observer/ghost = new(src, flags) //Transfer safety to observer spawning proc. - ghost.timeofdeath = src.timeofdeath //BS12 EDIT - if(ghost.can_reenter_corpse) - ADD_TRAIT(ghost, TRAIT_RESPAWNABLE, GHOSTED) - else - GLOB.non_respawnable_keys[ckey] = 1 - if(user_color) - add_atom_colour(user_color, ADMIN_COLOUR_PRIORITY) - ghost.color = user_color - if(ghost_name) - ghost.name = ghost_name - ghost.key = key - ghost.client?.init_verbs() - return ghost + if(!key) + return + if(player_logged) // if they have disconnected we want to remove their SSD overlay + overlays -= image('icons/effects/effects.dmi', icon_state = "zzz_glow") + if(GLOB.non_respawnable_keys[ckey]) + flags &= ~GHOST_CAN_REENTER + var/mob/dead/observer/ghost = new(src, flags) // Transfer safety to observer spawning proc. + ghost.timeofdeath = src.timeofdeath // BS12 EDIT + if(ghost.can_reenter_corpse) + ADD_TRAIT(ghost, TRAIT_RESPAWNABLE, GHOSTED) + else + GLOB.non_respawnable_keys[ckey] = 1 + + // mods, mentors, and the like will have admin observe anyway, so this is moot + if(((key in GLOB.antag_hud_users) || (key in GLOB.roundstart_observer_keys)) && !check_rights(R_MOD | R_ADMIN | R_MENTOR, FALSE, src)) + ghost.verbs |= /mob/dead/observer/proc/do_observe + ghost.verbs |= /mob/dead/observer/proc/observe + if(user_color) + add_atom_colour(user_color, ADMIN_COLOUR_PRIORITY) + ghost.color = user_color + if(ghost_name) + ghost.name = ghost_name + ghost.key = key + + ghost.client?.init_verbs() + + for(var/mob/dead/observer/obs in observers) + obs.cleanup_observe() + + + return ghost /* This is the proc mobs get to turn into a ghost. Forked from ghostize due to compatibility issues. @@ -492,52 +508,67 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp GLOB.ghost_crew_monitor.ui_interact(src) /mob/dead/observer/proc/add_observer_verbs() - verbs.Add(/mob/dead/observer/proc/ManualFollow) + verbs.Add( + /mob/dead/observer/proc/ManualFollow, + ) /mob/dead/observer/proc/remove_observer_verbs() - verbs.Remove(/mob/dead/observer/proc/ManualFollow) + verbs.Remove( + /mob/dead/observer/proc/ManualFollow, + // these might not necessarily be here, but we want to make sure they're gonezo anyway + /mob/dead/observer/proc/observe, + /mob/dead/observer/proc/do_observe + ) -// This is the ghost's follow verb with an argument +// This is the ghost's follow verb with an argument. +// We need to do the usr check on this verb itself, but the logic follows. /mob/dead/observer/proc/ManualFollow(atom/movable/target) set name = "\[Observer\] Orbit" set desc = "Orbits the specified movable atom." set category = null - if(!target || !isobserver(usr)) + // this usr check is apparently necessary for security + if(!isobserver(usr)) return + return do_manual_follow(target) + +// We need to check usr when calling the verb, but we still want this logic to be accessible elsewhere +/mob/dead/observer/proc/do_manual_follow(atom/movable/target) if(!get_turf(target)) return - if(target != src) - if(src in target.get_orbiters()) - return + if(!target || target == src) + return - var/icon/I = icon(target.icon,target.icon_state,target.dir) + if(src in target.get_orbiters()) + return - var/orbitsize = (I.Width()+I.Height())*0.5 + var/icon/I = icon(target.icon, target.icon_state, target.dir) - if(orbitsize == 0) - orbitsize = 40 + var/orbitsize = (I.Width() + I.Height())*0.5 - orbitsize -= (orbitsize/world.icon_size)*(world.icon_size*0.25) + if(orbitsize == 0) + orbitsize = 40 - var/rot_seg + orbitsize -= (orbitsize/world.icon_size)*(world.icon_size*0.25) - switch(ghost_orbit) - if(GHOST_ORBIT_TRIANGLE) - rot_seg = 3 - if(GHOST_ORBIT_SQUARE) - rot_seg = 4 - if(GHOST_ORBIT_PENTAGON) - rot_seg = 5 - if(GHOST_ORBIT_HEXAGON) - rot_seg = 6 - else //Circular - rot_seg = 36 //360/10 bby, smooth enough aproximation of a circle + var/rot_seg - to_chat(src, "Now following [target].") - orbit(target,orbitsize, FALSE, 20, rot_seg) + switch(ghost_orbit) + if(GHOST_ORBIT_TRIANGLE) + rot_seg = 3 + if(GHOST_ORBIT_SQUARE) + rot_seg = 4 + if(GHOST_ORBIT_PENTAGON) + rot_seg = 5 + if(GHOST_ORBIT_HEXAGON) + rot_seg = 6 + else // Circular + rot_seg = 36 // 360/10 bby, smooth enough aproximation of a circle + + to_chat(src, "Now following [target].") + orbit(target, orbitsize, FALSE, 20, rot_seg) /mob/dead/observer/orbit(atom/A, radius = 10, clockwise = FALSE, rotation_speed = 20, rotation_segments = 36, pre_rotation = TRUE, lock_in_orbit = FALSE, force_move = FALSE, orbit_layer = GHOST_LAYER) setDir(2)//reset dir so the right directional sprites show up @@ -825,6 +856,102 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp /mob/dead/observer/get_runechat_color() return alive_runechat_color +/mob/dead/observer/proc/observe() + set name = "Observe" + set desc = "Observe a mob." + set category = "Ghost" + + var/list/possible_targets = list() + for(var/mob/living/L in GLOB.player_list) + if(!L.mind) + continue + possible_targets.Add(L) + + if(!length(possible_targets)) + to_chat(src, "There's nobody for you to observe!") + return + + var/mob/target = tgui_input_list(usr, "Please, select a player!", "Observe", possible_targets) + if(!istype(target) || QDELETED(target)) + return + do_observe(target) + +/mob/dead/observer/proc/do_observe(mob/mob_eye) + set name = "\[Observer\] Observe" + set desc = "Observe the target mob." + set category = null + + if(isnewplayer(mob_eye)) + to_chat(src, "You can't observe someone in the lobby.") + return + + if(isobserver(mob_eye)) + to_chat(src, "You can't observe a ghost.") + return + + if(!mob_eye.mind) + to_chat(src, "You can only observe mobs that have been or are being inhabited by a player!") + return + + if(mob_eye == src) + to_chat(src, "You can't observe yourself!") + return + + if(mob_observed) + // clean up first + stop_orbit() + cleanup_observe() + + // Istype so we filter out points of interest that are not mobs + if(client && ismob(mob_eye)) + // follow the mob so they're technically right there for visible messages n stuff + // call the sub-proc since the base one checks for usr + do_manual_follow(mob_eye) + client.set_eye(mob_eye) + add_attack_logs(src, mob_eye, "observed", ATKLOG_ALMOSTALL) + client.perspective = EYE_PERSPECTIVE + if(mob_eye.hud_used) + client.clear_screen() + LAZYOR(mob_eye.observers, src) + mob_eye.hud_used?.show_hud(mob_eye.hud_used.hud_version, src) + mob_observed = mob_eye.UID() + + + // mentor observing grants you this trait, and provides its own signal handler for this + if(!HAS_MIND_TRAIT(src, TRAIT_MENTOR_OBSERVING)) + RegisterSignal(src, COMSIG_ATOM_ORBITER_STOP, PROC_REF(on_observer_orbit_end), override = TRUE) + else + if(!check_rights(R_MENTOR, FALSE, src)) + log_debug("[key_name(src)] has the the mobserve trait while observing, but isn't a mentor. This is likely an error, and may result in them getting stuck") + +/// Clean up observing +/mob/dead/observer/proc/cleanup_observe() + if(isnull(mob_observed)) + return + + var/mob/target = locateUID(mob_observed) + add_attack_logs(src, target, "un-observed", ATKLOG_ALL) + mob_observed = null + reset_perspective(null) + client?.perspective = initial(client.perspective) + set_sight(initial(sight)) + UnregisterSignal(src, COMSIG_ATOM_ORBITER_STOP) + + if(s_active) + var/obj/item/storage/bag = s_active + s_active = null + bag.update_viewers(src) + + if(!QDELETED(target) && istype(target)) + hide_other_mob_action_buttons(target) + target.observers -= src + +/mob/dead/observer/proc/on_observer_orbit_end(mob/follower, atom) + SIGNAL_HANDLER // COMSIG_ATOM_ORBITER_STOP + if(HAS_MIND_TRAIT(src, TRAIT_MENTOR_OBSERVING)) + log_debug("[key_name(src)] ended up in regular cleanup_observe rather than the mentor cleanup observe despite having TRAIT_MENTOR_OBSERVING. This is likely a bug and may result in them being stuck outside of their bodies.") + cleanup_observe() + /mob/dead/observer/proc/update_dead_radio() if(get_preference(PREFTOGGLE_CHAT_GHOSTRADIO)) GLOB.deadchat_radio.listeners |= src diff --git a/code/modules/mob/dead/observer/observer_logout.dm b/code/modules/mob/dead/observer/observer_logout.dm index 64ef90683c3..d6c1b6e96fb 100644 --- a/code/modules/mob/dead/observer/observer_logout.dm +++ b/code/modules/mob/dead/observer/observer_logout.dm @@ -1,6 +1,9 @@ /mob/dead/observer/Logout() if(client) client.images -= GLOB.ghost_images + if(mob_observed && ismob(locateUID(mob_observed))) + cleanup_observe() + ..() spawn(0) if(src && !key) //we've transferred to another mob. This ghost should be deleted. diff --git a/code/modules/mob/living/carbon/carbon_update_icons.dm b/code/modules/mob/living/carbon/carbon_update_icons.dm index f174dd227ca..203b7ae10d4 100644 --- a/code/modules/mob/living/carbon/carbon_update_icons.dm +++ b/code/modules/mob/living/carbon/carbon_update_icons.dm @@ -49,6 +49,8 @@ r_hand.screen_loc = ui_rhand client.screen += r_hand + update_observer_view(r_hand) + /mob/living/carbon/update_inv_l_hand(ignore_cuffs) if(handcuffed && !ignore_cuffs) drop_l_hand() @@ -57,6 +59,7 @@ if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD) l_hand.screen_loc = ui_lhand client.screen += l_hand + update_observer_view(l_hand) /mob/living/carbon/update_inv_wear_mask() if(istype(wear_mask, /obj/item/clothing/mask)) @@ -86,3 +89,17 @@ /mob/living/carbon/proc/update_hud_back(obj/item/I) return +/mob/living/carbon/proc/update_observer_view(obj/item/worn_item, inventory) + if(!length(observers)) + return + for(var/mob/dead/observe as anything in observers) + if(observe.client && observe.client.eye == src) + if(observe.hud_used) + if(inventory && !observe.hud_used.inventory_shown) + continue + observe.client.screen += worn_item + else + observers -= observe + if(!length(observers)) + observers.Cut() + break diff --git a/code/modules/mob/living/carbon/human/human_inventory.dm b/code/modules/mob/living/carbon/human/human_inventory.dm index 3b39a48a1f0..1ee19379c31 100644 --- a/code/modules/mob/living/carbon/human/human_inventory.dm +++ b/code/modules/mob/living/carbon/human/human_inventory.dm @@ -186,6 +186,13 @@ update_inv_r_hand() I.screen_loc = null + if(client) + client.screen -= I + if(length(observers)) + for(var/mob/dead/observe as anything in observers) + if(observe.client) + observe.client.screen -= I + I.forceMove(src) I.equipped(src, slot, initial) I.layer = ABOVE_HUD_LAYER @@ -271,8 +278,8 @@ var/obj/item/clothing/head/hat = I if(hat.vision_flags || hat.see_in_dark || !isnull(hat.lighting_alpha)) update_sight() + // this calls update_inv_head() on its own head_update(I) - update_inv_head() if(SLOT_HUD_SHOES) shoes = I update_inv_shoes() diff --git a/code/modules/mob/living/carbon/human/human_update_icons.dm b/code/modules/mob/living/carbon/human/human_update_icons.dm index feaad4085f9..c1109e078d1 100644 --- a/code/modules/mob/living/carbon/human/human_update_icons.dm +++ b/code/modules/mob/living/carbon/human/human_update_icons.dm @@ -567,10 +567,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) inv.update_icon() if(w_uniform && istype(w_uniform, /obj/item/clothing/under)) - if(client && hud_used && hud_used.hud_version == HUD_STYLE_STANDARD) - if(hud_used.inventory_shown) //if the inventory is open ... - w_uniform.screen_loc = ui_iclothing //...draw the item in the inventory screen - client.screen += w_uniform //Either way, add the item to the HUD + update_hud_uniform(w_uniform) var/t_color = w_uniform.item_color if(!t_color) @@ -644,9 +641,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) inv.update_icon() if(wear_id) - if(client && hud_used && hud_used.hud_version == HUD_STYLE_STANDARD) - wear_id.screen_loc = ui_id - client.screen += wear_id + update_hud_id(wear_id) if(w_uniform && w_uniform:displays_id) overlays_standing[ID_LAYER] = mutable_appearance('icons/mob/mob.dmi', "id", layer = -ID_LAYER) @@ -660,10 +655,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) inv.update_icon() if(gloves) - if(client && hud_used && hud_used.hud_version == HUD_STYLE_STANDARD) - if(hud_used.inventory_shown) //if the inventory is open ... - gloves.screen_loc = ui_gloves //...draw the item in the inventory screen - client.screen += gloves //Either way, add the item to the HUD + update_hud_gloves(gloves) var/t_state = gloves.item_state if(!t_state) t_state = gloves.icon_state @@ -702,10 +694,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) if(glasses) var/mutable_appearance/new_glasses var/obj/item/organ/external/head/head_organ = get_organ("head") - if(client && hud_used && hud_used.hud_version == HUD_STYLE_STANDARD) - if(hud_used.inventory_shown) //if the inventory is open ... - glasses.screen_loc = ui_glasses //...draw the item in the inventory screen - client.screen += glasses //Either way, add the item to the HUD + update_hud_glasses(glasses) if(glasses.icon_override) new_glasses = mutable_appearance(glasses.icon_override, "[glasses.icon_state]", layer = -GLASSES_LAYER) @@ -744,10 +733,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) if(l_ear || r_ear) if(l_ear) - if(client && hud_used && hud_used.hud_version == HUD_STYLE_STANDARD) - if(hud_used.inventory_shown) //if the inventory is open ... - l_ear.screen_loc = ui_l_ear //...draw the item in the inventory screen - client.screen += l_ear //Either way, add the item to the HUD + update_hud_l_ear(l_ear) var/t_type = l_ear.item_state if(!t_type) @@ -761,10 +747,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) overlays_standing[EARS_LAYER] = mutable_appearance('icons/mob/clothing/ears.dmi', "[t_type]", layer = -EARS_LAYER) if(r_ear) - if(client && hud_used && hud_used.hud_version == HUD_STYLE_STANDARD) - if(hud_used.inventory_shown) //if the inventory is open ... - r_ear.screen_loc = ui_r_ear //...draw the item in the inventory screen - client.screen += r_ear //Either way, add the item to the HUD + update_hud_r_ear(r_ear) var/t_type = r_ear.item_state if(!t_type) @@ -786,10 +769,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) inv.update_icon() if(shoes) - if(client && hud_used && hud_used.hud_version == HUD_STYLE_STANDARD) - if(hud_used.inventory_shown) //if the inventory is open ... - shoes.screen_loc = ui_shoes //...draw the item in the inventory screen - client.screen += shoes //Either way, add the item to the HUD + update_hud_shoes(shoes) if(!wear_suit || !(wear_suit.flags_inv & HIDESHOES)) var/mutable_appearance/standing @@ -822,9 +802,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) inv.update_icon() if(s_store) - if(client && hud_used && hud_used.hud_version == HUD_STYLE_STANDARD) - s_store.screen_loc = ui_sstore1 - client.screen += s_store + update_hud_s_store(s_store) var/t_state = s_store.item_state if(!t_state) @@ -844,6 +822,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) inv.update_icon() if(head) + update_hud_head(head) var/mutable_appearance/standing if(head.sprite_sheets && head.sprite_sheets[dna.species.sprite_sheet_name]) standing = mutable_appearance(head.sprite_sheets[dna.species.sprite_sheet_name], "[head.icon_state]", layer = -HEAD_LAYER) @@ -877,6 +856,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) belt.screen_loc = ui_belt if(belt) + update_observer_view(belt) var/t_state = belt.item_state if(!t_state) t_state = belt.icon_state @@ -899,10 +879,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) inv.update_icon() if(wear_suit && istype(wear_suit, /obj/item/clothing/suit)) - if(client && hud_used && hud_used.hud_version == HUD_STYLE_STANDARD) - if(hud_used.inventory_shown) //if the inventory is open ... - wear_suit.screen_loc = ui_oclothing //TODO //...draw the item in the inventory screen - client.screen += wear_suit //Either way, add the item to the HUD + update_hud_wear_suit(wear_suit) var/mutable_appearance/standing if(wear_suit.sprite_sheets && wear_suit.sprite_sheets[dna.species.sprite_sheet_name]) @@ -950,12 +927,17 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) if(hud_used.hud_version == HUD_STYLE_STANDARD) if(l_store) - client.screen += l_store l_store.screen_loc = ui_storage1 + if(hud_used.hud_shown) + client.screen += l_store + update_observer_view(l_store) if(r_store) - client.screen += r_store r_store.screen_loc = ui_storage2 + if(hud_used.hud_shown) + client.screen += r_store + r_store.screen_loc = ui_storage2 + update_observer_view(r_store) /mob/living/carbon/human/update_inv_wear_pda() if(client && hud_used) @@ -964,8 +946,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) inv.update_icon() if(wear_pda) - client.screen += wear_pda - wear_pda.screen_loc = ui_pda + update_hud_wear_pda(wear_pda) /mob/living/carbon/human/update_inv_wear_mask() ..() @@ -975,6 +956,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) if(inv) inv.update_icon() if(wear_mask && (istype(wear_mask, /obj/item/clothing/mask) || istype(wear_mask, /obj/item/clothing/accessory))) + update_hud_wear_mask(wear_mask) if(!(SLOT_HUD_WEAR_MASK in check_obscured_slots())) var/obj/item/organ/external/head/head_organ = get_organ("head") if(!istype(head_organ)) @@ -1009,6 +991,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) ..() remove_overlay(BACK_LAYER) if(back) + update_hud_back(back) //determine the icon to use var/t_state = back.item_state if(!t_state) @@ -1045,6 +1028,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) ..() remove_overlay(R_HAND_LAYER) if(r_hand) + show_hand_to_observers(r_hand, left = FALSE) var/t_state = r_hand.item_state if(!t_state) t_state = r_hand.icon_state @@ -1064,6 +1048,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) ..() remove_overlay(L_HAND_LAYER) if(l_hand) + show_hand_to_observers(l_hand, left = TRUE) var/t_state = l_hand.item_state if(!t_state) t_state = l_hand.icon_state @@ -1080,25 +1065,105 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) //human HUD updates for items in our inventory -//update whether our head item appears on our hud. -/mob/living/carbon/human/update_hud_head(obj/item/I) - if(client && hud_used && hud_used.hud_version == HUD_STYLE_STANDARD) - if(hud_used.inventory_shown) - I.screen_loc = ui_head - client.screen += I +/mob/living/carbon/human/proc/show_hand_to_observers(obj/item/worn_item, left = TRUE) + if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD) + worn_item.screen_loc = ui_hand_position(left ? 1 : 2) + client.screen += worn_item + if(length(observers)) + for(var/mob/dead/observe in observers) + if(observe.client && observe.client.eye == src) + observe.client.screen += worn_item + else + observers -= observe + if(!length(observers)) + observers = null + break -//update whether our mask item appears on our hud. -/mob/living/carbon/human/update_hud_wear_mask(obj/item/I) - if(client && hud_used && hud_used.hud_version == HUD_STYLE_STANDARD) - if(hud_used.inventory_shown) - I.screen_loc = ui_mask - client.screen += I -//update whether our back item appears on our hud. -/mob/living/carbon/human/update_hud_back(obj/item/I) - if(client && hud_used && hud_used.hud_version == HUD_STYLE_STANDARD) - I.screen_loc = ui_back - client.screen += I +/mob/living/carbon/human/update_hud_wear_mask(obj/item/worn_item) + worn_item.screen_loc = ui_mask + if((client && hud_used && hud_used.hud_version == HUD_STYLE_STANDARD) && (hud_used.inventory_shown && hud_used.hud_shown)) + client.screen += worn_item + update_observer_view(worn_item, TRUE) + + +/mob/living/carbon/human/proc/update_hud_uniform(obj/item/worn_item) + worn_item.screen_loc = ui_iclothing + if((client && hud_used && hud_used.hud_version == HUD_STYLE_STANDARD) && (hud_used.inventory_shown && hud_used.hud_shown)) + client.screen += worn_item + update_observer_view(worn_item, TRUE) + +/mob/living/carbon/human/proc/update_hud_id(obj/item/worn_item) + worn_item.screen_loc = ui_id + if(client && hud_used?.hud_shown && hud_used.hud_version == HUD_STYLE_STANDARD) + client.screen += worn_item + update_observer_view(worn_item) + +/mob/living/carbon/human/proc/update_hud_wear_pda(obj/item/worn_item) + worn_item.screen_loc = ui_pda + if(client && hud_used?.hud_shown && hud_used.hud_version == HUD_STYLE_STANDARD) + client.screen += worn_item + update_observer_view(worn_item) + +/mob/living/carbon/human/proc/update_hud_gloves(obj/item/worn_item) + worn_item.screen_loc = ui_gloves + if((client && hud_used && hud_used.hud_version == HUD_STYLE_STANDARD) && (hud_used.inventory_shown && hud_used.hud_shown)) + client.screen += worn_item + update_observer_view(worn_item, TRUE) + +/mob/living/carbon/human/proc/update_hud_glasses(obj/item/worn_item) + worn_item.screen_loc = ui_glasses + if((client && hud_used && hud_used.hud_version == HUD_STYLE_STANDARD) && (hud_used.inventory_shown && hud_used.hud_shown)) + client.screen += worn_item + update_observer_view(worn_item, TRUE) + +/mob/living/carbon/human/proc/update_hud_l_ear(obj/item/worn_item) + worn_item.screen_loc = ui_l_ear + if((client && hud_used && hud_used.hud_version == HUD_STYLE_STANDARD) && (hud_used.inventory_shown && hud_used.hud_shown)) + client.screen += worn_item + update_observer_view(worn_item, TRUE) + +/mob/living/carbon/human/proc/update_hud_r_ear(obj/item/worn_item) + worn_item.screen_loc = ui_r_ear + if((client && hud_used && hud_used.hud_version == HUD_STYLE_STANDARD) && (hud_used.inventory_shown && hud_used.hud_shown)) + client.screen += worn_item + update_observer_view(worn_item, TRUE) + +/mob/living/carbon/human/proc/update_hud_shoes(obj/item/worn_item) + worn_item.screen_loc = ui_shoes + if((client && hud_used && hud_used.hud_version == HUD_STYLE_STANDARD) && (hud_used.inventory_shown && hud_used.hud_shown)) + client.screen += worn_item + update_observer_view(worn_item, TRUE) + +/mob/living/carbon/human/proc/update_hud_s_store(obj/item/worn_item) + worn_item.screen_loc = ui_sstore1 + if(client && hud_used?.hud_shown && hud_used.hud_version == HUD_STYLE_STANDARD) + client.screen += worn_item + update_observer_view(worn_item, TRUE) + +/mob/living/carbon/human/proc/update_hud_wear_suit(obj/item/worn_item) + worn_item.screen_loc = ui_oclothing + if((client && hud_used && hud_used.hud_version == HUD_STYLE_STANDARD) && (hud_used.inventory_shown && hud_used.hud_shown)) + client.screen += worn_item + update_observer_view(worn_item, TRUE) + +/mob/living/carbon/human/proc/update_hud_belt(obj/item/worn_item) + belt.screen_loc = ui_belt + if(client && hud_used?.hud_shown && hud_used.hud_version == HUD_STYLE_STANDARD) + client.screen += worn_item + update_observer_view(worn_item, TRUE) + +/mob/living/carbon/human/update_hud_head(obj/item/worn_item) + worn_item.screen_loc = ui_head + if((client && hud_used && hud_used.hud_version == HUD_STYLE_STANDARD) && (hud_used.inventory_shown && hud_used.hud_shown)) + client.screen += worn_item + update_observer_view(worn_item, TRUE) + +/mob/living/carbon/human/update_hud_back(obj/item/worn_item) + worn_item.screen_loc = ui_back + if(client && hud_used?.hud_shown && hud_used.hud_version == HUD_STYLE_STANDARD) + client.screen += worn_item + update_observer_view(worn_item, TRUE) /mob/living/carbon/human/proc/update_wing_layer() remove_overlay(WING_UNDERLIMBS_LAYER) diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm index 890b5209345..23e6ba4f0ab 100644 --- a/code/modules/mob/living/death.dm +++ b/code/modules/mob/living/death.dm @@ -64,7 +64,7 @@ if(mind && suiciding) mind.suicided = TRUE reset_perspective(null) - hud_used?.reload_fullscreen() + reload_fullscreen() update_sight() update_action_buttons_icon() ADD_TRAIT(src, TRAIT_FLOORED, STAT_TRAIT) diff --git a/code/modules/mob/living/silicon/robot/robot_inventory.dm b/code/modules/mob/living/silicon/robot/robot_inventory.dm index 29cbd551747..78bf0e04a6d 100644 --- a/code/modules/mob/living/silicon/robot/robot_inventory.dm +++ b/code/modules/mob/living/silicon/robot/robot_inventory.dm @@ -15,6 +15,7 @@ O.mouse_opacity = MOUSE_OPACITY_OPAQUE + observer_screen_update(O, add = FALSE) if(client) client.screen -= O contents -= O @@ -78,6 +79,7 @@ set_actions(O) else to_chat(src, "You need to disable a module first!") + observer_screen_update(O, add = TRUE) check_module_damage(FALSE) update_icons() @@ -250,3 +252,25 @@ hands.icon_state = "nomod" else hands.icon_state = lowertext(module.module_type) + + +/** + * Updates the observers's screens with cyborg itemss. + * Arguments + * * item_module - the item being added or removed from the screen + * * add - whether or not the item is being added, or removed. + */ +/mob/living/silicon/robot/proc/observer_screen_update(obj/item/item_module, add = TRUE) + if(!length(observers)) + return + for(var/mob/dead/observe in observers) + if(observe.client && observe.client.eye == src) + if(add) + observe.client.screen += item_module + else + observe.client.screen -= item_module + else + observers -= observe + if(!length(observers)) + observers = null + break diff --git a/code/modules/mob/living/silicon/robot/robot_mob.dm b/code/modules/mob/living/silicon/robot/robot_mob.dm index d95d0d9d294..bb700a8d976 100644 --- a/code/modules/mob/living/silicon/robot/robot_mob.dm +++ b/code/modules/mob/living/silicon/robot/robot_mob.dm @@ -27,10 +27,6 @@ GLOBAL_LIST_INIT(robot_verbs_default, list( var/atom/movable/screen/lamp_button = null var/atom/movable/screen/thruster_button = null - /// Used to determine whether the robot has the module menu shown or not. - var/shown_robot_modules = FALSE - var/atom/movable/screen/robot_modules_background - // 3 Modules can be activated at any one time. var/obj/item/robot_module/module = null var/module_active = null @@ -172,11 +168,6 @@ GLOBAL_LIST_INIT(robot_verbs_default, list( wires = new(src) - robot_modules_background = new() - robot_modules_background.icon_state = "block" - robot_modules_background.layer = HUD_LAYER //Objects that appear on screen are on layer 20, UI should be just below it. - robot_modules_background.plane = HUD_PLANE - ident = rand(1, 999) rename_character(null, get_default_name()) update_icons() @@ -664,9 +655,6 @@ GLOBAL_LIST_INIT(robot_verbs_default, list( /mob/living/silicon/robot/proc/reset_module() notify_ai(2) - - shown_robot_modules = 0 - client?.screen -= robot_modules_background client?.screen -= hud_used.module_store_icon uneq_all() SStgui.close_user_uis(src) @@ -699,6 +687,8 @@ GLOBAL_LIST_INIT(robot_verbs_default, list( status_flags |= CANPUSH + hud_used.update_robot_modules_display() + //for borg hotkeys, here module refers to borg inv slot, not core module /mob/living/silicon/robot/verb/cmd_toggle_module(module as num) set name = "Toggle Module" diff --git a/code/modules/mob/living/stat_states.dm b/code/modules/mob/living/stat_states.dm index 551eac92d1d..0698dd7bf79 100644 --- a/code/modules/mob/living/stat_states.dm +++ b/code/modules/mob/living/stat_states.dm @@ -75,7 +75,7 @@ update_blind_effects() update_sight() updatehealth("update revive") - hud_used?.reload_fullscreen() + reload_fullscreen() SEND_SIGNAL(src, COMSIG_LIVING_REVIVE, updating) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index b89bb7ec75a..489d8edacaf 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -27,6 +27,10 @@ viewing_alternate_appearances = null LAssailant = null runechat_msg_location = null + if(length(observers)) + for(var/mob/dead/observe as anything in observers) + observe.reset_perspective(null) + return ..() /mob/Initialize(mapload) @@ -600,7 +604,7 @@ GLOBAL_LIST_INIT(slot_equipment_priority, list( \ if(.) // Allows sharing HUDs with ghosts if(hud_used) - client.screen = list() + client.clear_screen() hud_used.show_hud(hud_used.hud_version) //mob verbs are faster than object verbs. See http://www.byond.com/forum/?post=1326139&page=2#comment8198716 for why this isn't atom/verb/examine() diff --git a/code/modules/mob/mob_vars.dm b/code/modules/mob/mob_vars.dm index 9f71bc02cfe..b46a1d29f4e 100644 --- a/code/modules/mob/mob_vars.dm +++ b/code/modules/mob/mob_vars.dm @@ -207,7 +207,7 @@ var/datum/input_focus = null /// Is our mob currently suiciding? Used for suicide code along with many different revival checks var/suiciding = FALSE - /// Used for some screen objects, such as + /// Used for some screen objects var/list/screens = list() /// lazy list. contains /atom/movable/screen/alert only, On /mob so clientless mobs will throw alerts properly var/list/alerts @@ -246,7 +246,8 @@ var/next_click_modifier = 1 /// Tracks the open UIs that a mob has, used in TGUI for various things, such as updating UIs var/list/open_uis = list() - + /// List of observers currently observing us. + var/list/mob/dead/observer/observers = list() /// Does this mob speak OOC? /// Controls whether they can say some symbols. var/speaks_ooc = FALSE diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm index 9e354111a88..ecd4db10155 100644 --- a/code/modules/mob/new_player/new_player.dm +++ b/code/modules/mob/new_player/new_player.dm @@ -182,7 +182,10 @@ var/period_human_readable = "within [GLOB.configuration.general.roundstart_observer_period] minute\s" if(GLOB.configuration.general.roundstart_observer_period == 0) period_human_readable = "before the round started" - to_chat(src, "As you observed [period_human_readable], you can freely toggle antag-hud without losing respawnability.") + to_chat(src, "As you observed [period_human_readable], you can freely toggle antag-hud without losing respawnability, and can freely observe what other players see.") + if(!check_rights(R_MOD | R_ADMIN | R_MENTOR, FALSE, src)) + // admins always get aobserve + add_verb(observer, list(/mob/dead/observer/proc/do_observe, /mob/dead/observer/proc/observe)) observer.started_as_observer = 1 close_spawn_windows() var/obj/spawn_point diff --git a/code/modules/tgui/modules/ghost_hud_panel.dm b/code/modules/tgui/modules/ghost_hud_panel.dm index cff69061394..2e6a6b8d58d 100644 --- a/code/modules/tgui/modules/ghost_hud_panel.dm +++ b/code/modules/tgui/modules/ghost_hud_panel.dm @@ -81,6 +81,9 @@ GLOBAL_DATUM_INIT(ghost_hud_panel, /datum/ui_module/ghost_hud_panel, new) GLOB.antag_hud_users |= ghost.ckey + if(!check_rights(R_MOD | R_ADMIN | R_MENTOR, FALSE)) + // admins always get aobserve + add_verb(ghost, list(/mob/dead/observer/proc/do_observe, /mob/dead/observer/proc/observe)) ghost.antagHUD = TRUE for(var/datum/atom_hud/antag/H in GLOB.huds)