From 55612c43f78e6efceb11889ae9f89c36ae9e27cd Mon Sep 17 00:00:00 2001 From: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com> Date: Sun, 24 Jan 2021 19:03:53 +0000 Subject: [PATCH] Blockwise mazes --- code/controllers/subsystem/late_mapping.dm | 2 +- .../helper_modules/_helper_module_base.dm | 6 +- .../helper_modules/entry_exit.dm | 12 +- .../loot_modules/_loot_module_base.dm | 1 - .../modules/maze_generation/maze_generator.dm | 100 +++++++++----- .../maze_generator_blockwise.dm | 124 ++++++++++++++++++ .../maze_generation/maze_helper_atoms.dm | 1 + paradise.dme | 1 + 8 files changed, 203 insertions(+), 44 deletions(-) create mode 100644 code/modules/maze_generation/maze_generator_blockwise.dm diff --git a/code/controllers/subsystem/late_mapping.dm b/code/controllers/subsystem/late_mapping.dm index 799b07dfec6..719ddef2b36 100644 --- a/code/controllers/subsystem/late_mapping.dm +++ b/code/controllers/subsystem/late_mapping.dm @@ -14,5 +14,5 @@ SUBSYSTEM_DEF(late_mapping) log_startup_progress("Generating mazes...") for(var/i in maze_generators) var/obj/effect/mazegen/generator/MG = i - MG.generate() + MG.run_generator() return ..() diff --git a/code/modules/maze_generation/helper_modules/_helper_module_base.dm b/code/modules/maze_generation/helper_modules/_helper_module_base.dm index b68efe10747..a68030cfe8e 100644 --- a/code/modules/maze_generation/helper_modules/_helper_module_base.dm +++ b/code/modules/maze_generation/helper_modules/_helper_module_base.dm @@ -1,12 +1,14 @@ /obj/effect/mazegen/module_helper name = "maze helper" - desc = "You should not be seeing this!" /** * 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() +/obj/effect/mazegen/module_helper/proc/helper_run(blockwise = FALSE, obj/effect/mazegen/host) CRASH("spawn_loot() not overriden for [type]") diff --git a/code/modules/maze_generation/helper_modules/entry_exit.dm b/code/modules/maze_generation/helper_modules/entry_exit.dm index 0922d636406..3c17d4f719c 100644 --- a/code/modules/maze_generation/helper_modules/entry_exit.dm +++ b/code/modules/maze_generation/helper_modules/entry_exit.dm @@ -2,8 +2,12 @@ /obj/effect/mazegen/module_helper/entry_exit name = "entrance/exit allocator" -/obj/effect/mazegen/module_helper/entry_exit/helper_run() - for(var/obj/structure/window/reinforced/mazeglass/MG in get_turf(src)) - qdel(MG) +/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) - qdel(src) diff --git a/code/modules/maze_generation/loot_modules/_loot_module_base.dm b/code/modules/maze_generation/loot_modules/_loot_module_base.dm index 5afd1d3e611..ea6a996343b 100644 --- a/code/modules/maze_generation/loot_modules/_loot_module_base.dm +++ b/code/modules/maze_generation/loot_modules/_loot_module_base.dm @@ -1,6 +1,5 @@ /obj/effect/mazegen/module_loot name = "maze loot" - desc = "You should not be seeing this!" /// 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 diff --git a/code/modules/maze_generation/maze_generator.dm b/code/modules/maze_generation/maze_generator.dm index a39c786c7ca..96f6392a3f1 100644 --- a/code/modules/maze_generation/maze_generator.dm +++ b/code/modules/maze_generation/maze_generator.dm @@ -9,6 +9,15 @@ */ +// 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" @@ -16,17 +25,16 @@ // 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" - desc = "You should not be seeing this!" /// 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() - /// Total turfs - var/total_turfs = 0 - /// Generation start time - var/start_time = 0 + /// 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 @@ -50,13 +58,21 @@ working_stack.Remove(T) // Take it off the top return T // Send it back -// This is the main proc that does the work. It can get very performance intensive if youre not careful -// I was testing this and I managed to make the MC erase itself. Dont ask. -/obj/effect/mazegen/generator/proc/generate() - // Setup some initial vars - var/start_time = start_watch() - log_debug("Starting to generate maze at [x],[y],[z] (WxH: [mwidth]x[mheight])") +// 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 @@ -82,9 +98,6 @@ CHECK_TICK CHECK_TICK - total_turfs = length(turf_list) - log_debug("Maze at [x],[y],[z] has [total_turfs] turfs to process") - // Do the actual work push_turf(turf_list[1]) // Use the first turf as the stack base while(length(working_stack)) @@ -134,41 +147,56 @@ loot_modules |= ML CHECK_TICK - // Apply helper modules + // Gather helper modules for(var/obj/effect/mazegen/module_helper/MH in T) - MH.helper_run() + helper_modules |= MH CHECK_TICK // Make sure we have adequate CPU CHECK_TICK - // Apply them. Presence check the list first to save CPU. - if(length(loot_modules)) - 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. Apply loot. - if(windowcount == 3) - // Grab a random one - var/obj/effect/mazegen/module_loot/ML = pick(loot_modules) - if(prob(ML.spawn_probability)) - ML.spawn_loot(T) // Spawn on the turf +/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/obj/effect/mazegen/module_loot/ML in 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 - log_debug("Generation of maze at [x],[y],[z] completed within [stop_watch(start_time)] seconds") - qdel(src) #undef MAZEGEN_TURF_UNSEARCHED #undef MAZEGEN_TURF_CELL diff --git a/code/modules/maze_generation/maze_generator_blockwise.dm b/code/modules/maze_generation/maze_generator_blockwise.dm new file mode 100644 index 00000000000..2e6da911a82 --- /dev/null +++ b/code/modules/maze_generation/maze_generator_blockwise.dm @@ -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/unsimulated/wall + /// 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 diff --git a/code/modules/maze_generation/maze_helper_atoms.dm b/code/modules/maze_generation/maze_helper_atoms.dm index 85ea6f9a880..3e4c4dfd02f 100644 --- a/code/modules/maze_generation/maze_helper_atoms.dm +++ b/code/modules/maze_generation/maze_helper_atoms.dm @@ -1,5 +1,6 @@ // Initial declaration /obj/effect/mazegen + desc = "You should not be seeing this!" icon = 'icons/mob/screen_gen.dmi' icon_state = "x2" color = "#00FF00" diff --git a/paradise.dme b/paradise.dme index e33326b909b..1b9bc64a2f0 100644 --- a/paradise.dme +++ b/paradise.dme @@ -1721,6 +1721,7 @@ #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"