From c5b95ff5d0174642c58d1a5c37364a0722f57070 Mon Sep 17 00:00:00 2001
From: kevinz000 <2003111+kevinz000@users.noreply.github.com>
Date: Wed, 15 Jan 2020 18:22:43 -0700
Subject: [PATCH] Mapping helpers
---
.../_mapping_helpers.dm} | 87 -----
.../mapping/mapping_helpers/baseturf.dm | 82 +++++
.../network_builder/_network_builder.dm | 38 ++
.../network_builder/atmos_pipe.dm | 159 ++++++++
.../network_builder/power_cables.dm | 97 +++++
code/modules/mapping/ruins.dm | 342 +++++++++---------
.../space_management/space_reservation.dm | 170 ++++-----
icons/effects/mapping_helpers.dmi | Bin 5656 -> 6544 bytes
tgstation.dme | 16 +-
9 files changed, 642 insertions(+), 349 deletions(-)
rename code/modules/mapping/{mapping_helpers.dm => mapping_helpers/_mapping_helpers.dm} (59%)
create mode 100644 code/modules/mapping/mapping_helpers/baseturf.dm
create mode 100644 code/modules/mapping/mapping_helpers/network_builder/_network_builder.dm
create mode 100644 code/modules/mapping/mapping_helpers/network_builder/atmos_pipe.dm
create mode 100644 code/modules/mapping/mapping_helpers/network_builder/power_cables.dm
diff --git a/code/modules/mapping/mapping_helpers.dm b/code/modules/mapping/mapping_helpers/_mapping_helpers.dm
similarity index 59%
rename from code/modules/mapping/mapping_helpers.dm
rename to code/modules/mapping/mapping_helpers/_mapping_helpers.dm
index a7f84fd71f..99184b1c3e 100644
--- a/code/modules/mapping/mapping_helpers.dm
+++ b/code/modules/mapping/mapping_helpers/_mapping_helpers.dm
@@ -1,90 +1,4 @@
//Landmarks and other helpers which speed up the mapping process and reduce the number of unique instances/subtypes of items/turf/ect
-
-
-
-/obj/effect/baseturf_helper //Set the baseturfs of every turf in the /area/ it is placed.
- name = "baseturf editor"
- icon = 'icons/effects/mapping_helpers.dmi'
- icon_state = ""
-
- var/list/baseturf_to_replace
- var/baseturf
-
- layer = POINT_LAYER
-
-/obj/effect/baseturf_helper/Initialize()
- . = ..()
- return INITIALIZE_HINT_LATELOAD
-
-/obj/effect/baseturf_helper/LateInitialize()
- if(!baseturf_to_replace)
- baseturf_to_replace = typecacheof(list(/turf/open/space,/turf/baseturf_bottom))
- else if(!length(baseturf_to_replace))
- baseturf_to_replace = list(baseturf_to_replace = TRUE)
- else if(baseturf_to_replace[baseturf_to_replace[1]] != TRUE) // It's not associative
- var/list/formatted = list()
- for(var/i in baseturf_to_replace)
- formatted[i] = TRUE
- baseturf_to_replace = formatted
-
- var/area/our_area = get_area(src)
- for(var/i in get_area_turfs(our_area, z))
- replace_baseturf(i)
-
- qdel(src)
-
-/obj/effect/baseturf_helper/proc/replace_baseturf(turf/thing)
- var/list/baseturf_cache = thing.baseturfs
- if(length(baseturf_cache))
- for(var/i in baseturf_cache)
- if(baseturf_to_replace[i])
- baseturf_cache -= i
- if(!baseturf_cache.len)
- thing.assemble_baseturfs(baseturf)
- else
- thing.PlaceOnBottom(null, baseturf)
- else if(baseturf_to_replace[thing.baseturfs])
- thing.assemble_baseturfs(baseturf)
- else
- thing.PlaceOnBottom(null, baseturf)
-
-/obj/effect/baseturf_helper/space
- name = "space baseturf editor"
- baseturf = /turf/open/space
-
-/obj/effect/baseturf_helper/asteroid
- name = "asteroid baseturf editor"
- baseturf = /turf/open/floor/plating/asteroid
-
-/obj/effect/baseturf_helper/asteroid/airless
- name = "asteroid airless baseturf editor"
- baseturf = /turf/open/floor/plating/asteroid/airless
-
-/obj/effect/baseturf_helper/asteroid/basalt
- name = "asteroid basalt baseturf editor"
- baseturf = /turf/open/floor/plating/asteroid/basalt
-
-/obj/effect/baseturf_helper/asteroid/snow
- name = "asteroid snow baseturf editor"
- baseturf = /turf/open/floor/plating/asteroid/snow
-
-/obj/effect/baseturf_helper/beach/sand
- name = "beach sand baseturf editor"
- baseturf = /turf/open/floor/plating/beach/sand
-
-/obj/effect/baseturf_helper/beach/water
- name = "water baseturf editor"
- baseturf = /turf/open/floor/plating/beach/water
-
-/obj/effect/baseturf_helper/lava
- name = "lava baseturf editor"
- baseturf = /turf/open/lava/smooth
-
-/obj/effect/baseturf_helper/lava_land/surface
- name = "lavaland baseturf editor"
- baseturf = /turf/open/lava/smooth/lava_land_surface
-
-
/obj/effect/mapping_helpers
icon = 'icons/effects/mapping_helpers.dmi'
icon_state = ""
@@ -94,7 +8,6 @@
..()
return late ? INITIALIZE_HINT_LATELOAD : INITIALIZE_HINT_QDEL
-
//airlock helpers
/obj/effect/mapping_helpers/airlock
layer = DOOR_HELPER_LAYER
diff --git a/code/modules/mapping/mapping_helpers/baseturf.dm b/code/modules/mapping/mapping_helpers/baseturf.dm
new file mode 100644
index 0000000000..f4bd0d7c7f
--- /dev/null
+++ b/code/modules/mapping/mapping_helpers/baseturf.dm
@@ -0,0 +1,82 @@
+/obj/effect/baseturf_helper //Set the baseturfs of every turf in the /area/ it is placed.
+ name = "baseturf editor"
+ icon = 'icons/effects/mapping_helpers.dmi'
+ icon_state = ""
+
+ var/list/baseturf_to_replace
+ var/baseturf
+
+ layer = POINT_LAYER
+
+/obj/effect/baseturf_helper/Initialize()
+ . = ..()
+ return INITIALIZE_HINT_LATELOAD
+
+/obj/effect/baseturf_helper/LateInitialize()
+ if(!baseturf_to_replace)
+ baseturf_to_replace = typecacheof(list(/turf/open/space,/turf/baseturf_bottom))
+ else if(!length(baseturf_to_replace))
+ baseturf_to_replace = list(baseturf_to_replace = TRUE)
+ else if(baseturf_to_replace[baseturf_to_replace[1]] != TRUE) // It's not associative
+ var/list/formatted = list()
+ for(var/i in baseturf_to_replace)
+ formatted[i] = TRUE
+ baseturf_to_replace = formatted
+
+ var/area/our_area = get_area(src)
+ for(var/i in get_area_turfs(our_area, z))
+ replace_baseturf(i)
+
+ qdel(src)
+
+/obj/effect/baseturf_helper/proc/replace_baseturf(turf/thing)
+ var/list/baseturf_cache = thing.baseturfs
+ if(length(baseturf_cache))
+ for(var/i in baseturf_cache)
+ if(baseturf_to_replace[i])
+ baseturf_cache -= i
+ if(!baseturf_cache.len)
+ thing.assemble_baseturfs(baseturf)
+ else
+ thing.PlaceOnBottom(null, baseturf)
+ else if(baseturf_to_replace[thing.baseturfs])
+ thing.assemble_baseturfs(baseturf)
+ else
+ thing.PlaceOnBottom(null, baseturf)
+
+/obj/effect/baseturf_helper/space
+ name = "space baseturf editor"
+ baseturf = /turf/open/space
+
+/obj/effect/baseturf_helper/asteroid
+ name = "asteroid baseturf editor"
+ baseturf = /turf/open/floor/plating/asteroid
+
+/obj/effect/baseturf_helper/asteroid/airless
+ name = "asteroid airless baseturf editor"
+ baseturf = /turf/open/floor/plating/asteroid/airless
+
+/obj/effect/baseturf_helper/asteroid/basalt
+ name = "asteroid basalt baseturf editor"
+ baseturf = /turf/open/floor/plating/asteroid/basalt
+
+/obj/effect/baseturf_helper/asteroid/snow
+ name = "asteroid snow baseturf editor"
+ baseturf = /turf/open/floor/plating/asteroid/snow
+
+/obj/effect/baseturf_helper/beach/sand
+ name = "beach sand baseturf editor"
+ baseturf = /turf/open/floor/plating/beach/sand
+
+/obj/effect/baseturf_helper/beach/water
+ name = "water baseturf editor"
+ baseturf = /turf/open/floor/plating/beach/water
+
+/obj/effect/baseturf_helper/lava
+ name = "lava baseturf editor"
+ baseturf = /turf/open/lava/smooth
+
+/obj/effect/baseturf_helper/lava_land/surface
+ name = "lavaland baseturf editor"
+ baseturf = /turf/open/lava/smooth/lava_land_surface
+
diff --git a/code/modules/mapping/mapping_helpers/network_builder/_network_builder.dm b/code/modules/mapping/mapping_helpers/network_builder/_network_builder.dm
new file mode 100644
index 0000000000..b5cd470a32
--- /dev/null
+++ b/code/modules/mapping/mapping_helpers/network_builder/_network_builder.dm
@@ -0,0 +1,38 @@
+//Builds networks like power cables/atmos lines/etc
+//Just a holder parent type for now..
+/obj/effect/network_builder
+ /// set var to true to not del on lateload
+ var/custom_spawned = FALSE
+
+ /// what directions we know connections are in
+ var/list/network_directions
+
+/obj/effect/network_builder/Initialize(mapload)
+ . = ..()
+ var/conflict = check_duplicates()
+ if(conflict)
+ stack_trace("WARNING: [type] network building helper found check_duplicates() conflict [conflict] in its location.!")
+ return INITIALIZE_HINT_QDEL
+ if(!mapload)
+ if(GLOB.Debug2)
+ custom_spawned = TRUE
+ return INITIALIZE_HINT_NORMAL
+ else
+ return INITIALIZE_HINT_QDEL
+ return INITIALIZE_HINT_LATELOAD
+
+/// How this works: On LateInitialize, detect all directions that this should be applicable to, and do what it needs to do, and then inform all network builders in said directions that it's been around since it won't be around afterwards.
+/obj/effect/network_builder/LateInitialize()
+ scan_directions()
+ build_network()
+ if(!custom_spawned)
+ qdel(src)
+
+/obj/effect/network_builder/proc/check_duplicates()
+ CRASH("Base abstract network builder tried to check duplicates.")
+
+/obj/effect/network_builder/proc/scan_directions()
+ CRASH("Base abstract network builder tried to scan directions.")
+
+/obj/effect/network_builder/proc/build_network()
+ CRASH("Base abstract network builder tried to build network.")
diff --git a/code/modules/mapping/mapping_helpers/network_builder/atmos_pipe.dm b/code/modules/mapping/mapping_helpers/network_builder/atmos_pipe.dm
new file mode 100644
index 0000000000..1183ec0434
--- /dev/null
+++ b/code/modules/mapping/mapping_helpers/network_builder/atmos_pipe.dm
@@ -0,0 +1,159 @@
+#define NO_KNOT 0
+#define KNOT_AUTO 1
+#define KNOT_FORCED 2
+
+/* Automatically places pipes on init based on any pipes connecting to it and adjacent helpers. Only supports cardinals.
+ * Conflicts with ANY PIPE ON ITS LAYER, as well as atmos network build helpers on the same layer, as well as any pipe on all layers. Do those manually.
+*/
+/obj/effect/network_builder/atmos_pipe
+ name = "atmos pipe autobuilder"
+ icon_state = "atmospipebuilder"
+
+ /// Layer to put our pipes on
+ var/pipe_layer = PIPING_LAYER_DEFAULT
+
+ /// Color to set our pipes to
+ var/pipe_color
+
+ /// Whether or not pipes we make are visible
+ var/visible_pipes = FALSE
+
+ color = null
+
+/obj/effect/network_builder/atmos_pipe/check_duplicates()
+ for(var/obj/effect/network_builder/atmos_pipe/other in loc)
+ if(other.pipe_layer == pipe_layer)
+ return other
+ for(var/obj/machinery/atmospherics/A in loc)
+ if(A.pipe_flags & PIPING_ALL_LAYER)
+ return A
+ if(A.pipe_layer == pipe_layer)
+ return A
+ return FALSE
+
+/// Scans directions, sets network_directions to have every direction that we can link to. If there's another power cable builder detected, make sure they know we're here by adding us to their cable directions list before we're deleted.
+/obj/effect/network_builder/atmos_pipe/scan_directions()
+ var/turf/T
+ for(var/i in GLOB.cardinal)
+ if(i in network_directions)
+ continue //we're already set, that means another builder set us.
+ T = get_step(loc, i)
+ if(!T)
+ continue
+ var/found = FALSE
+ for(var/obj/effect/network_builder/atmos_pipe/other in T)
+ if(other.pipe_layer == pipe_layer)
+ network_directions += i
+ LAZYADD(other.network_directions, turn(i, 180))
+ found = TRUE
+ break
+ if(found)
+ continue
+ for(var/obj/machinery/atmospherics/A in T)
+ if((A.pipe_layer == pipe_layer) && (A.initialize_directions & i))
+ network_directions += i
+ break
+ return network_directions
+
+/// Directions should only ever have cardinals.
+/obj/effect/network_builder/atmos_pipe/build_network(list/directions = pipe_directions)
+ if(!length(directions) <= 1)
+ return
+ var/obj/machinery/atmospherics/pipe/built
+ switch(length(directions))
+ if(2) //straight pipe
+ built = new /obj/machinery/atmospherics/simple(loc)
+ built.setDir(directions[1] | directions[2])
+ if(3) //manifold
+ var/list/missing = directions ^ GLOB.cardinal
+ missing = missing[1]
+ built = new /obj/machinery/atmospherics/pipe/manifold(loc)
+ built.setDir(missing)
+ if(4) //4 way manifold
+ built = new /obj/machinery/atmospherics/pipe/manifold4w(loc)
+ built.SetInitDirections()
+ built.on_construction(pipe_color, piping_layer)
+
+/obj/item/pipe/wrench_act(mob/living/user, obj/item/wrench/W)
+ if(!isturf(loc))
+ return TRUE
+
+ add_fingerprint(user)
+
+ var/obj/machinery/atmospherics/fakeA = pipe_type
+ var/flags = initial(fakeA.pipe_flags)
+ for(var/obj/machinery/atmospherics/M in loc)
+ if((M.pipe_flags & flags & PIPING_ONE_PER_TURF)) //Only one dense/requires density object per tile, eg connectors/cryo/heater/coolers.
+ to_chat(user, "Something is hogging the tile!")
+ return TRUE
+ if((M.piping_layer != piping_layer) && !((M.pipe_flags | flags) & PIPING_ALL_LAYER)) //don't continue if either pipe goes across all layers
+ continue
+ if(M.GetInitDirections() & SSair.get_init_dirs(pipe_type, fixed_dir())) // matches at least one direction on either type of pipe
+ to_chat(user, "There is already a pipe at that location!")
+ return TRUE
+ // no conflicts found
+
+ var/obj/machinery/atmospherics/A = new pipe_type(loc)
+ build_pipe(A)
+ A.on_construction(color, piping_layer)
+ transfer_fingerprints_to(A)
+
+ W.play_tool_sound(src)
+ user.visible_message( \
+ "[user] fastens \the [src].", \
+ "You fasten \the [src].", \
+ "You hear ratcheting.")
+
+ qdel(src)
+
+/obj/item/pipe/proc/build_pipe(obj/machinery/atmospherics/A)
+ A.setDir(fixed_dir())
+ A.SetInitDirections()
+
+ if(pipename)
+ A.name = pipename
+ if(A.on)
+ // Certain pre-mapped subtypes are on by default, we want to preserve
+ // every other aspect of these subtypes (name, pre-set filters, etc.)
+ // but they shouldn't turn on automatically when wrenched.
+ A.on = FALSE
+
+/obj/effect/network_builder/atmos_pipe/proc/spawn_wires(list/directions)
+ if(!length(directions))
+ return
+ else if(length(directions) == 1)
+ var/knot = (knot == KNOT_FORCED) || ((knot == KNOT_AUTO) && should_auto_knot())
+ if(knot)
+ var/dir = directions[1]
+ new /obj/structure/cable(loc, cable_color, NONE, directions[1])
+ else
+ if(knot == KNOT_FORCED)
+ for(var/d in directions)
+ new /obj/structure/cable(loc, cable_color, NONE, d)
+ else
+ var/knot = (knot == KNOT_FORCED) || ((knot == KNOT_AUTO) && should_auto_knot())
+ var/dirs = length(directions)
+ for(var/i in dirs)
+ var/li = i - 1
+ if(li < 1)
+ li = dirs + li
+ new /obj/structure/cable(loc, cable_color, directions[i], directions[li])
+ if(knot)
+ new /obj/structure/cable(loc, cable_color, NONE, directions[i])
+ knot = FALSE
+
+/obj/effect/network_builder/atmos_pipe/distro
+ name = "distro line autobuilder"
+ piping_layer = PIPING_LAYER_MIN
+ pixel_x = -PIPING_LAYER_P_X
+ pixel_y = -PIPING_LAYER_P_Y
+ pipe_color = rgb(130,43,255)
+ color = rgb(130,43,255)
+
+/obj/effect/network_builder/atmos_pipe/scrubbers
+ name = "scrubbers line autobuilder"
+ piping_layer = PIPING_LAYER_MAX
+ pixel_x = PIPING_LAYER_P_X
+ pixel_y = PIPING_LAYER_P_Y
+ pipe_color = rgb(255,0,0)
+ color = rgb(255,0,0)
diff --git a/code/modules/mapping/mapping_helpers/network_builder/power_cables.dm b/code/modules/mapping/mapping_helpers/network_builder/power_cables.dm
new file mode 100644
index 0000000000..db89454349
--- /dev/null
+++ b/code/modules/mapping/mapping_helpers/network_builder/power_cables.dm
@@ -0,0 +1,97 @@
+#define NO_KNOT 0
+#define KNOT_AUTO 1
+#define KNOT_FORCED 2
+
+/// Automatically links on init to power cables and other cable builder helpers. Only supports cardinals.
+/obj/effect/network_builder/power_cable
+ name = "power line autobuilder"
+ icon_state = "powerlinebuilder"
+
+ /// Whether or not we forcefully make a knot
+ var/knot = NO_KNOT
+
+ /// cable color as from GLOB.cable_colors
+ var/cable_color = "red"
+
+ color = "ff0000"
+
+/obj/effect/network_builder/power_cable/check_duplicates()
+ return (locate(/obj/structure/cable) in loc) || (locate(/obj/effect/network_builder/power_cable) in loc)
+
+/// Scans directions, sets network_directions to have every direction that we can link to. If there's another power cable builder detected, make sure they know we're here by adding us to their cable directions list before we're deleted.
+/obj/effect/network_builder/power_cable/scan_directions()
+ var/turf/T
+ for(var/i in GLOB.cardinal)
+ if(i in network_directions)
+ continue //we're already set, that means another builder set us.
+ T = get_step(loc, i)
+ if(!T)
+ continue
+ var/obj/effect/network_builder/power_cable/other = locate() in T
+ if(other)
+ network_directions += i
+ LAZYADD(other.network_directions, turn(i, 180))
+ continue
+ for(var/obj/structure/cable/C in T)
+ if(C.d1 == turn(i, 180) || C.d2 == turn(i, 180))
+ network_directions += i
+ continue
+ return network_directions
+
+/// Directions should only ever have cardinals.
+/obj/effect/network_builder/power_cable/build_network(list/directions = network_directions)
+ if(!length(directions))
+ return
+ else if(length(directions) == 1)
+ var/knot = (knot == KNOT_FORCED) || ((knot == KNOT_AUTO) && should_auto_knot())
+ if(knot)
+ var/dir = directions[1]
+ new /obj/structure/cable(loc, cable_color, NONE, directions[1])
+ else
+ if(knot == KNOT_FORCED)
+ for(var/d in directions)
+ new /obj/structure/cable(loc, cable_color, NONE, d)
+ else
+ var/knot = (knot == KNOT_FORCED) || ((knot == KNOT_AUTO) && should_auto_knot())
+ var/dirs = length(directions)
+ for(var/i in dirs)
+ var/li = i - 1
+ if(li < 1)
+ li = dirs + li
+ new /obj/structure/cable(loc, cable_color, directions[i], directions[li])
+ if(knot)
+ new /obj/structure/cable(loc, cable_color, NONE, directions[i])
+ knot = FALSE
+
+/obj/effect/network_builder/power_cable/proc/should_auto_knot()
+ return (locate(/obj/machinery/terminal) in loc)
+
+/obj/effect/network_buidler/power_cable/knot
+ icon_state = "powerlinebuilderknot"
+ knot = KNOT_FORCED
+
+/obj/effect/network_builder/power_cable/auto
+ icon_state = "powerlinebuilderauto"
+ knot = KNOT_AUTO
+
+#define AUTODEF_COLOR(hex, enum) \
+/obj/effect/network_builder/power_cable/##enum \
+ color = #hex \
+ cable_color = #enum \
+/obj/effect/network_builder/power_cable/knot/##enum \
+ color = #hex \
+ cable_color = #enum \
+/obj/effect/network_builder/power_cable/auto/##enum \
+ color = #hex \
+ cable_color = #enum
+
+AUTODEF_COLOR("#ff0000", "red")
+AUTODEF_COLOR("#ffffff", "white")
+AUTODEF_COLOR("#00ffff", "cyan")
+AUTODEF_COLOR("#ff8000", "orange")
+AUTODEF_COLOR("#ff3cc8", "pink")
+AUTODEF_COLOR("#1919c8", "blue")
+AUTODEF_COLOR("#00aa00", "green")
+AUTODEF_COLOR("#ffff00", "yellow")
+
+#undef AUTODEF_COLOR
diff --git a/code/modules/mapping/ruins.dm b/code/modules/mapping/ruins.dm
index 9c578badb1..d88c7543f0 100644
--- a/code/modules/mapping/ruins.dm
+++ b/code/modules/mapping/ruins.dm
@@ -1,171 +1,171 @@
-/datum/map_template/ruin/proc/try_to_place(z,allowed_areas,turf/forced_turf)
- var/sanity = forced_turf ? 1 : PLACEMENT_TRIES
- if(SSmapping.level_trait(z,ZTRAIT_ISOLATED_RUINS))
- return place_on_isolated_level(z)
- while(sanity > 0)
- sanity--
- var/width_border = TRANSITIONEDGE + SPACERUIN_MAP_EDGE_PAD + round(width / 2)
- var/height_border = TRANSITIONEDGE + SPACERUIN_MAP_EDGE_PAD + round(height / 2)
- var/turf/central_turf = forced_turf ? forced_turf : locate(rand(width_border, world.maxx - width_border), rand(height_border, world.maxy - height_border), z)
- var/valid = TRUE
-
- for(var/turf/check in get_affected_turfs(central_turf,1))
- var/area/new_area = get_area(check)
- if(!(istype(new_area, allowed_areas)) || check.flags_1 & NO_RUINS_1)
- valid = FALSE
- break
-
- if(!valid)
- continue
-
- testing("Ruin \"[name]\" placed at ([central_turf.x], [central_turf.y], [central_turf.z])")
-
- for(var/i in get_affected_turfs(central_turf, 1))
- var/turf/T = i
- for(var/mob/living/simple_animal/monster in T)
- qdel(monster)
- for(var/obj/structure/flora/ash/plant in T)
- qdel(plant)
-
- load(central_turf,centered = TRUE)
- loaded++
-
- for(var/turf/T in get_affected_turfs(central_turf, 1))
- T.flags_1 |= NO_RUINS_1
-
- new /obj/effect/landmark/ruin(central_turf, src)
- return central_turf
-
-/datum/map_template/ruin/proc/place_on_isolated_level(z)
- var/datum/turf_reservation/reservation = SSmapping.RequestBlockReservation(width, height, z) //Make the new level creation work with different traits.
- if(!reservation)
- return
- var/turf/placement = locate(reservation.bottom_left_coords[1],reservation.bottom_left_coords[2],reservation.bottom_left_coords[3])
- load(placement)
- loaded++
- for(var/turf/T in get_affected_turfs(placement))
- T.flags_1 |= NO_RUINS_1
- var/turf/center = locate(placement.x + round(width/2),placement.y + round(height/2),placement.z)
- new /obj/effect/landmark/ruin(center, src)
- return center
-
-/proc/seedRuins(list/z_levels = null, budget = 0, whitelist = /area/space, list/potentialRuins)
- if(!z_levels || !z_levels.len)
- WARNING("No Z levels provided - Not generating ruins")
- return
-
- for(var/zl in z_levels)
- var/turf/T = locate(1, 1, zl)
- if(!T)
- WARNING("Z level [zl] does not exist - Not generating ruins")
- return
-
- var/list/ruins = potentialRuins.Copy()
-
- var/list/forced_ruins = list() //These go first on the z level associated (same random one by default) or if the assoc value is a turf to the specified turf.
- var/list/ruins_availible = list() //we can try these in the current pass
-
- //Set up the starting ruin list
- for(var/key in ruins)
- var/datum/map_template/ruin/R = ruins[key]
- if(R.cost > budget) //Why would you do that
- continue
- if(R.always_place)
- forced_ruins[R] = -1
- if(R.unpickable)
- continue
- ruins_availible[R] = R.placement_weight
-
- while(budget > 0 && (ruins_availible.len || forced_ruins.len))
- var/datum/map_template/ruin/current_pick
- var/forced = FALSE
- var/forced_z //If set we won't pick z level and use this one instead.
- var/forced_turf //If set we place the ruin centered on the given turf
- if(forced_ruins.len) //We have something we need to load right now, so just pick it
- for(var/ruin in forced_ruins)
- current_pick = ruin
- if(isturf(forced_ruins[ruin]))
- var/turf/T = forced_ruins[ruin]
- forced_z = T.z //In case of chained ruins
- forced_turf = T
- else if(forced_ruins[ruin] > 0) //Load into designated z
- forced_z = forced_ruins[ruin]
- forced = TRUE
- break
- else //Otherwise just pick random one
- current_pick = pickweight(ruins_availible)
-
- var/placement_tries = forced_turf ? 1 : PLACEMENT_TRIES //Only try once if we target specific turf
- var/failed_to_place = TRUE
- var/target_z = 0
- var/turf/placed_turf //Where the ruin ended up if we succeeded
- outer:
- while(placement_tries > 0)
- placement_tries--
- target_z = pick(z_levels)
- if(forced_z)
- target_z = forced_z
- if(current_pick.always_spawn_with) //If the ruin has part below, make sure that z exists.
- for(var/v in current_pick.always_spawn_with)
- if(current_pick.always_spawn_with[v] == PLACE_BELOW)
- var/turf/T = locate(1,1,target_z)
- if(!SSmapping.get_turf_below(T))
- if(forced_z)
- continue outer
- else
- break outer
-
- placed_turf = current_pick.try_to_place(target_z,whitelist,forced_turf)
- if(!placed_turf)
- continue
- else
- failed_to_place = FALSE
- break
-
- //That's done remove from priority even if it failed
- if(forced)
- //TODO : handle forced ruins with multiple variants
- forced_ruins -= current_pick
- forced = FALSE
-
- if(failed_to_place)
- for(var/datum/map_template/ruin/R in ruins_availible)
- if(R.id == current_pick.id)
- ruins_availible -= R
- log_world("Failed to place [current_pick.name] ruin.")
- else
- budget -= current_pick.cost
- if(!current_pick.allow_duplicates)
- for(var/datum/map_template/ruin/R in ruins_availible)
- if(R.id == current_pick.id)
- ruins_availible -= R
- if(current_pick.never_spawn_with)
- for(var/blacklisted_type in current_pick.never_spawn_with)
- for(var/possible_exclusion in ruins_availible)
- if(istype(possible_exclusion,blacklisted_type))
- ruins_availible -= possible_exclusion
- if(current_pick.always_spawn_with)
- for(var/v in current_pick.always_spawn_with)
- for(var/ruin_name in SSmapping.ruins_templates) //Because we might want to add space templates as linked of lava templates.
- var/datum/map_template/ruin/linked = SSmapping.ruins_templates[ruin_name] //why are these assoc, very annoying.
- if(istype(linked,v))
- switch(current_pick.always_spawn_with[v])
- if(PLACE_SAME_Z)
- forced_ruins[linked] = target_z //I guess you might want a chain somehow
- if(PLACE_LAVA_RUIN)
- forced_ruins[linked] = pick(SSmapping.levels_by_trait(ZTRAIT_LAVA_RUINS))
- if(PLACE_SPACE_RUIN)
- forced_ruins[linked] = pick(SSmapping.levels_by_trait(ZTRAIT_SPACE_RUINS))
- if(PLACE_DEFAULT)
- forced_ruins[linked] = -1
- if(PLACE_BELOW)
- forced_ruins[linked] = SSmapping.get_turf_below(placed_turf)
- if(PLACE_ISOLATED)
- forced_ruins[linked] = SSmapping.get_isolated_ruin_z()
-
- //Update the availible list
- for(var/datum/map_template/ruin/R in ruins_availible)
- if(R.cost > budget)
- ruins_availible -= R
-
- log_world("Ruin loader finished with [budget] left to spend.")
+/datum/map_template/ruin/proc/try_to_place(z,allowed_areas,turf/forced_turf)
+ var/sanity = forced_turf ? 1 : PLACEMENT_TRIES
+ if(SSmapping.level_trait(z,ZTRAIT_ISOLATED_RUINS))
+ return place_on_isolated_level(z)
+ while(sanity > 0)
+ sanity--
+ var/width_border = TRANSITIONEDGE + SPACERUIN_MAP_EDGE_PAD + round(width / 2)
+ var/height_border = TRANSITIONEDGE + SPACERUIN_MAP_EDGE_PAD + round(height / 2)
+ var/turf/central_turf = forced_turf ? forced_turf : locate(rand(width_border, world.maxx - width_border), rand(height_border, world.maxy - height_border), z)
+ var/valid = TRUE
+
+ for(var/turf/check in get_affected_turfs(central_turf,1))
+ var/area/new_area = get_area(check)
+ if(!(istype(new_area, allowed_areas)) || check.flags_1 & NO_RUINS_1)
+ valid = FALSE
+ break
+
+ if(!valid)
+ continue
+
+ testing("Ruin \"[name]\" placed at ([central_turf.x], [central_turf.y], [central_turf.z])")
+
+ for(var/i in get_affected_turfs(central_turf, 1))
+ var/turf/T = i
+ for(var/mob/living/simple_animal/monster in T)
+ qdel(monster)
+ for(var/obj/structure/flora/ash/plant in T)
+ qdel(plant)
+
+ load(central_turf,centered = TRUE)
+ loaded++
+
+ for(var/turf/T in get_affected_turfs(central_turf, 1))
+ T.flags_1 |= NO_RUINS_1
+
+ new /obj/effect/landmark/ruin(central_turf, src)
+ return central_turf
+
+/datum/map_template/ruin/proc/place_on_isolated_level(z)
+ var/datum/turf_reservation/reservation = SSmapping.RequestBlockReservation(width, height, z) //Make the new level creation work with different traits.
+ if(!reservation)
+ return
+ var/turf/placement = locate(reservation.bottom_left_coords[1],reservation.bottom_left_coords[2],reservation.bottom_left_coords[3])
+ load(placement)
+ loaded++
+ for(var/turf/T in get_affected_turfs(placement))
+ T.flags_1 |= NO_RUINS_1
+ var/turf/center = locate(placement.x + round(width/2),placement.y + round(height/2),placement.z)
+ new /obj/effect/landmark/ruin(center, src)
+ return center
+
+/proc/seedRuins(list/z_levels = null, budget = 0, whitelist = /area/space, list/potentialRuins)
+ if(!z_levels || !z_levels.len)
+ WARNING("No Z levels provided - Not generating ruins")
+ return
+
+ for(var/zl in z_levels)
+ var/turf/T = locate(1, 1, zl)
+ if(!T)
+ WARNING("Z level [zl] does not exist - Not generating ruins")
+ return
+
+ var/list/ruins = potentialRuins.Copy()
+
+ var/list/forced_ruins = list() //These go first on the z level associated (same random one by default) or if the assoc value is a turf to the specified turf.
+ var/list/ruins_availible = list() //we can try these in the current pass
+
+ //Set up the starting ruin list
+ for(var/key in ruins)
+ var/datum/map_template/ruin/R = ruins[key]
+ if(R.cost > budget) //Why would you do that
+ continue
+ if(R.always_place)
+ forced_ruins[R] = -1
+ if(R.unpickable)
+ continue
+ ruins_availible[R] = R.placement_weight
+
+ while(budget > 0 && (ruins_availible.len || forced_ruins.len))
+ var/datum/map_template/ruin/current_pick
+ var/forced = FALSE
+ var/forced_z //If set we won't pick z level and use this one instead.
+ var/forced_turf //If set we place the ruin centered on the given turf
+ if(forced_ruins.len) //We have something we need to load right now, so just pick it
+ for(var/ruin in forced_ruins)
+ current_pick = ruin
+ if(isturf(forced_ruins[ruin]))
+ var/turf/T = forced_ruins[ruin]
+ forced_z = T.z //In case of chained ruins
+ forced_turf = T
+ else if(forced_ruins[ruin] > 0) //Load into designated z
+ forced_z = forced_ruins[ruin]
+ forced = TRUE
+ break
+ else //Otherwise just pick random one
+ current_pick = pickweight(ruins_availible)
+
+ var/placement_tries = forced_turf ? 1 : PLACEMENT_TRIES //Only try once if we target specific turf
+ var/failed_to_place = TRUE
+ var/target_z = 0
+ var/turf/placed_turf //Where the ruin ended up if we succeeded
+ outer:
+ while(placement_tries > 0)
+ placement_tries--
+ target_z = pick(z_levels)
+ if(forced_z)
+ target_z = forced_z
+ if(current_pick.always_spawn_with) //If the ruin has part below, make sure that z exists.
+ for(var/v in current_pick.always_spawn_with)
+ if(current_pick.always_spawn_with[v] == PLACE_BELOW)
+ var/turf/T = locate(1,1,target_z)
+ if(!SSmapping.get_turf_below(T))
+ if(forced_z)
+ continue outer
+ else
+ break outer
+
+ placed_turf = current_pick.try_to_place(target_z,whitelist,forced_turf)
+ if(!placed_turf)
+ continue
+ else
+ failed_to_place = FALSE
+ break
+
+ //That's done remove from priority even if it failed
+ if(forced)
+ //TODO : handle forced ruins with multiple variants
+ forced_ruins -= current_pick
+ forced = FALSE
+
+ if(failed_to_place)
+ for(var/datum/map_template/ruin/R in ruins_availible)
+ if(R.id == current_pick.id)
+ ruins_availible -= R
+ log_world("Failed to place [current_pick.name] ruin.")
+ else
+ budget -= current_pick.cost
+ if(!current_pick.allow_duplicates)
+ for(var/datum/map_template/ruin/R in ruins_availible)
+ if(R.id == current_pick.id)
+ ruins_availible -= R
+ if(current_pick.never_spawn_with)
+ for(var/blacklisted_type in current_pick.never_spawn_with)
+ for(var/possible_exclusion in ruins_availible)
+ if(istype(possible_exclusion,blacklisted_type))
+ ruins_availible -= possible_exclusion
+ if(current_pick.always_spawn_with)
+ for(var/v in current_pick.always_spawn_with)
+ for(var/ruin_name in SSmapping.ruins_templates) //Because we might want to add space templates as linked of lava templates.
+ var/datum/map_template/ruin/linked = SSmapping.ruins_templates[ruin_name] //why are these assoc, very annoying.
+ if(istype(linked,v))
+ switch(current_pick.always_spawn_with[v])
+ if(PLACE_SAME_Z)
+ forced_ruins[linked] = target_z //I guess you might want a chain somehow
+ if(PLACE_LAVA_RUIN)
+ forced_ruins[linked] = pick(SSmapping.levels_by_trait(ZTRAIT_LAVA_RUINS))
+ if(PLACE_SPACE_RUIN)
+ forced_ruins[linked] = pick(SSmapping.levels_by_trait(ZTRAIT_SPACE_RUINS))
+ if(PLACE_DEFAULT)
+ forced_ruins[linked] = -1
+ if(PLACE_BELOW)
+ forced_ruins[linked] = SSmapping.get_turf_below(placed_turf)
+ if(PLACE_ISOLATED)
+ forced_ruins[linked] = SSmapping.get_isolated_ruin_z()
+
+ //Update the availible list
+ for(var/datum/map_template/ruin/R in ruins_availible)
+ if(R.cost > budget)
+ ruins_availible -= R
+
+ log_world("Ruin loader finished with [budget] left to spend.")
diff --git a/code/modules/mapping/space_management/space_reservation.dm b/code/modules/mapping/space_management/space_reservation.dm
index b514796bc5..43fe69d633 100644
--- a/code/modules/mapping/space_management/space_reservation.dm
+++ b/code/modules/mapping/space_management/space_reservation.dm
@@ -1,85 +1,85 @@
-
-//Yes, they can only be rectangular.
-//Yes, I'm sorry.
-/datum/turf_reservation
- var/list/reserved_turfs = list()
- var/width = 0
- var/height = 0
- var/bottom_left_coords[3]
- var/top_right_coords[3]
- var/wipe_reservation_on_release = TRUE
- var/turf_type = /turf/open/space
- var/borderturf
-
-/datum/turf_reservation/transit
- turf_type = /turf/open/space/transit
- borderturf = /turf/open/space/transit/border
-
-/datum/turf_reservation/proc/Release()
- var/v = reserved_turfs.Copy()
- for(var/i in reserved_turfs)
- reserved_turfs -= i
- SSmapping.used_turfs -= i
- SSmapping.reserve_turfs(v)
-
-/datum/turf_reservation/transit/Release()
- for(var/turf/open/space/transit/T in reserved_turfs)
- for(var/atom/movable/AM in T)
- T.throw_atom(AM)
- . = ..()
-
-/datum/turf_reservation/proc/Reserve(width, height, zlevel)
- if(width > world.maxx || height > world.maxy || width < 1 || height < 1)
- return FALSE
- var/list/avail = SSmapping.unused_turfs["[zlevel]"]
- var/turf/BL
- var/turf/TR
- var/list/turf/final = list()
- var/passing = FALSE
- for(var/i in avail)
- CHECK_TICK
- BL = i
- if(!(BL.flags_1 & UNUSED_RESERVATION_TURF_1))
- continue
- if(BL.x + width > world.maxx || BL.y + height > world.maxy)
- continue
- TR = locate(BL.x + width - 1, BL.y + height - 1, BL.z)
- if(!(TR.flags_1 & UNUSED_RESERVATION_TURF_1))
- continue
- final = block(BL, TR)
- if(!final)
- continue
- passing = TRUE
- for(var/I in final)
- var/turf/checking = I
- if(!(checking.flags_1 & UNUSED_RESERVATION_TURF_1))
- passing = FALSE
- break
- if(!passing)
- continue
- break
- if(!passing || !istype(BL) || !istype(TR))
- return FALSE
- bottom_left_coords = list(BL.x, BL.y, BL.z)
- top_right_coords = list(TR.x, TR.y, TR.z)
- for(var/i in final)
- var/turf/T = i
- reserved_turfs |= T
- T.flags_1 &= ~UNUSED_RESERVATION_TURF_1
- SSmapping.unused_turfs["[T.z]"] -= T
- SSmapping.used_turfs[T] = src
- if(borderturf && (T.x == BL.x || T.x == TR.x || T.y == BL.y || T.y == TR.y))
- T.ChangeTurf(borderturf, borderturf)
- else
- T.ChangeTurf(turf_type, turf_type)
- src.width = width
- src.height = height
- return TRUE
-
-/datum/turf_reservation/New()
- LAZYADD(SSmapping.turf_reservations, src)
-
-/datum/turf_reservation/Destroy()
- Release()
- LAZYREMOVE(SSmapping.turf_reservations, src)
- return ..()
+
+//Yes, they can only be rectangular.
+//Yes, I'm sorry.
+/datum/turf_reservation
+ var/list/reserved_turfs = list()
+ var/width = 0
+ var/height = 0
+ var/bottom_left_coords[3]
+ var/top_right_coords[3]
+ var/wipe_reservation_on_release = TRUE
+ var/turf_type = /turf/open/space
+ var/borderturf
+
+/datum/turf_reservation/transit
+ turf_type = /turf/open/space/transit
+ borderturf = /turf/open/space/transit/border
+
+/datum/turf_reservation/proc/Release()
+ var/v = reserved_turfs.Copy()
+ for(var/i in reserved_turfs)
+ reserved_turfs -= i
+ SSmapping.used_turfs -= i
+ SSmapping.reserve_turfs(v)
+
+/datum/turf_reservation/transit/Release()
+ for(var/turf/open/space/transit/T in reserved_turfs)
+ for(var/atom/movable/AM in T)
+ T.throw_atom(AM)
+ . = ..()
+
+/datum/turf_reservation/proc/Reserve(width, height, zlevel)
+ if(width > world.maxx || height > world.maxy || width < 1 || height < 1)
+ return FALSE
+ var/list/avail = SSmapping.unused_turfs["[zlevel]"]
+ var/turf/BL
+ var/turf/TR
+ var/list/turf/final = list()
+ var/passing = FALSE
+ for(var/i in avail)
+ CHECK_TICK
+ BL = i
+ if(!(BL.flags_1 & UNUSED_RESERVATION_TURF_1))
+ continue
+ if(BL.x + width > world.maxx || BL.y + height > world.maxy)
+ continue
+ TR = locate(BL.x + width - 1, BL.y + height - 1, BL.z)
+ if(!(TR.flags_1 & UNUSED_RESERVATION_TURF_1))
+ continue
+ final = block(BL, TR)
+ if(!final)
+ continue
+ passing = TRUE
+ for(var/I in final)
+ var/turf/checking = I
+ if(!(checking.flags_1 & UNUSED_RESERVATION_TURF_1))
+ passing = FALSE
+ break
+ if(!passing)
+ continue
+ break
+ if(!passing || !istype(BL) || !istype(TR))
+ return FALSE
+ bottom_left_coords = list(BL.x, BL.y, BL.z)
+ top_right_coords = list(TR.x, TR.y, TR.z)
+ for(var/i in final)
+ var/turf/T = i
+ reserved_turfs |= T
+ T.flags_1 &= ~UNUSED_RESERVATION_TURF_1
+ SSmapping.unused_turfs["[T.z]"] -= T
+ SSmapping.used_turfs[T] = src
+ if(borderturf && (T.x == BL.x || T.x == TR.x || T.y == BL.y || T.y == TR.y))
+ T.ChangeTurf(borderturf, borderturf)
+ else
+ T.ChangeTurf(turf_type, turf_type)
+ src.width = width
+ src.height = height
+ return TRUE
+
+/datum/turf_reservation/New()
+ LAZYADD(SSmapping.turf_reservations, src)
+
+/datum/turf_reservation/Destroy()
+ Release()
+ LAZYREMOVE(SSmapping.turf_reservations, src)
+ return ..()
diff --git a/icons/effects/mapping_helpers.dmi b/icons/effects/mapping_helpers.dmi
index dec83046b9112f1bcfbca17c0d6ac10a6e24ba62..037c1aa66090af6a10b89c422b3e00be0c0da471 100644
GIT binary patch
literal 6544
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
KA*5W>CggvOG2$2i
literal 5656
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
N-d$s@QVoaD{{gL08sGo`
diff --git a/tgstation.dme b/tgstation.dme
index 994c6af817..36bc39d902 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -800,11 +800,11 @@
#include "code\game\objects\effects\decals\decal.dm"
#include "code\game\objects\effects\decals\misc.dm"
#include "code\game\objects\effects\decals\remains.dm"
-#include "code\game\objects\effects\decals\cleanable\aliens.dm"
-#include "code\game\objects\effects\decals\cleanable\gibs.dm"
-#include "code\game\objects\effects\decals\cleanable\humans.dm"
-#include "code\game\objects\effects\decals\cleanable\misc.dm"
-#include "code\game\objects\effects\decals\cleanable\robots.dm"
+#include "code\game\objects\effects\decals\Cleanable\aliens.dm"
+#include "code\game\objects\effects\decals\Cleanable\gibs.dm"
+#include "code\game\objects\effects\decals\Cleanable\humans.dm"
+#include "code\game\objects\effects\decals\Cleanable\misc.dm"
+#include "code\game\objects\effects\decals\Cleanable\robots.dm"
#include "code\game\objects\effects\decals\turfdecal\dirt.dm"
#include "code\game\objects\effects\decals\turfdecal\markings.dm"
#include "code\game\objects\effects\decals\turfdecal\tilecoloring.dm"
@@ -2010,11 +2010,15 @@
#include "code\modules\lighting\lighting_turf.dm"
#include "code\modules\mapping\dmm_suite.dm"
#include "code\modules\mapping\map_template.dm"
-#include "code\modules\mapping\mapping_helpers.dm"
#include "code\modules\mapping\preloader.dm"
#include "code\modules\mapping\reader.dm"
#include "code\modules\mapping\ruins.dm"
#include "code\modules\mapping\verify.dm"
+#include "code\modules\mapping\mapping_helpers\_mapping_helpers.dm"
+#include "code\modules\mapping\mapping_helpers\baseturf.dm"
+#include "code\modules\mapping\mapping_helpers\network_builder\_network_builder.dm"
+#include "code\modules\mapping\mapping_helpers\network_builder\atmos_pipe.dm"
+#include "code\modules\mapping\mapping_helpers\network_builder\power_cables.dm"
#include "code\modules\mapping\space_management\multiz_helpers.dm"
#include "code\modules\mapping\space_management\space_level.dm"
#include "code\modules\mapping\space_management\space_reservation.dm"