From b50bef7b885cd8d6858556a01ee3f9b4c1ea9a2e Mon Sep 17 00:00:00 2001 From: kane-f <57303506+kane-f@users.noreply.github.com> Date: Sat, 2 Oct 2021 04:36:54 +0100 Subject: [PATCH] Map element (vault etc) updates: Item overwriting option, rotation on loading (#30957) * First attempt at making rotated map element loading working * Fixes * Fixes * Oversight * Fixes offsets properly, ugly but works * Makes some vaults have ability to override it * Now properly sets these loaded in the exact position, if not a bit hacky * And now, the moment of truth, the actual rotation in loading itself, plus a server config * And a fix for this maybe * Overwriting of movable atoms support * Maybe like this? * Ah, the grid parsing was going counterclockwise by mistake, that's why it was doing that * Makes this show up in jump formatting * Rotated dungeons below * Initialising again after rotation is probably better * Fixing turfs ie. shuttle not rotating properly * Stops a runtime Co-authored-by: kanef --- code/controllers/configuration.dm | 3 + code/datums/map_elements.dm | 7 +- code/modules/admin/admin_verbs.dm | 13 ++-- code/modules/admin/verbs/adminjump.dm | 2 +- code/modules/awaymissions/maploader/reader.dm | 64 +++++++++++++++---- code/modules/randomMaps/dungeons.dm | 4 +- code/modules/randomMaps/vault_definitions.dm | 10 ++- code/modules/randomMaps/vaults.dm | 8 ++- config-example/config.txt | 4 ++ maps/randomvaults/sokoban.dm | 2 +- 10 files changed, 87 insertions(+), 30 deletions(-) diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index afcac3acc62..ca5677d9679 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -183,6 +183,7 @@ var/skip_minimap_generation = 0 //If 1, don't generate minimaps var/skip_holominimap_generation = 0 //If 1, don't generate holominimaps var/skip_vault_generation = 0 //If 1, don't generate vaults + var/disable_vault_rotation = 0 //If 1, don't load vaults rotated var/shut_up_automatic_diagnostic_and_announcement_system = 0 //If 1, don't play the vox sounds at the start of every shift. var/no_lobby_music = 0 //If 1, don't play lobby music, regardless of client preferences. var/no_ambience = 0 //If 1, don't play ambience, regardless of client preferences. @@ -605,6 +606,8 @@ skip_holominimap_generation = 1 if("skip_vault_generation") skip_vault_generation = 1 + if("disable_vault_rotation") + disable_vault_rotation = 1 if("shut_up_automatic_diagnostic_and_announcement_system") shut_up_automatic_diagnostic_and_announcement_system = 1 if("no_lobby_music") diff --git a/code/datums/map_elements.dm b/code/datums/map_elements.dm index 9505a01fb2a..9143fcdb192 100644 --- a/code/datums/map_elements.dm +++ b/code/datums/map_elements.dm @@ -14,6 +14,8 @@ var/list/datum/map_element/map_elements = list() var/width //Width of the map element, in turfs var/height //Height of the map element, in turfs + var/can_rotate = TRUE //Can this be rotated? + var/rotation = 0 //The map's rotation value /datum/map_element/proc/pre_load() //Called before loading the element return @@ -35,11 +37,12 @@ var/list/datum/map_element/map_elements = list() for(var/atom/A in objects) A.spawned_by_map_element(src, objects) -/datum/map_element/proc/load(x, y, z) +/datum/map_element/proc/load(x, y, z, rotate=0) //Location is always lower left corner. //In some cases, location is set to null (when creating a new z-level, for example) //To account for that, location is set again in maploader's load_map() proc location = locate(x+1, y+1, z) + rotation = rotate if(!can_load(x,y)) return 0 @@ -49,7 +52,7 @@ var/list/datum/map_element/map_elements = list() if(file_path) var/file = file(file_path) if(isfile(file)) - var/list/L = maploader.load_map(file, z, x, y, src) + var/list/L = maploader.load_map(file, z, x, y, src, rotate) initialize(L) return L else //No file specified - empty map element diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 37cb57bd84a..c0cd403f95c 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -1219,10 +1219,15 @@ var/list/admin_verbs_mod = list( return - log_admin("[key_name(src)] is loading [ME.file_path] at [x_coord], [y_coord], [z_coord]") - message_admins("[key_name_admin(src)] is loading [ME.file_path] at [x_coord], [y_coord], [z_coord]") - ME.load(x_coord - 1, y_coord - 1, z_coord) //Reduce X and Y by 1 because these arguments are actually offsets, and they're added to 1;1 in the map loader. Without this, spawning something at 1;1 would result in it getting spawned at 2;2 - message_admins("[ME.file_path] loaded at [ME.location ? formatJumpTo(ME.location) : "[x_coord], [y_coord], [z_coord]"]") + var/rotate = input(usr, "Set the rotation offset: (0, 90, 180 or 270) ", "Map element loading") as null|num + if(rotate == null) + return + var/overwrite = alert("Overwrite original objects in area?","Map element loading","Yes","No") == "Yes" + + log_admin("[key_name(src)] is loading [ME.file_path] at [x_coord], [y_coord], [z_coord] rotated by [rotate] degrees") + message_admins("[key_name_admin(src)] is loading [ME.file_path] at [x_coord], [y_coord], [z_coord] rotated by [rotate] degrees") + ME.load(x_coord - 1, y_coord - 1, z_coord, rotate, overwrite) //Reduce X and Y by 1 because these arguments are actually offsets, and they're added to 1;1 in the map loader. Without this, spawning something at 1;1 would result in it getting spawned at 2;2 + message_admins("[ME.file_path] loaded at [ME.location ? formatJumpTo(ME.location) : "[x_coord], [y_coord], [z_coord]"] rotated by [rotate] degrees") /client/proc/create_awaymission() set category = "Admin" diff --git a/code/modules/admin/verbs/adminjump.dm b/code/modules/admin/verbs/adminjump.dm index fa7342e0946..8d7e7ebd6cd 100644 --- a/code/modules/admin/verbs/adminjump.dm +++ b/code/modules/admin/verbs/adminjump.dm @@ -126,7 +126,7 @@ var/list/vaults = list() for(var/datum/map_element/V in map_elements) - var/name = "[V.type_abbreviation] [V.name ? V.name : V.file_path] @ [V.location ? "[V.location.x],[V.location.y],[V.location.z]" : "UNKNOWN"]" + var/name = "[V.type_abbreviation] [V.name ? V.name : V.file_path] @ [V.location ? "[V.location.x],[V.location.y],[V.location.z][V.rotation ? " (rotated by [V.rotation] degrees)" : ""]" : "UNKNOWN"]" vaults[name] = V diff --git a/code/modules/awaymissions/maploader/reader.dm b/code/modules/awaymissions/maploader/reader.dm index 9364c7d88e7..7ad8883c5ec 100644 --- a/code/modules/awaymissions/maploader/reader.dm +++ b/code/modules/awaymissions/maploader/reader.dm @@ -57,7 +57,13 @@ var/list/map_dimension_cache = list() * A list of all atoms created * */ -/dmm_suite/load_map(var/dmm_file as file, var/z_offset as num, var/x_offset as num, var/y_offset as num, var/datum/map_element/map_element as null) +/dmm_suite/load_map(var/dmm_file as file, var/z_offset as num, var/x_offset as num, var/y_offset as num, var/datum/map_element/map_element as null, var/rotate as num, var/overwrite as num) + if((rotate % 90) != 0) //If not divisible by 90, make it + rotate += (rotate % 90) + + if(!map_element.can_rotate) //Abort rotation if disabled on map element + rotate = 0 + if(!z_offset)//what z_level we are creating the map on z_offset = world.maxz+1 @@ -105,6 +111,12 @@ var/list/map_dimension_cache = list() var/zcrd=-1 var/ycrd=x_offset var/xcrd=y_offset + var/ycrd_rotate=x_offset + var/xcrd_rotate=y_offset + var/ycrd_flip=x_offset + var/xcrd_flip=y_offset + var/ycrd_flip_rotate=y_offset + var/xcrd_flip_rotate=x_offset for(var/zpos=findtext(tfile,"\n(1,1,",lpos,0);zpos!=0;zpos=findtext(tfile,"\n(1,1,",zpos+1,0)) //in case there's several maps to load @@ -118,35 +130,54 @@ var/list/map_dimension_cache = list() //if exceeding the world max x or y, increase it var/x_depth = length(copytext(zgrid,1,findtext(zgrid,"\n",2,0))) //This is the length of an encoded line (like "aaaaaaaaBBBBaaaaccccaaa") + var/y_depth = z_depth / (x_depth+1) //x_depth + 1 because we're counting the '\n' characters in z_depth var/map_width = x_depth / key_len //To get the map's width, divide the length of the line by the length of the key - if(world.maxx < map_width + x_offset) + var/x_check = rotate == 0 || rotate == 180 ? map_width + x_offset : y_depth + y_offset + var/y_check = rotate == 0 || rotate == 180 ? y_depth + y_offset : map_width + x_offset + if(world.maxx < x_check) if(!map.can_enlarge) WARNING("Cancelled load of [map_element] due to map bounds.") return list() - world.maxx = map_width + x_offset + world.maxx = x_check WARNING("Loading [map_element] enlarged the map. New max x = [world.maxx]") - var/y_depth = z_depth / (x_depth+1) //x_depth + 1 because we're counting the '\n' characters in z_depth - if(world.maxy < y_depth + y_offset) + if(world.maxy < y_check) if(!map.can_enlarge) WARNING("Cancelled load of [map_element] due to map bounds.") return list() - world.maxy = y_depth + y_offset + world.maxy = y_check WARNING("Loading [map_element] enlarged the map. New max y = [world.maxy]") //then proceed it line by line, starting from top ycrd = y_offset + y_depth + ycrd_rotate = x_offset + map_width + ycrd_flip = y_offset + 1 + ycrd_flip_rotate = x_offset + 1 for(var/gpos=1;gpos!=0;gpos=findtext(zgrid,"\n",gpos,0)+1) var/grid_line = copytext(zgrid,gpos,findtext(zgrid,"\n",gpos,0)) //fill the current square using the model map xcrd=x_offset + xcrd_rotate=y_offset + xcrd_flip=x_offset + map_width + 1 + xcrd_flip_rotate=y_offset + map_width + 1 for(var/mpos=1;mpos<=x_depth;mpos+=key_len) xcrd++ + xcrd_rotate++ + xcrd_flip-- + xcrd_flip_rotate-- var/model_key = copytext(grid_line,mpos,mpos+key_len) - spawned_atoms |= parse_grid(grid_models[model_key],xcrd,ycrd,zcrd+z_offset) + switch(rotate) + if(0) + spawned_atoms |= parse_grid(grid_models[model_key],xcrd,ycrd,zcrd+z_offset,rotate,overwrite) + if(90) + spawned_atoms |= parse_grid(grid_models[model_key],ycrd_rotate,xcrd_flip_rotate,zcrd+z_offset,rotate,overwrite) + if(180) + spawned_atoms |= parse_grid(grid_models[model_key],xcrd_flip,ycrd_flip,zcrd+z_offset,rotate,overwrite) + if(270) + spawned_atoms |= parse_grid(grid_models[model_key],ycrd_flip_rotate,xcrd_rotate,zcrd+z_offset,rotate,overwrite) if (remove_lag) CHECK_TICK if(map_element) @@ -157,6 +188,9 @@ var/list/map_dimension_cache = list() break ycrd-- + ycrd_rotate-- + ycrd_flip++ + ycrd_flip_rotate++ if(remove_lag) CHECK_TICK @@ -198,7 +232,7 @@ var/list/map_dimension_cache = list() * A list with all spawned atoms * */ -/dmm_suite/proc/parse_grid(var/model as text,var/xcrd as num,var/ycrd as num,var/zcrd as num) +/dmm_suite/proc/parse_grid(var/model as text,var/xcrd as num,var/ycrd as num,var/zcrd as num,var/rotate as num,var/overwrite as num) /*Method parse_grid() - Accepts a text string containing a comma separated list of type paths of the same construction as those contained in a .dmm file, and instantiates them. @@ -286,7 +320,7 @@ var/list/map_dimension_cache = list() last_turf_index++ //instanciate the last /turf - var/turf/T = instance_atom(members[last_turf_index],members_attributes[last_turf_index],xcrd,ycrd,zcrd) + var/turf/T = instance_atom(members[last_turf_index],members_attributes[last_turf_index],xcrd,ycrd,zcrd,rotate) if(first_turf_index != last_turf_index) //More than one turf is present - go from the lowest turf to the turf before the last one var/turf_index = first_turf_index @@ -301,8 +335,12 @@ var/list/map_dimension_cache = list() spawned_atoms.Add(T) //finally instance all remainings objects/mobs + if(overwrite) + var/turf/T_old = locate(xcrd,ycrd,zcrd) + for(var/atom/thing in T_old) + qdel(T_old) for(index=1,index < first_turf_index,index++) - var/atom/new_atom = instance_atom(members[index],members_attributes[index],xcrd,ycrd,zcrd) + var/atom/new_atom = instance_atom(members[index],members_attributes[index],xcrd,ycrd,zcrd,rotate) spawned_atoms.Add(new_atom) return spawned_atoms @@ -312,7 +350,7 @@ var/list/map_dimension_cache = list() //////////////// //Instance an atom at (x,y,z) and gives it the variables in attributes -/dmm_suite/proc/instance_atom(var/path,var/list/attributes, var/x, var/y, var/z) +/dmm_suite/proc/instance_atom(var/path,var/list/attributes, var/x, var/y, var/z, var/rotate) if(!path) return var/atom/instance @@ -325,6 +363,10 @@ var/list/map_dimension_cache = list() else instance = new path (locate(x,y,z))//first preloader pass + // Stolen from shuttlecode but very good to reuse here + if(rotate && instance) + instance.shuttle_rotate(rotate) + if(_preloader && instance)//second preloader pass, for those atoms that don't ..() in New() _preloader.load(instance) diff --git a/code/modules/randomMaps/dungeons.dm b/code/modules/randomMaps/dungeons.dm index 56d3d62027d..cc24de24283 100644 --- a/code/modules/randomMaps/dungeons.dm +++ b/code/modules/randomMaps/dungeons.dm @@ -45,7 +45,7 @@ var/turf/dungeon_area = null #define MAXIMUM_DUNGEON_WIDTH 80 -proc/load_dungeon(dungeon_type) +proc/load_dungeon(dungeon_type, var/rotate = 0) if(!dungeon_area) return 0 @@ -96,6 +96,6 @@ proc/load_dungeon(dungeon_type) existing_dungeons.Add(ME) //Add it now, to prevent issues occuring when two dungeons are loaded at once //Reduce X and Y by 1 because these arguments are actually offsets, and they're added to 1;1 in the map loader. Without this, spawning something at 1;1 would result in it getting spawned at 2;2 - var/result = ME.load(spawn_x - 1, spawn_y - 1, dungeon_area.z) + var/result = ME.load(spawn_x - 1, spawn_y - 1, dungeon_area.z, rotate) return result diff --git a/code/modules/randomMaps/vault_definitions.dm b/code/modules/randomMaps/vault_definitions.dm index 6a9ed1e7677..804fc4f2add 100644 --- a/code/modules/randomMaps/vault_definitions.dm +++ b/code/modules/randomMaps/vault_definitions.dm @@ -106,10 +106,8 @@ var/list/existing_vaults = list() /datum/map_element/vault/spacepond file_path = "maps/randomvaults/spacepond.dmm" -/datum/map_element/vault/spacepond/initialize(list/objects) - ..() - - load_dungeon(/datum/map_element/dungeon/wine_cellar) +/datum/map_element/vault/spacepond/pre_load() + load_dungeon(/datum/map_element/dungeon/wine_cellar,rotation) /datum/map_element/dungeon/wine_cellar file_path = "maps/randomvaults/dungeons/wine_cellar.dmm" @@ -139,7 +137,7 @@ var/list/existing_vaults = list() file_path = "maps/randomvaults/prison_ship.dmm" /datum/map_element/vault/prison/pre_load() - load_dungeon(/datum/map_element/dungeon/prison) + load_dungeon(/datum/map_element/dungeon/prison,rotation) /datum/map_element/dungeon/prison file_path = "maps/randomvaults/dungeons/prison.dmm" @@ -190,7 +188,7 @@ var/list/existing_vaults = list() file_path = "maps/randomvaults/spy_satellite.dmm" /datum/map_element/vault/spy_sat/pre_load() - load_dungeon(/datum/map_element/dungeon/satellite_deployment) + load_dungeon(/datum/map_element/dungeon/satellite_deployment,rotation) /datum/map_element/dungeon/satellite_deployment file_path = "maps/randomvaults/dungeons/satellite_deployment.dmm" diff --git a/code/modules/randomMaps/vaults.dm b/code/modules/randomMaps/vaults.dm index c958a12efe5..75598909683 100644 --- a/code/modules/randomMaps/vaults.dm +++ b/code/modules/randomMaps/vaults.dm @@ -202,16 +202,18 @@ var/vault_x = new_spawn_point.x var/vault_y = new_spawn_point.y var/vault_z = new_spawn_point.z + var/vault_rotate = config.disable_vault_rotation ? 0 : pick(0,90,180,270) if(population_density == POPULATION_SCARCE) var/turf/t1 = locate(max(1, vault_x - MAX_VAULT_WIDTH - 1), max(1, vault_y - MAX_VAULT_HEIGHT - 1), vault_z) var/turf/t2 = locate(vault_x + new_width, vault_y + new_height, vault_z) valid_spawn_points.Remove(block(t1, t2)) - if(ME.load(vault_x, vault_y, vault_z)) + if(ME.load(vault_x, vault_y, vault_z, vault_rotate)) spawned.Add(ME) - message_admins("Loaded [ME.file_path]: [formatJumpTo(locate(vault_x, vault_y, vault_z))].") - + message_admins("Loaded [ME.file_path]: [formatJumpTo(locate(vault_x, vault_y, vault_z))] [config.disable_vault_rotation ? "" : ", rotated by [vault_rotate] degrees"].") + if(config.disable_vault_rotation) + message_admins("[ME.file_path] was not rotated, DISABLE_VAULT_ROTATION enabled in config.") successes++ if(amount > 0) amount-- diff --git a/config-example/config.txt b/config-example/config.txt index ecb6e35494e..762fc50f3a8 100644 --- a/config-example/config.txt +++ b/config-example/config.txt @@ -313,6 +313,10 @@ SKIP_HOLOMINIMAP_GENERATION ## Uncomment to disable generation of vaults (makes the server start faster!) SKIP_VAULT_GENERATION +## DISABLE_VAULT_ROTATION +## Uncomment to disable rotation of vaults +#DISABLE_VAULT_ROTATION + ## SHUT_UP_AUTOMATIC_DIAGNOSTIC_AND_ANNOUNCEMENT_SYSTEM ## Uncomment to disable the lovely robotic voice that tells you the time at the start of every shift. ## Recommended for your sanity if you start the server a lot for testing things. diff --git a/maps/randomvaults/sokoban.dm b/maps/randomvaults/sokoban.dm index 440889f0c52..4e1db21a182 100644 --- a/maps/randomvaults/sokoban.dm +++ b/maps/randomvaults/sokoban.dm @@ -49,7 +49,7 @@ SL.file_path = pick_n_take(src.available_levels) //No duplicate levels SL.parent = src - load_dungeon(SL) + load_dungeon(SL,rotation) loaded_levels.Add(SL) //Load ending