diff --git a/aurorastation.dme b/aurorastation.dme index ebefe865150..a9602f242d9 100644 --- a/aurorastation.dme +++ b/aurorastation.dme @@ -1573,6 +1573,14 @@ #include "code\modules\effects\map_effects\screen_shaker.dm" #include "code\modules\effects\map_effects\sound_emitter.dm" #include "code\modules\effects\map_effects\window_spawner.dm" +#include "code\modules\effects\maze_generation\maze_generator.dm" +#include "code\modules\effects\maze_generation\maze_generator_blockwise.dm" +#include "code\modules\effects\maze_generation\maze_helper_atoms.dm" +#include "code\modules\effects\maze_generation\helper_modules\_helper_module_base.dm" +#include "code\modules\effects\maze_generation\helper_modules\entry_exit.dm" +#include "code\modules\effects\maze_generation\loot_modules\_loot_module_base.dm" +#include "code\modules\effects\maze_generation\loot_modules\trash_module.dm" +#include "code\modules\effects\maze_generation\loot_modules\turf_painter.dm" #include "code\modules\effects\sparks\procs.dm" #include "code\modules\effects\sparks\spawner.dm" #include "code\modules\effects\sparks\visuals.dm" diff --git a/code/_helpers/maths.dm b/code/_helpers/maths.dm index 86443852787..617d61b223b 100644 --- a/code/_helpers/maths.dm +++ b/code/_helpers/maths.dm @@ -74,11 +74,8 @@ /proc/IsMultiple(x, y) return x % y == 0 -/proc/IsEven(x) - return !(x & 0x1) - -/proc/IsOdd(x) - return (x & 0x1) +#define ISEVEN(x) (x % 2 == 0) +#define ISODD(x) (x % 2 != 0) // Performs a linear interpolation between a and b. // Note: weight=0 returns a, weight=1 returns b, and weight=0.5 returns the mean of a and b. diff --git a/code/_helpers/time.dm b/code/_helpers/time.dm index f6d21a45b1f..522cfa584df 100644 --- a/code/_helpers/time.dm +++ b/code/_helpers/time.dm @@ -59,3 +59,16 @@ var/real_round_start_time if(!timevar) timevar = world.realtime return time2text(timevar, "YYYY-MM-DD hh:mm:ss") + +/** + * Returns "watch handle" (really just a timestamp :V) + */ +/proc/start_watch() + return REALTIMEOFDAY + +/** + * Returns number of seconds elapsed. + * @param wh number The "Watch Handle" from start_watch(). (timestamp) + */ +/proc/stop_watch(wh) + return round(0.1 * (REALTIMEOFDAY - wh), 0.1) \ No newline at end of file diff --git a/code/controllers/subsystems/initialization/misc_late.dm b/code/controllers/subsystems/initialization/misc_late.dm index fe6cf768b8a..b678515d6a4 100644 --- a/code/controllers/subsystems/initialization/misc_late.dm +++ b/code/controllers/subsystems/initialization/misc_late.dm @@ -44,6 +44,9 @@ else if(istype(thing, /mob/living/silicon/robot/drone/mining)) var/mob/living/silicon/robot/drone/mining/MD = thing MD.request_player() + else if(istype(thing, /obj/effect/mazegen/generator)) + var/obj/effect/mazegen/generator/MG = thing + MG.run_generator() LAZYREMOVE(SSatoms.late_misc_firers, thing) if (config.use_forumuser_api) diff --git a/code/game/objects/items/tajara.dm b/code/game/objects/items/tajara.dm index 74c4a800681..3ef88f94bc2 100644 --- a/code/game/objects/items/tajara.dm +++ b/code/game/objects/items/tajara.dm @@ -160,7 +160,7 @@ current_day += 59 + isLeap(text2num(time2text(world.realtime, "YYYY"))) // we can conveniently use the result of `isLeap` to add 1 when we are in a leap year var/real_time = text2num(time2text(world.time + (roundstart_hour HOURS), "hh")) var/adhomian_time = real_time - if(IsEven(current_day)) + if(ISEVEN(current_day)) adhomian_time = real_time + 24 adhomian_day = Floor(current_day / 2) to_chat(usr, "You check your [src.name], glancing over at the watch face, reading the time to be '[adhomian_time]'. Today's date is the '[adhomian_day]th day of [adhomian_month], [adhomian_year]'.") diff --git a/code/modules/effects/maze_generation/helper_modules/_helper_module_base.dm b/code/modules/effects/maze_generation/helper_modules/_helper_module_base.dm new file mode 100644 index 00000000000..a68030cfe8e --- /dev/null +++ b/code/modules/effects/maze_generation/helper_modules/_helper_module_base.dm @@ -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]") diff --git a/code/modules/effects/maze_generation/helper_modules/entry_exit.dm b/code/modules/effects/maze_generation/helper_modules/entry_exit.dm new file mode 100644 index 00000000000..ba6f2563c5a --- /dev/null +++ b/code/modules/effects/maze_generation/helper_modules/entry_exit.dm @@ -0,0 +1,12 @@ +// 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/crescent/MG in get_turf(src)) + qdel(MG) \ No newline at end of file diff --git a/code/modules/effects/maze_generation/loot_modules/_loot_module_base.dm b/code/modules/effects/maze_generation/loot_modules/_loot_module_base.dm new file mode 100644 index 00000000000..ea6a996343b --- /dev/null +++ b/code/modules/effects/maze_generation/loot_modules/_loot_module_base.dm @@ -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]") diff --git a/code/modules/effects/maze_generation/loot_modules/trash_module.dm b/code/modules/effects/maze_generation/loot_modules/trash_module.dm new file mode 100644 index 00000000000..68b42bb0afe --- /dev/null +++ b/code/modules/effects/maze_generation/loot_modules/trash_module.dm @@ -0,0 +1,6 @@ +/obj/effect/mazegen/module_loot/trash + name = "trash" + spawn_probability = 80 + +/obj/effect/mazegen/module_loot/trash/spawn_loot(turf/T) + new /obj/random/junk(T) \ No newline at end of file diff --git a/code/modules/effects/maze_generation/loot_modules/turf_painter.dm b/code/modules/effects/maze_generation/loot_modules/turf_painter.dm new file mode 100644 index 00000000000..4ba39728928 --- /dev/null +++ b/code/modules/effects/maze_generation/loot_modules/turf_painter.dm @@ -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" diff --git a/code/modules/effects/maze_generation/maze_generator.dm b/code/modules/effects/maze_generation/maze_generator.dm new file mode 100644 index 00000000000..d2e929679c3 --- /dev/null +++ b/code/modules/effects/maze_generation/maze_generator.dm @@ -0,0 +1,204 @@ +/* + 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 + + This Maze Generator was made by AffectedArc07, and was ported to Aurorastation from Paradise by me + - Geeves + +*/ + +// 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) + . = ..() + LAZYADD(SSatoms.late_misc_firers, src) + +/obj/effect/mazegen/generator/Destroy() + LAZYREMOVE(SSatoms.late_misc_firers, 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/crescent/WN = new(T) + WN.dir = NORTH + var/obj/structure/window/reinforced/crescent/WS = new(T) + WS.dir = SOUTH + var/obj/structure/window/reinforced/crescent/WE = new(T) + WE.dir = EAST + var/obj/structure/window/reinforced/crescent/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/crescent/W in T) + if(W.dir == text2num(D)) + qdel(W) + + // On both tiles + for(var/obj/structure/window/reinforced/crescent/W in T3) + if(W.dir == reverse_dir[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/crescent/W in T) + 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 diff --git a/code/modules/effects/maze_generation/maze_generator_blockwise.dm b/code/modules/effects/maze_generation/maze_generator_blockwise.dm new file mode 100644 index 00000000000..49cdb8d81cc --- /dev/null +++ b/code/modules/effects/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/simulated/wall + /// Material to make the floor out of + var/turf/floor_material = /turf/simulated/floor/plating + /// 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, reverse_dir[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/effects/maze_generation/maze_helper_atoms.dm b/code/modules/effects/maze_generation/maze_helper_atoms.dm new file mode 100644 index 00000000000..5d7469012fd --- /dev/null +++ b/code/modules/effects/maze_generation/maze_helper_atoms.dm @@ -0,0 +1,6 @@ +// Initial declaration +/obj/effect/mazegen + desc = "You should not be seeing this!" + icon = 'icons/mob/screen/generic.dmi' + icon_state = "x2" + color = "#00FF00" \ No newline at end of file diff --git a/html/changelogs/geeves-maint_maze.yml b/html/changelogs/geeves-maint_maze.yml new file mode 100644 index 00000000000..7d70d3a6594 --- /dev/null +++ b/html/changelogs/geeves-maint_maze.yml @@ -0,0 +1,6 @@ +author: Geeves + +delete-after: True + +changes: + - rscadd: "Ported Maze Generation from ParadiseStation. Replaced the maze next to research with the randomized maze." \ No newline at end of file diff --git a/maps/aurora/aurora-4_mainlevel.dmm b/maps/aurora/aurora-4_mainlevel.dmm index 05851864669..2d747a6adbf 100644 --- a/maps/aurora/aurora-4_mainlevel.dmm +++ b/maps/aurora/aurora-4_mainlevel.dmm @@ -44738,21 +44738,6 @@ }, /turf/simulated/floor/plating, /area/maintenance/research_port) -"bCo" = ( -/obj/structure/cable{ - d1 = 4; - d2 = 8; - icon_state = "4-8" - }, -/obj/machinery/atmospherics/pipe/simple/hidden/supply{ - dir = 4 - }, -/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{ - dir = 4 - }, -/obj/machinery/door/firedoor, -/turf/simulated/floor/plating, -/area/maintenance/research_port) "bCp" = ( /obj/structure/cable{ d1 = 1; @@ -45552,7 +45537,7 @@ /turf/simulated/floor/plating, /area/turbolift/command_sub) "bEc" = ( -/obj/random/loot, +/obj/effect/mazegen/module_helper/entry_exit, /turf/simulated/floor/plating, /area/maintenance/research_port) "bEe" = ( @@ -59735,9 +59720,9 @@ /turf/simulated/floor/tiled, /area/hallway/primary/aft) "dKN" = ( -/obj/random/junk, -/obj/effect/landmark{ - name = "Revenant" +/obj/effect/mazegen/generator/blockwise{ + mheight = 7; + mwidth = 5 }, /turf/simulated/floor/plating, /area/maintenance/research_port) @@ -68363,6 +68348,10 @@ /obj/effect/floor_decal/corner/grey/diagonal, /turf/simulated/floor/tiled/white, /area/crew_quarters/bar) +"tJU" = ( +/obj/effect/mazegen/module_loot/trash, +/turf/simulated/floor/plating, +/area/maintenance/research_port) "tKo" = ( /obj/effect/decal/cleanable/dirt, /obj/effect/decal/cleanable/flour, @@ -87743,13 +87732,13 @@ aab aab bBu beG -bbs -bbs bcY -bbs -bbs -bbs -bbs +bcY +bcY +bcY +bcY +bcY +dKN bJu bbs aox @@ -88000,13 +87989,13 @@ aab aab bBv beG -bbs -bcY -bcY -bQG bEc bcY -bbs +bcY +bcY +bcY +bcY +tJU beG bbs aox @@ -88257,14 +88246,14 @@ aab aab bBv beG -bbs -beC -bEc -bbs -bbs -beC -bbs -bCo +bcY +bcY +bcY +bcY +bcY +bcY +bcY +beG bbs bbs aox @@ -88514,13 +88503,13 @@ bGe aab bBv beG -bbs bcY -bbs -dKN -bbs bcY -bbs +bcY +bcY +bcY +bcY +bEc beG bFT bbs @@ -88771,7 +88760,7 @@ bzA aab bBv beG -bbs +bcY bcY bcY bcY