Saving more work.

This commit is contained in:
Neerti
2020-03-22 03:25:44 -04:00
parent 33657c9e11
commit dd2edef196
24 changed files with 770 additions and 122 deletions

View File

@@ -38,4 +38,18 @@
var/pressure = environment ? environment.return_pressure() : 0
if(pressure < SOUND_MINIMUM_PRESSURE)
return TRUE
return FALSE
return FALSE
// Picks a turf that is clearance tiles away from the map edge given by dir, on z-level Z
/proc/pick_random_edge_turf(var/dir, var/Z, var/clearance = TRANSITIONEDGE + 1)
if(!dir)
return
switch(dir)
if(NORTH)
return locate(rand(clearance, world.maxx - clearance), world.maxy - clearance, Z)
if(SOUTH)
return locate(rand(clearance, world.maxx - clearance), clearance, Z)
if(EAST)
return locate(world.maxx - clearance, rand(clearance, world.maxy - clearance), Z)
if(WEST)
return locate(clearance, rand(clearance, world.maxy - clearance), Z)

View File

@@ -28,8 +28,10 @@ SUBSYSTEM_DEF(event_ticker)
event_started(E)
/datum/controller/subsystem/event_ticker/proc/event_started(datum/event2/event/E)
log_debug("Event [E.type] is now being ran.")
active_events += E
/datum/controller/subsystem/event_ticker/proc/event_finished(datum/event2/event/E)
log_debug("Event [E.type] has finished.")
active_events -= E
finished_events += E

View File

@@ -69,6 +69,19 @@ SUBSYSTEM_DEF(game_master)
/datum/controller/subsystem/game_master/proc/run_event(datum/event2/meta/chosen_event)
var/datum/event2/event/E = chosen_event.make_event()
chosen_event.times_ran++
if(!chosen_event.reusable)
// Disable this event, so it only gets picked once.
chosen_event.enabled = FALSE
if(chosen_event.event_class)
// Disable similar events, too.
for(var/M in available_events)
var/datum/event2/meta/meta = M
if(meta.event_class == chosen_event.event_class)
meta.enabled = FALSE
SSevent_ticker.event_started(E)
adjust_danger(chosen_event.chaos)
adjust_staleness(-(10 + chosen_event.chaos)) // More chaotic events reduce staleness more, e.g. a 25 chaos event will reduce it by 35.
@@ -171,7 +184,11 @@ SUBSYSTEM_DEF(game_master)
for(var/E in best_events)
var/datum/event2/meta/event = E
weighted_events[event] = event.get_weight()
var/weight = event.get_weight()
if(weight <= 0)
continue
weighted_events[event] = weight
log_game_master("Filtered down to [weighted_events.len] choice\s.")
var/datum/event2/meta/choice = pickweight(weighted_events)
@@ -237,10 +254,10 @@ SUBSYSTEM_DEF(game_master)
. += event
// The `old_like` game master tries to act like the old system, choosing events without any specific goals.
// The `classic` game master tries to act like the old system, choosing events without any specific goals.
// * Has no goals, and instead operates purely off of the weights of the events it has.
// * Does not react to danger at all.
/datum/game_master/old_like/choose_event()
/datum/game_master/classic/choose_event()
var/list/weighted_events = list()
for(var/E in ticker.available_events)
var/datum/event2/meta/event = E
@@ -377,7 +394,8 @@ SUBSYSTEM_DEF(game_master)
dat += "<th>Event Name</th>"
dat += "<th>Involved Departments</th>"
dat += "<th>Chaos</th>"
dat += "<th>Current Weight</th>"
dat += "<th>Chaotic Threshold</th>"
dat += "<th>Weight</th>"
dat += "<th>Buttons</th>"
dat += "</tr>"
@@ -390,6 +408,7 @@ SUBSYSTEM_DEF(game_master)
dat += "<td>[event.name]</td>"
dat += "<td>[english_list(event.departments)]</td>"
dat += "<td>[event.chaos]</td>"
dat += "<td>[event.chaotic_threshold]</td>"
dat += "<td>[event.get_weight()]</td>"
dat += "<td>[href(event, list("force" = 1), "\[Force\]")] [href(event, list("toggle" = 1), "\[Toggle\]")]</td>"
dat += "</tr>"

View File

@@ -1,4 +1,4 @@
var/list/blobs = list()
GLOBAL_LIST_EMPTY(all_blobs)
/obj/structure/blob
name = "blob"
@@ -18,21 +18,21 @@ var/list/blobs = list()
var/mob/observer/blob/overmind = null
var/base_name = "blob" // The name that gets appended along with the blob_type's name.
/obj/structure/blob/New(var/newloc, var/new_overmind)
..(newloc)
/obj/structure/blob/Initialize(newloc, new_overmind)
if(new_overmind)
overmind = new_overmind
update_icon()
if(!integrity)
integrity = max_integrity
set_dir(pick(cardinal))
blobs += src
GLOB.all_blobs += src
consume_tile()
return ..()
/obj/structure/blob/Destroy()
playsound(src.loc, 'sound/effects/splat.ogg', 50, 1) //Expand() is no longer broken, no check necessary.
blobs -= src
GLOB.all_blobs -= src
overmind = null
return ..()
@@ -112,7 +112,7 @@ var/list/blobs = list()
if(overmind)
expand_probablity *= overmind.blob_type.spread_modifier
if(overmind.blob_type.slow_spread_with_size)
expand_probablity /= (blobs.len / 10)
expand_probablity /= (GLOB.all_blobs.len / 10)
if(distance <= expand_range)
var/can_expand = TRUE

View File

