diff --git a/.github/MC_tab.md b/.github/MC_tab.md
new file mode 100644
index 000000000000..01ad1e6aa174
--- /dev/null
+++ b/.github/MC_tab.md
@@ -0,0 +1,36 @@
+The MC tab hold information on how the game is performing. Here's a crash course on what the most important of those numbers mean.
+
+If you already know what these numbers mean and you want to see them update faster than the default refresh rate of once every 2 seconds, you can enable the admin pref to make the MC tab refresh every 4 deciseconds. Please don't do this unless you actually need that information at a faster refresh rate since updating every subsystems information is expensive.
+
+# Main Entries:
+
+ * CPU: What percentage of a tick the game is using before starting the next tick. If this is above 100 it means we are over budget.
+
+ * TickCount: How many ticks should have elapsed since the start of the game if no ticks were ever delayed from starting.
+
+ * TickDrift: How many ticks since the game started that have been delayed. Essentially this is how many ticks the game is running behind. If this is increasing then the game is currently not able to keep up with demand.
+
+ * Internal Tick Usage: You might have heard of this referred to as "maptick". It's how much of the tick that an internal byond function called SendMaps() has taken recently. The higher this is the less time our code has to run. SendMaps() deals with sending players updates of their view of the game world so it has to run every tick but it's expensive so ideally this is optimized as much as possible. You can see a more detailed breakdown of the cost of SendMaps by looking at the profiler in the debug tab -> "Send Maps Profile".
+
+# Master Controller Entry:
+
+ * TickRate: How many Byond ticks go between each master controller iteration. By default this is 1 meaning the MC runs once every byond tick. But certain configurations can increase this slightly.
+
+ * Iteration: How many times the MC has ran since starting.
+
+ * TickLimit: This SHOULD be what percentage of the tick the MC can use when it starts a run, however currently it just represents how much of the tick the MC can use by the time that SSstatpanels fires. Someone should fix that.
+
+# Subsystem Entries:
+
+Subsystems will typically have a base stat entry of the form:
+[ ] Name 12ms|28%(2%)|3
+
+The brackets hold a letter if the subsystem is in a state other than idle.
+
+The first numbered entry is the cost of the subsystem, which is a running average of how many milliseconds the subsystem takes to complete a full run. This is increased every time the subsystem resumes an uncompleted run or starts a new run and decays when runs take less time. If this balloons to huge values then it means that the amount of work the subsystem needs to complete in a run is far greater than the amount of time it actually has to execute in whenever it is its turn to fire.
+
+The second numbered entry is like cost, but in percentage of an ideal tick this subsystem takes to complete a run. They both represent the same data.
+
+The third entry (2%) is how much time this subsystem spent executing beyond the time it was allocated by the MC. This is bad, it means that this subsystem doesn't yield when it's taking too much time and makes the job of the MC harder. The MC will attempt to account for this but it is better for all subsystems to be able to correctly yield when their turn is done.
+
+The fourth entry represents how many times this subsystem fires before it completes a run.
diff --git a/check_regex.yaml b/check_regex.yaml
index df64dec9aae1..f19d67f7d16f 100644
--- a/check_regex.yaml
+++ b/check_regex.yaml
@@ -38,7 +38,7 @@ standards:
- exactly:
[
- 295,
+ 273,
"non-bitwise << uses",
'(? target_mob.see_invisible)
- continue
- if(turf_content in overrides)
- continue
- if(turf_content.IsObscured())
- continue
- if(length(turfitems) < 30) // only create images for the first 30 items on the turf, for performance reasons
- if(!(REF(turf_content) in cached_images))
- cached_images += REF(turf_content)
- turf_content.RegisterSignal(turf_content, COMSIG_PARENT_QDELETING, TYPE_PROC_REF(/atom, remove_from_cache)) // we reset cache if anything in it gets deleted
- if(ismob(turf_content) || length(turf_content.overlays) > 2)
- turfitems[++turfitems.len] = list("[turf_content.name]", REF(turf_content), costly_icon2html(turf_content, target, sourceonly=TRUE))
- else
- turfitems[++turfitems.len] = list("[turf_content.name]", REF(turf_content), icon2html(turf_content, target, sourceonly=TRUE))
- else
- turfitems[++turfitems.len] = list("[turf_content.name]", REF(turf_content))
- else
- turfitems[++turfitems.len] = list("[turf_content.name]", REF(turf_content))
- turfitems = url_encode(json_encode(turfitems))
- target << output("[turfitems];", "statbrowser:update_listedturf")
+ var/mob/target_mob = target.mob
+ if((target.stat_tab in target.spell_tabs) || !length(target.spell_tabs) && (length(target_mob.mob_spell_list) || length(target_mob.mind?.spell_list)))
+ if(num_fires % default_wait == 0)
+ set_spells_tab(target, 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)
+ var/datum/interview_manager/m = GLOB.interviews
+
+ // get open interview count
+ var/dc = 0
+ for (var/ckey in m.open_interviews)
+ var/datum/interview/current_interview = m.open_interviews[ckey]
+ if (current_interview && !current_interview.owner)
+ dc++
+ var/stat_string = "([m.open_interviews.len - dc] online / [dc] disconnected)"
+
+ // Prepare each queued interview
+ var/list/queued = list()
+ for (var/datum/interview/queued_interview in m.interview_queue)
+ queued += list(list(
+ "ref" = REF(queued_interview),
+ "status" = "\[[queued_interview.pos_in_queue]\]: [queued_interview.owner_ckey][!queued_interview.owner ? " (DC)": ""] \[INT-[queued_interview.id]\]"
+ ))
+
+ var/list/data = list(
+ "status" = list(
+ "Active:" = "[m.open_interviews.len] [stat_string]",
+ "Queued:" = "[m.interview_queue.len]",
+ "Closed:" = "[m.closed_interviews.len]"),
+ "interviews" = queued
+ )
+
+ // Push update
+ target.stat_panel.send_message("update_interviews", data)
+
+/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_spells_tab(client/target, mob/target_mob)
+ var/list/proc_holders = target_mob.get_proc_holders()
+ target.spell_tabs.Cut()
+
+ for(var/proc_holder_list as anything in proc_holders)
+ target.spell_tabs |= proc_holder_list[1]
+
+ target.stat_panel.send_message("update_spells", list(spell_tabs = target.spell_tabs, proc_holders_encoded = proc_holders))
+
+/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_PARENT_QDELETING, /datum/object_window_info/proc/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()
- var/list/mc_data = list(
+ mc_data = list(
list("CPU:", world.cpu),
list("Instances:", "[num2text(world.contents.len, 10)]"),
list("World Time:", "[world.time]"),
@@ -162,45 +235,147 @@ SUBSYSTEM_DEF(statpanels)
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(), text_ref(sub_system))
mc_data[++mc_data.len] = list("Camera Net", "Cameras: [GLOB.cameranet.cameras.len] | Chunks: [GLOB.cameranet.chunks.len]", text_ref(GLOB.cameranet))
- mc_data_encoded = url_encode(json_encode(mc_data))
-/atom/proc/remove_from_cache()
- SSstatpanels.cached_images -= REF(src)
+///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
+ if((target.stat_tab in target.spell_tabs) || !length(target.spell_tabs) && (length(target_mob.mob_spell_list) || length(target_mob.mind?.spell_list)))
+ set_spells_tab(target, target_mob)
+ return TRUE
+
+ 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
-/// verbs that send information from the browser UI
-/client/verb/set_tab(tab as text|null)
- set name = "Set Tab"
- set hidden = TRUE
+ if(target.stat_tab == "Tickets")
+ set_tickets_tab(target)
+ return TRUE
- stat_tab = tab
+ if(!length(GLOB.sdql2_queries) && ("SDQL2" in target.panel_tabs))
+ target.stat_panel.send_message("remove_sdql2")
-/client/verb/send_tabs(tabs as text|null)
- set name = "Send Tabs"
- set hidden = TRUE
+ else if(length(GLOB.sdql2_queries) && target.stat_tab == "SDQL2")
+ set_SDQL2_tab(target)
- panel_tabs |= tabs
+/// Stat panel window declaration
+/client/var/datum/tgui_window/stat_panel
-/client/verb/remove_tabs(tabs as text|null)
- set name = "Remove Tabs"
- set hidden = TRUE
+/// 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
- panel_tabs -= tabs
+/datum/object_window_info/New(client/parent)
+ . = ..()
+ src.parent = parent
-/client/verb/reset_tabs()
- set name = "Reset Tabs"
- set hidden = TRUE
+/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(delta_time)
+ // 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
- panel_tabs = list()
+/datum/object_window_info/proc/stop_turf_tracking()
+ qdel(GetComponent(/datum/component/connect_mob_behalf))
+ actively_tracking = FALSE
-/client/verb/panel_ready()
- set name = "Panel Ready"
- set hidden = TRUE
+/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)
- statbrowser_ready = TRUE
- init_verbs()
+/datum/object_window_info/proc/on_mob_logout(mob/source)
+ SIGNAL_HANDLER
+ on_mob_move(parent.mob)
-/client/verb/update_verbs()
- set name = "Update Verbs"
- set hidden = TRUE
+/// 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
- init_verbs()
+/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 000000000000..1c1a8a652342
--- /dev/null
+++ b/code/datums/components/connect_mob_behalf.dm
@@ -0,0 +1,59 @@
+/// 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_PARENT_QDELETING, PROC_REF(handle_tracked_qdel))
+ update_signals()
+
+/datum/component/connect_mob_behalf/UnregisterFromParent()
+ unregister_signals()
+ UnregisterSignal(tracked, COMSIG_PARENT_QDELETING)
+
+ tracked = null
+ tracked_mob = null
+
+/datum/component/connect_mob_behalf/proc/handle_tracked_qdel()
+ SIGNAL_HANDLER
+ qdel(src)
+
+/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])
+
+/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
+
+/datum/component/connect_mob_behalf/proc/on_logout(mob/source)
+ SIGNAL_HANDLER
+ update_signals()
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index af7d411245e2..fc91d2c71de1 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -728,7 +728,7 @@
if(istype(S, spell))
spell_list -= S
qdel(S)
- current?.client << output(null, "statbrowser:check_spells")
+ current?.client.stat_panel.send_message("check_spells")
/datum/mind/proc/RemoveAllSpells()
for(var/obj/effect/proc_holder/S in spell_list)
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index a853f66963af..986399d63299 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -766,7 +766,7 @@ GLOBAL_PROTECT(admin_verbs_hideable)
set name = "Debug Stat Panel"
set category = "Debug"
- src << output("", "statbrowser:create_debug")
+ src.stat_panel.send_message("create_debug")
#ifdef SENDMAPS_PROFILE
/client/proc/display_sendmaps()
diff --git a/code/modules/antagonists/borer/borer.dm b/code/modules/antagonists/borer/borer.dm
index 41e8b644fa53..d4af47670412 100644
--- a/code/modules/antagonists/borer/borer.dm
+++ b/code/modules/antagonists/borer/borer.dm
@@ -52,7 +52,7 @@
B.victim.adjustOrganLoss(ORGAN_SLOT_BRAIN, rand(5, 10))
to_chat(src, "With an immense exertion of will, you regain control of your body!")
to_chat(B, "You feel control of the host brain ripped from your grasp, and retract your probosci before the wild neural impulses can damage you.")
- B.detatch()
+ B.detach()
GLOBAL_LIST_EMPTY(borers)
GLOBAL_VAR_INIT(total_borer_hosts_needed, 3)
@@ -568,7 +568,7 @@ GLOBAL_VAR_INIT(total_borer_hosts_needed, 3)
return
if(controlling)
- detatch()
+ detach()
if(src.mind.language_holder)
var/datum/language_holder/language_holder = src.mind.language_holder
@@ -721,6 +721,8 @@ GLOBAL_VAR_INIT(total_borer_hosts_needed, 3)
victim.med_hud_set_status()
+ RegisterSignal(victim, COMSIG_MOB_GET_STATUS_TAB_ITEMS, PROC_REF(get_borer_stat_panel))
+
/mob/living/simple_animal/borer/verb/punish()
set category = "Borer"
set name = "Punish"
@@ -756,7 +758,6 @@ GLOBAL_VAR_INIT(total_borer_hosts_needed, 3)
/mob/living/carbon/proc/release_control()
-
set category = "Borer"
set name = "Release Control"
set desc = "Release control of your host's body."
@@ -764,8 +765,12 @@ GLOBAL_VAR_INIT(total_borer_hosts_needed, 3)
var/mob/living/simple_animal/borer/B = has_brain_worms()
if(B && B.host_brain)
to_chat(B, "You withdraw your probosci, releasing control of [B.host_brain]")
+ B.detach()
- B.detatch()
+/mob/living/simple_animal/borer/proc/get_borer_stat_panel(mob/living/source, list/items)
+ SIGNAL_HANDLER
+ items += "Borer Body Health: [health]"
+ items += "Chemicals: [chemicals]"
//Check for brain worms in head.
/mob/proc/has_brain_worms()
@@ -801,7 +806,7 @@ GLOBAL_VAR_INIT(total_borer_hosts_needed, 3)
to_chat(src, "You need 200 chemicals stored to reproduce.")
return
-/mob/living/simple_animal/borer/proc/detatch()
+/mob/living/simple_animal/borer/proc/detach()
if(!victim || !controlling)
return
@@ -829,6 +834,8 @@ GLOBAL_VAR_INIT(total_borer_hosts_needed, 3)
log_game("[src]/([src.ckey]) released control of [victim]/([victim.ckey]")
+ UnregisterSignal(victim, COMSIG_MOB_GET_STATUS_TAB_ITEMS)
+
qdel(host_brain)
/mob/living/simple_animal/borer/proc/toggle_leap()
diff --git a/code/modules/antagonists/changeling/powers/panacea.dm b/code/modules/antagonists/changeling/powers/panacea.dm
index 5c1010aaf69a..573ebd127fde 100644
--- a/code/modules/antagonists/changeling/powers/panacea.dm
+++ b/code/modules/antagonists/changeling/powers/panacea.dm
@@ -29,7 +29,7 @@
var/mob/living/simple_animal/borer/B = user.has_brain_worms() //WS Begin - Borers
if(B)
if(B.controlling)
- B.detatch()
+ B.detach()
B.leave_victim()
if(iscarbon(user))
var/mob/living/carbon/C = user
diff --git a/code/modules/assembly/health.dm b/code/modules/assembly/health.dm
index 2a07737e2c63..7bc5adc33de2 100644
--- a/code/modules/assembly/health.dm
+++ b/code/modules/assembly/health.dm
@@ -14,6 +14,13 @@
. += "Use it in hand to turn it off/on and Alt-click to swap between \"detect death\" mode and \"detect critical state\" mode."
. += "[src.scanning ? "The sensor is on and you can see [health_scan] displayed on the screen" : "The sensor is off"]."
+/obj/item/assembly/health/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
+ . = ..()
+ if(iscarbon(old_loc))
+ UnregisterSignal(old_loc, COMSIG_MOB_GET_STATUS_TAB_ITEMS)
+ if(iscarbon(loc))
+ RegisterSignal(loc, COMSIG_MOB_GET_STATUS_TAB_ITEMS, PROC_REF(get_status_tab_item))
+
/obj/item/assembly/health/activate()
if(!..())
return FALSE//Cooldown check
@@ -73,3 +80,7 @@
. = ..()
to_chat(user, "You toggle [src] [src.scanning ? "off" : "on"].")
toggle_scan()
+
+/obj/item/assembly/health/proc/get_status_tab_item(mob/living/carbon/source, list/items)
+ SIGNAL_HANDLER
+ items += "Health: [round((source.health / source.maxHealth) * 100)]%"
diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm
index de655ece5f1a..64e2476b400f 100644
--- a/code/modules/client/client_defines.dm
+++ b/code/modules/client/client_defines.dm
@@ -150,15 +150,14 @@
/// our current tab
var/stat_tab
- /// whether our browser is ready or not yet
- var/statbrowser_ready = FALSE
-
/// 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 EXAMINE_MORE_TIME (default 1.5) 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
var/list/parallax_layers
var/list/parallax_layers_cached
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index 8db2e8454828..711af230fd9d 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -80,7 +80,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
if(tgui_Topic(href_list))
return
if(href_list["reload_statbrowser"])
- src << browse(file('html/statbrowser.html'), "window=statbrowser")
+ stat_panel.reinitialize()
// Log all hrefs
log_href("[src] (usr:[usr]\[[COORD(usr)]\]) : [hsrc ? "[hsrc] " : ""][href]")
@@ -226,8 +226,12 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
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)
+ tgui_panel = new(src, "browseroutput")
GLOB.ahelp_tickets.client_login(src)
GLOB.interviews.client_login(src)
@@ -346,9 +350,15 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
if(SSinput.initialized)
set_macros()
- // Initialize tgui panel
- src << browse(file('html/statbrowser.html'), "window=statbrowser")
+ // Initialize stat panel
+ stat_panel.initialize(
+ inline_html = file2text('html/statbrowser.html'),
+ inline_js = file2text('html/statbrowser.js'),
+ inline_css = file2text('html/statbrowser.css'),
+ )
addtimer(CALLBACK(src, PROC_REF(check_panel_loaded)), 30 SECONDS)
+
+ // Initialize tgui panel
tgui_panel.initialize()
if(alert_mob_dupe_login)
@@ -506,6 +516,8 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
SSserver_maint.UpdateHubStatus()
if(credits)
QDEL_LIST(credits)
+ if(obj_window)
+ QDEL_NULL(obj_window)
if(holder)
adminGreet(1)
holder.owner = null
@@ -1082,12 +1094,10 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
var/list/verbstoprocess = verbs.Copy()
if(mob?.client?.prefs.broadcast_login_logout)
verbstoprocess += mob.verbs
- for(var/AM in mob.contents)
- var/atom/movable/thing = AM
+ 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/thing in verbstoprocess)
- var/procpath/verb_to_init = thing
+ for(var/procpath/verb_to_init as anything in verbstoprocess)
if(!verb_to_init)
continue
if(verb_to_init.hidden)
@@ -1096,10 +1106,10 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
continue
panel_tabs |= verb_to_init.category
verblist[++verblist.len] = list(verb_to_init.category, verb_to_init.name)
- src << output("[url_encode(json_encode(panel_tabs))];[url_encode(json_encode(verblist))]", "statbrowser:init_verbs")
+ src.stat_panel.send_message("init_verbs", list(panel_tabs = panel_tabs, verblist = verblist))
/client/proc/check_panel_loaded()
- if(statbrowser_ready)
+ if(stat_panel.is_ready())
return
to_chat(src, "Statpanel failed to load, click here to reload the panel ")
@@ -1140,3 +1150,20 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
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
+
+/**
+ * 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 3fa8bfc5266b..b52052cd15ec 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -48,7 +48,6 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/tgui_fancy = TRUE
var/tgui_lock = FALSE
var/windowflashing = TRUE
- var/crew_objectives = TRUE
var/toggles = TOGGLES_DEFAULT
var/db_flags
var/chat_toggles = TOGGLES_DEFAULT_CHAT
@@ -1112,6 +1111,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "Hide Radio Messages: [(chat_toggles & CHAT_RADIO)?"Shown":"Hidden"]
"
dat += "Hide Prayers: [(chat_toggles & CHAT_PRAYER)?"Shown":"Hidden"]
"
dat += "Split Admin Tabs: [(toggles & SPLIT_ADMIN_TABS)?"Enabled":"Disabled"]
"
+ dat += "Fast MC Refresh: [(toggles & FAST_MC_REFRESH)?"Enabled":"Disabled"]
"
dat += "Ignore Being Summoned as Cult Ghost: [(toggles & ADMIN_IGNORE_CULT_GHOST)?"Don't Allow Being Summoned":"Allow Being Summoned"]
"
dat += "Briefing Officer Outfit: [brief_outfit]
"
if(CONFIG_GET(flag/allow_admin_asaycolor))
@@ -2215,6 +2215,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
user.client.toggle_hear_radio()
if("toggle_split_admin_tabs")
toggles ^= SPLIT_ADMIN_TABS
+ if("toggle_fast_mc_refresh")
+ toggles ^= FAST_MC_REFRESH
if("toggle_prayers")
user.client.toggleprayers()
if("toggle_deadmin_always")
diff --git a/code/modules/mob/dead/dead.dm b/code/modules/mob/dead/dead.dm
index ee74d0475a34..c09a3c8bd4c8 100644
--- a/code/modules/mob/dead/dead.dm
+++ b/code/modules/mob/dead/dead.dm
@@ -37,12 +37,8 @@ INITIALIZE_IMMEDIATE(/mob/dead)
/mob/dead/get_status_tab_items()
. = ..()
- . += ""
- . += "Game Mode: [SSticker.hide_mode ? "Secret" : "[GLOB.master_mode]"]"
-
if(SSticker.HasRoundStarted())
return
-
var/time_remaining = SSticker.GetTimeLeft()
if(time_remaining > 0)
. += "Time To Start: [round(time_remaining/10)]s"
diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm
index d7865c9d2276..9baa46f526b5 100644
--- a/code/modules/mob/dead/new_player/new_player.dm
+++ b/code/modules/mob/dead/new_player/new_player.dm
@@ -493,8 +493,13 @@
/mob/dead/new_player/proc/register_for_interview()
// First we detain them by removing all the verbs they have on client
for (var/procpath/client_verb as anything in client.verbs)
- if(!(client_verb in GLOB.client_verbs_required))
- remove_verb(client, client_verb)
+ if(client_verb in GLOB.client_verbs_required)
+ continue
+ remove_verb(client, client_verb)
+
+ // Then remove those on their mob as well
+ for (var/procpath/verb_path as anything in verbs)
+ remove_verb(src, verb_path)
// Then we create the interview form and show it to the client
var/datum/interview/I = GLOB.interviews.interview_for_client(client)
diff --git a/code/modules/mob/living/carbon/alien/alien.dm b/code/modules/mob/living/carbon/alien/alien.dm
index 9dfd2484402e..23e937acf102 100644
--- a/code/modules/mob/living/carbon/alien/alien.dm
+++ b/code/modules/mob/living/carbon/alien/alien.dm
@@ -86,10 +86,6 @@
/mob/living/carbon/alien/IsAdvancedToolUser()
return has_fine_manipulation
-/mob/living/carbon/alien/get_status_tab_items()
- . = ..()
- . += "Intent: [a_intent]"
-
/mob/living/carbon/alien/getTrail()
if(getBruteLoss() < 200)
return pick (list("xltrails_1", "xltrails2"))
diff --git a/code/modules/mob/living/carbon/alien/organs.dm b/code/modules/mob/living/carbon/alien/organs.dm
index 8faa15b83929..cb7c7adafc4b 100644
--- a/code/modules/mob/living/carbon/alien/organs.dm
+++ b/code/modules/mob/living/carbon/alien/organs.dm
@@ -83,17 +83,23 @@
else
owner.adjustPlasma(plasma_rate * 0.1)
-/obj/item/organ/alien/plasmavessel/Insert(mob/living/carbon/M, special = 0)
+/obj/item/organ/alien/plasmavessel/Insert(mob/living/carbon/organ_owner, special = 0)
..()
- if(isalien(M))
- var/mob/living/carbon/alien/A = M
- A.updatePlasmaDisplay()
+ if(isalien(organ_owner))
+ var/mob/living/carbon/alien/target_alien = organ_owner
+ target_alien.updatePlasmaDisplay()
+ RegisterSignal(organ_owner, COMSIG_MOB_GET_STATUS_TAB_ITEMS, PROC_REF(get_status_tab_item))
-/obj/item/organ/alien/plasmavessel/Remove(mob/living/carbon/M, special = 0)
+/obj/item/organ/alien/plasmavessel/Remove(mob/living/carbon/organ_owner, special = 0)
..()
- if(isalien(M))
- var/mob/living/carbon/alien/A = M
- A.updatePlasmaDisplay()
+ if(isalien(organ_owner))
+ var/mob/living/carbon/alien/organ_owner_alien = organ_owner
+ organ_owner_alien.updatePlasmaDisplay()
+ UnregisterSignal(organ_owner, COMSIG_MOB_GET_STATUS_TAB_ITEMS)
+
+/obj/item/organ/alien/plasmavessel/proc/get_status_tab_item(mob/living/carbon/source, list/items)
+ SIGNAL_HANDLER
+ items += "Plasma Stored: [storedPlasma]/[max_plasma]"
#define QUEEN_DEATH_DEBUFF_DURATION 2400
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 82c27e95174b..5b316dad9cb6 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -28,6 +28,9 @@
if(!held_index)
held_index = (active_hand_index % held_items.len)+1
+ if(!isnum(held_index))
+ CRASH("You passed [held_index] into swap_hand instead of a number. WTF man")
+
var/oindex = active_hand_index
active_hand_index = held_index
if(hud_used)
@@ -415,14 +418,6 @@
var/turf/target = get_turf(loc)
I.safe_throw_at(target,I.throw_range,I.throw_speed,src, force = move_force)
-/mob/living/carbon/get_status_tab_items()
- . = ..()
- var/obj/item/organ/alien/plasmavessel/vessel = getorgan(/obj/item/organ/alien/plasmavessel)
- if(vessel)
- . += "Plasma Stored: [vessel.storedPlasma]/[vessel.max_plasma]"
- if(locate(/obj/item/assembly/health) in src)
- . += "Health: [health]"
-
/mob/living/carbon/get_proc_holders()
. = ..()
. += add_abilities_to_panel()
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 4bfe35b47060..cd80b13bcda6 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -61,7 +61,8 @@
. = ..()
. += "Intent: [a_intent]"
. += "Move Mode: [m_intent]"
- if (internal)
+
+ if (internal) //TODO: Refactor this to use the signal on tanks
if (!internal.air_contents)
qdel(internal)
else
@@ -69,32 +70,6 @@
. += "Internal Atmosphere Info: [internal.name]"
. += "Tank Pressure: [internal.air_contents.return_pressure()]"
. += "Distribution Pressure: [internal.distribute_pressure]"
- /*WS begin - no cells in suits
- if(istype(wear_suit, /obj/item/clothing/suit/space))
- var/obj/item/clothing/suit/space/S = wear_suit
- . += "Thermal Regulator: [S.thermal_on ? "on" : "off"]"
- . += "Cell Charge: [S.cell ? "[round(S.cell.percent(), 0.1)]%" : "!invalid!"]"
- */
- var/mob/living/simple_animal/borer/B = has_brain_worms() //WS Begin - Borers
- if(B && B.controlling)
- . += "Borer Body Health: [B.health]"
- . += "Chemicals: [B.chemicals]" //WS End
-
- if(mind)
- var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling)
- if(changeling)
- . += ""
- . += "Chemical Storage: [changeling.chem_charges]/[changeling.chem_storage]"
- . += "Absorbed DNA: [changeling.absorbedcount]"
-
- //WS Begin - Display Ethereal Charge
- if(istype(src))
- var/datum/species/ethereal/eth_species = src.dna?.species
- if(istype(eth_species))
- var/obj/item/organ/stomach/ethereal/stomach = src.getorganslot(ORGAN_SLOT_STOMACH)
- if(istype(stomach))
- . += "Crystal Charge: [round((stomach.crystal_charge / ETHEREAL_CHARGE_SCALING_MULTIPLIER), 0.1)]%"
- //WS End
//NINJACODE
if(istype(wear_suit, /obj/item/clothing/suit/space/space_ninja)) //Only display if actually a ninja.
diff --git a/code/modules/mob/living/carbon/human/species_types/vampire.dm b/code/modules/mob/living/carbon/human/species_types/vampire.dm
index ebc923c01075..070894a92bee 100644
--- a/code/modules/mob/living/carbon/human/species_types/vampire.dm
+++ b/code/modules/mob/living/carbon/human/species_types/vampire.dm
@@ -132,3 +132,15 @@
charge_max = 50
cooldown_min = 50
shapeshift_type = /mob/living/simple_animal/hostile/retaliate/bat
+
+/obj/item/organ/internal/heart/vampire/Insert(mob/living/carbon/receiver, special, drop_if_replaced)
+ . = ..()
+ RegisterSignal(receiver, COMSIG_MOB_GET_STATUS_TAB_ITEMS, PROC_REF(get_status_tab_item))
+
+/obj/item/organ/internal/heart/vampire/Remove(mob/living/carbon/heartless, special)
+ . = ..()
+ UnregisterSignal(heartless, COMSIG_MOB_GET_STATUS_TAB_ITEMS)
+
+/obj/item/organ/internal/heart/vampire/proc/get_status_tab_item(mob/living/carbon/source, list/items)
+ SIGNAL_HANDLER
+ items += "Blood Level: [source.blood_volume]/[BLOOD_VOLUME_MAXIMUM]"
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 44bfe5626754..4164844c0d8e 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -323,7 +323,6 @@
/mob/living/silicon/robot/get_status_tab_items()
. = ..()
- . += ""
if(cell)
. += "Charge Left: [cell.charge]/[cell.maxcharge]"
else
diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm
index d63c300e8ba6..bc22f78ae7a3 100644
--- a/code/modules/mob/living/simple_animal/parrot.dm
+++ b/code/modules/mob/living/simple_animal/parrot.dm
@@ -155,9 +155,7 @@
/mob/living/simple_animal/parrot/get_status_tab_items()
. = ..()
- . += ""
. += "Held Item: [held_item]"
- . += "Mode: [a_intent]"
/mob/living/simple_animal/parrot/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans, list/message_mods = list())
. = ..()
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index c21a2a6f365d..e4ead25880f9 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -374,8 +374,8 @@
/mob/living/simple_animal/get_status_tab_items()
. = ..()
- . += ""
. += "Health: [round((health / maxHealth) * 100)]%"
+ . += "Intent: [a_intent]"
/mob/living/simple_animal/proc/drop_loot()
if(loot.len)
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 9af72b034998..5a2e3731a703 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -843,7 +843,8 @@
/// Adds this list to the output to the stat browser
/mob/proc/get_status_tab_items()
- . = list()
+ . = list("") //we want to offset unique stuff from standard stuff
+ SEND_SIGNAL(src, COMSIG_MOB_GET_STATUS_TAB_ITEMS, .)
/// Gets all relevant proc holders for the browser statpenl
/mob/proc/get_proc_holders()
@@ -990,7 +991,7 @@
mob_spell_list -= S
qdel(S)
if(client)
- client << output(null, "statbrowser:check_spells")
+ client.stat_panel.send_message("check_spells")
///Return any anti magic atom on this mob that matches the magic type
/mob/proc/anti_magic_check(magic = TRUE, holy = FALSE, tinfoil = FALSE, chargecost = 1, self = FALSE)
diff --git a/code/modules/surgery/organs/augments_chest.dm b/code/modules/surgery/organs/augments_chest.dm
index dc95ab97cece..2cbdf9823eb6 100644
--- a/code/modules/surgery/organs/augments_chest.dm
+++ b/code/modules/surgery/organs/augments_chest.dm
@@ -214,9 +214,8 @@
return TRUE
// Priority 3: use internals tank.
- var/obj/item/tank/I = owner.internal
- if(I && I.air_contents && I.air_contents.total_moles() >= num)
- T.assume_air_moles(I.air_contents, num)
+ if(owner.internal?.air_contents?.total_moles() >= num)
+ T.assume_air_moles(owner.internal.air_contents, num)
toggle(silent = TRUE)
return FALSE
diff --git a/code/modules/surgery/organs/stomach.dm b/code/modules/surgery/organs/stomach.dm
index 2e2403db14c6..bf9346b5dcd9 100644
--- a/code/modules/surgery/organs/stomach.dm
+++ b/code/modules/surgery/organs/stomach.dm
@@ -107,16 +107,22 @@
..()
adjust_charge(-ETHEREAL_CHARGE_FACTOR)
-/obj/item/organ/stomach/ethereal/Insert(mob/living/carbon/M, special = 0)
+/obj/item/organ/stomach/ethereal/Insert(mob/living/carbon/organ_owner, special = 0)
..()
- RegisterSignal(owner, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, PROC_REF(charge))
- RegisterSignal(owner, COMSIG_LIVING_ELECTROCUTE_ACT, PROC_REF(on_electrocute))
-
-/obj/item/organ/stomach/ethereal/Remove(mob/living/carbon/M, special = 0)
- UnregisterSignal(owner, COMSIG_PROCESS_BORGCHARGER_OCCUPANT)
- UnregisterSignal(owner, COMSIG_LIVING_ELECTROCUTE_ACT)
+ RegisterSignal(organ_owner, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, PROC_REF(charge))
+ RegisterSignal(organ_owner, COMSIG_LIVING_ELECTROCUTE_ACT, PROC_REF(on_electrocute))
+ RegisterSignal(organ_owner, COMSIG_MOB_GET_STATUS_TAB_ITEMS, PROC_REF(get_status_tab_item))
+
+/obj/item/organ/stomach/ethereal/Remove(mob/living/carbon/organ_owner, special = 0)
+ UnregisterSignal(organ_owner, COMSIG_PROCESS_BORGCHARGER_OCCUPANT)
+ UnregisterSignal(organ_owner, COMSIG_LIVING_ELECTROCUTE_ACT)
+ UnregisterSignal(organ_owner, COMSIG_MOB_GET_STATUS_TAB_ITEMS)
..()
+/obj/item/organ/stomach/ethereal/proc/get_status_tab_item(mob/living/carbon/source, list/items)
+ SIGNAL_HANDLER
+ items += "Crystal Charge: [round((crystal_charge / ETHEREAL_CHARGE_SCALING_MULTIPLIER), 0.1)]%"
+
/obj/item/organ/stomach/ethereal/proc/charge(datum/source, amount, repairs)
adjust_charge((amount * ETHEREAL_CHARGE_SCALING_MULTIPLIER) / 70) //WS Edit -- Ethereal Charge Scaling
diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm
index a79966f69ba1..95875473133b 100644
--- a/code/modules/tgui/tgui.dm
+++ b/code/modules/tgui/tgui.dm
@@ -92,8 +92,9 @@
window.acquire_lock(src)
if(!window.is_ready())
window.initialize(
+ strict_mode = TRUE,
fancy = user.client.prefs.tgui_fancy,
- inline_assets = list(
+ assets = list(
get_asset_datum(/datum/asset/simple/tgui_common),
get_asset_datum(/datum/asset/simple/tgui),
))
diff --git a/code/modules/tgui/tgui_window.dm b/code/modules/tgui/tgui_window.dm
index 62574cb1aacd..844ba6239a0f 100644
--- a/code/modules/tgui/tgui_window.dm
+++ b/code/modules/tgui/tgui_window.dm
@@ -18,8 +18,12 @@
var/message_queue
var/sent_assets = list()
// Vars passed to initialize proc (and saved for later)
- var/inline_assets
- var/fancy
+ var/initial_strict_mode
+ var/initial_fancy
+ var/initial_assets
+ var/initial_inline_html
+ var/initial_inline_js
+ var/initial_inline_css
var/mouse_event_macro_set = FALSE
/**
@@ -45,21 +49,30 @@
* state. You can begin sending messages right after initializing. Messages
* will be put into the queue until the window finishes loading.
*
- * optional inline_assets list List of assets to inline into the html.
- * optional inline_html string Custom HTML to inject.
- * optional fancy bool If TRUE, will hide the window titlebar.
+ * optional strict_mode bool - Enables strict error handling and BSOD.
+ * optional fancy bool - If TRUE and if this is NOT a panel, will hide the window titlebar.
+ * optional assets list - List of assets to load during initialization.
+ * optional inline_html string - Custom HTML to inject.
+ * optional inline_js string - Custom JS to inject.
+ * optional inline_css string - Custom CSS to inject.
*/
/datum/tgui_window/proc/initialize(
- inline_assets = list(),
+ strict_mode = FALSE,
+ fancy = FALSE,
+ assets = list(),
inline_html = "",
- fancy = FALSE)
+ inline_js = "",
+ inline_css = "")
log_tgui(client,
context = "[id]/initialize",
window = src)
if(!client)
return
- src.inline_assets = inline_assets
- src.fancy = fancy
+ src.initial_fancy = fancy
+ src.initial_assets = assets
+ src.initial_inline_html = inline_html
+ src.initial_inline_js = inline_js
+ src.initial_inline_css = inline_css
status = TGUI_WINDOW_LOADING
fatally_errored = FALSE
// Build window options
@@ -72,9 +85,10 @@
// Generate page html
var/html = SStgui.basehtml
html = replacetextEx(html, "\[tgui:windowId]", id)
- // Inject inline assets
+ html = replacetextEx(html, "\[tgui:strictMode]", strict_mode)
+ // Inject assets
var/inline_assets_str = ""
- for(var/datum/asset/asset in inline_assets)
+ for(var/datum/asset/asset in assets)
var/mappings = asset.get_url_mappings()
for(var/name in mappings)
var/url = mappings[name]
@@ -87,8 +101,17 @@
if(length(inline_assets_str))
inline_assets_str = "\n"
html = replacetextEx(html, "\n", inline_assets_str)
- // Inject custom HTML
- html = replacetextEx(html, "\n", inline_html)
+ // Inject inline HTML
+ if (inline_html)
+ html = replacetextEx(html, "", inline_html)
+ // Inject inline JS
+ if (inline_js)
+ inline_js = ""
+ html = replacetextEx(html, "", inline_js)
+ // Inject inline CSS
+ if (inline_css)
+ inline_css = ""
+ html = replacetextEx(html, "", inline_css)
// Open the window
client << browse(html, "window=[id];[options]")
// Detect whether the control is a browser
@@ -97,6 +120,20 @@
if(!is_browser)
winset(client, id, "on-close=\"uiclose [id]\"")
+/**
+ * public
+ *
+ * Reinitializes the panel with previous data used for initialization.
+ */
+/datum/tgui_window/proc/reinitialize()
+ initialize(
+ strict_mode = initial_strict_mode,
+ fancy = initial_fancy,
+ assets = initial_assets,
+ inline_html = initial_inline_html,
+ inline_js = initial_inline_js,
+ inline_css = initial_inline_css)
+
/**
* public
*
@@ -320,7 +357,7 @@
client << link(href_list["url"])
if("cacheReloaded")
// Reinitialize
- initialize(inline_assets = inline_assets, fancy = fancy)
+ reinitialize()
// Resend the assets
for(var/asset in sent_assets)
send_asset(asset)
diff --git a/code/modules/tgui_panel/tgui_panel.dm b/code/modules/tgui_panel/tgui_panel.dm
index fdd74389c837..44fbffd917ce 100644
--- a/code/modules/tgui_panel/tgui_panel.dm
+++ b/code/modules/tgui_panel/tgui_panel.dm
@@ -13,9 +13,9 @@
var/broken = FALSE
var/initialized_at
-/datum/tgui_panel/New(client/client)
+/datum/tgui_panel/New(client/client, id)
src.client = client
- window = new(client, "browseroutput")
+ window = new(client, id)
window.subscribe(src, PROC_REF(on_message))
/datum/tgui_panel/Del()
@@ -42,10 +42,12 @@
sleep(1)
initialized_at = world.time
// Perform a clean initialization
- window.initialize(inline_assets = list(
- get_asset_datum(/datum/asset/simple/tgui_common),
- get_asset_datum(/datum/asset/simple/tgui_panel),
- ))
+ window.initialize(
+ strict_mode = TRUE,
+ assets = list(
+ get_asset_datum(/datum/asset/simple/tgui_common),
+ get_asset_datum(/datum/asset/simple/tgui_panel),
+ ))
window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/fontawesome))
window.send_asset(get_asset_datum(/datum/asset/spritesheet/chat))
request_telemetry()
diff --git a/html/statbrowser.css b/html/statbrowser.css
new file mode 100644
index 000000000000..dc693f42f756
--- /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
index 2bb7f8259afb..1aea8811d58a 100644
--- a/html/statbrowser.html
+++ b/html/statbrowser.html
@@ -1,1280 +1,3 @@
-
-
-