diff --git a/code/__DEFINES/maps.dm b/code/__DEFINES/maps.dm
index cdd2739b3c..b7043d01ff 100644
--- a/code/__DEFINES/maps.dm
+++ b/code/__DEFINES/maps.dm
@@ -21,7 +21,7 @@ Last space-z level = empty
#define CITY_OF_COGS "City of Cogs"
#define EMPTY_AREA_1 "Empty Area 1"
#define EMPTY_AREA_2 "Empty Area 2"
-#define MINING "Mining Asteroid"
+#define MINING_ASTEROID "Mining Asteroid"
#define EMPTY_AREA_3 "Empty Area 3"
#define EMPTY_AREA_4 "Empty Area 4"
#define EMPTY_AREA_5 "Empty Area 5"
@@ -36,11 +36,8 @@ Last space-z level = empty
#define MAP_REMOVE_JOB(jobpath) /datum/job/##jobpath/map_check() { return (SSmapping.config.map_name != JOB_MODIFICATION_MAP_NAME) && ..() }
//zlevel defines, can be overridden for different maps in the appropriate _maps file.
-#define ZLEVEL_CENTCOM 1
#define ZLEVEL_STATION_PRIMARY 2
-#define ZLEVEL_MINING 5
#define ZLEVEL_LAVALAND 5
-#define ZLEVEL_CITYOFCOGS 6
#define ZLEVEL_EMPTY_SPACE 12
//Unless you modify it in map config should be equal to ZLEVEL_SPACEMAX
#define ZLEVEL_TRANSIT 13
@@ -49,3 +46,31 @@ Last space-z level = empty
#define ZLEVEL_SPACEMAX 13
#define SPACERUIN_MAP_EDGE_PAD 15
+#define ZLEVEL_SPACE_RUIN_COUNT 5
+
+// traits
+#define ZTRAIT_CENTCOM "CentCom"
+#define ZTRAIT_STATION "Station"
+#define ZTRAIT_MINING "Mining"
+#define ZTRAIT_REEBE "Reebe"
+#define ZTRAIT_TRANSIT "Transit"
+#define ZTRAIT_AWAY "Away Mission"
+#define ZTRAIT_SPACE_RUINS "Space Ruins"
+#define ZTRAIT_LAVA_RUINS "Lava Ruins"
+#define ZTRAIT_BOMBCAP_MULTIPLIER "Bombcap Multiplier"
+
+// trait definitions
+#define DL_NAME "name"
+#define DL_LINKAGE "linkage"
+#define DL_TRAITS "traits"
+
+#define DECLARE_LEVEL(NAME, LINKAGE, TRAITS) list(DL_NAME = NAME, DL_LINKAGE = LINKAGE, DL_TRAITS = TRAITS)
+// corresponds to basemap.dm
+#define DEFAULT_MAP_TRAITS list(\
+ DECLARE_LEVEL("CentCom", SELFLOOPING, list(ZTRAIT_CENTCOM = TRUE)),\
+ DECLARE_LEVEL("Main Station", CROSSLINKED, list(ZTRAIT_STATION = TRUE)),\
+ DECLARE_LEVEL("Empty Area 1", CROSSLINKED, list(ZTRAIT_SPACE_RUINS = TRUE)),\
+ DECLARE_LEVEL("Empty Area 2", CROSSLINKED, list(ZTRAIT_SPACE_RUINS = TRUE)),\
+ DECLARE_LEVEL("Lavaland", UNAFFECTED, list(ZTRAIT_MINING = TRUE, ZTRAIT_LAVA_RUINS = TRUE, ZTRAIT_BOMBCAP_MULTIPLIER = 3)),\
+ DECLARE_LEVEL("Reebe", UNAFFECTED, list(ZTRAIT_REEBE = TRUE, ZTRAIT_BOMBCAP_MULTIPLIER = 0.5)),\
+)
diff --git a/code/__HELPERS/level_traits.dm b/code/__HELPERS/level_traits.dm
index e2a74cf8e0..4d20c9b513 100644
--- a/code/__HELPERS/level_traits.dm
+++ b/code/__HELPERS/level_traits.dm
@@ -1,17 +1,17 @@
// Helpers for checking whether a z-level conforms to a specific requirement
// Basic levels
-#define is_centcom_level(z) ((z) == ZLEVEL_CENTCOM)
+#define is_centcom_level(z) SSmapping.level_trait(z, ZTRAIT_CENTCOM)
-#define is_station_level(z) ((z) in GLOB.station_z_levels)
+#define is_station_level(z) SSmapping.level_trait(z, ZTRAIT_STATION)
-#define is_mining_level(z) ((z) == ZLEVEL_MINING)
+#define is_mining_level(z) SSmapping.level_trait(z, ZTRAIT_MINING)
-#define is_reebe(z) ((z) == ZLEVEL_CITYOFCOGS)
+#define is_reebe(z) SSmapping.level_trait(z, ZTRAIT_REEBE)
-#define is_transit_level(z) ((z) == ZLEVEL_TRANSIT)
+#define is_transit_level(z) SSmapping.level_trait(z, ZTRAIT_TRANSIT)
-#define is_away_level(z) ((z) > ZLEVEL_SPACEMAX)
+#define is_away_level(z) SSmapping.level_trait(z, ZTRAIT_AWAY)
// If true, the singularity cannot strip away asteroid turf on this Z
#define is_planet_level(z) (GLOB.z_is_planet["z"])
diff --git a/code/_globalvars/lists/mapping.dm b/code/_globalvars/lists/mapping.dm
index 51af2b3525..0d8965d10a 100644
--- a/code/_globalvars/lists/mapping.dm
+++ b/code/_globalvars/lists/mapping.dm
@@ -1,14 +1,7 @@
-#define Z_NORTH 1
-#define Z_EAST 2
-#define Z_SOUTH 3
-#define Z_WEST 4
-
GLOBAL_LIST_INIT(cardinals, list(NORTH, SOUTH, EAST, WEST))
GLOBAL_LIST_INIT(alldirs, list(NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST))
GLOBAL_LIST_INIT(diagonals, list(NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST))
-GLOBAL_LIST_INIT(station_z_levels, list(ZLEVEL_STATION_PRIMARY))
-
GLOBAL_LIST(global_map)
//list/global_map = list(list(1,5),list(4,3))//an array of map Z levels.
//Resulting sector map looks like
diff --git a/code/controllers/subsystem/mapping.dm b/code/controllers/subsystem/mapping.dm
index d13d340b03..8046dab41e 100644
--- a/code/controllers/subsystem/mapping.dm
+++ b/code/controllers/subsystem/mapping.dm
@@ -22,6 +22,9 @@ SUBSYSTEM_DEF(mapping)
var/loading_ruins = FALSE
+ // Z-manager stuff
+ var/list/z_list
+
/datum/controller/subsystem/mapping/PreInit()
if(!config)
#ifdef FORCE_MAP
@@ -39,28 +42,34 @@ SUBSYSTEM_DEF(mapping)
repopulate_sorted_areas()
process_teleport_locs() //Sets up the wizard teleport locations
preloadTemplates()
+
+ // Create space levels
+ for(var/I in 1 to ZLEVEL_SPACE_RUIN_COUNT)
+ add_new_zlevel("Empty Area [2 + I]", CROSSLINKED, list(ZTRAIT_SPACE_RUINS = TRUE))
+ add_new_zlevel("Empty Area [3 + ZLEVEL_SPACE_RUIN_COUNT]", CROSSLINKED, list()) // no ruins
+ add_new_zlevel("Transit", UNAFFECTED, list(ZTRAIT_TRANSIT = TRUE))
+
// Pick a random away mission.
createRandomZlevel()
- // Generate mining.
+ if (z_list.len < world.maxz)
+ add_new_zlevel("Away Mission", UNAFFECTED, list(ZTRAIT_AWAY = TRUE))
+
+ // Generate mining ruins
loading_ruins = TRUE
- var/mining_type = config.minetype
- if (mining_type == "lavaland")
- seedRuins(list(ZLEVEL_LAVALAND), CONFIG_GET(number/lavaland_budget), /area/lavaland/surface/outdoors/unexplored, lava_ruins_templates)
- spawn_rivers()
+ var/list/lava_ruins = levels_by_trait(ZTRAIT_LAVA_RUINS)
+ if (lava_ruins.len)
+ seedRuins(lava_ruins, CONFIG_GET(number/lavaland_budget), /area/lavaland/surface/outdoors/unexplored, lava_ruins_templates)
+ for (var/lava_z in lava_ruins)
+ spawn_rivers(lava_z)
- // deep space ruins
- var/space_zlevels = list()
- for(var/i in ZLEVEL_SPACEMIN to ZLEVEL_SPACEMAX)
- switch(i)
- if(ZLEVEL_MINING, ZLEVEL_LAVALAND, ZLEVEL_EMPTY_SPACE, ZLEVEL_TRANSIT, ZLEVEL_CITYOFCOGS)
- continue
- else
- space_zlevels += i
-
- seedRuins(space_zlevels, CONFIG_GET(number/space_budget), /area/space, space_ruins_templates)
+ // Generate deep space ruins
+ var/list/space_ruins = levels_by_trait(ZTRAIT_SPACE_RUINS)
+ if (space_ruins.len)
+ seedRuins(space_ruins, CONFIG_GET(number/space_budget), /area/space, space_ruins_templates)
loading_ruins = FALSE
+
repopulate_sorted_areas()
- // Set up Z-level transistions.
+ // Set up Z-level transitions.
setup_map_transitions()
generate_station_area_list()
..()
@@ -98,6 +107,8 @@ SUBSYSTEM_DEF(mapping)
config = SSmapping.config
next_map_config = SSmapping.next_map_config
+ z_list = SSmapping.z_list
+
/datum/controller/subsystem/mapping/proc/TryLoadZ(filename, errorList, forceLevel, last)
var/static/dmm_suite/loader
if(!loader)
@@ -107,11 +118,6 @@ SUBSYSTEM_DEF(mapping)
if(last)
QDEL_NULL(loader)
-/datum/controller/subsystem/mapping/proc/CreateSpace(MaxZLevel)
- while(world.maxz < MaxZLevel)
- ++world.maxz
- CHECK_TICK
-
#define INIT_ANNOUNCE(X) to_chat(world, "[X]"); log_world(X)
/datum/controller/subsystem/mapping/proc/loadWorld()
//if any of these fail, something has gone horribly, HORRIBLY, wrong
@@ -121,6 +127,7 @@ SUBSYSTEM_DEF(mapping)
INIT_ANNOUNCE("Loading [config.map_name]...")
TryLoadZ(config.GetFullMapPath(), FailedZs, ZLEVEL_STATION_PRIMARY)
+ InitializeDefaultZLevels()
INIT_ANNOUNCE("Loaded station in [(REALTIMEOFDAY - start_time)/10]s!")
if(SSdbcore.Connect())
var/datum/DBQuery/query_round_map_name = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET map_name = '[config.map_name]' WHERE id = [GLOB.round_id]")
@@ -129,8 +136,6 @@ SUBSYSTEM_DEF(mapping)
if(config.minetype != "lavaland")
INIT_ANNOUNCE("WARNING: A map without lavaland set as its minetype was loaded! This is being ignored! Update the maploader code!")
- CreateSpace(ZLEVEL_SPACEMAX)
-
if(LAZYLEN(FailedZs)) //but seriously, unless the server's filesystem is messed up this will never happen
var/msg = "RED ALERT! The following map files failed to load: [FailedZs[1]]"
if(FailedZs.len > 1)
diff --git a/code/controllers/subsystem/minimap.dm b/code/controllers/subsystem/minimap.dm
index 4bead36bb1..fa9821b85b 100644
--- a/code/controllers/subsystem/minimap.dm
+++ b/code/controllers/subsystem/minimap.dm
@@ -5,9 +5,10 @@ SUBSYSTEM_DEF(minimap)
var/const/MINIMAP_SIZE = 2048
var/const/TILE_SIZE = 8
- var/list/z_levels = list(ZLEVEL_STATION_PRIMARY)
+ var/list/z_levels
/datum/controller/subsystem/minimap/Initialize(timeofday)
+ z_levels = SSmapping.levels_by_trait(ZTRAIT_STATION)
var/hash = md5(SSmapping.config.GetFullMapPath())
if(CONFIG_GET(flag/generate_minimaps))
if(hash == trim(file2text(hash_path())))
diff --git a/code/controllers/subsystem/squeak.dm b/code/controllers/subsystem/squeak.dm
index 022b659d8a..861bb0d8d5 100644
--- a/code/controllers/subsystem/squeak.dm
+++ b/code/controllers/subsystem/squeak.dm
@@ -34,7 +34,7 @@ SUBSYSTEM_DEF(squeak)
/datum/controller/subsystem/squeak/proc/find_exposed_wires()
exposed_wires.Cut()
var/list/all_turfs
- for (var/z in GLOB.station_z_levels)
+ for (var/z in SSmapping.levels_by_trait(ZTRAIT_STATION))
all_turfs += block(locate(1,1,z), locate(world.maxx,world.maxy,z))
for(var/turf/open/floor/plating/T in all_turfs)
if(is_blocked_turf(T))
diff --git a/code/datums/explosion.dm b/code/datums/explosion.dm
index 9b5cd18774..0616d0e4c6 100644
--- a/code/datums/explosion.dm
+++ b/code/datums/explosion.dm
@@ -1,6 +1,4 @@
#define EXPLOSION_THROW_SPEED 4
-#define CITYOFCOGS_CAP_MULTIPLIER 0.5
-#define MINING_CAP_MULTIPLIER 3
GLOBAL_LIST_EMPTY(explosions)
//Against my better judgement, I will return the explosion datum
@@ -54,17 +52,14 @@ GLOBAL_LIST_EMPTY(explosions)
var/orig_dev_range = devastation_range
var/orig_heavy_range = heavy_impact_range
var/orig_light_range = light_impact_range
-
+
var/orig_max_distance = max(devastation_range, heavy_impact_range, light_impact_range, flash_range, flame_range)
-
+
//Zlevel specific bomb cap multiplier
- var/cap_multiplier = 1
- switch(epicenter.z)
- if(ZLEVEL_CITYOFCOGS)
- cap_multiplier = CITYOFCOGS_CAP_MULTIPLIER
- if(ZLEVEL_MINING)
- cap_multiplier = MINING_CAP_MULTIPLIER
-
+ var/cap_multiplier = SSmapping.level_trait(epicenter.z, ZTRAIT_BOMBCAP_MULTIPLIER)
+ if (isnull(cap_multiplier))
+ cap_multiplier = 1
+
if(!ignorecap)
devastation_range = min(GLOB.MAX_EX_DEVESTATION_RANGE * cap_multiplier, devastation_range)
heavy_impact_range = min(GLOB.MAX_EX_HEAVY_RANGE * cap_multiplier, heavy_impact_range)
@@ -91,11 +86,11 @@ GLOBAL_LIST_EMPTY(explosions)
if(adminlog)
message_admins("Explosion with size ([devastation_range], [heavy_impact_range], [light_impact_range], [flame_range]) in area: [epi_area] [ADMIN_COORDJMP(epicenter)]")
log_game("Explosion with size ([devastation_range], [heavy_impact_range], [light_impact_range], [flame_range]) in area [epicenter.loc.name] ([epicenter.x],[epicenter.y],[epicenter.z])")
-
+
var/x0 = epicenter.x
var/y0 = epicenter.y
var/z0 = epicenter.z
-
+
SSblackbox.record_feedback("associative", "explosion", 1, list("dev" = devastation_range, "heavy" = heavy_impact_range, "light" = light_impact_range, "flash" = flash_range, "flame" = flame_range, "orig_dev" = orig_dev_range, "orig_heavy" = orig_heavy_range, "orig_light" = orig_light_range, "x" = x0, "y" = y0, "z" = z0, "area" = epi_area.type))
// Play sounds; we want sounds to be different depending on distance so we will manually do it ourselves.
@@ -199,7 +194,7 @@ GLOBAL_LIST_EMPTY(explosions)
//------- EX_ACT AND TURF FIRES -------
if(T == epicenter) // Ensures explosives detonating from bags trigger other explosives in that bag
- var/list/items = list()
+ var/list/items = list()
for(var/I in T)
var/atom/A = I
items += A.GetAllContents()
@@ -418,6 +413,3 @@ GLOBAL_LIST_EMPTY(explosions)
// 10 explosion power is a (1, 3, 6) explosion.
// 5 explosion power is a (0, 1, 3) explosion.
// 1 explosion power is a (0, 0, 1) explosion.
-
-#undef CITYOFCOGS_CAP_MULTIPLIER
-#undef MINING_CAP_MULTIPLIER
diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm
index c030bd4c2a..f90d6eed32 100644
--- a/code/datums/helper_datums/teleport.dm
+++ b/code/datums/helper_datums/teleport.dm
@@ -166,9 +166,12 @@
// Safe location finder
-/proc/find_safe_turf(zlevel = ZLEVEL_STATION_PRIMARY, list/zlevels, extended_safety_checks = FALSE)
+/proc/find_safe_turf(zlevel, list/zlevels, extended_safety_checks = FALSE)
if(!zlevels)
- zlevels = list(zlevel)
+ if (zlevel)
+ zlevels = list(zlevel)
+ else
+ zlevels = SSmapping.levels_by_trait(ZTRAIT_STATION)
var/cycles = 1000
for(var/cycle in 1 to cycles)
// DRUNK DIALLING WOOOOOOOOO
diff --git a/code/datums/position_point_vector.dm b/code/datums/position_point_vector.dm
index 2a98d57efd..6fae092c9d 100644
--- a/code/datums/position_point_vector.dm
+++ b/code/datums/position_point_vector.dm
@@ -1,239 +1,239 @@
-//Designed for things that need precision trajectories like projectiles.
-//Don't use this for anything that you don't absolutely have to use this with (like projectiles!) because it isn't worth using a datum unless you need accuracy down to decimal places in pixels.
-
-#define RETURN_PRECISE_POSITION(A) new /datum/position(A)
-#define RETURN_PRECISE_POINT(A) new /datum/point(A)
-
-/datum/position //For positions with map x/y/z and pixel x/y so you don't have to return lists. Could use addition/subtraction in the future I guess.
- var/x = 0
- var/y = 0
- var/z = 0
- var/pixel_x = 0
- var/pixel_y = 0
-
-/datum/position/proc/valid()
- return x && y && z && !isnull(pixel_x) && !isnull(pixel_y)
-
-/datum/position/New(_x = 0, _y = 0, _z = 0, _pixel_x = 0, _pixel_y = 0) //first argument can also be a /datum/point.
- if(istype(_x, /datum/point))
- var/datum/point/P = _x
- var/turf/T = P.return_turf()
- _x = T.x
- _y = T.y
- _z = T.z
- _pixel_x = P.return_px()
- _pixel_y = P.return_py()
- else if(isatom(_x))
- var/atom/A = _x
- _x = A.x
- _y = A.y
- _z = A.z
- _pixel_x = A.pixel_x
- _pixel_y = A.pixel_y
- x = _x
- y = _y
- z = _z
- pixel_x = _pixel_x
- pixel_y = _pixel_y
-
-/datum/position/proc/return_turf()
- return locate(x, y, z)
-
-/datum/position/proc/return_px()
- return pixel_x
-
-/datum/position/proc/return_py()
- return pixel_y
-
-/datum/position/proc/return_point()
- return new /datum/point(src)
-
-/proc/point_midpoint_points(datum/point/a, datum/point/b) //Obviously will not support multiZ calculations! Same for the two below.
- var/datum/point/P = new
- P.x = round(a.x + (b.x - a.x) / 2, 1)
- P.y = round(a.y + (b.y - a.y) / 2, 1)
- P.z = a.z
- return P
-
-/proc/pixel_length_between_points(datum/point/a, datum/point/b)
- return sqrt(((b.x - a.x) ** 2) + ((b.y - a.y) ** 2))
-
-/proc/angle_between_points(datum/point/a, datum/point/b)
- return ATAN2((b.y - a.y), (b.x - a.x))
-
-/datum/point //A precise point on the map in absolute pixel locations based on world.icon_size. Pixels are FROM THE EDGE OF THE MAP!
- var/x = 0
- var/y = 0
- var/z = 0
-
-/datum/point/proc/valid()
- return x && y && z
-
-/datum/point/proc/copy_to(datum/point/p = new)
- p.x = x
- p.y = y
- p.z = z
- return p
-
-/datum/point/New(_x, _y, _z, _pixel_x = 0, _pixel_y = 0) //first argument can also be a /datum/position or /atom.
- if(istype(_x, /datum/position))
- var/datum/position/P = _x
- _x = P.x
- _y = P.y
- _z = P.z
- _pixel_x = P.pixel_x
- _pixel_y = P.pixel_y
- else if(istype(_x, /atom))
- var/atom/A = _x
- _x = A.x
- _y = A.y
- _z = A.z
- _pixel_x = A.pixel_x
- _pixel_y = A.pixel_y
- initialize_location(_x, _y, _z, _pixel_x, _pixel_y)
-
-/datum/point/proc/initialize_location(tile_x, tile_y, tile_z, p_x = 0, p_y = 0)
- if(!isnull(tile_x))
- x = ((tile_x - 1) * world.icon_size) + world.icon_size / 2 + p_x
- if(!isnull(tile_y))
- y = ((tile_y - 1) * world.icon_size) + world.icon_size / 2+ p_y
- if(!isnull(tile_z))
- z = tile_z
-
-/datum/point/proc/return_turf()
- return locate(CEILING(x / world.icon_size, 1), CEILING(y / world.icon_size, 1), z)
-
-/datum/point/proc/return_coordinates() //[turf_x, turf_y, z]
- return list(CEILING(x / world.icon_size, 1), CEILING(y / world.icon_size, 1), z)
-
-/datum/point/proc/return_position()
- return new /datum/position(src)
-
-/datum/point/proc/return_px()
- return MODULUS(x, world.icon_size) - 16
-
-/datum/point/proc/return_py()
- return MODULUS(y, world.icon_size) - 16
-
-/datum/point/proc/mapcheck()
- . = FALSE
- var/maxx = world.icon_size * world.maxx
- var/maxy = world.icon_size * world.maxy
- var/move_zx = 0
- var/move_zy = 0
- if(x < 0)
- x += maxx
- move_zx -= 1
- if(y < 0)
- y += maxy
- move_zy -= 1
- if(x > maxx)
- x -= maxx
- move_zx += 1
- if(y > maxy)
- y -= maxy
- move_zy += 1
- var/datum/space_level/S = GLOB.z_levels_list["[z]"]
- if(move_zx != 0)
- var/datum/space_level/L = S.neigbours["[move_zx < 0? WEST : EAST]"]
- z = L.z_value
- . = TRUE
- if(move_zy != 0)
- var/datum/space_level/L = S.neigbours["[move_zy < 0? SOUTH : NORTH]"]
- z = L.z_value
- . = TRUE
-
-/datum/point/vector
- var/speed = 32 //pixels per iteration
- var/iteration = 0
- var/angle = 0
- var/mpx = 0 //calculated x/y movement amounts to prevent having to do trig every step.
- var/mpy = 0
- var/starting_x = 0 //just like before, pixels from EDGE of map! This is set in initialize_location().
- var/starting_y = 0
- var/starting_z = 0
-
-/datum/point/vector/New(_x, _y, _z, _pixel_x = 0, _pixel_y = 0, _angle, _speed)
- ..()
- initialize_trajectory(_speed, _angle)
-
-/datum/point/vector/initialize_location(tile_x, tile_y, tile_z, p_x = 0, p_y = 0)
- . = ..()
- starting_x = x
- starting_y = y
- starting_z = z
-
-/datum/point/vector/copy_to(datum/point/vector/v = new)
- ..(v)
- v.speed = speed
- v.iteration = iteration
- v.angle = angle
- v.mpx = mpx
- v.mpy = mpy
- v.starting_x = starting_x
- v.starting_y = starting_y
- v.starting_z = starting_z
- return v
-
-/datum/point/vector/proc/initialize_trajectory(pixel_speed, new_angle)
- if(!isnull(pixel_speed))
- speed = pixel_speed
- set_angle(new_angle)
-
-/datum/point/vector/proc/set_angle(new_angle) //calculations use "byond angle" where north is 0 instead of 90, and south is 180 instead of 270.
- if(isnull(angle))
- return
- angle = new_angle
- update_offsets()
-
-/datum/point/vector/proc/update_offsets()
- mpx = sin(angle) * speed
- mpy = cos(angle) * speed
-
-/datum/point/vector/proc/set_speed(new_speed)
- if(isnull(new_speed) || speed == new_speed)
- return
- speed = new_speed
- update_offsets()
-
-/datum/point/vector/proc/increment(multiplier = 1)
- iteration++
- x += mpx * 1
- y += mpy * 1
- if(mapcheck())
- on_z_change()
-
-/datum/point/vector/proc/return_vector_after_increments(amount = 7, multiplier = 1, force_simulate = FALSE)
- var/datum/point/vector/v = copy_to()
- if(force_simulate)
- for(var/i in 1 to amount)
- v.increment(multiplier)
- else
- v.increment(multiplier * amount)
- return v
-
-/datum/point/vector/proc/on_z_change()
- return
-
-/datum/point/vector/processed //pixel_speed is per decisecond.
- var/last_process = 0
- var/last_move = 0
- var/paused = FALSE
-
-/datum/point/vector/processed/Destroy()
- STOP_PROCESSING(SSprojectiles, src)
-
-/datum/point/vector/processed/proc/start()
- last_process = world.time
- last_move = world.time
- START_PROCESSING(SSprojectiles, src)
-
-/datum/point/vector/processed/process()
- if(paused)
- last_move += world.time - last_process
- last_process = world.time
- return
- var/needed_time = world.time - last_move
- last_process = world.time
- last_move = world.time
- increment(needed_time)
+//Designed for things that need precision trajectories like projectiles.
+//Don't use this for anything that you don't absolutely have to use this with (like projectiles!) because it isn't worth using a datum unless you need accuracy down to decimal places in pixels.
+
+#define RETURN_PRECISE_POSITION(A) new /datum/position(A)
+#define RETURN_PRECISE_POINT(A) new /datum/point(A)
+
+/datum/position //For positions with map x/y/z and pixel x/y so you don't have to return lists. Could use addition/subtraction in the future I guess.
+ var/x = 0
+ var/y = 0
+ var/z = 0
+ var/pixel_x = 0
+ var/pixel_y = 0
+
+/datum/position/proc/valid()
+ return x && y && z && !isnull(pixel_x) && !isnull(pixel_y)
+
+/datum/position/New(_x = 0, _y = 0, _z = 0, _pixel_x = 0, _pixel_y = 0) //first argument can also be a /datum/point.
+ if(istype(_x, /datum/point))
+ var/datum/point/P = _x
+ var/turf/T = P.return_turf()
+ _x = T.x
+ _y = T.y
+ _z = T.z
+ _pixel_x = P.return_px()
+ _pixel_y = P.return_py()
+ else if(isatom(_x))
+ var/atom/A = _x
+ _x = A.x
+ _y = A.y
+ _z = A.z
+ _pixel_x = A.pixel_x
+ _pixel_y = A.pixel_y
+ x = _x
+ y = _y
+ z = _z
+ pixel_x = _pixel_x
+ pixel_y = _pixel_y
+
+/datum/position/proc/return_turf()
+ return locate(x, y, z)
+
+/datum/position/proc/return_px()
+ return pixel_x
+
+/datum/position/proc/return_py()
+ return pixel_y
+
+/datum/position/proc/return_point()
+ return new /datum/point(src)
+
+/proc/point_midpoint_points(datum/point/a, datum/point/b) //Obviously will not support multiZ calculations! Same for the two below.
+ var/datum/point/P = new
+ P.x = round(a.x + (b.x - a.x) / 2, 1)
+ P.y = round(a.y + (b.y - a.y) / 2, 1)
+ P.z = a.z
+ return P
+
+/proc/pixel_length_between_points(datum/point/a, datum/point/b)
+ return sqrt(((b.x - a.x) ** 2) + ((b.y - a.y) ** 2))
+
+/proc/angle_between_points(datum/point/a, datum/point/b)
+ return ATAN2((b.y - a.y), (b.x - a.x))
+
+/datum/point //A precise point on the map in absolute pixel locations based on world.icon_size. Pixels are FROM THE EDGE OF THE MAP!
+ var/x = 0
+ var/y = 0
+ var/z = 0
+
+/datum/point/proc/valid()
+ return x && y && z
+
+/datum/point/proc/copy_to(datum/point/p = new)
+ p.x = x
+ p.y = y
+ p.z = z
+ return p
+
+/datum/point/New(_x, _y, _z, _pixel_x = 0, _pixel_y = 0) //first argument can also be a /datum/position or /atom.
+ if(istype(_x, /datum/position))
+ var/datum/position/P = _x
+ _x = P.x
+ _y = P.y
+ _z = P.z
+ _pixel_x = P.pixel_x
+ _pixel_y = P.pixel_y
+ else if(istype(_x, /atom))
+ var/atom/A = _x
+ _x = A.x
+ _y = A.y
+ _z = A.z
+ _pixel_x = A.pixel_x
+ _pixel_y = A.pixel_y
+ initialize_location(_x, _y, _z, _pixel_x, _pixel_y)
+
+/datum/point/proc/initialize_location(tile_x, tile_y, tile_z, p_x = 0, p_y = 0)
+ if(!isnull(tile_x))
+ x = ((tile_x - 1) * world.icon_size) + world.icon_size / 2 + p_x
+ if(!isnull(tile_y))
+ y = ((tile_y - 1) * world.icon_size) + world.icon_size / 2+ p_y
+ if(!isnull(tile_z))
+ z = tile_z
+
+/datum/point/proc/return_turf()
+ return locate(CEILING(x / world.icon_size, 1), CEILING(y / world.icon_size, 1), z)
+
+/datum/point/proc/return_coordinates() //[turf_x, turf_y, z]
+ return list(CEILING(x / world.icon_size, 1), CEILING(y / world.icon_size, 1), z)
+
+/datum/point/proc/return_position()
+ return new /datum/position(src)
+
+/datum/point/proc/return_px()
+ return MODULUS(x, world.icon_size) - 16
+
+/datum/point/proc/return_py()
+ return MODULUS(y, world.icon_size) - 16
+
+/datum/point/proc/mapcheck()
+ . = FALSE
+ var/maxx = world.icon_size * world.maxx
+ var/maxy = world.icon_size * world.maxy
+ var/move_zx = 0
+ var/move_zy = 0
+ if(x < 0)
+ x += maxx
+ move_zx -= 1
+ if(y < 0)
+ y += maxy
+ move_zy -= 1
+ if(x > maxx)
+ x -= maxx
+ move_zx += 1
+ if(y > maxy)
+ y -= maxy
+ move_zy += 1
+ var/datum/space_level/S = SSmapping.get_level(z)
+ if(move_zx != 0)
+ var/datum/space_level/L = S.neigbours["[move_zx < 0? WEST : EAST]"]
+ z = L.z_value
+ . = TRUE
+ if(move_zy != 0)
+ var/datum/space_level/L = S.neigbours["[move_zy < 0? SOUTH : NORTH]"]
+ z = L.z_value
+ . = TRUE
+
+/datum/point/vector
+ var/speed = 32 //pixels per iteration
+ var/iteration = 0
+ var/angle = 0
+ var/mpx = 0 //calculated x/y movement amounts to prevent having to do trig every step.
+ var/mpy = 0
+ var/starting_x = 0 //just like before, pixels from EDGE of map! This is set in initialize_location().
+ var/starting_y = 0
+ var/starting_z = 0
+
+/datum/point/vector/New(_x, _y, _z, _pixel_x = 0, _pixel_y = 0, _angle, _speed)
+ ..()
+ initialize_trajectory(_speed, _angle)
+
+/datum/point/vector/initialize_location(tile_x, tile_y, tile_z, p_x = 0, p_y = 0)
+ . = ..()
+ starting_x = x
+ starting_y = y
+ starting_z = z
+
+/datum/point/vector/copy_to(datum/point/vector/v = new)
+ ..(v)
+ v.speed = speed
+ v.iteration = iteration
+ v.angle = angle
+ v.mpx = mpx
+ v.mpy = mpy
+ v.starting_x = starting_x
+ v.starting_y = starting_y
+ v.starting_z = starting_z
+ return v
+
+/datum/point/vector/proc/initialize_trajectory(pixel_speed, new_angle)
+ if(!isnull(pixel_speed))
+ speed = pixel_speed
+ set_angle(new_angle)
+
+/datum/point/vector/proc/set_angle(new_angle) //calculations use "byond angle" where north is 0 instead of 90, and south is 180 instead of 270.
+ if(isnull(angle))
+ return
+ angle = new_angle
+ update_offsets()
+
+/datum/point/vector/proc/update_offsets()
+ mpx = sin(angle) * speed
+ mpy = cos(angle) * speed
+
+/datum/point/vector/proc/set_speed(new_speed)
+ if(isnull(new_speed) || speed == new_speed)
+ return
+ speed = new_speed
+ update_offsets()
+
+/datum/point/vector/proc/increment(multiplier = 1)
+ iteration++
+ x += mpx * 1
+ y += mpy * 1
+ if(mapcheck())
+ on_z_change()
+
+/datum/point/vector/proc/return_vector_after_increments(amount = 7, multiplier = 1, force_simulate = FALSE)
+ var/datum/point/vector/v = copy_to()
+ if(force_simulate)
+ for(var/i in 1 to amount)
+ v.increment(multiplier)
+ else
+ v.increment(multiplier * amount)
+ return v
+
+/datum/point/vector/proc/on_z_change()
+ return
+
+/datum/point/vector/processed //pixel_speed is per decisecond.
+ var/last_process = 0
+ var/last_move = 0
+ var/paused = FALSE
+
+/datum/point/vector/processed/Destroy()
+ STOP_PROCESSING(SSprojectiles, src)
+
+/datum/point/vector/processed/proc/start()
+ last_process = world.time
+ last_move = world.time
+ START_PROCESSING(SSprojectiles, src)
+
+/datum/point/vector/processed/process()
+ if(paused)
+ last_move += world.time - last_process
+ last_process = world.time
+ return
+ var/needed_time = world.time - last_move
+ last_process = world.time
+ last_move = world.time
+ increment(needed_time)
diff --git a/code/game/gamemodes/blob/blob_report.dm b/code/game/gamemodes/blob/blob_report.dm
index 6585da0ba4..4385b732c3 100644
--- a/code/game/gamemodes/blob/blob_report.dm
+++ b/code/game/gamemodes/blob/blob_report.dm
@@ -8,7 +8,7 @@
var/mach = 0
/datum/station_state/proc/count()
- for(var/Z in GLOB.station_z_levels)
+ for(var/Z in SSmapping.levels_by_trait(ZTRAIT_STATION))
for(var/turf/T in block(locate(1,1,Z), locate(world.maxx,world.maxy,Z)))
// don't count shuttles since they may have just left
if(istype(T.loc, /area/shuttle))
diff --git a/code/game/gamemodes/cit_objectives.dm b/code/game/gamemodes/cit_objectives.dm
index faa376571c..fbcfc675e3 100644
--- a/code/game/gamemodes/cit_objectives.dm
+++ b/code/game/gamemodes/cit_objectives.dm
@@ -98,9 +98,9 @@
for(var/mob/living/carbon/C in GLOB.mob_list)
var/mob/living/simple_animal/borer/D = C.has_brain_worms()
var/turf/location = get_turf(C)
- if(location.z == ZLEVEL_CENTCOM && D && D.stat != DEAD)
+ if(is_centcom_level(location.z) && D && D.stat != DEAD)
total_borer_hosts++
if(target_amount <= total_borer_hosts)
return TRUE
else
- return FALSE
\ No newline at end of file
+ return FALSE
diff --git a/code/game/gamemodes/meteor/meteors.dm b/code/game/gamemodes/meteor/meteors.dm
index 9e0c965271..13ab7a8bb1 100644
--- a/code/game/gamemodes/meteor/meteors.dm
+++ b/code/game/gamemodes/meteor/meteors.dm
@@ -31,7 +31,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
var/max_i = 10//number of tries to spawn meteor.
while(!isspaceturf(pickedstart))
var/startSide = pick(GLOB.cardinals)
- var/startZ = pick(GLOB.station_z_levels)
+ var/startZ = pick(SSmapping.levels_by_trait(ZTRAIT_STATION))
pickedstart = spaceDebrisStartLoc(startSide, startZ)
pickedgoal = spaceDebrisFinishLoc(startSide, startZ)
max_i--
@@ -40,7 +40,6 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
var/Me = pickweight(meteortypes)
var/obj/effect/meteor/M = new Me(pickedstart, pickedgoal)
M.dest = pickedgoal
- M.z_original = M.z
/proc/spaceDebrisStartLoc(startSide, Z)
var/starty
@@ -95,7 +94,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
pass_flags = PASSTABLE
var/heavy = 0
var/meteorsound = 'sound/effects/meteorimpact.ogg'
- var/z_original = ZLEVEL_STATION_PRIMARY
+ var/z_original
var/threat = 0 // used for determining which meteors are most interesting
var/lifetime = DEFAULT_METEOR_LIFETIME
var/timerid = null
@@ -126,6 +125,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust)) //for space dust event
/obj/effect/meteor/Initialize(mapload, target)
. = ..()
+ z_original = z
GLOB.meteor_list += src
SSaugury.register_doom(src, threat)
SpinAnimation()
diff --git a/code/game/machinery/computer/camera_advanced.dm b/code/game/machinery/computer/camera_advanced.dm
index 7c790f63de..f007050560 100644
--- a/code/game/machinery/computer/camera_advanced.dm
+++ b/code/game/machinery/computer/camera_advanced.dm
@@ -17,7 +17,7 @@
/obj/machinery/computer/camera_advanced/Initialize()
. = ..()
if(station_lock_override)
- z_lock = GLOB.station_z_levels.Copy()
+ z_lock = SSmapping.levels_by_trait(ZTRAIT_STATION)
/obj/machinery/computer/camera_advanced/syndie
icon_keyboard = "syndie_key"
diff --git a/code/game/turfs/simulated/river.dm b/code/game/turfs/simulated/river.dm
index a5753edcd1..79846791d3 100644
--- a/code/game/turfs/simulated/river.dm
+++ b/code/game/turfs/simulated/river.dm
@@ -4,7 +4,7 @@
#define RANDOM_LOWER_X 50
#define RANDOM_LOWER_Y 50
-/proc/spawn_rivers(target_z = ZLEVEL_LAVALAND, nodes = 4, turf_type = /turf/open/lava/smooth/lava_land_surface, whitelist_area = /area/lavaland/surface/outdoors, min_x = RANDOM_LOWER_X, min_y = RANDOM_LOWER_Y, max_x = RANDOM_UPPER_X, max_y = RANDOM_UPPER_Y)
+/proc/spawn_rivers(target_z, nodes = 4, turf_type = /turf/open/lava/smooth/lava_land_surface, whitelist_area = /area/lavaland/surface/outdoors, min_x = RANDOM_LOWER_X, min_y = RANDOM_LOWER_Y, max_x = RANDOM_UPPER_X, max_y = RANDOM_UPPER_Y)
var/list/river_nodes = list()
var/num_spawned = 0
while(num_spawned < nodes)
diff --git a/code/modules/admin/verbs/mapping.dm b/code/modules/admin/verbs/mapping.dm
index ce50378f4d..fac7d93f9a 100644
--- a/code/modules/admin/verbs/mapping.dm
+++ b/code/modules/admin/verbs/mapping.dm
@@ -45,7 +45,8 @@ GLOBAL_LIST_INIT(admin_verbs_debug_mapping, list(
/client/proc/start_line_profiling,
/client/proc/stop_line_profiling,
/client/proc/show_line_profiling,
- /client/proc/create_mapping_job_icons
+ /client/proc/create_mapping_job_icons,
+ /client/proc/debug_z_levels,
))
/obj/effect/debugging/mapfix_marker
@@ -292,4 +293,64 @@ GLOBAL_VAR_INIT(say_disabled, FALSE)
//Also add the x
for(var/x_number in 1 to 4)
final.Insert(icon('icons/mob/screen_gen.dmi', "x[x_number == 1 ? "" : x_number]"), "x[x_number == 1 ? "" : x_number]")
- fcopy(final, "icons/mob/landmarks.dmi")
\ No newline at end of file
+ fcopy(final, "icons/mob/landmarks.dmi")
+
+/client/proc/debug_z_levels()
+ set name = "Debug Z-Levels"
+ set category = "Mapping"
+
+ var/list/z_list = SSmapping.z_list
+ var/list/messages = list()
+ messages += "World: [world.maxx] x [world.maxy] x [world.maxz]
"
+
+ var/list/linked_levels = list()
+ var/min_x = INFINITY
+ var/min_y = INFINITY
+ var/max_x = -INFINITY
+ var/max_y = -INFINITY
+
+ for(var/z in 1 to max(world.maxz, z_list.len))
+ if (z > z_list.len)
+ messages += "[z]: Unmanaged (out of bounds)
"
+ continue
+ var/datum/space_level/S = z_list[z]
+ if (!S)
+ messages += "[z]: Unmanaged (null)
"
+ continue
+ var/linkage
+ switch (S.linkage)
+ if (UNAFFECTED)
+ linkage = "no linkage"
+ if (SELFLOOPING)
+ linkage = "self-looping"
+ if (CROSSLINKED)
+ linkage = "linked at ([S.xi], [S.yi])"
+ linked_levels += S
+ min_x = min(min_x, S.xi)
+ min_y = min(min_y, S.yi)
+ max_x = max(max_x, S.xi)
+ max_y = max(max_y, S.yi)
+ else
+ linkage = "unknown linkage '[S.linkage]'"
+
+ messages += "[z]: [S.name], [linkage], traits: [json_encode(S.traits)]
"
+ if (S.z_value != z)
+ messages += "-- z_value is [S.z_value], should be [z]
"
+ if (S.name == initial(S.name))
+ messages += "-- name not set
"
+ if (z > world.maxz)
+ messages += "-- exceeds max z"
+
+ var/grid[max_x - min_x + 1][max_y - min_y + 1]
+ for(var/datum/space_level/S in linked_levels)
+ grid[S.xi - min_x + 1][S.yi - min_y + 1] = S.z_value
+
+ messages += "
| [part.Join(" | ")] |