Files
Bubberstation/code/modules/admin/verbs/plane_debugger.dm
Zonespace f7c26bbf25 515 Compat (#17465)
* ONLY SKYRAT CHANGES

* ACTUALLY SKYRAT CHANGES

* yolo, revert later

* Update alternate_byond_versions.txt

Co-authored-by: AnturK <AnturK@users.noreply.github.com>
2022-11-15 06:59:06 +00:00

401 lines
15 KiB
Plaintext

/// Used for testing/debugger plane masters and their associated rendering plates
/datum/plane_master_debug
var/datum/admins/owner
/// Assoc list of plane master group key -> its depth stack
var/list/depth_stack = list()
/// The current plane master group we're viewing
var/current_group = PLANE_GROUP_MAIN
/// Weakref to the mob to edit
var/datum/weakref/mob_ref
var/datum/visual_data/tracking/stored
var/datum/visual_data/mirroring/mirror
/// If we are actively mirroring the target of our current ui
var/mirror_target = FALSE
/datum/plane_master_debug/New(datum/admins/owner)
src.owner = owner
/datum/plane_master_debug/Destroy()
if(owner)
owner.plane_debug = null
owner = null
return ..()
/datum/plane_master_debug/proc/set_target(mob/new_mob)
QDEL_NULL(mirror)
QDEL_NULL(stored)
depth_stack = list()
if(!new_mob?.hud_used)
new_mob = owner.owner?.mob
mob_ref = WEAKREF(new_mob)
if(!mirror_target)
UnregisterSignal(owner.owner.mob, COMSIG_MOB_LOGOUT)
return
RegisterSignal(owner.owner.mob, COMSIG_MOB_LOGOUT, PROC_REF(on_our_logout), override = TRUE)
mirror = new()
mirror.shadow(new_mob)
if(new_mob == owner.owner.mob)
return
create_store()
/datum/plane_master_debug/proc/on_our_logout(mob/source)
SIGNAL_HANDLER
// Recreate our stored view, since we've changed mobs now
create_store()
UnregisterSignal(source, COMSIG_MOB_LOGOUT)
RegisterSignal(owner.owner.mob, COMSIG_MOB_LOGOUT, PROC_REF(on_our_logout), override = TRUE)
/// Create or refresh our stored visual data, represeting the viewing mob
/datum/plane_master_debug/proc/create_store()
if(stored)
QDEL_NULL(stored)
stored = new()
stored.shadow(owner.owner.mob)
stored.set_truth(mirror)
mirror.set_mirror_target(owner.owner.mob)
/datum/plane_master_debug/proc/get_target()
var/mob/target = mob_ref?.resolve()
if(!target?.hud_used)
target = owner.owner.mob
set_target(target)
return target
/// Setter for mirror_target, basically allows for enabling/disabiling viewing through mob's sight
/datum/plane_master_debug/proc/set_mirroring(value)
if(value == mirror_target)
return
mirror_target = value
// Refresh our target and mirrors and such
set_target(get_target())
/datum/plane_master_debug/ui_state(mob/user)
return GLOB.admin_state
/datum/plane_master_debug/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "PlaneMasterDebug")
ui.open()
/datum/plane_master_debug/ui_assets(mob/user)
return list(get_asset_datum(/datum/asset/simple/plane_background))
/datum/plane_master_debug/ui_data()
var/list/data = list()
var/mob/reference_frame = get_target()
data["mob_name"] = reference_frame.name
data["mob_ref"] = ref(reference_frame)
data["our_ref"] = ref(owner.owner.mob)
data["tracking_active"] = mirror_target
var/datum/hud/our_hud = reference_frame.hud_used
var/list/our_groups = our_hud.master_groups
if(!our_groups[current_group])
// We assume we'll always have at least one group
current_group = our_groups[length(our_hud.master_groups)]
var/list/groups = list()
for(var/key in our_groups)
groups += key
data["enable_group_view"] = length(groups) > 1
data["our_group"] = current_group
data["present_groups"] = groups
var/list/plane_info = list()
data["plane_info"] = plane_info
var/list/relay_deets = list()
data["relay_info"] = relay_deets
var/list/filter_connections = list()
data["filter_connect"] = filter_connections
var/list/filter_queue = list()
// Assoc of render targets -> planes
// Gotta be able to look these up so filter stuff can work
var/list/render_target_to_plane = list()
// Assoc list of pending planes -> relays
// Used to ensure the incoming_relays list is filled, even if the relay's generated before the plane's processed
var/list/pending_relays = list()
var/list/our_planes = our_hud?.get_planes_from(current_group)
for(var/plane_string as anything in our_planes)
var/list/this_plane = list()
var/atom/movable/screen/plane_master/plane = our_planes[plane_string]
var/string_plane = "[plane.plane]"
this_plane["name"] = plane.name
this_plane["documentation"] = plane.documentation
this_plane["plane"] = plane.plane
this_plane["our_ref"] = string_plane
this_plane["offset"] = plane.offset
this_plane["real_plane"] = plane.real_plane
this_plane["renders_onto"] = plane.render_relay_planes
this_plane["blend_mode"] = GLOB.blend_names["[plane.blend_mode_override || initial(plane.blend_mode)]"]
this_plane["color"] = plane.color
this_plane["alpha"] = plane.alpha
this_plane["render_target"] = plane.render_target
this_plane["intended_hidden"] = plane.force_hidden
var/list/incoming_relays = list()
this_plane["incoming_relays"] = incoming_relays
for(var/pending_relay in pending_relays[string_plane])
incoming_relays += pending_relay
var/list/this_relay = relay_deets[pending_relay]
this_relay["target_index"] = length(incoming_relays)
this_plane["outgoing_relays"] = list()
// You can think of relays as connections between plane master "nodes
// They do have some info of their own tho, best to pass that along
for(var/atom/movable/render_plane_relay/relay in plane.relays)
var/string_target = "[relay.plane]"
var/list/this_relay = list()
this_relay["name"] = relay.name
this_relay["source"] = plane.plane
this_relay["source_ref"] = string_plane
this_relay["target"] = relay.plane
this_relay["target_ref"] = string_target
this_relay["layer"] = relay.layer
// Now taht we've encoded our relay, we need to hand out references to it to our source plane, alongside the target plane
var/relay_ref = "[string_plane]-[string_target]"
this_relay["our_ref"] = relay_ref
relay_deets[relay_ref] = this_relay
this_plane["outgoing_relays"] += relay_ref
// If we've already encoded our target plane, update its incoming relays list
// Otherwise, we'll handle this later
var/list/existing_target = plane_info[string_target]
if(existing_target)
existing_target["incoming_relays"] += relay_ref
else
var/list/pending_plane = pending_relays[string_target]
if(!pending_plane)
pending_plane = list()
pending_relays[string_target] = pending_plane
pending_plane += relay_ref
this_plane["incoming_filters"] = list()
this_plane["outgoing_filters"] = list()
// We're gonna collect a list of filters, partly because they're useful info
// But also because they can be used as connections, and we need to support that
for(var/filter_id in plane.filter_data)
var/list/filter = plane.filter_data[filter_id]
if(!filter["render_source"])
continue
var/list/filter_info = filter.Copy()
filter_info["target_ref"] = string_plane
filter_info["name"] = filter_id
filter_queue += list(filter_info)
plane_info[plane_string] = this_plane
render_target_to_plane[plane.render_target] = this_plane
for(var/list/filter in filter_queue)
var/source = filter["render_source"]
var/list/source_plane = render_target_to_plane[source]
var/list/target_plane = plane_info[filter["target_ref"]]
var/source_ref = source_plane["our_ref"]
filter["source_ref"] = source_ref
var/our_ref = "[source_ref]-[filter["target_ref"]]-filter"
filter["our_ref"] = our_ref
filter_connections[our_ref] = filter
source_plane["outgoing_filters"] += our_ref
target_plane["incoming_filters"] += our_ref
// Only load this once. Prevents leaving off orphaned components
if(!depth_stack[current_group])
depth_stack[current_group] = treeify(plane_info, relay_deets, filter_connections)
// We will use this js side to arrange our plane masters and such
// It's essentially a stack of where they should be displayed
data["depth_stack"] = depth_stack[current_group]
return data
// Reading this in the queue tells the search to increase the depth, and then push another increase command to the end of the stack
// This way we ensure groupings always stay together, and depth is respected
#define COMMAND_DEPTH_INCREASE "increase_depth"
#define COMMAND_NEXT_PARENT "next_parent"
/// Takes a list of js formatted planes, and turns it into a tree based off the back connections of relays
/// So start at the top master plane, and work down
/// Haha jerry what if I added commands to my list parser lmao lol
/datum/plane_master_debug/proc/treeify(list/plane_info, list/relay_info, list/filter_connections)
// List in the form [depth in num] -> list(list(plane_ref -> parent_ref, ...), ...)
var/list/treelike_output = list()
// List in the form plane ref -> current depth
var/list/plane_to_depth = list()
// List of items/commands to process. FIFO queue, to ensure the brackets are built correctly
var/list/processing_queue = list()
// A FIFO queue of parents. Used so planes can have refs to their direct parent, to make sorting easier
var/list/parents = list("")
var/parent_head = 1
// The current depth of our search, used with treelike_output
var/depth = 0
// Push a depth increase onto the queue, to properly setup the sorta looping effect it has
processing_queue += COMMAND_DEPTH_INCREASE
processing_queue += "[RENDER_PLANE_MASTER]"
// We need to do a c style loop here because we are expanding the queue, and so need to update our conditional
for(var/i = 1; i <= length(processing_queue); i++)
var/entry = processing_queue[i]
// We've reached the end of a depth block
// Increment the depth and stick another command on the end of the queue
if(entry == COMMAND_DEPTH_INCREASE)
// The plane to continue on with, assuming we can find an unvisited head to use
var/continue_on_with = ""
// Don't wanna infinite loop now
if(i == length(processing_queue))
for(var/plane in TRUE_PLANE_TO_OFFSETS(RENDER_PLANE_MASTER))
if(!plane_to_depth["[plane]"])
continue_on_with = "[plane]"
// We only want to handle one plane master at a time
break
if(!continue_on_with)
continue
// Increment our depth
depth += 1
treelike_output += list(list())
// If this isn't the end, stick another entry on the end to ensure batches work proper
processing_queue += COMMAND_DEPTH_INCREASE
// If we found a plane to use to extend our process, tack it on the end here as god intended
if(continue_on_with)
processing_queue += continue_on_with
continue
if(entry == COMMAND_NEXT_PARENT)
parent_head += 1
continue
var/old_queue_len = length(processing_queue)
var/existing_depth = plane_to_depth[entry]
// If we've seen you before, remove your last entry
// We always want inputs before outputs in the stack
if(existing_depth)
treelike_output[existing_depth] -= entry
// If it's not a command, it must be a plane string
var/list/plane = plane_info[entry]
/// We want master planes to ALWAYS bubble down to their own space.
/// Just ignore this if this is the head we're processing, yeah?
if(PLANE_TO_TRUE(plane["real_plane"]) == RENDER_PLANE_MASTER && i > 2)
// If there's other stuff already in your depth entry, or there's more then one thing (a depth increase command)
// Left in the queue, "bubble" down a layer.
if(length(treelike_output[depth]) || i + 1 != length(processing_queue))
processing_queue += COMMAND_NEXT_PARENT
parents += parents[parent_head]
processing_queue += entry
continue
// Add all the planes that pipe into us to the queue, Intentionally allows dupes
// If we find the same entry twice, it'll get moved down the depth stack
for(var/relay_string in plane["incoming_relays"])
var/list/relay = relay_info[relay_string]
processing_queue += relay["source_ref"]
for(var/filter_ref in plane["incoming_filters"])
var/list/filter = filter_connections[filter_ref]
processing_queue += filter["source_ref"]
// If the queue has grown, we're a parent, so stick us in the parent queue
if(old_queue_len != length(processing_queue))
parents += entry
// Stick a parent increase right before our children show up in the queue. That way we're properly set as their parent
processing_queue.Insert(old_queue_len + 1, COMMAND_NEXT_PARENT)
// Stick us in the output at our designated depth
var/list/plane_packet = list()
plane_packet[entry] = parents[parent_head]
treelike_output[depth] += plane_packet
plane_to_depth[entry] = depth
/// Walk treelike output, remove allll the empty lists we've accidentially generated
for(var/depth_index = 1; depth_index <= length(treelike_output); depth_index++)
var/list/layer = treelike_output[depth_index]
if(!length(layer))
treelike_output.Cut(depth_index, depth_index + 1)
depth_index -= 1
return treelike_output
#undef COMMAND_DEPTH_INCREASE
#undef COMMAND_NEXT_PARENT
/datum/plane_master_debug/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
var/mob/reference_frame = get_target()
var/datum/hud/our_hud = reference_frame.hud_used
var/datum/plane_master_group/group = our_hud?.master_groups[current_group]
if(!group) // Nothing to act on
return
var/list/our_planes = group.plane_masters
switch(action)
if("refresh")
group.rebuild_hud()
if("reset_mob")
set_target(null)
if("toggle_mirroring")
set_mirroring(!mirror_target)
if("vv_mob")
owner.owner.debug_variables(reference_frame)
if("set_group")
current_group = params["target_group"]
if("connect_relay")
var/source_plane = params["source"]
var/target_plane = params["target"]
var/atom/movable/screen/plane_master/source = our_planes["[source_plane]"]
if(source.get_relay_to(target_plane)) // Fuck off
return
source.add_relay_to(target_plane)
return TRUE
if("disconnect_relay")
var/source_plane = params["source"]
var/target_plane = params["target"]
var/atom/movable/screen/plane_master/source = our_planes["[source_plane]"]
source.remove_relay_from(text2num(target_plane))
return TRUE
if("disconnect_filter")
var/target_plane = params["target"]
var/atom/movable/screen/plane_master/filtered_plane = our_planes["[target_plane]"]
filtered_plane.remove_filter(params["name"])
return TRUE
if("vv_plane")
var/plane_edit = params["edit"]
var/atom/movable/screen/plane_master/edit = our_planes["[plane_edit]"]
var/mob/user = ui.user
user?.client?.debug_variables(edit)
return TRUE
if("set_alpha")
var/plane_edit = params["edit"]
var/atom/movable/screen/plane_master/edit = our_planes["[plane_edit]"]
var/newalpha = params["alpha"]
animate(edit, 0.4 SECONDS, alpha = newalpha)
return TRUE
if("edit_color_matrix")
var/plane_edit = params["edit"]
var/atom/movable/screen/plane_master/edit = our_planes["[plane_edit]"]
var/mob/user = ui.user
user?.client?.open_color_matrix_editor(edit)
return TRUE
if("edit_filters")
var/plane_edit = params["edit"]
var/atom/movable/screen/plane_master/edit = our_planes["[plane_edit]"]
var/mob/user = ui.user
user?.client?.open_filter_editor(edit)
return TRUE
/datum/plane_master_debug/ui_close(mob/user)
. = ..()
set_mirroring(FALSE)