[MDB IGNORE] Procedurally Generated Maints (Sorry no Gax in this PR just the tools) (#20555)

* i think it's ready

* this rustg define should match what it will be after the rust code is merged

* adding some documentation

* ready again

* my rust dll was out of date lmao

* fix things that makes biome cry

* i think it's ready

* this rustg define should match what it will be after the rust code is merged

* will this fix the linter?

* was that the issue?

* rust update

* ready again

* rip multi-z gax, you will return later

* my rust dll was out of date lmao

* fix things that makes biome cry

* why do these changes keep lingering

* that's already done

* is this alphabetical now

* why was there duplicates that required me to use the web editor to fix

* how did you get deleted

* that would have been bad lmao

* probably not super necessary but whatever

* that was bothering me

* that's already defined

* ok i think that's all the cleanup

* lastly stairs
This commit is contained in:
Chubbygummibear
2024-04-01 15:10:51 -07:00
committed by GitHub
parent 7a893fb6c3
commit 093235868d
35 changed files with 24693 additions and 14 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -77,6 +77,7 @@ Always compile, always use that verb, and always make sure that it works for wha
#define ZTRAIT_STATION "Station"
#define ZTRAIT_MINING "Mining"
#define ZTRAIT_REEBE "Reebe"
#define ZTRAIT_PROCEDURAL_MAINTS "Random Maints"
#define ZTRAIT_RESERVED "Transit/Reserved"
#define ZTRAIT_AWAY "Away Mission"
#define ZTRAIT_SPACE_RUINS "Space Ruins"
@@ -143,6 +144,14 @@ Always compile, always use that verb, and always make sure that it works for wha
#define ZTRAITS_REEBE list(ZTRAIT_REEBE = TRUE, ZTRAIT_BOMBCAP_MULTIPLIER = 0.60)
#define ZTRAITS_BACKROOM_MAINTS list(\
ZTRAIT_PROCEDURAL_MAINTS = TRUE, \
ZTRAIT_LINKAGE = SELFLOOPING, \
ZTRAIT_GRAVITY = TRUE, \
ZTRAIT_NOPHASE = TRUE, \
ZTRAIT_NOXRAY = TRUE, \
ZTRAIT_BASETURF = /turf/open/floor/plating)
///Z level traits for Away Missions
#define ZTRAITS_AWAY list(ZTRAIT_AWAY = TRUE)
///Z level traits for Secret Away Missions

View File

@@ -91,6 +91,31 @@
*/
#define rustg_acreplace_with_replacements(key, text, replacements) RUSTG_CALL(RUST_G, "acreplace_with_replacements")(key, text, json_encode(replacements))
/**
* This proc generates rooms in a specified area of random size and placement. Essential for procedurally generated areas, BSP works by cutting a given area in half,
* then cutting one of those subsections in half, and repeating this process until a minimum size is reached, then backtracking to other subsections that are not of
* the minimum size yet. These cuts are offset by small random amounts so that the sections are all varied in size and shape.
*
* BSP excels at creating rooms or areas with a relatively even distribution over an area, so there won't be too much blank open area. However if you discard rooms that
* overlap pre-existing map structures or areas, you may still get blank areas where nothing interesting appears.
*
* Return:
* * a json list of room data to be processed by json_decode in byond and further processed there.
*
* Arguments:
* * width: the width of the area to generate in
* * height: the height of the area to generate in
* * hash: the rng seed the generator will use for this instance
* * map_subsection_min_size: The minimum size of a map subsection. When using this for rooms with walls, the minimum possible square will be a 5x5 room. Barring walls,
* this will be a 3x3 room. The maximum size will be 9x9, because a further cut could reduce this size beneath the minimum size.
* * map_subsection_min_room_width: The minimum room width once the subsections are finalized. Room width and height are random between this amount, and the subsection
* max size
* * map_subsection_min_room_height: The minimum room height once the subsections are finalized. Room width and height are random between this amount, and the subsection
* max size
*/
#define rustg_bsp_generate(width, height, hash, map_subsection_min_size, map_subsection_min_room_width, map_subsection_min_room_height) \
RUSTG_CALL(RUST_G, "bsp_generate")(width, height, hash, map_subsection_min_size, map_subsection_min_room_width, map_subsection_min_room_height)
/**
* This proc generates a cellular automata noise grid which can be used in procedural generation methods.
*
@@ -216,6 +241,31 @@
*/
#define rustg_generate_path_astar(start_node_id, goal_node_id) RUSTG_CALL(RUST_G, "generate_path_astar")(start_node_id, goal_node_id)
/**
* This proc generates rooms in a specified area of random size and placement. Used in procedural generation, but far less intensively than Binary Space Partitioning
* due to Random Room Placement being far more simple and unreliable for area coverage. These rooms will not overlap one another, but that is the only logic
* they do. The room dimensions returned by this call are hardcoded to be the dimensions of maint ruins so that I could sprinkle pre-generated areas over
* the binary space rooms that are random.
* These dimensions are:
* * 3x3
* * 3x5
* * 5x3
* * 5x4
* * 10x5
* * 10x10
*
* Return:
* * a json list of room data to be processed by json_decode in byond and further processed there.
*
* Arguments:
* * width: the width of the area to generate in
* * height: the height of the area to generate in
* * desired_room_count: the number of rooms you want generated and returned
* * hash: the rng seed the generator will use for this instance
*/
#define rustg_random_room_generate(width, height, desired_room_count, hash) \
RUSTG_CALL(RUST_G, "random_room_generate")(width, height, desired_room_count, hash)
#define RUSTG_REDIS_ERROR_CHANNEL "RUSTG_REDIS_ERROR_CHANNEL"
#define rustg_redis_connect(addr) RUSTG_CALL(RUST_G, "redis_connect")(addr)
@@ -285,5 +335,3 @@
*/
#define rustg_worley_generate(region_size, threshold, node_per_region_chance, size, node_min, node_max) \
RUSTG_CALL(RUST_G, "worley_generate")(region_size, threshold, node_per_region_chance, size, node_min, node_max)

View File

@@ -146,6 +146,7 @@
#define INIT_ORDER_QUIRKS 73
#define INIT_ORDER_EVENTS 70
#define INIT_ORDER_JOBS 65
#define INIT_ORDER_PATH 61
#define INIT_ORDER_TICKER 55
#define INIT_ORDER_MAPPING 50
#define INIT_ORDER_EARLY_ASSETS 48
@@ -172,7 +173,6 @@
#define INIT_ORDER_SHUTTLE -21
#define INIT_ORDER_MINOR_MAPPING -40
#define INIT_ORDER_BLUESPACE_LOCKER -45
#define INIT_ORDER_PATH -50
#define INIT_ORDER_DISCORD -60
#define INIT_ORDER_EXPLOSIONS -69
#define INIT_ORDER_STATPANELS -97

View File

@@ -0,0 +1,37 @@
/** Defines for the Dungeon Generator.
* These bitflags control which elements are present in each room theme so the parent room of the theme knows which furnishing procs it needs to call
*/
///If the room has specific floor tiles
#define ROOM_HAS_FLOORS (1<<0)
///If the room has specific wall tiles
#define ROOM_HAS_WALLS (1<<1)
///If the room has windows and specific window type
#define ROOM_HAS_WINDOWS (1<<2)
///If the room has windows and specific window type, but no walls to spawn, so this room should just be a glass box
#define ROOM_HAS_ONLY_WINDOWS (1<<3)
///If the room has specific door type
#define ROOM_HAS_DOORS (1<<4)
///If we want the room shape, but then the room to do something very specific. This is how I handle ruin spawning
#define ROOM_HAS_SPECIAL_FEATURES (1<<5)
///If the room has specific decorative features like blood splatters or loot spawners
#define ROOM_HAS_FEATURES (1<<6)
///If the room has mobs to spawn, whether they're hostile spiders or harmless rats
#define ROOM_HAS_MOBS (1<<7)
//The danger the room presents ranging from always safe to you should die NOW
#define ROOM_RATING_SAFE (1<<0)
#define ROOM_RATING_HOSTILE (1<<1)
#define ROOM_RATING_EXTREMELY_HOSTILE (1<<2)
#define ROOM_RATING_DEATH (1<<3)
//For categorizing rooms
#define ROOM_TYPE_RANDOM "random"
#define ROOM_TYPE_SPACE "space"
#define ROOM_TYPE_RUIN "ruin"
#define ROOM_TYPE_SECRET "secret"
#define VERTICAL "vertical"
#define HORIZONTAL "horizontal"
#define ALGORITHM_BSP "Binary Search Partition"
#define ALGORITHM_RRP "Random Room Placement"

View File

@@ -234,3 +234,7 @@ Actual Adjacent procs :
if(!istype(D, /obj/structure/window) && D.density) //had to do it silly like this so rwindows didn't stop it outright
return FALSE
return TRUE
/turf/proc/wiringTurfTest(caller, turf/T, ID, simulated_only)
if(T && !T.density && !istype(T.loc, /area/space))
return TRUE

View File

