From 93319097a173e13ef48c4fa57e3003bdd4868cbe Mon Sep 17 00:00:00 2001 From: Mikhail Dzianishchyts Date: Tue, 25 Mar 2025 23:07:47 +0300 Subject: [PATCH] Mass screenshot verb (#28537) * Mass screenshot verb * Well, this is actually useless * Apply suggestions from code review Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com> Signed-off-by: Mikhail Dzianishchyts --------- Signed-off-by: Mikhail Dzianishchyts Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com> --- code/_onclick/hud/ghost_hud.dm | 7 +- code/modules/admin/admin_verbs.dm | 3 +- code/modules/admin/verbs/screenshot.dm | 87 +++++++++++++++++++ paradise.dme | 1 + .../map_tile_image_tool.py | 85 ++++++++++++++++++ 5 files changed, 176 insertions(+), 7 deletions(-) create mode 100644 code/modules/admin/verbs/screenshot.dm create mode 100644 tools/map-tile-image-tool/map_tile_image_tool.py diff --git a/code/_onclick/hud/ghost_hud.dm b/code/_onclick/hud/ghost_hud.dm index 40c957b26c9..56650f032b7 100644 --- a/code/_onclick/hud/ghost_hud.dm +++ b/code/_onclick/hud/ghost_hud.dm @@ -117,12 +117,7 @@ plane_masters_update() return FALSE - . = ..() - if(!.) - return - var/mob/screenmob = viewmob || mymob - screenmob.client.screen += static_inventory - + return ..() // We should only see observed mob alerts. /datum/hud/ghost/reorganize_alerts(mob/viewmob) diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 0789af8ef8b..9ee5dcafec9 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -184,7 +184,8 @@ GLOBAL_LIST_INIT(admin_verbs_debug, list( /client/proc/visualize_interesting_turfs, /client/proc/profile_code, /client/proc/debug_atom_init, - /client/proc/debug_bloom + /client/proc/debug_bloom, + /client/proc/cmd_mass_screenshot, )) GLOBAL_LIST_INIT(admin_verbs_possess, list( /proc/possess, diff --git a/code/modules/admin/verbs/screenshot.dm b/code/modules/admin/verbs/screenshot.dm new file mode 100644 index 00000000000..6d7190398d4 --- /dev/null +++ b/code/modules/admin/verbs/screenshot.dm @@ -0,0 +1,87 @@ +/client/proc/cmd_mass_screenshot() + set category = "Debug" + set name = "Mass Screenshot" + set waitfor = FALSE + + if(!check_rights(R_DEBUG) || !mob) + return + + var/confirmation = tgui_alert( + usr, + "Are you sure you want to mass screenshot this z-level? \ + Ensure you have emptied your BYOND screenshots folder.", + "Mass Screenshot", + list("Yes", "No") + ) + if(confirmation != "Yes") + return + + var/sleep_duration = tgui_input_number( + usr, + "Enter a delay in deciseconds between screenshots to allow the client to render changes.", + "Screenshot delay", + default = 2, max_value = 10, min_value = 1, round_value = TRUE + ) + if(!sleep_duration) + return + + if(!isobserver(mob)) + admin_ghost() + + message_admins("[key_name(usr)] started a mass screenshot operation") + + // Prepare for screenshot + var/old_client_view = view + var/old_status_bar_visible = winget(src, "menu.statusbar", "is-checked") == "false" ? "true" : "false" + var/old_hud_version = mob.hud_used ? mob.hud_used.hud_version : HUD_STYLE_NOHUD + var/old_mob_alpha = mob.alpha + var/old_mob_movement = mob.animate_movement + + view = 15 + winset(src, "paramapwindow.status_bar", "is-visible=false") + mob.hud_used?.show_hud(HUD_STYLE_NOHUD) + mob.hud_used?.remove_parallax() + mob.alpha = 0 + mob.animate_movement = NO_STEPS + + var/half_chunk_size = view + 1 + var/chunk_size = half_chunk_size * 2 - 1 + var/cur_x = half_chunk_size + var/cur_y = half_chunk_size + var/cur_z = mob.z + var/width = world.maxx - half_chunk_size + 2 + var/height = world.maxy - half_chunk_size + 2 + var/width_inside = width - 1 + var/height_inside = height - 1 + + var/exception = null + try + while(cur_y < height) + while(cur_x < width) + mob.forceMove(locate(cur_x, cur_y, cur_z)) + sleep(sleep_duration) + winset(src, null, "command='.screenshot auto'") + if(cur_x == width_inside) + break + cur_x += chunk_size + cur_x = min(cur_x, width_inside) + if(cur_y == height_inside) + break + cur_x = half_chunk_size + cur_y += chunk_size + cur_y = min(cur_y, height_inside) + catch(var/exception/e) + exception = e + + // Bring UI back + view = old_client_view + winset(src, "paramapwindow.status_bar", "is-visible=" + old_status_bar_visible) + mob.alpha = old_mob_alpha + mob.hud_used?.show_hud(old_hud_version) + mob.hud_used?.update_parallax_pref() + mob.animate_movement = old_mob_movement + + if(exception) + throw exception + + to_chat(usr, "Provide these values when asked for the MapTileImageTool: [width] [height] [half_chunk_size] [world.icon_size]") diff --git a/paradise.dme b/paradise.dme index b648f45c506..c872d4c4ae6 100644 --- a/paradise.dme +++ b/paradise.dme @@ -1541,6 +1541,7 @@ #include "code\modules\admin\verbs\possess.dm" #include "code\modules\admin\verbs\pray.dm" #include "code\modules\admin\verbs\randomverbs.dm" +#include "code\modules\admin\verbs\screenshot.dm" #include "code\modules\admin\verbs\serialization.dm" #include "code\modules\admin\verbs\space_transitions.dm" #include "code\modules\admin\verbs\striketeam_syndicate.dm" diff --git a/tools/map-tile-image-tool/map_tile_image_tool.py b/tools/map-tile-image-tool/map_tile_image_tool.py new file mode 100644 index 00000000000..cf0919c3a88 --- /dev/null +++ b/tools/map-tile-image-tool/map_tile_image_tool.py @@ -0,0 +1,85 @@ +#This script processes screenshots from the Mass-Screenshot Debug verb in SS13 into a full map image + +# Loosely based on https://github.com/vortex1942/telescience/blob/master/src/tools/PhotoProcessor.py + +# This script uses the Pillow library (PIL) install it with pip install pillow + +#***************************************************************** +#******ALL .pngs in the rawimages folder will be processed******** +#********Exported file may be overwritten in the output*********** +#***************************************************************** + +from PIL import Image +from os import listdir, path + +# Selection of Input/Output directories +rawimgdir = str(input("Directory of RAW images: ")) +if path.exists(rawimgdir) == False: + print("Directory could not be found!") + exit(1) + +imgdir = str(input("Directory for output image (leave blank to use RAW image directory): ")) +if imgdir == "": + imgdir = rawimgdir +elif path.exists(imgdir) == False: + print("Directory could not be found!") + exit(1) + +exportfilename = str(input("Filename for Full Image (E.g LV624_Complete): ")) +if exportfilename == "": + print("Filename is invalid!") + exit(1) + +gamearguments = str(input("Values provided after using the Mass-Screenshot verb: ")) +gamearguments = gamearguments.split() +if len(gamearguments) != 4: + print("Invalid arguments!") + exit(1) +pixelsize = int(gamearguments[3]) +halfchunksize = int(gamearguments[2]) +width = (int(gamearguments[0]) + halfchunksize - 2) +height = (int(gamearguments[1]) + halfchunksize - 2) +if width < 1 or height < 1 or pixelsize < 1 or halfchunksize < 1 or halfchunksize * 2 >= width or halfchunksize * 2 >= height: + print("Invalid arguments!") + exit(1) +width *= pixelsize +height *= pixelsize +halfchunksize *= pixelsize + +# Function for saving the image (params: name=Filename, export=Image IMG variable) +def func_exportfullimage(name, export): + print("SAVING IMAGE") + file = (imgdir+"\\"+name+".png") + print(file) + file = open(file, "wb") + export.save(file) + +# Where the magic happens, Creates a canvas and pastes RAWimages in a grid fashion +masterexport = Image.new("RGBA", (width,height), color=(0,0,0,255)) +imagelist = [file for file in sorted(listdir(rawimgdir), key=lambda x: path.getmtime(rawimgdir+"\\"+x)) if file.endswith('.png')] +imagecount = len(imagelist) +chunksize = halfchunksize * 2 - pixelsize +x = fc = 0 +y = height - chunksize + +# For loop stitches RAw images together +for p in imagelist: + file = (rawimgdir+"\\"+p) + photo = Image.open(file).convert("RGBA") + #Verbose mode [Iteration] [Image Coords] [RAW Filename] + #print("iter: " f"{fc : >2}", "IMG XY: " f"{x : >4}", f"{y : >4}", "FILE: " f"{p : >13}") + masterexport.paste(photo, (x, y)) + x += chunksize + fc += 1 + if x >= width: + x = 0 + y -= chunksize + y = max(y, 0) + progress = fc / imagecount * 100 + print("%.1f" % progress, "%") + x = min(x, width - chunksize) + +func_exportfullimage(exportfilename, masterexport) + +# Hopefully you got this far +print("COMPLETED :)")