mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-07-13 08:03:43 +01:00
Merge pull request #15334 from AffectedArc07/maze-generator
AA's Monolith Maze Machine
This commit is contained in:
@@ -81,7 +81,7 @@
|
||||
#define INIT_ORDER_SHUTTLE -21
|
||||
#define INIT_ORDER_NIGHTSHIFT -22
|
||||
#define INIT_ORDER_NANOMOB -23
|
||||
#define INIT_ORDER_SQUEAK -40
|
||||
#define INIT_ORDER_LATE_MAPPING -40
|
||||
#define INIT_ORDER_PATH -50
|
||||
#define INIT_ORDER_PERSISTENCE -95
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// This subsystem is to initialize things which need to happen after SSatoms
|
||||
// This is for things which can take a long period of time and shouldnt bog down SSatoms
|
||||
// Use this for stuff like random room spawners or maze generators
|
||||
// Basically, this manages atom-based maploaders
|
||||
SUBSYSTEM_DEF(late_mapping)
|
||||
name = "Late Mapping"
|
||||
init_order = INIT_ORDER_LATE_MAPPING
|
||||
flags = SS_NO_FIRE
|
||||
/// List of all maze generators to process
|
||||
var/list/obj/effect/mazegen/generator/maze_generators = list()
|
||||
|
||||
/datum/controller/subsystem/late_mapping/Initialize(start_timeofday)
|
||||
if(length(maze_generators))
|
||||
log_startup_progress("Generating mazes...")
|
||||
for(var/i in maze_generators)
|
||||
var/obj/effect/mazegen/generator/MG = i
|
||||
MG.run_generator()
|
||||
return ..()
|
||||
@@ -0,0 +1,14 @@
|
||||
/obj/effect/mazegen/module_helper
|
||||
name = "maze helper"
|
||||
|
||||
/**
|
||||
* Helper handler proc
|
||||
*
|
||||
* This exists as an overridable method so you can do custom helper logic.
|
||||
* An example of this is removing all windows on a tile.
|
||||
*
|
||||
* Arguments:
|
||||
* * blockwise - This will be TRUE if the maze this is running on is a blockwise maze
|
||||
*/
|
||||
/obj/effect/mazegen/module_helper/proc/helper_run(blockwise = FALSE, obj/effect/mazegen/host)
|
||||
CRASH("spawn_loot() not overriden for [type]")
|
||||
@@ -0,0 +1,13 @@
|
||||
// Forces entry and exit points
|
||||
/obj/effect/mazegen/module_helper/entry_exit
|
||||
name = "entrance/exit allocator"
|
||||
|
||||
/obj/effect/mazegen/module_helper/entry_exit/helper_run(blockwise = FALSE, obj/effect/mazegen/host)
|
||||
if(blockwise)
|
||||
var/turf/T = get_turf(src)
|
||||
var/obj/effect/mazegen/generator/blockwise/BWG = host
|
||||
T.ChangeTurf(BWG.floor_material)
|
||||
else
|
||||
for(var/obj/structure/window/reinforced/mazeglass/MG in get_turf(src))
|
||||
qdel(MG)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/obj/effect/mazegen/module_loot
|
||||
name = "maze loot"
|
||||
/// Probability for this to spawn in a dead end at all. 0-100%. I dont recommend using values higher than 10
|
||||
var/spawn_probability = 0
|
||||
|
||||
/**
|
||||
* Loot spawner proc
|
||||
*
|
||||
* This exists as an overridable method so you can do more than just spawn objects.
|
||||
* An example of this is spawning a specific mob based on a condition.
|
||||
*
|
||||
* Arguments:
|
||||
* * T - The turf loot will be spawned on
|
||||
*/
|
||||
/obj/effect/mazegen/module_loot/proc/spawn_loot(turf/T)
|
||||
CRASH("spawn_loot() not overriden for [type]")
|
||||
@@ -0,0 +1,7 @@
|
||||
// Debug spawner
|
||||
/obj/effect/mazegen/module_loot/turf_painter
|
||||
name = "turf painter"
|
||||
spawn_probability = 100
|
||||
|
||||
/obj/effect/mazegen/module_loot/turf_painter/spawn_loot(turf/T)
|
||||
T.color = "#12a5f4"
|
||||
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
Everything in this file is related to the actual maze generator itself
|
||||
This does the brunt of the work and handles the actual calculations of the mazes
|
||||
|
||||
It is very finnicky code and isnt easy to work with since BYOND doesnt like wallwise mazes, and this would be much better being blockwise
|
||||
|
||||
I dont recommend touching the stuff in here, its very finnicky and can be difficult to get your head around if youre not familiar with maze generation algorithms
|
||||
-AA07
|
||||
|
||||
*/
|
||||
|
||||
// Nice way to format logs
|
||||
#define LOG_MAZE_PROGRESS(proc2run, opname) \
|
||||
do { \
|
||||
var/timer = start_watch(); \
|
||||
proc2run ;\
|
||||
log_debug("\[MAZE] Operation '[opname]' on maze at [##x],[##y],[##z] took [stop_watch(timer)]s"); \
|
||||
} while (FALSE)
|
||||
|
||||
|
||||
// These defines are used to mark the cells as explored or not
|
||||
#define MAZEGEN_TURF_UNSEARCHED "#ff0000"
|
||||
#define MAZEGEN_TURF_CELL "#00ff00"
|
||||
|
||||
// Dont place this in the very corner of a map. It relies on adjacent turfs, and at the very edges you dont have turfs on all sides
|
||||
/obj/effect/mazegen/generator
|
||||
name = "maze generator"
|
||||
/// List of turfs to iterate in total
|
||||
var/list/turf_list = list()
|
||||
/// "Stack" structure to be used while iterating
|
||||
var/list/working_stack = list()
|
||||
/// List of all loot modules being used in this maze
|
||||
var/list/obj/effect/mazegen/module_loot/loot_modules = list()
|
||||
/// List of all helper modules being used in this maze
|
||||
var/list/obj/effect/mazegen/module_helper/helper_modules = list()
|
||||
/// Potential loot spots
|
||||
var/list/turf/potential_loot_spots = list()
|
||||
/// Maze width
|
||||
var/mwidth = 0
|
||||
/// Maze height
|
||||
var/mheight = 0
|
||||
|
||||
/obj/effect/mazegen/generator/Initialize(mapload)
|
||||
. = ..()
|
||||
SSlate_mapping.maze_generators += src
|
||||
|
||||
/obj/effect/mazegen/generator/Destroy()
|
||||
SSlate_mapping.maze_generators -= src
|
||||
return ..()
|
||||
|
||||
// "Push" a turf to the working "stack"
|
||||
/obj/effect/mazegen/generator/proc/push_turf(turf/T)
|
||||
working_stack.Add(T) // Add it to the end of the list
|
||||
|
||||
// "Pop" a turf off the working "stack"
|
||||
/obj/effect/mazegen/generator/proc/pop_turf()
|
||||
var/turf/T = working_stack[length(working_stack)] // Get the last item in the list
|
||||
working_stack.Remove(T) // Take it off the top
|
||||
return T // Send it back
|
||||
|
||||
// Main generator runner. Override on children
|
||||
/obj/effect/mazegen/generator/proc/run_generator()
|
||||
ASSERT(ISODD(mwidth))
|
||||
ASSERT(ISODD(mheight))
|
||||
var/total_time = start_watch()
|
||||
log_debug("\[MAZE] Started generation on maze at [x],[y],[z] | [mwidth * mheight] turfs total")
|
||||
LOG_MAZE_PROGRESS(generate_path(), "Path Generation")
|
||||
LOG_MAZE_PROGRESS(apply_helper_modules(FALSE), "Helper Modules")
|
||||
if(length(loot_modules)) // Only bother with this if we have some
|
||||
LOG_MAZE_PROGRESS(calculate_loot_spots(), "Loot Spot Calculation")
|
||||
LOG_MAZE_PROGRESS(apply_loot_modules(), "Loot Modules")
|
||||
log_debug("\[MAZE] Generation of maze at [x],[y],[z] complete within [stop_watch(total_time)]s")
|
||||
qdel(src)
|
||||
|
||||
/obj/effect/mazegen/generator/proc/generate_path()
|
||||
// Setup our turf list
|
||||
var/width = clamp(mwidth, 0, (world.maxx - x)) // Make sure they dont loop off the world
|
||||
var/height = clamp(mheight, 0, (world.maxy - y)) // Make sure they dont loop off the world
|
||||
|
||||
// Generate a turf stack
|
||||
for(var/target_x in 0 to (width - 1)) // -1 so it spawns on the right tile
|
||||
for(var/target_y in 0 to (height - 1))
|
||||
var/turf/T = locate((x + target_x), (y + target_y), z)
|
||||
// Mark as unsearched
|
||||
T.color = MAZEGEN_TURF_UNSEARCHED
|
||||
// Throw it in the list
|
||||
turf_list |= T
|
||||
|
||||
// Windows time
|
||||
var/obj/structure/window/reinforced/mazeglass/WN = new(T)
|
||||
WN.dir = NORTH
|
||||
var/obj/structure/window/reinforced/mazeglass/WS = new(T)
|
||||
WS.dir = SOUTH
|
||||
var/obj/structure/window/reinforced/mazeglass/WE = new(T)
|
||||
WE.dir = EAST
|
||||
var/obj/structure/window/reinforced/mazeglass/WW = new(T)
|
||||
WW.dir = WEST
|
||||
CHECK_TICK
|
||||
CHECK_TICK
|
||||
|
||||
// Do the actual work
|
||||
push_turf(turf_list[1]) // Use the first turf as the stack base
|
||||
while(length(working_stack))
|
||||
var/turf/T = pop_turf()
|
||||
|
||||
// If you dont force cast these to strings, stuff cries. A lot.
|
||||
var/list/cardinals = list("[NORTH]", "[SOUTH]", "[EAST]", "[WEST]")
|
||||
|
||||
// Unvisited turfs
|
||||
var/list/turf/unvisited_neighbours = list()
|
||||
|
||||
// Check all cardinal turfs
|
||||
for(var/D in cardinals)
|
||||
var/turf/T2 = get_step(T, text2num(D))
|
||||
if(T2.color == MAZEGEN_TURF_UNSEARCHED)
|
||||
unvisited_neighbours["[D]"] += T2
|
||||
|
||||
if(length(unvisited_neighbours))
|
||||
push_turf(T)
|
||||
var/D = pick(unvisited_neighbours)
|
||||
var/turf/T3 = unvisited_neighbours["[D]"] // Pick random dir turf
|
||||
|
||||
// Remove the window between the two
|
||||
for(var/obj/structure/window/reinforced/mazeglass/W in T)
|
||||
if(W.dir == text2num(D))
|
||||
qdel(W)
|
||||
|
||||
// On both tiles
|
||||
for(var/obj/structure/window/reinforced/mazeglass/W in T3)
|
||||
if(W.dir == GetOppositeDir(text2num(D)))
|
||||
qdel(W)
|
||||
|
||||
// Mark as visited
|
||||
T3.color = MAZEGEN_TURF_CELL
|
||||
push_turf(T3)
|
||||
|
||||
CHECK_TICK
|
||||
|
||||
// The walls have been generated, now cleanup turfs and gather helpers
|
||||
for(var/i in turf_list)
|
||||
var/turf/T = i
|
||||
// Reset markings
|
||||
T.color = null
|
||||
|
||||
// Gather loot modules
|
||||
for(var/obj/effect/mazegen/module_loot/ML in T)
|
||||
loot_modules |= ML
|
||||
CHECK_TICK
|
||||
|
||||
// Gather helper modules
|
||||
for(var/obj/effect/mazegen/module_helper/MH in T)
|
||||
helper_modules |= MH
|
||||
CHECK_TICK
|
||||
|
||||
// Make sure we have adequate CPU
|
||||
CHECK_TICK
|
||||
|
||||
/obj/effect/mazegen/generator/proc/apply_helper_modules(blockwise = FALSE)
|
||||
// Apply helpers
|
||||
for(var/i in helper_modules)
|
||||
var/obj/effect/mazegen/module_helper/MH = i
|
||||
helper_modules -= MH // Make it happy
|
||||
MH.helper_run(blockwise, src)
|
||||
qdel(MH)
|
||||
|
||||
/obj/effect/mazegen/generator/proc/calculate_loot_spots()
|
||||
for(var/i in turf_list)
|
||||
var/turf/T = i
|
||||
// Gather the windows. If a spot has 3 windows on it, its a dead end
|
||||
var/windowcount = 0
|
||||
for(var/obj/structure/window/reinforced/mazeglass/W in T)
|
||||
pass(W) // Stops DM whining about unused vars
|
||||
windowcount++
|
||||
CHECK_TICK
|
||||
|
||||
// Its a dead end. Mark for loot.
|
||||
if(windowcount == 3)
|
||||
potential_loot_spots |= T
|
||||
|
||||
CHECK_TICK
|
||||
|
||||
/obj/effect/mazegen/generator/proc/apply_loot_modules()
|
||||
for(var/i in potential_loot_spots)
|
||||
var/turf/T = i
|
||||
|
||||
// Apply loot modules
|
||||
var/obj/effect/mazegen/module_loot/ML = pick(loot_modules)
|
||||
if(prob(ML.spawn_probability))
|
||||
ML.spawn_loot(T) // Spawn on the turf
|
||||
|
||||
CHECK_TICK
|
||||
|
||||
// Cleanup loot modules
|
||||
for(var/i in loot_modules)
|
||||
var/obj/effect/mazegen/module_loot/ML = i
|
||||
loot_modules -= ML // Take it out to make the GC happy
|
||||
qdel(ML)
|
||||
CHECK_TICK
|
||||
|
||||
|
||||
#undef MAZEGEN_TURF_UNSEARCHED
|
||||
#undef MAZEGEN_TURF_CELL
|
||||
@@ -0,0 +1,124 @@
|
||||
// Everything in this file is related to the blockwise maze generator.
|
||||
// It uses some components from the standard, but tweaks them to its own needs, since it needs to generate a half size maze then expand it out
|
||||
|
||||
#define MAZEGEN_TURF_UNSEARCHED "#ff0000"
|
||||
#define MAZEGEN_TURF_CELL "#00ff00"
|
||||
|
||||
/obj/effect/mazegen/generator/blockwise
|
||||
name = "blockwise maze generator"
|
||||
/// Material to make the walls out of
|
||||
var/turf/wall_material = /turf/simulated/wall/indestructible/riveted
|
||||
/// Material to make the floor out of
|
||||
var/turf/floor_material = /turf/simulated/floor/engine
|
||||
/// List of open spots (speeds up calculations)
|
||||
var/list/turf/open_spots = list()
|
||||
|
||||
// First we need to override the main proc so it only calculates half
|
||||
/obj/effect/mazegen/generator/blockwise/run_generator()
|
||||
ASSERT(ISODD(mwidth))
|
||||
ASSERT(ISODD(mheight))
|
||||
var/total_time = start_watch()
|
||||
log_debug("\[MAZE] Started generation on maze at [x],[y],[z] | [mwidth * mheight] turfs total")
|
||||
// First we need to partition the maze out into forced walls and open cells
|
||||
LOG_MAZE_PROGRESS(generate_path(), "Generate Path")
|
||||
LOG_MAZE_PROGRESS(apply_helper_modules(TRUE), "Helper Modules")
|
||||
if(length(loot_modules)) // Only bother with this if we have some
|
||||
LOG_MAZE_PROGRESS(calculate_loot_spots(), "Loot Spot Calculation")
|
||||
LOG_MAZE_PROGRESS(apply_loot_modules(), "Loot Modules")
|
||||
log_debug("\[MAZE] Generation of maze at [x],[y],[z] complete within [stop_watch(total_time)]s")
|
||||
qdel(src)
|
||||
|
||||
|
||||
/obj/effect/mazegen/generator/blockwise/generate_path()
|
||||
// Setup our turf list
|
||||
var/width = clamp(mwidth, 0, (world.maxx - x)) // Make sure they dont loop off the world
|
||||
var/height = clamp(mheight, 0, (world.maxy - y)) // Make sure they dont loop off the world
|
||||
|
||||
// Generate a turf stack
|
||||
for(var/target_x in 0 to (width - 1)) // -1 so it spawns on the right tile
|
||||
for(var/target_y in 0 to (height - 1))
|
||||
var/turf/T = locate((x + target_x), (y + target_y), z)
|
||||
// Mark as unsearched
|
||||
T.color = MAZEGEN_TURF_UNSEARCHED
|
||||
// Throw it in the list
|
||||
turf_list |= T
|
||||
CHECK_TICK
|
||||
CHECK_TICK
|
||||
|
||||
// Do the actual work
|
||||
push_turf(turf_list[1]) // Use the first turf as the stack base
|
||||
while(length(working_stack))
|
||||
var/turf/T = pop_turf()
|
||||
|
||||
// If you dont force cast these to strings, stuff cries. A lot.
|
||||
var/list/cardinals = list("[NORTH]", "[SOUTH]", "[EAST]", "[WEST]")
|
||||
|
||||
// Unvisited turfs
|
||||
var/list/turf/unvisited_neighbours = list()
|
||||
|
||||
// Check all cardinal turfs
|
||||
for(var/D in cardinals)
|
||||
var/turf/T2 = get_step(get_step(T, text2num(D)), text2num(D)) // Get the step TWICE because this is wallwise
|
||||
if(T2?.color == MAZEGEN_TURF_UNSEARCHED)
|
||||
unvisited_neighbours["[D]"] += T2
|
||||
|
||||
if(length(unvisited_neighbours))
|
||||
push_turf(T)
|
||||
var/D = pick(unvisited_neighbours)
|
||||
var/turf/T3 = unvisited_neighbours["[D]"] // Pick random dir turf
|
||||
|
||||
// Remove the color between the two
|
||||
var/turf/T4 = get_step(T3, GetOppositeDir(text2num(D)))
|
||||
T4?.color = MAZEGEN_TURF_CELL
|
||||
|
||||
// Mark as visited
|
||||
T3.color = MAZEGEN_TURF_CELL
|
||||
push_turf(T3)
|
||||
|
||||
CHECK_TICK
|
||||
|
||||
// The walls have been generated, now cleanup turfs and gather helpers
|
||||
for(var/i in turf_list)
|
||||
var/turf/T = i
|
||||
// Set correct turfs
|
||||
if(T.color == MAZEGEN_TURF_CELL)
|
||||
open_spots |= T
|
||||
T.ChangeTurf(floor_material)
|
||||
else
|
||||
T.ChangeTurf(wall_material)
|
||||
T.color = null
|
||||
|
||||
// Gather loot modules
|
||||
for(var/obj/effect/mazegen/module_loot/ML in T)
|
||||
loot_modules |= ML
|
||||
CHECK_TICK
|
||||
|
||||
// Gather helper modules
|
||||
for(var/obj/effect/mazegen/module_helper/MH in T)
|
||||
helper_modules |= MH
|
||||
CHECK_TICK
|
||||
|
||||
// Make sure we have adequate CPU
|
||||
CHECK_TICK
|
||||
|
||||
/obj/effect/mazegen/generator/blockwise/calculate_loot_spots()
|
||||
for(var/i in open_spots)
|
||||
var/turf/T = i
|
||||
// Gather amount of dense turfs on each side
|
||||
var/dense_surrounding_turfs = 0
|
||||
// Again, these gotta be casted like this
|
||||
var/list/cardinals = list("[NORTH]", "[EAST]", "[SOUTH]", "[WEST]")
|
||||
for(var/D in cardinals)
|
||||
var/turf/T2 = get_step(T, text2num(D))
|
||||
if(T2.density)
|
||||
dense_surrounding_turfs++
|
||||
CHECK_TICK
|
||||
|
||||
// Its a dead end. Mark for loot.
|
||||
if(dense_surrounding_turfs == 3)
|
||||
potential_loot_spots |= T
|
||||
|
||||
CHECK_TICK
|
||||
|
||||
#undef MAZEGEN_TURF_UNSEARCHED
|
||||
#undef MAZEGEN_TURF_CELL
|
||||
@@ -0,0 +1,16 @@
|
||||
// Initial declaration
|
||||
/obj/effect/mazegen
|
||||
desc = "You should not be seeing this!"
|
||||
icon = 'icons/mob/screen_gen.dmi'
|
||||
icon_state = "x2"
|
||||
color = "#00FF00"
|
||||
|
||||
// Windows which cant be taken apart. Thes are subtypes of windows so you can have atoms you can walk in at some dirs bit not others
|
||||
/obj/structure/window/reinforced/mazeglass
|
||||
name = "maze glass"
|
||||
desc = "You cant take this down. Looks like you have to solve the maze."
|
||||
resistance_flags = INDESTRUCTIBLE
|
||||
|
||||
// No taking apart
|
||||
/obj/structure/window/reinforced/mazeglass/tool_act(mob/living/user, obj/item/I, tool_type)
|
||||
return FALSE
|
||||
@@ -242,6 +242,7 @@
|
||||
#include "code\controllers\subsystem\input.dm"
|
||||
#include "code\controllers\subsystem\ipintel.dm"
|
||||
#include "code\controllers\subsystem\jobs.dm"
|
||||
#include "code\controllers\subsystem\late_mapping.dm"
|
||||
#include "code\controllers\subsystem\lighting.dm"
|
||||
#include "code\controllers\subsystem\machinery.dm"
|
||||
#include "code\controllers\subsystem\mapping.dm"
|
||||
@@ -1724,6 +1725,13 @@
|
||||
#include "code\modules\martial_arts\combos\sleeping_carp\head_kick.dm"
|
||||
#include "code\modules\martial_arts\combos\sleeping_carp\stomach_knee.dm"
|
||||
#include "code\modules\martial_arts\combos\sleeping_carp\wrist_wrench.dm"
|
||||
#include "code\modules\maze_generation\maze_generator.dm"
|
||||
#include "code\modules\maze_generation\maze_generator_blockwise.dm"
|
||||
#include "code\modules\maze_generation\maze_helper_atoms.dm"
|
||||
#include "code\modules\maze_generation\helper_modules\_helper_module_base.dm"
|
||||
#include "code\modules\maze_generation\helper_modules\entry_exit.dm"
|
||||
#include "code\modules\maze_generation\loot_modules\_loot_module_base.dm"
|
||||
#include "code\modules\maze_generation\loot_modules\turf_painter.dm"
|
||||
#include "code\modules\mining\abandonedcrates.dm"
|
||||
#include "code\modules\mining\fulton.dm"
|
||||
#include "code\modules\mining\machine_processing.dm"
|
||||
|
||||
Reference in New Issue
Block a user