@@ -0,0 +1,274 @@
/datum/map_generator/dungeon_generator
var/name = "Dungeon Generator"
///The original area that kicked off the whole generator. Used as a basis for all area based decisions
var/area/area_ref
///The area that this generator is working in
var/list/areas_included = list()
///Turfs that the generator can use to generate stuff. As rooms are created, they are deducted from this list so that in the end there is one "main" area left over
var/list/working_turfs = list()
///Width of the area being generated. Dynamically determined at init by getting the min and max x values
var/width
///Height of the area being generated. Dynamically determined at init by getting the min and max y values
var/height
///Minimum and Maximum X and Y coordinates, and the Z-level of the area being generated in. Generate_Terrain() will get these values for you automatically
var/min_x
var/max_x
var/min_y
var/max_y
var/z_level
///The rng Seed of the generator, randomly chosen between 1 and 1000 when the terrain is generated
var/hash = 1
/**
* Binary space partition works by cutting the map area in half, then that chunk in half, then that chunk in half, over and over until the next cut would
* create subsections smaller than the min specified size. so the size can be between the min size, and the min_size*2-1. The number of rooms you get back
* will be random based on how the algorithm decides to slice
*/
var/map_subsection_min_size = 6
/**
* Minimum width of a room generated in a map subsection, walls included. A minimum width of 5 is a 3 wide room with walls on each side.
* THIS VALUE SHOULD NOT BE LARGER THAN THE map_subsection_min_size!!!!!!!!!!!
*/
var/map_subsection_min_room_width = 5
/**
* Minimum height of a room generated in a map subsection, walls included. A minimum height of 5 is a 3 tall room with walls on each side.
* THIS VALUE SHOULD NOT BE LARGER THAN THE map_subsection_min_size!!!!!!!!!!!
*/
var/map_subsection_min_room_height = 5
/**
* The number of rooms the random room placement algorithm will attempt to generate when the function is called,
* unless the function is called with specific arguments otherwise
*/
var/desired_room_count = 20
///A list of the room datums generated by the Rust-g algorithm, and stored here
var/list/generated_rooms = list()
var/list/bsp_generated_rooms = list()
var/list/rrp_generated_rooms = list()
///All mobs created by the generator. Either from the base generator or itself or the subsequent rooms created should add their mobs here
var/list/datum/weakref/owned_mobs = list()
/**
* The path to the corresponding room datum for the generator. If you make a new generator subtype, make a new room subtype to go with it
* For example if you made:
* * /datum/map_generator/dungeon_generator/ice_dungeon
*
* You should make:
* * /datum/dungeon_room/ice_room
*
* And set the room_datum_path to that path
*/
var/room_datum_path = /datum/dungeon_room
var/room_theme_path = /datum/dungeon_room_theme
///Weighted list of the types that spawns if the turf is open
var/weighted_open_turf_types = list(/turf/open/floor/plating = 10)
///Expanded list of the types that spawns if the turf is open
var/open_turf_types
///Weighted list of the types that spawns if the turf is closed
var/weighted_closed_turf_types = list(/turf/closed/wall = 10)
///Expanded list of the types that spawns if the turf is closed
var/closed_turf_types
///If this is set to TRUE then it will only change turfs that are /turf/open/genturf, for more flexability in design
var/gen_gurf_only = TRUE
var/static/list/overlappable_areas = typecacheof(list(/area/space, /area/procedurally_generated))
/datum/map_generator/dungeon_generator/New(area/generate_in)
. = ..()
open_turf_types = expand_weights(weighted_open_turf_types)
closed_turf_types = expand_weights(weighted_closed_turf_types)
hash = rand(0, 1000)
area_ref = generate_in
z_level = generate_in.z
areas_included += generate_in
/datum/map_generator/dungeon_generator/combine_local_areas()
for(var/area/procedurally_generated/current_area in GLOB.areas)
if(istype(src, current_area.map_generator) && current_area.z == area_ref.z)
areas_included |= current_area
current_area.map_generator = src
/datum/map_generator/dungeon_generator/generate_terrain()
var/start_time = REALTIMEOFDAY
for(var/area/procedurally_generated/pg in areas_included)
pg.shared_generator_initialized = TRUE
for(var/turf/t in pg.contents)
working_turfs |= t
for (var/turf/t in working_turfs)
if(!min_x || t.x<min_x)
min_x = t.x
if(!max_x || t.x>max_x)
max_x = t.x
if(!min_y || t.y<min_y)
min_y = t.y
if(!max_y || t.y>max_y)
max_y = t.y
if(min_x && max_x && (max_x-min_x>0))
width = max_x-min_x
if(min_y && max_y && (max_y-min_y>0))
height = max_y-min_y
desired_room_count = ROUND_UP((width*height)*0.005)
bsp_generated_rooms = generate_rooms_with_bsp(width, height, hash, map_subsection_min_size, map_subsection_min_room_width, map_subsection_min_room_height)
rrp_generated_rooms = generate_rooms_with_rrp(width, height, desired_room_count, hash)
generated_rooms = bsp_generated_rooms + rrp_generated_rooms
for(var/turf/gen_turf in working_turfs)
if(gen_gurf_only && !istype(gen_turf, /turf/open/genturf))
continue
gen_turf.ChangeTurf(pick(open_turf_types), flags = CHANGETURF_DEFER_CHANGE)
build_dungeon()
toggle_owned_mob_ai(AI_ON)
CHECK_TICK
var/message = "[name] finished in [(REALTIMEOFDAY - start_time)/10]s!"
to_chat(world, span_boldannounce("[message]"))
log_world(message)
/**
* With the area we're going to be working in paved with the proper tiles, we now begin constructing the actual dungeon
*/
/datum/map_generator/dungeon_generator/proc/build_dungeon()
for(var/datum/dungeon_room/room in generated_rooms)
if(room.generate())
//if the room is successfully built, we want to remove that area from the working turfs so we can furnish the "main" dungeon area at the end
working_turfs -= (room.interior | room.exterior)
CHECK_TICK
return
/**
* Returns a list of rooms using the Binary Space Partition(bsp) algorithm. Binary space gives a pretty even spread of rooms with minimal overlapping by
* subsecting the area in half, and then that section in half, and that in half, until the next subsequent cut would be smaller than the minimum size.
* Each of these cuts have minor deviations for randomness, and alternate between vertical and horizontal cuts.
* Rooms are then generated in those sections of random height and width UP TO the size of the containing section. Rooms may border each other but will never overlap.
* Since the map area becomes a checkerboard of cuts, area coverage is extremely good
*/
/datum/map_generator/dungeon_generator/proc/generate_rooms_with_bsp(
width = src.width,
height = src.height,
hash = src.hash,
map_subsection_min_size = src.map_subsection_min_size,
map_subsection_min_room_width = src.map_subsection_min_room_width,
map_subsection_min_room_height = src.map_subsection_min_room_height,
)
var/rooms_json = rustg_bsp_generate(
"[width]",
"[height]",
"[hash]",
"[map_subsection_min_size]",
"[map_subsection_min_room_width]",
"[map_subsection_min_room_height]")
return parse_rooms_json(rooms_json, ALGORITHM_BSP)
/**
* Returns a list of rooms using the Random Room Placement(rrp) algorithm. Random room placement simply generates rooms of random height and width, then selects
* a random X and Y value to be the bottom left corner of it. The only validation check this algorith performs is if a newly generated room overlaps with an existing room
* that the algorithm generated previously. If it does, the algorithm will generate a new room and coordinates for it and try again.
*/
/datum/map_generator/dungeon_generator/proc/generate_rooms_with_rrp(
width = src.width,
height = src.height,
desired_room_count = src.desired_room_count,
hash = src.hash,
)
var/rooms_json = rustg_random_room_generate(
"[width]",
"[height]",
"[desired_room_count]",
"[hash]")
return parse_rooms_json(rooms_json, ALGORITHM_RRP)
/**
* Converts the json list of room objects into a list of
*
*/
/datum/map_generator/dungeon_generator/proc/parse_rooms_json(json_of_rooms, algorithm_type)
var/list/rooms = json_decode(json_of_rooms)
var/list/parsed_rooms = list()
//the response is a list of room objects in JSON format. you can't iterate through the list in the for var/i in list fashion
//because you need to keep track of the index you're on. each element doesn't have its own identifier
for(var/index in 1 to rooms.len)
var/room = rooms[index]
var/room_id = "[room["id"]]: [index]"
var/x1 = text2num(room["x"]) + min_x
var/y1 = text2num(room["y"]) + min_y
//byond starts counting from 1 so we need to subtract 1 from our top end coordinates
var/x2 = text2num(room["x2"]) + min_x - 1
var/y2 = text2num(room["y2"]) + min_y - 1
var/room_width = text2num(room["width"])
var/room_height = text2num(room["height"])
//We take the center of the room and if it's outside the generated area, we don't add it to our list
//var/turf/center = locate(ROUND_UP(x2-room_width/2), ROUND_UP(y2-room_height/2), z_level)
var/datum/dungeon_room/potential_room = new room_datum_path(
_id = room_id,
_x1 = x1,
_y1 = y1,
_x2 = x2,
_y2 = y2,
_z = z_level,
_width = room_width,
_height = room_height,
_generator_ref = src,
)
if(valid_room_check(potential_room))
parsed_rooms += potential_room
potential_room.Initialize()
CHECK_TICK
return parsed_rooms
/**
* Designate behavior for whether or not you want the rooms kept or discarded here. At base this will just ensure the center of the room falls within the workable area
* the generator has access to
*/
/datum/map_generator/dungeon_generator/proc/valid_room_check(datum/dungeon_room/room_to_check)
return working_turfs.Find(room_to_check.center)
/datum/map_generator/dungeon_generator/proc/rooms_intersect(datum/dungeon_room/room_to_check, datum/dungeon_room/other_room)
var/intersect = FALSE
return intersect
/**
* Toggle the AI setting of all mobs in the generator. If the assistant starts crying like a child lost in a haunted house, you can turn off the mobs and escort them
* to their parent.
*
* For quick refence the commands are:
* * 1: Turn mob AI on
* * 2: Set mob AI to idle
* * 3: Turn mob AI off completely
* * 4: Turn mob AI off until a player enters the z-level which will cause them to flip back to on
*/
/datum/map_generator/dungeon_generator/proc/toggle_owned_mob_ai(togglestatus)
if(!togglestatus)
return togglestatus
for(var/datum/weakref/mob_ref as anything in owned_mobs)
var/mob/living/simple_animal/mob_to_toggle = mob_ref.resolve()
if(mob_to_toggle)
mob_to_toggle.toggle_ai(togglestatus)
return togglestatus

View File

@@ -5,6 +5,10 @@
/datum/map_generator/proc/generate_terrain(list/turfs, area/generate_in)
return
///This proc will aggregate areas on the same z-level to share turfs for the purpose of generating the area. Currently just for procedural generation with maints
/datum/map_generator/proc/combine_local_areas()
return
/turf/open/genturf
name = "ungenerated turf"
desc = "If you see this, and you're not a ghost, yell at coders"

View File

