mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2025-12-19 14:51:27 +00:00
430 lines
14 KiB
Plaintext
430 lines
14 KiB
Plaintext
//This is realisation of the working torus-looping randomized-per-round space map, this kills the cube
|
|
|
|
|
|
/proc/get_opposite_direction(direction)
|
|
switch(direction)
|
|
if(Z_LEVEL_NORTH)
|
|
return Z_LEVEL_SOUTH
|
|
if(Z_LEVEL_SOUTH)
|
|
return Z_LEVEL_NORTH
|
|
if(Z_LEVEL_EAST)
|
|
return Z_LEVEL_WEST
|
|
if(Z_LEVEL_WEST)
|
|
return Z_LEVEL_EAST
|
|
|
|
|
|
// Do this before setting up new connections, or the old ones will haunt you
|
|
/datum/space_level/proc/reset_connections()
|
|
neighbors.Cut()
|
|
|
|
/datum/space_level/proc/link_to_self()
|
|
reset_connections()
|
|
neighbors = list()
|
|
var/list/L = list(Z_LEVEL_NORTH,Z_LEVEL_SOUTH,Z_LEVEL_EAST,Z_LEVEL_WEST)
|
|
for(var/A in L)
|
|
neighbors[A] = src
|
|
|
|
// Only call this when the `linkage_map` is already built
|
|
/datum/space_level/proc/add_to_space_network(datum/spacewalk_grid/SW)
|
|
// Make sure we don't bring any noise data into the network
|
|
reset_connections()
|
|
xi = initial(xi)
|
|
yi = initial(yi)
|
|
var/datum/point/P = SW.get_empty_node()
|
|
P.set_space_level(src)
|
|
|
|
/datum/space_level/proc/remove_from_space_network(datum/spacewalk_grid/SW)
|
|
var/datum/point/P = SW.get(xi,yi)
|
|
SW.release_node(P)
|
|
// Only do this when we're done, or we'll trample vars needed for releasing
|
|
// the level
|
|
xi = initial(xi)
|
|
yi = initial(yi)
|
|
reset_connections()
|
|
|
|
// This proc takes another space level, and establishes a connection between the
|
|
// two depending on how the `xi` and the `yi` values compare
|
|
/datum/space_level/proc/link_levels(datum/space_level/S)
|
|
if(S.xi == xi)
|
|
if(S.yi == yi+1)
|
|
add_connection(S, Z_LEVEL_NORTH)
|
|
else if(S.yi == yi-1)
|
|
add_connection(S, Z_LEVEL_SOUTH)
|
|
else if(S.yi == yi)
|
|
if(S.xi == xi+1)
|
|
add_connection(S, Z_LEVEL_EAST)
|
|
else if(S.xi == xi-1)
|
|
add_connection(S, Z_LEVEL_WEST)
|
|
else // yell about evil wizards, this shouldn't happen
|
|
log_debug("Two z levels attempted to link, but were not adjacent! Our z:([xi],[yi]). Other z:([S.xi],[S.yi])")
|
|
|
|
// `direction` here is the direction from `src` to `S`
|
|
/datum/space_level/proc/add_connection(datum/space_level/S, direction)
|
|
var/oppose = get_opposite_direction(direction)
|
|
neighbors[direction] = S
|
|
S.neighbors[oppose] = src
|
|
GLOB.space_manager.unbuilt_space_transitions |= src
|
|
GLOB.space_manager.unbuilt_space_transitions |= S
|
|
|
|
|
|
/datum/space_level/proc/get_connection(direction)
|
|
if(direction in neighbors)
|
|
return neighbors[direction]
|
|
|
|
// It's in a direction that loops - so we step as far in the opposite direction to get where to wrap to
|
|
var/datum/space_level/S = src
|
|
var/oppose = get_opposite_direction(direction)
|
|
// Loop all the way in the other direction that we can
|
|
while(S.neighbors[oppose])
|
|
if(S.neighbors[oppose] == src) // we've got a tesseract, boys
|
|
CRASH("Tesseract formed when routing connections between z levels. Culprit: z level '[S.zpos]' to '[src.zpos]', direction [oppose]")
|
|
S = S.neighbors[oppose]
|
|
return S
|
|
|
|
|
|
/datum/point //this is explicitly utilitarian datum type made specially for the space map generation and are absolutely unusable for anything else
|
|
var/list/neighbors = list()
|
|
var/x
|
|
var/y
|
|
var/datum/space_level/spl
|
|
|
|
/datum/point/New(nx, ny)
|
|
x = nx
|
|
y = ny
|
|
|
|
/datum/point/proc/hash()
|
|
return "([x],[y])"
|
|
|
|
// This is called only on `filled_nodes` - meaning we have a guarantee that
|
|
// it is already surrounded on all sides
|
|
/datum/point/proc/set_neighbors(datum/spacewalk_grid/SW)
|
|
neighbors.Cut()
|
|
neighbors |= SW.get(x+1, y, allow_empty = 1)
|
|
neighbors |= SW.get(x-1, y, allow_empty = 1)
|
|
neighbors |= SW.get(x, y+1, allow_empty = 1)
|
|
neighbors |= SW.get(x, y-1, allow_empty = 1)
|
|
|
|
// Updates variables of the space level
|
|
/datum/point/proc/set_space_level(datum/space_level/S)
|
|
spl = S
|
|
S.xi = x
|
|
S.yi = y
|
|
for(var/datum/point/P in neighbors)
|
|
if(istype(P.spl))
|
|
// Since each time this proc is called, it is the first time
|
|
// that the z level is added to the grid, we know for certain
|
|
// that no other z level has this as its neighbor
|
|
spl.link_levels(P.spl)
|
|
|
|
// Returns a list of all neighbors that don't have a space level yet
|
|
// If a node doesn't exist yet, it will create it
|
|
// This is only called for nodes within the `available_nodes` list
|
|
/datum/point/proc/get_empty_neighbors(datum/spacewalk_grid/SW)
|
|
var/list/result
|
|
var/datum/point/up = SW.get(x, y+1, allow_empty = 1)
|
|
var/datum/point/down = SW.get(x, y-1, allow_empty = 1)
|
|
var/datum/point/left = SW.get(x-1, y, allow_empty = 1)
|
|
var/datum/point/right = SW.get(x+1, y, allow_empty = 1)
|
|
if(isnull(up))
|
|
up = new(x, y+1)
|
|
SW.add_available_node(up)
|
|
|
|
if(isnull(down))
|
|
down = new(x, y-1)
|
|
SW.add_available_node(down)
|
|
|
|
if(isnull(left))
|
|
left = new(x-1, y)
|
|
SW.add_available_node(left)
|
|
|
|
if(isnull(right))
|
|
right = new(x+1, y)
|
|
SW.add_available_node(right)
|
|
|
|
// Nodes with a space datum are not empty
|
|
result = list(up, down, left, right)
|
|
for(var/datum/point/thing in result)
|
|
if(!isnull(thing.spl))
|
|
result -= thing
|
|
return result
|
|
|
|
// This looks around itself to see if it has any active nodes within the cardinal directions
|
|
/datum/point/proc/has_no_neighbors(datum/spacewalk_grid/SW)
|
|
var/result = 1
|
|
if(!isnull(SW.get(x+1,y)))
|
|
result = 0
|
|
if(!isnull(SW.get(x-1,y)))
|
|
result = 0
|
|
if(!isnull(SW.get(x,y+1)))
|
|
result = 0
|
|
if(!isnull(SW.get(x,y-1)))
|
|
result = 0
|
|
return result
|
|
|
|
|
|
/datum/point/proc/deactivate()
|
|
if(!spl)
|
|
throw EXCEPTION("Attempted to deactivate inactive point")
|
|
for(var/direction in spl.neighbors)
|
|
var/datum/space_level/S = spl.neighbors[direction]
|
|
var/oppose = get_opposite_direction(direction)
|
|
S.neighbors.Remove(oppose)
|
|
GLOB.space_manager.unbuilt_space_transitions |= S
|
|
spl.reset_connections()
|
|
spl = initial(spl)
|
|
|
|
// This is like the old algorithm, except this one
|
|
// can expand indefinitely and add new points on the fly
|
|
|
|
// The algorithm is: Start in the center, and add all adjacent points to a list of things to select
|
|
// Then repeatedly do the cycle of choosing a node next to any previously-selected node, then
|
|
// add all points next to your chosen node to the list of things to select
|
|
/datum/spacewalk_grid
|
|
// This is a list of fully initialized nodes - these have their neighbors
|
|
// assigned, and are surrounded
|
|
var/list/filled_nodes = list()
|
|
// These nodes are not fully initialized - their neighbors field is empty,
|
|
// and they do not correspond to a space level
|
|
var/list/available_nodes = list()
|
|
var/list/all_nodes = list() // filled_nodes | available_nodes
|
|
var/min_x = 0
|
|
var/min_y = 0
|
|
var/max_x = 0
|
|
var/max_y = 0
|
|
|
|
/datum/spacewalk_grid/New()
|
|
var/datum/point/P = new(0,0)
|
|
add_available_node(P)
|
|
|
|
/datum/spacewalk_grid/Destroy()
|
|
for(var/datum/point/P in filled_nodes)
|
|
release_node(P)
|
|
if(available_nodes.len > 1)
|
|
log_debug("Multiple nodes left behind after SW grid qdel: [available_nodes.len]")
|
|
for(var/datum/point/P in available_nodes)
|
|
log_debug("([P.x],[P.y])")
|
|
return ..()
|
|
|
|
/datum/spacewalk_grid/proc/add_available_node(datum/point/P)
|
|
var/hash = P.hash()
|
|
if(hash in all_nodes)
|
|
log_debug("Hash overlap! [hash]")
|
|
all_nodes[P.hash()] = P
|
|
available_nodes |= P
|
|
|
|
// We call this to flag a node as in use - all required variables will be
|
|
// ready after this is called
|
|
/datum/spacewalk_grid/proc/consume_node(datum/point/P)
|
|
filled_nodes |= P
|
|
available_nodes -= P
|
|
// This proc adds any new neighbors to the list of available nodes
|
|
P.get_empty_neighbors(src)
|
|
if(min_x > P.x)
|
|
min_x = P.x
|
|
else if(max_x < P.x)
|
|
max_x = P.x
|
|
if(min_y > P.y)
|
|
min_y = P.y
|
|
else if(max_y < P.y)
|
|
max_y = P.y
|
|
P.set_neighbors(src)
|
|
|
|
// You can call this with an active point, to remove it from the grid -
|
|
// this is important if you want to separate a z level from the network
|
|
/datum/spacewalk_grid/proc/release_node(datum/point/P)
|
|
filled_nodes -= P
|
|
available_nodes |= P
|
|
// Go through each neighbor node, and delete it if it's not
|
|
// next to any other active nodes
|
|
for(var/datum/point/P2 in P.neighbors)
|
|
var/isolated = P2.has_no_neighbors(src)
|
|
if(isolated)
|
|
if(!P2.spl)
|
|
available_nodes -= P2
|
|
all_nodes -= P2.hash()
|
|
qdel(P)
|
|
else
|
|
log_debug("Isolated z level at ([P2.x],[P2.y]): [P2.spl.zpos]")
|
|
P.deactivate()
|
|
P.neighbors.Cut()
|
|
|
|
// Now that we've cleaned out inactive nodes, we will update the bounds
|
|
var/datum/point/outer_bound
|
|
if(P.x == max_x)
|
|
for(var/i = min_y, i <= max_y, i++)
|
|
outer_bound = get(P.x, i)
|
|
if(!isnull(outer_bound)) // There is still an active node in this column
|
|
break
|
|
if(isnull(outer_bound))
|
|
max_x--
|
|
|
|
if(P.x == min_x)
|
|
for(var/i = min_y, i <= max_y, i++)
|
|
outer_bound = get(P.x, i)
|
|
if(!isnull(outer_bound)) // There is still an active node in this column
|
|
break
|
|
if(isnull(outer_bound))
|
|
min_x++
|
|
|
|
if(P.y == max_y)
|
|
for(var/i = min_x, i <= max_x, i++)
|
|
outer_bound = get(i, P.y)
|
|
if(!isnull(outer_bound)) // There is still an active node in this column
|
|
break
|
|
if(isnull(outer_bound))
|
|
max_y--
|
|
|
|
if(P.y == min_y)
|
|
for(var/i = min_x, i <= max_x, i++)
|
|
outer_bound = get(i, P.y)
|
|
if(!isnull(outer_bound)) // There is still an active node in this column
|
|
break
|
|
if(isnull(outer_bound))
|
|
min_y++
|
|
|
|
|
|
// If the node isn't in the grid, this will return null
|
|
/datum/spacewalk_grid/proc/get(x,y, allow_empty = 0)
|
|
var/datum/point/P = all_nodes["([x],[y])"]
|
|
if(!allow_empty && !(P in filled_nodes))
|
|
P = null // active nodes only
|
|
return P
|
|
|
|
/datum/spacewalk_grid/proc/get_width()
|
|
return 1 + max_x - min_x
|
|
|
|
/datum/spacewalk_grid/proc/get_height()
|
|
return 1 + max_y - min_y
|
|
|
|
// This function chooses an available point next to any node in the grid
|
|
/datum/spacewalk_grid/proc/get_empty_node()
|
|
var/datum/point/P = pick(available_nodes)
|
|
if(isnull(P))
|
|
throw EXCEPTION("The `available_nodes` list was either empty or contained a null entry")
|
|
consume_node(P)
|
|
return P
|
|
|
|
// This function is called repeatedly to build the map
|
|
/datum/spacewalk_grid/proc/add_level(datum/space_level/S)
|
|
var/datum/point/P = get_empty_node()
|
|
P.set_space_level(S)
|
|
|
|
|
|
// This proc substantiates the grid of points used to determine routes between levels
|
|
// Separating this from initialization gives us time in which we can add more crosslink z levels
|
|
// before we bake in all our connections
|
|
/datum/zlev_manager/proc/route_linkage()
|
|
var/list/crosslinks = list()
|
|
var/datum/space_level/D
|
|
for(var/A in z_list)
|
|
D = z_list[A]
|
|
if(D.linkage == CROSSLINKED)
|
|
crosslinks.Add(D)
|
|
D.reset_connections()
|
|
|
|
// We create an imaginary, square, grid, with dimensions that are
|
|
// twice the number of z levels, plus one, per side
|
|
|
|
// This is big enough to hold a straight line from the center to any side
|
|
// `point_grid` is indexed as a 2 way matrix of these points
|
|
// `grid` is a flat list of these same above points
|
|
|
|
// Each point represents a possible z level position
|
|
if(linkage_map)
|
|
qdel(linkage_map)
|
|
linkage_map = new
|
|
|
|
// Now, we pop entries in a random order from our list of space levels
|
|
// and assign its connections based on the grid
|
|
while(crosslinks.len)
|
|
D = pick(crosslinks)
|
|
crosslinks.Remove(D)
|
|
// Add it to our space grid
|
|
D.add_to_space_network(linkage_map)
|
|
|
|
// Used to loop through turfs in world, now just goes through each level's
|
|
// transit turf cache
|
|
/datum/zlev_manager/proc/setup_space_destinations(force_all_rebuilds = FALSE)
|
|
var/timer = start_watch()
|
|
log_debug("Assigning space turf destinations...")
|
|
var/datum/space_level/D
|
|
var/datum/space_level/E
|
|
var/turf/space/S
|
|
var/list/levels_to_rebuild = unbuilt_space_transitions
|
|
|
|
if(force_all_rebuilds)
|
|
// Assuming we don't have like 9000 zlevels this shouldn't hurt
|
|
levels_to_rebuild = list()
|
|
for(var/A in z_list)
|
|
levels_to_rebuild.Add(z_list[A])
|
|
|
|
|
|
for(var/foo in levels_to_rebuild) //Define the transitions of the z levels
|
|
D = foo
|
|
log_debug("Z level [D.zpos]")
|
|
switch(D.linkage)
|
|
if(UNAFFECTED)
|
|
for(var/B in D.transit_west | D.transit_east | D.transit_south | D.transit_north)
|
|
S = B
|
|
S.remove_transitions()
|
|
if(SELFLOOPING,CROSSLINKED)
|
|
// Left border
|
|
for(var/B in D.transit_west)
|
|
S = B
|
|
E = D.get_connection(Z_LEVEL_WEST)
|
|
S.set_transition_west(E.zpos)
|
|
|
|
// Right border
|
|
for(var/B in D.transit_east)
|
|
S = B
|
|
E = D.get_connection(Z_LEVEL_EAST)
|
|
S.set_transition_east(E.zpos)
|
|
|
|
// Bottom border
|
|
for(var/B in D.transit_south)
|
|
S = B
|
|
E = D.get_connection(Z_LEVEL_SOUTH)
|
|
S.set_transition_south(E.zpos)
|
|
|
|
// Top border
|
|
for(var/B in D.transit_north)
|
|
S = B
|
|
E = D.get_connection(Z_LEVEL_NORTH)
|
|
S.set_transition_north(E.zpos)
|
|
unbuilt_space_transitions -= D
|
|
|
|
log_debug("Assigning space turf destinations complete. Took [stop_watch(timer)]s.")
|
|
|
|
// Nothing fancy, just does it all at once
|
|
/datum/zlev_manager/proc/do_transition_setup()
|
|
route_linkage()
|
|
setup_space_destinations(force_all_rebuilds = TRUE)
|
|
|
|
// A debugging proc that expresses the map's shape as a bunch of turfs
|
|
/datum/zlev_manager/proc/map_as_turfs(turf/center)
|
|
// size is odd
|
|
// -1, /2 to get distance from the center
|
|
// center - radius = bottom left coordinate
|
|
var/datum/point/P
|
|
var/turf/our_spot
|
|
var/grid_desc = ""
|
|
for(var/i = linkage_map.min_x, i <= linkage_map.max_x, i++)
|
|
for(var/j = linkage_map.min_y, j <= linkage_map.max_y, j++)
|
|
P = linkage_map.get(i, j)
|
|
our_spot = locate(center.x + i, center.y + j, center.z)
|
|
grid_desc = "([i],[j])"
|
|
if(!isnull(P))
|
|
our_spot = our_spot.ChangeTurf(/turf/simulated/floor/plating/snow)
|
|
grid_desc += ": Z level [P.spl.zpos]. "
|
|
var/datum/space_level/up = P.spl.get_connection(Z_LEVEL_NORTH)
|
|
var/datum/space_level/down = P.spl.get_connection(Z_LEVEL_SOUTH)
|
|
var/datum/space_level/right = P.spl.get_connection(Z_LEVEL_EAST)
|
|
var/datum/space_level/left = P.spl.get_connection(Z_LEVEL_WEST)
|
|
grid_desc += "Up: [up.zpos], "
|
|
grid_desc += "Down: [down.zpos], "
|
|
grid_desc += "Right: [right.zpos], "
|
|
grid_desc += "Left: [left.zpos]"
|
|
else
|
|
our_spot = our_spot.ChangeTurf(/turf/simulated/floor/fakespace)
|
|
our_spot.desc = grid_desc
|