@@ -12,6 +12,7 @@ var/list/blob_cores = list()
health_regen = 0 //we regen in Life() instead of when pulsed
var/datum/blob_type/desired_blob_type = null // If this is set, the core always creates an overmind possessing this blob type.
var/difficulty_threshold = null // Otherwise if this is set, it picks a random blob_type that is equal or lower in difficulty.
var/difficulty_floor = null // Related to the above var, acts as a floor value to the above, inclusive.
var/core_regen = 2
var/overmind_get_delay = 0 //we don't want to constantly try to find an overmind, this var tracks when we'll try to get an overmind again
var/resource_delay = 0
@@ -23,14 +24,18 @@ var/list/blob_cores = list()
ai_controlled = FALSE
// Spawn these if you want a semi-random blob.
// Can give a random easy blob.
/obj/structure/blob/core/random_easy
difficulty_threshold = BLOB_DIFFICULTY_EASY
// Can give a random easy or medium blob.
/obj/structure/blob/core/random_medium
difficulty_threshold = BLOB_DIFFICULTY_MEDIUM
// Can give a random medium or hard blob.
/obj/structure/blob/core/random_hard
difficulty_threshold = BLOB_DIFFICULTY_HARD
difficulty_floor = BLOB_DIFFICULTY_MEDIUM
// Spawn these if you want a specific blob.
/obj/structure/blob/core/blazing_oil
@@ -81,8 +86,8 @@ var/list/blob_cores = list()
/obj/structure/blob/core/classic
desired_blob_type = /datum/blob_type/classic
/obj/structure/blob/core/New(var/newloc, var/client/new_overmind = null, new_rate = 2, placed = 0)
..(newloc)
/obj/structure/blob/core/Initialize(newloc, client/new_overmind = null, new_rate = 2, placed = 0)
. = ..(newloc)
blob_cores += src
START_PROCESSING(SSobj, src)
update_icon() //so it atleast appears
@@ -180,7 +185,9 @@ var/list/blob_cores = list()
var/list/valid_types = list()
for(var/thing in subtypesof(/datum/blob_type))
var/datum/blob_type/BT = thing
if(initial(BT.difficulty) > difficulty_threshold)
if(initial(BT.difficulty) > difficulty_threshold) // Too hard.
continue
if(initial(BT.difficulty) < difficulty_floor) // Too easy.
continue
valid_types += BT
return pick(valid_types)

View File

@@ -9,10 +9,10 @@
var/resource_delay = 0
var/resource_cooldown = 4 SECONDS
/obj/structure/blob/resource/New(var/newloc, var/new_overmind)
..(newloc, new_overmind)
/obj/structure/blob/resource/Initialize(newloc, new_overmind)
if(overmind)
overmind.resource_blobs += src
return ..()
/obj/structure/blob/resource/Destroy()
if(overmind)

View File

@@ -23,7 +23,7 @@ var/list/overminds = list()
var/ai_controlled = TRUE
var/auto_pilot = FALSE // If true, and if a client is attached, the AI routine will continue running.
/mob/observer/blob/New(var/newloc, pre_placed = 0, starting_points = 60, desired_blob_type = null)
/mob/observer/blob/Initialize(newloc, pre_placed = 0, starting_points = 60, desired_blob_type = null)
blob_points = starting_points
if(pre_placed) //we already have a core!
placed = 1
@@ -41,10 +41,10 @@ var/list/overminds = list()
if(blob_core)
blob_core.update_icon()
..(newloc)
return ..(newloc)
/mob/observer/blob/Destroy()
for(var/BL in blobs)
for(var/BL in GLOB.all_blobs)
var/obj/structure/blob/B = BL
if(B && B.overmind == src)
B.overmind = null
@@ -65,7 +65,7 @@ var/list/overminds = list()
if(blob_core)
stat(null, "Core Health: [blob_core.integrity]")
stat(null, "Power Stored: [blob_points]/[max_blob_points]")
stat(null, "Total Blobs: [blobs.len]")
stat(null, "Total Blobs: [GLOB.all_blobs.len]")
/mob/observer/blob/Move(NewLoc, Dir = 0)
if(placed)

View File

@@ -22,6 +22,9 @@
to_chat(src, "<span class='warning'>There is no blob here!</span>")
return
if(B.overmind != src)
to_chat(src, span("warning", "This blob isn't controlled by you."))
if(!istype(B, /obj/structure/blob/normal))
to_chat(src, "<span class='warning'>Unable to use this blob, find a normal one.</span>")
return
@@ -66,7 +69,7 @@
return FALSE
var/obj/structure/blob/B = null
var/list/potential_blobs = blobs.Copy()
var/list/potential_blobs = GLOB.all_blobs.Copy()
while(potential_blobs.len)
var/obj/structure/blob/temp = pick(potential_blobs)
if(!(locate(/obj/structure/blob/node) in range(temp, BLOB_NODE_PULSE_RANGE) ) && !(locate(/obj/structure/blob/core) in range(temp, BLOB_CORE_PULSE_RANGE) ))
@@ -77,6 +80,8 @@
potential_blobs -= temp // Don't take up the core's shield spot.
else if(!istype(temp, /obj/structure/blob/normal))
potential_blobs -= temp // Not a normal blob.
else if(temp.overmind != src)
potential_blobs -= temp // Not our blob.
else
B = temp
break
@@ -107,7 +112,7 @@
return FALSE
var/obj/structure/blob/B = null
var/list/potential_blobs = blobs.Copy()
var/list/potential_blobs = GLOB.all_blobs.Copy()
while(potential_blobs.len)
var/obj/structure/blob/temp = pick(potential_blobs)
if(!(locate(/obj/structure/blob/node) in range(temp, BLOB_NODE_PULSE_RANGE) ) && !(locate(/obj/structure/blob/core) in range(temp, BLOB_CORE_PULSE_RANGE) ))
@@ -118,6 +123,8 @@
potential_blobs -= temp // Don't take up the core's shield spot.
else if(!istype(temp, /obj/structure/blob/normal))
potential_blobs -= temp // Not a normal blob.
else if(temp.overmind != src)
potential_blobs -= temp // Not our blob.
else
B = temp
break
@@ -149,7 +156,7 @@
return FALSE
var/obj/structure/blob/B = null
var/list/potential_blobs = blobs.Copy()
var/list/potential_blobs = GLOB.all_blobs.Copy()
while(potential_blobs.len)
var/obj/structure/blob/temp = pick(potential_blobs)
if(locate(/obj/structure/blob/node) in range(temp, 5) )
@@ -158,6 +165,8 @@
potential_blobs -= temp
else if(!istype(temp, /obj/structure/blob/normal))
potential_blobs -= temp
else if(temp.overmind != src)
potential_blobs -= temp // Not our blob.
else
B = temp
break
@@ -184,7 +193,7 @@
other_T = get_step(T, direction)
if(other_T)
B = locate(/obj/structure/blob) in other_T
if(B)
if(B && B.overmind == src)
break
if(!B)
@@ -216,7 +225,7 @@
for(var/direction in cardinal)
var/turf/T = get_step(L, direction)
B = locate(/obj/structure/blob) in T
if(B)
if(B && B.overmind == src)
break
if(!B)
continue