@@ -0,0 +1,351 @@
/**
* This datum represents a "room" which is created by the Dungeon Generator process. Whether you use Binary Space Partition(BSP) or Random Room Placement(RRP),
* the Rust algorith will populate the given area with rooms, which are just squares and rectangles of various dimensions with X and Y coordinates. I created
* this datum to give better control over the data the generator is using. So you can look through the list of rooms for feedback or to understand what's happening.
*
* The datum has procs to construct itself at the given coordinates, and procs to furnish itself according to the randomly assigned "theme" it chooses for itself
* on init.
*/
/datum/dungeon_room
///Some kind of identifier. Will default to the number of the rooms in the list, like "Random room 5"
var/id
///X coordinate of the bottom left corner
var/x1
///Y coordinate of the bottom left corner
var/y1
///X coordinate of the top right corner
var/x2
///Y coordinate of the top right corner
var/y2
///z-level of the room
var/z
///Tile width of the room, including walls. A 5 width room is a 3 tile wide interior with a wall on each side
var/width
///Tile height of the room, including walls. A 5 height room is a 3 tile tall interior with a wall on each side
var/height
///The center tile of a room, or at least as close to center as it can get if the room is of different dimensions, or equal dimensions with no center
var/turf/center = null
/**
* A list of the outermost turfs. This is used to generate walls and doors
*
* * exterior = Full room - interior
* * [X][X][X][X] = [X][X][X][X] - [0][0][0][0]
* * [X][0][0][X] = [X][X][X][X] - [0][X][X][0]
* * [X][0][0][X] = [X][X][X][X] - [0][X][X][0]
* * [X][X][X][X] = [X][X][X][X] - [0][0][0][0]
*/
var/list/exterior = list()
/**
* A list of the turfs inside the outer walls. This is the room for activities and decorations
* * Interior area of full room
* * [0][0][0][0]
* * [0][X][X][0]
* * [0][X][X][0]
* * [0][0][0][0]
*/
var/list/interior = list()
var/min_doors = 1
var/max_doors = 4
///A list of the doors belonging to a room
var/list/datum/weakref/doors = list()
var/list/datum/weakref/features = list()
var/list/datum/weakref/mobs = list()
/**
* List of protected atom types. Rooms use turf.empty() to replace turfs and delete their contents at the same time. This is so that if a room overlaps another,
* lingering elements like walls, doors, windows, and even mobs will be removed. This is also helpful if you want to generate over elements that are already
* placed by map editors, like in the case of pipes and cables for atmos and station power.
*/
var/static/list/protected_atoms = typecacheof(list(
/obj/machinery/atmospherics/pipe,
/obj/structure/cable,
))
///If this room needed to get trimmed because it overlapped an area that shouldn't be touched
var/completed_room = TRUE
var/datum/map_generator/dungeon_generator/generator_ref
var/list/weighted_open_turf_types = list()
var/list/weighted_closed_turf_types = list()
var/datum/dungeon_room_theme/room_theme
///for differentiating room types yourself. like maybe 1 is a ruin, and 2 is empty
var/room_type
var/room_danger_level
var/area/area_ref = null
/datum/dungeon_room/New(
_id = "randomly generated room",
_x1 = 1,
_y1 = 1,
_x2 = 1,
_y2 = 1,
_z = 1,
_width = 1,
_height = 1,
_room_type,
_room_danger_level, datum/map_generator/dungeon_generator/_generator_ref)
id = _id
x1 = _x1
y1 = _y1
x2 = _x2
y2 = _y2
z = _z
width = _width
height = _height
center = locate(ROUND_UP(x1+width/2), ROUND_UP(y1+height/2), z)
area_ref = get_area(center)
if(_room_type)
room_type = _room_type
if(_room_danger_level)
room_danger_level = _room_danger_level
interior = block(locate(x1+1,y1+1,z),locate(x2-1,y2-1,z))
exterior = block(locate(x1,y1,z),locate(x2,y2,z)) - interior
generator_ref = _generator_ref
if(generator_ref.open_turf_types)
weighted_open_turf_types = generator_ref.open_turf_types
if(generator_ref.closed_turf_types)
weighted_closed_turf_types = generator_ref.closed_turf_types
///Override this for each new room type so you know whether to trim or discard rooms that overlap areas that should remain untouched
/datum/dungeon_room/proc/Initialize()
validation_check()
generate_room_theme()
/datum/dungeon_room/proc/generate_room_theme()
if(!room_danger_level)
room_danger_level = ROOM_RATING_SAFE
if(!room_type)
room_type = ROOM_TYPE_RANDOM
if(!room_theme)
room_theme = new generator_ref.room_theme_path(src)
room_theme.Initialize()
///checks if the room overlaps areas it shouldn't and if it does remove that tile from the room
/datum/dungeon_room/proc/validation_check()
for(var/turf/tile in exterior)
var/area/area_to_check = get_area(tile)
if(!is_type_in_typecache(area_to_check, generator_ref.overlappable_areas))
exterior -= tile
completed_room = FALSE
for(var/turf/tile in interior)
var/area/area_to_check = get_area(tile)
if(!is_type_in_typecache(area_to_check, generator_ref.overlappable_areas))
interior -= tile
completed_room = FALSE
///If you're making an area that has ruin map files, you can use this to determine if it should be a ruin room
/datum/dungeon_room/proc/is_ruin_compatible()
return FALSE
///Construct yourself in the game world NOW. Assuming you have a theme to pull from, otherwise stay theoretical.
/datum/dungeon_room/proc/generate()
if(isnull(room_theme))
return FALSE
if(room_theme.room_flags & ROOM_HAS_FLOORS)
build_flooring()
if(room_theme.room_flags & ROOM_HAS_WALLS || room_theme.room_flags & ROOM_HAS_WINDOWS)
build_walls()
if(room_theme.room_flags & ROOM_HAS_DOORS)
add_doors()
if(room_theme.room_flags & ROOM_HAS_SPECIAL_FEATURES)
add_special_features()
if(room_theme.room_flags & ROOM_HAS_FEATURES)
add_features()
if(room_theme.room_flags & ROOM_HAS_MOBS)
add_mobs()
room_theme.post_generate()
generator_ref.owned_mobs |= mobs
/**
* This loop is for setting the inside of a room to match the area of its center. The reason we also fuck around with the lighting overlays is because
* at the time of generation, the lighting subsystem isn't initialized yet, so we'll handle calling the building and clearing of area lighting diffs ourselves.
*/
for(var/turf/room_turf in (interior + exterior))
var/area/old_area = get_area(room_turf)
if(area_ref != old_area && !generator_ref.areas_included.Find(old_area))
area_ref.contents += room_turf
area_ref.contained_turfs += room_turf
old_area.turfs_to_uncontain += room_turf
room_turf.change_area(old_area, area_ref)
return TRUE
///For each tile in the exterior, build a wall to keep the assistants out. Or a window if the room theme calls for it
/datum/dungeon_room/proc/build_walls()
if(!(room_theme.room_flags & ROOM_HAS_WALLS || room_theme.room_flags & ROOM_HAS_WINDOWS))
return
for(var/turf/room_turf in exterior)
//we use the generator flooring for this because the space rooms only have space flooring
room_turf.empty(pickweight(weighted_open_turf_types), ignore_typecache = protected_atoms, flags = CHANGETURF_DEFER_CHANGE | CHANGETURF_IGNORE_AIR)
if(room_theme.room_flags & ROOM_HAS_WINDOWS && prob(room_theme.window_weight) || room_theme.room_flags & ROOM_HAS_ONLY_WINDOWS)
var/obj/window_to_spawn = room_theme.get_random_window()
new window_to_spawn(room_turf)
else
if(!room_theme.weighted_possible_wall_types.Find(room_turf.type))
room_turf.place_on_top(pick(room_theme.get_random_wall()), flags = CHANGETURF_DEFER_CHANGE | CHANGETURF_IGNORE_AIR)
return
///Build the flooring for the room. Potentially not necessary based on the build area, but making sure the room doesn't construct over space or gen turf is a good idea
/datum/dungeon_room/proc/build_flooring()
if(!(room_theme.room_flags & ROOM_HAS_FLOORS))
return
for(var/turf/room_turf in interior)
//we want to remove everything in the loc but don't want to change the loc type in this way
room_turf.empty(null, ignore_typecache = protected_atoms)
room_turf.place_on_top(pick(room_theme.get_random_flooring()), flags = CHANGETURF_DEFER_CHANGE | CHANGETURF_IGNORE_AIR)
return
///Add a random number of doors to the room. If you're lucky, people will actually be able to access the room. If not, it's a ~super secret room~
/datum/dungeon_room/proc/add_doors()
if(!(room_theme.room_flags & ROOM_HAS_DOORS))
return
var/num_of_doors_to_gen = rand(min_doors, max_doors)
var/list/directions = GLOB.cardinals.Copy()
var/max_attempts = 5
var/attempts_left = 5
while(doors.len < num_of_doors_to_gen && directions.len && attempts_left > 0 )
var/turf/door_spot = null
var/wall_side = pick_n_take(directions)
switch(wall_side)
if(NORTH)
door_spot = locate(ROUND_UP(x2-width/2),y2,z)
if(SOUTH)
door_spot = locate(ROUND_UP(x2-width/2),y1,z)
if(EAST)
door_spot = locate(x2,ROUND_UP(y2-height/2),z)
if(WEST)
door_spot = locate(x1,ROUND_UP(y2-height/2),z)
if(exterior.Find(door_spot))
var/turf/other_side_of_door = get_step(door_spot, wall_side)
if(istype(other_side_of_door, /turf/open/space))
num_of_doors_to_gen--
continue
door_spot.empty(pick(room_theme.get_random_flooring()), ignore_typecache = protected_atoms, flags = CHANGETURF_DEFER_CHANGE | CHANGETURF_IGNORE_AIR)
var/door_path = room_theme.get_random_door()
if(ispath(door_path))
var/obj/machinery/door/new_door = new door_path(door_spot)
doors += WEAKREF(new_door)
attempts_left = max_attempts
attempts_left--
///Sprinkle in the flavor from the room's theme like blood splatters, trash, or items. The number of flavor items is based on the room size and feature weight
/datum/dungeon_room/proc/add_special_features()
if(!(room_theme.room_flags & ROOM_HAS_SPECIAL_FEATURES) || !interior.len)
return
var/special_feature_path = room_theme.get_special_feature()
if(ispath(special_feature_path))
var/obj/special_feature = new special_feature_path(locate(x1+1, y1+1, z))
features += WEAKREF(special_feature)
return TRUE
return FALSE
///Sprinkle in the flavor from the room's theme like blood splatters, trash, or items. The number of flavor items is based on the room size and feature weight
/datum/dungeon_room/proc/add_features()
if(!(room_theme.room_flags & ROOM_HAS_FEATURES) || !interior.len)
return
var/features_to_spawn = round( (interior.len) * (room_theme.feature_weight * 0.01), 1 )
var/max_attempts = 10
var/attempts_left = 10
while(features_to_spawn > 0 && attempts_left > 0)
var/turf/spawn_point = pick(interior)
if(!spawn_point.is_blocked_turf())
var/selected_feature_path = room_theme.get_random_feature()
if(islist(selected_feature_path))
for(var/path_to_check in selected_feature_path)
if(ispath(path_to_check))
var/obj/new_feature = new path_to_check(spawn_point)
features += WEAKREF(new_feature)
features_to_spawn = clamp(features_to_spawn-1, 0, max_attempts)
//if we successfully spawn something, reset the attempt count
attempts_left = max_attempts
else if(ispath(selected_feature_path))
var/obj/new_feature = new selected_feature_path(spawn_point)
features += WEAKREF(new_feature)
features_to_spawn = clamp(features_to_spawn-1, 0, max_attempts)
//if we successfully spawn something, reset the attempt count
attempts_left = max_attempts
else
attempts_left = clamp(attempts_left-1, 0, max_attempts)
///Add mobs if the room's theme has any, be it rats or space dragons lmao. The number of mobs is based on the room size and mob weight
/datum/dungeon_room/proc/add_mobs()
if(!(room_theme.room_flags & ROOM_HAS_MOBS) || !interior.len)
return
var/mobs_to_spawn = round( log(interior.len) * ((interior.len/100) + 1), 1 )
var/max_attempts = 10
var/attempts_left = 10
while(mobs_to_spawn > 0 && attempts_left > 0)
if(!prob(room_theme.mob_spawn_chance))
mobs_to_spawn = clamp(mobs_to_spawn-1, 0, mobs_to_spawn)
continue
var/turf/spawn_point = pick(interior)
if(!spawn_point.is_blocked_turf())
var/selected_mob_path = room_theme.get_random_mob()
if(ispath(selected_mob_path, /mob/living/simple_animal))
var/mob/living/simple_animal/new_mob = new selected_mob_path(spawn_point)
/**
* We toggle the mob AI off as they're created because if the generator is being ran slowly and taking a while to generate,
* mobs can break out of their rooms and start fighting before the generator is even done
*/
new_mob.toggle_ai(AI_OFF)
mobs += WEAKREF(new_mob)
mobs_to_spawn = clamp(mobs_to_spawn-1, 0, mobs_to_spawn)
//if we successfully spawn a mob, reset the attempt count
attempts_left = max_attempts
attempts_left = clamp(attempts_left-1, 0, max_attempts)
/**
* Experimental and buggy, but calling this SHOULD remove the room from the game world, along with all the room's tiles and items.
* leaving the area looking how it did before generation. just thanos snap the room off the map like it never existed. however with rooms overlapping
* and intersecting this may just remove a chunk of another room
*/
/datum/dungeon_room/proc/delete_self()
for(var/turf/room_turf in interior)
//again we want to clean the tile of any mobs or props before reverting it to the pre-room setting
room_turf.empty(null, ignore_typecache = protected_atoms)
room_turf.ScrapeAway()
for(var/turf/room_turf in exterior)
//again we want to clean the tile of any mobs or props before reverting it to the pre-room setting
room_turf.empty(null, ignore_typecache = protected_atoms)
room_turf.ScrapeAway()

