-
-
Notifications
You must be signed in to change notification settings - Fork 444
/
Copy pathDungeonGenerator.dm
277 lines (232 loc) · 10.8 KB
/
DungeonGenerator.dm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
/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
///A list of the probability that a type of room theme can be selected. look at mapping.dm in yog defines
var/list/probability_room_types = list()
///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