From 5c5fc3795912f59b46bfa8b9f4cb19003c935d43 Mon Sep 17 00:00:00 2001 From: kevinz000 <2003111+kevinz000@users.noreply.github.com> Date: Wed, 6 May 2020 08:50:50 -0700 Subject: [PATCH] Introducing noitatsxoB (#12073) * okay * ok * ok * ok * ok * why am i doing this * WHY DID I DO THIS * mirror doesn't work * compile * shuttles should work * fix engine * fix engine * off by 1 * ok * adds proccall to write next map * haha post processing funny * fixes * Update code/modules/mapping/map_template.dm Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com> * Update code/modules/mapping/map_template.dm Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com> * Update code/modules/mapping/map_template.dm Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com> * Update code/modules/mapping/map_template.dm Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com> Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com> --- code/__DEFINES/mapping/maploader.dm | 3 + code/controllers/subsystem/mapping.dm | 6 +- code/datums/shuttles.dm | 9 +- code/game/objects/effects/landmarks.dm | 3 +- .../admin/verbs/map_template_loadverb.dm | 5 +- code/modules/mapping/dmm_suite.dm | 63 ----- .../{datums => modules/mapping}/map_config.dm | 31 ++- .../mapping/map_orientation_pattern.dm | 46 ++++ code/modules/mapping/map_template.dm | 151 +++++++++--- code/modules/mapping/preloader.dm | 27 ++- code/modules/mapping/reader.dm | 221 +++++++++++------- code/modules/power/apc.dm | 4 + code/modules/power/cable.dm | 15 ++ code/modules/shuttle/manipulator.dm | 2 +- tgstation.dme | 5 +- 15 files changed, 398 insertions(+), 193 deletions(-) create mode 100644 code/__DEFINES/mapping/maploader.dm delete mode 100644 code/modules/mapping/dmm_suite.dm rename code/{datums => modules/mapping}/map_config.dm (83%) create mode 100644 code/modules/mapping/map_orientation_pattern.dm diff --git a/code/__DEFINES/mapping/maploader.dm b/code/__DEFINES/mapping/maploader.dm new file mode 100644 index 0000000000..e225e68867 --- /dev/null +++ b/code/__DEFINES/mapping/maploader.dm @@ -0,0 +1,3 @@ +//map template annihilate_bounds +#define MAP_TEMPLATE_ANNIHILATE_PRELOAD 1 //annihilate bounds before starting loading +#define MAP_TEMPLATE_ANNIHILATE_LOADING 2 //"sweeping" delete during loading diff --git a/code/controllers/subsystem/mapping.dm b/code/controllers/subsystem/mapping.dm index 526e4b98e3..f1f7cf356a 100644 --- a/code/controllers/subsystem/mapping.dm +++ b/code/controllers/subsystem/mapping.dm @@ -196,7 +196,7 @@ SUBSYSTEM_DEF(mapping) z_list = SSmapping.z_list -/datum/controller/subsystem/mapping/proc/LoadGroup(list/errorList, name, path, files, list/traits, list/default_traits, silent = FALSE) +/datum/controller/subsystem/mapping/proc/LoadGroup(list/errorList, name, path, files, list/traits, list/default_traits, silent = FALSE, orientation = SOUTH) . = list() var/start_time = REALTIMEOFDAY @@ -236,7 +236,7 @@ SUBSYSTEM_DEF(mapping) // load the maps for (var/P in parsed_maps) var/datum/parsed_map/pm = P - if (!pm.load(1, 1, start_z + parsed_maps[P], no_changeturf = TRUE)) + if (!pm.load(1, 1, start_z + parsed_maps[P], no_changeturf = TRUE, orientation = orientation)) errorList |= pm.original_path if(!silent) INIT_ANNOUNCE("Loaded [name] in [(REALTIMEOFDAY - start_time)/10]s!") @@ -252,7 +252,7 @@ SUBSYSTEM_DEF(mapping) // load the station station_start = world.maxz + 1 INIT_ANNOUNCE("Loading [config.map_name]...") - LoadGroup(FailedZs, "Station", config.map_path, config.map_file, config.traits, ZTRAITS_STATION) + LoadGroup(FailedZs, "Station", config.map_path, config.map_file, config.traits, ZTRAITS_STATION, FALSE, config.orientation) if(SSdbcore.Connect()) var/datum/DBQuery/query_round_map_name = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET map_name = '[config.map_name]' WHERE id = [GLOB.round_id]") diff --git a/code/datums/shuttles.dm b/code/datums/shuttles.dm index 0a44954e51..6e4b1a6305 100644 --- a/code/datums/shuttles.dm +++ b/code/datums/shuttles.dm @@ -23,14 +23,14 @@ mappath = "[prefix][shuttle_id].dmm" . = ..() -/datum/map_template/shuttle/preload_size(path, cache) +/datum/map_template/shuttle/preload_size(path = mappath, force_cache = FALSE) . = ..(path, TRUE) // Done this way because we still want to know if someone actualy wanted to cache the map if(!cached_map) return discover_port_offset() - if(!cache) + if(!cached_map) cached_map = null /datum/map_template/shuttle/proc/discover_port_offset() @@ -53,12 +53,11 @@ ++xcrd --ycrd -/datum/map_template/shuttle/load(turf/T, centered, register=TRUE) +/datum/map_template/shuttle/load(turf/T, centered = FALSE, orientation = SOUTH, annihilate = default_annihilate, force_cache = FALSE, rotate_placement_to_orientation = FALSE, register = TRUE) . = ..() if(!.) return - var/list/turfs = block( locate(.[MAP_MINX], .[MAP_MINY], .[MAP_MINZ]), - locate(.[MAP_MAXX], .[MAP_MAXY], .[MAP_MAXZ])) + var/list/turfs = get_last_loaded_turf_block() for(var/i in 1 to turfs.len) var/turf/place = turfs[i] if(istype(place, /turf/open/space)) // This assumes all shuttles are loaded in a single spot then moved to their real destination. diff --git a/code/game/objects/effects/landmarks.dm b/code/game/objects/effects/landmarks.dm index 851a041aab..9934f1a658 100644 --- a/code/game/objects/effects/landmarks.dm +++ b/code/game/objects/effects/landmarks.dm @@ -492,7 +492,7 @@ INITIALIZE_IMMEDIATE(/obj/effect/landmark/start/new_player) if(!template) return FALSE testing("Room \"[template_name]\" placed at ([T.x], [T.y], [T.z])") - template.load(T, centered = FALSE) + template.load(T, centered = FALSE, orientation = dir, rotate_placement_to_orientation = TRUE) template.loaded++ GLOB.stationroom_landmarks -= src qdel(src) @@ -504,7 +504,6 @@ INITIALIZE_IMMEDIATE(/obj/effect/landmark/start/new_player) templates = list("Engine SM" = 3, "Engine Singulo" = 3, "Engine Tesla" = 3) icon = 'icons/rooms/box/engine.dmi' - /obj/effect/landmark/stationroom/box/engine/New() . = ..() templates = CONFIG_GET(keyed_list/box_random_engine) diff --git a/code/modules/admin/verbs/map_template_loadverb.dm b/code/modules/admin/verbs/map_template_loadverb.dm index d71cdca991..e89c3ad5a6 100644 --- a/code/modules/admin/verbs/map_template_loadverb.dm +++ b/code/modules/admin/verbs/map_template_loadverb.dm @@ -18,9 +18,12 @@ var/image/item = image('icons/turf/overlays.dmi',S,"greenOverlay") item.plane = ABOVE_LIGHTING_PLANE preview += item + var/list/orientations = list("South" = SOUTH, "North" = NORTH, "East" = EAST, "West" = WEST) + var/choice = input(src, "Which orientation? Maps are normally facing SOUTH.", "Template Orientation", "South") as null|anything in orientations + var/orientation = orientations[choice] images += preview if(alert(src,"Confirm location.","Template Confirm","Yes","No") == "Yes") - if(template.load(T, centered = TRUE)) + if(template.load(T, centered = TRUE, orientation = orientation)) message_admins("[key_name_admin(src)] has placed a map template ([template.name]) at [ADMIN_COORDJMP(T)]") else to_chat(src, "Failed to place map") diff --git a/code/modules/mapping/dmm_suite.dm b/code/modules/mapping/dmm_suite.dm deleted file mode 100644 index c4ceec33ee..0000000000 --- a/code/modules/mapping/dmm_suite.dm +++ /dev/null @@ -1,63 +0,0 @@ -dmm_suite{ - /* - - dmm_suite version 1.0 - Released January 30th, 2011. - - NOTE: Map saving functionality removed - - defines the object /dmm_suite - - Provides the proc load_map() - - Loads the specified map file onto the specified z-level. - - provides the proc write_map() - - Returns a text string of the map in dmm format - ready for output to a file. - - provides the proc save_map() - - Returns a .dmm file if map is saved - - Returns FALSE if map fails to save - - The dmm_suite provides saving and loading of map files in BYOND's native DMM map - format. It approximates the map saving and loading processes of the Dream Maker - and Dream Seeker programs so as to allow editing, saving, and loading of maps at - runtime. - - ------------------------ - - To save a map at runtime, create an instance of /dmm_suite, and then call - write_map(), which accepts three arguments: - - A turf representing one corner of a three dimensional grid (Required). - - Another turf representing the other corner of the same grid (Required). - - Any, or a combination, of several bit flags (Optional, see documentation). - - The order in which the turfs are supplied does not matter, the /dmm_writer will - determine the grid containing both, in much the same way as DM's block() function. - write_map() will then return a string representing the saved map in dmm format; - this string can then be saved to a file, or used for any other purose. - - ------------------------ - - To load a map at runtime, create an instance of /dmm_suite, and then call load_map(), - which accepts two arguments: - - A .dmm file to load (Required). - - A number representing the z-level on which to start loading the map (Optional). - - The /dmm_suite will load the map file starting on the specified z-level. If no - z-level was specified, world.maxz will be increased so as to fit the map. Note - that if you wish to load a map onto a z-level that already has objects on it, - you will have to handle the removal of those objects. Otherwise the new map will - simply load the new objects on top of the old ones. - - Also note that all type paths specified in the .dmm file must exist in the world's - code, and that the /dmm_reader trusts that files to be loaded are in fact valid - .dmm files. Errors in the .dmm format will cause runtime errors. - - */ - - verb/load_map(var/dmm_file as file, var/x_offset as num, var/y_offset as num, var/z_offset as num, var/cropMap as num, var/measureOnly as num, no_changeturf as num){ - // dmm_file: A .dmm file to load (Required). - // z_offset: A number representing the z-level on which to start loading the map (Optional). - // cropMap: When true, the map will be cropped to fit the existing world dimensions (Optional). - // measureOnly: When true, no changes will be made to the world (Optional). - // no_changeturf: When true, turf/AfterChange won't be called on loaded turfs - } -} \ No newline at end of file diff --git a/code/datums/map_config.dm b/code/modules/mapping/map_config.dm similarity index 83% rename from code/datums/map_config.dm rename to code/modules/mapping/map_config.dm index 5fcd1ae93c..176d58f337 100644 --- a/code/datums/map_config.dm +++ b/code/modules/mapping/map_config.dm @@ -38,6 +38,10 @@ var/year_offset = 540 //The offset of ingame year from the actual IRL year. You know you want to make a map that takes place in the 90's. Don't lie. + // "fun things" + /// Orientation to load in by default. + var/orientation = SOUTH //byond defaults to placing everyting SOUTH. + /proc/load_map_config(filename = "data/next_map.json", default_to_box, delete_after, error_if_missing = TRUE) var/datum/map_config/config = new if (default_to_box) @@ -139,13 +143,18 @@ if ("minetype" in json) minetype = json["minetype"] - + if ("maptype" in json) maptype = json["maptype"] if ("announcertype" in json) announcertype = json["announcertype"] + if ("orientation" in json) + orientation = json["orientation"] + if(!(orientation in GLOB.cardinals)) + orientation = SOUTH + allow_custom_shuttles = json["allow_custom_shuttles"] != FALSE defaulted = FALSE @@ -161,3 +170,23 @@ /datum/map_config/proc/MakeNextMap() return config_filename == "data/next_map.json" || fcopy(config_filename, "data/next_map.json") + +/// badmin moments. Keep up to date with LoadConfig()! +/datum/map_config/proc/WriteNextMap() + var/list/jsonlist = list() + jsonlist["map_name"] = map_name + jsonlist["map_path"] = map_path + jsonlist["map_file"] = map_file + jsonlist["shuttles"] = shuttles + jsonlist["traits"] = traits + jsonlist["space_ruin_levels"] = space_ruin_levels + jsonlist["year_offset"] = year_offset + jsonlist["minetype"] = minetype + jsonlist["maptype"] = maptype + jsonlist["announcertype"] = announcertype + jsonlist["orientation"] = orientation + jsonlist["allow_custom_shuttles"] = allow_custom_shuttles + if(fexists("data/next_map.json")) + fdel("data/next_map.json") + var/F = file("data/next_map.json") + WRITE_FILE(F, json_encode(jsonlist)) diff --git a/code/modules/mapping/map_orientation_pattern.dm b/code/modules/mapping/map_orientation_pattern.dm new file mode 100644 index 0000000000..565c25bef1 --- /dev/null +++ b/code/modules/mapping/map_orientation_pattern.dm @@ -0,0 +1,46 @@ +GLOBAL_LIST_INIT(map_orientation_patterns, list( + TEXT_NORTH = new /datum/map_orientation_pattern/north, + TEXT_SOUTH = new /datum/map_orientation_pattern/south, + TEXT_EAST = new /datum/map_orientation_pattern/east, + TEXT_WEST = new /datum/map_orientation_pattern/west + )) + +/datum/map_orientation_pattern + var/invert_x + var/invert_y + var/swap_xy + var/xi + var/yi + var/turn_angle + +/datum/map_orientation_pattern/north + invert_y = TRUE + invert_x = TRUE + swap_xy = FALSE + xi = -1 + yi = 1 + turn_angle = 180 + +/datum/map_orientation_pattern/south + invert_y = FALSE + invert_x = FALSE + swap_xy = FALSE + xi = 1 + yi = -1 + turn_angle = 0 + +/datum/map_orientation_pattern/east + invert_y = TRUE + invert_x = FALSE + swap_xy = TRUE + xi = 1 + yi = 1 + turn_angle = 90 + +/datum/map_orientation_pattern/west + invert_y = FALSE + invert_x = TRUE + swap_xy = TRUE + xi = -1 + yi = -1 + turn_angle = 270 diff --git a/code/modules/mapping/map_template.dm b/code/modules/mapping/map_template.dm index 873c10fc02..087552fada 100644 --- a/code/modules/mapping/map_template.dm +++ b/code/modules/mapping/map_template.dm @@ -1,11 +1,14 @@ /datum/map_template var/name = "Default Template Name" - var/width = 0 + var/width = 0 //all these are for SOUTH! var/height = 0 - var/mappath = null + var/zdepth = 1 + var/mappath var/loaded = 0 // Times loaded this round var/datum/parsed_map/cached_map var/keep_cached_map = FALSE + var/default_annihilate = FALSE + var/list/ztraits //zlevel traits for load_new_z /datum/map_template/New(path = null, rename = null, cache = FALSE) if(path) @@ -15,16 +18,45 @@ if(rename) name = rename -/datum/map_template/proc/preload_size(path, cache = FALSE) +/datum/map_template/Destroy() + QDEL_NULL(cached_map) + return ..() + +/datum/map_template/proc/preload_size(path = mappath, force_cache = FALSE) + if(cached_map) + return cached_map.parsed_bounds var/datum/parsed_map/parsed = new(file(path)) - var/bounds = parsed?.bounds + var/bounds = parsed?.parsed_bounds if(bounds) - width = bounds[MAP_MAXX] // Assumes all templates are rectangular, have a single Z level, and begin at 1,1,1 - height = bounds[MAP_MAXY] - if(cache) + width = bounds[MAP_MAXX] - bounds[MAP_MINX] + 1 + height = bounds[MAP_MAXY] - bounds[MAP_MINY] + 1 + zdepth = bounds[MAP_MAXZ] - bounds[MAP_MINZ] + 1 + if(force_cache || keep_cached_map) cached_map = parsed return bounds +/datum/map_template/proc/get_parsed_bounds() + return preload_size(mappath) + +/datum/map_template/proc/get_last_loaded_bounds() + if(cached_map) + return cached_map.bounds + return get_parsed_bounds() + +/datum/map_template/proc/get_last_loaded_turf_block() + if(!cached_map) + CRASH("Improper use of get_last_loaded_turf_block, no cached_map.") + var/list/B = cached_map.bounds + return block(locate(B[MAP_MINX], B[MAP_MINY], B[MAP_MINZ]), locate(B[MAP_MAXX], B[MAP_MAXY], B[MAP_MAXZ])) + +/datum/map_template/proc/get_size(orientation = SOUTH) + if(!width || !height || !zdepth) + preload_size(mappath) + var/rotate = (orientation & (NORTH|SOUTH)) != NONE + if(rotate) + return list(height, width, zdepth) + return list(width, height, zdepth) + /datum/parsed_map/proc/initTemplateBounds() var/list/obj/machinery/atmospherics/atmos_machines = list() var/list/obj/structure/cable/cables = list() @@ -55,12 +87,12 @@ SSmachines.setup_template_powernets(cables) SSair.setup_template_machinery(atmos_machines) -/datum/map_template/proc/load_new_z(list/traits = list(ZTRAIT_AWAY = TRUE)) - var/x = round((world.maxx - width)/2) - var/y = round((world.maxy - height)/2) +/datum/map_template/proc/load_new_z(orientation = SOUTH, list/ztraits = src.ztraits || list(ZTRAIT_AWAY = TRUE), centered = TRUE) + var/x = centered? max(round((world.maxx - width) / 2), 1) : 1 + var/y = centered? max(round((world.maxy - height) / 2), 1) : 1 - var/datum/space_level/level = SSmapping.add_new_zlevel(name, traits) - var/datum/parsed_map/parsed = load_map(file(mappath), x, y, level.z_value, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=TRUE) + var/datum/space_level/level = SSmapping.add_new_zlevel(name, ztraits) + var/datum/parsed_map/parsed = load_map(file(mappath), x, y, level.z_value, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop = TRUE, orientation = orientation) var/list/bounds = parsed.bounds if(!bounds) return FALSE @@ -71,31 +103,67 @@ parsed.initTemplateBounds() smooth_zlevel(world.maxz) log_game("Z-level [name] loaded at [x],[y],[world.maxz]") + on_map_loaded(world.maxz, parsed.bounds) return level -/datum/map_template/proc/load(turf/T, centered = FALSE) +//Override for custom behavior +/datum/map_template/proc/on_map_loaded(z, list/bounds) + loaded++ + +/** + * Proc to trigger a load at a specific area. Calls on_map_loaded(T.z, loaded_bounds) afterwards. + * + * @params + * * turf/T - Turf to load at + * * centered - Center at T or load with the bottomright corner being at T + * * orientation - SOUTH is default, anything else rotates the map to face it with the point of reference being the map itself is facing south by default. Cardinals only, don't be a 4head and put in multiple flags. It won't work or be pretty if you try. + * * annihilate - Should we destroy stuff in our bounds while loading + * * force_cache - Should we force the parsed shuttle to cache instead of being GC'd post loading if it wasn't going to be cached by default + * * rotate_placement_to_orientation - Has no effect if centered. Should we rotate where we load it around the turf we're loading at? Used for stuff like engine submaps when the station is rotated. + * + */ +/datum/map_template/proc/load(turf/T, centered = FALSE, orientation = SOUTH, annihilate = default_annihilate, force_cache = FALSE, rotate_placement_to_orientation = FALSE) + var/old_T = T if(centered) - T = locate(T.x - round(width/2) , T.y - round(height/2) , T.z) + T = locate(T.x - round(((orientation & (NORTH|SOUTH))? width : height) / 2) , T.y - round(((orientation & (NORTH|SOUTH)) ? height : width) / 2) , T.z) // %180 catches East/West (90,270) rotations on true, North/South (0,180) rotations on false + else if(rotate_placement_to_orientation && (orientation != SOUTH)) + var/newx = T.x + var/newy = T.y + if(orientation == NORTH) + newx -= width + newy -= height - 1 + else if(orientation == WEST) + newy -= width + else if(orientation == EAST) + newx -= height - 1 + // eh let's not silently fail. + if(!ISINRANGE(newx, 1, world.maxx) || !ISINRANGE(newy, 1, world.maxy)) + stack_trace("Warning: Rotation placed a map template load spot ([COORD(T)]) out of bounds of the game world. Clamping to world borders, this might cause issues.") + T = locate(clamp(newx, 1, world.maxx), clamp(newy, 1, world.maxy), T.z) if(!T) return - if(T.x+width > world.maxx) + if(T.x+width-1 > world.maxx) return - if(T.y+height > world.maxy) + if(T.y+height-1 > world.maxy) return - var/list/border = block(locate(max(T.x-1, 1), max(T.y-1, 1), T.z), - locate(min(T.x+width+1, world.maxx), min(T.y+height+1, world.maxy), T.z)) - for(var/L in border) - var/turf/turf_to_disable = L + var/list/border = block(locate(max(T.x - 1, 1), max(T.y - 1, 1), T.z), + locate(min(T.x + width + 1, world.maxx), min(T.y + height + 1, world.maxy), T.z)) + for(var/i in border) + var/turf/turf_to_disable = i SSair.remove_from_active(turf_to_disable) //stop processing turfs along the border to prevent runtimes, we return it in initTemplateBounds() turf_to_disable.atmos_adjacent_turfs?.Cut() + if(annihilate == MAP_TEMPLATE_ANNIHILATE_PRELOAD) + annihilate_bounds(old_T, centered, orientation) + // Accept cached maps, but don't save them automatically - we don't want // ruins clogging up memory for the whole round. - var/datum/parsed_map/parsed = cached_map || new(file(mappath)) - cached_map = keep_cached_map ? parsed : null - if(!parsed.load(T.x, T.y, T.z, cropMap=TRUE, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=TRUE)) + var/is_cached = cached_map + var/datum/parsed_map/parsed = is_cached || new(file(mappath)) + cached_map = (force_cache || keep_cached_map) ? parsed : is_cached + if(!parsed.load(T.x, T.y, T.z, cropMap=TRUE, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=TRUE, orientation = orientation, annihilate_tiles = (annihilate == MAP_TEMPLATE_ANNIHILATE_LOADING))) return var/list/bounds = parsed.bounds if(!bounds) @@ -108,19 +176,36 @@ parsed.initTemplateBounds() log_game("[name] loaded at [T.x],[T.y],[T.z]") + on_map_loaded(T.z, parsed.bounds) + return bounds -/datum/map_template/proc/get_affected_turfs(turf/T, centered = FALSE) - var/turf/placement = T - if(centered) - var/turf/corner = locate(placement.x - round(width/2), placement.y - round(height/2), placement.z) - if(corner) - placement = corner - return block(placement, locate(placement.x+width-1, placement.y+height-1, placement.z)) - +//This, get_affected_turfs, and load() calculations for bounds/center can probably be optimized. Later. +/datum/map_template/proc/annihilate_bounds(turf/origin, centered = FALSE, orientation = SOUTH) + var/deleted_atoms = 0 + log_world("Annihilating objects in map loading location.") + var/list/turfs_to_clean = get_affected_turfs(origin, centered, orientation) + if(turfs_to_clean.len) + var/list/kill_these = list() + for(var/i in turfs_to_clean) + var/turf/T = i + kill_these += T.contents + for(var/i in kill_these) + qdel(i) + CHECK_TICK + deleted_atoms++ + log_world("Annihilated [deleted_atoms] objects.") //for your ever biggening badminnery kevinz000 //❤ - Cyberboss -/proc/load_new_z_level(file, name, list/traits) +/proc/load_new_z_level(file, name, orientation, list/ztraits) var/datum/map_template/template = new(file, name) - return template.load_new_z(traits) + return template.load_new_z(orientation, ztraits) + +/datum/map_template/proc/get_affected_turfs(turf/T, centered = FALSE, orientation = SOUTH) + var/turf/placement = T + if(centered) + var/turf/corner = locate(placement.x - round(((orientation & (NORTH|SOUTH))? width : height) / 2), placement.y - round(((orientation & (NORTH|SOUTH))? height : width) / 2), placement.z) // %180 catches East/West (90,270) rotations on true, North/South (0,180) rotations on false + if(corner) + placement = corner + return block(placement, locate(placement.x + ((orientation & (NORTH|SOUTH)) ? width : height) - 1, placement.y + ((orientation & (NORTH|SOUTH))? height : width) - 1, placement.z)) diff --git a/code/modules/mapping/preloader.dm b/code/modules/mapping/preloader.dm index 4b61663f66..16adb43113 100644 --- a/code/modules/mapping/preloader.dm +++ b/code/modules/mapping/preloader.dm @@ -7,13 +7,21 @@ GLOBAL_DATUM_INIT(_preloader, /datum/map_preloader, new) parent_type = /datum var/list/attributes var/target_path + var/turn_angle + var/swap_x + var/swap_y + var/swap_xy -/world/proc/preloader_setup(list/the_attributes, path) - if(the_attributes.len) +/world/proc/preloader_setup(list/the_attributes, path, turn_angle, swap_x, swap_y, swap_xy) + if(length(the_attributes) || turn_angle) GLOB.use_preloader = TRUE var/datum/map_preloader/preloader_local = GLOB._preloader preloader_local.attributes = the_attributes preloader_local.target_path = path + preloader_local.turn_angle = turn_angle + preloader_local.swap_x = swap_x + preloader_local.swap_y = swap_y + preloader_local.swap_xy = swap_xy /world/proc/preloader_load(atom/what) GLOB.use_preloader = FALSE @@ -29,6 +37,21 @@ GLOBAL_DATUM_INIT(_preloader, /datum/map_preloader, new) GLOB.dirty_vars += message #endif what.vars[attribute] = value + // handle post processing, so things like directions on subtypes don't break. + if(preloader_local.turn_angle) //safe way to check for if this is necessary + what.dir = turn(what.dir, preloader_local.turn_angle) + var/px = what.pixel_x + var/py = what.pixel_y + if(preloader_local.swap_y) //same order of operations as the load rotation, mirror and then x/y swapping. + py = -py + if(preloader_local.swap_x) + px = -px + if(preloader_local.swap_xy) + var/opx = px + px = py + py = opx + what.pixel_x = px + what.pixel_y = py /area/template_noop name = "Area Passthrough" diff --git a/code/modules/mapping/reader.dm b/code/modules/mapping/reader.dm index e7d7fd4898..2f9ab4112e 100644 --- a/code/modules/mapping/reader.dm +++ b/code/modules/mapping/reader.dm @@ -19,9 +19,13 @@ /// Unoffset bounds. Null on parse failure. var/list/parsed_bounds + var/width + var/height /// Offset bounds. Same as parsed_bounds until load(). var/list/bounds + var/datum/map_template/template_host + // raw strings used to represent regexes more accurately // '' used to avoid confusing syntax highlighting var/static/regex/dmmRegex = new(@'"([a-zA-Z]+)" = \(((?:.|\n)*?)\)\n(?!\t)|\((\d+),(\d+),(\d+)\) = \{"([a-zA-Z\n]*)"\}', "g") @@ -41,14 +45,33 @@ /// - `no_changeturf`: When true, [turf/AfterChange] won't be called on loaded turfs /// - `x_lower`, `x_upper`, `y_lower`, `y_upper`: Coordinates (relative to the map) to crop to (Optional). /// - `placeOnTop`: Whether to use [turf/PlaceOnTop] rather than [turf/ChangeTurf] (Optional). -/proc/load_map(dmm_file as file, x_offset as num, y_offset as num, z_offset as num, cropMap as num, measureOnly as num, no_changeturf as num, x_lower = -INFINITY as num, x_upper = INFINITY as num, y_lower = -INFINITY as num, y_upper = INFINITY as num, placeOnTop = FALSE as num) - var/datum/parsed_map/parsed = new(dmm_file, x_lower, x_upper, y_lower, y_upper, measureOnly) +/proc/load_map( + dmm_file as file, + x_offset as num, + y_offset as num, + z_offset as num, + cropMap as num, + measureOnly as num, + no_changeturf as num, + x_lower = -INFINITY as num, + x_upper = INFINITY as num, + y_lower = -INFINITY as num, + y_upper = INFINITY as num, + placeOnTop = FALSE as num, + orientation = SOUTH as num, + annihilate_tiles = FALSE, + z_lower = -INFINITY as num, + z_upper = INFINITY as num + ) + var/datum/parsed_map/parsed = new(dmm_file, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, measureOnly) if(parsed.bounds && !measureOnly) - parsed.load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop) + parsed.load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, orientation, annihilate_tiles) return parsed /// Parse a map, possibly cropping it. -/datum/parsed_map/New(tfile, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper=INFINITY, measureOnly=FALSE) +//WHY THE HECK DO WE EVEN SUPPORT NEGATIVE COORDINATES, ALL IT IS IS A WASTE OF TIME AND CPU!!!??? +//DO NOT USE THIS TO TRIM MAPS UNLESS STRICTLY NEEDED! IT IS EXTREMELY EXPENSIVE TO DO SO! +/datum/parsed_map/New(tfile, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper = INFINITY, z_lower = -INFINITY, z_upper = INFINITY, measureOnly = FALSE) if(isfile(tfile)) original_path = "[tfile]" tfile = file2text(tfile) @@ -57,6 +80,9 @@ return bounds = parsed_bounds = list(1.#INF, 1.#INF, 1.#INF, -1.#INF, -1.#INF, -1.#INF) + ASSERT(x_upper >= x_lower) + ASSERT(y_upper >= y_lower) + ASSERT(z_upper >= z_lower) var/stored_index = 1 //multiz lool @@ -82,20 +108,23 @@ CRASH("Coords before model definition in DMM") var/curr_x = text2num(dmmRegex.group[3]) + var/curr_y = text2num(dmmRegex.group[4]) + var/curr_z = text2num(dmmRegex.group[5]) - if(curr_x < x_lower || curr_x > x_upper) + if(curr_x < x_lower || curr_y < y_lower || curr_z < z_lower || curr_z > z_upper) continue var/datum/grid_set/gridSet = new gridSet.xcrd = curr_x - //position of the currently processed square - gridSet.ycrd = text2num(dmmRegex.group[4]) - gridSet.zcrd = text2num(dmmRegex.group[5]) + gridSet.ycrd = curr_y + gridSet.zcrd = curr_z - bounds[MAP_MINX] = min(bounds[MAP_MINX], clamp(gridSet.xcrd, x_lower, x_upper)) - bounds[MAP_MINZ] = min(bounds[MAP_MINZ], gridSet.zcrd) - bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], gridSet.zcrd) + bounds[MAP_MINX] = min(bounds[MAP_MINX], curr_x) //since down is up for y/gridlines, we now know the lower left corner. + bounds[MAP_MINY] = min(bounds[MAP_MINY], curr_y) + bounds[MAP_MINZ] = min(bounds[MAP_MINZ], curr_z) + + bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], curr_z) //we know max z now var/list/gridLines = splittext(dmmRegex.group[6], "\n") gridSet.gridLines = gridLines @@ -105,102 +134,132 @@ if(leadingBlanks > 1) gridLines.Cut(1, leadingBlanks) // Remove all leading blank lines. - if(!gridLines.len) // Skip it if only blank lines exist. - continue - gridSets += gridSet - if(gridLines.len && gridLines[gridLines.len] == "") - gridLines.Cut(gridLines.len) // Remove only one blank line at the end. + var/lines = length(gridLines) + if(lines) + if(gridLines[gridLines.len] == "") + gridLines.Cut(gridLines.len) // Remove only one blank line at the end. + var/right_length = y_upper - curr_y + 1 + if(lines > right_length) + gridLines.len = right_length //this can't be negative due to our ASSERTions above, hopefully. - bounds[MAP_MINY] = min(bounds[MAP_MINY], clamp(gridSet.ycrd, y_lower, y_upper)) + if(!gridLines.len) // Skip it if there's no content. + continue + + //do not use curr_y after this point, ycrd has changed. use it before because local var. gridSet.ycrd += gridLines.len - 1 // Start at the top and work down - bounds[MAP_MAXY] = max(bounds[MAP_MAXY], clamp(gridSet.ycrd, y_lower, y_upper)) + bounds[MAP_MAXY] = max(bounds[MAP_MAXY], gridSet.ycrd) //we know max y now - var/maxx = gridSet.xcrd - if(gridLines.len) //Not an empty map - maxx = max(maxx, gridSet.xcrd + length(gridLines[1]) / key_len - 1) + var/linelength = length(gridLines[1]) //yes it only samples the first line, this is why you use TGM instead of DMM! + var/xlength = linelength / key_len - bounds[MAP_MAXX] = clamp(max(bounds[MAP_MAXX], maxx), x_lower, x_upper) + var/maxx = gridSet.xcrd + xlength - 1 + if(maxx > x_upper) + for(var/i in 1 to length(gridLines)) + gridLines[i] = copytext(gridLines[i], 1, key_len * (x_upper - curr_x + 1)) + bounds[MAP_MAXX] = max(bounds[MAP_MAXX], maxx) CHECK_TICK // Indicate failure to parse any coordinates by nulling bounds if(bounds[1] == 1.#INF) bounds = null + else + width = bounds[MAP_MAXX] - bounds[MAP_MINX] + 1 + height = bounds[MAP_MAXY] - bounds[MAP_MINY] + 1 parsed_bounds = bounds +/datum/parsed_map/Destroy() + if(template_host && template_host.cached_map == src) + template_host.cached_map = null + return ..() + /// Load the parsed map into the world. See [/proc/load_map] for arguments. -/datum/parsed_map/proc/load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop) +/datum/parsed_map/proc/load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, orientation, annihilate_tiles, datum/map_orientation_pattern/forced_pattern) //How I wish for RAII Master.StartLoadingMap() - . = _load_impl(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop) + . = _load_impl(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, orientation, annihilate_tiles, forced_pattern) Master.StopLoadingMap() // Do not call except via load() above. -/datum/parsed_map/proc/_load_impl(x_offset = 1, y_offset = 1, z_offset = world.maxz + 1, cropMap = FALSE, no_changeturf = FALSE, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper = INFINITY, placeOnTop = FALSE) +// Lower/upper here refers to the actual map template's parsed coordinates, NOT ACTUAL COORDINATES! Figure it out yourself my head hurts too much to implement that too. +/datum/parsed_map/proc/_load_impl(x_offset = 1, y_offset = 1, z_offset = world.maxz + 1, cropMap = FALSE, no_changeturf = FALSE, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper = INFINITY, placeOnTop = FALSE, orientation = SOUTH, annihilate_tiles = FALSE, datum/map_orientation_pattern/forced_pattern) var/list/areaCache = list() var/list/modelCache = build_cache(no_changeturf) var/space_key = modelCache[SPACE_KEY] var/list/bounds src.bounds = bounds = list(1.#INF, 1.#INF, 1.#INF, -1.#INF, -1.#INF, -1.#INF) + var/datum/map_orientation_pattern/mode = forced_pattern || GLOB.map_orientation_patterns["[orientation]"] || GLOB.map_orientation_patterns["[SOUTH]"] + var/invert_y = mode.invert_y + var/invert_x = mode.invert_x + var/swap_xy = mode.swap_xy + var/xi = mode.xi + var/yi = mode.yi + var/turn_angle = round(SIMPLIFY_DEGREES(mode.turn_angle), 90) + var/delta_swap = x_offset - y_offset - for(var/I in gridSets) - var/datum/grid_set/gset = I - var/ycrd = gset.ycrd + y_offset - 1 - var/zcrd = gset.zcrd + z_offset - 1 - if(!cropMap && ycrd > world.maxy) - world.maxy = ycrd // Expand Y here. X is expanded in the loop below - var/zexpansion = zcrd > world.maxz + for(var/__I in gridSets) + var/datum/grid_set/gridset = __I + var/parsed_z = gridset.zcrd + z_offset - 1 + var/zexpansion = parsed_z > world.maxz if(zexpansion) if(cropMap) continue else - while (zcrd > world.maxz) //create a new z_level if needed + while(parsed_z > world.maxz) world.incrementMaxZ() if(!no_changeturf) WARNING("Z-level expansion occurred without no_changeturf set, this may cause problems when /turf/AfterChange is called") + //these values are the same until a new gridset is reached. + var/edge_dist_x = gridset.xcrd - 1 //from left side, 0 is right on the x_offset + var/edge_dist_y = gridset.ycrd - length(gridset.gridLines) //from bottom, 0 is right on the y_offset + var/actual_x_starting = invert_x? (x_offset + width - edge_dist_x - 1) : (x_offset + edge_dist_x) //this value is not changed, cache. + //this value is changed + var/actual_y = invert_y? (y_offset + edge_dist_y) : (y_offset + gridset.ycrd - 1) + for(var/line in gridset.gridLines) + var/actual_x = actual_x_starting + for(var/pos = 1 to (length(line) - key_len + 1) step key_len) + var/placement_x = swap_xy? (actual_y + delta_swap) : actual_x + var/placement_y = swap_xy? (actual_x - delta_swap) : actual_y + if(placement_x > world.maxx) + if(cropMap) + actual_x += xi + continue + else + world.maxx = placement_x + if(placement_y > world.maxy) + if(cropMap) + break + else + world.maxy = placement_y + if(placement_x < 1) + actual_x += xi + continue + if(placement_y < 1) + break + var/model_key = copytext(line, pos, pos + key_len) + var/no_afterchange = no_changeturf || zexpansion + if(!no_afterchange || (model_key != space_key)) + var/list/cache = modelCache[model_key] + if(!cache) + CRASH("Undefined model key in DMM: [model_key]") + build_coordinate(areaCache, cache, locate(placement_x, placement_y, parsed_z), no_afterchange, placeOnTop, turn_angle, annihilate_tiles, swap_xy, invert_y, invert_x) - for(var/line in gset.gridLines) - if((ycrd - y_offset + 1) < y_lower || (ycrd - y_offset + 1) > y_upper) //Reverse operation and check if it is out of bounds of cropping. - --ycrd - continue - if(ycrd <= world.maxy && ycrd >= 1) - var/xcrd = gset.xcrd + x_offset - 1 - for(var/tpos = 1 to length(line) - key_len + 1 step key_len) - if((xcrd - x_offset + 1) < x_lower || (xcrd - x_offset + 1) > x_upper) //Same as above. - ++xcrd - continue //X cropping. - if(xcrd > world.maxx) - if(cropMap) - break - else - world.maxx = xcrd - - if(xcrd >= 1) - var/model_key = copytext(line, tpos, tpos + key_len) - var/no_afterchange = no_changeturf || zexpansion - if(!no_afterchange || (model_key != space_key)) - var/list/cache = modelCache[model_key] - if(!cache) - CRASH("Undefined model key in DMM: [model_key]") - build_coordinate(areaCache, cache, locate(xcrd, ycrd, zcrd), no_afterchange, placeOnTop) - - // only bother with bounds that actually exist - bounds[MAP_MINX] = min(bounds[MAP_MINX], xcrd) - bounds[MAP_MINY] = min(bounds[MAP_MINY], ycrd) - bounds[MAP_MINZ] = min(bounds[MAP_MINZ], zcrd) - bounds[MAP_MAXX] = max(bounds[MAP_MAXX], xcrd) - bounds[MAP_MAXY] = max(bounds[MAP_MAXY], ycrd) - bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], zcrd) - #ifdef TESTING - else - ++turfsSkipped - #endif - CHECK_TICK - ++xcrd - --ycrd - - CHECK_TICK + // only bother with bounds that actually exist + bounds[MAP_MINX] = min(bounds[MAP_MINX], placement_x) + bounds[MAP_MINY] = min(bounds[MAP_MINY], placement_y) + bounds[MAP_MINZ] = min(bounds[MAP_MINZ], parsed_z) + bounds[MAP_MAXX] = max(bounds[MAP_MAXX], placement_x) + bounds[MAP_MAXY] = max(bounds[MAP_MAXY], placement_y) + bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], parsed_z) + #ifdef TESTING + else + ++turfsSkipped + #endif + actual_x += xi + CHECK_TICK + actual_y += yi + CHECK_TICK if(!no_changeturf) for(var/t in block(locate(bounds[MAP_MINX], bounds[MAP_MINY], bounds[MAP_MINZ]), locate(bounds[MAP_MAXX], bounds[MAP_MAXY], bounds[MAP_MAXZ]))) @@ -294,7 +353,7 @@ .[model_key] = list(members, members_attributes) -/datum/parsed_map/proc/build_coordinate(list/areaCache, list/model, turf/crds, no_changeturf as num, placeOnTop as num) +/datum/parsed_map/proc/build_coordinate(list/areaCache, list/model, turf/crds, no_changeturf as num, placeOnTop as num, turn_angle as num, annihilate_tiles = FALSE, swap_xy, invert_y, invert_x) var/index var/list/members = model[1] var/list/members_attributes = model[2] @@ -306,6 +365,8 @@ //The next part of the code assumes there's ALWAYS an /area AND a /turf on a given tile //first instance the /area and remove it from the members list index = members.len + if(annihilate_tiles && crds) + crds.empty(null) if(members[index] != /area/template_noop) var/atype = members[index] world.preloader_setup(members_attributes[index], atype)//preloader for assigning set variables on atom creation @@ -332,20 +393,20 @@ //instanciate the first /turf var/turf/T if(members[first_turf_index] != /turf/template_noop) - T = instance_atom(members[first_turf_index],members_attributes[first_turf_index],crds,no_changeturf,placeOnTop) + T = instance_atom(members[first_turf_index],members_attributes[first_turf_index],crds,no_changeturf,placeOnTop,turn_angle, swap_xy, invert_y, invert_x) if(T) //if others /turf are presents, simulates the underlays piling effect index = first_turf_index + 1 while(index <= members.len - 1) // Last item is an /area var/underlay = T.appearance - T = instance_atom(members[index],members_attributes[index],crds,no_changeturf,placeOnTop)//instance new turf + T = instance_atom(members[index],members_attributes[index],crds,no_changeturf,placeOnTop,turn_angle, swap_xy, invert_y, invert_x)//instance new turf T.underlays += underlay index++ //finally instance all remainings objects/mobs for(index in 1 to first_turf_index-1) - instance_atom(members[index],members_attributes[index],crds,no_changeturf,placeOnTop) + instance_atom(members[index],members_attributes[index],crds,no_changeturf,placeOnTop,turn_angle, swap_xy, invert_y, invert_x) //Restore initialization to the previous value SSatoms.map_loader_stop() @@ -354,8 +415,8 @@ //////////////// //Instance an atom at (x,y,z) and gives it the variables in attributes -/datum/parsed_map/proc/instance_atom(path,list/attributes, turf/crds, no_changeturf, placeOnTop) - world.preloader_setup(attributes, path) +/datum/parsed_map/proc/instance_atom(path,list/attributes, turf/crds, no_changeturf, placeOnTop, turn_angle = 0, swap_xy, invert_y, invert_x) + world.preloader_setup(attributes, path, turn_angle, invert_x, invert_y, swap_xy) if(crds) if(ispath(path, /turf)) diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm index 7eabeafcb1..c2cdf1da8b 100644 --- a/code/modules/power/apc.dm +++ b/code/modules/power/apc.dm @@ -196,12 +196,16 @@ switch(tdir) if(NORTH) + pixel_x = 0 pixel_y = 23 if(SOUTH) + pixel_x = 0 pixel_y = -23 if(EAST) + pixel_y = 0 pixel_x = 24 if(WEST) + pixel_y = 0 pixel_x = -25 /obj/machinery/power/apc/Destroy() diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm index eecc1394de..239b38460c 100644 --- a/code/modules/power/cable.dm +++ b/code/modules/power/cable.dm @@ -91,6 +91,21 @@ By design, d1 is the smallest direction and d2 is the highest d1 = _d1 d2 = _d2 + if(dir != SOUTH) + var/angle_to_turn = dir2angle(dir) + if(angle_to_turn == 0 || angle_to_turn == 180) + angle_to_turn += 180 + // direct dir set instead of setDir intentional + dir = SOUTH + if(d1) + d1 = turn(d1, angle_to_turn) + if(d2) + d2 = turn(d2, angle_to_turn) + if(d1 > d2) + var/temp = d2 + d2 = d1 + d1 = temp + var/turf/T = get_turf(src) // hide if turf is not intact if(level==1) hide(T.intact) diff --git a/code/modules/shuttle/manipulator.dm b/code/modules/shuttle/manipulator.dm index 3ba2198b35..8f98a89c36 100644 --- a/code/modules/shuttle/manipulator.dm +++ b/code/modules/shuttle/manipulator.dm @@ -270,7 +270,7 @@ var/turf/landmark_turf = get_turf(locate(/obj/effect/landmark/shuttle_import) in GLOB.landmarks_list) S.load(landmark_turf, centered = TRUE, register = FALSE) - var/affected = S.get_affected_turfs(landmark_turf, centered=TRUE) + var/affected = S.get_affected_turfs(landmark_turf, centered = TRUE) var/found = 0 // Search the turfs for docking ports diff --git a/tgstation.dme b/tgstation.dme index 58840f56e2..e6f95c086b 100755 --- a/tgstation.dme +++ b/tgstation.dme @@ -124,6 +124,7 @@ #include "code\__DEFINES\dcs\helpers.dm" #include "code\__DEFINES\dcs\signals.dm" #include "code\__DEFINES\flags\shields.dm" +#include "code\__DEFINES\mapping\maploader.dm" #include "code\__DEFINES\misc\return_values.dm" #include "code\__DEFINES\skills\skills.dm" #include "code\__HELPERS\_cit_helpers.dm" @@ -348,7 +349,6 @@ #include "code\datums\forced_movement.dm" #include "code\datums\holocall.dm" #include "code\datums\hud.dm" -#include "code\datums\map_config.dm" #include "code\datums\mind.dm" #include "code\datums\mutable_appearance.dm" #include "code\datums\numbered_display.dm" @@ -2148,7 +2148,8 @@ #include "code\modules\lighting\lighting_setup.dm" #include "code\modules\lighting\lighting_source.dm" #include "code\modules\lighting\lighting_turf.dm" -#include "code\modules\mapping\dmm_suite.dm" +#include "code\modules\mapping\map_config.dm" +#include "code\modules\mapping\map_orientation_pattern.dm" #include "code\modules\mapping\map_template.dm" #include "code\modules\mapping\minimaps.dm" #include "code\modules\mapping\preloader.dm"