View File

@@ -0,0 +1,175 @@
/**
* Dungeon room themes determine the style of the generated rooms. If you want to create a new dungeon generator for say, Icemeta,
* Then you would want to create a new subtype of room theme with maybe snow floor tiles, ice or snow walls and some fitting kind of door. Then from there further subtype
* into hostile, neutral, beneficial and whatever other types of rooms you want to appear in that area.
*
* TLDR: This datum is basically a box of furniture that randomly generted rooms will pull from to build themselves with a cohesive style
*/
/datum/dungeon_room_theme
var/datum/dungeon_room/dungeon_room_ref
///Contains flags pertaining to the qualities of the room, such as ROOM_HAS_FLOORS if it, y'know, has floors
var/room_flags = 0
var/room_type = ROOM_TYPE_RANDOM
var/room_danger_level = ROOM_RATING_SAFE
///Weighted list of floorings for the room to choose from
var/list/weighted_possible_floor_types = list()
///Weighted list of walls for the room to choose from
var/list/weighted_possible_wall_types = list()
///If you want the theme or rooms to include windows, specify the window type here, be it the basic window obj, or a spawner for a preset window
var/list/weighted_possible_window_types = list()
///percent chance of spawning a window instead of a wall
var/window_weight = 0
///Weighted list of doors for the room to choose from
var/list/weighted_possible_door_types = list(/obj/machinery/door/airlock = 1)
///For room themes that call for very specific features appearing. Currently just for handling ruins via their landmark object
var/special_feature = null
/**
* The number of features spawned in a room is:
* (area inside the room * feature_weight as a percent) rounded up
* the higher this number, the more features spawned
*
* I.E. room with a 4x5 inner area has an area of 20, so with a feature weight of 75 results in 75% of 20, therefore 15 features will be spawned in random open spots
*/
var/feature_weight = 0
///Weighted list of features you want to spawn. Like unfinished machines, broken mechs, blood splatters. Keep in mind these will be placed randomly
var/list/weighted_feature_spawn_list = list()
var/list/expanded_weighted_feature_spawn_list = list()
/**
* The upper limit of mobs spawned in a room is determined by a logarithmic function using the interior size of the room. This variable
* is the chance that a mob spawns in each iteration of the spawning loop.
* For example if a room has an interior size of 20 and has a limit of 5 mobs for the room, If the spawn chance is 50 then each of those 5 spawn attempts has
* a 50% chance to actually spawn a mob, otherwise it will decrease the number of spawnable mobs by 1 and try again.
*
*/
var/mob_spawn_chance = 0
///Weighted list of features you want to spawn. Spiders in a room that has webbing, or possums in a room full of trash.
var/list/weighted_mob_spawn_list = list()
/** Initialize the new room by sanity checking values and then assigning the bitflags that describe room behavior to the parent room*/
/datum/dungeon_room_theme/New(datum/dungeon_room/_dungeon_room_ref)
//No negatives pls
feature_weight = clamp(feature_weight, 0, 100)
mob_spawn_chance = clamp(mob_spawn_chance, 0, 100)
dungeon_room_ref = _dungeon_room_ref
if(isemptylist(weighted_possible_floor_types))
weighted_possible_floor_types = dungeon_room_ref.weighted_open_turf_types
if(isemptylist(weighted_possible_wall_types))
weighted_possible_wall_types = dungeon_room_ref.weighted_closed_turf_types
/**
* Any sort of room specific logic you need to do before running the Initialize() that will assign room flags. For example if you want to generate a list
* using subtypes or typesof, which is something you can't do from the base definition and needs to be done in a proc.
*/
/datum/dungeon_room_theme/proc/pre_initialize()
return
/**
* Room flags are assigned based on which lists have any elements.
* These flags are checked by the room that owns the theme to determine which furnishing procs need to be called
*/
/datum/dungeon_room_theme/proc/Initialize()
pre_initialize()
if(dungeon_room_ref.room_danger_level & ROOM_RATING_SAFE)
var/list/blatently_hostile_mob_paths = (typesof(/mob/living/simple_animal/hostile) - typesof(/mob/living/simple_animal/hostile/retaliate))
for(var/mob_path in weighted_mob_spawn_list)
if(blatently_hostile_mob_paths.Find(mob_path))
weighted_mob_spawn_list.Remove(mob_path)
if(weighted_possible_floor_types.len)
room_flags |= ROOM_HAS_FLOORS
if(weighted_possible_wall_types.len)
room_flags |= ROOM_HAS_WALLS
if(weighted_possible_window_types.len)
room_flags |= ROOM_HAS_WINDOWS
if(room_flags & ROOM_HAS_WINDOWS && !(room_flags & ROOM_HAS_WALLS))
room_flags |= ROOM_HAS_ONLY_WINDOWS
if(weighted_possible_door_types.len)
room_flags |= ROOM_HAS_DOORS
if(special_feature)
room_flags |= ROOM_HAS_SPECIAL_FEATURES
if(weighted_feature_spawn_list.len && feature_weight > 0)
room_flags |= ROOM_HAS_FEATURES
if(weighted_mob_spawn_list.len && mob_spawn_chance > 0)
room_flags |= ROOM_HAS_MOBS
///Return a random flooring /turf from the list of flooring options, selected based on weight. Defaults to basic plating if no flooring is found
/datum/dungeon_room_theme/proc/get_random_flooring()
if(!weighted_possible_floor_types.len)
return /turf/open/floor/plating
var/turf/flooring = pickweight(weighted_possible_floor_types)
return flooring
///Return a random wall /turf from the list of wall options, selected based on weight. Defaults to basic wall if no wall is found
/datum/dungeon_room_theme/proc/get_random_wall()
if(!weighted_possible_wall_types.len)
return /turf/closed/wall
var/wall_path = pickweight(weighted_possible_wall_types)
return wall_path
///Return a random window /obj from the list of window options, selected based on weight. Defaults to basic window spawner if no window is found
/datum/dungeon_room_theme/proc/get_random_window()
if(!weighted_possible_window_types.len)
return /obj/effect/spawner/structure/window
var/window_path = pickweight(weighted_possible_window_types)
return window_path
///Return a random door /obj from the list of door options, selected based on weight. Defaults to basic door if no door is found
/datum/dungeon_room_theme/proc/get_random_door()
if(!weighted_possible_wall_types.len)
return /obj/machinery/door/airlock
var/door_path = pickweight(weighted_possible_door_types)
return door_path
///Return the path for the special feature of the room. Returns null if nothing is found
/datum/dungeon_room_theme/proc/get_special_feature()
if(!special_feature)
return null
return special_feature
///Return a random feature /obj the list of feature options, selected based on weight. Returns null if nothing is found
/datum/dungeon_room_theme/proc/get_random_feature()
if(!weighted_feature_spawn_list.len)
return null
var/feature_path = pickweight(weighted_feature_spawn_list)
// else
weighted_feature_spawn_list[feature_path]--
if(!weighted_feature_spawn_list[feature_path])
weighted_feature_spawn_list.Cut(weighted_feature_spawn_list.Find(feature_path), weighted_feature_spawn_list.Find(feature_path)+1)
return feature_path
/**
* Return a random mob /atom the list of mob options, selected based on weight. Returns null if nothing is found
* The reason it returns an atom rather than a mob specifically is because there are some weird cases where mobs are objects, like specific silicons
*/
/datum/dungeon_room_theme/proc/get_random_mob()
if(!weighted_mob_spawn_list.len)
return null
var/mob_path = pickweight(weighted_mob_spawn_list)
weighted_mob_spawn_list[mob_path]--
if(!weighted_mob_spawn_list[mob_path])
weighted_mob_spawn_list.Remove(mob_path)
return mob_path
/**
* If you want to do anything fancy with the mobs or items in the room after you know they exist.
* Like renaming items, reassigning factions, calling specific procs from them.
* Whatever your little heart desires
*/
/datum/dungeon_room_theme/proc/post_generate()
return

View File

