mirror of
https://github.com/PolarisSS13/Polaris.git
synced 2025-12-15 12:42:50 +00:00
Saving more work.
This commit is contained in:
@@ -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)
|
||||
@@ -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
|
||||
@@ -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>"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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')
|
||||
*/
|
||||
@@ -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)
|
||||
19
code/modules/gamemaster/event2/events/engineering/dust.dm
Normal file
19
code/modules/gamemaster/event2/events/engineering/dust.dm
Normal 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")
|
||||
@@ -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)
|
||||
@@ -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.
|
||||
|
||||
@@ -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")
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
*/
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
BIN
sound/effects/light_flicker.ogg
Normal file
BIN
sound/effects/light_flicker.ogg
Normal file
Binary file not shown.
Reference in New Issue
Block a user