From bfcdf83e55292cd867ba587aba5171e891a65508 Mon Sep 17 00:00:00 2001 From: Artur Date: Tue, 26 Oct 2021 16:31:36 +0300 Subject: [PATCH] buildmode mapgen tool save --- code/__DEFINES/mapexporting.dm | 14 ++ code/datums/components/storage/storage.dm | 12 ++ code/game/objects/objs.dm | 31 +++ .../structures/crates_lockers/closets.dm | 12 ++ code/modules/buildmode/submodes/save_area.dm | 65 +++++++ code/modules/mapexporting/mapexporter.dm | 184 ++++++++++++++++++ code/modules/mining/machine_silo.dm | 14 ++ tgstation.dme | 3 + 8 files changed, 335 insertions(+) create mode 100644 code/__DEFINES/mapexporting.dm create mode 100644 code/modules/buildmode/submodes/save_area.dm create mode 100644 code/modules/mapexporting/mapexporter.dm diff --git a/code/__DEFINES/mapexporting.dm b/code/__DEFINES/mapexporting.dm new file mode 100644 index 0000000000..cc0e2c0282 --- /dev/null +++ b/code/__DEFINES/mapexporting.dm @@ -0,0 +1,14 @@ +//Bits to save +#define SAVE_OBJECTS (1 << 1) //Save objects? +#define SAVE_MOBS (1 << 2) //Save Mobs? +#define SAVE_TURFS (1 << 3) //Save turfs? +#define SAVE_AREAS (1 << 4) //Save areas? +#define SAVE_SPACE (1 << 5) //Save space areas? (If not they will be saved as NOOP) +#define SAVE_OBJECT_PROPERTIES (1 << 6) //Save custom properties of objects (obj.on_object_saved() output) + +#define SAVE_ALL SAVE_OBJECTS | SAVE_MOBS | SAVE_TURFS | SAVE_AREAS | SAVE_SPACE | SAVE_OBJECT_PROPERTIES + +//Ignore turf if it contains +#define SAVE_SHUTTLEAREA_DONTCARE 0 +#define SAVE_SHUTTLEAREA_IGNORE 1 +#define SAVE_SHUTTLEAREA_ONLY 2 diff --git a/code/datums/components/storage/storage.dm b/code/datums/components/storage/storage.dm index 97d6748dc7..01a8f41c89 100644 --- a/code/datums/components/storage/storage.dm +++ b/code/datums/components/storage/storage.dm @@ -732,3 +732,15 @@ */ /datum/component/storage/proc/get_max_volume() return max_volume || AUTO_SCALE_STORAGE_VOLUME(max_w_class, max_combined_w_class) + +/obj/item/storage/on_object_saved(depth) + if(depth >= 10) + return "" + var/dat = "" + for(var/obj/item in contents) + var/metadata = generate_tgm_metadata(item) + dat += "[dat ? ",\n" : ""][item.type][metadata]" + //Save the contents of things inside the things inside us, EG saving the contents of bags inside lockers + var/custom_data = item.on_object_saved(depth++) + dat += "[custom_data ? ",\n[custom_data]" : ""]" + return dat diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 58c571d9e6..e263678943 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -383,3 +383,34 @@ /obj/proc/plunger_act(obj/item/plunger/P, mob/living/user, reinforced) return + + +//For returning special data when the object is saved +//For example, or silos will return a list of their materials which will be dumped on top of them +//Can be customised if you have something that contains something you want saved +//If you put an incorrect format it will break outputting, so don't use this if you don't know what you are doing +//NOTE: Contents is automatically saved, so if you store your things in the contents var, don't worry about this +//====Output Format Examples====: +//===Single Object=== +// "/obj/item/folder/blue" +//===Multiple Objects=== +// "/obj/item/folder/blue,\n +// /obj/item/folder/red" +//===Single Object with metadata=== +// "/obj/item/folder/blue{\n +// \tdir = 8;\n +// \tname = "special folder"\n +// \t}" +//===Multiple Objects with metadata=== +// "/obj/item/folder/blue{\n +// \tdir = 8;\n +// \tname = "special folder"\n +// \t},\n +// /obj/item/folder/red" +//====How to save easily====: +// return "[thing.type][generate_tgm_metadata(thing)]" +//Where thing is the additional thing you want to same (For example ores inside an ORM) +//Just add ,\n between each thing +//generate_tgm_metadata(thing) handles everything inside the {} for you +/obj/proc/on_object_saved(depth) + return "" diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index fe1fb03426..f5512f2301 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -672,3 +672,15 @@ if(allowed(user)) return TRUE to_chat(user, "Access denied.") + +/obj/structure/closet/on_object_saved(depth) + if(depth >= 10) + return "" + var/dat = "" + for(var/obj/item in contents) + var/metadata = generate_tgm_metadata(item) + dat += "[dat ? ",\n" : ""][item.type][metadata]" + //Save the contents of things inside the things inside us, EG saving the contents of bags inside lockers + var/custom_data = item.on_object_saved(depth++) + dat += "[custom_data ? ",\n[custom_data]" : ""]" + return dat diff --git a/code/modules/buildmode/submodes/save_area.dm b/code/modules/buildmode/submodes/save_area.dm new file mode 100644 index 0000000000..6cd235f161 --- /dev/null +++ b/code/modules/buildmode/submodes/save_area.dm @@ -0,0 +1,65 @@ +GLOBAL_VAR_INIT(save_area_executing, FALSE) + +/datum/mapGenerator/save_area + buildmode_name = "Save Area" + modules = list(/datum/mapGeneratorModule/save_area) + var/min_x = 0 + var/min_y = 0 + var/max_x = 0 + var/max_y = 0 + +/datum/mapGenerator/save_area/defineRegion(turf/Start, turf/End, replace = 0) + min_x = min(Start.x,End.x) + min_y = min(Start.y,End.y) + max_x = max(Start.x,End.x) + max_y = max(Start.y,End.y) + ..() + +/datum/mapGeneratorModule/save_area + var/areaName = "default.dm" + +//This could be optimised by making turfs that are the same go in the same, but this is a quick bodge solution so yea, fun job for coder here :) +/datum/mapGeneratorModule/save_area/generate() + var/datum/mapGenerator/save_area/L = mother + if(!istype(L)) + return + //If someone somehow gets build mode, stop them from using this. + if(!check_rights(R_ADMIN)) + message_admins("[ckey(usr)] tried to run the map save generator but was rejected due to insufficient perms.") + to_chat(usr, "You must have R_ADMIN privellages to use this.") + return + //Emergency check + if(L.map.len > 1600) + var/confirm = alert("Uhm, are you sure, the area is quiet large?", "Run generator", "Yes", "No") + if(confirm != "Yes") + return + + if(GLOB.save_area_executing) + to_chat(usr, "Someone is already running the generator! Try again in a bit.") + return + + to_chat(usr, "Saving, please wait...") + GLOB.save_area_executing = TRUE + + //Log just in case something happens + log_game("[key_name(usr)] ran the save level map generator on [L.map.len] turfs.") + message_admins("[key_name(usr)] ran the save level map generator on [L.map.len] turfs.") + + //Step 1: Get the data (This can take a while) + var/dat = "//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE\n" + dat += convert_map_to_tgm(L.map) + + //Step 2: Write the data to a file + var/filedir = file("data/temp.dmm") + if(fexists(filedir)) + fdel(filedir) + WRITE_FILE(filedir, "[dat]") + + //Step 3: Give the file to client for download + usr << ftp(filedir) + + //Step 4: Remove the file from the server (hopefully we can find a way to avoid step) + fdel(filedir) + log_game("[L.map.len] turfs have been saved by [ckey(usr)]") + alert("Area saved successfully.", "Action Successful!", "Ok") + GLOB.save_area_executing = FALSE diff --git a/code/modules/mapexporting/mapexporter.dm b/code/modules/mapexporting/mapexporter.dm new file mode 100644 index 0000000000..d21caa0f6c --- /dev/null +++ b/code/modules/mapexporting/mapexporter.dm @@ -0,0 +1,184 @@ +//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 + +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" +)) + +//Converts a list of turfs into TGM file format +/proc/convert_map_to_tgm(list/map,\ + save_flag = SAVE_ALL, \ + shuttle_area_flag = SAVE_SHUTTLEAREA_DONTCARE, \ + list/vars_to_save = list("pixel_x", "pixel_y", "dir", "name", "req_access", "req_access_txt", "piping_layer", "color", "icon_state", "pipe_color", "amount"),\ + list/obj_blacklist = list()) + //Calculate the bounds + var/minx = 1024 + var/miny = 1024 + var/maxx = -1 + var/maxy = -1 + for(var/turf/place in map) + minx = min(place.x, minx) + miny = min(place.y, miny) + maxx = max(place.x, maxx) + maxy = max(place.y, maxy) + var/width = maxx - minx + 1 + var/height = maxy - miny + 1 + + //Sort the map so weird shaped / bad inputted maps can be handled + var/list/sortedmap = sort_map(map, minx, miny, maxx, maxy) + + //Step 0: Calculate the amount of letters we need (26 ^ n > turf count) + var/turfsNeeded = width * height + var/layers = FLOOR(log(GLOB.save_file_chars.len, turfsNeeded) + 0.999,1) + + //Step 1: Run through the area and generate file data + var/list/header_chars = list() //The characters of the header + var/list/header_dat = list() //The data of the header, lines up with chars + var/header = "" //The actual header in text + var/contents = "" //The contents in text (bit at the end) + var/index = 1 + for(var/x in 1 to width) + contents += "\n([x],1,1) = {\"\n" + for(var/y in height to 1 step -1) + //====Get turfs Data==== + var/turf/place = sortedmap[x][y] + var/area/location + var/list/objects + //If there is nothing there, save as a noop (For odd shapes) + if(!place) + place = /turf/template_noop + location = /area/template_noop + objects = list() + //Ignore things in space, must be a space turf and the area has to be empty space + else if(istype(place, /turf/open/space) && istype(get_area(place), /area/space) && !(save_flag & SAVE_SPACE)) + place = /turf/template_noop + location = /area/template_noop + //Stuff to add + else + var/area/location_type = get_area(place) + location = location_type.type + objects = place + place = place.type + //====Saving shuttles only / non shuttles only==== + var/is_shuttle_area = istype(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 + objects = list() + //====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==== + var/header_char = calculate_tgm_header_index(index, layers) //The characters of the header + var/current_header = "(\n" //The actual stuff inside the header + //Add objects to the header file + var/empty = TRUE + //====SAVING OBJECTS==== + if(save_flag & SAVE_OBJECTS) + for(var/obj/thing in objects) + if(thing.type in obj_blacklist) + continue + var/metadata = generate_tgm_metadata(thing, vars_to_save) + 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 objects) + if(istype(thing, /mob/living/carbon)) //Ignore people, but not animals + continue + var/metadata = generate_tgm_metadata(thing, vars_to_save) + current_header += "[empty?"":",\n"][thing.type][metadata]" + empty = FALSE + current_header += "[empty?"":",\n"][place],\n[location])\n" + //====Fill the contents file==== + //Compression is done here + var/position_of_header = header_dat.Find(current_header) + if(position_of_header) + //If the header has already been saved, change the character to the other saved header + header_char = header_chars[position_of_header] + else + header += "\"[header_char]\" = [current_header]" + header_chars += header_char + header_dat += current_header + index ++ + contents += "[header_char]\n" + contents += "\"}" + return "[header][contents]" + +//Sorts maps in terms of their positions, so scrambled / odd shaped maps can be saved +/proc/sort_map(list/map, minx, miny, maxx, maxy) + var/width = maxx - minx + 1 + var/height = maxy - miny + 1 + var/allTurfs = new/list(width, height) + for(var/turf/place in map) + allTurfs[place.x - minx + 1][place.y - miny + 1] = place + return allTurfs + +//vars_to_save = list() to save all vars +/proc/generate_tgm_metadata(atom/O, list/vars_to_save = list("pixel_x", "pixel_y", "dir", "name", "req_access", "req_access_txt", "piping_layer", "color", "icon_state", "pipe_color", "amount")) + var/dat = "" + var/data_to_add = list() + for(var/V in O.vars) + if(O.get_save_vars()) + if(!(V in O.get_save_vars())) + continue + else + if(!(V in vars_to_save) && vars_to_save) + continue + var/value = O.vars[V] + if(!value) + continue + if(value == initial(O.vars[V]) || !issaved(O.vars[V])) + continue + var/symbol = "" + if(istext(value)) + symbol = "\"" + value = sanitize_simple(value, list("{"="", "}"="", "\""="", ";"="", ","="")) + else if(isicon(value) || isfile(value)) + symbol = "'" + else if(!(isnum(value) || ispath(value))) + continue + //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. + data_to_add += "[V] = [symbol][value][symbol]" + //Process data to add + var/first = TRUE + for(var/data in data_to_add) + dat += "[first ? "" : ";\n"]\t[data]" + first = FALSE + if(dat) + dat = "{\n[dat]\n\t}" + return dat + +/proc/calculate_tgm_header_index(index, layers) + var/output = "" + for(var/i in 1 to layers) + var/l = GLOB.save_file_chars.len + var/c = FLOOR((index-1) / (l ** (i - 1)), 1) + c = (c % l) + 1 + output = "[GLOB.save_file_chars[c]][output]" + return output diff --git a/code/modules/mining/machine_silo.dm b/code/modules/mining/machine_silo.dm index 34b349b198..e6281bb695 100644 --- a/code/modules/mining/machine_silo.dm +++ b/code/modules/mining/machine_silo.dm @@ -243,3 +243,17 @@ GLOBAL_LIST_EMPTY(silo_access_logs) sep = ", " msg += "[amount < 0 ? "-" : "+"][val] [M.name]" formatted = msg.Join() + +/obj/machinery/ore_silo/on_object_saved(var/depth = 0) + if(depth >= 10) + return "" + var/dat + 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] / MINERAL_MATERIAL_AMOUNT + var/datum/material/material_datum = each + while(amount > 0) + var/amount_in_stack = max(1, min(50, amount)) + amount -= amount_in_stack + dat += "[dat ? ",\n" : ""][material_datum.sheet_type]{\n\tamount = [amount_in_stack]\n\t}" + return dat diff --git a/tgstation.dme b/tgstation.dme index 30c413815b..0c72aa5c46 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -69,6 +69,7 @@ #include "code\__DEFINES\loadout.dm" #include "code\__DEFINES\logging.dm" #include "code\__DEFINES\machines.dm" +#include "code\__DEFINES\mapexporting.dm" #include "code\__DEFINES\maps.dm" #include "code\__DEFINES\materials.dm" #include "code\__DEFINES\maths.dm" @@ -1909,6 +1910,7 @@ #include "code\modules\buildmode\submodes\boom.dm" #include "code\modules\buildmode\submodes\copy.dm" #include "code\modules\buildmode\submodes\fill.dm" +#include "code\modules\buildmode\submodes\save_area.dm" #include "code\modules\buildmode\submodes\mapgen.dm" #include "code\modules\buildmode\submodes\throwing.dm" #include "code\modules\buildmode\submodes\variable_edit.dm" @@ -2453,6 +2455,7 @@ #include "code\modules\mafia\_defines.dm" #include "code\modules\mafia\controller.dm" #include "code\modules\mafia\map_pieces.dm" +#include "code\modules\mapexporting\mapexporter.dm" #include "code\modules\mafia\outfits.dm" #include "code\modules\mafia\roles.dm" #include "code\modules\mapping\map_config.dm"