@@ -0,0 +1,263 @@
/datum/map_generator/dungeon_generator/maintenance
weighted_open_turf_types = list(
/turf/open/floor/plating = 10,
/turf/open/floor/plating/rust = 1,
)
weighted_closed_turf_types = list(/turf/closed/wall = 5, /turf/closed/wall/rust = 2 )
room_datum_path = /datum/dungeon_room/maintenance
room_theme_path = /datum/dungeon_room_theme/maintenance
//var/list/used_spawn_points = list()
/datum/map_generator/dungeon_generator/maintenance/build_dungeon()
. = ..()
add_firelocks()
add_apcs()
wire_apcs()
add_maint_loot()
/datum/map_generator/dungeon_generator/maintenance/proc/add_firelocks()
///we only want to look to place firedoors every 5 tiles, so we don't place too many
var/step_increment = 5
var/consecutive_firedoor_limit = 3
var/list/fire_door_spawn_points = list()
var/fire_doors_path = /obj/effect/mapping_helpers/firedoor_border_spawner
for(var/y in min_y to max_y step step_increment)
fire_door_spawn_points = list()
for(var/turf/current_turf in block(locate(min_x,y,z_level),locate(max_x,y,z_level)))
if(working_turfs.Find(current_turf) && !current_turf.is_blocked_turf(TRUE))
fire_door_spawn_points |= current_turf
if(current_turf.is_blocked_turf(TRUE) && fire_door_spawn_points.len <= consecutive_firedoor_limit)
for(var/turf/spawn_point in fire_door_spawn_points)
new fire_doors_path(spawn_point)
fire_door_spawn_points -= spawn_point
if(fire_door_spawn_points.len > consecutive_firedoor_limit && current_turf.is_blocked_turf(TRUE))
fire_door_spawn_points = list()
fire_doors_path = /obj/effect/mapping_helpers/firedoor_border_spawner/horizontal
for(var/x in min_x to max_x step step_increment)
fire_door_spawn_points = list()
for(var/turf/current_turf in block(locate(x,min_y,z_level),locate(x,max_y,z_level)))
if(working_turfs.Find(current_turf) && !current_turf.is_blocked_turf(TRUE))
fire_door_spawn_points |= current_turf
if(current_turf.is_blocked_turf(TRUE) && fire_door_spawn_points.len <= consecutive_firedoor_limit)
for(var/turf/spawn_point in fire_door_spawn_points)
new fire_doors_path(spawn_point)
fire_door_spawn_points -= spawn_point
if(fire_door_spawn_points.len > consecutive_firedoor_limit && current_turf.is_blocked_turf(TRUE))
fire_door_spawn_points = list()
/datum/map_generator/dungeon_generator/maintenance/proc/add_maint_loot()
//Gax maints typically ends up being about 2000 turfs, so this would end up being ~200 items, structures, and decals to decorate maint with
var/list/valid_spawn_points = typecache_filter_list(working_turfs, typecacheof(/turf/open/floor))
var/items_to_spawn = ROUND_UP(valid_spawn_points.len * 0.15)
var/max_attempts = 5
var/attempts = max_attempts
while(items_to_spawn>0 && attempts>0 && valid_spawn_points.len > 0)
attempts--
var/turf/spawn_point = pick_n_take(valid_spawn_points)
var/against_wall = FALSE
var/blocking_passage = FALSE
var/brazil = FALSE
var/blocked_directions = 0
if(spawn_point?.is_blocked_turf() || spawn_point.contents.len>0)
continue
for(var/direction in GLOB.cardinals)
var/turf/neighbor = get_step(spawn_point, direction)
if(neighbor?.is_blocked_turf(TRUE, ignore_atoms = list(/obj/structure), type_list = TRUE))
blocked_directions |= direction
if( blocked_directions )
//probably
against_wall = TRUE
if( ((blocked_directions & (NORTH|SOUTH)) == (NORTH|SOUTH)) || ((blocked_directions & (EAST|WEST)) == (EAST|WEST)) )
//definitely
blocking_passage = TRUE
if( (blocked_directions & (NORTH|SOUTH|EAST|WEST)) == (NORTH|SOUTH|EAST|WEST) )
//what the fuck how did you get here
brazil = TRUE
if(brazil)
new /obj/item/toy/plush/lizard/azeel(spawn_point)
items_to_spawn--
else if(blocking_passage)
switch(rand(1,10))
if(1 to 6)
new /obj/structure/grille/broken(spawn_point)
else
new /obj/structure/grille(spawn_point)
items_to_spawn--
else if(against_wall)
switch(rand(1,10))
if(1 to 3)
if(prob(50))
new /obj/structure/table(spawn_point)
else
new /obj/structure/rack(spawn_point)
items_to_spawn--
new /obj/effect/spawner/lootdrop/maintenance(spawn_point)
if(4 to 5)
new /obj/machinery/space_heater(spawn_point)
if(6 to 7)
new /obj/structure/closet/emcloset(spawn_point)
if(8 to 9)
new /obj/structure/closet/firecloset(spawn_point)
if(10)
new /obj/structure/closet/toolcloset(spawn_point)
items_to_spawn--
else
switch(rand(1,10))
if(1 to 3)
new /obj/structure/grille/broken(spawn_point)
if(4 to 6)
new /obj/structure/girder/displaced(spawn_point)
if(7 to 9)
new /obj/structure/grille(spawn_point)
else
new /obj/effect/spawner/lootdrop/maintenance(spawn_point)
items_to_spawn--
//used_spawn_points += spawn_point
attempts = max_attempts
/datum/map_generator/dungeon_generator/maintenance/proc/add_apcs()
for(var/area/current_area in areas_included)
var/list/available_turfs = (working_turfs & typecache_filter_list(current_area.contents, typecacheof(/turf/open/floor)))
if(current_area.get_apc() || available_turfs.len <= 0)
continue
var/obj/machinery/power/apc/apc_placed = null
var/attempts = 10
while(!apc_placed && attempts>0)
var/turf/apc_spot = pick(available_turfs)
for(var/direction in GLOB.cardinals)
var/turf/neighbor = get_step(apc_spot, direction)
if(istype(neighbor, /turf/closed/wall) && !apc_placed)
switch(direction)
if(NORTH)
apc_placed = new /obj/machinery/power/apc/auto_name/north(apc_spot)
if(SOUTH)
apc_placed = new /obj/machinery/power/apc/auto_name/south(apc_spot)
if(EAST)
apc_placed = new /obj/machinery/power/apc/auto_name/east(apc_spot)
if(WEST)
apc_placed = new /obj/machinery/power/apc/auto_name/west(apc_spot)
attempts--
//We're not actually building these so they can break if Init doesn't trigger right like using a generator mid round
if(!apc_placed.area)
apc_placed.area = get_area(apc_placed.loc)
/datum/map_generator/dungeon_generator/maintenance/proc/wire_apcs()
var/list/nearby_apcs = list()
var/list/gen_area_apcs = list()
for(var/obj/machinery/power/apc/apc in GLOB.apcs_list)
if(apc.z == z_level)
if(areas_included.Find(get_area(apc)))
//log_world("[apc] added to gen area apcs")
gen_area_apcs += apc
else
//log_world("[apc] added to nearby apcs")
nearby_apcs += apc
if(!nearby_apcs.len)
return
for(var/obj/machinery/power/apc/our_apc in gen_area_apcs)
//log_world("finding path for [our_apc]")
var/obj/machinery/power/apc/closest_apc = null
var/min_dist = 0
for(var/obj/machinery/power/apc/target_apc in nearby_apcs)
if(!closest_apc)
closest_apc = target_apc
if(!min_dist)
min_dist = get_dist(our_apc, closest_apc)
if(get_dist(our_apc, target_apc) < min_dist)
closest_apc = target_apc
min_dist = get_dist(our_apc, closest_apc)
var/access_card = new /obj/item/card/id/captains_spare
//We have to rawdog the Astar pathfinding and skip the wrapper proc because that's made specifically for mobs
var/list/cable_path = AStar(
caller = our_apc,
_end = closest_apc,
dist = /turf/proc/Distance_cardinal,
maxnodes = 0,
maxnodedepth = 0,
mintargetdist = 0,
adjacent = /turf/proc/wiringTurfTest,
id = access_card,
exclude = null,
simulated_only = FALSE,
get_best_attempt = TRUE)
if(!cable_path || cable_path.len <= 1)
//log_world("Cable path for [our_apc] either null or only 1 tile")
continue
for(var/i in 1 to cable_path.len)
var/turf/cable_step = cable_path[i]
var/turf/cable_step_prev
var/turf/cable_step_next
var/d1 = 0
var/d2 = NORTH
if (i == 1)
cable_step_next = cable_path[i+1]
d1 = 0
d2 = get_dir(cable_step, cable_step_next)
else if (i == cable_path.len)
cable_step_prev = cable_path[i-1]
d1 = 0
d2 = get_dir(cable_step, cable_step_prev)
else
cable_step_prev = cable_path[i-1]
cable_step_next = cable_path[i+1]
d1 = get_dir(cable_step, cable_step_prev)
d2 = get_dir(cable_step, cable_step_next)
//cables fucking suck and require the smaller direction value be d1 and larger direction value be d2
if(d1>d2)
var/tmp_dir = d1
d1 = d2
d2 = tmp_dir
var/reached_existing_powernet = FALSE
for(var/obj/structure/cable/existing_cable in cable_step)
//if there's a duplicate cable in the exact position and orientation we're about to use, we have tied into existing cabling on the map and can stop
if(existing_cable.icon_state == "[d1]-[d2]")
reached_existing_powernet = TRUE
if(reached_existing_powernet)
//this could really be continue or break, but if you continue, it might make more unnecessary cables later to get to the same spot
break
var/obj/structure/cable/new_cable = new(cable_step)
new_cable.icon_state = "[d1]-[d2]"
if(istype(cable_step, /turf/open/space))
for(var/obj/structure/lattice/existing_lat in cable_step.contents)
qdel(existing_lat)
new /obj/structure/lattice/catwalk(cable_step)
//create a new powernet with the cable, if needed it will be merged later
//new_cable.mergeConnectedNetworks(new_cable.d1)
/datum/map_generator/dungeon_generator/maintenance/proc/check_adjacent_turfs(turf/turf_to_check)
var/against_wall = FALSE
var/blocking_passage = FALSE
var/brazil = FALSE
var/blocked_directions = 0
for(var/direction in GLOB.cardinals)
var/turf/neighbor = get_step(turf_to_check, direction)
if(neighbor?.is_blocked_turf(TRUE, ignore_atoms = list(/obj/structure), type_list = TRUE))
blocked_directions |= direction
if( blocked_directions )
//probably
against_wall = TRUE
if( ((blocked_directions & (NORTH|SOUTH)) == (NORTH|SOUTH)) || ((blocked_directions & (EAST|WEST)) == (EAST|WEST)) )
//definitely
blocking_passage = TRUE
if( (blocked_directions & (NORTH|SOUTH|EAST|WEST)) == (NORTH|SOUTH|EAST|WEST) )
//what the fuck how did you get here
brazil = TRUE
return "blocked directions: [blocked_directions], against a wall: [against_wall], in a one tile hallway: [blocking_passage], brazil: [brazil]"

View File

@@ -0,0 +1,49 @@
/datum/dungeon_room/maintenance
min_doors = 2
/datum/dungeon_room/maintenance/generate_room_theme()
if(!room_type)
if(completed_room && is_ruin_compatible() && prob(75))
//because ruins are a special type we overwrite the previous flags so the only possible theme is the ruin type
room_type = ROOM_TYPE_RUIN
else if(completed_room && prob(20))
room_type = ROOM_TYPE_SPACE
else
room_type = ROOM_TYPE_RANDOM
if(!room_danger_level)
if(completed_room && prob(50))
room_danger_level = ROOM_RATING_HOSTILE
else
room_danger_level = ROOM_RATING_SAFE
var/list/possible_themes = list()
for(var/datum/dungeon_room_theme/potential_theme as anything in typecacheof(list(generator_ref.room_theme_path), TRUE))
if((room_type == initial(potential_theme.room_type)) && (room_danger_level & (initial(potential_theme.room_danger_level))))
possible_themes |= potential_theme
var/new_theme_path = pick(possible_themes)
room_theme = new new_theme_path(src)
room_theme.Initialize()
/datum/dungeon_room/maintenance/is_ruin_compatible()
var/width_without_walls = width-2
var/height_without_walls = height-2
var/compatible = FALSE
switch("[width_without_walls]x[height_without_walls]")
if("3x3")
compatible = TRUE
if("3x5")
compatible = TRUE
if("5x3")
compatible = TRUE
if("5x4")
compatible = TRUE
if("10x5")
compatible = TRUE
if("10x10")
compatible = TRUE
return (compatible && completed_room)

View File

