From 05dc930edd74c4ecbda0f3d4083bd80d553bc00d Mon Sep 17 00:00:00 2001 From: Letter N <24603524+LetterN@users.noreply.github.com> Date: Tue, 13 Jan 2026 00:18:13 +0800 Subject: [PATCH] update orbiting (#7469) ## About The Pull Request ## Why It's Good For The Game ## Changelog :cl: code: updated WrapAdminProcCall so it doesnt sleep (remove the waiting feature) tweak: you can now see how many people are orbiting something /:cl: --- citadel.dme | 2 + code/__DEFINES/traits/unsorted.dm | 3 + code/datums/callback.dm | 4 + code/datums/components/orbiter.dm | 157 ++++++++++------ code/datums/movement_detector.dm | 55 ++++++ code/game/atoms/_atom.dm | 2 - code/game/atoms/atom_orbit.dm | 33 ++++ code/game/atoms/movable/movable.dm | 7 +- code/modules/admin/callproc/callproc.dm | 173 +++++++++++++----- code/modules/mob/observer/dead/orbit.dm | 41 +++-- .../tgui/interfaces/Orbit/JobIcon.tsx | 7 +- .../tgui/interfaces/Orbit/OrbitBlade.tsx | 2 +- .../interfaces/Orbit/OrbitCollapsible.tsx | 4 +- .../tgui/interfaces/Orbit/OrbitContent.tsx | 2 +- .../tgui/interfaces/Orbit/OrbitItem.tsx | 2 +- .../tgui/interfaces/Orbit/OrbitSearchBar.tsx | 2 +- .../tgui/interfaces/Orbit/OrbitTooltip.tsx | 2 +- .../tgui/interfaces/Orbit/constants.ts | 4 +- .../packages/tgui/interfaces/Orbit/helpers.ts | 2 +- tgui/packages/tgui/interfaces/Orbit/types.ts | 4 +- .../tgui/interfaces/common/JobToIcon.ts | 59 ++++++ 21 files changed, 432 insertions(+), 135 deletions(-) create mode 100644 code/datums/movement_detector.dm create mode 100644 code/game/atoms/atom_orbit.dm create mode 100644 tgui/packages/tgui/interfaces/common/JobToIcon.ts diff --git a/citadel.dme b/citadel.dme index a378c919fb5..d9e35d0f61c 100644 --- a/citadel.dme +++ b/citadel.dme @@ -754,6 +754,7 @@ #include "code\datums\material_container.dm" #include "code\datums\mind.dm" #include "code\datums\mixed.dm" +#include "code\datums\movement_detector.dm" #include "code\datums\mutable_appearance.dm" #include "code\datums\periodic_news.dm" #include "code\datums\position_point_vector.dm" @@ -1151,6 +1152,7 @@ #include "code\game\atoms\atom-tool_system-wrappers.dm" #include "code\game\atoms\atom-tool_system.dm" #include "code\game\atoms\atom-vv.dm" +#include "code\game\atoms\atom_orbit.dm" #include "code\game\atoms\atoms_initializing_EXPENSIVE.dm" #include "code\game\atoms\buckling.dm" #include "code\game\atoms\defense_old.dm" diff --git a/code/__DEFINES/traits/unsorted.dm b/code/__DEFINES/traits/unsorted.dm index bd77b3c1c9a..bb818edde08 100644 --- a/code/__DEFINES/traits/unsorted.dm +++ b/code/__DEFINES/traits/unsorted.dm @@ -34,3 +34,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_FISH_SAFE_STORAGE "fish_case" /// Stuff that can go inside fish cases #define TRAIT_FISH_CASE_COMPATIBILE "fish_case_compatibile" + +/// Whether or not orbiting is blocked or not +#define TRAIT_ORBITING_FORBIDDEN "orbiting_forbidden" diff --git a/code/datums/callback.dm b/code/datums/callback.dm index 18216deae1a..8b571ccf0f2 100644 --- a/code/datums/callback.dm +++ b/code/datums/callback.dm @@ -115,6 +115,8 @@ else calling_arguments = args if(datum_flags & DF_VAR_EDITED) + if(usr != GLOB.AdminProcCallHandler && !(usr && usr?.client?.ckey)) //This happens when a timer or the MC invokes a callback + return HandleUserlessProcCall(usr, object, delegate, calling_arguments) return WrapAdminProcCall(object, delegate, calling_arguments) if (object == GLOBAL_PROC) return call(delegate)(arglist(calling_arguments)) @@ -150,6 +152,8 @@ else calling_arguments = args if(datum_flags & DF_VAR_EDITED) + if(usr != GLOB.AdminProcCallHandler && !(usr && usr?.client?.ckey)) //This happens when a timer or the MC invokes a callback + return HandleUserlessProcCall(usr, object, delegate, calling_arguments) return WrapAdminProcCall(object, delegate, calling_arguments) if (object == GLOBAL_PROC) return call(delegate)(arglist(calling_arguments)) diff --git a/code/datums/components/orbiter.dm b/code/datums/components/orbiter.dm index 3726f071906..f67d635ea2e 100644 --- a/code/datums/components/orbiter.dm +++ b/code/datums/components/orbiter.dm @@ -1,7 +1,12 @@ /datum/component/orbiter can_transfer = TRUE dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS - var/list/orbiters + /// Assoc list of all orbiters -> their initial matrix + var/list/orbiter_list + /// Assoc list of orbiters -> their orbiting parameters + var/list/orbiter_params + /// Movement tracker used to check when our owner moves + var/datum/movement_detector/tracker //radius: range to orbit at, radius of the circle formed by orbiting (in pixels) //clockwise: whether you orbit clockwise or anti clockwise @@ -12,33 +17,34 @@ if(!istype(orbiter) || !isatom(parent) || isarea(parent)) return COMPONENT_INCOMPATIBLE - orbiters = list() - - var/atom/master = parent - master.orbiters = src + orbiter_list = list() + orbiter_params = list() begin_orbit(orbiter, radius, clockwise, rotation_speed, rotation_segments, pre_rotation) /datum/component/orbiter/RegisterWithParent() - . = ..() var/atom/target = parent - while(ismovable(target)) - RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(move_react)) - target = target.loc + + target.orbiters = src + if(ismovable(target)) + tracker = new(target, CALLBACK(src, PROC_REF(move_react))) + + RegisterSignal(parent, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, PROC_REF(orbiter_glide_size_update)) /datum/component/orbiter/UnregisterFromParent() - . = ..() + UnregisterSignal(parent, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE) var/atom/target = parent - while(ismovable(target)) - UnregisterSignal(target, COMSIG_MOVABLE_MOVED) - target = target.loc + target.orbiters = null + QDEL_NULL(tracker) /datum/component/orbiter/Destroy() var/atom/master = parent - master.orbiters = null - for(var/i in orbiters) + if(master.orbiters == src) + master.orbiters = null + for(var/i in orbiter_list) end_orbit(i) - orbiters = null + orbiter_list = null + orbiter_params = null return ..() /datum/component/orbiter/InheritComponent(datum/component/orbiter/newcomp, original, atom/movable/orbiter, radius, clockwise, rotation_speed, rotation_segments, pre_rotation) @@ -46,26 +52,43 @@ begin_orbit(arglist(args.Copy(3))) return // The following only happens on component transfers - orbiters += newcomp.orbiters + for(var/o in newcomp.orbiter_list) + var/atom/movable/incoming_orbiter = o + incoming_orbiter.orbiting = src + // It is important to transfer the signals so we don't get locked to the new orbiter component for all time + newcomp.UnregisterSignal(incoming_orbiter, list(COMSIG_MOVABLE_MOVED /*, COMSIG_ATOM_BEFORE_SHUTTLE_MOVE, COMSIG_ATOM_AFTER_SHUTTLE_MOVE */)) + RegisterSignal(incoming_orbiter, COMSIG_MOVABLE_MOVED, PROC_REF(orbiter_move_react)) + // RegisterSignal(incoming_orbiter, COMSIG_ATOM_BEFORE_SHUTTLE_MOVE, PROC_REF(orbiter_before_shuttle_move)) -/datum/component/orbiter/PostTransfer() - if(!isatom(parent) || isarea(parent) || !get_turf(parent)) + orbiter_list += newcomp.orbiter_list + orbiter_params += newcomp.orbiter_params + newcomp.orbiter_list = null + newcomp.orbiter_params = null + +/datum/component/orbiter/PostTransfer(datum/new_parent) + if(!isatom(new_parent) || isarea(new_parent) || !get_turf(new_parent)) return COMPONENT_INCOMPATIBLE - move_react() + move_react(new_parent) /datum/component/orbiter/proc/begin_orbit(atom/movable/orbiter, radius, clockwise, rotation_speed, rotation_segments, pre_rotation) - SEND_SIGNAL(parent, COMSIG_ATOM_ORBIT_BEGIN, orbiter, radius, clockwise, rotation_speed, rotation_segments, pre_rotation) if(orbiter.orbiting) if(orbiter.orbiting == src) orbiter.orbiting.end_orbit(orbiter, TRUE) else orbiter.orbiting.end_orbit(orbiter) - orbiters[orbiter] = TRUE + + orbiter_params[orbiter] = args.Copy(2) + orbiter_list[orbiter] = TRUE orbiter.orbiting = src + + // ADD_TRAIT(orbiter, TRAIT_NO_FLOATING_ANIM, ORBITING_TRAIT) RegisterSignal(orbiter, COMSIG_MOVABLE_MOVED, PROC_REF(orbiter_move_react)) + // RegisterSignal(orbiter, COMSIG_ATOM_BEFORE_SHUTTLE_MOVE, PROC_REF(orbiter_before_shuttle_move)) + + SEND_SIGNAL(parent, COMSIG_ATOM_ORBIT_BEGIN, orbiter) var/matrix/initial_transform = matrix(orbiter.transform) - orbiters[orbiter] = initial_transform + orbiter_list[orbiter] = initial_transform // Head first! if(pre_rotation) @@ -82,28 +105,59 @@ orbiter.SpinAnimation(rotation_speed, -1, clockwise, rotation_segments, parallel = FALSE) - orbiter.forceMove(get_turf(parent)) - to_chat(orbiter, "Now orbiting [parent].") + // if(ismob(orbiter)) + // var/mob/orbiter_mob = orbiter + // orbiter_mob.updating_glide_size = FALSE + if(ismovable(parent)) + var/atom/movable/movable_parent = parent + orbiter.glide_size = movable_parent.glide_size + + orbiter.abstract_move(get_turf(parent)) + to_chat(orbiter, SPAN_NOTICE("Now orbiting [parent].")) + +/datum/component/orbiter/proc/orbiter_before_shuttle_move(atom/source) + SIGNAL_HANDLER + // We need to detach ourselves before the shuttle moves and reattach afterwards + end_orbit(source, TRUE) + // RegisterSignal(source, COMSIG_ATOM_AFTER_SHUTTLE_MOVE, PROC_REF(orbiter_after_shuttle_move)) + +/datum/component/orbiter/proc/orbiter_after_shuttle_move(atom/source) + SIGNAL_HANDLER + // UnregisterSignal(source, COMSIG_ATOM_AFTER_SHUTTLE_MOVE) + begin_orbit(arglist(list(source) + orbiter_params[source])) /datum/component/orbiter/proc/end_orbit(atom/movable/orbiter, refreshing=FALSE) - if(!orbiters[orbiter]) + if(!orbiter_list[orbiter]) return - SEND_SIGNAL(parent, COMSIG_ATOM_ORBIT_END, orbiter, refreshing) - UnregisterSignal(orbiter, COMSIG_MOVABLE_MOVED) - orbiter.SpinAnimation(0, 0, parallel = FALSE) - if(istype(orbiters[orbiter],/matrix)) //This is ugly. - orbiter.transform = orbiters[orbiter] - orbiters -= orbiter + UnregisterSignal(orbiter, list(COMSIG_MOVABLE_MOVED /*, COMSIG_ATOM_BEFORE_SHUTTLE_MOVE, COMSIG_ATOM_AFTER_SHUTTLE_MOVE */)) + // SEND_SIGNAL(parent, COMSIG_ATOM_ORBIT_STOP, orbiter) // on tg used by ghost player plays + orbiter.SpinAnimation(0, 0) + if(istype(orbiter_list[orbiter],/matrix)) //This is ugly. + orbiter.transform = orbiter_list[orbiter] + orbiter_list -= orbiter + if(!refreshing) + orbiter_params -= orbiter orbiter.stop_orbit(src) orbiter.orbiting = null - if(!refreshing && !length(orbiters) && !QDELING(src)) + + if(ismob(orbiter)) + var/mob/orbiter_mob = orbiter + // orbiter_mob.updating_glide_size = TRUE + orbiter_mob.glide_size = 8 + + if(isobserver(orbiter)) + var/mob/observer/dead/ghostie = orbiter + ghostie.orbiting_ref = null + + // REMOVE_TRAIT(orbiter, TRAIT_NO_FLOATING_ANIM, ORBITING_TRAIT) + + if(!refreshing && !length(orbiter_list) && !QDELING(src)) qdel(src) // This proc can receive signals by either the thing being directly orbited or anything holding it -/datum/component/orbiter/proc/move_react(atom/orbited, atom/oldloc, direction) +/datum/component/orbiter/proc/move_react(atom/movable/master, atom/mover, atom/oldloc, direction) set waitfor = FALSE // Transfer calls this directly and it doesnt care if the ghosts arent done moving - var/atom/movable/master = parent if(master.loc == oldloc) return @@ -111,41 +165,38 @@ if(!newturf) qdel(src) - // Handling the signals of stuff holding us (or not anymore) - // These are prety rarely activated, how often are you following something in a bag? - if(oldloc && !isturf(oldloc)) // We used to be registered to it, probably - var/atom/target = oldloc - while(ismovable(target)) - UnregisterSignal(target, COMSIG_MOVABLE_MOVED) - target = target.loc - if(orbited?.loc && orbited.loc != newturf) // We want to know when anything holding us moves too - var/atom/target = orbited.loc - while(ismovable(target)) - RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(move_react), TRUE) - target = target.loc - var/atom/curloc = master.loc - for(var/i in orbiters) - var/atom/movable/thing = i - if(QDELETED(thing) || thing.loc == newturf) + for(var/atom/movable/movable_orbiter as anything in orbiter_list) + if(QDELETED(movable_orbiter) || movable_orbiter.loc == newturf) continue - thing.forceMove(newturf) + movable_orbiter.abstract_move(newturf) if(CHECK_TICK && master.loc != curloc) // We moved again during the checktick, cancel current operation break /datum/component/orbiter/proc/orbiter_move_react(atom/movable/orbiter, atom/oldloc, direction) + SIGNAL_HANDLER + if(orbiter.loc == get_turf(parent)) return end_orbit(orbiter) +/datum/component/orbiter/proc/orbiter_glide_size_update(datum/source, target) + SIGNAL_HANDLER + for(var/orbiter in orbiter_list) + var/atom/movable/movable_orbiter = orbiter + movable_orbiter.glide_size = target + ///////////////////// /atom/movable/proc/orbit(atom/A, radius = 10, clockwise = FALSE, rotation_speed = 20, rotation_segments = 36, pre_rotation = TRUE) if(!istype(A) || !get_turf(A) || A == src) return - + if (HAS_TRAIT(A, TRAIT_ORBITING_FORBIDDEN)) + // Stealth-mins have an empty name, don't want "You cannot orbit at this time." + to_chat(src, SPAN_NOTICE("You cannot orbit ["[A]" || "them"] at this time.")) + return orbit_target = A return A.AddComponent(/datum/component/orbiter, src, radius, clockwise, rotation_speed, rotation_segments, pre_rotation) diff --git a/code/datums/movement_detector.dm b/code/datums/movement_detector.dm new file mode 100644 index 00000000000..be36d62e660 --- /dev/null +++ b/code/datums/movement_detector.dm @@ -0,0 +1,55 @@ +/// A datum to handle the busywork of registering signals to handle in depth tracking of a movable +/datum/movement_detector + var/atom/movable/tracked + var/datum/callback/listener + +/datum/movement_detector/New(atom/movable/target, datum/callback/listener) + if(target) + track(target, listener) + +/datum/movement_detector/Destroy() + untrack() + tracked = null + listener = null + return ..() + +/// Sets up tracking of the given movable atom +/datum/movement_detector/proc/track(atom/movable/target, datum/callback/listener) + untrack() + tracked = target + src.listener = listener + + while(ismovable(target)) + RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(move_react)) + target = target.loc + +/// Stops tracking +/datum/movement_detector/proc/untrack() + if(!tracked) + return + var/atom/movable/target = tracked + while(ismovable(target)) + UnregisterSignal(target, COMSIG_MOVABLE_MOVED) + target = target.loc + +/** + * Reacts to any movement that would cause a change in coordinates of the tracked movable atom + * This works by detecting movement of either the tracked object, or anything it is inside, recursively + */ +/datum/movement_detector/proc/move_react(atom/movable/mover, atom/oldloc, direction) + SIGNAL_HANDLER + + var/turf/newturf = get_turf(tracked) + + if(oldloc && !isturf(oldloc)) + var/atom/target = oldloc + while(ismovable(target)) + UnregisterSignal(target, COMSIG_MOVABLE_MOVED) + target = target.loc + if(tracked.loc != newturf) + var/atom/target = mover.loc + while(ismovable(target)) + RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(move_react), TRUE) + target = target.loc + + listener.Invoke(tracked, mover, oldloc, direction) diff --git a/code/game/atoms/_atom.dm b/code/game/atoms/_atom.dm index 564bc13ab9b..8986046fbfc 100644 --- a/code/game/atoms/_atom.dm +++ b/code/game/atoms/_atom.dm @@ -197,8 +197,6 @@ //? Misc /// What mobs are interacting with us right now, associated directly to concurrent interactions. (use defines) var/list/interacting_mobs - /// The orbiter comopnent if we're being orbited. - var/datum/component/orbiter/orbiters //? Sounds /// Default sound played on impact when damaged by a weapon / projectile / whatnot. This is usually null for default. diff --git a/code/game/atoms/atom_orbit.dm b/code/game/atoms/atom_orbit.dm new file mode 100644 index 00000000000..8eecddd952d --- /dev/null +++ b/code/game/atoms/atom_orbit.dm @@ -0,0 +1,33 @@ +/atom + ///Reference to atom being orbited + var/atom/orbit_target + ///The orbiter component, if there's anything orbiting this atom + var/datum/component/orbiter/orbiters + +/** + * Recursive getter method to return a list of all ghosts orbitting this atom + * + * This will work fine without manually passing arguments. + * * processed - The list of atoms we've already convered + * * source - Is this the atom for who we're counting up all the orbiters? + * * ignored_stealthed_admins - If TRUE, don't count admins who are stealthmoded and orbiting this + */ +/atom/proc/get_all_orbiters(list/processed, source = TRUE, ignore_stealthed_admins = TRUE) + var/list/output = list() + if(!processed) + processed = list() + else if(src in processed) + return output + + if(!source) + output += src + + processed += src + for(var/atom/atom_orbiter as anything in orbiters?.orbiter_list) + output += atom_orbiter.get_all_orbiters(processed, source = FALSE) + return output + +/mob/get_all_orbiters(list/processed, source = TRUE, ignore_stealthed_admins = TRUE) + if(!source && ignore_stealthed_admins && client?.holder?.fakekey) + return list() + return ..() diff --git a/code/game/atoms/movable/movable.dm b/code/game/atoms/movable/movable.dm index fd45ea30fea..887b73b3430 100644 --- a/code/game/atoms/movable/movable.dm +++ b/code/game/atoms/movable/movable.dm @@ -14,9 +14,6 @@ /// The image we use for our client to let them see where we are. var/image/cloaked_selfimage - /// Reference to atom being orbited. - var/atom/orbit_target - /// The orbiter component of the thing we're orbiting. var/datum/component/orbiter/orbiting ///Used for the calculate_adjacencies proc for icon smoothing. @@ -230,6 +227,10 @@ if(ai_holder) QDEL_NULL(ai_holder) + if(orbiting) + orbiting.end_orbit(src) + orbiting = null + . = ..() #ifdef CF_ATOM_TRACE_INIT_EARLY_QDEL diff --git a/code/modules/admin/callproc/callproc.dm b/code/modules/admin/callproc/callproc.dm index 756605473a8..59fe23befd3 100644 --- a/code/modules/admin/callproc/callproc.dm +++ b/code/modules/admin/callproc/callproc.dm @@ -1,3 +1,78 @@ +GLOBAL_DATUM_INIT(AdminProcCallHandler, /mob/proccall_handler, new()) +GLOBAL_PROTECT(AdminProcCallHandler) + +/// Used to handle proccalls called indirectly by an admin (e.g. tgs, circuits). +/// Has to be a mob because IsAdminAdvancedProcCall() checks usr, which is a mob variable. +/// So usr is set to this for any proccalls that don't have any usr mob/client to refer to. +/mob/proccall_handler + name = "ProcCall Handler" + desc = "If you are seeing this, tell a coder." + + var/list/callers = list() + + invisibility = INVISIBILITY_ABSTRACT + density = FALSE + +/// Adds a caller. +/mob/proccall_handler/proc/add_caller(caller_name) + callers += caller_name + name = "[initial(name)] ([callers.Join(") (")])" + +/// Removes a caller. +/mob/proccall_handler/proc/remove_caller(caller_name) + callers -= caller_name + name = "[initial(name)] ([callers.Join(") (")])" + +/mob/proccall_handler/Initialize(mapload) + . = ..() + if(GLOB.AdminProcCallHandler && GLOB.AdminProcCallHandler != src) + return INITIALIZE_HINT_QDEL + GLOB.AdminProcCallHandler = src + +/mob/proccall_handler/vv_edit_var(var_name, var_value) + if(GLOB.AdminProcCallHandler != src) + return ..() + return FALSE + +/mob/proccall_handler/vv_do_topic(list/href_list) + if(GLOB.AdminProcCallHandler != src) + return ..() + return FALSE + +/mob/proccall_handler/CanProcCall(procname) + if(GLOB.AdminProcCallHandler != src) + return ..() + return FALSE + +// Shit will break if this is allowed to be deleted +/mob/proccall_handler/Destroy(force) + if(GLOB.AdminProcCallHandler != src) + return ..() + if(!force) + stack_trace("Attempted deletion on [type] - [name], aborting.") + return QDEL_HINT_LETMELIVE + return ..() + +/** + * Handles a userless proccall, used by circuits. + * + * Arguments: + * * user - a string used to identify the user + * * target - the target to proccall on + * * proc - the proc to call + * * arguments - any arguments + */ +/proc/HandleUserlessProcCall(user, datum/target, procname, list/arguments) + if(IsAdminAdvancedProcCall()) + return + var/mob/proccall_handler/handler = GLOB.AdminProcCallHandler + handler.add_caller(user) + var/lastusr = usr + usr = handler + . = WrapAdminProcCall(target, procname, arguments) + usr = lastusr + handler.remove_caller(user) + /client/proc/callproc() set category = "Debug" set name = "Advanced ProcCall" @@ -12,18 +87,15 @@ var/targetselected = FALSE var/returnval - switch(alert("Proc owned by something?",,"Yes","No")) + switch(tgui_alert(usr,"Proc owned by something?",,list("Yes","No"))) if("Yes") targetselected = TRUE - var/list/value = vv_get_value( - default_class = VV_ATOM_REFERENCE, - classes = list(VV_ATOM_REFERENCE, VV_DATUM_REFERENCE, VV_MOB_REFERENCE, VV_CLIENT, VV_MARKED_DATUM, VV_TEXT_LOCATE, VV_PROCCALL_RETVAL), - ) + var/list/value = vv_get_value(default_class = VV_ATOM_REFERENCE, classes = list(VV_ATOM_REFERENCE, VV_DATUM_REFERENCE, VV_MOB_REFERENCE, VV_CLIENT, VV_MARKED_DATUM, VV_TEXT_LOCATE, VV_PROCCALL_RETVAL)) if (!value["class"] || !value["value"]) return target = value["value"] if(!istype(target)) - to_chat(usr, "Invalid target.") + to_chat(usr, SPAN_DANGER("Invalid target."), confidential = TRUE) return if("No") target = null @@ -43,12 +115,12 @@ if(targetselected) if(!hascall(target, procname)) - to_chat(usr, "Error: callproc(): type [target.type] has no [proctype] named [procpath].") + to_chat(usr, SPAN_WARNING("Error: callproc(): type [target.type] has no [proctype] named [procpath]."), confidential = TRUE) return else procpath = "/[proctype]/[procname]" if(!text2path(procpath)) - to_chat(usr, "Error: callproc(): [procpath] does not exist.") + to_chat(usr, SPAN_WARNING("Error: callproc(): [procpath] does not exist."), confidential = TRUE) return var/list/lst = get_callproc_args() @@ -57,24 +129,27 @@ if(targetselected) if(!target) - to_chat(usr, "Error: callproc(): owner of proc no longer exists.") + to_chat(usr, "Error: callproc(): owner of proc no longer exists.", confidential = TRUE) return var/msg = "[key_name(src)] called [target]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"]." log_admin(msg) - message_admins(msg) //Proccall announce removed. + message_admins(msg) //Proccall announce removed. admin_ticket_log(target, msg) returnval = WrapAdminProcCall(target, procname, lst) // Pass the lst as an argument list to the proc else //this currently has no hascall protection. wasn't able to get it working. log_admin("[key_name(src)] called [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"].") - message_admins("[key_name(src)] called [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"].") //Proccall announce removed. + message_admins("[key_name(src)] called [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"].") //Proccall announce removed. returnval = WrapAdminProcCall(GLOBAL_PROC, procname, lst) // Pass the lst as an argument list to the proc - //SSblackbox.record_feedback("tally", "admin_verb", 1, "Advanced ProcCall") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + // BLACKBOX_LOG_ADMIN_VERB("Advanced ProcCall") if(get_retval) - get_retval["VALUE"] = returnval + if (islist(returnval)) + get_retval += list(returnval) // Wrap to stop BYOND from concat-ing the lists + else + get_retval += returnval . = get_callproc_returnval(returnval, procname) if(.) - to_chat(usr, .) + to_chat(usr, ., confidential = TRUE) GLOBAL_VAR(AdminProcCaller) GLOBAL_PROTECT(AdminProcCaller) @@ -86,43 +161,47 @@ GLOBAL_VAR(LastAdminCalledTarget) GLOBAL_PROTECT(LastAdminCalledTarget) GLOBAL_VAR(LastAdminCalledProc) GLOBAL_PROTECT(LastAdminCalledProc) -GLOBAL_LIST_EMPTY(AdminProcCallSpamPrevention) -GLOBAL_PROTECT(AdminProcCallSpamPrevention) +/// Wrapper for proccalls where the datum is flagged as vareditted /proc/WrapAdminProcCall(datum/target, procname, list/arguments) if(target && procname == "Del") - to_chat(usr, "Calling Del() is not allowed") + to_chat(usr, "Calling Del() is not allowed", confidential = TRUE) return if(target != GLOBAL_PROC && !target.CanProcCall(procname)) - to_chat(usr, "Proccall on [target.type]/proc/[procname] is disallowed!") + to_chat(usr, "Proccall on [target.type]/proc/[procname] is disallowed!", confidential = TRUE) return var/current_caller = GLOB.AdminProcCaller - var/ckey = usr ? usr.client.ckey : GLOB.AdminProcCaller - if(!ckey) + var/user_identifier = usr ? usr.client?.ckey : GLOB.AdminProcCaller + var/is_remote_handler = usr == GLOB.AdminProcCallHandler + if(is_remote_handler) + user_identifier = GLOB.AdminProcCallHandler.name + + if(!user_identifier) CRASH("WrapAdminProcCall with no ckey: [target] [procname] [english_list(arguments)]") - if(current_caller && current_caller != ckey) - if(!GLOB.AdminProcCallSpamPrevention[ckey]) - to_chat(usr, "Another set of admin called procs are still running, your proc will be run after theirs finish.") - GLOB.AdminProcCallSpamPrevention[ckey] = TRUE - UNTIL(!GLOB.AdminProcCaller) - to_chat(usr, "Running your proc") - GLOB.AdminProcCallSpamPrevention -= ckey - else - UNTIL(!GLOB.AdminProcCaller) + + if(!is_remote_handler && current_caller && current_caller != user_identifier) + to_chat(usr, SPAN_ADMINNOTICE("Another set of admin called procs are still running. Try again later."), confidential = TRUE) + return + GLOB.LastAdminCalledProc = procname if(target != GLOBAL_PROC) GLOB.LastAdminCalledTargetRef = REF(target) - GLOB.AdminProcCaller = ckey //if this runtimes, too bad for you - ++GLOB.AdminProcCallCount - . = world.WrapAdminProcCall(target, procname, arguments) - if(--GLOB.AdminProcCallCount == 0) - GLOB.AdminProcCaller = null + + if(!is_remote_handler) + GLOB.AdminProcCaller = user_identifier //if this runtimes, too bad for you + ++GLOB.AdminProcCallCount + . = world.WrapAdminProcCall(target, procname, arguments) + GLOB.AdminProcCallCount-- + if(GLOB.AdminProcCallCount == 0) + GLOB.AdminProcCaller = null + else + . = world.WrapAdminProcCall(target, procname, arguments) //adv proc call this, ya nerds /world/proc/WrapAdminProcCall(datum/target, procname, list/arguments) if(target == GLOBAL_PROC) - return call(text2path("/proc/[procname]"))(arglist(arguments)) + return call("/proc/[procname]")(arglist(arguments)) else if(target != world) return call(target, procname)(arglist(arguments)) else @@ -135,7 +214,7 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention) return usr && usr.client && GLOB.AdminProcCaller == usr.client.ckey #endif -/client/proc/callproc_datum(datum/A as null|area|mob|obj|turf) +/client/proc/callproc_datum(datum/thing as null|area|mob|obj|turf) set category = "Debug" set name = "Atom ProcCall" set waitfor = 0 @@ -143,29 +222,29 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention) if(!check_rights(R_DEBUG)) return - var/procname = input("Proc name, eg: fake_blood","Proc:", null) as text|null + var/procname = input(usr, "Proc name, eg: fake_blood","Proc:", null) as text|null if(!procname) return - if(!hascall(A,procname)) - to_chat(usr, "Error: callproc_datum(): type [A.type] has no proc named [procname].") + if(!hascall(thing, procname)) + to_chat(usr, "Error: callproc_datum(): type [thing.type] has no proc named [procname].", confidential = TRUE) return var/list/lst = get_callproc_args() if(!lst) return - if(!A || !is_valid_src(A)) - to_chat(usr, "Error: callproc_datum(): owner of proc no longer exists.") + if(!thing || !is_valid_src(thing)) + to_chat(usr, SPAN_WARNING("Error: callproc_datum(): owner of proc no longer exists."), confidential = TRUE) return - log_admin("[key_name(src)] called [A]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"].") - var/msg = "[key_name(src)] called [A]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"]." + log_admin("[key_name(usr)] called [thing]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"].") + var/msg = "[key_name(usr)] called [thing]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"]." message_admins(msg) - admin_ticket_log(A, msg) - //SSblackbox.record_feedback("tally", "admin_verb", 1, "Atom ProcCall") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + admin_ticket_log(thing, msg) + // BLACKBOX_LOG_ADMIN_VERB("Atom ProcCall") - var/returnval = WrapAdminProcCall(A, procname, lst) // Pass the lst as an argument list to the proc + var/returnval = WrapAdminProcCall(thing, procname, lst) // Pass the lst as an argument list to the proc . = get_callproc_returnval(returnval,procname) if(.) - to_chat(usr, .) + to_chat(usr, ., confidential = TRUE) /client/proc/get_callproc_args() var/argnum = input("Number of arguments","Number:",0) as num|null diff --git a/code/modules/mob/observer/dead/orbit.dm b/code/modules/mob/observer/dead/orbit.dm index c4a92c48a84..c06bcc20d5f 100644 --- a/code/modules/mob/observer/dead/orbit.dm +++ b/code/modules/mob/observer/dead/orbit.dm @@ -70,7 +70,7 @@ GLOBAL_DATUM_INIT(orbit_menu, /datum/orbit_menu, new) for(var/mobthing in sortmobs()) var/list/serialized = list() var/mob/mob_poi = mobthing - // var/number_of_orbiters = length(mob_poi.get_all_orbiters()) + var/number_of_orbiters = length(mob_poi.get_all_orbiters()) // ignore magical objects if (isEye(mob_poi) || isvoice(mob_poi)) @@ -85,12 +85,15 @@ GLOBAL_DATUM_INIT(orbit_menu, /datum/orbit_menu, new) serialized["ref"] = REF(mob_poi) serialized["full_name"] = name - // if(number_of_orbiters) - // serialized["orbiters"] = number_of_orbiters + if(number_of_orbiters) + serialized["orbiters"] = number_of_orbiters + + if (is_admin) + serialized["ckey"] = mob_poi.ckey if(isobserver(mob_poi)) - // TODO what does this mean? invismin??? - if(mob_poi.invisibility >= INVISIBILITY_MAXIMUM) + // TODO remove this, go use TRAIT_ORBITING_FORBIDDEN + if(mob_poi.invisibility >= INVISIBILITY_MAXIMUM) // invismin continue ghosts += list(serialized) continue @@ -110,9 +113,6 @@ GLOBAL_DATUM_INIT(orbit_menu, /datum/orbit_menu, new) serialized["client"] = !!mob_poi.client serialized["name"] = mob_poi.real_name - if (is_admin) - serialized["ckey"] = mob_poi.ckey - if(isliving(mob_poi)) serialized += get_living_data(mob_poi) @@ -139,11 +139,6 @@ GLOBAL_DATUM_INIT(orbit_menu, /datum/orbit_menu, new) "can_observe" = FALSE, // autoobserve ) -// do we need it immediately? nah -// /datum/orbit_menu/ui_asset_injection(datum/tgui/ui, list/immediate, list/deferred) -// immediate += /datum/asset_pack/simple/orbit -// return ..() - /// Shows the UI to the specified user. /datum/orbit_menu/proc/show(mob/user) ui_interact(user) @@ -198,7 +193,7 @@ GLOBAL_DATUM_INIT(orbit_menu, /datum/orbit_menu, new) var/obj/item/card/id/id_card = player.GetIdCard() serialized["job"] = id_card?.assignment || id_card?.rank - serialized["icon"] = "hudunknown" //id_card? + serialized["icon"] = ckey(id_card?.GetJobName() || "unknown") // ckey for formatting var/job = player.mind?.assigned_role if (isnull(job)) @@ -233,6 +228,24 @@ GLOBAL_DATUM_INIT(orbit_menu, /datum/orbit_menu, new) return list(misc, critical) + // Display the holder if its a nuke disk + if(istype(atom_poi, /obj/item/disk/nuclear)) + var/obj/item/disk/nuclear/disk = atom_poi + var/mob/holder = disk.pulledby || get(disk, /mob) + misc["extra"] = "Location: [holder?.real_name || "Unsecured"]" + + return list(misc, critical) + + // Display singuloths if they exist + if(istype(atom_poi, /obj/singularity)) + var/obj/singularity/singulo = atom_poi + misc["extra"] = "Energy: [round(singulo.energy)]" + + if(singulo.current_size > 2) + critical = TRUE + + return list(misc, critical) + // Display the shuttle if (istype(atom_poi, /obj/overmap/entity/visitable/ship)) var/obj/overmap/entity/visitable/ship/shuttle = atom_poi diff --git a/tgui/packages/tgui/interfaces/Orbit/JobIcon.tsx b/tgui/packages/tgui/interfaces/Orbit/JobIcon.tsx index ed2046805b3..5d4c7fbebf3 100644 --- a/tgui/packages/tgui/interfaces/Orbit/JobIcon.tsx +++ b/tgui/packages/tgui/interfaces/Orbit/JobIcon.tsx @@ -1,7 +1,7 @@ import { DmIcon, Icon } from 'tgui-core/components'; -// import { JOB2ICON } from '../common/JobToIcon'; -import { Antagonist, Observable } from './types'; +import { JOB2ICON } from '../common/JobToIcon'; +import type { Antagonist, Observable } from './types'; type Props = { item: Observable | Antagonist; @@ -43,8 +43,7 @@ export function JobIcon(props: Props) { return (
{icon === 'borg' ? ( - // - + ) : ( {filteredSection.map((item) => ( diff --git a/tgui/packages/tgui/interfaces/Orbit/OrbitContent.tsx b/tgui/packages/tgui/interfaces/Orbit/OrbitContent.tsx index 431e15d32c0..1a58b2eaa21 100644 --- a/tgui/packages/tgui/interfaces/Orbit/OrbitContent.tsx +++ b/tgui/packages/tgui/interfaces/Orbit/OrbitContent.tsx @@ -11,7 +11,7 @@ import { useBackend } from '../../backend'; import { ANTAG2COLOR } from './constants'; import { getAntagCategories } from './helpers'; import { OrbitCollapsible } from './OrbitCollapsible'; -import { AntagGroup, Observable, OrbitData } from './types'; +import type { AntagGroup, Observable, OrbitData } from './types'; type ContentSection = { content: Observable[]; diff --git a/tgui/packages/tgui/interfaces/Orbit/OrbitItem.tsx b/tgui/packages/tgui/interfaces/Orbit/OrbitItem.tsx index bfe8e96feeb..792a5351811 100644 --- a/tgui/packages/tgui/interfaces/Orbit/OrbitItem.tsx +++ b/tgui/packages/tgui/interfaces/Orbit/OrbitItem.tsx @@ -5,7 +5,7 @@ import { useBackend } from '../../backend'; import { getDisplayColor, getDisplayName } from './helpers'; import { JobIcon } from './JobIcon'; import { OrbitTooltip } from './OrbitTooltip'; -import { Antagonist, Observable, OrbitData, ViewMode } from './types'; +import type { Antagonist, Observable, OrbitData, ViewMode } from './types'; type Props = { item: Observable | Antagonist; diff --git a/tgui/packages/tgui/interfaces/Orbit/OrbitSearchBar.tsx b/tgui/packages/tgui/interfaces/Orbit/OrbitSearchBar.tsx index 8890f19cc83..3c25a09e294 100644 --- a/tgui/packages/tgui/interfaces/Orbit/OrbitSearchBar.tsx +++ b/tgui/packages/tgui/interfaces/Orbit/OrbitSearchBar.tsx @@ -5,7 +5,7 @@ import { useBackend } from '../../backend'; import { OrbitContext } from '.'; import { VIEWMODE } from './constants'; import { isJobCkeyOrNameMatch, sortByOrbiters } from './helpers'; -import { OrbitData } from './types'; +import type { OrbitData } from './types'; /** Search bar for the orbit ui. Has a few buttons to switch between view modes and auto-observe */ export function OrbitSearchBar(props) { diff --git a/tgui/packages/tgui/interfaces/Orbit/OrbitTooltip.tsx b/tgui/packages/tgui/interfaces/Orbit/OrbitTooltip.tsx index dbccf69c8fb..86c84bf0e6d 100644 --- a/tgui/packages/tgui/interfaces/Orbit/OrbitTooltip.tsx +++ b/tgui/packages/tgui/interfaces/Orbit/OrbitTooltip.tsx @@ -1,6 +1,6 @@ import { LabeledList, NoticeBox } from 'tgui-core/components'; -import { Antagonist, Observable } from './types'; +import type { Antagonist, Observable } from './types'; type Props = { item: Observable | Antagonist; diff --git a/tgui/packages/tgui/interfaces/Orbit/constants.ts b/tgui/packages/tgui/interfaces/Orbit/constants.ts index a9d9e62643b..fe7cd829978 100644 --- a/tgui/packages/tgui/interfaces/Orbit/constants.ts +++ b/tgui/packages/tgui/interfaces/Orbit/constants.ts @@ -22,11 +22,11 @@ type Department = { export const DEPARTMENT2COLOR: Record = { cargo: { color: 'brown', - trims: ['Bitrunner', 'Cargo Technician', 'Shaft Miner', 'Quartermaster'], + trims: ['Cargo Technician', 'Shaft Miner', 'Quartermaster'], }, command: { color: 'blue', - trims: ['Captain', 'Head of Personnel'], + trims: ['Facility Director', 'Captain', 'Head of Personnel'], }, engineering: { color: 'orange', diff --git a/tgui/packages/tgui/interfaces/Orbit/helpers.ts b/tgui/packages/tgui/interfaces/Orbit/helpers.ts index f49dbfb890b..e67d58ff5fe 100644 --- a/tgui/packages/tgui/interfaces/Orbit/helpers.ts +++ b/tgui/packages/tgui/interfaces/Orbit/helpers.ts @@ -1,5 +1,5 @@ import { DEPARTMENT2COLOR, HEALTH, THREAT, VIEWMODE } from './constants'; -import { AntagGroup, Antagonist, Observable, ViewMode } from './types'; +import type { AntagGroup, Antagonist, Observable, ViewMode } from './types'; /** Return a map of strings with each antag in its antag_category */ export function getAntagCategories(antagonists: Antagonist[]): AntagGroup[] { diff --git a/tgui/packages/tgui/interfaces/Orbit/types.ts b/tgui/packages/tgui/interfaces/Orbit/types.ts index 3fad9e1bfc5..18160f3b543 100644 --- a/tgui/packages/tgui/interfaces/Orbit/types.ts +++ b/tgui/packages/tgui/interfaces/Orbit/types.ts @@ -1,6 +1,6 @@ -import { BooleanLike } from 'tgui-core/react'; +import type { BooleanLike } from 'tgui-core/react'; -import { VIEWMODE } from './constants'; +import type { VIEWMODE } from './constants'; export type Antagonist = Observable & { antag: string; diff --git a/tgui/packages/tgui/interfaces/common/JobToIcon.ts b/tgui/packages/tgui/interfaces/common/JobToIcon.ts new file mode 100644 index 00000000000..87dca7f7896 --- /dev/null +++ b/tgui/packages/tgui/interfaces/common/JobToIcon.ts @@ -0,0 +1,59 @@ +export type AvailableJob = keyof typeof JOB2ICON; + +/** Icon map of jobs to their fontawesome5 (free) counterpart. */ +export const JOB2ICON = { + AI: 'eye', + Assistant: 'toolbox', + 'Atmospheric Technician': 'fan', + Bartender: 'cocktail', + 'Bit Avatar': 'code', + Botanist: 'seedling', + 'Bridge Assistant': 'building-shield', + Captain: 'crown', + 'Facility Director': 'crown', + 'Cargo Gorilla': 'paw', + 'Cargo Technician': 'box', + 'CentCom Commander': 'star', + 'CentCom Head Intern': 'pen-fancy', + 'CentCom Intern': 'pen-alt', + 'CentCom Official': 'medal', + Chaplain: 'cross', + Chef: 'utensils', + Chemist: 'prescription-bottle', + 'Chief Engineer': 'user-astronaut', + 'Chief Medical Officer': 'user-md', + Clown: 'face-grin-tears', + Cook: 'utensils', + Coroner: 'skull', + Curator: 'book', + 'Cyber Police': 'qrcode', + Cyborg: 'robot', + Detective: 'user-secret', + Geneticist: 'dna', + 'Head of Personnel': 'dog', + 'Head of Security': 'user-shield', + 'Big Brother': 'eye', + Janitor: 'soap', + Lawyer: 'gavel', + 'Medical Doctor': 'staff-snake', + Mime: 'comment-slash', + Paramedic: 'truck-medical', + 'Personal AI': 'mobile-alt', + Prisoner: 'lock', + Psychologist: 'brain', + Quartermaster: 'sack-dollar', + 'Research Director': 'user-graduate', + Roboticist: 'battery-half', + Scientist: 'flask', + 'Security Officer (Cargo)': 'shield-halved', + 'Security Officer (Engineering)': 'shield-halved', + 'Security Officer (Medical)': 'shield-halved', + 'Security Officer (Science)': 'shield-halved', + 'Security Officer': 'shield-halved', + 'Shaft Miner': 'digging', + 'Station Engineer': 'gears', + 'Syndicate Operative': 'dragon', + 'Veteran Security Advisor': 'ribbon', + 'Pun Pun': 'paw', + Warden: 'handcuffs', +} as const;