Files
Bubberstation/code/modules/admin/verbs/map_export.dm
SpaceLoveSs13 a9ff046352 Administrator Cherrypick (#27405)
* Admin Verb Datums MkIII | Now with functional command bar (#82511)

* Modular stuffs

* Put some admin jump verbs back into the context menu | sorts area jump list again (#82647)

## About The Pull Request

See title.

## Why It's Good For The Game

Some admins wanted all the jump verbs back, aswell as making them not
AGhost you.
Also make the Jump To Area verb use a sorted list again

* Hey what if admins were allowed to use the player panel (#82682)

Re-adds the player panel verb to the verb panel.

* Controller Overview UI (#82739)

* Fixes a minor spelling mistake on the admin panel/verb list (#82747)

## About The Pull Request

Corrects `inisimin` to `invisimin`. This addresses #82728, but only
fixes one of the two issues mentioned

## Why It's Good For The Game

-1 spelling mistake

## Changelog
🆑
spellcheck: 'inisimin' verb corrected to 'invisimin'
/🆑

* Player Panel-age (#82757)

* Admin Forced Mob Rename and Preference Update (#82715)

---------

Co-authored-by: Zephyr <12817816+ZephyrTFA@users.noreply.github.com>
Co-authored-by: Useroth <37159550+Useroth@users.noreply.github.com>
Co-authored-by: chel <64568243+iliyaxox@users.noreply.github.com>
2024-04-23 20:43:45 +02:00

306 lines
10 KiB
Plaintext

ADMIN_VERB(map_export, R_DEBUG, "Map Export", "Select a part of the map by coordinates and download it.", ADMIN_CATEGORY_DEBUG)
var/user_x = user.mob.x
var/user_y = user.mob.y
var/user_z = user.mob.z
var/z_level = tgui_input_number(user, "Export Which Z-Level?", "Map Exporter", user_z || 2)
var/start_x = tgui_input_number(user, "Start X?", "Map Exporter", user_x || 1, world.maxx, 1)
var/start_y = tgui_input_number(user, "Start Y?", "Map Exporter", user_y || 1, world.maxy, 1)
var/end_x = tgui_input_number(user, "End X?", "Map Exporter", user_x || 1, world.maxx, 1)
var/end_y = tgui_input_number(user, "End Y?", "Map Exporter", user_y || 1, world.maxy, 1)
var/date = time2text(world.timeofday, "YYYY-MM-DD_hh-mm-ss")
var/file_name = sanitize_filename(tgui_input_text(user, "Filename?", "Map Exporter", "exported_map_[date]"))
var/confirm = tgui_alert(user, "Are you sure you want to do this? This will cause extreme lag!", "Map Exporter", list("Yes", "No"))
if(confirm != "Yes")
return
var/map_text = write_map(start_x, start_y, z_level, end_x, end_y, z_level)
log_admin("Build Mode: [key_name(user)] is exporting the map area from ([start_x], [start_y], [z_level]) through ([end_x], [end_y], [z_level])")
send_exported_map(user, file_name, map_text)
/**
* A procedure for saving DMM text to a file and then sending it to the user.
* Arguments:
* * user - a user which get map
* * name - name of file + .dmm
* * map - text with DMM format
*/
/proc/send_exported_map(user, name, map)
var/file_path = "data/[name].dmm"
rustg_file_write(map, file_path)
DIRECT_OUTPUT(user, ftp(file_path, "[name].dmm"))
var/file_to_delete = file(file_path)
fdel(file_to_delete)
/proc/sanitize_filename(text)
return hashtag_newlines_and_tabs(text, list("\n"="", "\t"="", "/"="", "\\"="", "?"="", "%"="", "*"="", ":"="", "|"="", "\""="", "<"="", ">"=""))
/proc/hashtag_newlines_and_tabs(text, list/repl_chars = list("\n"="#","\t"="#"))
for(var/char in repl_chars)
var/index = findtext(text, char)
while(index)
text = copytext(text, 1, index) + repl_chars[char] + copytext(text, index + length(char))
index = findtext(text, char, index + length(char))
return text
/**
* A procedure for saving non-standard properties of an object.
* For example, saving ore into a silo, and further spavn by coordinates of metal stacks objects
*/
/obj/proc/on_object_saved()
return null
// Save resources in silo
/obj/machinery/ore_silo/on_object_saved()
var/data
var/datum/component/material_container/material_holder = GetComponent(/datum/component/material_container)
for(var/each in material_holder.materials)
var/amount = material_holder.materials[each] / 100
var/datum/material/material_datum = each
while(amount > 0)
var/amount_in_stack = max(1, min(50, amount))
amount -= amount_in_stack
data += "[data ? ",\n" : ""][material_datum.sheet_type]{\n\tamount = [amount_in_stack]\n\t}"
return data
/**Map exporter
* Inputting a list of turfs into convert_map_to_tgm() will output a string
* with the turfs and their objects / areas on said turf into the TGM mapping format
* for .dmm files. This file can then be opened in the map editor or imported
* back into the game.
* ============================
* This has been made semi-modular so you should be able to use these functions
* elsewhere in code if you ever need to get a file in the .dmm format
**/
/atom/proc/get_save_vars()
return list(
NAMEOF(src, color),
NAMEOF(src, dir),
NAMEOF(src, icon),
NAMEOF(src, icon_state),
NAMEOF(src, name),
NAMEOF(src, pixel_x),
NAMEOF(src, pixel_y),
)
/obj/get_save_vars()
return ..() + list(NAMEOF(src, req_access), NAMEOF(src, id_tag))
/obj/item/stack/get_save_vars()
return ..() + NAMEOF(src, amount)
/obj/docking_port/get_save_vars()
return ..() + list(
NAMEOF(src, dheight),
NAMEOF(src, dwidth),
NAMEOF(src, height),
NAMEOF(src, shuttle_id),
NAMEOF(src, width),
)
/obj/docking_port/stationary/get_save_vars()
return ..() + NAMEOF(src, roundstart_template)
/obj/machinery/atmospherics/get_save_vars()
return ..() + list(
NAMEOF(src, piping_layer),
NAMEOF(src, pipe_color),
)
/obj/item/pipe/get_save_vars()
return ..() + list(
NAMEOF(src, piping_layer),
NAMEOF(src, pipe_color),
)
GLOBAL_LIST_INIT(save_file_chars, list(
"a","b","c","d","e",
"f","g","h","i","j",
"k","l","m","n","o",
"p","q","r","s","t",
"u","v","w","x","y",
"z","A","B","C","D",
"E","F","G","H","I",
"J","K","L","M","N",
"O","P","Q","R","S",
"T","U","V","W","X",
"Y","Z",
))
/proc/to_list_string(list/build_from)
var/list/build_into = list()
build_into += "list("
var/first_entry = TRUE
for(var/item in build_from)
CHECK_TICK
if(!first_entry)
build_into += ", "
if(isnum(item) || !build_from[item])
build_into += "[tgm_encode(item)]"
else
build_into += "[tgm_encode(item)] = [tgm_encode(build_from[item])]"
first_entry = FALSE
build_into += ")"
return build_into.Join("")
/// Takes a constant, encodes it into a TGM valid string
/proc/tgm_encode(value)
if(istext(value))
//Prevent symbols from being because otherwise you can name something
// [";},/obj/item/gun/energy/laser/instakill{name="da epic gun] and spawn yourself an instakill gun.
return "\"[hashtag_newlines_and_tabs("[value]", list("{"="", "}"="", "\""="", ";"="", ","=""))]\""
if(isnum(value) || ispath(value))
return "[value]"
if(islist(value))
return to_list_string(value)
if(isnull(value))
return "null"
if(isicon(value) || isfile(value))
return "'[value]'"
// not handled:
// - pops: /obj{name="foo"}
// - new(), newlist(), icon(), matrix(), sound()
// fallback: string
return tgm_encode("[value]")
/**
*Procedure for converting a coordinate-selected part of the map into text for the .dmi format
*/
/proc/write_map(
minx,
miny,
minz,
maxx,
maxy,
maxz,
save_flag = ALL,
shuttle_area_flag = SAVE_SHUTTLEAREA_DONTCARE,
list/obj_blacklist = list(),
)
var/width = maxx - minx
var/height = maxy - miny
var/depth = maxz - minz
//Step 0: Calculate the amount of letters we need (26 ^ n > turf count)
var/turfs_needed = width * height
var/layers = FLOOR(log(GLOB.save_file_chars.len, turfs_needed) + 0.999,1)
//Step 1: Run through the area and generate file data
var/list/header_data = list() //holds the data of a header -> to its key
var/list/header = list() //The actual header in text
var/list/contents = list() //The contents in text (bit at the end)
var/key_index = 1 // How many keys we've generated so far
for(var/z in 0 to depth)
for(var/x in 0 to width)
contents += "\n([x + 1],1,[z + 1]) = {\"\n"
for(var/y in height to 0 step -1)
CHECK_TICK
//====Get turfs Data====
var/turf/place
var/area/location
var/turf/pull_from = locate((minx + x), (miny + y), (minz + z))
//If there is nothing there, save as a noop (For odd shapes)
if(isnull(pull_from))
place = /turf/template_noop
location = /area/template_noop
//Ignore things in space, must be a space turf
else if(istype(pull_from, /turf/open/space) && !(save_flag & SAVE_SPACE))
place = /turf/template_noop
location = /area/template_noop
pull_from = null
//Stuff to add
else
var/area/place_area = get_area(pull_from)
location = place_area.type
place = pull_from.type
//====Saving shuttles only / non shuttles only====
var/is_shuttle_area = ispath(location, /area/shuttle)
if((is_shuttle_area && shuttle_area_flag == SAVE_SHUTTLEAREA_IGNORE) || (!is_shuttle_area && shuttle_area_flag == SAVE_SHUTTLEAREA_ONLY))
place = /turf/template_noop
location = /area/template_noop
pull_from = null
//====For toggling not saving areas and turfs====
if(!(save_flag & SAVE_AREAS))
location = /area/template_noop
if(!(save_flag & SAVE_TURFS))
place = /turf/template_noop
//====Generate Header Character====
// Info that describes this turf and all its contents
// Unique, will be checked for existing later
var/list/current_header = list()
current_header += "(\n"
//Add objects to the header file
var/empty = TRUE
//====SAVING OBJECTS====
if(save_flag & SAVE_OBJECTS)
for(var/obj/thing in pull_from)
CHECK_TICK
if(obj_blacklist[thing.type])
continue
var/metadata = generate_tgm_metadata(thing)
current_header += "[empty ? "" : ",\n"][thing.type][metadata]"
empty = FALSE
//====SAVING SPECIAL DATA====
//This is what causes lockers and machines to save stuff inside of them
if(save_flag & SAVE_OBJECT_PROPERTIES)
var/custom_data = thing.on_object_saved()
current_header += "[custom_data ? ",\n[custom_data]" : ""]"
//====SAVING MOBS====
if(save_flag & SAVE_MOBS)
for(var/mob/living/thing in pull_from)
CHECK_TICK
if(istype(thing, /mob/living/carbon)) //Ignore people, but not animals
continue
var/metadata = generate_tgm_metadata(thing)
current_header += "[empty ? "" : ",\n"][thing.type][metadata]"
empty = FALSE
current_header += "[empty ? "" : ",\n"][place],\n[location])\n"
//====Fill the contents file====
var/textiftied_header = current_header.Join()
// If we already know this header just use its key, otherwise we gotta make a new one
var/key = header_data[textiftied_header]
if(!key)
key = calculate_tgm_header_index(key_index, layers)
key_index++
header += "\"[key]\" = [textiftied_header]"
header_data[textiftied_header] = key
contents += "[key]\n"
contents += "\"}"
return "//[DMM2TGM_MESSAGE]\n[header.Join()][contents.Join()]"
/proc/generate_tgm_metadata(atom/object)
var/list/data_to_add = list()
var/list/vars_to_save = object.get_save_vars()
for(var/variable in vars_to_save)
CHECK_TICK
var/value = object.vars[variable]
if(value == initial(object.vars[variable]) || !issaved(object.vars[variable]))
continue
if(variable == "icon_state" && object.smoothing_flags)
continue
var/text_value = tgm_encode(value)
if(!text_value)
continue
data_to_add += "[variable] = [text_value]"
if(!length(data_to_add))
return
return "{\n\t[data_to_add.Join(";\n\t")]\n\t}"
// Could be inlined, not a massive cost tho so it's fine
/// Generates a key matching our index
/proc/calculate_tgm_header_index(index, key_length)
var/list/output = list()
// We want to stick the first one last, so we walk backwards
var/list/pull_from = GLOB.save_file_chars
var/length = length(pull_from)
for(var/i in key_length to 1 step -1)
var/calculated = FLOOR((index-1) / (length ** (i - 1)), 1)
calculated = (calculated % length) + 1
output += pull_from[calculated]
return output.Join()