@@ -0,0 +1,12 @@
/datum/dungeon_room_theme/maintenance
room_type = ROOM_TYPE_RANDOM
room_danger_level = ROOM_RATING_SAFE | ROOM_RATING_HOSTILE
weighted_possible_floor_types = list(/turf/open/floor/plasteel/dark = 5, /turf/open/floor/plating = 5)
weighted_possible_window_types = list(/obj/effect/spawner/structure/window/reinforced = 2, /obj/effect/spawner/structure/window = 5)
window_weight = 10
weighted_possible_door_types = list(/obj/machinery/door/airlock/maintenance_hatch = 1)
feature_weight = 80
mob_spawn_chance = 50

View File

@@ -0,0 +1,263 @@
/datum/dungeon_room_theme/maintenance/botany
weighted_possible_floor_types = list(
/turf/open/floor/plating = 3,
/turf/open/floor/grass = 5,
/turf/open/floor/wood = 5
)
weighted_feature_spawn_list = list(
/obj/machinery/hydroponics/soil = 4,
/obj/structure/closet/secure_closet/hydroponics = 2,
/obj/item/queen_bee = 1,
/obj/item/seeds/random = 3,
list(/obj/structure/table, /obj/item/reagent_containers/glass/bottle/nutrient/ez, /obj/item/reagent_containers/glass/bottle/nutrient/rh) = 1,
list(/obj/structure/table, /obj/item/cultivator, /obj/item/hatchet) = 1,
/obj/item/reagent_containers/glass/bottle/ammonia = 1,
/obj/item/reagent_containers/glass/bottle/diethylamine = 1,
)
weighted_mob_spawn_list = list(
/mob/living/simple_animal/butterfly = 3,
/mob/living/simple_animal/cow = 2,
/mob/living/simple_animal/chick = 1,
/mob/living/simple_animal/chicken = 1,
/mob/living/simple_animal/sheep = 1
)
/datum/dungeon_room_theme/maintenance/botany/pre_initialize()
. = ..()
for(var/i in 1 to 5)
weighted_feature_spawn_list |= pick(subtypesof(/obj/item/seeds) - /obj/item/seeds/lavaland)
/datum/dungeon_room_theme/maintenance/material_storeroom
weighted_feature_spawn_list = list(
/obj/item/stack/rods/fifty = 2,
/obj/item/stack/cable_coil/random = 2,
/obj/item/stack/sheet/metal/fifty = 2,
/obj/item/stack/sheet/glass/fifty = 2,
/obj/item/stack/sheet/mineral/wood = 2,
/obj/item/stack/sheet/mineral/plasma/ten = 2,
/obj/machinery/power/port_gen/pacman = 1,
/obj/structure/frame/machine = 2,
/obj/structure/frame/computer = 1,
)
/datum/dungeon_room_theme/maintenance/material_storeroom/pre_initialize()
. = ..()
for(var/i in 1 to 3)
if(prob(10))
//if i include all types of every stock part and subtype, it gets super bogged down, so one from each pool
weighted_feature_spawn_list[/obj/item/storage/toolbox/syndicate]++
else
weighted_feature_spawn_list[/obj/item/storage/toolbox/mechanical]++
/datum/dungeon_room_theme/maintenance/material_storeroom/post_generate()
. = ..()
for(var/obj/item/stack/stack_to_randomize in dungeon_room_ref.features)
if(stack_to_randomize.amount == 1)
stack_to_randomize.amount = rand(1, 25)
/datum/dungeon_room_theme/maintenance/gym
weighted_feature_spawn_list = list(
/obj/item/reagent_containers/glass/beaker/waterbottle = 5,
/obj/item/reagent_containers/food/snacks/bearsteak = 1,
/obj/structure/punching_bag = 1,
/obj/structure/weightmachine/stacklifter = 2,
/obj/structure/closet/boxinggloves = 1,
/obj/item/reagent_containers/glass/rag = 1,
/obj/structure/holohoop = 1,
/obj/item/toy/beach_ball/holoball = 1,
//steroids are based
/obj/item/dnainjector/strong = 1,
)
weighted_mob_spawn_list = list(
/mob/living/simple_animal/hostile/carp = 1,
/mob/living/simple_animal/mouse = 2,
)
/datum/dungeon_room_theme/maintenance/gym/post_generate()
. = ..()
for(var/datum/weakref/mob_ref as anything in dungeon_room_ref.mobs)
var/mob/living/simple_animal/gym_mob = mob_ref.resolve()
if(gym_mob)
gym_mob.faction |= "gym"
if(istype(gym_mob, /mob/living/simple_animal/hostile/carp))
gym_mob.name = "\improper Gym Shark"
gym_mob.desc = "The only thing this up and coming shark hits harder than the weights is anyone who interrupts their sets."
if(istype(gym_mob, /mob/living/simple_animal/mouse))
gym_mob.name = "\improper Gym Rat"
gym_mob.desc = "He's not about to settle for Gouda-nough."
/datum/dungeon_room_theme/maintenance/junk
weighted_feature_spawn_list = list(
/obj/item/reagent_containers/food/drinks/soda_cans/grey_bull = 1,
)
weighted_mob_spawn_list = list(
/mob/living/simple_animal/cockroach = 3,
/mob/living/simple_animal/hostile/glockroach = 2,
/mob/living/simple_animal/mouse = 3,
/mob/living/simple_animal/opossum = 1,
/mob/living/simple_animal/hostile/retaliate/goat = 1,
)
/datum/dungeon_room_theme/maintenance/junk/pre_initialize()
. = ..()
weighted_feature_spawn_list |= subtypesof(/obj/item/trash)
/datum/dungeon_room_theme/maintenance/junk/post_generate()
. = ..()
for(var/datum/weakref/mob_ref in dungeon_room_ref.mobs)
var/mob/living/simple_animal/trash_animal = mob_ref.resolve()
if(trash_animal)
trash_animal.faction |= "trash"
/datum/dungeon_room_theme/maintenance/medical
weighted_possible_floor_types = list(
/turf/open/floor/plasteel = 5,
/turf/open/floor/plating = 4,
)
weighted_feature_spawn_list = list(
/obj/structure/bed/roller = 2,
/obj/structure/table/optable = 1,
/obj/machinery/iv_drip = 2,
/obj/machinery/stasis = 1,
/obj/machinery/sleeper = 1,
)
weighted_mob_spawn_list = list(
/mob/living/simple_animal/hostile/zombie = 3,
/mob/living/simple_animal/bot/medbot = 1
)
/datum/dungeon_room_theme/maintenance/medical/pre_initialize()
. = ..()
var/list/pills_here = list(
/obj/structure/table,
pick(subtypesof(/obj/item/reagent_containers/pill)),
pick(subtypesof(/obj/item/reagent_containers/pill)),
pick(subtypesof(/obj/item/reagent_containers/pill)))
weighted_feature_spawn_list += list(pills_here)
var/list/operating_table = list(/obj/structure/table)
switch(rand(1,20))
if(1 to 2)
//full set with the drip
operating_table += /obj/item/storage/backpack/duffelbag/med/surgery
if(3 to 5)
//come on baby give me the mini e-sword
operating_table += pick(/obj/item/scalpel/advanced, /obj/item/retractor/advanced, /obj/item/cautery/advanced)
if(6 to 10)
//everything you need
operating_table |= list(/obj/item/scalpel, /obj/item/retractor, /obj/item/cautery, /obj/item/circular_saw, /obj/item/bonesetter)
if(11 to 20)
//the absolute basics and sterilizine for when you try to cut open their skull with a scalpel
operating_table |= list(/obj/item/scalpel, /obj/item/retractor, /obj/item/cautery, /obj/item/reagent_containers/medspray/sterilizine)
weighted_feature_spawn_list += list(operating_table)
/datum/dungeon_room_theme/maintenance/medical/post_generate()
. = ..()
for(var/datum/weakref/mob_ref in dungeon_room_ref.mobs)
var/mob/living/simple_animal/medical_professional = mob_ref.resolve()
if(medical_professional)
//I AM A SURGEON DR HAN
medical_professional.faction |= "surgeon"
if(istype(medical_professional, /mob/living/simple_animal/hostile/zombie) && prob(1))
medical_professional.desc = "Oh my god he IS a surgeon..."
/datum/dungeon_room_theme/maintenance/robotics
weighted_feature_spawn_list = list(
/obj/effect/decal/cleanable/robot_debris = 2,
/obj/effect/decal/cleanable/oil = 3,
/obj/item/stack/cable_coil/random = 2,
/obj/item/weldingtool/largetank = 1,
/obj/structure/frame/machine = 1,
/obj/structure/frame/computer = 1,
)
weighted_mob_spawn_list = list(
/mob/living/simple_animal/hostile/hivebot = 5,
/mob/living/simple_animal/hostile/hivebot/range = 2,
/mob/living/simple_animal/hostile/hivebot/rapid = 1,
)
/datum/dungeon_room_theme/maintenance/robotics/pre_initialize()
. = ..()
if(prob(25))
//if i include all types of every stock part and subtype, it gets super bogged down, so one from each pool
weighted_feature_spawn_list |= pick(typesof(/obj/item/storage/part_replacer/bluespace))
else
weighted_feature_spawn_list |= list(/obj/item/storage/part_replacer = 1)
weighted_feature_spawn_list |= pick( (subtypesof(/mob/living/simple_animal/bot) - /mob/living/simple_animal/bot/secbot/grievous) )
weighted_feature_spawn_list |= pick(subtypesof(/obj/item/borg/upgrade) - typesof(/obj/item/borg/upgrade/modkit))
//all wrecks except the god damn ones that explode upon existing
weighted_feature_spawn_list |= pick(subtypesof(/obj/structure/mecha_wreckage) - list(/obj/structure/mecha_wreckage/gygax/dark, /obj/structure/mecha_wreckage/mauler))
weighted_feature_spawn_list |= pick(subtypesof(/obj/item/stock_parts/capacitor))
weighted_feature_spawn_list |= pick(subtypesof(/obj/item/stock_parts/scanning_module))
weighted_feature_spawn_list |= pick(subtypesof(/obj/item/stock_parts/manipulator))
weighted_feature_spawn_list |= pick(subtypesof(/obj/item/stock_parts/micro_laser))
weighted_feature_spawn_list |= pick(subtypesof(/obj/item/stock_parts/matter_bin))
/datum/dungeon_room_theme/maintenance/spiders
weighted_feature_spawn_list = list(
/obj/structure/spider/stickyweb = 5,
/obj/structure/spider/cocoon = 5,
/obj/structure/spider/spiderling = 2,
)
weighted_mob_spawn_list = list(
/mob/living/simple_animal/hostile/poison/giant_spider = 10,
/mob/living/simple_animal/hostile/poison/giant_spider/hunter = 4,
/mob/living/simple_animal/hostile/poison/giant_spider/ice = 2,
)
/datum/dungeon_room_theme/maintenance/xenobio
weighted_possible_floor_types = list(
/turf/open/floor/plasteel/dark = 3,
/turf/open/floor/plasteel = 5,
/turf/open/floor/plating = 3,
)
weighted_feature_spawn_list = list(
/obj/machinery/processor/slime = 1,
/obj/item/extinguisher = 2,
list(/obj/structure/table, /obj/item/slime_extract/grey, /obj/item/storage/box/monkeycubes, /obj/item/reagent_containers/syringe/plasma) = 1,
list(/obj/structure/table, /obj/item/reagent_containers/glass/beaker/waterbottle, /obj/item/reagent_containers/food/snacks/monkeycube) = 1,
/obj/structure/frame/machine = 1,
/obj/structure/frame/computer = 1,
)
weighted_mob_spawn_list = list(
/mob/living/carbon/monkey = 2,
)
/datum/dungeon_room_theme/maintenance/xenobio/pre_initialize()
. = ..()
switch(rand(1,10))
if(1)
weighted_feature_spawn_list += pick(subtypesof(/obj/item/slimecross))
if(2)
weighted_feature_spawn_list += /obj/item/slime_extract/rainbow
if(3)
weighted_feature_spawn_list += pick(subtypesof(/obj/item/slime_extract))
if(4 to 7)
for(var/x in 1 to 3)
weighted_feature_spawn_list += pick(
/obj/item/slime_extract/grey,
/obj/item/slime_extract/orange,
/obj/item/slime_extract/purple,
/obj/item/slime_extract/blue,
/obj/item/slime_extract/metal)
else
weighted_feature_spawn_list[/obj/item/slime_extract/grey] = 2
for(var/x in 1 to 3)
if(prob(10))
weighted_mob_spawn_list[/mob/living/simple_animal/slime/random]++
else
weighted_mob_spawn_list[/mob/living/simple_animal/slime]++

