diff --git a/code/__DEFINES/_subsystems.dm b/code/__DEFINES/_subsystems.dm index da73dede763f8..99d7ee1143da6 100755 --- a/code/__DEFINES/_subsystems.dm +++ b/code/__DEFINES/_subsystems.dm @@ -110,6 +110,7 @@ #define INIT_ORDER_PATH -50 #define INIT_ORDER_EXPLOSIONS -69 #define INIT_ORDER_EXCAVATION -78 +#define INIT_ORDER_STATPANELS -97 #define INIT_ORDER_CHAT -100 //Should be last to ensure chat remains smooth during init. // Subsystem fire priority, from lowest to highest priority @@ -140,6 +141,7 @@ #define FIRE_PRIORITY_MOBS 100 #define FIRE_PRIORITY_TGUI 110 #define FIRE_PRIORITY_TICKER 200 +#define FIRE_PRIORITY_STATPANEL 390 #define FIRE_PRIORITY_CHAT 400 #define FIRE_PRIORITY_LOOPINGSOUND 405 #define FIRE_PRIORITY_RUNECHAT 410 diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 644e5bec93637..2aabb7d0be671 100755 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -155,6 +155,10 @@ #define COMSIG_CLIENT_MOUSEDRAG "client_mousedrag" //from base of client/MouseUp(): (/client, object, location, control, params) #define COMSIG_CLIENT_DISCONNECTED "client_disconnecred" //from base of /client/Destroy(): (/client) #define COMSIG_CLIENT_PREFERENCES_UIACTED "client_preferences_uiacted" //called after preferences have been updated for this client after /datum/preferences/ui_act has completed +/// Called after one or more verbs are added: (list of verbs added) +#define COMSIG_CLIENT_VERB_ADDED "client_verb_added" +/// Called after one or more verbs are removed: (list of verbs added) +#define COMSIG_CLIENT_VERB_REMOVED "client_verb_removed" // /atom signals #define COMSIG_ATOM_ATTACKBY "atom_attackby" //from base of atom/attackby(): (/obj/item, /mob/living) @@ -451,6 +455,8 @@ #define COMSIG_RANGED_SCATTER_MOD_CHANGED "ranged_scatter_mod_changed" #define COMSIG_MOB_SKILLS_CHANGED "mob_skills_changed" #define COMSIG_MOB_SHOCK_STAGE_CHANGED "mob_shock_stage_changed" +/// from mob/get_status_tab_items(): (list/items) +#define COMSIG_MOB_GET_STATUS_TAB_ITEMS "mob_get_status_tab_items" //mob/dead/observer #define COMSIG_OBSERVER_CLICKON "observer_clickon" //from mob/dead/observer/ClickOn(): (atom/A, params) @@ -915,6 +921,8 @@ // /datum/action signals #define COMSIG_ACTION_TRIGGER "action_trigger" //from base of datum/action/proc/Trigger(): (datum/action) +/// From base of /datum/action/cooldown/proc/set_statpanel_format(): (list/stat_panel_data) +#define COMSIG_ACTION_SET_STATPANEL "ability_set_statpanel" #define COMPONENT_ACTION_BLOCK_TRIGGER (1<<0) //Signals for CIC orders diff --git a/code/__DEFINES/procpath.dm b/code/__DEFINES/procpath.dm index 81087555e9806..642ca3eab6cc8 100644 --- a/code/__DEFINES/procpath.dm +++ b/code/__DEFINES/procpath.dm @@ -15,10 +15,12 @@ // below, their accesses are optimized away. /// A text string of the verb's name. - var/name + var/name as text /// The verb's help text or description. - var/desc + var/desc as text /// The category or tab the verb will appear in. - var/category + var/category as text /// Only clients/mobs with `see_invisibility` higher can use the verb. - var/invisibility + var/invisibility as num + /// Whether or not the verb appears in statpanel and commandbar when you press space + var/hidden as num diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm index b175474bce61c..2b086c5ff90fc 100644 --- a/code/__HELPERS/icons.dm +++ b/code/__HELPERS/icons.dm @@ -1055,7 +1055,7 @@ ColorTone(rgb, tone) //Costlier version of icon2html() that uses getFlatIcon() to account for overlays, underlays, etc. Use with extreme moderation, ESPECIALLY on mobs. -/proc/costly_icon2html(thing, target) +/proc/costly_icon2html(thing, target, sourceonly = FALSE) if(!thing) return @@ -1063,7 +1063,7 @@ ColorTone(rgb, tone) return icon2html(thing, target) var/icon/I = getFlatIcon(thing) - return icon2html(I, target) + return icon2html(I, target, sourceonly = sourceonly) //For creating consistent icons for human looking simple animals diff --git a/code/__HELPERS/verbs.dm b/code/__HELPERS/verbs.dm new file mode 100644 index 0000000000000..badd3c6727ae1 --- /dev/null +++ b/code/__HELPERS/verbs.dm @@ -0,0 +1,98 @@ +/** + * handles adding verbs and updating the stat panel browser + * + * pass the verb type path to this instead of adding it directly to verbs so the statpanel can update + * Arguments: + * * target - Who the verb is being added to, client or mob typepath + * * verb - typepath to a verb, or a list of verbs, supports lists of lists + */ +/proc/add_verb(client/target, verb_or_list_to_add) + if(!target) + CRASH("add_verb called without a target") + if(IsAdminAdvancedProcCall()) + return + var/mob/mob_target = null + + if(ismob(target)) + mob_target = target + target = mob_target.client + else if(!istype(target, /client)) + CRASH("add_verb called on a non-mob and non-client") + var/list/verbs_list = list() + if(!islist(verb_or_list_to_add)) + verbs_list += verb_or_list_to_add + else + var/list/verb_listref = verb_or_list_to_add + var/list/elements_to_process = verb_listref.Copy() + while(length(elements_to_process)) + var/element_or_list = elements_to_process[length(elements_to_process)] //Last element + elements_to_process.len-- + if(islist(element_or_list)) + elements_to_process += element_or_list //list/a += list/b adds the contents of b into a, not the reference to the list itself + else + verbs_list += element_or_list + + if(mob_target) + mob_target.verbs += verbs_list + if(!target) + return //Our work is done. + else + target.verbs += verbs_list + + var/list/output_list = list() + for(var/thing in verbs_list) + var/procpath/verb_to_add = thing + output_list[++output_list.len] = list(verb_to_add.category, verb_to_add.name) + + target.stat_panel.send_message("add_verb_list", output_list) + + SEND_SIGNAL(target, COMSIG_CLIENT_VERB_ADDED, verbs_list) + +/** + * handles removing verb and sending it to browser to update, use this for removing verbs + * + * pass the verb type path to this instead of removing it from verbs so the statpanel can update + * Arguments: + * * target - Who the verb is being removed from, client or mob typepath + * * verb - typepath to a verb, or a list of verbs, supports lists of lists + */ +/proc/remove_verb(client/target, verb_or_list_to_remove) + if(IsAdminAdvancedProcCall()) + return + + var/mob/mob_target = null + if(ismob(target)) + mob_target = target + target = mob_target.client + else if(!istype(target, /client)) + CRASH("remove_verb called on a non-mob and non-client") + + var/list/verbs_list = list() + if(!islist(verb_or_list_to_remove)) + verbs_list += verb_or_list_to_remove + else + var/list/verb_listref = verb_or_list_to_remove + var/list/elements_to_process = verb_listref.Copy() + while(length(elements_to_process)) + var/element_or_list = elements_to_process[length(elements_to_process)] //Last element + elements_to_process.len-- + if(islist(element_or_list)) + elements_to_process += element_or_list //list/a += list/b adds the contents of b into a, not the reference to the list itself + else + verbs_list += element_or_list + + if(mob_target) + mob_target.verbs -= verbs_list + if(!target) + return //Our work is done. + else + target.verbs -= verbs_list + + var/list/output_list = list() + for(var/thing in verbs_list) + var/procpath/verb_to_remove = thing + output_list[++output_list.len] = list(verb_to_remove.category, verb_to_remove.name) + + target.stat_panel.send_message("remove_verb_list", output_list) + + SEND_SIGNAL(target, COMSIG_CLIENT_VERB_REMOVED, verbs_list) diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index ce34ebd0b95a2..4ca49e2fa4e86 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -465,10 +465,9 @@ if(selected_ability.target_flags & flagname && !istype(A, typepath)){\ /atom/proc/AltClick(mob/user) SEND_SIGNAL(src, COMSIG_CLICK_ALT, user) - var/turf/examined_turf = get_turf(src) - if(examined_turf && user.TurfAdjacent(examined_turf)) - user.listed_turf = examined_turf - user.client.statpanel = examined_turf.name + var/turf/T = get_turf(src) + if(T && (isturf(loc) || isturf(src)) && user.TurfAdjacent(T)) + user.set_listed_turf(T) return TRUE diff --git a/code/controllers/configuration/configuration.dm b/code/controllers/configuration/configuration.dm index a6d3b92ccea14..767ce41b7ba99 100644 --- a/code/controllers/configuration/configuration.dm +++ b/code/controllers/configuration/configuration.dm @@ -231,10 +231,9 @@ return !(var_name in banned_edits) && ..() -/datum/controller/configuration/stat_entry() - if(!statclick) - statclick = new/obj/effect/statclick/debug(null, "Debug", src) - stat("[name]:", statclick) +/datum/controller/configuration/stat_entry(msg) + msg = "Edit" + return msg /datum/controller/configuration/proc/Get(entry_type) diff --git a/code/controllers/controller.dm b/code/controllers/controller.dm index c9d5f1e5650c6..1f0a01a072cc5 100644 --- a/code/controllers/controller.dm +++ b/code/controllers/controller.dm @@ -16,4 +16,4 @@ /datum/controller/proc/Recover() -/datum/controller/proc/stat_entry() +/datum/controller/proc/stat_entry(msg) diff --git a/code/controllers/failsafe.dm b/code/controllers/failsafe.dm index 45896b1546e4e..07b5650486809 100644 --- a/code/controllers/failsafe.dm +++ b/code/controllers/failsafe.dm @@ -176,8 +176,6 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe) /datum/controller/failsafe/proc/defcon_pretty() return defcon -/datum/controller/failsafe/stat_entry() - if(!statclick) - statclick = new/obj/effect/statclick/debug(null, "Initializing...", src) - - stat("Failsafe Controller:", statclick.update("Defcon: [defcon_pretty()] (Interval: [Failsafe.processing_interval] | Iteration: [Failsafe.master_iteration])")) +/datum/controller/failsafe/stat_entry(msg) + msg = "Defcon: [defcon_pretty()] (Interval: [Failsafe.processing_interval] | Iteration: [Failsafe.master_iteration])" + return msg diff --git a/code/controllers/globals.dm b/code/controllers/globals.dm index 756f8f04cbde9..3108a9ce30169 100644 --- a/code/controllers/globals.dm +++ b/code/controllers/globals.dm @@ -25,11 +25,9 @@ GLOBAL_REAL(GLOB, /datum/controller/global_vars) SHOULD_CALL_PARENT(FALSE) return QDEL_HINT_IWILLGC -/datum/controller/global_vars/stat_entry() - if(!statclick) - statclick = new/obj/effect/statclick/debug(null, "Initializing...", src) - - stat("Globals:", statclick.update("Debug")) +/datum/controller/global_vars/stat_entry(msg) + msg = "Edit" + return msg /datum/controller/global_vars/vv_edit_var(var_name, var_value) if(gvars_datum_protected_varlist[var_name]) diff --git a/code/controllers/master.dm b/code/controllers/master.dm index 56e96a466cbde..4827cb30cede1 100644 --- a/code/controllers/master.dm +++ b/code/controllers/master.dm @@ -784,11 +784,9 @@ GLOBAL_REAL(Master, /datum/controller/master) = new /datum/controller/master/stat_entry() - if(!statclick) - statclick = new/obj/effect/statclick/debug(null, "Initializing...", src) - - stat("Byond:", "(FPS:[world.fps]) (TickCount:[world.time/world.tick_lag]) (TickDrift:[round(Master.tickdrift,1)]([round((Master.tickdrift/(world.time/world.tick_lag))*100,0.1)]%)) (Internal Tick Usage: [round(MAPTICK_LAST_INTERNAL_TICK_USAGE,0.1)]%)") - stat("Master Controller:", statclick.update("(TickRate:[Master.processing]) (Iteration:[Master.iteration]) (TickLimit: [round(Master.current_ticklimit, 0.1)])")) +/datum/controller/master/stat_entry(msg) + msg = "(TickRate:[Master.processing]) (Iteration:[Master.iteration]) (TickLimit: [round(Master.current_ticklimit, 0.1)])" + return msg /datum/controller/master/StartLoadingMap() diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm index 5b75e1377555e..8c458073221eb 100644 --- a/code/controllers/subsystem.dm +++ b/code/controllers/subsystem.dm @@ -198,21 +198,11 @@ //hook for printing stats to the "MC" statuspanel for admins to see performance and related stats etc. /datum/controller/subsystem/stat_entry(msg) - if(!statclick) - statclick = new/obj/effect/statclick/debug(null, "Initializing...", src) - - - if(can_fire && !(SS_NO_FIRE & flags) && init_stage <= Master.init_stage_completed) msg = "[round(cost,1)]ms|[round(tick_usage,1)]%([round(tick_overrun,1)]%)|[round(ticks,0.1)]\t[msg]" else msg = "OFFLINE\t[msg]" - - var/title = name - if (can_fire) - title = "\[[state_letter()]][title]" - - stat(title, statclick.update(msg)) + return msg /datum/controller/subsystem/proc/state_letter() switch (state) diff --git a/code/controllers/subsystem/advanced_pathfinding.dm b/code/controllers/subsystem/advanced_pathfinding.dm index 79b611f15e419..0564b0650b076 100644 --- a/code/controllers/subsystem/advanced_pathfinding.dm +++ b/code/controllers/subsystem/advanced_pathfinding.dm @@ -60,8 +60,10 @@ SUBSYSTEM_DEF(advanced_pathfinding) if (MC_TICK_CHECK) return -/datum/controller/subsystem/advanced_pathfinding/stat_entry() - ..("Node pathfinding : [length(node_pathfinding_to_do)] || Tile pathfinding : [length(tile_pathfinding_to_do)]") +/datum/controller/subsystem/advanced_pathfinding/stat_entry(msg) + msg = "Node pathfinding : [length(node_pathfinding_to_do)] || Tile pathfinding : [length(tile_pathfinding_to_do)]" + return ..() + #define NODE_PATHING "node_pathing" //Looking through the network of nodes the best node path #define TILE_PATHING "tile_pathing" //Looking the best tile path diff --git a/code/controllers/subsystem/autofire.dm b/code/controllers/subsystem/autofire.dm index 30e0c03bee14b..f4afb5cb4cd63 100644 --- a/code/controllers/subsystem/autofire.dm +++ b/code/controllers/subsystem/autofire.dm @@ -39,7 +39,8 @@ SUBSYSTEM_DEF(automatedfire) head_offset = world.time bucket_resolution = world.tick_lag -/datum/controller/subsystem/automatedfire/stat_entry(msg = "ActShooters:[shooter_count]") +/datum/controller/subsystem/automatedfire/stat_entry(msg) + msg = "ActShooters:[shooter_count]" return ..() /datum/controller/subsystem/automatedfire/fire(resumed = FALSE) diff --git a/code/controllers/subsystem/events.dm b/code/controllers/subsystem/events.dm index a584409f73e2f..a4d76f8e2f73e 100644 --- a/code/controllers/subsystem/events.dm +++ b/code/controllers/subsystem/events.dm @@ -97,7 +97,7 @@ SUBSYSTEM_DEF(events) //aka Badmin Central /client/proc/force_event() set name = "Trigger Event" - set category = "Fun" + set category = "Admin.Fun" if(!holder ||!check_rights(R_FUN)) return @@ -120,7 +120,7 @@ SUBSYSTEM_DEF(events) /client/proc/toggle_events() set name = "Toggle Events Subsystem" - set category = "Fun" + set category = "Admin.Fun" if(!holder ||!check_rights(R_FUN)) return diff --git a/code/controllers/subsystem/explosions.dm b/code/controllers/subsystem/explosions.dm index 549c15267849c..af289141309c1 100644 --- a/code/controllers/subsystem/explosions.dm +++ b/code/controllers/subsystem/explosions.dm @@ -63,7 +63,7 @@ SUBSYSTEM_DEF(explosions) msg += "TO:[length(throwTurf)]" msg += "} " - ..(msg) + return ..() #define SSEX_TURF "turf" diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm index 2219b3593b041..42d66084d2200 100644 --- a/code/controllers/subsystem/garbage.dm +++ b/code/controllers/subsystem/garbage.dm @@ -77,7 +77,7 @@ SUBSYSTEM_DEF(garbage) msg += "TGR:[round((totalgcs/(totaldels+totalgcs))*100, 0.01)]%" msg += " P:[pass_counts.Join(",")]" msg += "|F:[fail_counts.Join(",")]" - ..(msg) + return ..() /datum/controller/subsystem/garbage/Shutdown() //Adds the del() log to the qdel log file diff --git a/code/controllers/subsystem/idlenpcpool.dm b/code/controllers/subsystem/idlenpcpool.dm index 8fa8dc4dc73ac..2a36583d81d50 100644 --- a/code/controllers/subsystem/idlenpcpool.dm +++ b/code/controllers/subsystem/idlenpcpool.dm @@ -9,10 +9,11 @@ SUBSYSTEM_DEF(idlenpcpool) var/static/list/idle_mobs_by_zlevel[][] -/datum/controller/subsystem/idlenpcpool/stat_entry() +/datum/controller/subsystem/idlenpcpool/stat_entry(msg) var/list/idlelist = GLOB.simple_animals[AI_IDLE] var/list/zlist = GLOB.simple_animals[AI_Z_OFF] - ..("IdleNPCS:[length(idlelist)]|Z:[length(zlist)]") + msg = "IdleNPCS:[length(idlelist)]|Z:[length(zlist)]" + return ..() /datum/controller/subsystem/idlenpcpool/proc/MaxZChanged() diff --git a/code/controllers/subsystem/lighting.dm b/code/controllers/subsystem/lighting.dm index 900d9f444b810..60f17bac1842c 100644 --- a/code/controllers/subsystem/lighting.dm +++ b/code/controllers/subsystem/lighting.dm @@ -26,8 +26,9 @@ SUBSYSTEM_DEF(lighting) return SS_INIT_SUCCESS -/datum/controller/subsystem/lighting/stat_entry() - . = ..("ShCalcs:[total_shadow_calculations]|SourcQ:[length(static_sources_queue)]|CcornQ:[length(corners_queue)]|ObjQ:[length(objects_queue)]|HybrQ:[length(mask_queue)]") +/datum/controller/subsystem/lighting/stat_entry(msg) + msg = "ShCalcs:[total_shadow_calculations]|SourcQ:[length(static_sources_queue)]|CcornQ:[length(corners_queue)]|ObjQ:[length(objects_queue)]|HybrQ:[length(mask_queue)]" + return ..() /datum/controller/subsystem/lighting/fire(resumed, init_tick_checks) MC_SPLIT_TICK_INIT(3) diff --git a/code/controllers/subsystem/machines.dm b/code/controllers/subsystem/machines.dm index 506d74f5cb7b9..2a3ac60cb664b 100644 --- a/code/controllers/subsystem/machines.dm +++ b/code/controllers/subsystem/machines.dm @@ -25,8 +25,9 @@ SUBSYSTEM_DEF(machines) NewPN.add_cable(PC) propagate_network(PC,PC.powernet) -/datum/controller/subsystem/machines/stat_entry() - ..("PN:[length(powernets)]|PM:[length(processing)]") +/datum/controller/subsystem/machines/stat_entry(msg) + msg = "PM:[length(processing)]|PN:[length(powernets)]" + return ..() /datum/controller/subsystem/machines/fire(resumed = FALSE) if (!resumed) diff --git a/code/controllers/subsystem/mobs.dm b/code/controllers/subsystem/mobs.dm index e5d9f81320038..1b5b616344997 100644 --- a/code/controllers/subsystem/mobs.dm +++ b/code/controllers/subsystem/mobs.dm @@ -15,8 +15,9 @@ SUBSYSTEM_DEF(mobs) var/list/list/crates = list(list(), list(), list(), list()) var/crate = 1 -/datum/controller/subsystem/mobs/stat_entry() - ..("P:[length(processing)]") +/datum/controller/subsystem/mobs/stat_entry(msg) + msg = "P:[length(GLOB.mob_living_list)]" + return ..() /datum/controller/subsystem/mobs/proc/stop_processing(mob/living/L) if(!CHECK_BITFIELD(L.datum_flags, DF_ISPROCESSING)) diff --git a/code/controllers/subsystem/npcpool.dm b/code/controllers/subsystem/npcpool.dm index da298bccea747..06078e17b4d66 100644 --- a/code/controllers/subsystem/npcpool.dm +++ b/code/controllers/subsystem/npcpool.dm @@ -7,9 +7,10 @@ SUBSYSTEM_DEF(npcpool) var/list/currentrun = list() -/datum/controller/subsystem/npcpool/stat_entry() +/datum/controller/subsystem/npcpool/stat_entry(msg) var/list/activelist = GLOB.simple_animals[AI_ON] - ..("NPCS:[length(activelist)]") + msg = "NPCS:[length(activelist)]" + return ..() /datum/controller/subsystem/npcpool/fire(resumed = FALSE) diff --git a/code/controllers/subsystem/processing/obj_tab_items.dm b/code/controllers/subsystem/processing/obj_tab_items.dm new file mode 100644 index 0000000000000..53786daf0117e --- /dev/null +++ b/code/controllers/subsystem/processing/obj_tab_items.dm @@ -0,0 +1,24 @@ +PROCESSING_SUBSYSTEM_DEF(obj_tab_items) + name = "Obj Tab Items" + flags = SS_NO_INIT + runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT + wait = 0.1 SECONDS + +// I know this is mostly copypasta, but I want to change the processing logic +// Sorry bestie :( +/datum/controller/subsystem/processing/obj_tab_items/fire(resumed = FALSE) + if (!resumed) + currentrun = processing.Copy() + //cache for sanic speed (lists are references anyways) + var/list/current_run = currentrun + + while(current_run.len) + var/datum/thing = current_run[current_run.len] + if(QDELETED(thing)) + processing -= thing + else if(thing.process(wait * 0.1) == PROCESS_KILL) + // fully stop so that a future START_PROCESSING will work + STOP_PROCESSING(src, thing) + if (MC_TICK_CHECK) + return + current_run.len-- diff --git a/code/controllers/subsystem/processing/processing.dm b/code/controllers/subsystem/processing/processing.dm index 79605cc57ea39..1744f21517367 100644 --- a/code/controllers/subsystem/processing/processing.dm +++ b/code/controllers/subsystem/processing/processing.dm @@ -10,8 +10,9 @@ SUBSYSTEM_DEF(processing) var/list/processing = list() var/list/currentrun = list() -/datum/controller/subsystem/processing/stat_entry() - ..("[stat_tag]:[length(processing)]") +/datum/controller/subsystem/processing/stat_entry(msg) + msg = "[stat_tag]:[length(processing)]" + return ..() /datum/controller/subsystem/processing/fire(resumed = 0) if (!resumed) diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm new file mode 100644 index 0000000000000..fdfc577677c79 --- /dev/null +++ b/code/controllers/subsystem/statpanel.dm @@ -0,0 +1,335 @@ +SUBSYSTEM_DEF(statpanels) + name = "Stat Panels" + wait = 4 + init_order = INIT_ORDER_STATPANELS + init_stage = INITSTAGE_EARLY + priority = FIRE_PRIORITY_STATPANEL + runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY + flags = SS_NO_INIT + var/list/currentrun = list() + var/list/global_data + var/list/mc_data + + ///how many subsystem fires between most tab updates + var/default_wait = 10 + ///how many subsystem fires between updates of the status tab + var/status_wait = 2 + ///how many subsystem fires between updates of the MC tab + var/mc_wait = 5 + ///how many full runs this subsystem has completed. used for variable rate refreshes. + var/num_fires = 0 + +/datum/controller/subsystem/statpanels/fire(resumed = FALSE) + if (!resumed) + num_fires++ + global_data = list( + "Ground Map: [length(SSmapping.configs) ? SSmapping.configs[GROUND_MAP].map_name : "Loading..."]", + "Ship Map: [length(SSmapping.configs) ? SSmapping.configs[SHIP_MAP].map_name : "Loading..."]", + "Game Mode: [GLOB.master_mode]", + "Round ID: [GLOB.round_id ? GLOB.round_id : "NULL"]", + "Server Time: [time2text(world.timeofday, "YYYY-MM-DD hh:mm:ss")]", + "Operation Time: [stationTimestamp("hh:mm")]", + "Time Dilation: [round(SStime_track.time_dilation_current,1)]% AVG:([round(SStime_track.time_dilation_avg_fast,1)]%, [round(SStime_track.time_dilation_avg,1)]%, [round(SStime_track.time_dilation_avg_slow,1)]%)" + ) + + src.currentrun = GLOB.clients.Copy() + mc_data = null + + var/list/currentrun = src.currentrun + while(length(currentrun)) + var/client/target = currentrun[length(currentrun)] + currentrun.len-- + + if(!target.stat_panel.is_ready()) + continue + + if(target.stat_tab == "Status" && num_fires % status_wait == 0) + set_status_tab(target) + + if(!target.holder) + target.stat_panel.send_message("remove_admin_tabs") + else + target.stat_panel.send_message("update_split_admin_tabs", target?.prefs.split_admin_tabs) + + if(!("MC" in target.panel_tabs) || !("Tickets" in target.panel_tabs)) + target.stat_panel.send_message("add_admin_tabs", target.holder.href_token) + + if(target.stat_tab == "MC" && ((num_fires % mc_wait == 0) || target?.prefs.fast_mc_refresh)) + set_MC_tab(target) + + if(target.stat_tab == "Tickets" && num_fires % default_wait == 0) + set_tickets_tab(target) + + if(!length(GLOB.sdql2_queries) && ("SDQL2" in target.panel_tabs)) + target.stat_panel.send_message("remove_sdql2") + + else if(length(GLOB.sdql2_queries) && (target.stat_tab == "SDQL2" || !("SDQL2" in target.panel_tabs)) && num_fires % default_wait == 0) + set_SDQL2_tab(target) + + if(target.mob) + var/mob/target_mob = target.mob + + // Handle the examined turf of the stat panel, if it's been long enough, or if we've generated new images for it + var/turf/listed_turf = target_mob?.listed_turf + if(listed_turf && num_fires % default_wait == 0) + if(target.stat_tab == listed_turf.name || !(listed_turf.name in target.panel_tabs)) + set_turf_examine_tab(target, target_mob) + + if(MC_TICK_CHECK) + return + +/datum/controller/subsystem/statpanels/proc/set_status_tab(client/target) + if(!global_data)//statbrowser hasnt fired yet and we were called from immediate_send_stat_data() + return + + target.stat_panel.send_message("update_stat", list( + "global_data" = global_data, + "ping_str" = "Ping: [round(target.lastping, 1)]ms (Average: [round(target.avgping, 1)]ms)", + "other_str" = target.mob?.get_status_tab_items(), + )) + +/datum/controller/subsystem/statpanels/proc/set_MC_tab(client/target) + var/turf/eye_turf = get_turf(target.eye) + var/coord_entry = COORD(eye_turf) + if(!mc_data) + generate_mc_data() + target.stat_panel.send_message("update_mc", list("mc_data" = mc_data, "coord_entry" = coord_entry)) + +/datum/controller/subsystem/statpanels/proc/set_tickets_tab(client/target) + var/list/ahelp_tickets = GLOB.ahelp_tickets.stat_entry() + target.stat_panel.send_message("update_tickets", ahelp_tickets) + +/datum/controller/subsystem/statpanels/proc/set_SDQL2_tab(client/target) + var/list/sdql2A = list() + sdql2A[++sdql2A.len] = list("", "Access Global SDQL2 List", REF(GLOB.sdql2_vv_statobj)) + var/list/sdql2B = list() + for(var/datum/SDQL2_query/query as anything in GLOB.sdql2_queries) + sdql2B = query.generate_stat() + + sdql2A += sdql2B + target.stat_panel.send_message("update_sdql2", sdql2A) + +/datum/controller/subsystem/statpanels/proc/set_turf_examine_tab(client/target, mob/target_mob) + var/list/overrides = list() + for(var/image/target_image as anything in target.images) + if(!target_image.loc || target_image.loc.loc != target_mob.listed_turf || !target_image.override) + continue + overrides += target_image.loc + + var/list/atoms_to_display = list(target_mob.listed_turf) + for(var/atom/movable/turf_content as anything in target_mob.listed_turf) + if(turf_content.mouse_opacity == MOUSE_OPACITY_TRANSPARENT) + continue + if(turf_content.invisibility > target_mob.see_invisible) + continue + if(turf_content in overrides) + continue + if(turf_content.IsObscured()) + continue + atoms_to_display += turf_content + + /// Set the atoms we're meant to display + var/datum/object_window_info/obj_window = target.obj_window + obj_window.atoms_to_show = atoms_to_display + START_PROCESSING(SSobj_tab_items, obj_window) + refresh_client_obj_view(target) + +/datum/controller/subsystem/statpanels/proc/refresh_client_obj_view(client/refresh) + var/list/turf_items = return_object_images(refresh) + if(!length(turf_items) || !refresh.mob?.listed_turf) + return + refresh.stat_panel.send_message("update_listedturf", turf_items) + +#define OBJ_IMAGE_LOADING "statpanels obj loading temporary" +/// Returns all our ready object tab images +/// Returns a list in the form list(list(object_name, object_ref, loaded_image), ...) +/datum/controller/subsystem/statpanels/proc/return_object_images(client/load_from) + // You might be inclined to think that this is a waste of cpu time, since we + // A: Double iterate over atoms in the build case, or + // B: Generate these lists over and over in the refresh case + // It's really not very hot. The hot portion of this code is genuinely mostly in the image generation + // So it's ok to pay a performance cost for cleanliness here + + // No turf? go away + if(!load_from.mob?.listed_turf) + return list() + var/datum/object_window_info/obj_window = load_from.obj_window + var/list/already_seen = obj_window.atoms_to_images + var/list/to_make = obj_window.atoms_to_imagify + var/list/turf_items = list() + for(var/atom/turf_item as anything in obj_window.atoms_to_show) + // First, we fill up the list of refs to display + // If we already have one, just use that + var/existing_image = already_seen[turf_item] + if(existing_image == OBJ_IMAGE_LOADING) + continue + // We already have it. Success! + if(existing_image) + turf_items[++turf_items.len] = list("[turf_item.name]", REF(turf_item), existing_image) + continue + // Now, we're gonna queue image generation out of those refs + to_make += turf_item + already_seen[turf_item] = OBJ_IMAGE_LOADING + obj_window.RegisterSignal(turf_item, COMSIG_QDELETING, TYPE_PROC_REF(/datum/object_window_info,viewing_atom_deleted)) // we reset cache if anything in it gets deleted + return turf_items + +#undef OBJ_IMAGE_LOADING + +/datum/controller/subsystem/statpanels/proc/generate_mc_data() + mc_data = list( + list("CPU:", world.cpu), + list("Instances:", "[num2text(world.contents.len, 10)]"), + list("World Time:", "[world.time]"), + list("Globals:", GLOB.stat_entry(), ref(GLOB)), + list("[config]:", config.stat_entry(), ref(config)), + list("Byond:", "(FPS:[world.fps]) (TickCount:[world.time/world.tick_lag]) (TickDrift:[round(Master.tickdrift,1)]([round((Master.tickdrift/(world.time/world.tick_lag))*100,0.1)]%)) (Internal Tick Usage: [round(MAPTICK_LAST_INTERNAL_TICK_USAGE,0.1)]%)"), + list("Master Controller:", Master.stat_entry(), ref(Master)), + list("Failsafe Controller:", Failsafe.stat_entry(), ref(Failsafe)), + list("","") + ) + for(var/datum/controller/subsystem/sub_system as anything in Master.subsystems) + mc_data[++mc_data.len] = list("\[[sub_system.state_letter()]][sub_system.name]", sub_system.stat_entry(), ref(sub_system)) + mc_data[++mc_data.len] = list("Camera Net", "Cameras: [GLOB.cameranet.cameras.len] | Chunks: [GLOB.cameranet.chunks.len]", ref(GLOB.cameranet)) + +///immediately update the active statpanel tab of the target client +/datum/controller/subsystem/statpanels/proc/immediate_send_stat_data(client/target) + if(!target.stat_panel.is_ready()) + return FALSE + + if(target.stat_tab == "Status") + set_status_tab(target) + return TRUE + + var/mob/target_mob = target.mob + + // Handle turfs + + if(target_mob?.listed_turf) + if(!target_mob.TurfAdjacent(target_mob.listed_turf)) + target_mob.set_listed_turf(null) + + else if(target.stat_tab == target_mob?.listed_turf.name || !(target_mob?.listed_turf.name in target.panel_tabs)) + set_turf_examine_tab(target, target_mob) + return TRUE + + if(!target.holder) + return FALSE + + if(target.stat_tab == "MC") + set_MC_tab(target) + return TRUE + + if(target.stat_tab == "Tickets") + set_tickets_tab(target) + return TRUE + + if(!length(GLOB.sdql2_queries) && ("SDQL2" in target.panel_tabs)) + target.stat_panel.send_message("remove_sdql2") + + else if(length(GLOB.sdql2_queries) && target.stat_tab == "SDQL2") + set_SDQL2_tab(target) + +/// Stat panel window declaration +/client/var/datum/tgui_window/stat_panel + +/// Datum that holds and tracks info about a client's object window +/// Really only exists because I want to be able to do logic with signals +/// And need a safe place to do the registration +/datum/object_window_info + /// list of atoms to show to our client via the object tab, at least currently + var/list/atoms_to_show = list() + /// list of atom -> image string for objects we have had in the right click tab + /// this is our caching + var/list/atoms_to_images = list() + /// list of atoms to turn into images for the object tab + var/list/atoms_to_imagify = list() + /// Our owner client + var/client/parent + /// Are we currently tracking a turf? + var/actively_tracking = FALSE + +/datum/object_window_info/New(client/parent) + . = ..() + src.parent = parent + +/datum/object_window_info/Destroy(force, ...) + atoms_to_show = null + atoms_to_images = null + atoms_to_imagify = null + parent.obj_window = null + parent = null + STOP_PROCESSING(SSobj_tab_items, src) + return ..() + +/// Takes a client, attempts to generate object images for it +/// We will update the client with any improvements we make when we're done +/datum/object_window_info/process(seconds_per_tick) + // Cache the datum access for sonic speed + var/list/to_make = atoms_to_imagify + var/list/newly_seen = atoms_to_images + var/index = 0 + for(index in 1 to length(to_make)) + var/atom/thing = to_make[index] + + var/generated_string + if(ismob(thing) || length(thing.overlays) > 2) + generated_string = costly_icon2html(thing, parent, sourceonly=TRUE) + else + generated_string = icon2html(thing, parent, sourceonly=TRUE) + + newly_seen[thing] = generated_string + if(TICK_CHECK) + to_make.Cut(1, index + 1) + index = 0 + break + // If we've not cut yet, do it now + if(index) + to_make.Cut(1, index + 1) + SSstatpanels.refresh_client_obj_view(parent) + if(!length(to_make)) + return PROCESS_KILL + +/datum/object_window_info/proc/start_turf_tracking() + if(actively_tracking) + stop_turf_tracking() + var/static/list/connections = list( + COMSIG_MOVABLE_MOVED = PROC_REF(on_mob_move), + COMSIG_MOB_LOGOUT = PROC_REF(on_mob_logout), + ) + AddComponent(/datum/component/connect_mob_behalf, parent, connections) + actively_tracking = TRUE + +/datum/object_window_info/proc/stop_turf_tracking() + qdel(GetComponent(/datum/component/connect_mob_behalf)) + actively_tracking = FALSE + +/datum/object_window_info/proc/on_mob_move(mob/source) + SIGNAL_HANDLER + var/turf/listed = source.listed_turf + if(!listed || !source.TurfAdjacent(listed)) + source.set_listed_turf(null) + +/datum/object_window_info/proc/on_mob_logout(mob/source) + SIGNAL_HANDLER + on_mob_move(parent.mob) + +/// Clears any cached object window stuff +/// We use hard refs cause we'd need a signal for this anyway. Cleaner this way +/datum/object_window_info/proc/viewing_atom_deleted(atom/deleted) + SIGNAL_HANDLER + atoms_to_show -= deleted + atoms_to_imagify -= deleted + atoms_to_images -= deleted + +/mob/proc/set_listed_turf(turf/new_turf) + listed_turf = new_turf + if(!client) + return + if(!client.obj_window) + client.obj_window = new(client) + if(listed_turf) + client.stat_panel.send_message("create_listedturf", listed_turf.name) + client.obj_window.start_turf_tracking() + else + client.stat_panel.send_message("remove_listedturf") + client.obj_window.stop_turf_tracking() diff --git a/code/datums/components/connect_mob_behalf.dm b/code/datums/components/connect_mob_behalf.dm new file mode 100644 index 0000000000000..f7989a83982fc --- /dev/null +++ b/code/datums/components/connect_mob_behalf.dm @@ -0,0 +1,63 @@ +/// This component behaves similar to connect_loc_behalf, but working off clients and mobs instead of loc +/// To be clear, we hook into a signal on a tracked client's mob +/// We retain the ability to react to that signal on a seperate listener, which makes this quite powerful +/datum/component/connect_mob_behalf + dupe_mode = COMPONENT_DUPE_UNIQUE + + /// An assoc list of signal -> procpath to register to the mob our client "owns" + var/list/connections + /// The master client we're working with + var/client/tracked + /// The mob we're currently tracking + var/mob/tracked_mob + +/datum/component/connect_mob_behalf/Initialize(client/tracked, list/connections) + . = ..() + if (!istype(tracked)) + return COMPONENT_INCOMPATIBLE + src.connections = connections + src.tracked = tracked + +/datum/component/connect_mob_behalf/RegisterWithParent() + RegisterSignal(tracked, COMSIG_QDELETING, PROC_REF(handle_tracked_qdel)) + update_signals() + +/datum/component/connect_mob_behalf/UnregisterFromParent() + unregister_signals() + UnregisterSignal(tracked, COMSIG_QDELETING) + + tracked = null + tracked_mob = null + +/// Delete ourselves when our tracked client is deleted +/datum/component/connect_mob_behalf/proc/handle_tracked_qdel() + SIGNAL_HANDLER + qdel(src) + +/// Re-register signals on tracked mob +/datum/component/connect_mob_behalf/proc/update_signals() + unregister_signals() + // Yes this is a runtime silencer + // We could be in a position where logout is sent to two things, one thing intercepts it, then deletes the client's new mob + // It's rare, and the same check in connect_loc_behalf is more fruitful, but it's still worth doing + if(QDELETED(tracked?.mob)) + return + tracked_mob = tracked.mob + RegisterSignal(tracked_mob, COMSIG_MOB_LOGOUT, PROC_REF(on_logout)) + for (var/signal in connections) + parent.RegisterSignal(tracked_mob, signal, connections[signal]) + +/// Unregister signals on tracked mob +/datum/component/connect_mob_behalf/proc/unregister_signals() + if(isnull(tracked_mob)) + return + + parent.UnregisterSignal(tracked_mob, connections) + UnregisterSignal(tracked_mob, COMSIG_MOB_LOGOUT) + + tracked_mob = null + +/// update_signals on tracked mob logout +/datum/component/connect_mob_behalf/proc/on_logout(mob/source) + SIGNAL_HANDLER + update_signals() diff --git a/code/datums/gamemodes/_game_mode.dm b/code/datums/gamemodes/_game_mode.dm index 05bb5bdf3a54f..9cab1a0017d3b 100644 --- a/code/datums/gamemodes/_game_mode.dm +++ b/code/datums/gamemodes/_game_mode.dm @@ -164,6 +164,7 @@ GLOBAL_VAR(common_report) //Contains common part of roundend report continue qdel(player) + living.client.init_verbs() living.notransform = TRUE livings += living @@ -269,7 +270,7 @@ GLOBAL_LIST_INIT(bioscan_locations, list( /datum/game_mode/proc/grant_eord_respawn(datum/dcs, mob/source) SIGNAL_HANDLER - source.verbs |= /mob/proc/eord_respawn + add_verb(source, /mob/proc/eord_respawn) /datum/game_mode/proc/end_of_round_deathmatch() RegisterSignal(SSdcs, COMSIG_GLOB_MOB_LOGIN, PROC_REF(grant_eord_respawn)) // New mobs can now respawn into EORD @@ -283,7 +284,7 @@ GLOBAL_LIST_INIT(bioscan_locations, list( for(var/i in GLOB.player_list) var/mob/M = i - M.verbs |= /mob/proc/eord_respawn + add_verb(M, /mob/proc/eord_respawn) if(isnewplayer(M)) continue if(!(M.client?.prefs?.be_special & BE_DEATHMATCH)) @@ -574,6 +575,7 @@ GLOBAL_LIST_INIT(bioscan_locations, list( player.mind.transfer_to(player.new_character) var/datum/job/job = player.assigned_role job.on_late_spawn(player.new_character) + player.new_character.client?.init_verbs() var/area/A = get_area(player.new_character) deadchat_broadcast(span_game(" has woken at [span_name("[A?.name]")]."), span_game("[span_name("[player.new_character.real_name]")] ([job.title])"), follow_target = player.new_character, message_type = DEADCHAT_ARRIVALRATTLE) qdel(player) diff --git a/code/datums/mind.dm b/code/datums/mind.dm index 1f73d2c342a68..21c2d5f9a3811 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -74,6 +74,7 @@ if(active || force_key_move) new_character.key = key //now transfer the key to link the client to our new body + new_character.client.init_verbs() /datum/mind/proc/set_death_time() last_death = world.time diff --git a/code/game/atoms.dm b/code/game/atoms.dm index dcac16c585c0a..9f5c0c4083dc0 100755 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -925,6 +925,32 @@ Proc for attack log creation, because really why not . = ..() if(.) return + if(!usr?.client) + return + var/client/usr_client = usr.client + var/list/paramslist = list() + + if(href_list["statpanel_item_click"]) + switch(href_list["statpanel_item_click"]) + if("left") + paramslist[LEFT_CLICK] = "1" + if("right") + paramslist[RIGHT_CLICK] = "1" + if("middle") + paramslist[MIDDLE_CLICK] = "1" + else + return + + if(href_list["statpanel_item_shiftclick"]) + paramslist[SHIFT_CLICK] = "1" + if(href_list["statpanel_item_ctrlclick"]) + paramslist[CTRL_CLICK] = "1" + if(href_list["statpanel_item_altclick"]) + paramslist[ALT_CLICK] = "1" + + var/mouseparams = list2params(paramslist) + usr_client.Click(src, loc, null, mouseparams) + . = TRUE add_fingerprint(usr, "topic") diff --git a/code/game/objects/items/embedding.dm b/code/game/objects/items/embedding.dm index 8c46fe9411f80..1b4199f2d64a5 100644 --- a/code/game/objects/items/embedding.dm +++ b/code/game/objects/items/embedding.dm @@ -37,7 +37,7 @@ yankable_embedded = TRUE break if(!yankable_embedded) - verbs -= /mob/living/proc/yank_out_object + remove_verb(src, /mob/living/proc/yank_out_object) /mob/living/carbon/human/unembed_item(obj/item/embedding) @@ -52,7 +52,7 @@ yankable_embedded = TRUE break if(!yankable_embedded) - verbs -= /mob/living/proc/yank_out_object + remove_verb(src, /mob/living/proc/yank_out_object) /datum/limb/proc/unembed(obj/item/embedding) @@ -88,7 +88,7 @@ owner.visible_message(span_danger("\The [embedding] sticks in the wound!")) implants += embedding if(embedding.embedding.embedded_flags & EMBEDDED_CAN_BE_YANKED_OUT) - owner.verbs += /mob/living/proc/yank_out_object + add_verb(owner, /mob/living/proc/yank_out_object) embedding.add_mob_blood(owner) embedding.forceMove(owner) embedding.RegisterSignal(src, COMSIG_LIMB_DESTROYED, TYPE_PROC_REF(/obj/item, embedded_on_limb_destruction)) diff --git a/code/game/verbs/ooc.dm b/code/game/verbs/ooc.dm index c4e8dd9a348de..7e7ec84f1f3e0 100644 --- a/code/game/verbs/ooc.dm +++ b/code/game/verbs/ooc.dm @@ -583,3 +583,9 @@ set name = "Ping" set category = "OOC" winset(src, null, "command=.display_ping+[world.time + world.tick_lag * TICK_USAGE_REAL / 100]") + +/client/verb/fix_stat_panel() + set name = "Fix Stat Panel" + set hidden = TRUE + + init_verbs() diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 03bf2d4606a3d..c162c0020e8a5 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -17,6 +17,7 @@ return var/mob/dead/observer/ghost = M.ghostize(TRUE, TRUE) + owner.init_verbs() log_admin("[key_name(ghost)] admin ghosted at [AREACOORD(ghost)].") if(M.stat != DEAD) @@ -1299,7 +1300,7 @@ /client/proc/get_togglebuildmode() set name = "Toggle Build Mode" - set category = "Fun" + set category = "Admin.Fun" if(!check_rights(R_SPAWN)) return togglebuildmode(mob) @@ -1340,13 +1341,14 @@ SSblackbox.record_feedback("tally", "admin_verb", 1, "Ghost Drag Control") tomob.ckey = frommob.ckey + tomob.client?.init_verbs() qdel(frommob) return TRUE /client/proc/mass_replace() set name = "Mass replace atom" - set category = "Fun" + set category = "Admin.Fun" if(!check_rights(R_SPAWN)) return var/to_replace = pick_closest_path(input("Pick a movable atom path to be replaced", "Enter path as text") as text) diff --git a/code/modules/admin/debug_verbs.dm b/code/modules/admin/debug_verbs.dm index 828cc4c8c49ed..15bae5e6768e8 100644 --- a/code/modules/admin/debug_verbs.dm +++ b/code/modules/admin/debug_verbs.dm @@ -696,3 +696,9 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention) dellog += "" usr << browse(dellog.Join(), "window=dellog") + +/client/proc/debugstatpanel() + set name = "Debug Stat Panel" + set category = "Debug" + + src.stat_panel.send_message("create_debug") diff --git a/code/modules/admin/fun_verbs.dm b/code/modules/admin/fun_verbs.dm index 34c9194a283fd..47af429d18734 100644 --- a/code/modules/admin/fun_verbs.dm +++ b/code/modules/admin/fun_verbs.dm @@ -1,5 +1,5 @@ /datum/admins/proc/set_view_range() - set category = "Fun" + set category = "Admin.Fun" set name = "Set View Range" if(!check_rights(R_FUN)) @@ -24,7 +24,7 @@ /datum/admins/proc/emp() - set category = "Fun" + set category = "Admin.Fun" set name = "EM Pulse" if(!check_rights(R_FUN)) @@ -48,7 +48,7 @@ /datum/admins/proc/queen_report() - set category = "Fun" + set category = "Admin.Fun" set name = "Queen Mother Report" if(!check_rights(R_FUN)) @@ -70,7 +70,7 @@ /datum/admins/proc/rouny_all() set name = "Toggle Glob Xeno Rouny" - set category = "Fun" + set category = "Admin.Fun" set desc = "Toggle all living xenos into rouny versions of themselves" if(!check_rights(R_FUN)) @@ -83,7 +83,7 @@ /datum/admins/proc/hive_status() - set category = "Fun" + set category = "Admin.Fun" set name = "Hive Status" set desc = "Check the status of the hive." @@ -99,7 +99,7 @@ /datum/admins/proc/ai_report() - set category = "Fun" + set category = "Admin.Fun" set name = "AI Report" if(!check_rights(R_FUN)) @@ -129,7 +129,7 @@ /datum/admins/proc/command_report() - set category = "Fun" + set category = "Admin.Fun" set name = "Command Report" if(!check_rights(R_FUN)) @@ -158,7 +158,7 @@ /datum/admins/proc/narrate_global() - set category = "Fun" + set category = "Admin.Fun" set name = "Global Narrate" if(!check_rights(R_FUN)) @@ -217,7 +217,7 @@ /datum/admins/proc/subtle_message_panel() - set category = "Fun" + set category = "Admin.Fun" set name = "Subtle Message Mob" if(!check_rights(R_FUN|R_MENTOR)) @@ -256,7 +256,7 @@ /datum/admins/proc/award_medal() - set category = "Fun" + set category = "Admin.Fun" set name = "Award a Medal" if(!check_rights(R_FUN)) @@ -266,7 +266,7 @@ /datum/admins/proc/custom_info() - set category = "Fun" + set category = "Admin.Fun" set name = "Change Custom Info" if(!check_rights(R_FUN)) @@ -304,7 +304,7 @@ /datum/admins/proc/sound_file(S as sound) - set category = "Fun" + set category = "Admin.Fun" set name = "Play Imported Sound" set desc = "Play a sound imported from anywhere on your computer." @@ -336,7 +336,7 @@ /datum/admins/proc/sound_web() - set category = "Fun" + set category = "Admin.Fun" set name = "Play Internet Sound" if(!check_rights(R_SOUND)) @@ -428,7 +428,7 @@ /datum/admins/proc/sound_stop() - set category = "Fun" + set category = "Admin.Fun" set name = "Stop Regular Sounds" if(!check_rights(R_SOUND)) @@ -443,7 +443,7 @@ /datum/admins/proc/music_stop() - set category = "Fun" + set category = "Admin.Fun" set name = "Stop Playing Music" if(!check_rights(R_SOUND)) @@ -459,7 +459,7 @@ /datum/admins/proc/announce() - set category = "Fun" + set category = "Admin.Fun" set name = "Admin Announce" if(!check_rights(R_FUN)) @@ -478,7 +478,7 @@ /datum/admins/proc/force_distress() - set category = "Fun" + set category = "Admin.Fun" set name = "Distress Beacon" set desc = "Call a distress beacon manually." @@ -567,7 +567,7 @@ /datum/admins/proc/drop_bomb() - set category = "Fun" + set category = "Admin.Fun" set name = "Drop Bomb" set desc = "Cause an explosion of varying strength at your location." @@ -656,7 +656,7 @@ /datum/admins/proc/drop_dynex_bomb() - set category = "Fun" + set category = "Admin.Fun" set name = "Drop DynEx Bomb" set desc = "Cause an explosion of varying strength at your location." @@ -669,7 +669,7 @@ /datum/admins/proc/change_security_level() - set category = "Fun" + set category = "Admin.Fun" set name = "Set Security Level" if(!check_rights(R_FUN)) @@ -689,7 +689,7 @@ /datum/admins/proc/rank_and_equipment(mob/living/carbon/human/H in GLOB.human_mob_list) - set category = "Fun" + set category = "Admin.Fun" set name = "Rank and Equipment" if(!check_rights(R_FUN)) @@ -731,7 +731,7 @@ /datum/admins/proc/edit_appearance(mob/living/carbon/human/H in GLOB.human_mob_list) - set category = "Fun" + set category = "Admin.Fun" set name = "Edit Appearance" if(!check_rights(R_FUN)) @@ -766,7 +766,7 @@ /datum/admins/proc/offer(mob/living/L in GLOB.mob_living_list) - set category = "Fun" + set category = "Admin.Fun" set name = "Offer Mob" if(!check_rights(R_FUN)) @@ -801,7 +801,7 @@ /datum/admins/proc/xeno_panel(mob/living/carbon/xenomorph/X in GLOB.xeno_mob_list) - set category = "Fun" + set category = "Admin.Fun" set name = "Xeno Panel" if(!check_rights(R_FUN)) @@ -873,7 +873,7 @@ /client/proc/toggle_buildmode() - set category = "Fun" + set category = "Admin.Fun" set name = "Toggle Build Mode" if(!check_rights(R_FUN)) @@ -883,7 +883,7 @@ /datum/admins/proc/imaginary_friend() - set category = "Fun" + set category = "Admin.Fun" set name = "Imaginary Friend" if(!check_rights(R_FUN|R_MENTOR)) @@ -925,7 +925,7 @@ message_admins("[ADMIN_TPMONTY(IF)] started being imaginary friend of [ADMIN_TPMONTY(friend_owner)].") /datum/admins/proc/force_dropship() - set category = "Fun" + set category = "Admin.Fun" set name = "Force Dropship" if(!check_rights(R_FUN)) @@ -999,7 +999,7 @@ /datum/admins/proc/play_cinematic() - set category = "Fun" + set category = "Admin.Fun" set name = "Play Cinematic" if(!check_rights(R_FUN)) @@ -1016,7 +1016,7 @@ /datum/admins/proc/set_tip() - set category = "Fun" + set category = "Admin.Fun" set name = "Set Tip" if(!check_rights(R_FUN)) @@ -1037,7 +1037,7 @@ /datum/admins/proc/ghost_interact() - set category = "Fun" + set category = "Admin.Fun" set name = "Ghost Interact" if(!check_rights(R_FUN)) @@ -1049,7 +1049,7 @@ message_admins("[ADMIN_TPMONTY(usr)] has [usr.client.holder.ghost_interact ? "enabled" : "disabled"] ghost interact.") /client/proc/run_weather() - set category = "Fun" + set category = "Admin.Fun" set name = "Run Weather" set desc = "Triggers a weather on the z-level you choose." @@ -1073,7 +1073,7 @@ ///client verb to set round end sound /client/proc/set_round_end_sound(S as sound) - set category = "Fun" + set category = "Admin.Fun" set name = "Set Round End Sound" if(!check_rights(R_SOUND)) return @@ -1086,7 +1086,7 @@ ///Adjusts gravity, modifying the jump component for all mobs /datum/admins/proc/adjust_gravity() - set category = "Fun" + set category = "Admin.Fun" set name = "Adjust Gravity" if(!check_rights(R_FUN)) diff --git a/code/modules/admin/holder.dm b/code/modules/admin/holder.dm index c61557b2c8594..f34ac59188518 100644 --- a/code/modules/admin/holder.dm +++ b/code/modules/admin/holder.dm @@ -84,7 +84,7 @@ var/client/C if((C = owner) || (C = GLOB.directory[target])) disassociate() - C.verbs += /client/proc/readmin + add_verb(C, /client/proc/readmin) /datum/admins/proc/associate(client/C) @@ -105,7 +105,8 @@ owner = C owner.holder = src owner.add_admin_verbs() - owner.verbs -= /client/proc/readmin + remove_verb(owner, /client/proc/readmin) + owner.init_verbs() GLOB.admins |= C @@ -349,6 +350,7 @@ GLOBAL_PROTECT(admin_verbs_asay) /datum/admins/proc/delete_all, /datum/admins/proc/generate_powernets, /datum/admins/proc/debug_mob_lists, + /client/proc/debugstatpanel, /datum/admins/proc/delete_atom, /datum/admins/proc/restart_controller, /datum/admins/proc/check_contents, @@ -499,41 +501,41 @@ GLOBAL_PROTECT(admin_verbs_log) /client/proc/add_admin_verbs() if(holder) var/rights = holder.rank.rights - verbs += GLOB.admin_verbs_default + add_verb(src, GLOB.admin_verbs_default) if(rights & R_ADMIN) - verbs += GLOB.admin_verbs_admin + add_verb(src, GLOB.admin_verbs_admin) if(rights & R_MENTOR) - verbs += GLOB.admin_verbs_mentor + add_verb(src, GLOB.admin_verbs_mentor) if(rights & R_BAN) - verbs += GLOB.admin_verbs_ban + add_verb(src, GLOB.admin_verbs_ban) if(rights & R_ASAY) - verbs += GLOB.admin_verbs_asay + add_verb(src, GLOB.admin_verbs_asay) if(rights & R_FUN) - verbs += GLOB.admin_verbs_fun + add_verb(src, GLOB.admin_verbs_fun) if(rights & R_SERVER) - verbs += GLOB.admin_verbs_server + add_verb(src, GLOB.admin_verbs_server) if(rights & R_DEBUG) - verbs += GLOB.admin_verbs_debug + add_verb(src, GLOB.admin_verbs_debug) if(rights & R_RUNTIME) - verbs += GLOB.admin_verbs_runtimes + add_verb(src, GLOB.admin_verbs_runtimes) if(rights & R_PERMISSIONS) - verbs += GLOB.admin_verbs_permissions + add_verb(src, GLOB.admin_verbs_permissions) if(rights & R_DBRANKS) - verbs += GLOB.admin_verbs_permissions + add_verb(src, GLOB.admin_verbs_permissions) if(rights & R_SOUND) - verbs += GLOB.admin_verbs_sound + add_verb(src, GLOB.admin_verbs_sound) if(rights & R_COLOR) - verbs += GLOB.admin_verbs_color + add_verb(src, GLOB.admin_verbs_color) if(rights & R_VAREDIT) - verbs += GLOB.admin_verbs_varedit + add_verb(src, GLOB.admin_verbs_varedit) if(rights & R_SPAWN) - verbs += GLOB.admin_verbs_spawn + add_verb(src, GLOB.admin_verbs_spawn) if(rights & R_LOG) - verbs += GLOB.admin_verbs_log + add_verb(src, GLOB.admin_verbs_log) /client/proc/remove_admin_verbs() - verbs.Remove( + remove_verb(src, list( GLOB.admin_verbs_default, GLOB.admin_verbs_admin, GLOB.admin_verbs_mentor, @@ -548,7 +550,7 @@ GLOBAL_PROTECT(admin_verbs_log) GLOB.admin_verbs_varedit, GLOB.admin_verbs_spawn, GLOB.admin_verbs_log, - ) + )) /proc/is_mentor(client/C) diff --git a/code/modules/admin/verbs/SDQL2/SDQL_2.dm b/code/modules/admin/verbs/SDQL2/SDQL_2.dm index 5f7122d6e3b3b..8b6f97a2eb783 100644 --- a/code/modules/admin/verbs/SDQL2/SDQL_2.dm +++ b/code/modules/admin/verbs/SDQL2/SDQL_2.dm @@ -231,7 +231,7 @@ return var/list/datum/SDQL2_query/running = list() var/list/datum/SDQL2_query/waiting_queue = list() //Sequential queries queue. - + for(var/list/query_tree in querys) var/datum/SDQL2_query/query = new /datum/SDQL2_query(query_tree) if(QDELETED(query)) @@ -258,7 +258,7 @@ to_chat(usr, span_admin("[msg]")) log_admin(msg) query.ARun() - + var/finished = FALSE var/objs_all = 0 var/objs_eligible = 0 @@ -429,11 +429,13 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null delete_click = new(null, "INITIALIZING", src) if(!action_click) action_click = new(null, "INITIALIZNG", src) - stat("[id] ", delete_click.update("DELETE QUERY | STATE : [text_state()] | ALL/ELIG/FIN \ + var/list/L = list() + L[++L.len] = list("[id] ", "[delete_click.update("DELETE QUERY | STATE : [text_state()] | ALL/ELIG/FIN \ [islist(obj_count_all)? length(obj_count_all) : (isnull(obj_count_all)? "0" : obj_count_all)]/\ [islist(obj_count_eligible)? length(obj_count_eligible) : (isnull(obj_count_eligible)? "0" : obj_count_eligible)]/\ - [islist(obj_count_finished)? length(obj_count_finished) : (isnull(obj_count_finished)? "0" : obj_count_finished)] - [get_query_text()]")) - stat(" ", action_click.update("[SDQL2_IS_RUNNING? "HALT" : "RUN"]")) + [islist(obj_count_finished)? length(obj_count_finished) : (isnull(obj_count_finished)? "0" : obj_count_finished)] - [get_query_text()]")]", REF(delete_click)) + L[++L.len] = list(" ", "[action_click.update("[SDQL2_IS_RUNNING? "HALT" : "RUN"]")]", REF(action_click)) + return L /datum/SDQL2_query/proc/delete_click() admin_del(usr) diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm index 532dee00fe20e..f20544487ff84 100644 --- a/code/modules/admin/verbs/adminhelp.dm +++ b/code/modules/admin/verbs/adminhelp.dm @@ -109,6 +109,9 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) //Tickets statpanel /datum/admin_help_tickets/proc/stat_entry() + SHOULD_CALL_PARENT(TRUE) + SHOULD_NOT_SLEEP(TRUE) + var/list/L = list() var/num_mentors_active = 0 var/num_admins_active = 0 var/num_mentors_closed = 0 @@ -138,32 +141,35 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) num_admins_resolved++ if(check_rights(R_ADMINTICKET, FALSE)) - stat("Active Tickets:", astatclick.update("[num_mentors_active + num_admins_active]")) + L[++L.len] = list("Active Tickets:", "[astatclick.update("[num_mentors_active + num_admins_active]")]", null, REF(astatclick)) else if(check_rights(R_MENTOR, FALSE)) - stat("Active Tickets:", astatclick.update("[num_mentors_active]")) + L[++L.len] = list("Active Tickets:", "[astatclick.update("[num_mentors_active]")]", null, REF(astatclick)) for(var/I in active_tickets) var/datum/admin_help/AH = I + var/obj/effect/statclick/updated = AH.statclick.update() if(AH.tier == TICKET_MENTOR && check_rights(R_ADMINTICKET|R_MENTOR, FALSE)) if(AH.initiator) - stat("\[[AH.marked ? "X" : " "]\] #[AH.id]. Mentor. [AH.initiator_key_name]:", AH.statclick.update()) + L[++L.len] = list("\[[AH.marked ? "X" : " "]\] #[AH.id]. Mentor. [AH.initiator_key_name]:", "[updated.name]", REF(AH)) else - stat("\[D\] #[AH.id]. Mentor. [AH.initiator_key_name]:", AH.statclick.update()) + L[++L.len] = list("\[D\] #[AH.id]. Mentor. [AH.initiator_key_name]:", "[updated.name]", REF(AH)) else if(AH.tier == TICKET_ADMIN && check_rights(R_ADMINTICKET, FALSE)) if(AH.initiator) - stat("\[[AH.marked ? "X" : " "]\] #[AH.id]. Admin. [AH.initiator_key_name]:", AH.statclick.update()) + L[++L.len] = list("\[[AH.marked ? "X" : " "]\] #[AH.id]. Admin. [AH.initiator_key_name]:", "[updated.name]", REF(AH)) else - stat("\[D\] #[AH.id]. Admin. [AH.initiator_key_name]:", AH.statclick.update()) + L[++L.len] = list("\[D\] #[AH.id]. Admin. [AH.initiator_key_name]:", "[updated.name]", REF(AH)) if(check_rights(R_ADMINTICKET, FALSE)) - stat("Closed Tickets:", cstatclick.update("[num_mentors_closed + num_admins_closed]")) + L[++L.len] = list("Closed Tickets:", "[cstatclick.update("[num_mentors_closed + num_admins_closed]")]", null, REF(cstatclick)) else if(check_rights(R_MENTOR, FALSE)) - stat("Closed Tickets:", cstatclick.update("[num_mentors_closed]")) + L[++L.len] = list("Closed Tickets:", "[cstatclick.update("[num_mentors_closed]")]", null, REF(cstatclick)) if(check_rights(R_ADMINTICKET, FALSE)) - stat("Resolved Tickets:", rstatclick.update("[num_mentors_resolved + num_admins_resolved]")) + L[++L.len] = list("Resolved Tickets:", "[rstatclick.update("[num_mentors_resolved + num_admins_resolved]")]", null, REF(rstatclick)) else if(check_rights(R_MENTOR, FALSE)) - stat("Resolved Tickets:", rstatclick.update("[num_mentors_resolved]")) + L[++L.len] = list("Resolved Tickets:", "[rstatclick.update("[num_mentors_resolved]")]", null, REF(rstatclick)) + + return L //Reassociate still open ticket if one exists @@ -761,7 +767,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) // /client/proc/giveadminhelpverb() - verbs |= /client/verb/adminhelp + add_verb(src, /client/verb/adminhelp) deltimer(adminhelptimerid) adminhelptimerid = 0 diff --git a/code/modules/admin/verbs/datumvars.dm b/code/modules/admin/verbs/datumvars.dm index d43b5ce8e73fe..d0ffd44fd005b 100644 --- a/code/modules/admin/verbs/datumvars.dm +++ b/code/modules/admin/verbs/datumvars.dm @@ -563,9 +563,9 @@ else if(istype(value, /datum)) var/datum/D = value if("[D]" != "[D.type]") //if the thing as a name var, lets use it. - item = "[VV_HTML_ENCODE(name)] [REF(value)] = [D] [D.type]" + item = "[VV_HTML_ENCODE(name)] [REF(value)] = [D] [D.type]" else - item = "[VV_HTML_ENCODE(name)] [REF(value)] = [D.type]" + item = "[VV_HTML_ENCODE(name)] [REF(value)] = [D.type]" else if(islist(value)) var/list/L = value @@ -585,9 +585,9 @@ items += debug_variable(key, val, level + 1, sanitize = sanitize) - item = "[VV_HTML_ENCODE(name)] = /list ([length(L)])" + item = "[VV_HTML_ENCODE(name)] = /list ([length(L)])" else - item = "[VV_HTML_ENCODE(name)] = /list ([length(L)])" + item = "[VV_HTML_ENCODE(name)] = /list ([length(L)])" else if(name in GLOB.bitfields) var/list/flags = list() @@ -608,8 +608,8 @@ return - if(href_list["vars"]) - debug_variables(locate(href_list["vars"])) + if(href_list["Vars"]) + debug_variables(locate(href_list["Vars"])) else if(href_list["datumrefresh"]) diff --git a/code/modules/admin/verbs/faxes.dm b/code/modules/admin/verbs/faxes.dm index 95324baa6bb1a..34c3539b61e26 100644 --- a/code/modules/admin/verbs/faxes.dm +++ b/code/modules/admin/verbs/faxes.dm @@ -74,7 +74,7 @@ GLOBAL_LIST_EMPTY(faxes) /datum/admins/proc/view_faxes() - set category = "Fun" + set category = "Admin.Fun" set name = "View Faxes" if(!check_rights(R_ADMIN, FALSE) && !is_mentor(usr.client)) diff --git a/code/modules/admin/verbs/podlauncher.dm b/code/modules/admin/verbs/podlauncher.dm index 396cc39e0eb5a..42e1495fb52bd 100644 --- a/code/modules/admin/verbs/podlauncher.dm +++ b/code/modules/admin/verbs/podlauncher.dm @@ -1,5 +1,5 @@ /datum/admins/proc/launch_pod() - set category = "Fun" + set category = "Admin.Fun" set name = "Launch Supply Pod" if(!check_rights(R_FUN)) diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm index ba366bf186e45..8c34fe22ec510 100644 --- a/code/modules/client/client_defines.dm +++ b/code/modules/client/client_defines.dm @@ -51,6 +51,18 @@ /// datum wrapper for client view var/datum/view_data/view_size + /// our current tab + var/stat_tab + + /// list of all tabs + var/list/panel_tabs = list() + /// list of tabs containing spells and abilities + var/list/spell_tabs = list() + ///A lazy list of atoms we've examined in the last RECENT_EXAMINE_MAX_WINDOW (default 2) seconds, so that we will call [/atom/proc/examine_more] instead of [/atom/proc/examine] on them when examining + var/list/recent_examines + ///Our object window datum. It stores info about and handles behavior for the object tab + var/datum/object_window_info/obj_window + //Database related var/player_age = -1 //Used to determine how old the account is - in days. var/player_join_date = null //Date that this account was first seen in the server diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 2f06fb73d26ef..a43d4402c14f5 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -64,7 +64,7 @@ to_chat(src, span_danger("[msg]")) return var/stl = CONFIG_GET(number/second_topic_limit) - if (!holder && stl) + if (!holder && stl && href_list["window_id"] != "statbrowser") var/second = round(world.time, 10) if (!topiclimiter) topiclimiter = new(LIMITER_SIZE) @@ -80,6 +80,8 @@ return if(href_list["reload_tguipanel"]) nuke_chat() + if(href_list["reload_statbrowser"]) + stat_panel.reinitialize() //Logs all hrefs. log_href("[src] (usr:[usr]\[[COORD(usr)]\]) : [hsrc ? "[hsrc] " : ""][href]") @@ -141,6 +143,10 @@ GLOB.clients += src GLOB.directory[ckey] = src + // Instantiate stat panel + stat_panel = new(src, "statbrowser") + stat_panel.subscribe(src, PROC_REF(on_stat_panel_message)) + // Instantiate tgui panel tgui_panel = new(src, "browseroutput") @@ -161,7 +167,7 @@ holder.owner = src holder.activate() else if(GLOB.deadmins[ckey]) - verbs += /client/proc/readmin + add_verb(src, /client/proc/readmin) //preferences datum - also holds some persistant data for the client (because we may as well keep these datums to a minimum) prefs = GLOB.preferences_datums[ckey] @@ -222,7 +228,13 @@ tgui_say.initialize() - + // Initialize stat panel + stat_panel.initialize( + inline_html = file("html/statbrowser.html"), + inline_js = file("html/statbrowser.js"), + inline_css = file("html/statbrowser.css"), + ) + addtimer(CALLBACK(src, PROC_REF(check_panel_loaded)), 30 SECONDS) if(byond_version < REQUIRED_CLIENT_MAJOR || (byond_build && byond_build < REQUIRED_CLIENT_MINOR)) //to_chat(src, span_userdanger("Your version of byond is severely out of date.")) @@ -368,6 +380,8 @@ /client/Destroy() SEND_SIGNAL(src, COMSIG_CLIENT_DISCONNECTED) log_access("Logout: [key_name(src)]") + if(obj_window) + QDEL_NULL(obj_window) if(holder) if(check_rights(R_ADMIN, FALSE)) message_admins("Admin logout: [key_name(src)].") @@ -407,8 +421,6 @@ return QDEL_HINT_HARDDEL_NOW /client/Click(atom/object, atom/location, control, params) - if(!control) - return if(click_intercepted) if(click_intercepted >= world.time) click_intercepted = 0 //Reset and return. Next click should work, but not this one. @@ -977,3 +989,47 @@ GLOBAL_VAR_INIT(automute_on, null) SSambience.ambience_listening_clients[src] = world.time + 10 SECONDS //Just wait 10 seconds before the next one aight mate? cheers. else SSambience.ambience_listening_clients -= src + +/// compiles a full list of verbs and sends it to the browser +/client/proc/init_verbs() + if(IsAdminAdvancedProcCall()) + return + var/list/verblist = list() + var/list/verbstoprocess = verbs.Copy() + if(mob) + verbstoprocess += mob.verbs + for(var/atom/movable/thing as anything in mob.contents) + verbstoprocess += thing.verbs + panel_tabs.Cut() // panel_tabs get reset in init_verbs on JS side anyway + for(var/procpath/verb_to_init as anything in verbstoprocess) + if(!verb_to_init) + continue + if(verb_to_init.hidden) + continue + if(!istext(verb_to_init.category)) + continue + panel_tabs |= verb_to_init.category + verblist[++verblist.len] = list(verb_to_init.category, verb_to_init.name) + src.stat_panel.send_message("init_verbs", list(panel_tabs = panel_tabs, verblist = verblist)) + +/client/proc/check_panel_loaded() + if(stat_panel.is_ready()) + return + to_chat(src, span_userdanger("Statpanel failed to load, click here to reload the panel ")) + +/** + * Handles incoming messages from the stat-panel TGUI. + */ +/client/proc/on_stat_panel_message(type, payload) + switch(type) + if("Update-Verbs") + init_verbs() + if("Remove-Tabs") + panel_tabs -= payload["tab"] + if("Send-Tabs") + panel_tabs |= payload["tab"] + if("Reset-Tabs") + panel_tabs = list() + if("Set-Tab") + stat_tab = payload["tab"] + SSstatpanels.immediate_send_stat_data(src) diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index d6ecea2edd905..1f3b7c66d0c16 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -169,6 +169,11 @@ GLOBAL_LIST_EMPTY(preferences_datums) ///If the game is in fullscreen mode var/fullscreen_mode = FALSE + ///Whether or not the MC tab of the Stat Panel refreshes fast. This is expensive so make sure you need it. + var/fast_mc_refresh = FALSE + ///When enabled, will split the 'Admin' panel into several tabs. + var/split_admin_tabs = FALSE + /// New TGUI Preference preview var/map_name = "player_pref_map" var/atom/movable/screen/map_view/screen_main diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm index afbf559d72147..dea42a098e9d6 100644 --- a/code/modules/client/preferences_savefile.dm +++ b/code/modules/client/preferences_savefile.dm @@ -152,6 +152,8 @@ READ_FILE(S["sound_tts"], sound_tts) READ_FILE(S["volume_tts"], volume_tts) READ_FILE(S["sound_tts_blips"], sound_tts_blips) + READ_FILE(S["fast_mc_refresh"], fast_mc_refresh) + READ_FILE(S["split_admin_tabs"], split_admin_tabs) READ_FILE(S["key_bindings"], key_bindings) READ_FILE(S["custom_emotes"], custom_emotes) @@ -228,6 +230,9 @@ tgui_input = sanitize_integer(tgui_input, FALSE, TRUE, initial(tgui_input)) tgui_input_big_buttons = sanitize_integer(tgui_input_big_buttons, FALSE, TRUE, initial(tgui_input_big_buttons)) tgui_input_buttons_swap = sanitize_integer(tgui_input_buttons_swap, FALSE, TRUE, initial(tgui_input_buttons_swap)) + + fast_mc_refresh = sanitize_integer(fast_mc_refresh, FALSE, TRUE, initial(fast_mc_refresh)) + split_admin_tabs = sanitize_integer(split_admin_tabs, FALSE, TRUE, initial(split_admin_tabs)) return TRUE @@ -294,6 +299,10 @@ tgui_input_big_buttons = sanitize_integer(tgui_input_big_buttons, FALSE, TRUE, initial(tgui_input_big_buttons)) tgui_input_buttons_swap = sanitize_integer(tgui_input_buttons_swap, FALSE, TRUE, initial(tgui_input_buttons_swap)) + // Admin + fast_mc_refresh = sanitize_integer(fast_mc_refresh, FALSE, TRUE, initial(fast_mc_refresh)) + split_admin_tabs = sanitize_integer(split_admin_tabs, FALSE, TRUE, initial(split_admin_tabs)) + WRITE_FILE(S["default_slot"], default_slot) WRITE_FILE(S["lastchangelog"], lastchangelog) WRITE_FILE(S["ooccolor"], ooccolor) @@ -343,6 +352,10 @@ WRITE_FILE(S["tgui_input_big_buttons"], tgui_input_big_buttons) WRITE_FILE(S["tgui_input_buttons_swap"], tgui_input_buttons_swap) + // Admin options + WRITE_FILE(S["fast_mc_refresh"], fast_mc_refresh) + WRITE_FILE(S["split_admin_tabs"], split_admin_tabs) + return TRUE /datum/preferences/proc/save_keybinds() diff --git a/code/modules/client/preferences_ui.dm b/code/modules/client/preferences_ui.dm index 599f39456cded..6f6480f9f4afb 100644 --- a/code/modules/client/preferences_ui.dm +++ b/code/modules/client/preferences_ui.dm @@ -96,6 +96,7 @@ data["alternate_option"] = alternate_option data["special_occupation"] = be_special if(GAME_SETTINGS) + data["is_admin"] = user.client?.holder ? TRUE : FALSE data["ui_style_color"] = ui_style_color data["ui_style"] = ui_style data["ui_style_alpha"] = ui_style_alpha @@ -130,6 +131,8 @@ data["quick_equip"] = list() for(var/quick_equip_slots in quick_equip) data["quick_equip"] += slot_flag_to_fluff(quick_equip_slots) + data["fast_mc_refresh"] = fast_mc_refresh + data["split_admin_tabs"] = split_admin_tabs if(KEYBIND_SETTINGS) data["is_admin"] = user.client?.holder ? TRUE : FALSE data["key_bindings"] = list() @@ -801,6 +804,12 @@ if("unique_action_use_active_hand") unique_action_use_active_hand = !unique_action_use_active_hand + if("fast_mc_refresh") + fast_mc_refresh = !fast_mc_refresh + + if("split_admin_tabs") + split_admin_tabs = !split_admin_tabs + else // Handle the unhandled cases return diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index f88eb2768f52e..40f15d50d8f37 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -270,6 +270,7 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER) ghost.mind = mind mind = null ghost.key = key + ghost.client?.init_verbs() ghost.mind?.current = ghost ghost.faction = faction @@ -318,51 +319,51 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER) return FALSE -/mob/dead/observer/Stat() +/mob/dead/observer/get_status_tab_items() . = ..() - if(statpanel("Status")) - if(SSticker.current_state == GAME_STATE_PREGAME) - stat("Time To Start:", "[SSticker.time_left > 0 ? SSticker.GetTimeLeft() : "(DELAYED)"]") - stat("Players: [length(GLOB.player_list)]", "Players Ready: [length(GLOB.ready_players)]") - for(var/i in GLOB.player_list) - if(isnewplayer(i)) - var/mob/new_player/N = i - stat("[N.client?.holder?.fakekey ? N.client.holder.fakekey : N.key]", N.ready ? "Playing" : "") - else if(isobserver(i)) - var/mob/dead/observer/O = i - stat("[O.client?.holder?.fakekey ? O.client.holder.fakekey : O.key]", "Observing") - var/status_value = SSevacuation?.get_status_panel_eta() - if(status_value) - stat("Evacuation in:", status_value) - if(SSticker.mode) - var/rulerless_countdown = SSticker.mode.get_hivemind_collapse_countdown() - if(rulerless_countdown) - stat("Orphan hivemind collapse timer:", rulerless_countdown) - if(GLOB.respawn_allowed) - status_value = (GLOB.key_to_time_of_role_death[key] + SSticker.mode?.respawn_time - world.time) * 0.1 - if(status_value <= 0) - stat("Respawn timer:", "READY") - else - stat("Respawn timer:", "[(status_value / 60) % 60]:[add_leading(num2text(status_value % 60), 2, "0")]") - if(SSticker.mode?.flags_round_type & MODE_INFESTATION) - if(larva_position) - stat("Position in larva candidate queue: ", "[larva_position]") - var/datum/job/xeno_job = SSjob.GetJobType(/datum/job/xenomorph) - var/stored_larva = xeno_job.total_positions - xeno_job.current_positions - if(stored_larva) - stat("Burrowed larva:", stored_larva) - //game end timer for patrol and sensor capture - var/patrol_end_countdown = SSticker.mode?.game_end_countdown() - if(patrol_end_countdown) - stat("Round End timer:", patrol_end_countdown) - //respawn wave timer - var/patrol_wave_countdown = SSticker.mode?.wave_countdown() - if(patrol_wave_countdown) - stat("Respawn wave timer:", patrol_wave_countdown) - var/datum/game_mode/combat_patrol/sensor_capture/sensor_mode = SSticker.mode - if(issensorcapturegamemode(SSticker.mode)) - stat("Activated Sensor Towers:", sensor_mode.sensors_activated) + if(SSticker.current_state == GAME_STATE_PREGAME) + . += "Time To Start: [SSticker.time_left > 0 ? SSticker.GetTimeLeft() : "(DELAYED)"]" + . += "Players: [length(GLOB.player_list)]" + . += "Players Ready: [length(GLOB.ready_players)]" + for(var/i in GLOB.player_list) + if(isnewplayer(i)) + var/mob/new_player/N = i + . += "[N.client?.holder?.fakekey ? N.client.holder.fakekey : N.key][N.ready ? " Playing" : ""]" + else if(isobserver(i)) + var/mob/dead/observer/O = i + . += "[O.client?.holder?.fakekey ? O.client.holder.fakekey : O.key] Observing" + var/status_value = SSevacuation?.get_status_panel_eta() + if(status_value) + . += "Evacuation in: [status_value]" + if(SSticker.mode) + var/rulerless_countdown = SSticker.mode.get_hivemind_collapse_countdown() + if(rulerless_countdown) + . += "Orphan hivemind collapse timer: [rulerless_countdown]" + if(GLOB.respawn_allowed) + status_value = (GLOB.key_to_time_of_role_death[key] + SSticker.mode?.respawn_time - world.time) * 0.1 + if(status_value <= 0) + . += "Respawn timer: READY" + else + . += "Respawn timer: [(status_value / 60) % 60]:[add_leading(num2text(status_value % 60), 2, "0")]" + if(SSticker.mode?.flags_round_type & MODE_INFESTATION) + if(larva_position) + . += "Position in larva candidate queue: [larva_position]" + var/datum/job/xeno_job = SSjob.GetJobType(/datum/job/xenomorph) + var/stored_larva = xeno_job.total_positions - xeno_job.current_positions + if(stored_larva) + . += "Burrowed larva: [stored_larva]" + //game end timer for patrol and sensor capture + var/patrol_end_countdown = SSticker.mode?.game_end_countdown() + if(patrol_end_countdown) + . += "Round End timer: [patrol_end_countdown]" + //respawn wave timer + var/patrol_wave_countdown = SSticker.mode?.wave_countdown() + if(patrol_wave_countdown) + . += "Respawn wave timer: [patrol_wave_countdown]" + var/datum/game_mode/combat_patrol/sensor_capture/sensor_mode = SSticker.mode + if(issensorcapturegamemode(SSticker.mode)) + . += "Activated Sensor Towers: [sensor_mode.sensors_activated]" /mob/dead/observer/verb/reenter_corpse() set category = "Ghost" @@ -387,6 +388,7 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER) client.view_size.set_default(get_screen_size(client.prefs.widescreenpref))//Let's reset so people can't become allseeing gods mind.transfer_to(old_mob, TRUE) + client.init_verbs() return TRUE /mob/dead/observer/verb/toggle_HUDs() diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index fa1cf9a88fcf4..b81209831bb52 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -1,5 +1,5 @@ /mob/living/carbon/human/Initialize(mapload) - verbs += /mob/living/proc/lay_down + add_verb(src, /mob/living/proc/lay_down) b_type = pick(7;"O-", 38;"O+", 6;"A-", 34;"A+", 2;"B-", 9;"B+", 1;"AB-", 3;"AB+") blood_type = b_type @@ -89,39 +89,38 @@ GLOB.dead_human_list -= src return ..() -/mob/living/carbon/human/Stat() +/mob/living/carbon/human/get_status_tab_items() . = ..() - if(statpanel("Game")) - var/eta_status = SSevacuation?.get_status_panel_eta() - if(eta_status) - stat("Evacuation in:", eta_status) - - //combat patrol timer - var/patrol_end_countdown = SSticker.mode?.game_end_countdown() - if(patrol_end_countdown) - stat("Round End timer:", patrol_end_countdown) - - if(internal) - stat("Internal Atmosphere Info", internal.name) - stat("Tank Pressure", internal.pressure) - stat("Distribution Pressure", internal.distribute_pressure) - - if(assigned_squad) - if(assigned_squad.primary_objective) - stat("Primary Objective: ", assigned_squad.primary_objective) - if(assigned_squad.secondary_objective) - stat("Secondary Objective: ", assigned_squad.secondary_objective) - - if(mobility_aura) - stat(null, "You are affected by a MOVE order.") - if(protection_aura) - stat(null, "You are affected by a HOLD order.") - if(marksman_aura) - stat(null, "You are affected by a FOCUS order.") - var/datum/game_mode/combat_patrol/sensor_capture/sensor_mode = SSticker.mode - if(issensorcapturegamemode(SSticker.mode)) - stat("Activated Sensor Towers:", sensor_mode.sensors_activated) + var/eta_status = SSevacuation?.get_status_panel_eta() + if(eta_status) + . += "Evacuation in: [eta_status]" + + //combat patrol timer + var/patrol_end_countdown = SSticker.mode?.game_end_countdown() + if(patrol_end_countdown) + . += "Round End timer: [patrol_end_countdown]" + + if(internal) + . += "Internal Atmosphere Info [internal.name]" + . += "Tank Pressure [internal.pressure]" + . += "Distribution Pressure [internal.distribute_pressure]" + + if(assigned_squad) + if(assigned_squad.primary_objective) + . += "Primary Objective: [assigned_squad.primary_objective]" + if(assigned_squad.secondary_objective) + . += "Secondary Objective: [assigned_squad.secondary_objective]" + + if(mobility_aura) + . += "You are affected by a MOVE order." + if(protection_aura) + . += "You are affected by a HOLD order." + if(marksman_aura) + . += "You are affected by a FOCUS order." + var/datum/game_mode/combat_patrol/sensor_capture/sensor_mode = SSticker.mode + if(issensorcapturegamemode(SSticker.mode)) + . += "Activated Sensor Towers: [sensor_mode.sensors_activated]" /mob/living/carbon/human/ex_act(severity) if(status_flags & GODMODE) diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index 2efe4f674c632..c9941aa061a3c 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -249,8 +249,7 @@ /// Removes all species-specific verbs and actions /datum/species/proc/remove_inherent_abilities(mob/living/carbon/human/H) if(inherent_verbs) - for(var/verb_path in inherent_verbs) - H.verbs -= verb_path + remove_verb(H, inherent_verbs) if(inherent_actions) for(var/action_path in inherent_actions) var/datum/action/old_species_action = H.actions_by_path[action_path] @@ -260,8 +259,7 @@ /// Adds all species-specific verbs and actions /datum/species/proc/add_inherent_abilities(mob/living/carbon/human/H) if(inherent_verbs) - for(var/verb_path in inherent_verbs) - H.verbs |= verb_path + add_verb(H, inherent_verbs) if(inherent_actions) for(var/action_path in inherent_actions) var/datum/action/new_species_action = new action_path(H) diff --git a/code/modules/mob/living/carbon/xenomorph/castes/carrier/carrier.dm b/code/modules/mob/living/carbon/xenomorph/castes/carrier/carrier.dm index d4003fedd9d10..aee9da70a6b39 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/carrier/carrier.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/carrier/carrier.dm @@ -27,10 +27,9 @@ . = ..() hugger_overlays_icon = mutable_appearance('icons/Xeno/2x2_Xenos.dmi',"empty") -/mob/living/carbon/xenomorph/carrier/Stat() +/mob/living/carbon/xenomorph/carrier/get_status_tab_items() . = ..() - if(statpanel("Game")) - stat("Stored Huggers:", "[huggers] / [xeno_caste.huggers_max]") + . += "Stored Huggers: [huggers] / [xeno_caste.huggers_max]" /mob/living/carbon/xenomorph/carrier/update_icons() . = ..() diff --git a/code/modules/mob/living/carbon/xenomorph/castes/hivelord/hivelord.dm b/code/modules/mob/living/carbon/xenomorph/castes/hivelord/hivelord.dm index f8ed8d097f1a5..5455834c84a82 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/hivelord/hivelord.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/hivelord/hivelord.dm @@ -24,8 +24,6 @@ update_spits() -/mob/living/carbon/xenomorph/hivelord/Stat() +/mob/living/carbon/xenomorph/hivelord/get_status_tab_items() . = ..() - - if(statpanel("Game")) - stat("Active Tunnel Sets:", "[LAZYLEN(tunnels)] / [HIVELORD_TUNNEL_SET_LIMIT]") + . += "Active Tunnel Sets: [LAZYLEN(tunnels)] / [HIVELORD_TUNNEL_SET_LIMIT]" diff --git a/code/modules/mob/living/carbon/xenomorph/xenoprocs.dm b/code/modules/mob/living/carbon/xenomorph/xenoprocs.dm index 0129c9d1e8515..620aeb9012ca6 100644 --- a/code/modules/mob/living/carbon/xenomorph/xenoprocs.dm +++ b/code/modules/mob/living/carbon/xenomorph/xenoprocs.dm @@ -98,30 +98,27 @@ //Adds stuff to your "Status" pane -- Specific castes can have their own, like carrier hugger count //Those are dealt with in their caste files. -/mob/living/carbon/xenomorph/Stat() +/mob/living/carbon/xenomorph/get_status_tab_items() . = ..() - if(!statpanel("Game")) - return - if(!(xeno_caste.caste_flags & CASTE_EVOLUTION_ALLOWED)) - stat("Evolve Progress:", "(FINISHED)") + . += "Evolve Progress: (FINISHED)" else if(!hive.check_ruler()) - stat("Evolve Progress:", "(HALTED - NO RULER)") + . += "Evolve Progress: (HALTED - NO RULER)" else - stat("Evolve Progress:", "[evolution_stored]/[xeno_caste.evolution_threshold]") + . += "Evolve Progress: [evolution_stored]/[xeno_caste.evolution_threshold]" if(upgrade_possible()) - stat("Upgrade Progress:", "[upgrade_stored]/[xeno_caste.upgrade_threshold]") + . += "Upgrade Progress: [upgrade_stored]/[xeno_caste.upgrade_threshold]" else //Upgrade process finished or impossible - stat("Upgrade Progress:", "(FINISHED)") + . += "Upgrade Progress: (FINISHED)" - stat("Health:", "[overheal ? "[overheal] + ": ""][health]/[maxHealth]") //Changes with balance scalar, can't just use the caste + . += "Health: [overheal ? "[overheal] + ": ""][health]/[maxHealth]" //Changes with balance scalar, can't just use the caste if(xeno_caste.plasma_max > 0) - stat("Plasma:", "[plasma_stored]/[xeno_caste.plasma_max]") + . += "Plasma: [plasma_stored]/[xeno_caste.plasma_max]" - stat("Sunder:", "[100-sunder]% armor left") + . += "Sunder: [100-sunder]% armor left" //Very weak <= 1.0, weak <= 2.0, no modifier 2-3, strong <= 3.5, very strong <= 4.5 var/msg_holder = "" @@ -137,7 +134,7 @@ msg_holder = "Strong" if(4.0 to INFINITY) msg_holder = "Very strong" - stat("[AURA_XENO_FRENZY] pheromone strength:", msg_holder) + . += "[AURA_XENO_FRENZY] pheromone strength: [msg_holder]" if(warding_aura) switch(warding_aura) if(-INFINITY to 1.0) @@ -150,7 +147,7 @@ msg_holder = "Strong" if(4.0 to INFINITY) msg_holder = "Very strong" - stat("[AURA_XENO_WARDING] pheromone strength:", msg_holder) + . += "[AURA_XENO_WARDING] pheromone strength: [msg_holder]" if(recovery_aura) switch(recovery_aura) if(-INFINITY to 1.0) @@ -163,12 +160,12 @@ msg_holder = "Strong" if(4.0 to INFINITY) msg_holder = "Very strong" - stat("[AURA_XENO_RECOVERY] pheromone strength:", msg_holder) + . += "[AURA_XENO_RECOVERY] pheromone strength: [msg_holder]" if(hivenumber == XENO_HIVE_NORMAL) var/hivemind_countdown = SSticker.mode?.get_hivemind_collapse_countdown() if(hivemind_countdown) - stat("Orphan hivemind collapse timer:", hivemind_countdown) + . += "Orphan hivemind collapse timer: [hivemind_countdown]" //A simple handler for checking your state. Used in pretty much all the procs. /mob/living/carbon/xenomorph/proc/check_state() @@ -199,13 +196,12 @@ /mob/living/carbon/xenomorph/proc/remove_inherent_verbs() if(inherent_verbs) for(var/verb_path in inherent_verbs) - verbs -= verb_path + remove_verb(verbs, verb_path) //Add all your inherent caste verbs and procs. Used in evolution. /mob/living/carbon/xenomorph/proc/add_inherent_verbs() if(inherent_verbs) - for(var/verb_path in inherent_verbs) - verbs |= verb_path + add_verb(src, inherent_verbs) //Adds or removes a delay to movement based on your caste. If speed = 0 then it shouldn't do much. @@ -494,7 +490,7 @@ return FALSE /mob/living/carbon/xenomorph/proc/setup_verbs() - verbs += /mob/living/proc/lay_down + add_verb(src, /mob/living/proc/lay_down) /mob/living/carbon/xenomorph/hivemind/setup_verbs() return diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 2ed55c44e8771..52d9904f00028 100755 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -350,36 +350,34 @@ lighting_alpha = initial(lighting_alpha) // yes you really have to change both the eye and the ai vars -/mob/living/silicon/ai/Stat() +/mob/living/silicon/ai/get_status_tab_items() . = ..() - if(statpanel("Game")) - - if(stat != CONSCIOUS) - stat("System status:", "Nonfunctional") - return + if(stat != CONSCIOUS) + . += "System status: Nonfunctional" + return - stat("System integrity:", "[(health + 100) / 2]%") - stat("
- Operation information -
") - stat("Current orbit:", "[GLOB.current_orbit]") + . += "System integrity: [(health + 100) / 2]%" + . += "
- Operation information -
" + . += "Current orbit: [GLOB.current_orbit]" - if(!GLOB.marine_main_ship?.orbital_cannon?.chambered_tray) - stat("Orbital bombardment status:", "No ammo chambered in the cannon.
") - else - stat("Orbital bombardment warhead:", "[GLOB.marine_main_ship.orbital_cannon.tray.warhead.name] Detected
") + if(!GLOB.marine_main_ship?.orbital_cannon?.chambered_tray) + . += "Orbital bombardment status: No ammo chambered in the cannon.
" + else + . += "Orbital bombardment warhead: [GLOB.marine_main_ship.orbital_cannon.tray.warhead.name] Detected
" - stat("Current supply points:", "[round(SSpoints.supply_points[FACTION_TERRAGOV])]") + . += "Current supply points: [round(SSpoints.supply_points[FACTION_TERRAGOV])]" - stat("Current dropship points:", "[round(SSpoints.dropship_points)]") + . += "Current dropship points: [round(SSpoints.dropship_points)]" - stat("Current alert level:", "[GLOB.marine_main_ship.get_security_level()]") + . += "Current alert level: [GLOB.marine_main_ship.get_security_level()]" - stat("Number of living marines:", "[SSticker.mode.count_humans_and_xenos()[1]]") + . += "Number of living marines: [SSticker.mode.count_humans_and_xenos()[1]]" - if(GLOB.marine_main_ship?.rail_gun?.last_firing_ai + COOLDOWN_RAILGUN_FIRE > world.time) - stat("Railgun status:", "Cooling down, next fire in [(GLOB.marine_main_ship?.rail_gun?.last_firing_ai + COOLDOWN_RAILGUN_FIRE - world.time)/10] seconds.") - else - stat("Railgun status:", "Railgun is ready to fire.") + if(GLOB.marine_main_ship?.rail_gun?.last_firing_ai + COOLDOWN_RAILGUN_FIRE > world.time) + . += "Railgun status: Cooling down, next fire in [(GLOB.marine_main_ship?.rail_gun?.last_firing_ai + COOLDOWN_RAILGUN_FIRE - world.time)/10] seconds." + else + . += "Railgun status: Railgun is ready to fire." /mob/living/silicon/ai/fully_replace_character_name(oldname, newname) . = ..() diff --git a/code/modules/mob/living/simple_animal/friendly/parrot.dm b/code/modules/mob/living/simple_animal/friendly/parrot.dm index afa0a662c0ec3..f30a6ab2f1d59 100644 --- a/code/modules/mob/living/simple_animal/friendly/parrot.dm +++ b/code/modules/mob/living/simple_animal/friendly/parrot.dm @@ -95,10 +95,9 @@ GLOBAL_LIST_INIT(strippable_parrot_items, create_strippable_list(list( return ..() -/mob/living/simple_animal/parrot/Stat() +/mob/living/simple_animal/parrot/get_status_tab_items() . = ..() - if(statpanel("Game")) - stat("Held Item", held_item) + . += "Held Item: [held_item]" /mob/living/simple_animal/parrot/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans, message_mode) diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 8bc0aca07c4a4..44b35686f276b 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -216,11 +216,9 @@ return TRUE -/mob/living/simple_animal/Stat() +/mob/living/simple_animal/get_status_tab_items() . = ..() - - if(statpanel("Game")) - stat("Health:", "[round((health / maxHealth) * 100)]%") + . += "Health: [round((health / maxHealth) * 100)]%" /mob/living/simple_animal/ex_act(severity) diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm index 40b530a618aad..1a2f8c86a778e 100644 --- a/code/modules/mob/login.dm +++ b/code/modules/mob/login.dm @@ -65,3 +65,4 @@ log_mob_tag("\[[tag]\] NEW OWNER: [key_name(src)]") SEND_SIGNAL(src, COMSIG_MOB_LOGIN) SEND_GLOBAL_SIGNAL(COMSIG_GLOB_MOB_LOGIN, src) + client.init_verbs() diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index fdbc3dfd8b010..14bce229952a9 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -40,72 +40,6 @@ log_mob_tag("\[[tag]\] CREATED: [key_name(src)]") become_hearing_sensitive() - -/mob/Stat() - . = ..() - if(statpanel("Status")) - if(GLOB.round_id) - stat("Round ID:", GLOB.round_id) - stat("Operation Time:", stationTimestamp("hh:mm")) - stat("Current Map:", length(SSmapping.configs) ? SSmapping.configs[GROUND_MAP].map_name : "Loading...") - stat("Current Ship:", length(SSmapping.configs) ? SSmapping.configs[SHIP_MAP].map_name : "Loading...") - stat("Game Mode:", "[GLOB.master_mode]") - - if(statpanel("Game")) - if(client) - stat("Ping:", "[round(client.lastping, 1)]ms (Average: [round(client.avgping, 1)]ms)") - stat("Time Dilation:", "[round(SStime_track.time_dilation_current,1)]% AVG:([round(SStime_track.time_dilation_avg_fast,1)]%, [round(SStime_track.time_dilation_avg,1)]%, [round(SStime_track.time_dilation_avg_slow,1)]%)") - - if(client?.holder?.rank?.rights) - if(client.holder.rank.rights & (R_DEBUG)) - if(statpanel("MC")) - stat("CPU:", "[world.cpu]") - stat("Instances:", "[num2text(length(world.contents), 10)]") - stat("World Time:", "[world.time]") - GLOB.stat_entry() - config.stat_entry() - GLOB.cameranet.stat_entry() - stat(null) - if(Master) - Master.stat_entry() - else - stat("Master Controller:", "ERROR") - if(Failsafe) - Failsafe.stat_entry() - else - stat("Failsafe Controller:", "ERROR") - if(Master) - stat(null) - for(var/datum/controller/subsystem/SS in Master.subsystems) - SS.stat_entry() - if(client.holder.rank.rights & (R_ADMIN|R_MENTOR)) - if(statpanel("Tickets")) - GLOB.ahelp_tickets.stat_entry() - if(length(GLOB.sdql2_queries)) - if(statpanel("SDQL2")) - stat("Access Global SDQL2 List", GLOB.sdql2_vv_statobj) - for(var/i in GLOB.sdql2_queries) - var/datum/SDQL2_query/Q = i - Q.generate_stat() - - if(listed_turf && client) - if(!TurfAdjacent(listed_turf)) - listed_turf = null - else - statpanel(listed_turf.name, null, listed_turf) - var/list/overrides = list() - for(var/image/I in client.images) - if(I.loc && I.loc.loc == listed_turf && I.override) - overrides += I.loc - for(var/atom/A in listed_turf) - if(!A.mouse_opacity) - continue - if(A.invisibility > see_invisible) - continue - if(length(overrides) && (A in overrides)) - continue - statpanel(listed_turf.name, null, A) - /mob/proc/show_message(msg, type, alt_msg, alt_type, avoid_highlight) if(!client) return FALSE @@ -712,6 +646,10 @@ /mob/proc/is_muzzled() return FALSE +/// Adds this list to the output to the stat browser +/mob/proc/get_status_tab_items() + . = list("") //we want to offset unique stuff from standard stuff + SEND_SIGNAL(src, COMSIG_MOB_GET_STATUS_TAB_ITEMS, .) // reset_perspective(thing) set the eye to the thing (if it's equal to current default reset to mob perspective) // reset_perspective(null) set eye to common default : mob on turf, loc otherwise diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm index 7a49f326816e5..8ac11ae5aa67c 100644 --- a/code/modules/mob/new_player/new_player.dm +++ b/code/modules/mob/new_player/new_player.dm @@ -69,24 +69,23 @@ return null return output -/mob/new_player/Stat() +/mob/new_player/get_status_tab_items() . = ..() if(!SSticker) return - if(statpanel("Status")) - - if(SSticker.current_state == GAME_STATE_PREGAME) - stat("Time To Start:", "[SSticker.time_left > 0 ? SSticker.GetTimeLeft() : "(DELAYED)"]") - stat("Players: [length(GLOB.player_list)]", "Players Ready: [length(GLOB.ready_players)]") - for(var/i in GLOB.player_list) - if(isnewplayer(i)) - var/mob/new_player/N = i - stat("[N.client?.holder?.fakekey ? N.client.holder.fakekey : N.key]", N.ready ? "Playing" : "") - else if(isobserver(i)) - var/mob/dead/observer/O = i - stat("[O.client?.holder?.fakekey ? O.client.holder.fakekey : O.key]", "Observing") + if(SSticker.current_state == GAME_STATE_PREGAME) + . += "Time To Start: [SSticker.time_left > 0 ? SSticker.GetTimeLeft() : "(DELAYED)"]" + . += "Players: [length(GLOB.player_list)]" + . += "Players Ready: [length(GLOB.ready_players)]" + for(var/i in GLOB.player_list) + if(isnewplayer(i)) + var/mob/new_player/N = i + . += "[N.client?.holder?.fakekey ? N.client.holder.fakekey : N.key][N.ready ? " Playing" : ""]" + else if(isobserver(i)) + var/mob/dead/observer/O = i + . += "[O.client?.holder?.fakekey ? O.client.holder.fakekey : O.key] Observing" /mob/new_player/Topic(href, href_list[]) @@ -295,6 +294,7 @@ var/spawn_type = assigned_role.return_spawn_type(client.prefs) var/mob/living/spawning_living = new spawn_type() GLOB.joined_player_list += ckey + client.init_verbs() spawning_living.on_spawn(src) @@ -394,6 +394,7 @@ observer.name = observer.real_name mind.transfer_to(observer, TRUE) + observer.client?.init_verbs() qdel(src) ///Toggles the new players ready state diff --git a/html/statbrowser.css b/html/statbrowser.css new file mode 100644 index 0000000000000..dc693f42f756b --- /dev/null +++ b/html/statbrowser.css @@ -0,0 +1,227 @@ +body { + font-family: Verdana, Geneva, Tahoma, sans-serif; + font-size: 12px !important; + margin: 0 !important; + padding: 0 !important; + overflow-x: hidden; + overflow-y: scroll; +} + +body.dark { + background-color: #131313; + color: #b2c4dd; + scrollbar-base-color: #1c1c1c; + scrollbar-face-color: #3b3b3b; + scrollbar-3dlight-color: #252525; + scrollbar-highlight-color: #252525; + scrollbar-track-color: #1c1c1c; + scrollbar-arrow-color: #929292; + scrollbar-shadow-color: #3b3b3b; +} + +#menu { + background-color: #F0F0F0; + position: fixed; + width: 100%; + z-index: 100; +} + +.dark #menu { + background-color: #202020; +} + +#statcontent { + padding: 7px 7px 7px 7px; +} + +a { + color: black; + text-decoration: none +} + +.dark a { + color: #b2c4dd; +} + +a:hover, +.dark a:hover { + text-decoration: underline; +} + +ul { + list-style-type: none; + margin: 0; + padding: 0; + background-color: #333; +} + +li { + float: left; +} + +li a { + display: block; + color: white; + text-align: center; + padding: 14px 16px; + text-decoration: none; +} + +li a:hover:not(.active) { + background-color: #111; +} + +.button-container { + display: inline-flex; + flex-wrap: wrap-reverse; + flex-direction: row; + align-items: flex-start; + overflow-x: hidden; + white-space: pre-wrap; + padding: 0 4px; +} + +.button { + background-color: #dfdfdf; + border: 1px solid #cecece; + border-bottom-width: 2px; + color: rgba(0, 0, 0, 0.7); + padding: 6px 4px 4px; + text-align: center; + text-decoration: none; + font-size: 12px; + margin: 0; + cursor: pointer; + transition-duration: 100ms; + order: 3; + min-width: 40px; +} + +.dark button { + background-color: #222222; + border-color: #343434; + color: rgba(255, 255, 255, 0.5); +} + +.button:hover { + background-color: #ececec; + transition-duration: 0; +} + +.dark button:hover { + background-color: #2e2e2e; +} + +.button:active, +.button.active { + background-color: #ffffff; + color: black; + border-top-color: #cecece; + border-left-color: #cecece; + border-right-color: #cecece; + border-bottom-color: #ffffff; +} + +.dark .button:active, +.dark .button.active { + background-color: #444444; + color: white; + border-top-color: #343434; + border-left-color: #343434; + border-right-color: #343434; + border-bottom-color: #ffffff; +} + +.grid-container { + margin: -2px; + margin-right: -15px; +} + +.grid-item { + position: relative; + display: inline-block; + width: 100%; + box-sizing: border-box; + overflow: visible; + padding: 3px 2px; + text-decoration: none; +} + +@media only screen and (min-width: 300px) { + .grid-item { + width: 50%; + } +} + +@media only screen and (min-width: 430px) { + .grid-item { + width: 33%; + } +} + +@media only screen and (min-width: 560px) { + .grid-item { + width: 25%; + } +} + +@media only screen and (min-width: 770px) { + .grid-item { + width: 20%; + } +} + +.grid-item:hover { + z-index: 1; +} + +.grid-item:hover .grid-item-text { + width: auto; + text-decoration: underline; +} + +.grid-item-text { + display: inline-block; + width: 100%; + background-color: #ffffff; + margin: 0 -6px; + padding: 0 6px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + pointer-events: none; +} + +.dark .grid-item-text { + background-color: #131313; +} + +.link { + display: inline; + background: none; + border: none; + padding: 7px 14px; + color: black; + text-decoration: none; + cursor: pointer; + font-size: 13px; + margin: 2px 2px; +} + +.dark .link { + color: #abc6ec; +} + +.link:hover { + text-decoration: underline; +} + +img { + -ms-interpolation-mode: nearest-neighbor; + image-rendering: pixelated; +} + +.interview_panel_controls, +.interview_panel_stats { + margin-bottom: 10px; +} diff --git a/html/statbrowser.html b/html/statbrowser.html new file mode 100644 index 0000000000000..1aea8811d58a0 --- /dev/null +++ b/html/statbrowser.html @@ -0,0 +1,3 @@ + +
+
diff --git a/html/statbrowser.js b/html/statbrowser.js new file mode 100644 index 0000000000000..1a8fba254ad61 --- /dev/null +++ b/html/statbrowser.js @@ -0,0 +1,1003 @@ +// Polyfills and compatibility ------------------------------------------------ +var decoder = decodeURIComponent || unescape; +if (!Array.prototype.includes) { + Array.prototype.includes = function (thing) { + for (var i = 0; i < this.length; i++) { + if (this[i] == thing) return true; + } + return false; + } +} +if (!String.prototype.trim) { + String.prototype.trim = function () { + return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); + }; +} + +// Status panel implementation ------------------------------------------------ +var status_tab_parts = ["Loading..."]; +var current_tab = null; +var mc_tab_parts = [["Loading...", ""]]; +var href_token = null; +var spells = []; +var spell_tabs = []; +var verb_tabs = []; +var verbs = [["", ""]]; // list with a list inside +var tickets = []; +var interviewManager = { status: "", interviews: [] }; +var sdql2 = []; +var permanent_tabs = []; // tabs that won't be cleared by wipes +var turfcontents = []; +var turfname = ""; +var imageRetryDelay = 500; +var imageRetryLimit = 50; +var menu = document.getElementById('menu'); +var under_menu = document.getElementById('under_menu'); +var statcontentdiv = document.getElementById('statcontent'); +var storedimages = []; +var split_admin_tabs = false; + +// Any BYOND commands that could result in the client's focus changing go through this +// to ensure that when we relinquish our focus, we don't do it after the result of +// a command has already taken focus for itself. +function run_after_focus(callback) { + setTimeout(callback, 0); +} + +function createStatusTab(name) { + if (name.indexOf(".") != -1) { + var splitName = name.split("."); + if (split_admin_tabs && splitName[0] === "Admin") + name = splitName[1]; + else + name = splitName[0]; + } + if (document.getElementById(name) || name.trim() == "") { + return; + } + if (!verb_tabs.includes(name) && !permanent_tabs.includes(name)) { + return; + } + var B = document.createElement("BUTTON"); + B.onclick = function () { + tab_change(name); + this.blur(); + }; + B.id = name; + B.textContent = name; + B.className = "button"; + //ORDERING ALPHABETICALLY + B.style.order = name.charCodeAt(0); + if (name == "Status" || name == "MC") { + B.style.order = name == "Status" ? 1 : 2; + } + //END ORDERING + menu.appendChild(B); + SendTabToByond(name); + under_menu.style.height = menu.clientHeight + 'px'; +} + +function removeStatusTab(name) { + if (!document.getElementById(name) || permanent_tabs.includes(name)) { + return; + } + for (var i = verb_tabs.length - 1; i >= 0; --i) { + if (verb_tabs[i] == name) { + verb_tabs.splice(i, 1); + } + } + menu.removeChild(document.getElementById(name)); + TakeTabFromByond(name); + under_menu.style.height = menu.clientHeight + 'px'; +} + +function sortVerbs() { + verbs.sort(function (a, b) { + var selector = a[0] == b[0] ? 1 : 0; + if (a[selector].toUpperCase() < b[selector].toUpperCase()) { + return 1; + } + else if (a[selector].toUpperCase() > b[selector].toUpperCase()) { + return -1; + } + return 0; + }) +} + +window.onresize = function () { + under_menu.style.height = menu.clientHeight + 'px'; +} + +function addPermanentTab(name) { + if (!permanent_tabs.includes(name)) { + permanent_tabs.push(name); + } + createStatusTab(name); +} + +function removePermanentTab(name) { + for (var i = permanent_tabs.length - 1; i >= 0; --i) { + if (permanent_tabs[i] == name) { + permanent_tabs.splice(i, 1); + } + } + removeStatusTab(name); +} + +function checkStatusTab() { + for (var i = 0; i < menu.children.length; i++) { + if (!verb_tabs.includes(menu.children[i].id) && !permanent_tabs.includes(menu.children[i].id)) { + menu.removeChild(menu.children[i]); + } + } +} + +function remove_verb(v) { + var verb_to_remove = v; // to_remove = [verb:category, verb:name] + for (var i = verbs.length - 1; i >= 0; i--) { + var part_to_remove = verbs[i]; + if (part_to_remove[1] == verb_to_remove[1]) { + verbs.splice(i, 1) + } + } +} + +function check_verbs() { + for (var v = verb_tabs.length - 1; v >= 0; v--) { + verbs_cat_check(verb_tabs[v]); + } +} + +function verbs_cat_check(cat) { + var tabCat = cat; + if (cat.indexOf(".") != -1) { + var splitName = cat.split("."); + if (split_admin_tabs && splitName[0] === "Admin") + tabCat = splitName[1]; + else + tabCat = splitName[0]; + } + var verbs_in_cat = 0; + var verbcat = ""; + if (!verb_tabs.includes(tabCat)) { + removeStatusTab(tabCat); + return; + } + for (var v = 0; v < verbs.length; v++) { + var part = verbs[v]; + verbcat = part[0]; + if (verbcat.indexOf(".") != -1) { + var splitName = verbcat.split("."); + if (split_admin_tabs && splitName[0] === "Admin") + verbcat = splitName[1]; + else + verbcat = splitName[0]; + } + if (verbcat != tabCat || verbcat.trim() == "") { + continue; + } + else { + verbs_in_cat = 1; + break; // we only need one + } + } + if (verbs_in_cat != 1) { + removeStatusTab(tabCat); + if (current_tab == tabCat) + tab_change("Status"); + } +} + +function findVerbindex(name, verblist) { + for (var i = 0; i < verblist.length; i++) { + var part = verblist[i]; + if (part[1] == name) + return i; + } +} +function wipe_verbs() { + verbs = [["", ""]]; + verb_tabs = []; + checkStatusTab(); // remove all empty verb tabs +} + +function update_verbs() { + wipe_verbs(); + Byond.sendMessage("Update-Verbs"); +} + +function SendTabsToByond() { + var tabstosend = []; + tabstosend = tabstosend.concat(permanent_tabs, verb_tabs); + for (var i = 0; i < tabstosend.length; i++) { + SendTabToByond(tabstosend[i]); + } +} + +function SendTabToByond(tab) { + Byond.sendMessage("Send-Tabs", {tab: tab}); +} + +//Byond can't have this tab anymore since we're removing it +function TakeTabFromByond(tab) { + Byond.sendMessage("Remove-Tabs", {tab: tab}); +} + +function spell_cat_check(cat) { + var spells_in_cat = 0; + var spellcat = ""; + for (var s = 0; s < spells.length; s++) { + var spell = spells[s]; + spellcat = spell[0]; + if (spellcat == cat) { + spells_in_cat++; + } + } + if (spells_in_cat < 1) { + removeStatusTab(cat); + } +} + +function tab_change(tab) { + if (tab == current_tab) return; + if (document.getElementById(current_tab)) + document.getElementById(current_tab).className = "button"; // disable active on last button + current_tab = tab; + set_byond_tab(tab); + if (document.getElementById(tab)) + document.getElementById(tab).className = "button active"; // make current button active + var spell_tabs_thingy = (spell_tabs.includes(tab)); + var verb_tabs_thingy = (verb_tabs.includes(tab)); + if (tab == "Status") { + draw_status(); + } else if (tab == "MC") { + draw_mc(); + } else if (spell_tabs_thingy) { + draw_spells(tab); + } else if (verb_tabs_thingy) { + draw_verbs(tab); + } else if (tab == "Debug Stat Panel") { + draw_debug(); + } else if (tab == "Tickets") { + draw_tickets(); + //draw_interviews(); + } else if (tab == "SDQL2") { + draw_sdql2(); + } else if (tab == turfname) { + draw_listedturf(); + } else { + statcontentdiv.textContext = "Loading..."; + } + Byond.winset(Byond.windowId, { + 'is-visible': true, + }); +} + +function set_byond_tab(tab) { + Byond.sendMessage("Set-Tab", {tab: tab}); +} + +function draw_debug() { + statcontentdiv.textContent = ""; + var wipeverbstabs = document.createElement("div"); + var link = document.createElement("a"); + link.onclick = function () { wipe_verbs() }; + link.textContent = "Wipe All Verbs"; + wipeverbstabs.appendChild(link); + document.getElementById("statcontent").appendChild(wipeverbstabs); + var wipeUpdateVerbsTabs = document.createElement("div"); + var updateLink = document.createElement("a"); + updateLink.onclick = function () { update_verbs() }; + updateLink.textContent = "Wipe and Update All Verbs"; + wipeUpdateVerbsTabs.appendChild(updateLink); + document.getElementById("statcontent").appendChild(wipeUpdateVerbsTabs); + var text = document.createElement("div"); + text.textContent = "Verb Tabs:"; + document.getElementById("statcontent").appendChild(text); + var table1 = document.createElement("table"); + for (var i = 0; i < verb_tabs.length; i++) { + var part = verb_tabs[i]; + // Hide subgroups except admin subgroups if they are split + if (verb_tabs[i].lastIndexOf(".") != -1) { + var splitName = verb_tabs[i].split("."); + if (split_admin_tabs && splitName[0] === "Admin") + part = splitName[1]; + else + continue; + } + var tr = document.createElement("tr"); + var td1 = document.createElement("td"); + td1.textContent = part; + var a = document.createElement("a"); + a.onclick = function (part) { + return function () { removeStatusTab(part) }; + }(part); + a.textContent = " Delete Tab " + part; + td1.appendChild(a); + tr.appendChild(td1); + table1.appendChild(tr); + } + document.getElementById("statcontent").appendChild(table1); + var header2 = document.createElement("div"); + header2.textContent = "Verbs:"; + document.getElementById("statcontent").appendChild(header2); + var table2 = document.createElement("table"); + for (var v = 0; v < verbs.length; v++) { + var part2 = verbs[v]; + var trr = document.createElement("tr"); + var tdd1 = document.createElement("td"); + tdd1.textContent = part2[0]; + var tdd2 = document.createElement("td"); + tdd2.textContent = part2[1]; + trr.appendChild(tdd1); + trr.appendChild(tdd2); + table2.appendChild(trr); + } + document.getElementById("statcontent").appendChild(table2); + var text3 = document.createElement("div"); + text3.textContent = "Permanent Tabs:"; + document.getElementById("statcontent").appendChild(text3); + var table3 = document.createElement("table"); + for (var i = 0; i < permanent_tabs.length; i++) { + var part3 = permanent_tabs[i]; + var trrr = document.createElement("tr"); + var tddd1 = document.createElement("td"); + tddd1.textContent = part3; + trrr.appendChild(tddd1); + table3.appendChild(trrr); + } + document.getElementById("statcontent").appendChild(table3); + +} +function draw_status() { + if (!document.getElementById("Status")) { + createStatusTab("Status"); + current_tab = "Status"; + } + statcontentdiv.textContent = ''; + for (var i = 0; i < status_tab_parts.length; i++) { + if (status_tab_parts[i].trim() == "") { + document.getElementById("statcontent").appendChild(document.createElement("br")); + } else { + var div = document.createElement("div"); + div.textContent = status_tab_parts[i]; + document.getElementById("statcontent").appendChild(div); + } + } + if (verb_tabs.length == 0 || !verbs) { + Byond.command("Fix-Stat-Panel"); + } +} + +function draw_mc() { + statcontentdiv.textContent = ""; + var table = document.createElement("table"); + for (var i = 0; i < mc_tab_parts.length; i++) { + var part = mc_tab_parts[i]; + var tr = document.createElement("tr"); + var td1 = document.createElement("td"); + td1.textContent = part[0]; + var td2 = document.createElement("td"); + if (part[2]) { + var a = document.createElement("a"); + a.href = "?_src_=vars;admin_token=" + href_token + ";Vars=" + part[2]; + a.textContent = part[1]; + td2.appendChild(a); + } else { + td2.textContent = part[1]; + } + tr.appendChild(td1); + tr.appendChild(td2); + table.appendChild(tr); + } + document.getElementById("statcontent").appendChild(table); +} + +function remove_tickets() { + if (tickets) { + tickets = []; + removePermanentTab("Tickets"); + if (current_tab == "Tickets") + tab_change("Status"); + } + checkStatusTab(); +} + +function remove_sdql2() { + if (sdql2) { + sdql2 = []; + removePermanentTab("SDQL2"); + if (current_tab == "SDQL2") + tab_change("Status"); + } + checkStatusTab(); +} + +function remove_interviews() { + if (tickets) { + tickets = []; + } + checkStatusTab(); +} + +function iconError(e) { + if(current_tab != turfname) { + return; + } + setTimeout(function () { + var node = e.target; + var current_attempts = Number(node.getAttribute("data-attempts")) || 0 + if (current_attempts > imageRetryLimit) { + return; + } + var src = node.src; + node.src = null; + node.src = src + '#' + current_attempts; + node.setAttribute("data-attempts", current_attempts + 1) + draw_listedturf(); + }, imageRetryDelay); +} + +function draw_listedturf() { + statcontentdiv.textContent = ""; + var table = document.createElement("table"); + for (var i = 0; i < turfcontents.length; i++) { + var part = turfcontents[i]; + if (storedimages[part[1]] == null && part[2]) { + var img = document.createElement("img"); + img.src = part[2]; + img.id = part[1]; + storedimages[part[1]] = part[2]; + img.onerror = iconError; + table.appendChild(img); + } else { + var img = document.createElement("img"); + img.onerror = iconError; + img.src = storedimages[part[1]]; + img.id = part[1]; + table.appendChild(img); + } + var b = document.createElement("div"); + var clickcatcher = ""; + b.className = "link"; + b.onmousedown = function (part) { + // The outer function is used to close over a fresh "part" variable, + // rather than every onmousedown getting the "part" of the last entry. + return function (e) { + e.preventDefault(); + clickcatcher = "?src=" + part[1]; + switch (e.button) { + case 1: + clickcatcher += ";statpanel_item_click=middle" + break; + case 2: + clickcatcher += ";statpanel_item_click=right" + break; + default: + clickcatcher += ";statpanel_item_click=left" + } + if (e.shiftKey) { + clickcatcher += ";statpanel_item_shiftclick=1"; + } + if (e.ctrlKey) { + clickcatcher += ";statpanel_item_ctrlclick=1"; + } + if (e.altKey) { + clickcatcher += ";statpanel_item_altclick=1"; + } + window.location.href = clickcatcher; + } + }(part); + b.textContent = part[0]; + table.appendChild(b); + table.appendChild(document.createElement("br")); + } + document.getElementById("statcontent").appendChild(table); +} + +function remove_listedturf() { + removePermanentTab(turfname); + checkStatusTab(); + if (current_tab == turfname) { + tab_change("Status"); + } +} + +function remove_mc() { + removePermanentTab("MC"); + if (current_tab == "MC") { + tab_change("Status"); + } +}; + +function draw_sdql2() { + statcontentdiv.textContent = ""; + var table = document.createElement("table"); + for (var i = 0; i < sdql2.length; i++) { + var part = sdql2[i]; + var tr = document.createElement("tr"); + var td1 = document.createElement("td"); + td1.textContent = part[0]; + var td2 = document.createElement("td"); + if (part[2]) { + var a = document.createElement("a"); + a.href = "?src=" + part[2] + ";statpanel_item_click=left"; + a.textContent = part[1]; + td2.appendChild(a); + } else { + td2.textContent = part[1]; + } + tr.appendChild(td1); + tr.appendChild(td2); + table.appendChild(tr); + } + document.getElementById("statcontent").appendChild(table); +} + +function draw_tickets() { + statcontentdiv.textContent = ""; + var table = document.createElement("table"); + if (!tickets) { + return; + } + for (var i = 0; i < tickets.length; i++) { + var part = tickets[i]; + var tr = document.createElement("tr"); + var td1 = document.createElement("td"); + td1.textContent = part[0]; + var td2 = document.createElement("td"); + if (part[2]) { + var a = document.createElement("a"); + a.href = "?_src_=holder;admin_token=" + href_token + ";ahelp=" + part[2] + ";ahelp_action=ticket;statpanel_item_click=left;action=ticket"; + a.textContent = part[1]; + td2.appendChild(a); + } else if (part[3]) { + var a = document.createElement("a"); + a.href = "?src=" + part[3] + ";statpanel_item_click=left"; + a.textContent = part[1]; + td2.appendChild(a); + } else { + td2.textContent = part[1]; + } + tr.appendChild(td1); + tr.appendChild(td2); + table.appendChild(tr); + } + document.getElementById("statcontent").appendChild(table); +} + +function draw_interviews() { + var body = document.createElement("div"); + var header = document.createElement("h3"); + header.textContent = "Interviews"; + body.appendChild(header); + var manDiv = document.createElement("div"); + manDiv.className = "interview_panel_controls" + var manLink = document.createElement("a"); + manLink.textContent = "Open Interview Manager Panel"; + manLink.href = "?_src_=holder;admin_token=" + href_token + ";interview_man=1;statpanel_item_click=left"; + manDiv.appendChild(manLink); + body.appendChild(manDiv); + + // List interview stats + var statsDiv = document.createElement("table"); + statsDiv.className = "interview_panel_stats"; + for (var key in interviewManager.status) { + var d = document.createElement("div"); + var tr = document.createElement("tr"); + var stat_name = document.createElement("td"); + var stat_text = document.createElement("td"); + stat_name.textContent = key; + stat_text.textContent = interviewManager.status[key]; + tr.appendChild(stat_name); + tr.appendChild(stat_text); + statsDiv.appendChild(tr); + } + body.appendChild(statsDiv); + document.getElementById("statcontent").appendChild(body); + + // List interviews if any are open + var table = document.createElement("table"); + table.className = "interview_panel_table"; + if (!interviewManager) { + return; + } + for (var i = 0; i < interviewManager.interviews.length; i++) { + var part = interviewManager.interviews[i]; + var tr = document.createElement("tr"); + var td = document.createElement("td"); + var a = document.createElement("a"); + a.textContent = part["status"]; + a.href = "?_src_=holder;admin_token=" + href_token + ";interview=" + part["ref"] + ";statpanel_item_click=left"; + td.appendChild(a); + tr.appendChild(td); + table.appendChild(tr); + } + document.getElementById("statcontent").appendChild(table); +} + +function draw_spells(cat) { + statcontentdiv.textContent = ""; + var table = document.createElement("table"); + for (var i = 0; i < spells.length; i++) { + var part = spells[i]; + if (part[0] != cat) continue; + var tr = document.createElement("tr"); + var td1 = document.createElement("td"); + td1.textContent = part[1]; + var td2 = document.createElement("td"); + if (part[3]) { + var a = document.createElement("a"); + a.href = "?src=" + part[3] + ";statpanel_item_click=left"; + a.textContent = part[2]; + td2.appendChild(a); + } else { + td2.textContent = part[2]; + } + tr.appendChild(td1); + tr.appendChild(td2); + table.appendChild(tr); + } + document.getElementById("statcontent").appendChild(table); +} + +function make_verb_onclick(command) { + return function () { + run_after_focus(function () { + Byond.command(command); + }); + }; +} + +function draw_verbs(cat) { + statcontentdiv.textContent = ""; + var table = document.createElement("div"); + var additions = {}; // additional sub-categories to be rendered + table.className = "grid-container"; + sortVerbs(); + if (split_admin_tabs && cat.lastIndexOf(".") != -1) { + var splitName = cat.split("."); + if (splitName[0] === "Admin") + cat = splitName[1]; + } + verbs.reverse(); // sort verbs backwards before we draw + for (var i = 0; i < verbs.length; ++i) { + var part = verbs[i]; + var name = part[0]; + if (split_admin_tabs && name.lastIndexOf(".") != -1) { + var splitName = name.split("."); + if (splitName[0] === "Admin") + name = splitName[1]; + } + var command = part[1]; + + if (command && name.lastIndexOf(cat, 0) != -1 && (name.length == cat.length || name.charAt(cat.length) == ".")) { + var subCat = name.lastIndexOf(".") != -1 ? name.split(".")[1] : null; + if (subCat && !additions[subCat]) { + var newTable = document.createElement("div"); + newTable.className = "grid-container"; + additions[subCat] = newTable; + } + + var a = document.createElement("a"); + a.href = "#"; + a.onclick = make_verb_onclick(command.replace(/\s/g, "-")); + a.className = "grid-item"; + var t = document.createElement("span"); + t.textContent = command; + t.className = "grid-item-text"; + a.appendChild(t); + (subCat ? additions[subCat] : table).appendChild(a); + } + } + + // Append base table to view + var content = document.getElementById("statcontent"); + content.appendChild(table); + + // Append additional sub-categories if relevant + for (var cat in additions) { + if (additions.hasOwnProperty(cat)) { + // do addition here + var header = document.createElement("h3"); + header.textContent = cat; + content.appendChild(header); + content.appendChild(additions[cat]); + } + } +} + +function set_theme(which) { + if (which == "light") { + document.body.className = ""; + set_style_sheet("browserOutput_white"); + } else if (which == "dark") { + document.body.className = "dark"; + set_style_sheet("browserOutput"); + } +} + +function set_style_sheet(sheet) { + if (document.getElementById("goonStyle")) { + var currentSheet = document.getElementById("goonStyle"); + currentSheet.parentElement.removeChild(currentSheet); + } + var head = document.getElementsByTagName('head')[0]; + var sheetElement = document.createElement("link"); + sheetElement.id = "goonStyle"; + sheetElement.rel = "stylesheet"; + sheetElement.type = "text/css"; + sheetElement.href = sheet + ".css"; + sheetElement.media = 'all'; + head.appendChild(sheetElement); +} + +function restoreFocus() { + run_after_focus(function () { + Byond.winset('map', { + focus: true, + }); + }); +} + +function getCookie(cname) { + var name = cname + '='; + var ca = document.cookie.split(';'); + for (var i = 0; i < ca.length; i++) { + var c = ca[i]; + while (c.charAt(0) == ' ') c = c.substring(1); + if (c.indexOf(name) === 0) { + return decoder(c.substring(name.length, c.length)); + } + } + return ''; +} + +function add_verb_list(payload) { + var to_add = payload; // list of a list with category and verb inside it + to_add.sort(); // sort what we're adding + for (var i = 0; i < to_add.length; i++) { + var part = to_add[i]; + if (!part[0]) + continue; + var category = part[0]; + if (category.indexOf(".") != -1) { + var splitName = category.split("."); + if (split_admin_tabs && splitName[0] === "Admin") + category = splitName[1]; + else + category = splitName[0]; + } + if (findVerbindex(part[1], verbs)) + continue; + if (verb_tabs.includes(category)) { + verbs.push(part); + if (current_tab == category) { + draw_verbs(category); // redraw if we added a verb to the tab we're currently in + } + } else if (category) { + verb_tabs.push(category); + verbs.push(part); + createStatusTab(category); + } + } +}; + +function init_spells() { + var cat = ""; + for (var i = 0; i < spell_tabs.length; i++) { + cat = spell_tabs[i]; + if (cat.length > 0) { + verb_tabs.push(cat); + createStatusTab(cat); + } + } +} + +document.addEventListener("mouseup", restoreFocus); +document.addEventListener("keyup", restoreFocus); + +if (!current_tab) { + addPermanentTab("Status"); + tab_change("Status"); +} + +window.onload = function () { + Byond.sendMessage("Update-Verbs"); +}; + +Byond.subscribeTo('update_spells', function (payload) { + spell_tabs = payload.spell_tabs; + var do_update = false; + if (spell_tabs.includes(current_tab)) { + do_update = true; + } + init_spells(); + if (payload.actions) { + spells = payload.actions; + if (do_update) { + draw_spells(current_tab); + } + } else { + remove_spells(); + } +}); + +Byond.subscribeTo('remove_verb_list', function (v) { + var to_remove = v; + for (var i = 0; i < to_remove.length; i++) { + remove_verb(to_remove[i]); + } + check_verbs(); + sortVerbs(); + if (verb_tabs.includes(current_tab)) + draw_verbs(current_tab); +}); + +// passes a 2D list of (verbcategory, verbname) creates tabs and adds verbs to respective list +// example (IC, Say) +Byond.subscribeTo('init_verbs', function (payload) { + wipe_verbs(); // remove all verb categories so we can replace them + checkStatusTab(); // remove all status tabs + verb_tabs = payload.panel_tabs; + verb_tabs.sort(); // sort it + var do_update = false; + var cat = ""; + for (var i = 0; i < verb_tabs.length; i++) { + cat = verb_tabs[i]; + createStatusTab(cat); // create a category if the verb doesn't exist yet + } + if (verb_tabs.includes(current_tab)) { + do_update = true; + } + if (payload.verblist) { + add_verb_list(payload.verblist); + sortVerbs(); // sort them + if (do_update) { + draw_verbs(current_tab); + } + } + SendTabsToByond(); +}); + +Byond.subscribeTo('update_stat', function (payload) { + status_tab_parts = [payload.ping_str]; + var parsed = payload.global_data; + + for (var i = 0; i < parsed.length; i++) if (parsed[i] != null) status_tab_parts.push(parsed[i]); + + parsed = payload.other_str; + + for (var i = 0; i < parsed.length; i++) if (parsed[i] != null) status_tab_parts.push(parsed[i]); + + if (current_tab == "Status") { + draw_status(); + } else if (current_tab == "Debug Stat Panel") { + draw_debug(); + } +}); + +Byond.subscribeTo('update_mc', function (payload) { + mc_tab_parts = payload.mc_data; + mc_tab_parts.splice(0, 0, ["Location:", payload.coord_entry]); + + if (!verb_tabs.includes("MC")) { + verb_tabs.push("MC"); + } + + createStatusTab("MC"); + + if (current_tab == "MC") { + draw_mc(); + } +}); + +Byond.subscribeTo('remove_spells', function () { + for (var s = 0; s < spell_tabs.length; s++) { + removeStatusTab(spell_tabs[s]); + } +}); + +Byond.subscribeTo('init_spells', function () { + var cat = ""; + for (var i = 0; i < spell_tabs.length; i++) { + cat = spell_tabs[i]; + if (cat.length > 0) { + verb_tabs.push(cat); + createStatusTab(cat); + } + } +}); + +Byond.subscribeTo('check_spells', function () { + for (var v = 0; v < spell_tabs.length; v++) { + spell_cat_check(spell_tabs[v]); + } +}); + +Byond.subscribeTo('create_debug', function () { + if (!document.getElementById("Debug Stat Panel")) { + addPermanentTab("Debug Stat Panel"); + } else { + removePermanentTab("Debug Stat Panel"); + } +}); + +Byond.subscribeTo('create_listedturf', function (TN) { + remove_listedturf(); // remove the last one if we had one + turfname = TN; + addPermanentTab(turfname); + tab_change(turfname); +}); + +Byond.subscribeTo('remove_admin_tabs', function () { + href_token = null; + remove_mc(); + remove_tickets(); + remove_sdql2(); + remove_interviews(); +}); + +Byond.subscribeTo('update_listedturf', function (TC) { + turfcontents = TC; + if (current_tab == turfname) { + draw_listedturf(); + } +}); + +Byond.subscribeTo('update_interviews', function (I) { + interviewManager = I; + if (current_tab == "Tickets") { + draw_interviews(); + } +}); + +Byond.subscribeTo('update_split_admin_tabs', function (status) { + status = (status == true); + + if (split_admin_tabs !== status) { + if (split_admin_tabs === true) { + removeStatusTab("Events"); + removeStatusTab("Fun"); + removeStatusTab("Game"); + } + update_verbs(); + } + split_admin_tabs = status; +}); + +Byond.subscribeTo('add_admin_tabs', function (ht) { + href_token = ht; + addPermanentTab("MC"); + addPermanentTab("Tickets"); +}); + +Byond.subscribeTo('update_sdql2', function (S) { + sdql2 = S; + if (sdql2.length > 0 && !verb_tabs.includes("SDQL2")) { + verb_tabs.push("SDQL2"); + addPermanentTab("SDQL2"); + } + if (current_tab == "SDQL2") { + draw_sdql2(); + } +}); + +Byond.subscribeTo('update_tickets', function (T) { + tickets = T; + if (!verb_tabs.includes("Tickets")) { + verb_tabs.push("Tickets"); + addPermanentTab("Tickets"); + } + if (current_tab == "Tickets") { + draw_tickets(); + } +}); + +Byond.subscribeTo('remove_listedturf', remove_listedturf); + +Byond.subscribeTo('remove_sdql2', remove_sdql2); + +Byond.subscribeTo('remove_mc', remove_mc); + +Byond.subscribeTo('add_verb_list', add_verb_list); diff --git a/interface/skin.dmf b/interface/skin.dmf index 6a916397352f3..cb0db3bef49d7 100644 --- a/interface/skin.dmf +++ b/interface/skin.dmf @@ -331,17 +331,17 @@ window "statwindow" type = MAIN pos = 281,0 size = 640x480 - anchor1 = none - anchor2 = none + anchor1 = -1,-1 + anchor2 = -1,-1 saved-params = "pos;size;is-minimized;is-maximized" is-pane = true - elem "stat" - type = INFO + elem "statbrowser" + type = BROWSER pos = 0,0 size = 640x480 anchor1 = 0,0 anchor2 = 100,100 - is-default = true + is-visible = false saved-params = "" window "tgui_say" diff --git a/tgmc.dme b/tgmc.dme index 54fd5336be454..76e09f021e5a8 100755 --- a/tgmc.dme +++ b/tgmc.dme @@ -176,6 +176,7 @@ #include "code\__HELPERS\tts.dm" #include "code\__HELPERS\type2type.dm" #include "code\__HELPERS\unsorted.dm" +#include "code\__HELPERS\verbs.dm" #include "code\__HELPERS\view.dm" #include "code\__HELPERS\sorts\__main.dm" #include "code\__HELPERS\sorts\InsertSort.dm" @@ -293,6 +294,7 @@ #include "code\controllers\subsystem\spatial_gridmap.dm" #include "code\controllers\subsystem\spawning.dm" #include "code\controllers\subsystem\speech_controller.dm" +#include "code\controllers\subsystem\statpanel.dm" #include "code\controllers\subsystem\stickyban.dm" #include "code\controllers\subsystem\strings.dm" #include "code\controllers\subsystem\tgui.dm" @@ -307,6 +309,7 @@ #include "code\controllers\subsystem\processing\fastprocess.dm" #include "code\controllers\subsystem\processing\instruments.dm" #include "code\controllers\subsystem\processing\obj.dm" +#include "code\controllers\subsystem\processing\obj_tab_items.dm" #include "code\controllers\subsystem\processing\processing.dm" #include "code\controllers\subsystem\processing\projectiles.dm" #include "code\controllers\subsystem\processing\resinshaping.dm" @@ -370,6 +373,7 @@ #include "code\datums\components\clothing_tint.dm" #include "code\datums\components\companion.dm" #include "code\datums\components\connect_loc_behalf.dm" +#include "code\datums\components\connect_mob_behalf.dm" #include "code\datums\components\deployable_item.dm" #include "code\datums\components\dripping.dm" #include "code\datums\components\grillable.dm" diff --git a/tgui/packages/tgui/interfaces/PlayerPreferences/GameSettings.tsx b/tgui/packages/tgui/interfaces/PlayerPreferences/GameSettings.tsx index ae67bf639c46e..575bcae245843 100644 --- a/tgui/packages/tgui/interfaces/PlayerPreferences/GameSettings.tsx +++ b/tgui/packages/tgui/interfaces/PlayerPreferences/GameSettings.tsx @@ -28,8 +28,14 @@ const ParallaxNumToString = (integer) => { export const GameSettings = (props, context) => { const { act, data } = useBackend(context); - const { ui_style_color, scaling_method, pixel_size, parallax, quick_equip } = - data; + const { + ui_style_color, + scaling_method, + pixel_size, + parallax, + quick_equip, + is_admin, + } = data; return (
@@ -292,6 +298,32 @@ export const GameSettings = (props, context) => {
+ {is_admin && ( + + +
+ + + + + + +
+
+
+ )} ); }; diff --git a/tgui/packages/tgui/interfaces/PlayerPreferences/Types.tsx b/tgui/packages/tgui/interfaces/PlayerPreferences/Types.tsx index 91270dd100fe3..4d0b3b7bd8f70 100644 --- a/tgui/packages/tgui/interfaces/PlayerPreferences/Types.tsx +++ b/tgui/packages/tgui/interfaces/PlayerPreferences/Types.tsx @@ -36,6 +36,7 @@ type GameSettingData = { pixel_size: number; parallax: number; quick_equip: string[]; + is_admin: number; }; type GearCustomizationData = {