View File

@@ -65,6 +65,58 @@ This allows for events that have their announcement happen after the end itself.
/datum/event2/event/proc/get_location_z_levels()
return using_map.station_levels
// Returns a list of empty turfs in the same area.
/datum/event2/event/proc/find_random_turfs(minimum_free_space = 5, ignore_occupancy = FALSE)
// In the future it might be better to have areas themselves have a 'dont put event stuff here' var instead.
var/list/excluded_areas = list(
/area/submap,
/area/shuttle,
/area/crew_quarters,
/area/holodeck,
/area/engineering/engine_room
)
var/list/area/grand_list_of_areas = get_station_areas(excluded_areas)
if(!LAZYLEN(grand_list_of_areas))
return list()
for(var/i = 1 to 10)
var/area/A = pick(grand_list_of_areas)
if(!ignore_occupancy && is_area_occupied(A))
continue // Occupied.
var/list/turfs = list()
for(var/turf/T in A)
if(turf_clear(T))
turfs += T
if(turfs.len < minimum_free_space)
continue // Not enough free space.
return turfs
return list()
/*
var/list/area/grand_list_of_areas = get_station_areas(excluded)
if(grand_list_of_areas.len)
for(var/i in 1 to 10)
var/area/A = pick(grand_list_of_areas)
if(is_area_occupied(A))
log_debug("Blob infestation event: Rejected [A] because it is occupied.")
continue
var/list/turfs = list()
for(var/turf/simulated/floor/F in A)
if(turf_clear(F))
turfs += F
if(turfs.len < 5 + number_of_blobs)
log_debug("Blob infestation event: Rejected [A] because it has too little clear turfs.")
continue
open_turfs = turfs.Copy()
target_area = A
log_debug("Blob infestation event: Going to place blob at [A].")
break
if(!open_turfs.len)
log_debug("Blob infestation event: Giving up after too many failures to pick a blob spot.")
abort()
*/
// Starts the event.
/datum/event2/event/proc/execute()
time_started = world.time

View File

@@ -6,7 +6,7 @@
event_type = /datum/event2/event/shipping_error
/datum/event2/meta/shipping_error/get_weight()
return metric.count_people_in_department(DEPARTMENT_CARGO) * 40
return (metric.count_people_with_job(/datum/job/cargo_tech) + metric.count_people_with_job(/datum/job/qm)) * 30
/datum/event2/event/shipping_error/start()
var/datum/supply_order/O = new /datum/supply_order()

View File