View File

@@ -0,0 +1,31 @@
/datum/dungeon_room_theme/maintenance/ruin
room_type = ROOM_TYPE_RUIN
/datum/dungeon_room_theme/maintenance/ruin/pre_initialize()
. = ..()
var/width_without_walls = dungeon_room_ref.width-2
var/height_without_walls = dungeon_room_ref.height-2
switch("[width_without_walls]x[height_without_walls]")
if("3x3")
special_feature = /obj/effect/landmark/stationroom/maint/threexthree
if("3x5")
special_feature = /obj/effect/landmark/stationroom/maint/threexfive
if("5x3")
special_feature = /obj/effect/landmark/stationroom/maint/fivexthree
if("5x4")
special_feature = /obj/effect/landmark/stationroom/maint/fivexfour
if("10x5")
special_feature = /obj/effect/landmark/stationroom/maint/tenxfive
if("10x10")
special_feature = /obj/effect/landmark/stationroom/maint/tenxten
/datum/dungeon_room_theme/maintenance/ruin/post_generate()
. = ..()
for(var/datum/weakref/spawner_ref as anything in dungeon_room_ref.features)
var/obj/effect/landmark/stationroom/spawner = spawner_ref.resolve()
if(spawner)
spawner.unique = FALSE
spawner.load()
// if(spawner.load())
// dungeon_room_ref.features -= spawner

View File

@@ -0,0 +1,74 @@
/datum/dungeon_room_theme/maintenance/space
room_type = ROOM_TYPE_SPACE
weighted_possible_floor_types = list(/turf/open/space/basic = 1)
//no doors to space rooms
weighted_possible_door_types = list()
weighted_possible_window_types = list(/obj/effect/spawner/structure/window/reinforced/shutter = 1)
//no mobs in space, althought if i find a way to percent chance spawn a mob, i might make a space cat spawn
weighted_mob_spawn_list = list()
window_weight = 80
/datum/dungeon_room_theme/maintenance/space/pre_initialize()
. = ..()
for(var/turf/turf_between_space in dungeon_room_ref.exterior)
var/turf/turf_outside_room = get_step(turf_between_space, get_dir(dungeon_room_ref.center, turf_between_space))
if(isspaceturf(turf_outside_room))
dungeon_room_ref.exterior -= turf_between_space
/datum/dungeon_room_theme/maintenance/space/post_generate()
. = ..()
for(var/turf/space_to_check in dungeon_room_ref.interior)
var/needs_catwalk = FALSE
if(locate(/obj/machinery/atmospherics/pipe) in space_to_check)
needs_catwalk = TRUE
else if (locate(/obj/structure/cable) in space_to_check)
needs_catwalk = TRUE
else
continue
if(needs_catwalk && !locate(/obj/structure/lattice/catwalk) in space_to_check)
for(var/obj/structure/lattice/existing_lattice in space_to_check)
qdel(existing_lattice)
new /obj/structure/lattice/catwalk(space_to_check)
/datum/dungeon_room_theme/maintenance/space/basic
weighted_feature_spawn_list = list(
/obj/structure/lattice = 10,
/obj/item/stack/ore/glass = 3,
/obj/item/tank/internals/emergency_oxygen = 1,
)
/datum/dungeon_room_theme/maintenance/space/basic/pre_initialize()
. = ..()
switch(rand(1,10))
if(1)
//the blessed space cat
weighted_feature_spawn_list += /mob/living/simple_animal/pet/cat/space
if(2)
//a "dolphin"
weighted_feature_spawn_list += /mob/living/simple_animal/hostile/retaliate/dolphin/bouncer
if(3 to 4)
//TWO DOLPHS
for(var/x in 1 to 3)
weighted_feature_spawn_list += pick(/mob/living/simple_animal/hostile/retaliate/dolphin, /mob/living/simple_animal/hostile/retaliate/dolphin/manatee)
if(5 to 6)
//a dolphino
weighted_feature_spawn_list += pick(/mob/living/simple_animal/hostile/retaliate/dolphin, /mob/living/simple_animal/hostile/retaliate/dolphin/manatee)
else
//you get nothing good day sir
/datum/dungeon_room_theme/maintenance/space/hostile
room_type = ROOM_RATING_HOSTILE
weighted_feature_spawn_list = list(
/obj/structure/lattice = 10,
/obj/item/stack/ore/glass = 3,
/obj/item/tank/internals/emergency_oxygen = 1,
/obj/effect/mob_spawn/human/corpse = 2,
)
weighted_mob_spawn_list = list(
/mob/living/simple_animal/hostile/carp = 1,
)
mob_spawn_chance = 50

View File

@@ -374,10 +374,12 @@
/datum/status_effect/exercised/on_creation(mob/living/new_owner, ...)
. = ..()
owner.faction |= "gym"
STOP_PROCESSING(SSfastprocess, src)
START_PROCESSING(SSprocessing, src) //this lasts 20 minutes, so SSfastprocess isn't needed.
/datum/status_effect/exercised/Destroy()
owner.faction &= "gym"
. = ..()
STOP_PROCESSING(SSprocessing, src)

View File

@@ -100,7 +100,6 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
lighting_colour_tube = "#ffe5cb"
lighting_colour_bulb = "#ffdbb4"
//Departments
/area/maintenance/department/chapel

View File

@@ -0,0 +1,22 @@
/area/procedurally_generated
name = "Dungeon"
sound_environment = SOUND_AREA_LARGE_ENCLOSED
map_generator = /datum/map_generator/dungeon_generator
unique = FALSE
///If there's other areas on the same z-level that share a generator, their turfs will be aggregated together into a combined working area
var/shared_generator_initialized = FALSE
/area/procedurally_generated/RunGeneration()
if(ispath(map_generator))
map_generator = new map_generator(src)
map_generator.combine_local_areas()
if(!shared_generator_initialized)
map_generator.generate_terrain()
/area/procedurally_generated/test_gen()
if(!ispath(map_generator))
map_generator.generate_terrain()

View File

@@ -0,0 +1,171 @@
/area/procedurally_generated/maintenance
name = "Unfinished Maintenance"
ambience_index = AMBIENCE_MAINT
sound_environment = SOUND_AREA_TUNNEL_ENCLOSED
valid_territory = FALSE
minimap_color = "#4f4e3a"
airlock_wires = /datum/wires/airlock/maint
ambient_buzz = 'sound/ambience/source_corridor2.ogg'
ambient_buzz_vol = 10
min_ambience_cooldown = 20 SECONDS
max_ambience_cooldown = 35 SECONDS
map_generator = /datum/map_generator/dungeon_generator/maintenance
/area/procedurally_generated/maintenance/department/chapel
name = "Chapel Maintenance"
icon_state = "maint_chapel"
/area/procedurally_generated/maintenance/department/chapel/monastery
name = "Monastery Maintenance"
icon_state = "maint_monastery"
/area/procedurally_generated/maintenance/department/crew_quarters/bar
name = "Bar Maintenance"
icon_state = "maint_bar"
sound_environment = SOUND_AREA_WOODFLOOR
/area/procedurally_generated/maintenance/department/crew_quarters/dorms
name = "Dormitory Maintenance"
icon_state = "maint_dorms"
/area/procedurally_generated/maintenance/department/eva
name = "EVA Maintenance"
icon_state = "maint_eva"
/area/procedurally_generated/maintenance/department/electrical
name = "Electrical Maintenance"
icon_state = "maint_electrical"
/area/procedurally_generated/maintenance/department/tcoms
name = "Telecommunications Maintenance"
icon_state = "tcomsatmaint"
/area/procedurally_generated/maintenance/department/engine/atmos
name = "Atmospherics Maintenance"
icon_state = "maint_atmos"
/area/procedurally_generated/maintenance/department/security
name = "Security Maintenance"
icon_state = "maint_sec"
/area/procedurally_generated/maintenance/department/security/brig
name = "Brig Maintenance"
icon_state = "maint_brig"
/area/procedurally_generated/maintenance/department/medical
name = "Medbay Maintenance"
icon_state = "medbay_maint"
/area/procedurally_generated/maintenance/department/medical/central
name = "Central Medbay Maintenance"
icon_state = "medbay_maint_central"
/area/procedurally_generated/maintenance/department/medical/morgue
name = "Morgue Maintenance"
icon_state = "morgue_maint"
/area/procedurally_generated/maintenance/department/science
name = "Science Maintenance"
icon_state = "maint_sci"
/area/procedurally_generated/maintenance/department/science/central
name = "Central Science Maintenance"
icon_state = "maint_sci_central"
/area/procedurally_generated/maintenance/department/cargo
name = "Cargo Maintenance"
icon_state = "maint_cargo"
/area/procedurally_generated/maintenance/department/bridge
name = "Bridge Maintenance"
icon_state = "maint_bridge"
/area/procedurally_generated/maintenance/department/engine
name = "Engineering Maintenance"
icon_state = "maint_engi"
/area/procedurally_generated/maintenance/department/science/xenobiology
name = "Xenobiology Maintenance"
icon_state = "xenomaint"
xenobiology_compatible = TRUE
//Maintenance - Generic
/area/procedurally_generated/maintenance/aft
name = "Aft (S) Maintenance"
icon_state = "amaint"
/area/procedurally_generated/maintenance/aft/secondary
name = "Aft (S) Maintenance"
icon_state = "amaint_2"
/area/procedurally_generated/maintenance/central
name = "Central Maintenance"
icon_state = "maintcentral"
/area/procedurally_generated/maintenance/central/secondary
name = "Central Maintenance"
icon_state = "maintcentral"
clockwork_warp_allowed = FALSE
/area/procedurally_generated/maintenance/fore
name = "Fore (N) Maintenance"
icon_state = "fmaint"
/area/procedurally_generated/maintenance/fore/secondary
name = "Fore (N) Maintenance"
icon_state = "fmaint_2"
/area/procedurally_generated/maintenance/starboard
name = "Starboard (E) Maintenance"
icon_state = "smaint"
/area/procedurally_generated/maintenance/starboard/central
name = "Central Starboard (E) Maintenance"
icon_state = "smaint"
/area/procedurally_generated/maintenance/starboard/secondary
name = "Secondary Starboard (E) Maintenance"
icon_state = "smaint_2"
/area/procedurally_generated/maintenance/starboard/aft
name = "Starboard Quarter (SE) Maintenance"
icon_state = "asmaint"
/area/procedurally_generated/maintenance/starboard/aft/secondary
name = "Secondary Starboard Quarter (SE) Maintenance"
icon_state = "asmaint_2"
/area/procedurally_generated/maintenance/starboard/fore
name = "Starboard Bow (NE) Maintenance"
icon_state = "fsmaint"
/area/procedurally_generated/maintenance/port
name = "Port (W) Maintenance"
icon_state = "pmaint"
/area/procedurally_generated/maintenance/port/central
name = "Central Port (W) Maintenance"
icon_state = "maintcentral"
/area/procedurally_generated/maintenance/port/aft
name = "Port Quarter (SW) Maintenance"
icon_state = "apmaint"
/area/procedurally_generated/maintenance/port/fore
name = "Port Bow (NW) Maintenance"
icon_state = "fpmaint"
/area/procedurally_generated/maintenance/disposal
name = "Waste Disposal"
icon_state = "disposal"
/area/procedurally_generated/maintenance/disposal/incinerator
name = "Incinerator"
icon_state = "incinerator"
/area/procedurally_generated/maintenance/the_backrooms
name = "The Backrooms"

