diff --git a/code/__DEFINES/__game.dm b/code/__DEFINES/__game.dm index dfa335375b67..87c5e2255b52 100644 --- a/code/__DEFINES/__game.dm +++ b/code/__DEFINES/__game.dm @@ -1,9 +1,3 @@ -#define RANGE_TURFS(RADIUS, CENTER) \ -block( \ - locate(max(CENTER.x-(RADIUS),1), max(CENTER.y-(RADIUS),1), CENTER.z), \ - locate(min(CENTER.x+(RADIUS),world.maxx), min(CENTER.y+(RADIUS),world.maxy), CENTER.z) \ -) - //Admin perms are in global.dm. /// To make it even more clear that something is a bitfield. diff --git a/code/__DEFINES/_math.dm b/code/__DEFINES/_math.dm index f0281f51cedb..d7c068237987 100644 --- a/code/__DEFINES/_math.dm +++ b/code/__DEFINES/_math.dm @@ -14,6 +14,8 @@ #define CEILING(x, y) ( -round(-(x) / (y)) * (y) ) +#define ROUND_UP(x) ( -round(-(x))) + // round() acts like floor(x, 1) by default but can't handle other values #define FLOOR(x, y) ( round((x) / (y)) * (y) ) diff --git a/code/__DEFINES/dcs/signals/atom/signals_atom.dm b/code/__DEFINES/dcs/signals/atom/signals_atom.dm index d9bd1202c159..4e4458232669 100644 --- a/code/__DEFINES/dcs/signals/atom/signals_atom.dm +++ b/code/__DEFINES/dcs/signals/atom/signals_atom.dm @@ -1,6 +1,7 @@ /// From /atom/proc/Decorate #define COMSIG_ATOM_DECORATED "atom_decorated" - +//from SSatoms InitAtom - Only if the atom was not deleted or failed initialization and has a loc +#define COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON "atom_init_success_on" ///from base of atom/setDir(): (old_dir, new_dir). Called before the direction changes. #define COMSIG_ATOM_DIR_CHANGE "atom_dir_change" diff --git a/code/__DEFINES/dcs/signals/atom/signals_turf.dm b/code/__DEFINES/dcs/signals/atom/signals_turf.dm index 6a0788bcf871..881ffc3ee139 100644 --- a/code/__DEFINES/dcs/signals/atom/signals_turf.dm +++ b/code/__DEFINES/dcs/signals/atom/signals_turf.dm @@ -19,3 +19,6 @@ ///from /turf/closed/wall/proc/place_poster #define COMSIG_POSTER_PLACED "poster_placed" + +///from base of /datum/turf_reservation/proc/Release: (datum/turf_reservation/reservation) +#define COMSIG_TURF_RESERVATION_RELEASED "turf_reservation_released" diff --git a/code/__DEFINES/dcs/signals/signals_global.dm b/code/__DEFINES/dcs/signals/signals_global.dm index 905ff1246182..8aa700f4fb3a 100644 --- a/code/__DEFINES/dcs/signals/signals_global.dm +++ b/code/__DEFINES/dcs/signals/signals_global.dm @@ -8,6 +8,8 @@ ///from base of datum/controller/subsystem/mapping/proc/add_new_zlevel(): (list/args) #define COMSIG_GLOB_NEW_Z "!new_z" +/// sent after world.maxx and/or world.maxy are expanded: (has_exapnded_world_maxx, has_expanded_world_maxy) +#define COMSIG_GLOB_EXPANDED_WORLD_BOUNDS "!expanded_world_bounds" ///from base of datum/controller/subsystem/mapping/proc/add_new_zlevel(): (list/args) #define COMSIG_GLOB_VEHICLE_ORDERED "!vehicle_ordered" /// from /datum/controller/subsystem/ticker/fire diff --git a/code/__DEFINES/maps.dm b/code/__DEFINES/maps.dm index 155a91fa62ed..ef3d17572f0d 100644 --- a/code/__DEFINES/maps.dm +++ b/code/__DEFINES/maps.dm @@ -118,8 +118,5 @@ require only minor tweaks. #define MAP_ARMOR_STYLE_JUNGLE "jungle" #define MAP_ARMOR_STYLE_PRISON "prison" -//turf-only flags -#define NOJAUNT_1 (1<<0) -#define UNUSED_RESERVATION_TURF (1<<1) -/// If a turf can be made dirty at roundstart. This is also used in areas. -#define CAN_BE_DIRTY_1 (1<<2) +/// A map key that corresponds to being one exclusively for Space. +#define SPACE_KEY "space" diff --git a/code/__DEFINES/objects.dm b/code/__DEFINES/objects.dm index 292b315360c5..d495c8e8c012 100644 --- a/code/__DEFINES/objects.dm +++ b/code/__DEFINES/objects.dm @@ -175,3 +175,8 @@ GLOBAL_LIST_INIT(RESTRICTED_CAMERA_NETWORKS, list( //Those networks can only be #define CHECKS_PASSED 1 #define STILL_ON_COOLDOWN 2 #define NO_LIGHT_STATE_CHANGE 3 + +//tool capabilities or something i don't know +#define REMOVE_CROWBAR (1<<0) +#define BREAK_CROWBAR (1<<1) +#define REMOVE_SCREWDRIVER (1<<2) diff --git a/code/__DEFINES/shuttles.dm b/code/__DEFINES/shuttles.dm index a3299184e4ef..dfd470a5dba3 100644 --- a/code/__DEFINES/shuttles.dm +++ b/code/__DEFINES/shuttles.dm @@ -41,7 +41,7 @@ #define TRANSIT_REQUEST 1 #define TRANSIT_READY 2 -#define SHUTTLE_TRANSIT_BORDER 8 +#define SHUTTLE_TRANSIT_BORDER 16 #define PARALLAX_LOOP_TIME 25 #define HYPERSPACE_END_TIME 5 diff --git a/code/__DEFINES/turf_flags.dm b/code/__DEFINES/turf_flags.dm index d7b3e90811d8..19dc17191d7c 100644 --- a/code/__DEFINES/turf_flags.dm +++ b/code/__DEFINES/turf_flags.dm @@ -1,3 +1,12 @@ +//turf_flags values +/// Marks a turf as organic. Used for alien wall and membranes. +#define TURF_ORGANIC (1<<0) +/// If a turf is an usused reservation turf awaiting assignment +#define UNUSED_RESERVATION_TURF (1<<1) +/// If a turf is a reserved turf +#define RESERVATION_TURF (1<<2) + +//ChangeTurf options to change its behavior #define CHANGETURF_DEFER_CHANGE (1<<0) /// This flag prevents changeturf from gathering air from nearby turfs to fill the new turf with an approximation of local air #define CHANGETURF_IGNORE_AIR (1<<1) @@ -5,12 +14,3 @@ /// A flag for PlaceOnTop to just instance the new turf instead of calling ChangeTurf. Used for uninitialized turfs NOTHING ELSE #define CHANGETURF_SKIP (1<<3) -#define IS_OPAQUE_TURF(turf) (turf.directional_opacity == ALL_CARDINALS) - -/// Marks a turf as organic. Used for alien wall and membranes. -#define TURF_ORGANIC (1<<0) - - -#define REMOVE_CROWBAR (1<<0) -#define BREAK_CROWBAR (1<<1) -#define REMOVE_SCREWDRIVER (1<<2) diff --git a/code/__DEFINES/turfs.dm b/code/__DEFINES/turfs.dm new file mode 100644 index 000000000000..b9a80d4ab257 --- /dev/null +++ b/code/__DEFINES/turfs.dm @@ -0,0 +1,29 @@ +#define RANGE_TURFS(RADIUS, CENTER) \ +block( \ + locate(max(CENTER.x-(RADIUS),1), max(CENTER.y-(RADIUS),1), CENTER.z), \ + locate(min(CENTER.x+(RADIUS),world.maxx), min(CENTER.y+(RADIUS),world.maxy), CENTER.z) \ +) + +#define RECT_TURFS(H_RADIUS, V_RADIUS, CENTER) \ + block( \ + locate(max((CENTER).x-(H_RADIUS),1), max((CENTER).y-(V_RADIUS),1), (CENTER).z), \ + locate(min((CENTER).x+(H_RADIUS),world.maxx), min((CENTER).y+(V_RADIUS),world.maxy), (CENTER).z) \ + ) + +///Returns all turfs in a zlevel +#define Z_TURFS(ZLEVEL) block(locate(1,1,ZLEVEL), locate(world.maxx, world.maxy, ZLEVEL)) + +/// Returns a list of turfs in the rectangle specified by BOTTOM LEFT corner and height/width, checks for being outside the world border for you +#define CORNER_BLOCK(corner, width, height) CORNER_BLOCK_OFFSET(corner, width, height, 0, 0) + +/// Returns a list of turfs similar to CORNER_BLOCK but with offsets +#define CORNER_BLOCK_OFFSET(corner, width, height, offset_x, offset_y) ((block(locate(corner.x + offset_x, corner.y + offset_y, corner.z), locate(min(corner.x + (width - 1) + offset_x, world.maxx), min(corner.y + (height - 1) + offset_y, world.maxy), corner.z)))) + +/// Returns an outline (neighboring turfs) of the given block +#define CORNER_OUTLINE(corner, width, height) ( \ + CORNER_BLOCK_OFFSET(corner, width + 2, 1, -1, -1) + \ + CORNER_BLOCK_OFFSET(corner, width + 2, 1, -1, height) + \ + CORNER_BLOCK_OFFSET(corner, 1, height, -1, 0) + \ + CORNER_BLOCK_OFFSET(corner, 1, height, width, 0)) + +#define TURF_FROM_COORDS_LIST(List) (locate(List[1], List[2], List[3])) diff --git a/code/__DEFINES/typecheck/generic_types.dm b/code/__DEFINES/typecheck/generic_types.dm index d9fa3df55430..587108d5b5e6 100644 --- a/code/__DEFINES/typecheck/generic_types.dm +++ b/code/__DEFINES/typecheck/generic_types.dm @@ -11,6 +11,7 @@ #define ismovableatom(A) (ismovable(A)) #define isatom(A) (isloc(A)) #define isfloorturf(A) (istype(A, /turf/open/floor)) +#define isclosedturf(A) (istype(A, /turf/closed)) #define isweakref(D) (istype(D, /datum/weakref)) #define isgenerator(A) (istype(A, /generator)) diff --git a/code/__HELPERS/lighting.dm b/code/__HELPERS/lighting.dm index 08c360849b58..e768d9d1255c 100644 --- a/code/__HELPERS/lighting.dm +++ b/code/__HELPERS/lighting.dm @@ -1,3 +1,5 @@ +#define IS_OPAQUE_TURF(turf) (turf.directional_opacity == ALL_CARDINALS) + /// Produces a mutable appearance glued to the [EMISSIVE_PLANE] dyed to be the [EMISSIVE_COLOR]. /proc/emissive_appearance(icon, icon_state = "", layer = FLOAT_LAYER, alpha = 255, appearance_flags = NONE) var/mutable_appearance/appearance = mutable_appearance(icon, icon_state, layer, EMISSIVE_PLANE, alpha, appearance_flags | EMISSIVE_APPEARANCE_FLAGS) diff --git a/code/__HELPERS/lists.dm b/code/__HELPERS/lists.dm index 30ef9428586d..9a8528aabcc3 100644 --- a/code/__HELPERS/lists.dm +++ b/code/__HELPERS/lists.dm @@ -534,7 +534,7 @@ //Copies a list, and all lists inside it recusively //Does not copy any other reference type -/proc/deepCopyList(list/L) +/proc/deep_copy_list(list/L) if(!islist(L)) return L . = L.Copy() @@ -545,10 +545,10 @@ continue var/value = .[key] if(islist(value)) - value = deepCopyList(value) + value = deep_copy_list(value) .[key] = value if(islist(key)) - key = deepCopyList(key) + key = deep_copy_list(key) .[i] = key .[key] = value diff --git a/code/__HELPERS/logging.dm b/code/__HELPERS/logging.dm index d6c18c8a93be..59e4c7710992 100644 --- a/code/__HELPERS/logging.dm +++ b/code/__HELPERS/logging.dm @@ -286,6 +286,16 @@ GLOBAL_PROTECT(config_error_log) WRITE_LOG(GLOB.config_error_log, text) SEND_TEXT(world.log, text) +/// Logging for mapping errors +/proc/log_mapping(text, skip_world_log) +#ifdef UNIT_TESTS + GLOB.unit_test_mapping_logs += text +#endif + if(skip_world_log) + return + WRITE_LOG(GLOB.mapping_log, text) + SEND_TEXT(world.log, text) + /proc/log_admin_private(text) log_admin(text) diff --git a/code/__HELPERS/string_lists.dm b/code/__HELPERS/string_lists.dm new file mode 100644 index 000000000000..076bbf642756 --- /dev/null +++ b/code/__HELPERS/string_lists.dm @@ -0,0 +1,23 @@ +GLOBAL_LIST_EMPTY(string_lists) + +/** + * Caches lists with non-numeric stringify-able values (text or typepath). + */ +/proc/string_list(list/values) + var/string_id = values.Join("-") + + . = GLOB.string_lists[string_id] + + if(.) + return . + + return GLOB.string_lists[string_id] = values + +///A wrapper for baseturf string lists, to offer support of non list values, and a stack_trace if we have major issues +/proc/baseturfs_string_list(list/values, turf/baseturf_holder) + if(!islist(values)) + return values //baseturf things + // return values + if(length(values) > 10) + return string_list(list(/turf/closed/cordon/debug)) + return string_list(values) diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm index 9287197bbcc2..7396e8624ba9 100644 --- a/code/__HELPERS/text.dm +++ b/code/__HELPERS/text.dm @@ -201,6 +201,24 @@ return "" +//Returns a string with reserved characters and spaces after the first and last letters removed +//Like trim(), but very slightly faster. worth it for niche usecases +/proc/trim_reduced(text) + var/starting_coord = 1 + var/text_len = length(text) + for (var/i in 1 to text_len) + if (text2ascii(text, i) > 32) + starting_coord = i + break + + for (var/i = text_len, i >= starting_coord, i--) + if (text2ascii(text, i) > 32) + return copytext(text, starting_coord, i + 1) + + if(starting_coord > 1) + return copytext(text, starting_coord) + return "" + //Returns a string with reserved characters and spaces before the first word and after the last word removed. /proc/trim(text) return trim_left(trim_right(text)) diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 2fd7ea8919df..aa23131847d7 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -820,7 +820,7 @@ animation.master = target flick(flick_anim, animation) -//Will return the contents of an atom recursivly to a depth of 'searchDepth' +///Will return the contents of an atom recursivly to a depth of 'searchDepth', not including starting atom /atom/proc/GetAllContents(searchDepth = 5, list/toReturn = list()) for(var/atom/part as anything in contents) toReturn += part @@ -828,6 +828,16 @@ part.GetAllContents(searchDepth - 1, toReturn) return toReturn +///Returns the src and all recursive contents as a list. Includes the starting atom. +/atom/proc/get_all_contents(ignore_flag_1) + . = list(src) + var/i = 0 + while(i < length(.)) + var/atom/checked_atom = .[++i] + if(checked_atom.flags_atom & ignore_flag_1) + continue + . += checked_atom.contents + /// Returns list of contents of a turf recursively, much like GetAllContents /// We only get containing atoms in the turf, excluding multitiles bordering on it /turf/proc/GetAllTurfStrictContents(searchDepth = 5, list/toReturn = list()) @@ -1905,8 +1915,6 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new) return list(region_x1 & region_x2, region_y1 & region_y2) -#define TURF_FROM_COORDS_LIST(List) (locate(List[1], List[2], List[3])) - //Vars that will not be copied when using /DuplicateObject GLOBAL_LIST_INIT(duplicate_forbidden_vars,list( "tag", "datum_components", "area", "type", "loc", "locs", "vars", "parent", "parent_type", "verbs", "ckey", "key", diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index 59d14f2e0fed..bf9330c567cc 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -154,6 +154,12 @@ DEFINE_BITFIELD(flags_atom, list( "HTML_USE_INITAL_ICON" = HTML_USE_INITAL_ICON, )) +DEFINE_BITFIELD(turf_flags, list( + "TURF_ORGANIC" = TURF_ORGANIC, + "UNUSED_RESERVATION_TURF" = UNUSED_RESERVATION_TURF, + "RESERVATION_TURF" = RESERVATION_TURF, +)) + DEFINE_BITFIELD(flags_item, list( "NODROP" = NODROP, "NOBLUDGEON" = NOBLUDGEON, diff --git a/code/_globalvars/global_lists.dm b/code/_globalvars/global_lists.dm index 7d9cd3324067..4dbf46086f02 100644 --- a/code/_globalvars/global_lists.dm +++ b/code/_globalvars/global_lists.dm @@ -37,6 +37,9 @@ GLOBAL_LIST_EMPTY(minimap_icons) GLOBAL_LIST_EMPTY(mainship_pipes) +/// List of all the maps that have been cached for /proc/load_map +GLOBAL_LIST_EMPTY(cached_maps) + /proc/initiate_minimap_icons() var/list/icons = list() for(var/iconstate in icon_states('icons/UI_icons/map_blips.dmi')) diff --git a/code/controllers/subsystem/atoms.dm b/code/controllers/subsystem/atoms.dm index ae4783e4837a..f0d5ee14363e 100644 --- a/code/controllers/subsystem/atoms.dm +++ b/code/controllers/subsystem/atoms.dm @@ -19,6 +19,9 @@ SUBSYSTEM_DEF(atoms) var/list/BadInitializeCalls = list() + ///initAtom() adds the atom its creating to this list iff InitializeAtoms() has been given a list to populate as an argument + var/list/created_atoms + initialized = INITIALIZATION_INSSATOMS /datum/controller/subsystem/atoms/Initialize(timeofday) @@ -34,7 +37,7 @@ SUBSYSTEM_DEF(atoms) populate_seed_list() return SS_INIT_SUCCESS -/datum/controller/subsystem/atoms/proc/InitializeAtoms(list/atoms) +/datum/controller/subsystem/atoms/proc/InitializeAtoms(list/atoms, list/atoms_to_return) if(initialized == INITIALIZATION_INSSATOMS) return @@ -73,7 +76,10 @@ SUBSYSTEM_DEF(atoms) processing_late_loaders = FALSE /// Actually creates the list of atoms. Exists soley so a runtime in the creation logic doesn't cause initalized to totally break -/datum/controller/subsystem/atoms/proc/CreateAtoms(list/atoms) +/datum/controller/subsystem/atoms/proc/CreateAtoms(list/atoms, list/atoms_to_return = null) + if (atoms_to_return) + LAZYINITLIST(created_atoms) + #ifdef TESTING var/count #endif @@ -152,12 +158,10 @@ SUBSYSTEM_DEF(atoms) qdeleted = TRUE else if(!(A.flags_atom & INITIALIZED)) BadInitializeCalls[the_type] |= BAD_INIT_DIDNT_INIT - /* else - SEND_SIGNAL(A,COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE) + SEND_SIGNAL(A, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON) if(created_atoms && from_template && ispath(the_type, /atom/movable))//we only want to populate the list with movables created_atoms += A.get_all_contents() - */ return qdeleted || QDELING(A) diff --git a/code/controllers/subsystem/interior.dm b/code/controllers/subsystem/interior.dm index 8abc3179f191..e2b845f833d7 100644 --- a/code/controllers/subsystem/interior.dm +++ b/code/controllers/subsystem/interior.dm @@ -15,11 +15,11 @@ SUBSYSTEM_DEF(interior) var/height_to_request = template.height + INTERIOR_BORDER_SIZE var/width_to_request = template.width + INTERIOR_BORDER_SIZE - var/datum/turf_reservation/reserved_area = SSmapping.RequestBlockReservation(width_to_request, height_to_request, type = /datum/turf_reservation/interior) + var/datum/turf_reservation/reserved_area = SSmapping.request_turf_block_reservation(width_to_request, height_to_request, reservation_type = /datum/turf_reservation/interior) - var/list/bottom_left = reserved_area.bottom_left_coords + var/turf/bottom_left = reserved_area.bottom_left_turfs[1] - var/list/bounds = template.load(locate(bottom_left[1] + (INTERIOR_BORDER_SIZE / 2), bottom_left[2] + (INTERIOR_BORDER_SIZE / 2), bottom_left[3]), centered = FALSE) + var/list/bounds = template.load(locate(bottom_left.x + (INTERIOR_BORDER_SIZE / 2), bottom_left.y + (INTERIOR_BORDER_SIZE / 2), bottom_left.z), centered = FALSE) var/list/turfs = block( locate(bounds[MAP_MINX], bounds[MAP_MINY], bounds[MAP_MINZ]), locate(bounds[MAP_MAXX], bounds[MAP_MAXY], bounds[MAP_MAXZ])) @@ -51,12 +51,7 @@ SUBSYSTEM_DEF(interior) if(!isturf(loc)) loc = get_turf(loc) - var/datum/weakref/reservation_weakref = SSmapping.used_turfs[loc] - - if(!reservation_weakref) - return - - var/datum/turf_reservation/interior/reservation = reservation_weakref.resolve() + var/datum/turf_reservation/interior/reservation = SSmapping.used_turfs[loc] if(!istype(reservation)) return FALSE diff --git a/code/controllers/subsystem/mapping.dm b/code/controllers/subsystem/mapping.dm index 0f4a63ff65e8..f4e4b5979618 100644 --- a/code/controllers/subsystem/mapping.dm +++ b/code/controllers/subsystem/mapping.dm @@ -1,7 +1,7 @@ SUBSYSTEM_DEF(mapping) name = "Mapping" init_order = SS_INIT_MAPPING - flags = SS_NO_FIRE + runlevels = ALL var/list/datum/map_config/configs var/list/datum/map_config/next_map_configs @@ -19,16 +19,24 @@ SUBSYSTEM_DEF(mapping) var/list/turf/unused_turfs = list() //Not actually unused turfs they're unused but reserved for use for whatever requests them. "[zlevel_of_turf]" = list(turfs) var/list/datum/turf_reservations //list of turf reservations var/list/used_turfs = list() //list of turf = datum/turf_reservation + /// List of lists of turfs to reserve + var/list/lists_to_reserve = list() var/list/reservation_ready = list() var/clearing_reserved_turfs = FALSE // Z-manager stuff var/ground_start // should only be used for maploading-related tasks - var/list/z_list + ///list of all z level datums in the order of their z (z level 1 is at index 1, etc.) + var/list/datum/space_level/z_list var/datum/space_level/transit var/num_of_res_levels = 1 + /// True when in the process of adding a new Z-level, global locking + var/adding_new_zlevel = FALSE + /// list of traits and their associated z leves + var/list/z_trait_levels = list() + //dlete dis once #39770 is resolved /datum/controller/subsystem/mapping/proc/HACK_LoadMapConfig() if(!configs) @@ -52,14 +60,10 @@ SUBSYSTEM_DEF(mapping) loadWorld() repopulate_sorted_areas() preloadTemplates() - // Add the transit level - transit = add_new_zlevel("Transit/Reserved", list(ZTRAIT_RESERVED = TRUE)) - initialize_reserved_level(transit.z_value) + // Add the first transit level + var/datum/space_level/base_transit = add_reservation_zlevel() + initialize_reserved_level(base_transit.z_value) repopulate_sorted_areas() - for(var/maptype as anything in configs) - var/datum/map_config/MC = configs[maptype] - if(MC.perf_mode) - GLOB.perf_flags |= MC.perf_mode if(configs[GROUND_MAP]) send2chat(new /datum/tgs_message_content("<@&[CONFIG_GET(string/new_round_alert_role_id)]> Round restarted! Map is [configs[GROUND_MAP].map_name]"), CONFIG_GET(string/new_round_alert_channel)) @@ -68,21 +72,60 @@ SUBSYSTEM_DEF(mapping) return SS_INIT_SUCCESS +/datum/controller/subsystem/mapping/fire(resumed) + // Cache for sonic speed + var/list/unused_turfs = src.unused_turfs + // CM TODO: figure out if these 2 are needed. Might be required by updated versions of map reader + //var/list/world_contents = GLOB.areas_by_type[world.area].contents + //var/list/world_turf_contents = GLOB.areas_by_type[world.area].contained_turfs + var/list/lists_to_reserve = src.lists_to_reserve + var/index = 0 + while(index < length(lists_to_reserve)) + var/list/packet = lists_to_reserve[index + 1] + var/packetlen = length(packet) + while(packetlen) + if(MC_TICK_CHECK) + if(index) + lists_to_reserve.Cut(1, index) + return + var/turf/T = packet[packetlen] + T.empty(RESERVED_TURF_TYPE, RESERVED_TURF_TYPE, null, TRUE) + LAZYINITLIST(unused_turfs["[T.z]"]) + unused_turfs["[T.z]"] |= T + //var/area/old_area = T.loc + //old_area.turfs_to_uncontain += T + T.turf_flags = UNUSED_RESERVATION_TURF + //world_contents += T + //world_turf_contents += T + packet.len-- + packetlen = length(packet) + + index++ + lists_to_reserve.Cut(1, index) + /datum/controller/subsystem/mapping/proc/wipe_reservations(wipe_safety_delay = 100) if(clearing_reserved_turfs || !initialized) //in either case this is just not needed. return clearing_reserved_turfs = TRUE message_admins("Clearing dynamic reservation space.") + // /tg/ Shuttles have extra handling here to avoid them being desallocated do_wipe_turf_reservations() clearing_reserved_turfs = FALSE +/datum/controller/subsystem/mapping/proc/get_reservation_from_turf(turf/T) + RETURN_TYPE(/datum/turf_reservation) + return used_turfs[T] + /datum/controller/subsystem/mapping/Recover() flags |= SS_NO_INIT initialized = SSmapping.initialized map_templates = SSmapping.map_templates + + shuttle_templates = SSmapping.shuttle_templates unused_turfs = SSmapping.unused_turfs turf_reservations = SSmapping.turf_reservations used_turfs = SSmapping.used_turfs + areas_in_z = SSmapping.areas_in_z configs = SSmapping.configs next_map_configs = SSmapping.next_map_configs @@ -126,18 +169,33 @@ SUBSYSTEM_DEF(mapping) var/start_z = world.maxz + 1 var/i = 0 for (var/level in traits) - add_new_zlevel("[name][i ? " [i + 1]" : ""]", level) + add_new_zlevel("[name][i ? " [i + 1]" : ""]", level, contain_turfs = FALSE) ++i + // ================== CM Change ================== + // For some reason /tg/ SSmapping attempts to center the map in new Z-Level + // but because it's done before loading, it's calculated before performing + // X/Y world expansion. When loading a map bigger than world, this results + // in a negative offset and the start of the map to not be loaded. + // load the maps for (var/datum/parsed_map/pm as anything in parsed_maps) - var/cur_z = start_z + parsed_maps[pm] - if (!pm.load(1, 1, cur_z, no_changeturf = TRUE)) + var/bounds = pm.bounds + var/x_offset = 1 + var/y_offset = 1 + if(bounds && world.maxx > bounds[MAP_MAXX]) + x_offset = round(world.maxx / 2 - bounds[MAP_MAXX] / 2) + 1 + if(bounds && world.maxy > bounds[MAP_MAXY]) + y_offset = round(world.maxy / 2 - bounds[MAP_MAXY] / 2) + 1 + if (!pm.load(x_offset, y_offset, start_z + parsed_maps[pm], no_changeturf = TRUE, new_z = TRUE)) errorList |= pm.original_path - if(istype(z_list[cur_z], /datum/space_level)) - var/datum/space_level/cur_level = z_list[cur_z] - cur_level.x_bounds = pm.bounds[MAP_MAXX] - cur_level.y_bounds = pm.bounds[MAP_MAXY] + // CM Snowflake for Mass Screenshot dimensions auto detection + for(var/z in bounds[MAP_MINZ] to bounds[MAP_MAXZ]) + var/datum/space_level/zlevel = z_list[start_z + z - 1] + zlevel.bounds = list(bounds[MAP_MINX], bounds[MAP_MINY], z, bounds[MAP_MAXX], bounds[MAP_MAXY], z) + + // =============== END CM Change ================= + if(!silent) INIT_ANNOUNCE("Loaded [name] in [(REALTIMEOFDAY - start_time)/10]s!") return parsed_maps @@ -256,66 +314,78 @@ SUBSYSTEM_DEF(mapping) var/datum/map_template/tent/new_tent = new template() tent_type_templates[new_tent.map_id] = new_tent -/datum/controller/subsystem/mapping/proc/RequestBlockReservation(width, height, z, type = /datum/turf_reservation, turf_type_override) - UNTIL(initialized && !clearing_reserved_turfs) - var/datum/turf_reservation/reserve = new type - if(turf_type_override) +/// Adds a new reservation z level. A bit of space that can be handed out on request +/// Of note, reservations default to transit turfs, to make their most common use, shuttles, faster +/datum/controller/subsystem/mapping/proc/add_reservation_zlevel(for_shuttles) + num_of_res_levels++ + return add_new_zlevel("Transit/Reserved #[num_of_res_levels]", list(ZTRAIT_RESERVED = TRUE)) + +/// Requests a /datum/turf_reservation based on the given width, height, and z_size. You can specify a z_reservation to use a specific z level, or leave it null to use any z level. +/datum/controller/subsystem/mapping/proc/request_turf_block_reservation( + width, + height, + z_size = 1, + z_reservation = null, + reservation_type = /datum/turf_reservation, + turf_type_override = null, +) + UNTIL((!z_reservation || reservation_ready["[z_reservation]"]) && !clearing_reserved_turfs) + var/datum/turf_reservation/reserve = new reservation_type + if(!isnull(turf_type_override)) reserve.turf_type = turf_type_override - if(!z) + if(!z_reservation) for(var/i in levels_by_trait(ZTRAIT_RESERVED)) - if(reserve.Reserve(width, height, i)) + if(reserve.reserve(width, height, z_size, i)) return reserve //If we didn't return at this point, theres a good chance we ran out of room on the exisiting reserved z levels, so lets try a new one - log_debug("Ran out of space in existing transit levels, adding a new one") - num_of_res_levels++ - var/datum/space_level/newReserved = add_new_zlevel("Transit/Reserved [num_of_res_levels]", list(ZTRAIT_RESERVED = TRUE)) + var/datum/space_level/newReserved = add_reservation_zlevel() initialize_reserved_level(newReserved.z_value) - for(var/i in levels_by_trait(ZTRAIT_RESERVED)) - if(reserve.Reserve(width, height, i)) - return reserve - CRASH("Despite adding a fresh reserved zlevel still failed to get a reservation") + if(reserve.reserve(width, height, z_size, newReserved.z_value)) + return reserve else - if(!level_trait(z, ZTRAIT_RESERVED)) - log_debug("Cannot block reserve on a non-ZTRAIT_RESERVED level") + if(!level_trait(z_reservation, ZTRAIT_RESERVED)) qdel(reserve) return else - if(reserve.Reserve(width, height, z)) + if(reserve.reserve(width, height, z_size, z_reservation)) return reserve - log_debug("unknown reservation failure") QDEL_NULL(reserve) -//This is not for wiping reserved levels, use wipe_reservations() for that. +///Sets up a z level as reserved +///This is not for wiping reserved levels, use wipe_reservations() for that. +///If this is called after SSatom init, it will call Initialize on all turfs on the passed z, as its name promises /datum/controller/subsystem/mapping/proc/initialize_reserved_level(z) UNTIL(!clearing_reserved_turfs) //regardless, lets add a check just in case. clearing_reserved_turfs = TRUE //This operation will likely clear any existing reservations, so lets make sure nothing tries to make one while we're doing it. if(!level_trait(z,ZTRAIT_RESERVED)) clearing_reserved_turfs = FALSE CRASH("Invalid z level prepared for reservations.") - var/turf/A = get_turf(locate(8,8,z)) - var/turf/B = get_turf(locate(world.maxx - 8,world.maxy - 8,z)) + var/turf/A = get_turf(locate(SHUTTLE_TRANSIT_BORDER,SHUTTLE_TRANSIT_BORDER,z)) + var/turf/B = get_turf(locate(world.maxx - SHUTTLE_TRANSIT_BORDER,world.maxy - SHUTTLE_TRANSIT_BORDER,z)) var/block = block(A, B) - for(var/t in block) - // No need to empty() these, because it's world init and they're - // already /turf/open/space/basic. - var/turf/T = t - T.flags_atom |= UNUSED_RESERVATION_TURF + for(var/turf/T as anything in block) + // No need to empty() these, because they just got created and are already /turf/open/space/basic. + T.turf_flags = UNUSED_RESERVATION_TURF + CHECK_TICK + + // Gotta create these suckers if we've not done so already + if(SSatoms.initialized) + SSatoms.InitializeAtoms(Z_TURFS(z)) + unused_turfs["[z]"] = block reservation_ready["[z]"] = TRUE clearing_reserved_turfs = FALSE -/datum/controller/subsystem/mapping/proc/reserve_turfs(list/turfs) - for(var/i in turfs) - var/turf/T = i - T.empty(RESERVED_TURF_TYPE, RESERVED_TURF_TYPE, null, TRUE) - LAZYINITLIST(unused_turfs["[T.z]"]) - unused_turfs["[T.z]"] |= T - T.flags_atom |= UNUSED_RESERVATION_TURF - GLOB.areas_by_type[world.area].contents += T - CHECK_TICK +/// Schedules a group of turfs to be handed back to the reservation system's control +/// If await is true, will sleep until the turfs are finished work +/datum/controller/subsystem/mapping/proc/reserve_turfs(list/turfs, await = FALSE) + lists_to_reserve += list(turfs) + if(await) + UNTIL(!length(turfs)) //DO NOT CALL THIS PROC DIRECTLY, CALL wipe_reservations(). /datum/controller/subsystem/mapping/proc/do_wipe_turf_reservations() + PRIVATE_PROC(TRUE) UNTIL(initialized) //This proc is for AFTER init, before init turf reservations won't even exist and using this will likely break things. for(var/i in turf_reservations) var/datum/turf_reservation/TR = i @@ -323,19 +393,28 @@ SUBSYSTEM_DEF(mapping) qdel(TR, TRUE) UNSETEMPTY(turf_reservations) var/list/clearing = list() - for(var/l in unused_turfs) //unused_turfs is a assoc list by z = list(turfs) + for(var/l in unused_turfs) //unused_turfs is an assoc list by z = list(turfs) if(islist(unused_turfs[l])) clearing |= unused_turfs[l] clearing |= used_turfs //used turfs is an associative list, BUT, reserve_turfs() can still handle it. If the code above works properly, this won't even be needed as the turfs would be freed already. unused_turfs.Cut() used_turfs.Cut() - reserve_turfs(clearing) + reserve_turfs(clearing, await = TRUE) /datum/controller/subsystem/mapping/proc/reg_in_areas_in_z(list/areas) for(var/B in areas) var/area/A = B A.reg_in_areas_in_z() +/// Takes a z level datum, and tells the mapping subsystem to manage it +/// Also handles things like plane offset generation, and other things that happen on a z level to z level basis +/datum/controller/subsystem/mapping/proc/manage_z_level(datum/space_level/new_z, filled_with_space, contain_turfs = TRUE) + // First, add the z + z_list += new_z + // Then we build our lookup lists + //var/z_value = new_z.z_value + //TODO: All the Z-plane init stuff goes below here normally, we don't have that yet + /// Gets a name for the marine ship as per the enabled ship map configuration /datum/controller/subsystem/mapping/proc/get_main_ship_name() if(!configs) diff --git a/code/controllers/subsystem/shuttles.dm b/code/controllers/subsystem/shuttles.dm index db0d449b4dd7..439f83ceb8c0 100644 --- a/code/controllers/subsystem/shuttles.dm +++ b/code/controllers/subsystem/shuttles.dm @@ -39,9 +39,6 @@ SUBSYSTEM_DEF(shuttle) var/loading_shuttle = FALSE /datum/controller/subsystem/shuttle/Initialize(timeofday) - if(GLOB.perf_flags & PERF_TOGGLE_SHUTTLES) - can_fire = FALSE - return initial_load() return SS_INIT_SUCCESS @@ -52,8 +49,6 @@ SUBSYSTEM_DEF(shuttle) CHECK_TICK /datum/controller/subsystem/shuttle/fire(resumed = FALSE) - if(!resumed && (GLOB.perf_flags & PERF_TOGGLE_SHUTTLES)) - return for(var/thing in mobile) if(!thing) mobile.Remove(thing) @@ -187,13 +182,19 @@ SUBSYSTEM_DEF(shuttle) var/transit_path = M.get_transit_path_type() - var/datum/turf_reservation/proposal = SSmapping.RequestBlockReservation(transit_width, transit_height, null, /datum/turf_reservation/transit, transit_path) + var/datum/turf_reservation/proposal = SSmapping.request_turf_block_reservation( + transit_width, + transit_height, + 1, + reservation_type = /datum/turf_reservation/transit, + turf_type_override = transit_path, + ) if(!istype(proposal)) log_debug("generate_transit_dock() failed to get a block reservation from mapping system") return FALSE - var/turf/bottomleft = locate(proposal.bottom_left_coords[1], proposal.bottom_left_coords[2], proposal.bottom_left_coords[3]) + var/turf/bottomleft = proposal.bottom_left_turfs[1] // Then create a transit docking port in the middle var/coords = M.return_coords(0, 0, dock_dir) /* 0------2 @@ -362,7 +363,7 @@ SUBSYSTEM_DEF(shuttle) return shuttle -/datum/controller/subsystem/shuttle/proc/action_load(datum/map_template/shuttle/loading_template, obj/docking_port/stationary/destination_port) +/datum/controller/subsystem/shuttle/proc/action_load(datum/map_template/shuttle/loading_template, obj/docking_port/stationary/destination_port, replace = FALSE) // Check for an existing preview if(preview_shuttle && (loading_template != preview_template)) preview_shuttle.jumpToNullSpace() @@ -371,8 +372,7 @@ SUBSYSTEM_DEF(shuttle) QDEL_NULL(preview_reservation) if(!preview_shuttle) - if(load_template(loading_template)) - preview_shuttle.linkup(loading_template, destination_port) + load_template(loading_template) preview_template = loading_template // get the existing shuttle information, if any @@ -407,9 +407,6 @@ SUBSYSTEM_DEF(shuttle) for(var/area/A as anything in preview_shuttle.shuttle_areas) for(var/turf/T as anything in A) - // turfs inside the shuttle are not available for shuttles - T.flags_atom &= ~UNUSED_RESERVATION_TURF - // update underlays if(istype(T, /turf/closed/shuttle)) var/dx = T.x - preview_shuttle.x @@ -418,8 +415,10 @@ SUBSYSTEM_DEF(shuttle) T.underlays.Cut() T.underlays += mutable_appearance(target_lz.icon, target_lz.icon_state, TURF_LAYER, FLOOR_PLANE) + preview_shuttle.register(replace) var/list/force_memory = preview_shuttle.movement_force preview_shuttle.movement_force = list("KNOCKDOWN" = 0, "THROW" = 0) + preview_shuttle.initiate_docking(D) preview_shuttle.movement_force = force_memory @@ -430,7 +429,7 @@ SUBSYSTEM_DEF(shuttle) preview_shuttle.timer = timer preview_shuttle.mode = mode - preview_shuttle.register() + preview_shuttle.postregister(replace) // TODO indicate to the user that success happened, rather than just // blanking the modification tab @@ -440,16 +439,21 @@ SUBSYSTEM_DEF(shuttle) selected = null QDEL_NULL(preview_reservation) -/datum/controller/subsystem/shuttle/proc/load_template(datum/map_template/shuttle/S) +/datum/controller/subsystem/shuttle/proc/load_template(datum/map_template/shuttle/loading_template) . = FALSE - // load shuttle template, centred at shuttle import landmark, - preview_reservation = SSmapping.RequestBlockReservation(S.width, S.height, SSmapping.transit.z_value, /datum/turf_reservation/transit) + // Load shuttle template to a fresh block reservation. + preview_reservation = SSmapping.request_turf_block_reservation( + loading_template.width, + loading_template.height, + 1, + reservation_type = /datum/turf_reservation/transit, + ) if(!preview_reservation) CRASH("failed to reserve an area for shuttle template loading") - var/turf/BL = TURF_FROM_COORDS_LIST(preview_reservation.bottom_left_coords) - S.load(BL, centered = FALSE, register = FALSE) + var/turf/bottom_left = preview_reservation.bottom_left_turfs[1] + loading_template.load(bottom_left, centered = FALSE, register = FALSE) - var/affected = S.get_affected_turfs(BL, centered=FALSE) + var/affected = loading_template.get_affected_turfs(bottom_left, centered=FALSE) var/found = 0 // Search the turfs for docking ports @@ -463,13 +467,13 @@ SUBSYSTEM_DEF(shuttle) found++ if(found > 1) qdel(P, force=TRUE) - log_world("Map warning: Shuttle Template [S.mappath] has multiple mobile docking ports.") + log_world("Map warning: Shuttle Template [loading_template.mappath] has multiple mobile docking ports.") else preview_shuttle = P if(istype(P, /obj/docking_port/stationary)) - log_world("Map warning: Shuttle Template [S.mappath] has a stationary docking port.") + log_world("Map warning: Shuttle Template [loading_template.mappath] has a stationary docking port.") if(!found) - var/msg = "load_template(): Shuttle Template [S.mappath] has no mobile docking port. Aborting import." + var/msg = "load_template(): Shuttle Template [loading_template.mappath] has no mobile docking port. Aborting import." for(var/T in affected) var/turf/T0 = T T0.empty() @@ -478,7 +482,7 @@ SUBSYSTEM_DEF(shuttle) WARNING(msg) return //Everything fine - S.post_load(preview_shuttle) + loading_template.post_load(preview_shuttle) return TRUE /datum/controller/subsystem/shuttle/proc/unload_preview() diff --git a/code/datums/ammo/bullet/pistol.dm b/code/datums/ammo/bullet/pistol.dm index 8be63b0a15af..937c40d16cff 100644 --- a/code/datums/ammo/bullet/pistol.dm +++ b/code/datums/ammo/bullet/pistol.dm @@ -66,7 +66,7 @@ /datum/ammo/bullet/pistol/ap/toxin/on_hit_turf(turf/T, obj/projectile/P) . = ..() - if(T.flags_turf & TURF_ORGANIC) + if(T.turf_flags & TURF_ORGANIC) P.damage *= organic_damage_mult /datum/ammo/bullet/pistol/ap/toxin/on_hit_obj(obj/O, obj/projectile/P) @@ -197,7 +197,7 @@ /datum/ammo/bullet/pistol/squash/toxin/on_hit_turf(turf/T, obj/projectile/P) . = ..() - if(T.flags_turf & TURF_ORGANIC) + if(T.turf_flags & TURF_ORGANIC) P.damage *= organic_damage_mult /datum/ammo/bullet/pistol/squash/toxin/on_hit_obj(obj/O, obj/projectile/P) diff --git a/code/datums/ammo/bullet/revolver.dm b/code/datums/ammo/bullet/revolver.dm index 633bf3e2f7ff..0688e615378e 100644 --- a/code/datums/ammo/bullet/revolver.dm +++ b/code/datums/ammo/bullet/revolver.dm @@ -52,7 +52,7 @@ /datum/ammo/bullet/revolver/marksman/toxin/on_hit_turf(turf/T, obj/projectile/P) . = ..() - if(T.flags_turf & TURF_ORGANIC) + if(T.turf_flags & TURF_ORGANIC) P.damage *= organic_damage_mult /datum/ammo/bullet/revolver/marksman/toxin/on_hit_obj(obj/O, obj/projectile/P) diff --git a/code/datums/ammo/bullet/rifle.dm b/code/datums/ammo/bullet/rifle.dm index 279a828ed786..7711e082a596 100644 --- a/code/datums/ammo/bullet/rifle.dm +++ b/code/datums/ammo/bullet/rifle.dm @@ -74,7 +74,7 @@ /datum/ammo/bullet/rifle/ap/toxin/on_hit_turf(turf/T, obj/projectile/P) . = ..() - if(T.flags_turf & TURF_ORGANIC) + if(T.turf_flags & TURF_ORGANIC) P.damage *= organic_damage_mult /datum/ammo/bullet/rifle/ap/toxin/on_hit_obj(obj/O, obj/projectile/P) diff --git a/code/datums/ammo/bullet/smg.dm b/code/datums/ammo/bullet/smg.dm index e24b3021da97..3fa087972fbe 100644 --- a/code/datums/ammo/bullet/smg.dm +++ b/code/datums/ammo/bullet/smg.dm @@ -51,7 +51,7 @@ /datum/ammo/bullet/smg/ap/toxin/on_hit_turf(turf/T, obj/projectile/P) . = ..() - if(T.flags_turf & TURF_ORGANIC) + if(T.turf_flags & TURF_ORGANIC) P.damage *= organic_damage_mult /datum/ammo/bullet/smg/ap/toxin/on_hit_obj(obj/O, obj/projectile/P) diff --git a/code/datums/shuttles.dm b/code/datums/shuttles.dm index 98bcf296755b..0eba86add45d 100644 --- a/code/datums/shuttles.dm +++ b/code/datums/shuttles.dm @@ -62,43 +62,30 @@ locate(.[MAP_MAXX], .[MAP_MAXY], .[MAP_MAXZ])) for(var/i in 1 to turfs.len) var/turf/place = turfs[i] + + // ================== CM Change ================== + // We perform atom initialization of the docking_ports BEFORE skipping space, + // because our lifeboats have their corners as object props and still + // reside on space turfs. Notably the bottom left corner, which also contains + // the docking port. + + for(var/obj/docking_port/mobile/port in place) + SSatoms.InitializeAtoms(list(port)) + if(register) + port.register() + if(istype(place, /turf/open/space)) // This assumes all shuttles are loaded in a single spot then moved to their real destination. continue if(length(place.baseturfs) < 2) // Some snowflake shuttle shit continue place.baseturfs.Insert(3, /turf/baseturf_skipover/shuttle) - - for(var/obj/docking_port/mobile/port in place) - if(register) - port.register() - if(isnull(port_x_offset)) - continue - switch(port.dir) // Yeah this looks a little ugly but mappers had to do this in their head before - if(NORTH) - port.width = width - port.height = height - port.dwidth = port_x_offset - 1 - port.dheight = port_y_offset - 1 - if(EAST) - port.width = height - port.height = width - port.dwidth = height - port_y_offset - port.dheight = port_x_offset - 1 - if(SOUTH) - port.width = width - port.height = height - port.dwidth = width - port_x_offset - port.dheight = height - port_y_offset - if(WEST) - port.width = height - port.height = width - port.dwidth = port_y_offset - 1 - port.dheight = width - port_x_offset + // =============== END CM Change ================= //Whatever special stuff you want -/datum/map_template/shuttle/proc/post_load(obj/docking_port/mobile/M) +/datum/map_template/shuttle/post_load(obj/docking_port/mobile/M) if(movement_force) M.movement_force = movement_force.Copy() + M.linkup() /datum/map_template/shuttle/vehicle @@ -115,3 +102,9 @@ /datum/map_template/shuttle/trijent_elevator/B elevator_network = "B" + +/datum/map_template/shuttle/trijent_elevator/post_load(obj/docking_port/mobile/M) + . = ..() + var/obj/docking_port/mobile/trijent_elevator/elev = M + elev.elevator_network = elevator_network + log_debug("Adding network [elevator_network] to [M.id]") diff --git a/code/datums/tutorial/_tutorial.dm b/code/datums/tutorial/_tutorial.dm index 7dd7ac85c04d..f6e70e33cdd9 100644 --- a/code/datums/tutorial/_tutorial.dm +++ b/code/datums/tutorial/_tutorial.dm @@ -50,12 +50,12 @@ GLOBAL_LIST_EMPTY_TYPED(ongoing_tutorials, /datum/tutorial) tutorial_mob = starting_mob - reservation = SSmapping.RequestBlockReservation(initial(tutorial_template.width), initial(tutorial_template.height)) + reservation = SSmapping.request_turf_block_reservation(initial(tutorial_template.width), initial(tutorial_template.height), 1) if(!reservation) abort_tutorial() return FALSE - var/turf/bottom_left_corner_reservation = locate(reservation.bottom_left_coords[1], reservation.bottom_left_coords[2], reservation.bottom_left_coords[3]) + var/turf/bottom_left_corner_reservation = reservation.bottom_left_turfs[1] var/datum/map_template/tutorial/template = new tutorial_template template.load(bottom_left_corner_reservation, FALSE, TRUE) var/obj/landmark = locate(/obj/effect/landmark/tutorial_bottom_left) in GLOB.landmarks_list @@ -98,11 +98,7 @@ GLOBAL_LIST_EMPTY_TYPED(ongoing_tutorials, /datum/tutorial) /// Verify the template loaded fully and without error. /datum/tutorial/proc/verify_template_loaded() // We subtract 1 from x and y because the bottom left corner doesn't start at the walls. - var/turf/true_bottom_left_corner = locate( - reservation.bottom_left_coords[1], - reservation.bottom_left_coords[2], - reservation.bottom_left_coords[3], - ) + var/turf/true_bottom_left_corner = reservation.bottom_left_turfs[1] // We subtract 1 from x and y here because the bottom left corner counts as the first tile var/turf/top_right_corner = locate( true_bottom_left_corner.x + initial(tutorial_template.width) - 1, diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 5f36b3b8b390..f2ae11425f42 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -503,7 +503,7 @@ Parameters are passed from New. onclose(usr, "[name]") ///This proc is called on atoms when they are loaded into a shuttle -/atom/proc/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) +/atom/proc/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock) return /** diff --git a/code/game/machinery/doors/multi_tile.dm b/code/game/machinery/doors/multi_tile.dm index 0a179af27803..f943cd696897 100644 --- a/code/game/machinery/doors/multi_tile.dm +++ b/code/game/machinery/doors/multi_tile.dm @@ -336,7 +336,7 @@ continue INVOKE_ASYNC(atom_movable, TYPE_PROC_REF(/atom/movable, throw_atom), projected, 1, SPEED_FAST, null, FALSE) -/obj/structure/machinery/door/airlock/multi_tile/almayer/dropshiprear/lifeboat/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override) +/obj/structure/machinery/door/airlock/multi_tile/almayer/dropshiprear/lifeboat/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock) . = ..() if(istype(port, /obj/docking_port/mobile/crashable/lifeboat)) var/obj/docking_port/mobile/crashable/lifeboat/lifeboat = port diff --git a/code/game/objects/items/fulton.dm b/code/game/objects/items/fulton.dm index e36d269c8b90..664c7871ba7f 100644 --- a/code/game/objects/items/fulton.dm +++ b/code/game/objects/items/fulton.dm @@ -140,10 +140,12 @@ GLOBAL_LIST_EMPTY(deployed_fultons) sleep(30) original_location = get_turf(attached_atom) playsound(loc, 'sound/items/fulton.ogg', 50, 1) - reservation = SSmapping.RequestBlockReservation(3, 3, turf_type_override = /turf/open/space) - var/middle_x = reservation.bottom_left_coords[1] + Floor((reservation.top_right_coords[1] - reservation.bottom_left_coords[1]) / 2) - var/middle_y = reservation.bottom_left_coords[2] + Floor((reservation.top_right_coords[2] - reservation.bottom_left_coords[2]) / 2) - var/turf/space_tile = locate(middle_x, middle_y, reservation.bottom_left_coords[3]) + reservation = SSmapping.request_turf_block_reservation(3, 3, 1, turf_type_override = /turf/open/space) + var/turf/bottom_left_turf = reservation.bottom_left_turfs[1] + var/turf/top_right_turf = reservation.top_right_turfs[1] + var/middle_x = bottom_left_turf.x + Floor((top_right_turf.x - bottom_left_turf.x) / 2) + var/middle_y = bottom_left_turf.y + Floor((top_right_turf.y - bottom_left_turf.y) / 2) + var/turf/space_tile = locate(middle_x, middle_y, bottom_left_turf.z) if(!space_tile) visible_message(SPAN_WARNING("[src] begins beeping like crazy. Something is wrong!")) return diff --git a/code/game/turfs/closed.dm b/code/game/turfs/closed.dm index bf84bc04bf10..abc745dbdd45 100644 --- a/code/game/turfs/closed.dm +++ b/code/game/turfs/closed.dm @@ -15,6 +15,21 @@ icon_state = "black" mouse_opacity = FALSE +/// Cordon turf marking z-level boundaries and surrounding reservations +/turf/closed/cordon + name = "world border" + icon = 'icons/turf/shuttle.dmi' + icon_state = "pclosed" + layer = ABOVE_TURF_LAYER + baseturfs = /turf/closed/cordon + +/// Used as placeholder turf when something went really wrong, as per /tg/ string lists handler +/turf/closed/cordon/debug + name = "debug turf" + desc = "This turf shouldn't be here and probably result of incorrect turf replacement. Adminhelp about it or report it in an issue." + color = "#660088" + baseturfs = /turf/closed/cordon/debug + /turf/closed/mineral //mineral deposits name = "Rock" icon = 'icons/turf/walls/walls.dmi' diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index be58259e17ba..3d15a8c46570 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -24,7 +24,6 @@ */ - /turf icon = 'icons/turf/floors/floors.dmi' var/intact_tile = 1 //used by floors to distinguish floor with/without a floortile(e.g. plating). @@ -47,7 +46,7 @@ var/changing_turf = FALSE var/chemexploded = FALSE // Prevents explosion stacking - var/flags_turf = NO_FLAGS + var/turf_flags = NO_FLAGS /// Whether we've broken through the ceiling yet var/ceiling_debrised = FALSE @@ -57,6 +56,7 @@ ///Lumcount added by sources other than lighting datum objects, such as the overlay lighting component. var/dynamic_lumcount = 0 + ///List of light sources affecting this turf. ///Which directions does this turf block the vision of, taking into account both the turf's opacity and the movable opacity_sources. var/directional_opacity = NONE @@ -145,6 +145,22 @@ /turf/proc/update_icon() //Base parent. - Abby return +/// Call to move a turf from its current area to a new one +/turf/proc/change_area(area/old_area, area/new_area) + //dont waste our time + if(old_area == new_area) + return + + //move the turf + new_area.contents += src + + //changes to make after turf has moved + on_change_area(old_area, new_area) + +/// Allows for reactions to an area change without inherently requiring change_area() be called (I hate maploading) +/turf/proc/on_change_area(area/old_area, area/new_area) + transfer_area_lighting(old_area, new_area) + /turf/proc/add_cleanable_overlays() for(var/cleanable_type in cleanables) var/obj/effect/decal/cleanable/C = cleanables[cleanable_type] @@ -435,6 +451,10 @@ W.levelupdate() return W +//If you modify this function, ensure it works correctly with lateloaded map templates. +/turf/proc/AfterChange(flags, oldType) //called after a turf has been replaced in ChangeTurf() + return // Placeholder. This is mostly used by /tg/ code for atmos updates + // Take off the top layer turf and replace it with the next baseturf down /turf/proc/ScrapeAway(amount=1, flags) if(!amount) @@ -767,6 +787,33 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list( change_type = new_baseturfs return ChangeTurf(change_type, null, flags) +/// Places a turf on top - for map loading +/turf/proc/load_on_top(turf/added_layer, flags) + var/area/our_area = get_area(src) + flags = our_area.PlaceOnTopReact(list(baseturfs), added_layer, flags) + + if(flags & CHANGETURF_SKIP) // We haven't been initialized + if(flags_atom & INITIALIZED) + stack_trace("CHANGETURF_SKIP was used in a PlaceOnTop call for a turf that's initialized. This is a mistake. [src]([type])") + assemble_baseturfs() + + var/turf/new_turf + if(!length(baseturfs)) + baseturfs = list(baseturfs) + + var/list/old_baseturfs = baseturfs.Copy() + if(!isclosedturf(src)) + old_baseturfs += type + + new_turf = ChangeTurf(added_layer, null, flags) + new_turf.assemble_baseturfs(initial(added_layer.baseturfs)) // The baseturfs list is created like roundstart + if(!length(new_turf.baseturfs)) + new_turf.baseturfs = list(baseturfs) + + // The old baseturfs are put underneath, and we sort out the unwanted ones + new_turf.baseturfs = baseturfs_string_list(old_baseturfs + (new_turf.baseturfs - GLOB.blacklisted_automated_baseturfs), new_turf) + return new_turf + /turf/proc/insert_self_into_baseturfs() baseturfs += type diff --git a/code/game/turfs/walls/wall_types.dm b/code/game/turfs/walls/wall_types.dm index d546e7274331..a90e821fabbf 100644 --- a/code/game/turfs/walls/wall_types.dm +++ b/code/game/turfs/walls/wall_types.dm @@ -720,7 +720,7 @@ INITIALIZE_IMMEDIATE(/turf/closed/wall/indestructible/splashscreen) var/hivenumber = XENO_HIVE_NORMAL var/should_track_build = FALSE var/datum/cause_data/construction_data - flags_turf = TURF_ORGANIC + turf_flags = TURF_ORGANIC /turf/closed/wall/resin/Initialize(mapload) . = ..() diff --git a/code/game/turfs/walls/walls.dm b/code/game/turfs/walls/walls.dm index cb58ad2274a4..251b23ad9c57 100644 --- a/code/game/turfs/walls/walls.dm +++ b/code/game/turfs/walls/walls.dm @@ -173,7 +173,7 @@ if (acided_hole) . += SPAN_WARNING("There's a large hole in the wall that could've been caused by some sort of acid.") - if(flags_turf & TURF_ORGANIC) + if(turf_flags & TURF_ORGANIC) return // Skip the part below. 'Organic' walls aren't deconstructable with tools. switch(d_state) diff --git a/code/game/world.dm b/code/game/world.dm index 2b7dacee373a..958278042ea5 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -125,6 +125,7 @@ GLOBAL_LIST_INIT(reboot_sfx, file2list("config/reboot_sfx.txt")) GLOB.world_runtime_log = "[GLOB.log_directory]/runtime.log" GLOB.round_stats = "[GLOB.log_directory]/round_stats.log" GLOB.scheduler_stats = "[GLOB.log_directory]/round_scheduler_stats.log" + GLOB.mapping_log = "[GLOB.log_directory]/mapping.log" GLOB.strain_logs = "[GLOB.log_directory]/strain_logs.log" start_log(GLOB.tgui_log) @@ -134,6 +135,7 @@ GLOBAL_LIST_INIT(reboot_sfx, file2list("config/reboot_sfx.txt")) start_log(GLOB.world_runtime_log) start_log(GLOB.round_stats) start_log(GLOB.scheduler_stats) + start_log(GLOB.mapping_log) start_log(GLOB.strain_logs) if(fexists(GLOB.config_error_log)) @@ -317,9 +319,39 @@ GLOBAL_LIST_INIT(reboot_sfx, file2list("config/reboot_sfx.txt")) /world/proc/on_tickrate_change() SStimer.reset_buckets() +/** + * Handles incresing the world's maxx var and intializing the new turfs and assigning them to the global area. + * If map_load_z_cutoff is passed in, it will only load turfs up to that z level, inclusive. + * This is because maploading will handle the turfs it loads itself. + */ +/world/proc/increase_max_x(new_maxx, map_load_z_cutoff = maxz) + if(new_maxx <= maxx) + return +// var/old_max = world.maxx + maxx = new_maxx + if(!map_load_z_cutoff) + return +// var/area/global_area = GLOB.areas_by_type[world.area] // We're guaranteed to be touching the global area, so we'll just do this +// var/list/to_add = block( +// locate(old_max + 1, 1, 1), +// locate(maxx, maxy, map_load_z_cutoff)) +// global_area.contained_turfs += to_add + +/world/proc/increase_max_y(new_maxy, map_load_z_cutoff = maxz) + if(new_maxy <= maxy) + return +// var/old_maxy = maxy + maxy = new_maxy + if(!map_load_z_cutoff) + return +// var/area/global_area = GLOB.areas_by_type[world.area] // We're guarenteed to be touching the global area, so we'll just do this +// var/list/to_add = block( +// locate(1, old_maxy + 1, 1), +// locate(maxx, maxy, map_load_z_cutoff)) +// global_area.contained_turfs += to_add + /world/proc/incrementMaxZ() maxz++ - //SSmobs.MaxZChanged() /** For initializing and starting byond-tracy when BYOND_TRACY is defined * byond-tracy is a useful profiling tool that allows the user to view the CPU usage and execution time of procs as they run. diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index 535a55ca47b3..ede1cd029d9d 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -115,8 +115,10 @@ var/height if(istype(SSmapping.z_list[cur_z], /datum/space_level)) var/datum/space_level/cur_level = SSmapping.z_list[cur_z] - width = cur_level.x_bounds - half_chunk_size + 2 - height = cur_level.y_bounds - half_chunk_size + 2 + cur_x += cur_level.bounds[MAP_MINX] - 1 + cur_y += cur_level.bounds[MAP_MINY] - 1 + width = cur_level.bounds[MAP_MAXX] - cur_level.bounds[MAP_MINX] - half_chunk_size + 1 + height = cur_level.bounds[MAP_MAXY] - cur_level.bounds[MAP_MINY] - half_chunk_size + 1 else width = world.maxx - half_chunk_size + 2 height = world.maxy - half_chunk_size + 2 @@ -339,3 +341,20 @@ show_browser(usr, "[str]", "Ticker Count", "tickercount") + +#ifdef TESTING +GLOBAL_LIST_EMPTY(dirty_vars) + +/client/proc/see_dirty_varedits() + set category = "Debug.Mapping" + set name = "Dirty Varedits" + + var/list/dat = list() + dat += "