@@ -1,67 +1,182 @@
/datum/event2/meta/blob
name = "blob"
departments = list(DEPARTMENT_ENGINEERING, DEPARTMENT_SECURITY, DEPARTMENT_MEDICAL)
chaos = 50
chaos = 30
chaotic_threshold = EVENT_CHAOS_THRESHOLD_HIGH_IMPACT
event_class = "blob" // This makes it so there is no potential for multiple blob events of different types happening in the same round.
event_type = /datum/event2/event/blob
// In the distant future, if a mechanical skill system were to come into being, these vars could be replaced with skill checks so off duty people could count.
var/required_fighters = 2 // Fighters refers to engineering OR security.
var/required_support = 1 // Support refers to doctors AND roboticists, depending on fighter composition.
/datum/event2/meta/blob/hard
name = "harder blob"
chaos = 40
event_type = /datum/event2/event/blob/hard_blob
required_fighters = 3
/datum/event2/meta/blob/multi_blob
name = "multi blob"
chaos = 60
event_type = /datum/event2/event/blob/multi_blob
required_fighters = 4
required_support = 2
// For bussing only.
/datum/event2/meta/blob/omni_blob
name = "omni blob"
chaos = 200
event_type = /datum/event2/event/blob/omni_blob
enabled = FALSE
/datum/event2/meta/blob/get_weight()
var/engineers = metric.count_people_in_department(DEPARTMENT_ENGINEERING)
var/security = metric.count_people_in_department(DEPARTMENT_SECURITY)
var/medical = metric.count_people_in_department(DEPARTMENT_MEDICAL)
// Count the 'fighters'.
var/list/engineers = metric.get_people_in_department(DEPARTMENT_ENGINEERING)
var/list/security = metric.get_people_in_department(DEPARTMENT_SECURITY)
var/assigned_staff = engineers + security
if(engineers || security) // Medical only counts if one of the other two exists, and even then they count as half.
assigned_staff += round(medical / 2)
if(engineers.len + security.len < required_fighters)
return 0
var/weight = (max(assigned_staff - 2, 0) * 20) // An assigned staff count of 2 must be had to spawn a blob.
return weight
// Now count the 'support'.
var/list/medical = metric.get_people_in_department(DEPARTMENT_MEDICAL)
var/need_medical = FALSE
var/list/robotics = metric.get_people_with_job(/datum/job/roboticist)
var/need_robotics = FALSE
// Determine what kind of support might be needed.
for(var/mob/living/L in engineers|security)
if(L.isSynthetic())
need_robotics = TRUE
else
need_medical = TRUE
// Medical is more important than robotics, since robits tend to not suffer slow deaths if there isn't a roboticist.
if(medical.len < required_support && need_medical)
return 0
// Engineers can sometimes fill in as robotics. This is done in the interest of the event having a chance of not being super rare.
// In the uncertain future, a mechanical skill system check could replace this check here.
if(robotics.len + engineers.len < required_support && need_robotics)
return 0
var/fighter_weight = (engineers.len + security.len) * 20
var/support_weight = (medical.len + robotics.len) * 10 // Not counting engineers as support so they don't cause 30 weight each.
var/chaos_weight = chaos / 2 // Chaos is added as a weight in order to make more chaotic variants be preferred if they are allowed to be picked.
return fighter_weight + support_weight + chaos_weight
/datum/event2/event/blob
// This could be made into a GLOB accessible list if needed.
var/list/area/excuded = list(
announce_delay_lower_bound = 1 MINUTE
announce_delay_upper_bound = 5 MINUTES
// This could be made into a GLOB accessible list for reuse if needed.
var/list/area/excluded = list(
/area/submap,
/area/shuttle,
/area/crew_quarters,
/area/holodeck,
/area/engineering/engine_room
)
var/turf/target_turf = null
var/list/open_turfs = list()
var/area/target_area = null
var/spawn_blob_type = /obj/structure/blob/core/random_medium
var/obj/structure/blob/core/blob_core = null
var/number_of_blobs = 1
var/list/blobs = list() // A list containing weakrefs to blob cores created. Weakrefs mean this event won't interfere with qdel.
/datum/event2/event/blob/hard_blob
spawn_blob_type = /obj/structure/blob/core/random_hard
/datum/event2/event/blob/multi_blob
spawn_blob_type = /obj/structure/blob/core/random_hard // Lethargic blobs are boring.
number_of_blobs = 2
// For adminbus only.
/datum/event2/event/blob/omni_blob
number_of_blobs = 16 // Someday maybe we can get this to specifically spawn every blob.
/datum/event2/event/blob/set_up()
var/list/area/grand_list_of_areas = get_station_areas(excluded)
if(grand_list_of_areas.len)
for(var/i in 1 to 10)
var/area/A = pick(grand_list_of_areas)
if(is_area_occupied(A))
log_debug("Blob infestation event: Rejected [A] because it is occupied.")
continue
var/list/turfs = list()
for(var/turf/simulated/floor/F in A)
if(turf_clear(F))
turfs += F
if(turfs.len < 5 + number_of_blobs)
log_debug("Blob infestation event: Rejected [A] because it has too little clear turfs.")
continue
open_turfs = turfs.Copy()
target_area = A
log_debug("Blob infestation event: Going to place blob at [A].")
break
for(var/i in 1 to 10)
var/area/A = pick(grand_list_of_areas)
if(is_area_occupied(A))
log_debug("Blob infestation event: Rejected [A] because it is occupied.")
continue
var/list/turfs = list()
for(var/turf/simulated/floor/F in A)
if(turf_clear(F))
turfs += F
if(turfs.len == 0)
log_debug("Blob infestation event: Rejected [A] because it has no clear turfs.")
continue
target_area = A
target_turf = pick(turfs)
if(!target_area)
log_debug("Blob infestation event: Giving up after too many failures to pick target area")
if(!open_turfs.len)
log_debug("Blob infestation event: Giving up after too many failures to pick a blob spot.")
abort()
/datum/event2/event/blob/start()
blob_core = new spawn_blob_type(target_turf)
for(var/i = 1 to number_of_blobs)
var/turf/T = pick(open_turfs)
var/obj/structure/blob/core/new_blob = new spawn_blob_type(T)
blobs += weakref(new_blob)
open_turfs -= T // So we can't put two cores on the same tile if doing multiblob.
log_debug("Spawned [new_blob.overmind.blob_type.name] blob at [get_area(new_blob)].")
/datum/event2/event/blob/should_end()
for(var/WR in blobs)
var/weakref/weakref = WR
if(weakref.resolve()) // If the weakref is resolvable, that means the blob hasn't been deleted yet.
return FALSE
return TRUE // Only end if all blobs die.
// Normally this does nothing, but is useful if aborted by an admin.
/datum/event2/event/blob/end()
for(var/WR in blobs)
var/weakref/weakref = WR
var/obj/structure/blob/core/B = weakref.resolve()
if(istype(B))
qdel(B)
/datum/event2/event/blob/announce()
spawn(rand(600, 3000)) // 1-5 minute leeway for the blob to go un-detected.
command_announcement.Announce("Confirmed outbreak of level 7 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", new_sound = 'sound/AI/outbreak7.ogg')
if(!ended) // Don't announce if the blobs die early.
var/danger_level = 0
var/list/blob_type_names = list()
var/multiblob = FALSE
for(var/WR in blobs)
var/weakref/weakref = WR
var/obj/structure/blob/core/B = weakref.resolve()
if(!istype(B))
continue
var/datum/blob_type/blob_type = B.overmind.blob_type
blob_type_names += blob_type.name
if(danger_level > blob_type.difficulty) // The highest difficulty is used, if multiple blobs are present.
danger_level = blob_type.difficulty
if(blob_type_names.len > 1) // More than one blob is harder.
danger_level += blob_type_names.len
multiblob = TRUE
var/list/lines = list()
lines += "Confirmed outbreak of level [7 + danger_level] biohazard[multiblob ? "s": ""] \
aboard [location_name()]. All personnel must contain the outbreak."
if(danger_level >= BLOB_DIFFICULTY_MEDIUM) // Tell them what kind of blob it is if it's tough.
lines += "The biohazard[multiblob ? "s have": " has"] been identified as [english_list(blob_type_names)]."
if(danger_level >= BLOB_DIFFICULTY_HARD) // If it's really hard then tell them where it is so the response occurs faster.
lines += "[multiblob ? "It is": "They are"] suspected to have originated from \the [target_area]."
if(danger_level >= BLOB_DIFFICULTY_SUPERHARD)
lines += "Extreme caution is advised."
command_announcement.Announce(lines.Join("\n"), "Biohazard Alert", new_sound = 'sound/AI/outbreak7.ogg')
/*
/datum/gm_action/blob
@@ -141,23 +256,3 @@
*/
/*
/proc/level_seven_blob_announcement(var/obj/structure/blob/core/B)
if(!B || !B.overmind)
return
var/datum/blob_type/blob = B.overmind.blob_type // Shortcut so we don't need to delve into three variables every time.
var/list/lines = list()
lines += "Confirmed outbreak of level [7 + blob.difficulty] biohazard aboard [station_name()]. All personnel must contain the outbreak."
if(blob.difficulty >= BLOB_DIFFICULTY_MEDIUM) // Tell them what kind of blob it is if it's tough.
lines += "The biohazard has been identified as a '[blob.name]'."
if(blob.difficulty >= BLOB_DIFFICULTY_HARD) // If it's really hard then tell them where it is so the response occurs faster.
lines += "It is suspected to have originated from \the [get_area(B)]."
if(blob.difficulty >= BLOB_DIFFICULTY_SUPERHARD)
lines += "Extreme caution is advised."
command_announcement.Announce(lines.Join("\n"), "Biohazard Alert", new_sound = 'sound/AI/outbreak7.ogg')
*/

View File

@@ -0,0 +1,142 @@
/datum/event2/meta/carp_migration
name = "carp migration"
event_class = "carp"
departments = list(DEPARTMENT_SECURITY, DEPARTMENT_EVERYONE)
chaos = 30
chaotic_threshold = EVENT_CHAOS_THRESHOLD_MEDIUM_IMPACT
event_type = /datum/event2/event/carp_migration
/datum/event2/meta/carp_migration/get_weight()
return 10 + (metric.count_people_in_department(DEPARTMENT_SECURITY) * 20) + (metric.count_all_space_mobs() * 40)
/datum/event2/event/carp_migration
announce_delay_lower_bound = 1 MINUTE
announce_delay_upper_bound = 2 MINUTES
length_lower_bound = 30 SECONDS
length_upper_bound = 1 MINUTE
var/use_map_edge_with_landmarks = TRUE // Use both landmarks and spawning from the "edge" of the map. Otherise uses landmarks over map edge.
var/carp_cap = 30 // No more than this many (living) carp can exist from this event.
var/carp_smallest_group = 3
var/carp_largest_group = 5
var/carp_wave_cooldown = 10 SECONDS
var/list/spawned_carp = list()
var/last_carp_wave_time = null // Last world.time we spawned a carp wave.
var/list/valid_z_levels = null
/datum/event2/event/carp_migration/set_up()
valid_z_levels = get_location_z_levels()
valid_z_levels -= using_map.sealed_levels // Space levels only please!
/datum/event2/event/carp_migration/announce()
var/announcement = "Unknown biological entities been detected near \the [location_name()], please stand-by."
command_announcement.Announce(announcement, "Lifesign Alert")
/datum/event2/event/carp_migration/event_tick()
if(last_carp_wave_time + carp_wave_cooldown > world.time)
return
last_carp_wave_time = world.time
if(count_spawned_carps() < carp_cap)
spawn_fish(number_of_groups = rand(1, 4), min_size_of_group = carp_smallest_group, max_size_of_group = carp_largest_group)
/datum/event2/event/carp_migration/end()
// Clean up carp that died in space for some reason.
for(var/mob/living/simple_mob/SM in spawned_carp)
if(SM.stat == DEAD)
var/turf/T = get_turf(SM)
if(istype(T, /turf/space))
if(prob(75))
qdel(SM)
// Makes carp spawn, no landmarks required, but they're still used if they exist.
/datum/event2/event/carp_migration/proc/spawn_fish(number_of_groups, min_size_of_group, max_size_of_group, dir)
if(isnull(dir))
dir = pick(GLOB.cardinal)
// Check if any landmarks exist!
var/list/spawn_locations = list()
for(var/obj/effect/landmark/C in landmarks_list)
if(C.name == "carpspawn" && (C.z in valid_z_levels))
spawn_locations.Add(C.loc)
var/prioritize_landmarks = TRUE
if(use_map_edge_with_landmarks && prob(50))
prioritize_landmarks = FALSE // One in two chance to come from the edge instead.
if(spawn_locations.len && prioritize_landmarks) // Okay we've got landmarks, lets use those!
shuffle_inplace(spawn_locations)
number_of_groups = min(number_of_groups, spawn_locations.len)
var/i = 1
while (i <= number_of_groups)
var/group_size = rand(min_size_of_group, max_size_of_group)
for (var/j = 0, j < group_size, j++)
spawn_one_carp(spawn_locations[i])
i++
return
// Okay we did *not* have any landmarks, or we're being told to do both, so lets do our best!
var/i = 1
while(i <= number_of_groups)
var/z_level = pick(valid_z_levels)
var/group_size = rand(min_size_of_group, max_size_of_group)
var/turf/map_center = locate(round(world.maxx/2), round(world.maxy/2), z_level)
var/turf/group_center = pick_random_edge_turf(dir, z_level, TRANSITIONEDGE + 2)
var/list/turfs = getcircle(group_center, 2)
for(var/j = 0, j < group_size, j++)
// On larger maps, BYOND gets in the way of letting simple_mobs path to the closest edge of the station.
// So instead we need to simulate the carp's travel, then spawn them somewhere still hopefully off screen.
// Find a turf to be the edge of the map.
var/turf/edge_of_map = turfs[(i % turfs.len) + 1]
// Now walk a straight line towards the center of the map, until we find a non-space tile.
var/turf/edge_of_station = null
var/list/space_line = list() // This holds all space tiles on the line. Will be used a bit later.
for(var/turf/T in getline(edge_of_map, map_center))
if(!T.is_space())
break // We found the station!
space_line += T
edge_of_station = T
// Now put the carp somewhere on the line, hopefully off screen.
// I wish this was higher than 8 but the BYOND internal A* algorithm gives up sometimes when using
// 16 or more.
// In the future, a new AI stance that handles long distance travel using getline() could work.
var/max_distance = 8
var/turf/spawn_turf = null
for(var/P in space_line)
var/turf/point = P
if(get_dist(point, edge_of_station) <= max_distance)
spawn_turf = P
break
if(spawn_turf)
// Finally, make the carp go towards the edge of the station.
var/mob/living/simple_mob/animal/M = spawn_one_carp(spawn_turf)
if(edge_of_station)
M.ai_holder?.give_destination(edge_of_station) // Ask carp to swim towards the edge of the station.
i++
/datum/event2/event/carp_migration/proc/spawn_one_carp(new_loc)
var/mob/living/simple_mob/animal/M = new /mob/living/simple_mob/animal/space/carp/event(new_loc)
GLOB.destroyed_event.register(M, src, .proc/on_carp_destruction)
spawned_carp += M
return M
// Counts living carp spawned by this event.
/datum/event2/event/carp_migration/proc/count_spawned_carps()
. = 0
for(var/I in spawned_carp)
var/mob/living/simple_mob/animal/M = I
if(!QDELETED(M) && M.stat != DEAD)
. += 1
// If carp is bomphed, remove it from the list.
/datum/event2/event/carp_migration/proc/on_carp_destruction(mob/M)
spawned_carp -= M
GLOB.destroyed_event.unregister(M, src, .proc/on_carp_destruction)

View File

@@ -0,0 +1,19 @@
/datum/event2/meta/dust
name = "dust"
departments = list(DEPARTMENT_ENGINEERING)
chaos = 10
chaotic_threshold = EVENT_CHAOS_THRESHOLD_LOW_IMPACT
reusable = TRUE
event_type = /datum/event2/event/dust
/datum/event2/meta/dust/get_weight()
return metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 20
/datum/event2/event/dust/announce()
if(prob(33))
command_announcement.Announce("Dust has been detected on a collision course with \the [location_name()].")
/datum/event2/event/dust/start()
dust_swarm("norm")

View File

@@ -0,0 +1,47 @@
/datum/event2/meta/gas_leak
name = "gas leak"
departments = list(DEPARTMENT_ENGINEERING, DEPARTMENT_SYNTHETIC)
chaos = 10
chaotic_threshold = EVENT_CHAOS_THRESHOLD_LOW_IMPACT
reusable = TRUE
event_type = /datum/event2/event/gas_leak
/datum/event2/meta/gas_leak/get_weight()
// Synthetics are counted in higher value because they can wirelessly connect to alarms.
var/engineering_factor = metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 10
var/synthetic_factor = metric.count_people_in_department(DEPARTMENT_SYNTHETIC) * 30
return (15 + engineering_factor + synthetic_factor) / (times_ran + 1)
/datum/event2/event/gas_leak
var/potential_gas_choices = list("carbon_dioxide", "sleeping_agent", "phoron", "volatile_fuel")
var/chosen_gas = null
var/turf/chosen_turf = null
/datum/event2/event/gas_leak/set_up()
chosen_gas = pick(potential_gas_choices)
var/list/turfs = find_random_turfs()
if(!turfs.len)
log_debug("Gas Leak event failed to find any available turfs to leak into. Aborting.")
abort()
return
chosen_turf = pick(turfs)
/datum/event2/event/gas_leak/announce()
if(chosen_turf)
command_announcement.Announce("Warning, hazardous [lowertext(gas_data.name[chosen_gas])] gas leak detected in \the [chosen_turf.loc], evacuate the area.", "Hazard Alert")
/datum/event2/event/gas_leak/start()
// Okay, time to actually put the gas in the room!
// TODO - Would be nice to break a waste pipe perhaps?
// TODO - Maybe having it released from a single point and thus causing airflow to blow stuff around
// Fow now just add a bunch of it to the air
var/datum/gas_mixture/air_contents = new
air_contents.temperature = T20C + rand(-50, 50)
air_contents.gas[chosen_gas] = 10 * MOLES_CELLSTANDARD
chosen_turf.assume_air(air_contents)
playsound(chosen_turf, 'sound/effects/smoke.ogg', 75, 1)

View File

@@ -23,7 +23,11 @@
return highest_overpower
/datum/event2/meta/grid_check/get_weight()
return 50 + (metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 20) + (50 * get_overpower())
var/population_factor = metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 10
var/overpower_factor = 50 * get_overpower() // Will be 0 if not overloaded at all, and 50 if turbines are outputting twice as much as rated.
return (20 + population_factor + overpower_factor) / (times_ran + 1)
/datum/event2/event/grid_check
var/obj/machinery/power/generator/engine // The turbine that will send a power spike.

View File

@@ -0,0 +1,83 @@
// This event gives the station an advance warning about meteors, so that they can prepare in various ways.
/datum/event2/meta/meteor_defense
name = "meteor defense"
departments = list(DEPARTMENT_ENGINEERING, DEPARTMENT_CARGO)
chaos = 50
chaotic_threshold = EVENT_CHAOS_THRESHOLD_HIGH_IMPACT
event_class = "meteor defense"
event_type = /datum/event2/event/meteor_defense
/datum/event2/meta/meteor_defense/get_weight()
// Engineers count as 20.
var/engineers = metric.count_people_in_department(DEPARTMENT_ENGINEERING)
if(engineers < 3) // There -must- be at least three engineers for this to be possible.
return 0
. = engineers * 20
// Cargo and AI/borgs count as 10.
var/cargo = metric.count_people_with_job(/datum/job/cargo_tech) + metric.count_people_with_job(/datum/job/qm)
var/bots = metric.count_people_in_department(DEPARTMENT_SYNTHETIC)
. += (cargo + bots) * 10
/datum/event2/event/meteor_defense
start_delay_lower_bound = 10 MINUTES
start_delay_upper_bound = 15 MINUTES
var/soon_announced = FALSE
var/direction = null // Actual dir used for which side the meteors come from.
var/dir_text = null // Direction shown in the announcement.
var/list/meteor_types = null
var/waves = null // How many times to send meteors.
var/last_wave_time = null // world.time of latest wave.
var/wave_delay = 10 SECONDS
var/wave_upper_bound = 8 // Max amount of meteors per wave.
var/wave_lower_bound = 4 // Min amount.
/datum/event2/event/meteor_defense/proc/set_meteor_types()
meteor_types = meteors_threatening.Copy()
/datum/event2/event/meteor_defense/set_up()
direction = pick(cardinal) // alldirs doesn't work with current meteor code unfortunately.
waves = rand(3, 6)
switch(direction)
if(NORTH)
dir_text = "aft" // For some reason this is needed.
if(SOUTH)
dir_text = "fore"
if(EAST)
dir_text = "port"
if(WEST)
dir_text = "starboard"
set_meteor_types()
/datum/event2/event/meteor_defense/announce()
var/announcement = "Meteors are expected to approach from the [dir_text] side, in approximately [DisplayTimeText(time_to_start - world.time, 60)]."
command_announcement.Announce(announcement, "Meteor Alert", new_sound = 'sound/AI/meteors.ogg')
/datum/event2/event/meteor_defense/wait_tick()
if(!soon_announced)
if((time_to_start - world.time) <= 5 MINUTES)
soon_announced = TRUE
var/announcement = "The incoming meteors are expected to approach from the [dir_text] side. \
ETA to arrival is approximately [DisplayTimeText(time_to_start - world.time, 60)]."
command_announcement.Announce(announcement, "Meteor Alert - Update")
/datum/event2/event/meteor_defense/start()
command_announcement.Announce("Incoming meteors approach from \the [dir_text] side!", "Meteor Alert - Update")
/datum/event2/event/meteor_defense/event_tick()
if(world.time > last_wave_time + wave_delay)
last_wave_time = world.time
waves--
message_admins("[waves] more wave\s of meteors remain.")
// Dir is reversed because the direction describes where meteors are going, not what side it's gonna hit.
spawn_meteors(rand(wave_upper_bound, wave_lower_bound), meteor_types, reverse_dir[direction])
/datum/event2/event/meteor_defense/should_end()
return waves <= 0
/datum/event2/event/meteor_defense/end()
command_announcement.Announce("\The [location_name()] will clear the incoming meteors in a moment.", "Meteor Alert - Update")

View File

@@ -22,11 +22,13 @@
to_chat(A, "<br>")
/datum/event2/event/comms_blackout/start()
// One in two chance for the radios to turn i%t# t&_)#%, which can be more alarming than radio silence.
if(prob(50))
// One in two chance for the radios to turn i%t# t&_)#%, which can be more alarming than radio silence.
log_debug("Doing partial outage of telecomms.")
for(var/obj/machinery/telecomms/processor/P in telecomms_list)
P.emp_act(1)
// Otherwise just shut everything down.
else
// Otherwise just shut everything down, madagascar style.
log_debug("Doing complete outage of telecomms.")
for(var/obj/machinery/telecomms/T in telecomms_list)
T.emp_act(1)

View File

@@ -0,0 +1,111 @@
// Makes a spooky electrical thing happen, that can blow the lights or make the APCs turn off for a short period of time.
// Doesn't do any permanent damage beyond the small chance to emag an APC, which just unlocks it forever. As such, this is free to occur even with no engineers.
/datum/event2/meta/electrical_fault
name = "electrical fault"
departments = list(DEPARTMENT_EVERYONE)
chaos = 10
chaotic_threshold = EVENT_CHAOS_THRESHOLD_LOW_IMPACT
event_type = /datum/event2/event/electrical_fault
/datum/event2/meta/electrical_fault/get_weight()
return 10 + (metric.count_people_in_department(DEPARTMENT_EVERYONE) * 5)
/datum/event2/event/electrical_fault
start_delay_lower_bound = 30 SECONDS
start_delay_upper_bound = 1 MINUTE
length_lower_bound = 20 SECONDS
length_upper_bound = 40 SECONDS
var/max_apcs_per_tick = 2
var/list/valid_apcs = null
var/list/valid_z_levels = null
/datum/event2/event/electrical_fault/announce()
// Trying to be vague to avoid 'space lightning storms'.
// This could be re-flavored to be a solar flare or something and have robots outside be sad.
command_announce.Announce("External conditions near \the [location_name()] are likely \
to cause voltage spikes and other electrical issues very soon. Please secure sensitive electrical equipment until conditions improve.", "[location_name()] Sensor Array")
/datum/event2/event/electrical_fault/set_up()
valid_z_levels = get_location_z_levels()
valid_z_levels -= using_map.sealed_levels // Space levels only please!
valid_apcs = list()
for(var/obj/machinery/power/apc/A in global.machines)
if(A.z in valid_z_levels)
valid_apcs += A
/datum/event2/event/electrical_fault/event_tick()
if(!valid_apcs.len)
log_debug("No valid APCs found for electrical fault event.")
return
var/list/picked_apcs = list()
for(var/i = 1 to max_apcs_per_tick)
picked_apcs |= pick(valid_apcs)
for(var/A in picked_apcs)
affect_apc(A)
/datum/event2/event/electrical_fault/end()
command_announce.Announce("External conditions have returned to normal around \the [location_name()].")
/datum/event2/event/electrical_fault/proc/affect_apc(obj/machinery/power/apc/A)
// Main breaker is turned off or is Special(tm). Consider it protected.
if((!A.operating || failure_timer > 0) || A.is_critical)
return
playsound(
// Decent chance to overload lighting circuit.
if(prob(6))
A.overload_lighting()
// Small chance to make the APC turn off for awhile.
// This will actually protect it from further damage.
if(prob(3))
A.energy_fail(rand(20, 40))
// Relatively small chance to emag the apc as apc_damage event does.
if(prob(1))
A.emagged = TRUE
A.update_icon()
/*
/datum/event/electrical_storm/start()
..()
valid_apcs = list()
for(var/obj/machinery/power/apc/A in global.machines)
if(A.z in affecting_z)
valid_apcs.Add(A)
endWhen = (severity * 60) + startWhen
/datum/event/electrical_storm/tick()
..()
// See if shields can stop it first (It would be nice to port baystation's cooler shield gens perhaps)
// TODO - We need a better shield generator system to handle this properly.
if(!valid_apcs.len)
log_debug("No valid APCs found for electrical storm event ship=[victim]!")
return
var/list/picked_apcs = list()
for(var/i=0, i< severity * 2, i++) // up to 2/4/6 APCs per tick depending on severity
picked_apcs |= pick(valid_apcs)
for(var/obj/machinery/power/apc/T in picked_apcs)
affect_apc(T)
/datum/event/electrical_storm/proc/affect_apc(var/obj/machinery/power/apc/T)
// Main breaker is turned off. Consider this APC protected.
if(!T.operating)
return
// Decent chance to overload lighting circuit.
if(prob(3 * severity))
T.overload_lighting()
// Relatively small chance to emag the apc as apc_damage event does.
if(prob(0.2 * severity))
T.emagged = 1
T.update_icon()
*/

View File

@@ -4,7 +4,9 @@
event_type = /datum/event2/event/solar_storm
/datum/event2/meta/solar_storm/get_weight()
return 20 + (metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 10) + (metric.count_all_space_mobs() * 30)
var/population_factor = metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 10
var/space_factor = metric.count_all_space_mobs() * 50
return (20 + population_factor + space_factor) / (times_ran + 1)
/datum/event2/event/solar_storm

View File

@@ -7,7 +7,7 @@
/datum/event2/meta/ion_storm/get_weight()
var/bots = metric.count_people_in_department(DEPARTMENT_SYNTHETIC)
var/weight = 5 + (bots * 20)
var/weight = 5 + (bots * 40) // A small chance even if no synths are on, since it can still emag beepsky.
return weight
@@ -29,6 +29,8 @@
var/mob/living/silicon/robot/R = target
if(R.connected_ai)
continue
if(R.shell)
continue
// Crew member names, and excluding off station antags, are handled by `generate_ion_law()` automatically.
var/law = target.generate_ion_law()

View File

@@ -26,6 +26,15 @@
// If true, the event won't have it's `enabled` var set to FALSE when ran by the GM system.
var/reusable = FALSE
// A string used to identify a 'class' of similar events.
// If the event is not reusable, than all events sharing the same class are disabled.
// Useful if you only ever want one event per round while having a lot of different subtypes of the event.
var/event_class = null
// Counter for how many times this event has been picked by the GM.
// Can be used to make event repeats discouraged but not forbidden by adjusting the weight based on it.
var/times_ran = 0
// A reference to the system that initialized us.
var/datum/controller/subsystem/game_master/GM = null
@@ -42,13 +51,10 @@
return ..()
// Called by the GM system to actually start an event,
// and makes it so events that should only be ran once are made to not be usable again.
// Called by the GM system to actually start an event.
/datum/event2/meta/proc/make_event()
var/datum/event2/event/E = new event_type(GM)
E.execute()
if(!reusable)
enabled = FALSE
return E
// Returns a TRUE or FALSE for if the GM system should be able to pick this event.

View File

@@ -29,44 +29,72 @@
return DEPARTMENT_UNKNOWN // Welp.
// Similar to above, but gets the actual job. Note that it returns the job datum itself, or null.
/datum/metric/proc/guess_job(mob/M)
// Like before, records are the most reliable way.
var/datum/data/record/R = find_general_record("name", M.real_name)
if(R) // They got a record, now find the job datum.
var/datum/job/J = SSjob.get_job(R.fields["real_rank"])
if(istype(J))
return J
// Try the mind.
if(M.mind)
var/datum/job/J = SSjob.get_job(M.mind.assigned_role)
if(istype(J))
return J
// Last ditch effort, check for job assigned to the mob itself.
var/datum/job/J = SSjob.get_job(M.job)
if(istype(J))
return J
return null
// Feed this proc the name of a job, and it will try to figure out what department they are apart of.
// Note that this returns a list, as some jobs are in more than one department, like Command. The 'primary' department is the first
// in the list, e.g. a HoS has Security as first, Command as second in the returned list.
// Improved with the addition of SSjob, which has departments be an actual thing and not a virtual concept.
/datum/metric/proc/role_name_to_department(var/role_name)
var/list/result = list()
var/datum/job/J = SSjob.get_job(role_name)
if(istype(J))
if(LAZYLEN(J.departments))
return J.departments
return list(DEPARTMENT_UNKNOWN)
if(SSjob.is_job_in_department(role_name, DEPARTMENT_SECURITY))
result += DEPARTMENT_SECURITY
/datum/metric/proc/count_people_in_department(var/department, cutoff = 75)
var/list/L = get_people_in_department(department, cutoff)
return L.len
if(SSjob.is_job_in_department(role_name, DEPARTMENT_ENGINEERING))
result += DEPARTMENT_ENGINEERING
if(SSjob.is_job_in_department(role_name, DEPARTMENT_MEDICAL))
result += DEPARTMENT_MEDICAL
if(SSjob.is_job_in_department(role_name, DEPARTMENT_RESEARCH))
result += DEPARTMENT_RESEARCH
if(SSjob.is_job_in_department(role_name, DEPARTMENT_CARGO))
result += DEPARTMENT_CARGO
if(SSjob.is_job_in_department(role_name, DEPARTMENT_CIVILIAN))
result += DEPARTMENT_CIVILIAN
if(SSjob.is_job_in_department(role_name, DEPARTMENT_SYNTHETIC))
result += DEPARTMENT_SYNTHETIC
if(SSjob.is_job_in_department(role_name, DEPARTMENT_COMMAND)) // We do Command last, since we consider command to only be a primary department for hop/admin.
result += DEPARTMENT_COMMAND
if(!result.len) // No department was found.
result += DEPARTMENT_UNKNOWN
return result
/datum/metric/proc/count_people_in_department(var/department)
/datum/metric/proc/get_people_in_department(department, cutoff = 75)
. = list()
if(!department)
return
for(var/mob/M in player_list)
if(guess_department(M) != department) // Ignore people outside the department we're counting.
continue
. += 1
if(assess_player_activity(M) < cutoff)
continue
. += M
/datum/metric/proc/get_people_with_job(job_type, cutoff = 75)
. = list()
// First, get the name.
var/datum/job/J = SSjob.get_job_type(job_type)
if(!istype(J))
return
// Now find people with the job name.
for(var/M in player_list)
var/datum/job/their_job = guess_job(M)
if(!istype(their_job)) // No job was guessed.
continue
if(their_job.title != J.title) // Jobs don't match.
continue
if(assess_player_activity(M) < cutoff) // Too AFK.
continue
. += M
/datum/metric/proc/count_people_with_job(job_type, cutoff = 75)
var/list/L = get_people_with_job(job_type, cutoff)
return L.len

View File

@@ -1424,7 +1424,6 @@
#include "code\modules\awaymissions\trigger.dm"
#include "code\modules\awaymissions\zlevel.dm"
#include "code\modules\blob2\_defines.dm"
#include "code\modules\blob2\announcement.dm"
#include "code\modules\blob2\blobs\base_blob.dm"
#include "code\modules\blob2\blobs\core.dm"
#include "code\modules\blob2\blobs\factory.dm"
@@ -1732,9 +1731,14 @@
#include "code\modules\gamemaster\event2\event.dm"
#include "code\modules\gamemaster\event2\meta.dm"
#include "code\modules\gamemaster\event2\events\cargo\shipping_error.dm"
#include "code\modules\gamemaster\event2\events\engineering\blob.dm"
#include "code\modules\gamemaster\event2\events\engineering\camera_damage.dm"
#include "code\modules\gamemaster\event2\events\engineering\canister_leak.dm"
#include "code\modules\gamemaster\event2\events\engineering\carp_migration.dm"
#include "code\modules\gamemaster\event2\events\engineering\dust.dm"
#include "code\modules\gamemaster\event2\events\engineering\gas_leak.dm"
#include "code\modules\gamemaster\event2\events\engineering\grid_check.dm"
#include "code\modules\gamemaster\event2\events\engineering\meteor_defense.dm"
#include "code\modules\gamemaster\event2\events\engineering\spacevine.dm"
#include "code\modules\gamemaster\event2\events\everyone\comms_blackout.dm"
#include "code\modules\gamemaster\event2\events\everyone\solar_storm.dm"
@@ -2828,7 +2832,7 @@
#include "code\ZAS\Zone.dm"
#include "interface\interface.dm"
#include "interface\skin.dmf"
#include "maps\example\example.dm"
#include "maps\southern_cross\southern_cross.dm"
#include "maps\submaps\space_submaps\space.dm"
#include "maps\submaps\surface_submaps\mountains\mountains.dm"
#include "maps\submaps\surface_submaps\mountains\mountains_areas.dm"

Binary file not shown.