View File

@@ -1,6 +1,4 @@
#define SINGLE "single"
#define VERTICAL "vertical"
#define HORIZONTAL "horizontal"
#define METAL 1
#define WOOD 2
@@ -214,8 +212,6 @@
#undef SINGLE
#undef VERTICAL
#undef HORIZONTAL
#undef METAL
#undef WOOD

View File

@@ -6,6 +6,7 @@ again.
/obj/effect/spawner/structure
name = "map structure spawner"
density = TRUE
var/list/spawn_list
/obj/effect/spawner/structure/Initialize(mapload)

View File

@@ -169,6 +169,8 @@ GLOBAL_LIST_INIT(uranium_recipes, list ( \
point_value = 25
merge_type = /obj/item/stack/sheet/mineral/plasma
/obj/item/stack/sheet/mineral/plasma/ten
amount = 10
/obj/item/stack/sheet/mineral/plasma/fifty
amount = 50

View File

@@ -308,6 +308,8 @@ GLOBAL_LIST_INIT(wood_recipes, list ( \
if(!wood_stack && replace)
user.put_in_hands(new_item)
/obj/item/stack/sheet/mineral/wood/ten
amount = 10
/obj/item/stack/sheet/mineral/wood/fifty
amount = 50

View File

@@ -675,9 +675,15 @@ GLOBAL_LIST_EMPTY(lockers)
return ..()
/obj/structure/closet/CanAStarPass(ID, dir, caller)
if(can_open(caller) || allowed(caller))
return TRUE
//The parent function just checks if it's not dense, and if a closet is open then it's not dense
. = ..()
if(!.)
if(ismob(caller))
//i'm hilarious, but fr only mobs should be passed to allowed()
var/mob/mobchamp = caller
return can_open(mobchamp) && allowed(mobchamp)
else
return can_open(caller) && check_access(ID)
/// Signal proc for [COMSIG_ATOM_MAGICALLY_UNLOCKED]. Unlock and open up when we get knock casted.
/obj/structure/closet/proc/on_magic_unlock(datum/source, datum/action/cooldown/spell/aoe/knock/spell, mob/living/caster)

View File

@@ -7,6 +7,8 @@
obj_flags = CAN_BE_HIT | BLOCKS_CONSTRUCTION_DIR
density = TRUE
anchored = TRUE
pass_flags = LETPASSTHROW|PASSSTRUCTURE
layer = ABOVE_MOB_LAYER
pixel_y = -16
///Boolean on whether the railing should be cimable.

View File

@@ -11,7 +11,7 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
allowed_contents -= src
for(var/i in 1 to allowed_contents.len)
var/thing = allowed_contents[i]
qdel(thing, force=TRUE)
qdel(thing)
if(turf_type)
ChangeTurf(turf_type, baseturf_type, flags)

View File

@@ -48,6 +48,7 @@
/area/construction,
/area/vacant_room/commissary,
/area/survivalpod,
/area/procedurally_generated/maintenance,
))
if(is_type_in_typecache(A, engine_dirt_areas))
if(prob(3))
@@ -91,6 +92,7 @@
/area/ai_monitored/turret_protected,
/area/security,
/area/crew_quarters/heads/hos,
/area/procedurally_generated/maintenance,
))
if(is_type_in_typecache(A, gib_covered_areas))
if(prob(20))

0
code/game/turfs/turf.dm Executable file → Normal file
View File

View File

@@ -271,3 +271,26 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava)
return
level.traits |= traits_to_add
SSweather.update_z_level(level) //in case of someone adding a weather for the level, we want SSweather to update for that
/obj/effect/mapping_helpers/firedoor_border_spawner
var/orientation = VERTICAL
var/obj/machinery/door/firedoor/border_only/door1
var/obj/machinery/door/firedoor/border_only/door2
/obj/effect/mapping_helpers/firedoor_border_spawner/Initialize(mapload, _orientation)
. = ..()
if(_orientation)
orientation = _orientation
door1 = new(loc)
door2 = new(loc)
switch(orientation)
if(VERTICAL)
//door1 is naturally the right orientation
door2.dir = 1
if(HORIZONTAL)
door1.dir = 4
door2.dir = 8
/obj/effect/mapping_helpers/firedoor_border_spawner/horizontal
orientation = HORIZONTAL

View File

@@ -41,3 +41,14 @@
if (z_list && z >= 1 && z <= z_list.len)
return z_list[z]
CRASH("Unmanaged z-level [z]! maxz = [world.maxz], z_list.len = [z_list ? z_list.len : "null"]")
/datum/controller/subsystem/mapping/proc/generate_backrooms()
var/datum/space_level/backrooms = add_new_zlevel("The Backrooms", ZTRAITS_BACKROOM_MAINTS, contain_turfs = FALSE)
backrooms.set_linkage(SELFLOOPING)
var/area/procedurally_generated/maintenance/the_backrooms/suffer = new()
suffer.setup("The Backrooms")
for(var/turf/to_contain as anything in Z_TURFS(backrooms.z_value))
var/area/old_area = to_contain.loc
to_contain.ChangeTurf(/turf/open/genturf, flags = CHANGETURF_DEFER_CHANGE)
to_contain.change_area(old_area, suffer)
suffer.RunGeneration()

View File

@@ -102,8 +102,10 @@
name = "adminordrazine pill"
desc = "It's magic. We don't have to explain it."
icon_state = "pill16"
volume = 50
list_reagents = list(/datum/reagent/medicine/adminordrazine = 50)
volume = 5
grind_results = null
dissolvable = FALSE
list_reagents = list(/datum/reagent/medicine/adminordrazine = 5)
/obj/item/reagent_containers/pill/morphine
name = "morphine pill"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -229,6 +229,7 @@
#include "code\__DEFINES\{yogs_defines}\jungle.dm"
#include "code\__DEFINES\{yogs_defines}\layers.dm"
#include "code\__DEFINES\{yogs_defines}\logging.dm"
#include "code\__DEFINES\{yogs_defines}\mapping.dm"
#include "code\__DEFINES\{yogs_defines}\maps.dm"
#include "code\__DEFINES\{yogs_defines}\mentor.dm"
#include "code\__DEFINES\{yogs_defines}\misc.dm"
@@ -789,9 +790,18 @@
#include "code\datums\looping_sounds\weather.dm"
#include "code\datums\mapgen\_MapGenerator.dm"
#include "code\datums\mapgen\CaveGenerator.dm"
#include "code\datums\mapgen\DungeonGenerator.dm"
#include "code\datums\mapgen\biomes\_biome.dm"
#include "code\datums\mapgen\Cavegens\IcemoonCaves.dm"
#include "code\datums\mapgen\Cavegens\LavalandGenerator.dm"
#include "code\datums\mapgen\dungeon_generators\dungeon_room.dm"
#include "code\datums\mapgen\dungeon_generators\dungeon_room_theme.dm"
#include "code\datums\mapgen\dungeon_generators\maintenance_generator\maintenance_generator.dm"
#include "code\datums\mapgen\dungeon_generators\maintenance_generator\maintenance_room.dm"
#include "code\datums\mapgen\dungeon_generators\maintenance_generator\maintenance_room_theme.dm"
#include "code\datums\mapgen\dungeon_generators\maintenance_generator\maintenance_room_themes\random.dm"
#include "code\datums\mapgen\dungeon_generators\maintenance_generator\maintenance_room_themes\ruin.dm"
#include "code\datums\mapgen\dungeon_generators\maintenance_generator\maintenance_room_themes\space.dm"
#include "code\datums\martial\boxing.dm"
#include "code\datums\martial\buster_style.dm"
#include "code\datums\martial\cqc.dm"
@@ -926,7 +936,9 @@
#include "code\game\area\areas\centcom.dm"
#include "code\game\area\areas\holodeck.dm"
#include "code\game\area\areas\mining.dm"
#include "code\game\area\areas\procedurally_generated.dm"
#include "code\game\area\areas\shuttles.dm"
#include "code\game\area\areas\procedurally_generated_areas\maintenance.dm"
#include "code\game\area\areas\ruins\_ruins.dm"
#include "code\game\area\areas\ruins\icemoon.dm"
#include "code\game\area\areas\ruins\lavaland.dm"
@@ -4014,8 +4026,8 @@
#include "yogstation\code\datums\mutations\alcohol.dm"
#include "yogstation\code\datums\mutations\extendoarm.dm"
#include "yogstation\code\datums\ruins\free_miners.dm"
#include "yogstation\code\datums\ruins\jungle_fluff.dm"
#include "yogstation\code\datums\ruins\jungle.dm"
#include "yogstation\code\datums\ruins\jungle_fluff.dm"
#include "yogstation\code\datums\ruins\station.dm"
#include "yogstation\code\datums\status_effects\buffs.dm"
#include "yogstation\code\datums\status_effects\neutral.dm"