Abandon all hope ye who enter here



" + for(var/thing in GLOB.dirty_vars) + dat += "[thing]
" + CHECK_TICK + var/datum/browser/popup = new(usr, "dirty_vars", "Dirty Varedits", nwidth = 900, nheight = 750) + popup.set_content(dat.Join()) + popup.open() +#endif diff --git a/code/modules/admin/verbs/load_event_level.dm b/code/modules/admin/verbs/load_event_level.dm index 165506376b9b..72d004e03261 100644 --- a/code/modules/admin/verbs/load_event_level.dm +++ b/code/modules/admin/verbs/load_event_level.dm @@ -24,8 +24,6 @@ // Get dims & guesstimate center turf (in practice, current implem means min is always 1) var/dim_x = boundaries[MAP_MAXX] - boundaries[MAP_MINX] + 1 var/dim_y = boundaries[MAP_MAXY] - boundaries[MAP_MINY] + 1 - var/center_x = boundaries[MAP_MINX] + round(dim_x / 2) // Technically off by 0.5 due to above +1. Whatever - var/center_y = boundaries[MAP_MINY] + round(dim_y / 2) var/prompt = alert(C, "Are you SURE you want to load this template as level ? This is SLOW and can freeze server for a bit. Dimensions are: [dim_x] x [dim_y]", "Template Confirm" ,"Yes","Nope!") if(prompt != "Yes") @@ -40,6 +38,9 @@ to_chat(C, "Failed to load the template to a Z-Level! Sorry!") return + var/center_x = round(loaded.bounds[MAP_MAXX] / 2) // Technically off by 0.5 due to above +1. Whatever + var/center_y = round(loaded.bounds[MAP_MAXY] / 2) + // Now notify the staff of the load - this goes in addition to the generic template load game log message_admins("Successfully loaded template as new Z-Level by ckey: [logckey], template name: [template.name]", center_x, center_y, loaded.z_value) if(isobserver(C?.mob)) diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index 221736b5c84f..113d585e44ef 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -245,7 +245,7 @@ GLOBAL_LIST_INIT(bgstate_options, list( var/auto_observe = TRUE /datum/preferences/New(client/C) - key_bindings = deepCopyList(GLOB.hotkey_keybinding_list_by_key) // give them default keybinds and update their movement keys + key_bindings = deep_copy_list(GLOB.hotkey_keybinding_list_by_key) // give them default keybinds and update their movement keys macros = new(C, src) if(istype(C)) owner = C diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm index 2261ddf5ebfa..8f3d10c102ce 100644 --- a/code/modules/client/preferences_savefile.dm +++ b/code/modules/client/preferences_savefile.dm @@ -100,7 +100,7 @@ /proc/sanitize_keybindings(value) var/list/base_bindings = sanitize_islist(value, list()) if(!length(base_bindings)) - base_bindings = deepCopyList(GLOB.hotkey_keybinding_list_by_key) + base_bindings = deep_copy_list(GLOB.hotkey_keybinding_list_by_key) for(var/key in base_bindings) base_bindings[key] = base_bindings[key] & GLOB.keybindings_by_name if(!length(base_bindings[key])) diff --git a/code/modules/client/tgui_macro.dm b/code/modules/client/tgui_macro.dm index 5159a8b7414c..cd621cebab84 100644 --- a/code/modules/client/tgui_macro.dm +++ b/code/modules/client/tgui_macro.dm @@ -126,7 +126,7 @@ GLOBAL_LIST_EMPTY(ui_data_keybindings) if(choice == "Cancel") return TRUE prefs.hotkeys = (choice == "Hotkey") - prefs.key_bindings = (prefs.hotkeys) ? deepCopyList(GLOB.hotkey_keybinding_list_by_key) : deepCopyList(GLOB.classic_keybinding_list_by_key) + prefs.key_bindings = (prefs.hotkeys) ? deep_copy_list(GLOB.hotkey_keybinding_list_by_key) : deep_copy_list(GLOB.classic_keybinding_list_by_key) INVOKE_ASYNC(owner, /client/proc/set_macros) prefs.save_preferences() return TRUE diff --git a/code/modules/lighting/lighting_static/static_lighting_turf.dm b/code/modules/lighting/lighting_static/static_lighting_turf.dm index 2fe918fa88bb..ec91e17e4fca 100644 --- a/code/modules/lighting/lighting_static/static_lighting_turf.dm +++ b/code/modules/lighting/lighting_static/static_lighting_turf.dm @@ -31,7 +31,8 @@ return !(luminosity || dynamic_lumcount) -/turf/proc/change_area(area/old_area, area/new_area) +///Transfer the lighting of one area to another +/turf/proc/transfer_area_lighting(area/old_area, area/new_area) if(SSlighting.initialized) if (new_area.static_lighting != old_area.static_lighting) if (new_area.static_lighting) @@ -44,6 +45,8 @@ if(new_area.lighting_effect) overlays += new_area.lighting_effect + + /turf/proc/static_generate_missing_corners() if (!lighting_corner_NE) lighting_corner_NE = new/datum/static_lighting_corner(src, NORTH|EAST) diff --git a/code/modules/logging/global_logs.dm b/code/modules/logging/global_logs.dm index e0055907d67a..3fbcb70a1a8d 100644 --- a/code/modules/logging/global_logs.dm +++ b/code/modules/logging/global_logs.dm @@ -28,6 +28,9 @@ GLOBAL_PROTECT(scheduler_stats) GLOBAL_VAR(strain_logs) GLOBAL_PROTECT(strain_logs) +GLOBAL_VAR(mapping_log) +GLOBAL_PROTECT(mapping_log) + GLOBAL_VAR(round_stats) GLOBAL_PROTECT(round_stats) diff --git a/code/modules/mapping/map_template.dm b/code/modules/mapping/map_template.dm index 35f7d30a9732..50a343635de9 100644 --- a/code/modules/mapping/map_template.dm +++ b/code/modules/mapping/map_template.dm @@ -7,6 +7,20 @@ var/datum/parsed_map/cached_map var/keep_cached_map = FALSE + ///Default area associated with the map template + var/default_area + + ///if true, turfs loaded from this template are placed on top of the turfs already there, defaults to TRUE + var/should_place_on_top = TRUE + + ///if true, creates a list of all atoms created by this template loading, defaults to FALSE + var/returns_created_atoms = FALSE + + ///the list of atoms created by this template being loaded, only populated if returns_created_atoms is TRUE + var/list/created_atoms = list() + //make sure this list is accounted for/cleared if you request it from ssatoms! + + /datum/map_template/New(path = null, rename = null, cache = FALSE) if(path) mappath = path @@ -25,71 +39,129 @@ cached_map = parsed return bounds -/datum/parsed_map/proc/initTemplateBounds() - var/list/obj/structure/cable/cables = list() - var/list/atom/atoms = list() - var/list/area/areas = list() +/datum/map_template/proc/initTemplateBounds(list/bounds) + if (!bounds) //something went wrong + stack_trace("[name] template failed to initialize correctly!") + return - var/list/turfs = block( locate(bounds[MAP_MINX], bounds[MAP_MINY], bounds[MAP_MINZ]), - locate(bounds[MAP_MAXX], bounds[MAP_MAXY], bounds[MAP_MAXZ])) - for(var/L in turfs) - var/turf/B = L - atoms += B - areas |= B.loc - for(var/A in B) - atoms += A - if(istype(A, /obj/structure/cable)) - cables += A - continue + var/list/atom/movable/movables = list() + var/list/obj/docking_port/stationary/ports = list() + var/list/area/areas = list() + var/list/turfs = block( + locate( + bounds[MAP_MINX], + bounds[MAP_MINY], + bounds[MAP_MINZ] + ), + locate( + bounds[MAP_MAXX], + bounds[MAP_MAXY], + bounds[MAP_MAXZ] + ) + ) + for(var/turf/current_turf as anything in turfs) + var/area/current_turfs_area = current_turf.loc + areas |= current_turfs_area + if(!SSatoms.initialized) + continue + + for(var/movable_in_turf in current_turf) + if(istype(movable_in_turf, /obj/docking_port/mobile)) + continue // mobile docking ports need to be initialized after their template has finished loading, to ensure that their bounds are setup + movables += movable_in_turf + if(istype(movable_in_turf, /obj/docking_port/stationary)) + ports += movable_in_turf + + // Not sure if there is some importance here to make sure the area is in z + // first or not. Its defined In Initialize yet its run first in templates + // BEFORE so... hummm SSmapping.reg_in_areas_in_z(areas) - SSatoms.InitializeAtoms(atoms) - //SSmachines.setup_template_powernets(cables) // mapping TODO: - -/datum/map_template/proc/load_new_z() - var/x = round((world.maxx - width)/2) - var/y = round((world.maxy - height)/2) + if(!SSatoms.initialized) + return - var/datum/space_level/level = SSmapping.add_new_zlevel(name, list(ZTRAIT_AWAY = TRUE)) - var/datum/parsed_map/parsed = load_map(file(mappath), x, y, level.z_value, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=TRUE) + SSatoms.InitializeAtoms(areas + turfs + movables, returns_created_atoms ? created_atoms : null) + + for(var/turf/unlit as anything in turfs) + var/area/loc_area = unlit.loc + if(!loc_area.static_lighting) + continue + unlit.static_lighting_build_overlay() + +/datum/map_template/proc/load_new_z(secret = FALSE) + var/x = round((world.maxx - width) * 0.5) + 1 + var/y = round((world.maxy - height) * 0.5) + 1 + + var/datum/space_level/level = SSmapping.add_new_zlevel(name, list(), contain_turfs = FALSE) + var/datum/parsed_map/parsed = load_map( + file(mappath), + x, + y, + level.z_value, + no_changeturf = (SSatoms.initialized == INITIALIZATION_INSSATOMS), + place_on_top = should_place_on_top, + new_z = TRUE, + ) var/list/bounds = parsed.bounds if(!bounds) return FALSE repopulate_sorted_areas() - //initialize things that are normally initialized after map load - parsed.initTemplateBounds() - log_game("Z-level [name] loaded at at [x],[y],[world.maxz]") + initTemplateBounds(bounds) + log_game("Z-level [name] loaded at [x],[y],[world.maxz]") return level -/datum/map_template/proc/load(turf/T, centered, delete) +/datum/map_template/proc/load(turf/T, centered = FALSE, delete = FALSE) if(centered) T = locate(T.x - round(width/2) , T.y - round(height/2) , 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 // 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, delete = delete)) + + var/list/turf_blacklist = list() + update_blacklist(T, turf_blacklist) + + UNSETEMPTY(turf_blacklist) + parsed.turf_blacklist = turf_blacklist + if(!parsed.load( + T.x, + T.y, + T.z, + crop_map = TRUE, + no_changeturf = (SSatoms.initialized == INITIALIZATION_INSSATOMS), + place_on_top = should_place_on_top, + delete = delete + )) return + var/list/bounds = parsed.bounds if(!bounds) return + repopulate_sorted_areas() + //initialize things that are normally initialized after map load - parsed.initTemplateBounds() + initTemplateBounds(bounds) - log_game("[name] loaded at at [T.x],[T.y],[T.z]") + log_game("[name] loaded at [T.x],[T.y],[T.z]") return bounds +/datum/map_template/proc/post_load() + return + +/datum/map_template/proc/update_blacklist(turf/T, list/input_blacklist) + return + /datum/map_template/proc/get_affected_turfs(turf/T, centered = FALSE) var/turf/placement = T if(centered) diff --git a/code/modules/mapping/preloader.dm b/code/modules/mapping/preloader.dm index e8eee898a711..bd89d0e030a2 100644 --- a/code/modules/mapping/preloader.dm +++ b/code/modules/mapping/preloader.dm @@ -1,33 +1,42 @@ // global datum that will preload variables on atoms instanciation GLOBAL_VAR_INIT(use_preloader, FALSE) -GLOBAL_DATUM_INIT(_preloader, /datum/map_preloader, new) +GLOBAL_LIST_INIT(_preloader_attributes, null) +GLOBAL_LIST_INIT(_preloader_path, null) /// Preloader datum /datum/map_preloader - parent_type = /datum var/list/attributes var/target_path -/datum/map_preloader/proc/setup(list/the_attributes, path) +/world/proc/preloader_setup(list/the_attributes, path) if(the_attributes.len) GLOB.use_preloader = TRUE - attributes = the_attributes - target_path = path + GLOB._preloader_attributes = the_attributes + GLOB._preloader_path = path -/datum/map_preloader/proc/load(atom/what) +/world/proc/preloader_load(atom/what) GLOB.use_preloader = FALSE + var/list/attributes = GLOB._preloader_attributes for(var/attribute in attributes) var/value = attributes[attribute] if(islist(value)) - value = deepCopyList(value) + value = deep_copy_list(value) + #ifdef TESTING + if(what.vars[attribute] == value) + var/message = "[what.type] at [AREACOORD(what)] - VAR: [attribute] = [isnull(value) ? "null" : (isnum(value) ? value : "\"[value]\"")]" + log_mapping("DIRTY VAR: [message]") + GLOB.dirty_vars += message + #endif what.vars[attribute] = value -/// Area passthrough: do not instanciate a new area, reuse the current one +/// Template noop (no operation) is used to skip a turf or area when the template is loaded this allows for template transparency +/// ex. if a ship has gaps in it's design, you would use template_noop to fill these in so that when the ship moves z-level, any +/// tiles these gaps land on will not be deleted and replaced with the ships (empty) tiles /area/template_noop name = "Area Passthrough" icon_state = "noop" -/// Turf passthrough: do not instanciate a new turf, reuse the current one +/// See above explanation /turf/template_noop name = "Turf Passthrough" icon_state = "noop" diff --git a/code/modules/mapping/reader.dm b/code/modules/mapping/reader.dm index 969dc6a795f8..424ab22ae4ac 100644 --- a/code/modules/mapping/reader.dm +++ b/code/modules/mapping/reader.dm @@ -1,7 +1,67 @@ /////////////////////////////////////////////////////////////// //SS13 Optimized Map loader ////////////////////////////////////////////////////////////// -#define SPACE_KEY "space" +// We support two different map formats +// It is kinda possible to process them together, but if we split them up +// I can make optimization decisions more easily +/** + * DMM SPEC: + * DMM is split into two parts. First we have strings of text linked to lists of paths and their modifications (I will call this the cache) + * We call these strings "keys" and the things they point to members. Keys have a static length + * + * The second part is a list of locations matched to a string of keys. (I'll be calling this the grid) + * These are used to lookup the cache we built earlier. + * We store location lists as grid_sets. the lines represent different things depending on the spec + * + * In standard DMM (which you can treat as the base case, since it also covers weird modifications) each line + * represents an x file, and there's typically only one grid set per z level. + * The meme is you can look at a DMM formatted map and literally see what it should roughly look like + * This differs in TGM, and we can pull some performance from this + * + * Any restrictions here also apply to TGM + * + * /tg/ Restrictions: + * Paths have a specified order. First atoms in the order in which they should be loaded, then a single turf, then the area of the cell + * DMM technically supports turf stacking, but this is deprecated for all formats + + */ +#define MAP_DMM "dmm" +/** + * TGM SPEC: + * TGM is a derevation of DMM, with restrictions placed on it + * to make it easier to parse and to reduce merge conflicts/ease their resolution + * + * Requirements: + * Each "statement" in a key's details ends with a new line, and wrapped in (...) + * All paths end with either a comma or occasionally a {, then a new line + * Excepting the area, who is listed last and ends with a ) to mark the end of the key + * + * {} denotes a list of variable edits applied to the path that came before the first { + * the final } is followed by a comma, and then a new line + * Variable edits have the form \tname = value;\n + * Except the last edit, which has no final ;, and just ends in a newline + * No extra padding is permitted + * Many values are supported. See parse_constant() + * Strings must be wrapped in "...", files in '...', and lists in list(...) + * Files are kinda susy, and may not actually work. buyer beware + * Lists support assoc values as expected + * These constants can be further embedded into lists + * + * There can be no padding in front of, or behind a path + * + * Therefore: + * "key" = ( + * /path, + * /other/path{ + * var = list("name" = 'filepath'); + * other_var = /path + * }, + * /turf, + * /area) + * + */ +#define MAP_TGM "tgm" +#define MAP_UNKNOWN "unknown" /datum/grid_set var/xcrd @@ -11,9 +71,20 @@ /datum/parsed_map var/original_path + var/map_format + /// The length of a key in this file. This is promised by the standard to be static var/key_len = 0 + /// The length of a line in this file. Not promised by dmm but standard dmm uses it, so we can trust it + var/line_len = 0 + /// If we've expanded world.maxx + var/expanded_y = FALSE + /// If we've expanded world.maxy + var/expanded_x = FALSE var/list/grid_models = list() var/list/gridSets = list() + /// List of area types we've loaded AS A PART OF THIS MAP + /// We do this to allow non unique areas, so we'll only load one per map + var/list/area/loaded_areas = list() var/list/modelCache @@ -22,33 +93,97 @@ /// Offset bounds. Same as parsed_bounds until load(). var/list/bounds + ///any turf in this list is skipped inside of build_coordinate. Lazy assoc list + var/list/turf_blacklist + // 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") - var/static/regex/trimQuotesRegex = new(@'^[\s\n]+"?|"?[\s\n]+$|^"|"$', "g") - var/static/regex/trimRegex = new(@'^[\s\n]+|[\s\n]+$', "g") + var/static/regex/dmm_regex = new(@'"([a-zA-Z]+)" = (?:\(\n|\()((?:.|\n)*?)\)\n(?!\t)|\((\d+),(\d+),(\d+)\) = \{"([a-zA-Z\n]*)"\}', "g") + /// Matches key formats in TMG (IE: newline after the \() + var/static/regex/matches_tgm = new(@'^"[A-z]*"[\s]*=[\s]*\([\s]*\n', "m") + /// Pulls out key value pairs for TGM + var/static/regex/var_edits_tgm = new(@'^\t([A-z]*) = (.*?);?$') + /// Pulls out model paths for DMM + var/static/regex/model_path = new(@'(\/[^\{]*?(?:\{.*?\})?)(?:,|$)', "g") + + /// If we are currently loading this map + var/loading = FALSE #ifdef TESTING var/turfsSkipped = 0 #endif -/// Shortcut function to parse a map and apply it to the world. -/// -/// - `dmm_file`: A .dmm file to load (Required). -/// - `x_offset`, `y_offset`, `z_offset`: Positions representign where to load 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 -/// - `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) - if(parsed.bounds && !measureOnly) - parsed.load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop) - return parsed +/datum/parsed_map/proc/copy() + // Avoids duped work just in case + build_cache() + var/datum/parsed_map/newfriend = new() + newfriend.original_path = original_path + newfriend.map_format = map_format + newfriend.key_len = key_len + newfriend.line_len = line_len + newfriend.grid_models = grid_models.Copy() + newfriend.gridSets = gridSets.Copy() + newfriend.modelCache = modelCache.Copy() + newfriend.parsed_bounds = parsed_bounds.Copy() + // Copy parsed bounds to reset to initial values + newfriend.bounds = parsed_bounds.Copy() + newfriend.turf_blacklist = turf_blacklist?.Copy() + return newfriend + +//text trimming (both directions) helper macro +#define TRIM_TEXT(text) (trim_reduced(text)) + +/** + * Helper and recommened way to load a map file + * - dmm_file: The path to the map file + * - x_offset: The x offset to load the map at + * - y_offset: The y offset to load the map at + * - z_offset: The z offset to load the map at + * - crop_map: If true, the map will be cropped to the world bounds + * - measure_only: If true, the map will not be loaded, but the bounds will be calculated + * - no_changeturf: If true, the map will not call /turf/AfterChange + * - x_lower: The minimum x coordinate to load + * - x_upper: The maximum x coordinate to load + * - y_lower: The minimum y coordinate to load + * - y_upper: The maximum y coordinate to load + * - z_lower: The minimum z coordinate to load + * - z_upper: The maximum z coordinate to load + * - place_on_top: Whether to use /turf/proc/PlaceOnTop rather than /turf/proc/ChangeTurf + * - new_z: If true, a new z level will be created for the map + * - delete: CM/TGMC addition, if we need to manually clear turf contents before spawning stuff + */ +/proc/load_map( + dmm_file, + x_offset = 0, + y_offset = 0, + z_offset = 0, + crop_map = FALSE, + measure_only = FALSE, + no_changeturf = FALSE, + x_lower = -INFINITY, + x_upper = INFINITY, + y_lower = -INFINITY, + y_upper = INFINITY, + z_lower = -INFINITY, + z_upper = INFINITY, + place_on_top = FALSE, + new_z = FALSE, + delete = FALSE, +) + if(!(dmm_file in GLOB.cached_maps)) + GLOB.cached_maps[dmm_file] = new /datum/parsed_map(dmm_file) + + var/datum/parsed_map/parsed_map = GLOB.cached_maps[dmm_file] + parsed_map = parsed_map.copy() + if(!measure_only && !isnull(parsed_map.bounds)) + parsed_map.load(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z, delete) + return parsed_map /// 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) +/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) + // This proc sleeps for like 6 seconds. why? + // Is it file accesses? if so, can those be done ahead of time, async to save on time here? I wonder. + // Love ya :) if(isfile(tfile)) original_path = "[tfile]" tfile = file2text(tfile) @@ -56,16 +191,30 @@ // create a new datum without loading a map return - bounds = parsed_bounds = list(1.#INF, 1.#INF, 1.#INF, -1.#INF, -1.#INF, -1.#INF) - var/stored_index = 1 + src.bounds = parsed_bounds = list(1.#INF, 1.#INF, 1.#INF, -1.#INF, -1.#INF, -1.#INF) + + if(findtext(tfile, matches_tgm)) + map_format = MAP_TGM + else + map_format = MAP_DMM // Fallback + + // lists are structs don't you know :) + var/list/bounds = src.bounds + var/list/grid_models = src.grid_models + var/key_len = src.key_len + var/line_len = src.line_len + var/stored_index = 1 + var/list/regexOutput //multiz lool - while(dmmRegex.Find(tfile, stored_index)) - stored_index = dmmRegex.next + while(dmm_regex.Find(tfile, stored_index)) + stored_index = dmm_regex.next + // Datum var lookup is expensive, this isn't + regexOutput = dmm_regex.group // "aa" = (/type{vars=blah}) - if(dmmRegex.group[1]) // Model - var/key = dmmRegex.group[1] + if(regexOutput[1]) // Model + var/key = regexOutput[1] if(grid_models[key]) // Duplicate model keys are ignored in DMMs continue if(key_len != length(key)) @@ -74,328 +223,776 @@ else CRASH("Inconsistent key length in DMM") if(!measureOnly) - grid_models[key] = dmmRegex.group[2] + grid_models[key] = regexOutput[2] // (1,1,1) = {"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"} - else if(dmmRegex.group[3]) // Coords + else if(regexOutput[3]) // Coords if(!key_len) CRASH("Coords before model definition in DMM") - var/curr_x = text2num(dmmRegex.group[3]) - + var/curr_x = text2num(regexOutput[3]) if(curr_x < x_lower || curr_x > x_upper) continue + var/curr_y = text2num(regexOutput[4]) + if(curr_y < y_lower || curr_y > y_upper) + continue + + var/curr_z = text2num(regexOutput[5]) + if(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) + bounds[MAP_MINZ] = min(bounds[MAP_MINZ], curr_y) + bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], curr_z) - var/list/gridLines = splittext(dmmRegex.group[6], "\n") + var/list/gridLines = splittext(regexOutput[6], "\n") gridSet.gridLines = gridLines var/leadingBlanks = 0 - while(leadingBlanks < gridLines.len && gridLines[++leadingBlanks] == "") + while(leadingBlanks < length(gridLines) && gridLines[++leadingBlanks] == "") if(leadingBlanks > 1) gridLines.Cut(1, leadingBlanks) // Remove all leading blank lines. - if(!gridLines.len) // Skip it if only blank lines exist. + if(!length(gridLines)) // 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. + if(gridLines[length(gridLines)] == "") + gridLines.Cut(length(gridLines)) // Remove only one blank line at the end. + + bounds[MAP_MINY] = min(bounds[MAP_MINY], gridSet.ycrd) + gridSet.ycrd += length(gridLines) - 1 // Start at the top and work down + bounds[MAP_MAXY] = max(bounds[MAP_MAXY], gridSet.ycrd) - bounds[MAP_MINY] = min(bounds[MAP_MINY], clamp(gridSet.ycrd, y_lower, y_upper)) - 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)) + if(!line_len) + line_len = length(gridLines[1]) - var/maxx = gridSet.xcrd - if(gridLines.len) //Not an empty map - maxx = max(maxx, gridSet.xcrd + length(gridLines[1]) / key_len - 1) + var/maxx = curr_x + if(length(gridLines)) //Not an empty map + maxx = max(maxx, curr_x + line_len / key_len - 1) - bounds[MAP_MAXX] = clamp(max(bounds[MAP_MAXX], maxx), x_lower, x_upper) + 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 - parsed_bounds = bounds + src.bounds = null + else + // Clamp all our mins and maxes down to the proscribed limits + bounds[MAP_MINX] = clamp(bounds[MAP_MINX], x_lower, x_upper) + bounds[MAP_MAXX] = clamp(bounds[MAP_MAXX], x_lower, x_upper) + bounds[MAP_MINY] = clamp(bounds[MAP_MINY], y_lower, y_upper) + bounds[MAP_MAXY] = clamp(bounds[MAP_MAXY], y_lower, y_upper) + bounds[MAP_MINZ] = clamp(bounds[MAP_MINZ], z_lower, z_upper) + bounds[MAP_MAXZ] = clamp(bounds[MAP_MAXZ], z_lower, z_upper) + + parsed_bounds = src.bounds + src.key_len = key_len + src.line_len = line_len + +/// Iterates over all grid sets and returns ones with z values within the given bounds. Inclusive +/datum/parsed_map/proc/filter_grid_sets_based_on_z_bounds(lower_z, upper_z) + var/list/filtered_sets = list() + for(var/datum/grid_set/grid_set as anything in gridSets) + if(grid_set.zcrd < lower_z) + continue + if(grid_set.zcrd > upper_z) + continue + filtered_sets += grid_set + return filtered_sets -/// 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, delete) +/// Load the parsed map into the world. You probably want [/proc/load_map]. Keep the signature the same. +/datum/parsed_map/proc/load(x_offset = 0, y_offset = 0, z_offset = 0, crop_map = FALSE, no_changeturf = FALSE, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper = INFINITY, z_lower = -INFINITY, z_upper = INFINITY, place_on_top = FALSE, new_z = FALSE, delete = FALSE) //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, delete) + . = _load_impl(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z, delete) Master.StopLoadingMap() +#define MAPLOADING_CHECK_TICK \ + if(TICK_CHECK) { \ + if(loading) { \ + SSatoms.map_loader_stop(REF(src)); \ + stoplag(); \ + SSatoms.map_loader_begin(REF(src)); \ + } else { \ + stoplag(); \ + } \ + } + // 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, delete = FALSE) - var/list/areaCache = list() +/datum/parsed_map/proc/_load_impl(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z, delete) + PRIVATE_PROC(TRUE) + // Tell ss atoms that we're doing maploading + // We'll have to account for this in the following tick_checks so it doesn't overflow + loading = TRUE + SSatoms.map_loader_begin(REF(src)) + + // Loading used to be done in this proc + // We make the assumption that if the inner procs runtime, we WANT to do cleanup on them, but we should stil tell our parents we failed + // Since well, we did + var/sucessful = FALSE + switch(map_format) + if(MAP_TGM) + sucessful = _tgm_load(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z, delete) + else + sucessful = _dmm_load(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z, delete) + + // And we are done lads, call it off + SSatoms.map_loader_stop(REF(src)) + loading = FALSE + + // CM: Disabled due to not using contained_turfs and SSarea_contents +// if(new_z) +// for(var/z_index in bounds[MAP_MINZ] to bounds[MAP_MAXZ]) +// SSmapping.build_area_turfs(z_index) + + if(!no_changeturf) + var/list/turfs = block( + locate(bounds[MAP_MINX], bounds[MAP_MINY], bounds[MAP_MINZ]), + locate(bounds[MAP_MAXX], bounds[MAP_MAXY], bounds[MAP_MAXZ])) + for(var/turf/T as anything in turfs) + //we do this after we load everything in. if we don't, we'll have weird atmos bugs regarding atmos adjacent turfs + T.AfterChange(CHANGETURF_IGNORE_AIR) + + if(expanded_x || expanded_y) + SEND_GLOBAL_SIGNAL(COMSIG_GLOB_EXPANDED_WORLD_BOUNDS, expanded_x, expanded_y) + + #ifdef TESTING + if(turfsSkipped) + testing("Skipped loading [turfsSkipped] default turfs") + #endif + + return sucessful + +// Wanna clear something up about maps, talking in 255x255 here +// In the tgm format, each gridset contains 255 lines, each line representing one tile, with 255 total gridsets +// In the dmm format, each gridset contains 255 lines, each line representing one row of tiles, containing 255 * line length characters, with one gridset per z +// You can think of dmm as storing maps in rows, whereas tgm stores them in columns +/datum/parsed_map/proc/_tgm_load(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z, delete) + // setup 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) - 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 + // Building y coordinate ranges + var/y_relative_to_absolute = y_offset - 1 + var/x_relative_to_absolute = x_offset - 1 + + // Ok so like. something important + // We talk in "relative" coords here, so the coordinate system of the map datum + // This is so we can do offsets, but it is NOT the same as positions in game + // That's why there's some uses of - y_relative_to_absolute here, to turn absolute positions into relative ones + // TGM maps process in columns, so the starting y will always be the max size + // We know y starts at 1 + var/datum/grid_set/first_column = gridSets[1] + var/relative_y = first_column.ycrd + var/highest_y = relative_y + y_relative_to_absolute + + if(!crop_map && highest_y > world.maxy) + if(new_z) + // Need to avoid improperly loaded area/turf_contents + world.increase_max_y(highest_y, map_load_z_cutoff = z_offset - 1) + else + world.increase_max_y(highest_y) + expanded_y = TRUE + + // Skip Y coords that are above the smallest of the three params + // So maxy and y_upper get to act as thresholds, and relative_y can play + var/y_skip_above = min(world.maxy - y_relative_to_absolute, y_upper, relative_y) + // How many lines to skip because they'd be above the y cuttoff line + var/y_starting_skip = relative_y - y_skip_above + highest_y -= y_starting_skip + + // Y is the LOWEST it will ever be here, so we can easily set a threshold for how low to go + var/line_count = length(first_column.gridLines) + var/lowest_y = relative_y - (line_count - 1) // -1 because we decrement at the end of the loop, not the start + var/y_ending_skip = max(max(y_lower, 1 - y_relative_to_absolute) - lowest_y, 0) + + // X setup + var/x_delta_with = x_upper + if(crop_map) + // Take our smaller crop threshold yes? + x_delta_with = min(x_delta_with, world.maxx) + + // We're gonna skip all the entries above the upper x, or maxx if cropMap is set + // The last column is guarenteed to have the highest x value we;ll encounter + // Even if z scales, this still works + var/datum/grid_set/last_column = gridSets[length(gridSets)] + var/final_x = last_column.xcrd + x_relative_to_absolute + + if(final_x > x_delta_with) + // If our relative x is greater then X upper, well then we've gotta limit our expansion + var/delta = max(final_x - x_delta_with, 0) + final_x -= delta + if(final_x > world.maxx && !crop_map) + if(new_z) + // Need to avoid improperly loaded area/turf_contents + world.increase_max_x(final_x, map_load_z_cutoff = z_offset - 1) + else + world.increase_max_x(final_x) + expanded_x = TRUE + + var/lowest_x = max(x_lower, 1 - x_relative_to_absolute) + + // Amount we offset the grid zcrd to get the true zcrd + var/grid_z_offset = z_offset - 1 + var/z_upper_set = z_upper < INFINITY + var/z_lower_set = z_lower > -INFINITY + + // We make the assumption that the last block of turfs will have the highest embedded z in it + // true max zcrd + var/map_bounds_z_max = last_column.zcrd + var/z_upper_parsed = map_bounds_z_max + z_offset - 1 + if(z_upper_set) + z_upper_parsed -= map_bounds_z_max - z_upper + if(z_lower_set) + var/offset_amount = z_lower - 1 + z_upper_parsed -= offset_amount + grid_z_offset -= offset_amount + + var/list/target_grid_sets = gridSets + if(z_lower_set || z_upper_set) // bounds are set, filter out gridsets for z levels we don't want + target_grid_sets = filter_grid_sets_based_on_z_bounds(z_lower, z_upper) + + var/z_threshold = world.maxz + if(z_upper_parsed > z_threshold && crop_map) + for(var/i in z_threshold + 1 to z_upper_parsed) //create a new z_level if needed + world.incrementMaxZ() + if(!no_changeturf) + WARNING("Z-level expansion occurred without no_changeturf set, this may cause problems when /turf/AfterChange is called") + + for(var/datum/grid_set/gset as anything in target_grid_sets) + var/true_xcrd = gset.xcrd + x_relative_to_absolute + + // any cutoff of x means we just shouldn't iterate this gridset + if(final_x < true_xcrd || lowest_x > gset.xcrd) + continue + + var/zcrd = gset.zcrd + grid_z_offset + // If we're using changeturf, we disable it if we load into a z level we JUST created + var/no_afterchange = no_changeturf || zcrd > z_threshold + + // We're gonna track the first and last pairs of coords we find + // Since x is always incremented in steps of 1, we only need to deal in y + // The first x is guarenteed to be the lowest, the first y the highest, and vis versa + // This is faster then doing mins and maxes inside the hot loop below + var/first_found = FALSE + var/first_y = 0 + var/last_y = 0 + + var/ycrd = highest_y + // Everything following this line is VERY hot. + for(var/i in 1 + y_starting_skip to line_count - y_ending_skip) + if(gset.gridLines[i] == space_key && no_afterchange) + #ifdef TESTING + ++turfsSkipped + #endif + ycrd-- + MAPLOADING_CHECK_TICK + continue + + var/list/cache = modelCache[gset.gridLines[i]] + if(!cache) + SSatoms.map_loader_stop(REF(src)) + CRASH("Undefined model key in DMM: [gset.gridLines[i]]") + build_coordinate(cache, locate(true_xcrd, ycrd, zcrd), no_afterchange, place_on_top, new_z, delete) + + // only bother with bounds that actually exist + if(!first_found) + first_found = TRUE + first_y = ycrd + last_y = ycrd + ycrd-- + MAPLOADING_CHECK_TICK + + // The x coord never changes, so not tracking first x is safe + // If no ycrd is found, we assume this row is totally empty and just continue on + if(first_found) + bounds[MAP_MINX] = min(bounds[MAP_MINX], true_xcrd) + bounds[MAP_MINY] = min(bounds[MAP_MINY], last_y) + bounds[MAP_MINZ] = min(bounds[MAP_MINZ], zcrd) + bounds[MAP_MAXX] = max(bounds[MAP_MAXX], true_xcrd) + bounds[MAP_MAXY] = max(bounds[MAP_MAXY], first_y) + bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], zcrd) + return TRUE + +/// Stanrdard loading, not used in production +/// Doesn't take advantage of any tgm optimizations, which makes it slower but also more general +/// Use this if for some reason your map format is messy +/datum/parsed_map/proc/_dmm_load(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z, delete) + // setup + var/list/modelCache = build_cache(no_changeturf) + var/space_key = modelCache[SPACE_KEY] + var/list/bounds + var/key_len = src.key_len + src.bounds = bounds = list(1.#INF, 1.#INF, 1.#INF, -1.#INF, -1.#INF, -1.#INF) + + var/y_relative_to_absolute = y_offset - 1 + var/x_relative_to_absolute = x_offset - 1 + var/line_len = src.line_len + + // Amount we offset the grid zcrd to get the true zcrd + var/grid_z_offset = z_offset - 1 + var/z_upper_set = z_upper < INFINITY + var/z_lower_set = z_lower > -INFINITY + + // we now need to find the maximum z, fun! + var/map_bounds_z_max = 1 + for(var/datum/grid_set/grid_set as anything in gridSets) + map_bounds_z_max = max(map_bounds_z_max, grid_set.zcrd) + + var/z_upper_parsed = map_bounds_z_max + z_offset - 1 + if(z_upper_set) + z_upper_parsed -= map_bounds_z_max - z_upper + if(z_lower_set) + var/offset_amount = z_lower - 1 + z_upper_parsed -= offset_amount + grid_z_offset -= offset_amount + + var/list/target_grid_sets = gridSets + if(z_lower_set || z_upper_set) // bounds are set, filter out gridsets for z levels we don't want + target_grid_sets = filter_grid_sets_based_on_z_bounds(z_lower, z_upper) + + for(var/datum/grid_set/gset as anything in target_grid_sets) + var/relative_x = gset.xcrd + var/relative_y = gset.ycrd + var/true_xcrd = relative_x + x_relative_to_absolute + var/ycrd = relative_y + y_relative_to_absolute + var/zcrd = gset.zcrd + grid_z_offset + if(!crop_map && ycrd > world.maxy) + if(new_z) + // Need to avoid improperly loaded area/turf_contents + world.increase_max_y(ycrd, map_load_z_cutoff = z_offset - 1) + else + world.increase_max_y(ycrd) + expanded_y = TRUE var/zexpansion = zcrd > world.maxz + var/no_afterchange = no_changeturf if(zexpansion) - if(cropMap) + if(crop_map) continue else while (zcrd > world.maxz) //create a new z_level if needed world.incrementMaxZ() if(!no_changeturf) WARNING("Z-level expansion occurred without no_changeturf set, this may cause problems when /turf/AfterChange is called") - - 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, delete) - - // 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 + no_afterchange = TRUE + // Ok so like. something important + // We talk in "relative" coords here, so the coordinate system of the map datum + // This is so we can do offsets, but it is NOT the same as positions in game + // That's why there's some uses of - y_relative_to_absolute here, to turn absolute positions into relative ones + + // Skip Y coords that are above the smallest of the three params + // So maxy and y_upper get to act as thresholds, and relative_y can play + var/y_skip_above = min(world.maxy - y_relative_to_absolute, y_upper, relative_y) + // How many lines to skip because they'd be above the y cuttoff line + var/y_starting_skip = relative_y - y_skip_above + ycrd += y_starting_skip + + // Y is the LOWEST it will ever be here, so we can easily set a threshold for how low to go + var/line_count = length(gset.gridLines) + var/lowest_y = relative_y - (line_count - 1) // -1 because we decrement at the end of the loop, not the start + var/y_ending_skip = max(max(y_lower, 1 - y_relative_to_absolute) - lowest_y, 0) + + // Now we're gonna precompute the x thresholds + // We skip all the entries below the lower x, or 1 + var/starting_x_delta = max(max(x_lower, 1 - x_relative_to_absolute) - relative_x, 0) + // The x loop counts by key length, so we gotta multiply here + var/x_starting_skip = starting_x_delta * key_len + true_xcrd += starting_x_delta + + // We're gonna skip all the entries above the upper x, or maxx if cropMap is set + var/x_target = line_len - key_len + 1 + var/x_step_count = ROUND_UP(x_target / key_len) + var/final_x = relative_x + (x_step_count - 1) + var/x_delta_with = x_upper + if(crop_map) + // Take our smaller crop threshold yes? + x_delta_with = min(x_delta_with, world.maxx) + if(final_x > x_delta_with) + // If our relative x is greater then X upper, well then we've gotta limit our expansion + var/delta = max(final_x - x_delta_with, 0) + x_step_count -= delta + final_x -= delta + x_target = x_step_count * key_len + if(final_x > world.maxx && !crop_map) + if(new_z) + // Need to avoid improperly loaded area/turf_contents + world.increase_max_x(final_x, map_load_z_cutoff = z_offset - 1) + else + world.increase_max_x(final_x) + expanded_x = TRUE + + // We're gonna track the first and last pairs of coords we find + // The first x is guarenteed to be the lowest, the first y the highest, and vis versa + // This is faster then doing mins and maxes inside the hot loop below + var/first_found = FALSE + var/first_x = 0 + var/first_y = 0 + var/last_x = 0 + var/last_y = 0 + + // Everything following this line is VERY hot. How hot depends on the map format + // (Yes this does mean dmm is technically faster to parse. shut up) + for(var/i in 1 + y_starting_skip to line_count - y_ending_skip) + var/line = gset.gridLines[i] + + var/xcrd = true_xcrd + for(var/tpos in 1 + x_starting_skip to x_target step key_len) + var/model_key = copytext(line, tpos, tpos + key_len) + if(model_key == space_key && no_afterchange) + #ifdef TESTING + ++turfsSkipped + #endif + MAPLOADING_CHECK_TICK ++xcrd - --ycrd - - CHECK_TICK + continue + var/list/cache = modelCache[model_key] + if(!cache) + SSatoms.map_loader_stop(REF(src)) + CRASH("Undefined model key in DMM: [model_key]") + build_coordinate(cache, locate(xcrd, ycrd, zcrd), no_afterchange, place_on_top, new_z, delete) + + // only bother with bounds that actually exist + if(!first_found) + first_found = TRUE + first_x = xcrd + first_y = ycrd + last_x = xcrd + last_y = ycrd + MAPLOADING_CHECK_TICK + ++xcrd + ycrd-- + MAPLOADING_CHECK_TICK + bounds[MAP_MINX] = min(bounds[MAP_MINX], first_x) + bounds[MAP_MINY] = min(bounds[MAP_MINY], last_y) + bounds[MAP_MINZ] = min(bounds[MAP_MINZ], zcrd) + bounds[MAP_MAXX] = max(bounds[MAP_MAXX], last_x) + bounds[MAP_MAXY] = max(bounds[MAP_MAXY], first_y) + bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], zcrd) - //if(!no_changeturf)// mapping TODO: - // 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]))) - // var/turf/T = t - // //we do this after we load everything in. if we don't; we'll have weird atmos bugs regarding atmos adjacent turfs - // T.AfterChange(CHANGETURF_IGNORE_AIR) + return TRUE - #ifdef TESTING - if(turfsSkipped) - testing("Skipped loading [turfsSkipped] default turfs") - #endif +GLOBAL_LIST_EMPTY(map_model_default) - return TRUE +/datum/parsed_map/proc/build_cache(no_changeturf, bad_paths) + if(map_format == MAP_TGM) + return tgm_build_cache(no_changeturf, bad_paths) + return dmm_build_cache(no_changeturf, bad_paths) -/datum/parsed_map/proc/build_cache(no_changeturf, bad_paths=null) +/datum/parsed_map/proc/tgm_build_cache(no_changeturf, bad_paths=null) if(modelCache && !bad_paths) return modelCache . = modelCache = list() var/list/grid_models = src.grid_models + var/set_space = FALSE + // Use where a list is needed, but where it will not be modified + // Used here to remove the cost of needing to make a new list for each fields entry when it's set manually later + var/static/list/default_list = GLOB.map_model_default // It's stupid, but it saves += list(list) + var/static/list/wrapped_default_list = list(default_list) // It's stupid, but it saves += list(list) + var/static/regex/var_edits = var_edits_tgm + + var/path_to_init = "" + // Reference to the attributes list we're currently filling, if any + var/list/current_attributes + // If we are currently editing a path or not + var/editing = FALSE for(var/model_key in grid_models) - var/model = grid_models[model_key] - var/list/members = list() //will contain all members (paths) in model (in our example : /turf/unsimulated/wall and /area/mine/explored) - var/list/members_attributes = list() //will contain lists filled with corresponding variables, if any (in our example : list(icon_state = "rock") and list()) + // We're going to split models by newline + // This guarentees that each entry will be of interest to us + // Then we'll process them step by step + // Hopefully this reduces the cost from read_list that we'd otherwise have + var/list/lines = splittext(grid_models[model_key], "\n") + // Builds list of path/edits for later + // Of note: we cannot preallocate them to save time in list expansion later + // But fortunately lists allocate at least 8 entries normally anyway, and + // We are unlikely to have more then that many members + //will contain all members (paths) in model (in our example : /turf/unsimulated/wall) + var/list/members = list() + //will contain lists filled with corresponding variables, if any (in our example : list(icon_state = "rock") and list()) + var/list/members_attributes = list() ///////////////////////////////////////////////////////// //Constructing members and corresponding variables lists //////////////////////////////////////////////////////// + // string representation of the path to init + for(var/line in lines) + // We do this here to avoid needing to check at each return statement + // No harm in it anyway + MAPLOADING_CHECK_TICK + + switch(line[length(line)]) + if(";") // Var edit, we'll apply it + // Var edits look like \tname = value; + // I'm gonna try capturing them with regex, since it ought to be the fastest here + // Should hand back key = value + var_edits.Find(line) + var/value = parse_constant(var_edits.group[2]) + if(istext(value)) + value = apply_text_macros(value) + current_attributes[var_edits.group[1]] = value + continue // Keep on keeping on brother + if("{") // Start of an edit, and so also the start of a path + editing = TRUE + current_attributes = list() // Init the list we'll be filling + members_attributes += list(current_attributes) + path_to_init = copytext(line, 1, -1) + if(",") // Either the end of a path, or the end of an edit + if(editing) // it was the end of a path + editing = FALSE + continue + members_attributes += wrapped_default_list // We know this is a path, and we also know it has no vv's. so we'll just set this to the default list + // Drop the last char mind + path_to_init = copytext(line, 1, -1) + if("}") // Gotta be the end of an area edit, let's check to be sure + if(editing) // it was the end of an area edit (shouldn't do those anyhow) + editing = FALSE + continue + stack_trace("ended a line on JUST a }, with no ongoing edit. What? Area shit?") + else // If we're editing, this is a var edit entry. the last one in a stack, cause god hates me. Otherwise, it's an area + if(editing) // I want inline I want inline I want inline + // Var edits look like \tname = value; + // I'm gonna try capturing them with regex, since it ought to be the fastest here + // Should hand back key = value + var_edits.Find(line) + var/value = parse_constant(var_edits.group[2]) + if(istext(value)) + value = apply_text_macros(value) + current_attributes[var_edits.group[1]] = value + continue // Keep on keeping on brother - var/index = 1 - var/old_position = 1 - var/dpos + members_attributes += wrapped_default_list // We know this is a path, and we also know it has no vv's. so we'll just set this to the default list + path_to_init = line - while(dpos != 0) - //finding next member (e.g /turf/unsimulated/wall{icon_state = "rock"} or /area/mine/explored) - dpos = find_next_delimiter_position(model, old_position, ",", "{", "}") //find next delimiter (comma here) that's not within {...} - var/full_def = trim_text(copytext(model, old_position, dpos)) //full definition, e.g : /obj/foo/bar{variables=derp} - var/variables_start = findtext(full_def, "{") - var/path_text = trim_text(copytext(full_def, 1, variables_start)) + // Alright, if we've gotten to this point, our string is a path + // Oh and we don't trim it, because we require no padding for these + // Saves like 1.5 deciseconds + var/atom_def = text2path(path_to_init) //path definition, e.g /obj/foo/bar + + if(!ispath(atom_def, /atom)) // Skip the item if the path does not exist. Fix your crap, mappers! + if(bad_paths) + // Rare case, avoid the var to save time most of the time + LAZYOR(bad_paths[copytext(line, 1, -1)], model_key) + continue + // Index is already incremented either way, just gotta set the path and all + members += atom_def + + //check and see if we can just skip this turf + //So you don't have to understand this horrid statement, we can do this if + // 1. the space_key isn't set yet + // 2. no_changeturf is set + // 3. there are exactly 2 members + // 4. with no attributes + // 5. and the members are world.turf and world.area + // Basically, if we find an entry like this: "XXX" = (/turf/default, /area/default) + // We can skip calling this proc every time we see XXX + if(!set_space \ + && no_changeturf \ + && members_attributes.len == 2 \ + && members.len == 2 \ + && members_attributes[1] == default_list \ + && members_attributes[2] == default_list \ + && members[2] == world.area \ + && members[1] == world.turf + ) + set_space = TRUE + .[SPACE_KEY] = model_key + continue + + .[model_key] = list(members, members_attributes) + return . + +/// Builds key caches for general formats +/// Slower then the proc above, tho it could still be optimized slightly. it's just not a priority +/// Since we don't run DMM maps, ever. +/datum/parsed_map/proc/dmm_build_cache(no_changeturf, bad_paths=null) + if(modelCache && !bad_paths) + return modelCache + . = modelCache = list() + var/list/grid_models = src.grid_models + var/set_space = FALSE + // Use where a list is needed, but where it will not be modified + // Used here to remove the cost of needing to make a new list for each fields entry when it's set manually later + var/static/list/default_list = list(GLOB.map_model_default) + for(var/model_key in grid_models) + //will contain all members (paths) in model (in our example : /turf/unsimulated/wall) + var/list/members = list() + //will contain lists filled with corresponding variables, if any (in our example : list(icon_state = "rock") and list()) + var/list/members_attributes = list() + + var/model = grid_models[model_key] + ///////////////////////////////////////////////////////// + //Constructing members and corresponding variables lists + //////////////////////////////////////////////////////// + + var/model_index = 1 + while(model_path.Find(model, model_index)) + var/variables_start = 0 + var/member_string = model_path.group[1] + model_index = model_path.next + //findtext is a bit expensive, lets only do this if the last char of our string is a } (IE: we know we have vars) + //this saves about 25 miliseconds on my machine. Not a major optimization + if(member_string[length(member_string)] == "}") + variables_start = findtext(member_string, "{") + + var/path_text = TRIM_TEXT(copytext(member_string, 1, variables_start)) var/atom_def = text2path(path_text) //path definition, e.g /obj/foo/bar - if(dpos) - old_position = dpos + length(model[dpos]) if(!ispath(atom_def, /atom)) // Skip the item if the path does not exist. Fix your crap, mappers! if(bad_paths) LAZYOR(bad_paths[path_text], model_key) continue - members.Add(atom_def) + members += atom_def //transform the variables in text format into a list (e.g {var1="derp"; var2; var3=7} => list(var1="derp", var2, var3=7)) - var/list/fields = list() - + // OF NOTE: this could be made faster by replacing readlist with a progressive regex + // I'm just too much of a bum to do it rn, especially since we mandate tgm format for any maps in repo + var/list/fields = default_list if(variables_start)//if there's any variable - full_def = copytext(full_def, variables_start + length(full_def[variables_start]), -length(copytext_char(full_def, -1))) //removing the last '}' - fields = readlist(full_def, ";") - if(fields.len) - if(!trim(fields[fields.len])) - --fields.len - for(var/I in fields) - var/value = fields[I] - if(istext(value)) - fields[I] = apply_text_macros(value) + member_string = copytext(member_string, variables_start + length(member_string[variables_start]), -length(copytext_char(member_string, -1))) //removing the last '}' + fields = list(readlist(member_string, ";")) + for(var/I in fields) + var/value = fields[I] + if(istext(value)) + fields[I] = apply_text_macros(value) //then fill the members_attributes list with the corresponding variables - members_attributes.len++ - members_attributes[index++] = fields - - CHECK_TICK + members_attributes += fields + MAPLOADING_CHECK_TICK //check and see if we can just skip this turf //So you don't have to understand this horrid statement, we can do this if - // 1. no_changeturf is set - // 2. the space_key isn't set yet + // 1. the space_key isn't set yet + // 2. no_changeturf is set // 3. there are exactly 2 members // 4. with no attributes // 5. and the members are world.turf and world.area // Basically, if we find an entry like this: "XXX" = (/turf/default, /area/default) // We can skip calling this proc every time we see XXX - if(no_changeturf \ - && !(.[SPACE_KEY]) \ + if(!set_space \ + && no_changeturf \ && members.len == 2 \ && members_attributes.len == 2 \ && length(members_attributes[1]) == 0 \ && length(members_attributes[2]) == 0 \ && (world.area in members) \ && (world.turf in members)) - + set_space = TRUE .[SPACE_KEY] = model_key continue - .[model_key] = list(members, members_attributes) + return . -/datum/parsed_map/proc/build_coordinate(list/areaCache, list/model, turf/crds, no_changeturf as num, placeOnTop as num, delete) +/datum/parsed_map/proc/build_coordinate(list/model, turf/crds, no_changeturf as num, placeOnTop as num, new_z, delete) + // If we don't have a turf, nothing we will do next will actually acomplish anything, so just go back + // Note, this would actually drop area vvs in the tile, but like, why tho + if(!crds) + return var/index var/list/members = model[1] var/list/members_attributes = model[2] + // We use static lists here because it's cheaper then passing them around + var/static/list/default_list = GLOB.map_model_default //////////////// //Instanciation //////////////// + if(turf_blacklist?[crds]) + return + //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 + var/area/old_area if(members[index] != /area/template_noop) - var/atype = members[index] - GLOB._preloader.setup(members_attributes[index], atype)//preloader for assigning set variables on atom creation - var/atom/instance = areaCache[atype] - if(!instance) - instance = GLOB.areas_by_type[atype] - if(!instance) - instance = new atype(null) - areaCache[atype] = instance - if(crds) - instance.contents.Add(crds) - - if(GLOB.use_preloader && instance) - GLOB._preloader.load(instance) - - //then instance the /turf and, if multiple tiles are presents, simulates the DMM underlays piling effect - - var/first_turf_index = 1 - while(!ispath(members[first_turf_index], /turf)) //find first /turf object in members - first_turf_index++ - - //turn off base new Initialization until the whole thing is loaded - SSatoms.map_loader_begin() - //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, delete) - - 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, delete)//instance new turf - T.underlays += underlay - index++ + if(members_attributes[index] != default_list) + world.preloader_setup(members_attributes[index], members[index])//preloader for assigning set variables on atom creation + var/area/area_instance = loaded_areas[members[index]] + if(!area_instance) + var/area_type = members[index] + // If this parsed map doesn't have that area already, we check the global cache + area_instance = GLOB.areas_by_type[area_type] + // If the global list DOESN'T have this area it's either not a unique area, or it just hasn't been created yet + if (!area_instance) + area_instance = new area_type(null) + if(!area_instance) + CRASH("[area_type] failed to be new'd, what'd you do?") + loaded_areas[area_type] = area_instance + + if(!new_z) + old_area = crds.loc +// old_area.turfs_to_uncontain += crds +// area_instance.contained_turfs.Add(crds) + area_instance.contents.Add(crds) + + if(GLOB.use_preloader) + world.preloader_load(area_instance) + + // Index right before /area is /turf + index-- + var/atom/instance + //then instance the /turf + //NOTE: this used to place any turfs before the last "underneath" it using .appearance and underlays + //We don't actually use this, and all it did was cost cpu, so we don't do this anymore + if(members[index] != /turf/template_noop) + if(members_attributes[index] != default_list) + world.preloader_setup(members_attributes[index], members[index]) + + // CM/TGMC addition: delete map contents before inserting if delete truthy + if(delete) + for(var/atom/turf_atom as anything in crds.GetAllTurfStrictContents()) + if(isobserver(turf_atom)) + continue + qdel(turf_atom, force = TRUE) + + // Note: we make the assertion that the last path WILL be a turf. if it isn't, this will fail. + if(placeOnTop) + instance = crds.load_on_top(members[index], CHANGETURF_DEFER_CHANGE | (no_changeturf ? CHANGETURF_SKIP : NONE)) + else if(no_changeturf) + instance = create_atom(members[index], crds)//first preloader pass + else + instance = crds.ChangeTurf(members[index], null, CHANGETURF_DEFER_CHANGE) + + if(GLOB.use_preloader && instance)//second preloader pass, for those atoms that don't ..() in New() + world.preloader_load(instance) + // If this isn't template work, we didn't change our turf and we changed area, then we've gotta handle area lighting transfer + else if(!no_changeturf && old_area) + // Don't do contain/uncontain stuff, this happens a few lines up when the area actally changes + crds.on_change_area(old_area, crds.loc) + MAPLOADING_CHECK_TICK //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, delete) - //Restore initialization to the previous value - SSatoms.map_loader_stop() + for(var/atom_index in 1 to index-1) + if(members_attributes[atom_index] != default_list) + world.preloader_setup(members_attributes[atom_index], members[atom_index]) + + // We make the assertion that only /atom s will be in this portion of the code. if that isn't true, this will fail + instance = create_atom(members[atom_index], crds)//first preloader pass + + if(GLOB.use_preloader && instance)//second preloader pass, for those atoms that don't ..() in New() + world.preloader_load(instance) + MAPLOADING_CHECK_TICK //////////////// //Helpers procs //////////////// -//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, delete) - GLOB._preloader.setup(attributes, path) - - if(crds) - if(ispath(path, /turf)) - if(delete) - for(var/atom/A as anything in crds.GetAllTurfStrictContents()) - if(isobserver(A)) - continue - qdel(A, force=TRUE) - - if(placeOnTop) - . = crds.PlaceOnTop(null, path, CHANGETURF_DEFER_CHANGE | (no_changeturf ? CHANGETURF_SKIP : NONE)) - else if(!no_changeturf) - . = crds.ChangeTurf(path, null, CHANGETURF_DEFER_CHANGE) - else - . = create_atom(path, crds)//first preloader pass - else - . = create_atom(path, crds)//first preloader pass - - if(GLOB.use_preloader && .)//second preloader pass, for those atoms that don't ..() in New() - GLOB._preloader.load(.) - - //custom CHECK_TICK here because we don't want things created while we're sleeping to not initialize - if(TICK_CHECK) - SSatoms.map_loader_stop() - stoplag() - SSatoms.map_loader_begin() - /datum/parsed_map/proc/create_atom(path, crds) set waitfor = FALSE . = new path (crds) -//text trimming (both directions) helper proc -//optionally removes quotes before and after the text (for variable name) -/datum/parsed_map/proc/trim_text(what as text,trim_quotes=0) - if(trim_quotes) - return trimQuotesRegex.Replace(what, "") - else - return trimRegex.Replace(what, "") - - //find the position of the next delimiter,skipping whatever is comprised between opening_escape and closing_escape //returns 0 if reached the last delimiter /datum/parsed_map/proc/find_next_delimiter_position(text as text,initial_position as num, delimiter=",",opening_escape="\"",closing_escape="\"") @@ -410,7 +1007,6 @@ return next_delimiter - //build a list from variables in text form (e.g {var1="derp"; var2; var3=7} => list(var1="derp", var2, var3=7)) //return the filled list /datum/parsed_map/proc/readlist(text as text, delimiter=",") @@ -418,28 +1014,29 @@ if (!text) return + // If we're using a semi colon, we can do this as splittext rather then constant calls to find_next_delimiter_position + // This does make the code a bit harder to read, but saves a good bit of time so suck it up var/position var/old_position = 1 - while(position != 0) // find next delimiter that is not within "..." position = find_next_delimiter_position(text,old_position,delimiter) // check if this is a simple variable (as in list(var1, var2)) or an associative one (as in list(var1="foo",var2=7)) var/equal_position = findtext(text,"=",old_position, position) - - var/trim_left = trim_text(copytext(text,old_position,(equal_position ? equal_position : position))) - var/left_constant = delimiter == ";" ? trim_left : parse_constant(trim_left) + var/trim_left = TRIM_TEXT(copytext(text,old_position,(equal_position ? equal_position : position))) + var/left_constant = parse_constant(trim_left) if(position) old_position = position + length(text[position]) + if(!left_constant) // damn newlines man. Exists to provide behavior consistency with the above loop. not a major cost becuase this path is cold + continue if(equal_position && !isnum(left_constant)) // Associative var, so do the association. // Note that numbers cannot be keys - the RHS is dropped if so. - var/trim_right = trim_text(copytext(text, equal_position + length(text[equal_position]), position)) + var/trim_right = TRIM_TEXT(copytext(text, equal_position + length(text[equal_position]), position)) var/right_constant = parse_constant(trim_right) .[left_constant] = right_constant - else // simple var . += list(left_constant) @@ -451,7 +1048,10 @@ // string if(text[1] == "\"") - return copytext(text, length(text[1]) + 1, findtext(text, "\"", length(text[1]) + 1)) + // insert implied locate \" and length("\"") here + // It's a minimal timesave but it is a timesave + // Safe becuase we're guarenteed trimmed constants + return copytext(text, 2, -1) // list if(copytext(text, 1, 6) == "list(")//6 == length("list(") + 1 @@ -479,4 +1079,17 @@ /datum/parsed_map/Destroy() ..() + SSatoms.map_loader_stop(REF(src)) // Just in case, I don't want to double up here + if(turf_blacklist) + turf_blacklist.Cut() + parsed_bounds.Cut() + bounds.Cut() + grid_models.Cut() + gridSets.Cut() return QDEL_HINT_HARDDEL_NOW + +#undef MAP_DMM +#undef MAP_TGM +#undef MAP_UNKNOWN +#undef TRIM_TEXT +#undef MAPLOADING_CHECK_TICK diff --git a/code/modules/mapping/space_management/space_level.dm b/code/modules/mapping/space_management/space_level.dm index 861258aa20a2..48303b5e39ae 100644 --- a/code/modules/mapping/space_management/space_level.dm +++ b/code/modules/mapping/space_management/space_level.dm @@ -4,13 +4,20 @@ var/list/traits var/z_value = 1 //actual z placement var/linkage = SELFLOOPING - var/x_bounds - var/y_bounds + /// Bounds at time of loading the map + var/bounds /datum/space_level/New(new_z, new_name, list/new_traits = list()) z_value = new_z name = new_name traits = new_traits + + if (islist(new_traits)) + for (var/trait in new_traits) + SSmapping.z_trait_levels[trait] += list(new_z) + else // in case a single trait is passed in + SSmapping.z_trait_levels[new_traits] += list(new_z) //set_linkage(new_traits[ZTRAIT_LINKAGE]) - x_bounds = world.maxx - y_bounds = world.maxy + + //Lazy Init value, will be hopefully changed by SSmapping + bounds = list(1, 1, z_value, world.maxx, world.maxy, z_value) diff --git a/code/modules/mapping/space_management/space_reservation.dm b/code/modules/mapping/space_management/space_reservation.dm index adaff04000ff..3c47ac3b5c2a 100644 --- a/code/modules/mapping/space_management/space_reservation.dm +++ b/code/modules/mapping/space_management/space_reservation.dm @@ -1,81 +1,240 @@ +/// Cordon area surrounding turf reservations +/area/misc/cordon + name = "CORDON" + icon_state = "cordon" + static_lighting = FALSE + base_lighting_alpha = 255 + requires_power = FALSE + +#define CORDON_TURF_TYPE /turf/closed/cordon //Yes, they can only be rectangular. //Yes, I'm sorry. /datum/turf_reservation + /// All turfs that we've reserved var/list/reserved_turfs = list() + + /// Turfs around the reservation for cordoning + var/list/cordon_turfs = list() + + /// Area of turfs next to the cordon to fill with pre_cordon_area's + var/list/pre_cordon_turfs = list() + + /// The width of the reservation var/width = 0 + + /// The height of the reservation var/height = 0 - var/bottom_left_coords[3] - var/top_right_coords[3] - var/wipe_reservation_on_release = TRUE + + /// The z stack size of the reservation. Note that reservations are ALWAYS reserved from the bottom up + var/z_size = 0 + + /// List of the bottom left turfs. Indexed by what their z index for this reservation is + var/list/bottom_left_turfs = list() + + /// List of the top right turfs. Indexed by what their z index for this reservation is + var/list/top_right_turfs = list() + + /// The turf type the reservation is initially made with var/turf_type = /turf/open/space + ///Distance away from the cordon where we can put a "sort-cordon" and run some extra code (see make_repel). 0 makes nothing happen + var/pre_cordon_distance = 0 + /datum/turf_reservation/transit turf_type = /turf/open/space/transit + pre_cordon_distance = 7 /datum/turf_reservation/interior turf_type = /turf/open/void/vehicle /datum/turf_reservation/proc/Release() - var/v = reserved_turfs.Copy() - for(var/i in reserved_turfs) - var/turf/T = i - T.flags_atom |= UNUSED_RESERVATION_TURF - reserved_turfs -= i - SSmapping.used_turfs -= i - SSmapping.reserve_turfs(v) + bottom_left_turfs.Cut() + top_right_turfs.Cut() + + var/list/reserved_copy = reserved_turfs.Copy() + SSmapping.used_turfs -= reserved_turfs + reserved_turfs = list() + + var/list/cordon_copy = cordon_turfs.Copy() + SSmapping.used_turfs -= cordon_turfs + cordon_turfs = list() -/datum/turf_reservation/proc/Reserve(width, height, zlevel) + var/release_turfs = reserved_copy + cordon_copy + + for(var/turf/reserved_turf as anything in release_turfs) + SEND_SIGNAL(reserved_turf, COMSIG_TURF_RESERVATION_RELEASED, src) + + // Makes the linter happy, even tho we don't await this + INVOKE_ASYNC(SSmapping, TYPE_PROC_REF(/datum/controller/subsystem/mapping, reserve_turfs), release_turfs) + +/// Attempts to calaculate and store a list of turfs around the reservation for cordoning. Returns whether a valid cordon was calculated +/datum/turf_reservation/proc/calculate_cordon_turfs(turf/bottom_left, turf/top_right) + if(bottom_left.x < 2 || bottom_left.y < 2 || top_right.x > (world.maxx - 2) || top_right.y > (world.maxy - 2)) + return FALSE // no space for a cordon here + + var/list/possible_turfs = CORNER_OUTLINE(bottom_left, width, height) + // if they're our cordon turfs, accept them + possible_turfs -= cordon_turfs + for(var/turf/cordon_turf as anything in possible_turfs) + if(!(cordon_turf.turf_flags & UNUSED_RESERVATION_TURF)) + return FALSE + cordon_turfs |= possible_turfs + + if(pre_cordon_distance) + var/turf/offset_turf = locate(bottom_left.x + pre_cordon_distance, bottom_left.y + pre_cordon_distance, bottom_left.z) + var/list/to_add = CORNER_OUTLINE(offset_turf, width - pre_cordon_distance * 2, height - pre_cordon_distance * 2) //we step-by-stop move inwards from the outer cordon + for(var/turf/turf_being_added as anything in to_add) + pre_cordon_turfs |= turf_being_added //add one by one so we can filter out duplicates + + return TRUE + +/// Actually generates the cordon around the reservation, and marking the cordon turfs as reserved +/datum/turf_reservation/proc/generate_cordon() + for(var/turf/cordon_turf as anything in cordon_turfs) + var/area/misc/cordon/cordon_area = GLOB.areas_by_type[/area/misc/cordon] || new + //var/area/old_area = cordon_turf.loc + //old_area.turfs_to_uncontain += cordon_turf + //cordon_area.contained_turfs += cordon_turf + cordon_area.contents += cordon_turf + // Its no longer unused, but its also not "used" + cordon_turf.turf_flags &= ~UNUSED_RESERVATION_TURF + cordon_turf.ChangeTurf(CORDON_TURF_TYPE, CORDON_TURF_TYPE) + SSmapping.unused_turfs["[cordon_turf.z]"] -= cordon_turf + // still gets linked to us though + SSmapping.used_turfs[cordon_turf] = src + + //swap the area with the pre-cordoning area + +/// Internal proc which handles reserving the area for the reservation. +/datum/turf_reservation/proc/_reserve_area(width, height, zlevel) + src.width = width + src.height = height if(width > world.maxx || height > world.maxy || width < 1 || height < 1) - log_debug("turf reservation had invalid dimensions") return FALSE var/list/avail = SSmapping.unused_turfs["[zlevel]"] - var/turf/bottom_left - var/turf/top_right + var/turf/BL + var/turf/TR var/list/turf/final = list() var/passing = FALSE for(var/i in avail) CHECK_TICK - bottom_left = i - if(!(bottom_left.flags_atom & UNUSED_RESERVATION_TURF)) + BL = i + if(!(BL.turf_flags & UNUSED_RESERVATION_TURF)) continue - if(bottom_left.x + width > world.maxx || bottom_left.y + height > world.maxy) + if(BL.x + width > world.maxx || BL.y + height > world.maxy) continue - top_right = locate(bottom_left.x + width - 1, bottom_left.y + height - 1, bottom_left.z) - if(!(top_right.flags_atom & UNUSED_RESERVATION_TURF)) + TR = locate(BL.x + width - 1, BL.y + height - 1, BL.z) + if(!(TR.turf_flags & UNUSED_RESERVATION_TURF)) continue - final = block(bottom_left, top_right) + final = block(BL, TR) if(!final) continue passing = TRUE - for(var/turf/checking as anything in final) - if(!(checking.flags_atom & UNUSED_RESERVATION_TURF)) + for(var/I in final) + var/turf/checking = I + if(!(checking.turf_flags & UNUSED_RESERVATION_TURF)) passing = FALSE break + if(passing) // found a potentially valid area, now try to calculate its cordon + passing = calculate_cordon_turfs(BL, TR) if(!passing) continue break - if(!passing || !istype(bottom_left) || !istype(top_right)) - log_debug("failed to pass reservation tests, [passing], [istype(bottom_left)], [istype(top_right)]") + if(!passing || !istype(BL) || !istype(TR)) return FALSE - bottom_left_coords = list(bottom_left.x, bottom_left.y, bottom_left.z) - top_right_coords = list(top_right.x, top_right.y, top_right.z) - var/weakref = WEAKREF(src) for(var/i in final) var/turf/T = i reserved_turfs |= T SSmapping.unused_turfs["[T.z]"] -= T - SSmapping.used_turfs[T] = weakref - T = T.ChangeTurf(turf_type, turf_type) - T.flags_atom &= ~UNUSED_RESERVATION_TURF - src.width = width - src.height = height + SSmapping.used_turfs[T] = src + T.turf_flags = (T.turf_flags | RESERVATION_TURF) & ~UNUSED_RESERVATION_TURF + T.ChangeTurf(turf_type, turf_type) + + bottom_left_turfs += BL + top_right_turfs += TR + return TRUE + +/datum/turf_reservation/proc/reserve(width, height, z_size, z_reservation) + src.z_size = z_size + var/failed_reservation = FALSE + for(var/_ in 1 to z_size) + if(!_reserve_area(width, height, z_reservation)) + failed_reservation = TRUE + break + + if(failed_reservation) + Release() + return FALSE + + generate_cordon() return TRUE +/// Calculates the effective bounds information for the given turf. Returns a list of the information, or null if not applicable. +/datum/turf_reservation/proc/calculate_turf_bounds_information(turf/target) + for(var/z_idx in 1 to z_size) + var/turf/bottom_left = bottom_left_turfs[z_idx] + var/turf/top_right = top_right_turfs[z_idx] + var/bl_x = bottom_left.x + var/bl_y = bottom_left.y + var/tr_x = top_right.x + var/tr_y = top_right.y + + if(target.x < bl_x) + continue + + if(target.y < bl_y) + continue + + if(target.x > tr_x) + continue + + if(target.y > tr_y) + continue + + var/list/return_information = list() + return_information["z_idx"] = z_idx + return_information["offset_x"] = target.x - bl_x + return_information["offset_y"] = target.y - bl_y + return return_information + return null + +/// Gets the turf below the given target. Returns null if there is no turf below the target +/datum/turf_reservation/proc/get_turf_below(turf/target) + var/list/bounds_info = calculate_turf_bounds_information(target) + if(isnull(bounds_info)) + return null + + var/z_idx = bounds_info["z_idx"] + // check what z level, if its the max, then there is no turf below + if(z_idx == z_size) + return null + + var/offset_x = bounds_info["offset_x"] + var/offset_y = bounds_info["offset_y"] + var/turf/bottom_left = bottom_left_turfs[z_idx + 1] + return locate(bottom_left.x + offset_x, bottom_left.y + offset_y, bottom_left.z) + +/// Gets the turf above the given target. Returns null if there is no turf above the target +/datum/turf_reservation/proc/get_turf_above(turf/target) + var/list/bounds_info = calculate_turf_bounds_information(target) + if(isnull(bounds_info)) + return null + + var/z_idx = bounds_info["z_idx"] + // check what z level, if its the min, then there is no turf above + if(z_idx == 1) + return null + + var/offset_x = bounds_info["offset_x"] + var/offset_y = bounds_info["offset_y"] + var/turf/bottom_left = bottom_left_turfs[z_idx - 1] + return locate(bottom_left.x + offset_x, bottom_left.y + offset_y, bottom_left.z) + /datum/turf_reservation/New() LAZYADD(SSmapping.turf_reservations, src) /datum/turf_reservation/Destroy() - INVOKE_ASYNC(src, PROC_REF(Release)) + Release() LAZYREMOVE(SSmapping.turf_reservations, src) return ..() diff --git a/code/modules/mapping/space_management/zlevel_manager.dm b/code/modules/mapping/space_management/zlevel_manager.dm index 9311719ea7d8..2b96065929f8 100644 --- a/code/modules/mapping/space_management/zlevel_manager.dm +++ b/code/modules/mapping/space_management/zlevel_manager.dm @@ -14,16 +14,22 @@ for (var/I in 1 to default_map_traits.len) var/list/features = default_map_traits[I] var/datum/space_level/S = new(I, features[DL_NAME], features[DL_TRAITS]) - z_list += S + manage_z_level(S, filled_with_space = FALSE) + //generate_z_level_linkages() // Default Zs don't use add_new_zlevel() so they don't automatically generate z-linkages. -/datum/controller/subsystem/mapping/proc/add_new_zlevel(name, traits = list(), z_type = /datum/space_level) +/datum/controller/subsystem/mapping/proc/add_new_zlevel(name, traits = list(), z_type = /datum/space_level, contain_turfs = TRUE) + UNTIL(!adding_new_zlevel) + adding_new_zlevel = TRUE var/new_z = z_list.len + 1 if (world.maxz < new_z) world.incrementMaxZ() CHECK_TICK // TODO: sleep here if the Z level needs to be cleared var/datum/space_level/S = new z_type(new_z, name, traits) - z_list += S + manage_z_level(S, filled_with_space = TRUE, contain_turfs = contain_turfs) + //generate_linkages_for_z_level(new_z) + //calculate_z_level_gravity(new_z) + adding_new_zlevel = FALSE SEND_GLOBAL_SIGNAL(COMSIG_GLOB_NEW_Z, S) return S diff --git a/code/modules/nightmare/nmtasks/mapload.dm b/code/modules/nightmare/nmtasks/mapload.dm index 1ace7cd2fbec..d53b5663197d 100644 --- a/code/modules/nightmare/nmtasks/mapload.dm +++ b/code/modules/nightmare/nmtasks/mapload.dm @@ -53,7 +53,7 @@ if(!target_turf?.z) log_debug("Nightmare Mapload: Could not find landmark: [landmark]") return - var/result = parsed.load(target_turf.x, target_turf.y, target_turf.z, cropMap = TRUE, no_changeturf = FALSE, placeOnTop = FALSE, delete = replace) + var/result = parsed.load(target_turf.x, target_turf.y, target_turf.z, crop_map = TRUE, no_changeturf = FALSE, place_on_top = FALSE, delete = replace) if(!result || !parsed.bounds) log_debug("Nightmare Mapload: Map insertion failed unexpectedly for file: [filepath]") return @@ -66,22 +66,23 @@ log_debug("Nightmare Mapload: Loaded map file but could not initialize: '[filepath]' at ([target_turf.x], [target_turf.y], [target_turf.z])") return TRUE -/// Initialize atoms/areas in bounds +/// Initialize atoms/areas in bounds - basically a Nightmare version of [/datum/map_template/initTemplateBounds] /datum/nmtask/mapload/proc/initialize_boundary_contents() var/list/bounds = parsed.bounds if(length(bounds) < 6) return - var/list/TT = block( locate(bounds[MAP_MINX], bounds[MAP_MINY], bounds[MAP_MINZ]), + var/list/turf_block = block( locate(bounds[MAP_MINX], bounds[MAP_MINY], bounds[MAP_MINZ]), locate(bounds[MAP_MAXX], bounds[MAP_MAXY], bounds[MAP_MAXZ])) var/list/area/arealist = list() var/list/atom/atomlist = list() - for(var/turf/T as anything in TT) - atomlist |= T - if(T.loc) arealist |= T.loc - for(var/A in T) - atomlist |= A - SSmapping.reg_in_areas_in_z(arealist) - SSatoms.InitializeAtoms(atomlist) - // We still defer lighting, area sorting, etc + for(var/turf/turf as anything in turf_block) + atomlist += turf + if(turf.loc) + arealist |= turf.loc + for(var/atom/movable/movable as anything in turf) + atomlist += movable // Much like initTemplateBounds() this only recurses content once. Never been an issue so far, but keep it in mind. + SSmapping.reg_in_areas_in_z(arealist) // Legacy. Not sure this is needed as it should already be carried out through area Initialize. + SSatoms.InitializeAtoms(atomlist + arealist) + // We still defer lighting, area sorting, etc, to be done all in one go! SEND_SIGNAL(src, COMSIG_NIGHTMARE_TAINTED_BOUNDS, bounds) return TRUE diff --git a/code/modules/shuttle/computer.dm b/code/modules/shuttle/computer.dm index 79377d9c0849..b803a8f66b55 100644 --- a/code/modules/shuttle/computer.dm +++ b/code/modules/shuttle/computer.dm @@ -81,8 +81,8 @@ to_chat(usr, SPAN_NOTICE("Unable to comply.")) return TRUE -/obj/structure/machinery/computer/shuttle/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) - if(port && (shuttleId == initial(shuttleId) || override)) +/obj/structure/machinery/computer/shuttle/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock) + if(port && (shuttleId == initial(shuttleId))) shuttleId = port.id /obj/structure/machinery/computer/shuttle/ert diff --git a/code/modules/shuttle/docking.dm b/code/modules/shuttle/docking.dm index a21ec330d4c9..63e220deadc6 100644 --- a/code/modules/shuttle/docking.dm +++ b/code/modules/shuttle/docking.dm @@ -1,7 +1,5 @@ /// This is the main proc. It instantly moves our mobile port to stationary port `new_dock`. /obj/docking_port/mobile/proc/initiate_docking(obj/docking_port/stationary/new_dock, movement_direction, force=FALSE) - // Crashing this ship with NO SURVIVORS - if(new_dock.get_docked() == src) remove_ripples() return DOCKING_SUCCESS diff --git a/code/modules/shuttle/shuttle.dm b/code/modules/shuttle/shuttle.dm index 6f02bf00e89d..d72dcdda5bfe 100644 --- a/code/modules/shuttle/shuttle.dm +++ b/code/modules/shuttle/shuttle.dm @@ -380,10 +380,82 @@ var/shuttle_flags = NONE +#define WORLDMAXX_CUTOFF (world.maxx + 1) +#define WORLDMAXY_CUTOFF (world.maxx + 1) +/** + * Calculated and populates the information used for docking and some internal vars. + * This can also be used to calculate from shuttle_areas so that you can expand/shrink shuttles! + * + * Arguments: + * * loading_from - The template that the shuttle was loaded from, if not given we iterate shuttle_areas to calculate information instead + */ +/obj/docking_port/mobile/proc/calculate_docking_port_information(datum/map_template/shuttle/loading_from) + var/port_x_offset = loading_from?.port_x_offset + var/port_y_offset = loading_from?.port_y_offset + var/width = loading_from?.width + var/height = loading_from?.height + if(!loading_from) + if(!length(shuttle_areas)) + CRASH("Attempted to calculate a docking port's information without a template before it was assigned any areas!") + // no template given, use shuttle_areas to calculate width and height + var/min_x = -1 + var/min_y = -1 + var/max_x = WORLDMAXX_CUTOFF + var/max_y = WORLDMAXY_CUTOFF + for(var/area/area as anything in shuttle_areas) + for(var/turf/turf in area) + min_x = max(turf.x, min_x) + max_x = min(turf.x, max_x) + min_y = max(turf.y, min_y) + max_y = min(turf.y, max_y) + CHECK_TICK + + if(min_x == -1 || max_x == WORLDMAXX_CUTOFF) + CRASH("Failed to locate shuttle boundaries when iterating through shuttle areas, somehow.") + if(min_y == -1 || max_y == WORLDMAXY_CUTOFF) + CRASH("Failed to locate shuttle boundaries when iterating through shuttle areas, somehow.") + + width = (max_x - min_x) + 1 + height = (max_y - min_y) + 1 + port_x_offset = min_x - x + port_y_offset = min_y - y + + if(dir in list(EAST, WEST)) + src.width = height + src.height = width + else + src.width = width + src.height = height + + switch(dir) + if(NORTH) + dwidth = port_x_offset - 1 + dheight = port_y_offset - 1 + if(EAST) + dwidth = height - port_y_offset + dheight = port_x_offset - 1 + if(SOUTH) + dwidth = width - port_x_offset + dheight = height - port_y_offset + if(WEST) + dwidth = port_y_offset - 1 + dheight = width - port_x_offset +#undef WORLDMAXX_CUTOFF +#undef WORLDMAXY_CUTOFF + /obj/docking_port/mobile/register() . = ..() SSshuttle.mobile += src +/** + * Actions to be taken after shuttle is loaded and has been moved to its final location + * + * Arguments: + * * replace - TRUE if this shuttle is replacing an existing one. FALSE by default. + */ +/obj/docking_port/mobile/proc/postregister(replace = FALSE) + return + /obj/docking_port/mobile/Destroy(force) if(force) QDEL_NULL(alarm_sound_loop) @@ -442,6 +514,10 @@ // Called after the shuttle is loaded from template /obj/docking_port/mobile/proc/linkup(datum/map_template/shuttle/template, obj/docking_port/stationary/dock) + + // ================== CM Change ================== + // This is gone in /tg/ backend but kept for historical reasons + // Suspect this is supposed to be handled in register var/list/static/shuttle_id = list() var/idnum = ++shuttle_id[id] if(idnum > 1) @@ -449,12 +525,12 @@ id = "[id][idnum]" if(name == initial(name)) name = "[name] [idnum]" - for(var/place in shuttle_areas) - var/area/area = place - area.connect_to_shuttle(src, dock, idnum, FALSE) - for(var/each in place) - var/atom/atom = each - atom.connect_to_shuttle(src, dock, idnum, FALSE) + // ================ END CM Change ================ + + for(var/area/place as anything in shuttle_areas) + place.connect_to_shuttle(TRUE, src, dock) + for(var/atom/individual_atoms in place) + individual_atoms.connect_to_shuttle(TRUE, src, dock) //this is a hook for custom behaviour. Maybe at some point we could add checks to see if engines are intact diff --git a/code/modules/shuttle/shuttles/trijent_elevator.dm b/code/modules/shuttle/shuttles/trijent_elevator.dm index 457c150212c0..ff10485e8d5a 100644 --- a/code/modules/shuttle/shuttles/trijent_elevator.dm +++ b/code/modules/shuttle/shuttles/trijent_elevator.dm @@ -37,12 +37,6 @@ . = ..() door_control.control_doors("force-lock-launch", "all", force=TRUE) -/obj/docking_port/mobile/trijent_elevator/linkup(datum/map_template/shuttle/template, obj/docking_port/stationary/dock) - ..() - var/datum/map_template/shuttle/trijent_elevator/elev = template - elevator_network = elev.elevator_network - log_debug("Adding network [elev.elevator_network] to [id]") - /obj/docking_port/stationary/trijent_elevator dir=NORTH width=7 diff --git a/code/modules/vehicles/interior/interior.dm b/code/modules/vehicles/interior/interior.dm index f2afcd5ae5f7..8fb65602c9b3 100644 --- a/code/modules/vehicles/interior/interior.dm +++ b/code/modules/vehicles/interior/interior.dm @@ -304,21 +304,13 @@ // Returns min and max turfs for the interior /datum/interior/proc/get_bound_turfs() - var/turf/min = TURF_FROM_COORDS_LIST(reservation.bottom_left_coords) - if(!min) - return null - - var/turf/max = TURF_FROM_COORDS_LIST(reservation.top_right_coords) - if(!max) - return null - - return list(min, max) + return list(reservation.bottom_left_turfs[1], reservation.top_right_turfs[1]) /datum/interior/proc/get_middle_coords() - var/turf/min = reservation.bottom_left_coords - var/turf/max = reservation.top_right_coords + var/turf/min = reservation.bottom_left_turfs[1] + var/turf/max = reservation.top_right_turfs[1] + return list(Floor(min.x + (max.x - min.x)/2), Floor(min.y + (max.y - min.y)/2), min.z) - return list(Floor(min[1] + (max[1] - min[1])/2), Floor(min[2] + (max[2] - min[2])/2), min[3]) /datum/interior/proc/get_middle_turf() var/list/turf/bounds = get_bound_turfs() diff --git a/colonialmarines.dme b/colonialmarines.dme index 71fe1545c2fe..1098868950db 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -112,6 +112,7 @@ #include "code\__DEFINES\tgui.dm" #include "code\__DEFINES\traits.dm" #include "code\__DEFINES\turf_flags.dm" +#include "code\__DEFINES\turfs.dm" #include "code\__DEFINES\tutorial.dm" #include "code\__DEFINES\unit_tests.dm" #include "code\__DEFINES\urls.dm" @@ -184,6 +185,7 @@ #include "code\__HELPERS\sanitize_values.dm" #include "code\__HELPERS\shell.dm" #include "code\__HELPERS\status_effects.dm" +#include "code\__HELPERS\string_lists.dm" #include "code\__HELPERS\text.dm" #include "code\__HELPERS\traits.dm" #include "code\__HELPERS\type2type.dm" diff --git a/maps/shuttles/dropship_alamo.dmm b/maps/shuttles/dropship_alamo.dmm index d23036afdea3..7ea59dbb1185 100644 --- a/maps/shuttles/dropship_alamo.dmm +++ b/maps/shuttles/dropship_alamo.dmm @@ -101,7 +101,7 @@ /area/shuttle/drop1/sulaco) "if" = ( /obj/structure/shuttle/part/dropship1/left_outer_wing_connector, -/turf/open/space/basic, +/turf/template_noop, /area/shuttle/drop1/sulaco) "ig" = ( /turf/closed/shuttle/dropship1{ @@ -218,7 +218,7 @@ /area/shuttle/drop1/sulaco) "rl" = ( /obj/structure/shuttle/part/dropship1/lower_right_wall, -/turf/open/space/basic, +/turf/template_noop, /area/shuttle/drop1/sulaco) "rr" = ( /turf/closed/shuttle/dropship1/transparent{ @@ -239,7 +239,7 @@ /area/shuttle/drop1/sulaco) "sA" = ( /obj/structure/shuttle/part/dropship1/lower_left_wall, -/turf/open/space/basic, +/turf/template_noop, /area/shuttle/drop1/sulaco) "tR" = ( /obj/item/device/radio/intercom/alamo{ @@ -279,7 +279,7 @@ /area/shuttle/drop1/sulaco) "xM" = ( /obj/structure/shuttle/part/dropship1/bottom_right_wall, -/turf/open/space/basic, +/turf/template_noop, /area/shuttle/drop1/sulaco) "yQ" = ( /turf/closed/shuttle/dropship1/transparent{ @@ -288,7 +288,7 @@ /area/shuttle/drop1/sulaco) "yU" = ( /obj/structure/shuttle/part/dropship1/bottom_left_wall, -/turf/open/space/basic, +/turf/template_noop, /area/shuttle/drop1/sulaco) "zw" = ( /obj/structure/shuttle/part/dropship1/transparent/upper_left_wing, @@ -358,7 +358,7 @@ /area/shuttle/drop1/sulaco) "EN" = ( /obj/structure/shuttle/part/dropship1/transparent/engine_left_exhaust, -/turf/open/space/basic, +/turf/template_noop, /area/shuttle/drop1/sulaco) "FA" = ( /obj/structure/shuttle/part/dropship1/transparent/engine_left_cap, @@ -408,7 +408,7 @@ /area/shuttle/drop1/sulaco) "Jm" = ( /obj/structure/shuttle/part/dropship1/transparent/engine_right_exhaust, -/turf/open/space/basic, +/turf/template_noop, /area/shuttle/drop1/sulaco) "JP" = ( /turf/closed/shuttle/dropship1{ @@ -442,7 +442,7 @@ /area/shuttle/drop1/sulaco) "Me" = ( /obj/structure/shuttle/part/dropship1/left_inner_wing_connector, -/turf/open/space/basic, +/turf/template_noop, /area/shuttle/drop1/sulaco) "MP" = ( /turf/closed/shuttle/dropship1{ @@ -539,7 +539,7 @@ /area/shuttle/drop1/sulaco) "Ta" = ( /obj/structure/shuttle/part/dropship1/right_inner_wing_connector, -/turf/open/space/basic, +/turf/template_noop, /area/shuttle/drop1/sulaco) "Te" = ( /obj/structure/shuttle/part/dropship1/transparent/right_inner_bottom_wing, @@ -609,7 +609,7 @@ /area/shuttle/drop1/sulaco) "Wg" = ( /obj/structure/shuttle/part/dropship1/right_outer_wing_connector, -/turf/open/space/basic, +/turf/template_noop, /area/shuttle/drop1/sulaco) "WQ" = ( /turf/closed/shuttle/dropship1{ diff --git a/maps/shuttles/dropship_normandy.dmm b/maps/shuttles/dropship_normandy.dmm index e64fbe62372d..0cf629e638e1 100644 --- a/maps/shuttles/dropship_normandy.dmm +++ b/maps/shuttles/dropship_normandy.dmm @@ -63,7 +63,7 @@ /area/shuttle/drop2/sulaco) "fQ" = ( /obj/structure/shuttle/part/dropship2/left_inner_wing_connector, -/turf/open/space/basic, +/turf/template_noop, /area/shuttle/drop2/sulaco) "gD" = ( /turf/closed/shuttle/dropship2/transparent{ @@ -118,7 +118,7 @@ /area/shuttle/drop2/sulaco) "iI" = ( /obj/structure/shuttle/part/dropship2/right_inner_wing_connector, -/turf/open/space/basic, +/turf/template_noop, /area/shuttle/drop2/sulaco) "jc" = ( /obj/item/device/radio/intercom/normandy{ @@ -216,7 +216,7 @@ /area/shuttle/drop2/sulaco) "nS" = ( /obj/structure/shuttle/part/dropship2/lower_left_wall, -/turf/open/space/basic, +/turf/template_noop, /area/shuttle/drop2/sulaco) "oc" = ( /obj/structure/shuttle/part/dropship2/transparent/nose_top_right, @@ -514,7 +514,7 @@ /area/shuttle/drop2/sulaco) "NM" = ( /obj/structure/shuttle/part/dropship2/left_outer_wing_connector, -/turf/open/space/basic, +/turf/template_noop, /area/shuttle/drop2/sulaco) "Od" = ( /obj/effect/attach_point/fuel/dropship2{ @@ -587,7 +587,7 @@ /area/shuttle/drop2/sulaco) "QK" = ( /obj/structure/shuttle/part/dropship2/lower_right_wall, -/turf/open/space/basic, +/turf/template_noop, /area/shuttle/drop2/sulaco) "Rr" = ( /turf/template_noop, @@ -607,7 +607,7 @@ /area/shuttle/drop2/sulaco) "RJ" = ( /obj/structure/shuttle/part/dropship2/right_outer_wing_connector, -/turf/open/space/basic, +/turf/template_noop, /area/shuttle/drop2/sulaco) "SB" = ( /obj/structure/shuttle/part/dropship2/nose_front_left,