mirror of
https://github.com/PolarisSS13/Polaris.git
synced 2026-08-22 04:36:53 +01:00
Events ported, now for cleanup if needed.
This commit is contained in:
@@ -40,7 +40,7 @@
|
||||
// A good area, great! Lets try and pick a turf
|
||||
var/list/turfs = list()
|
||||
for(var/turf/simulated/floor/F in A)
|
||||
if(turf_clear(F))
|
||||
if(!check_density(F))
|
||||
turfs += F
|
||||
if(turfs.len == 0)
|
||||
log_debug("atmos_leak event: Rejected [A] because it has no clear turfs.")
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
continue
|
||||
var/list/turfs = list()
|
||||
for(var/turf/simulated/floor/F in A)
|
||||
if(turf_clear(F))
|
||||
if(!check_density(F))
|
||||
turfs += F
|
||||
if(turfs.len == 0)
|
||||
log_debug("Blob infestation event: Rejected [A] because it has no clear turfs.")
|
||||
|
||||
@@ -55,6 +55,11 @@ This allows for events that have their announcement happen after the end itself.
|
||||
var/time_to_announce = null
|
||||
var/time_to_end = null
|
||||
|
||||
// These are also set automatically, and are provided for events to know what RNG decided for the various durations.
|
||||
var/start_delay = null
|
||||
var/announce_delay = null
|
||||
var/length = null
|
||||
|
||||
// Returns the name of where the event is taking place.
|
||||
// In the future this might be handy for off-station events.
|
||||
/datum/event2/event/proc/location_name()
|
||||
@@ -66,69 +71,57 @@ This allows for events that have their announcement happen after the end itself.
|
||||
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)
|
||||
/datum/event2/event/proc/find_random_turfs(minimum_free_space = 5, list/specific_areas = list(), ignore_occupancy = FALSE)
|
||||
var/list/area/grand_list_of_areas = find_random_areas(specific_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.
|
||||
|
||||
for(var/thing in grand_list_of_areas)
|
||||
var/list/A = thing
|
||||
var/list/turfs = list()
|
||||
for(var/turf/T in A)
|
||||
if(turf_clear(T))
|
||||
if(!T.check_density())
|
||||
turfs += T
|
||||
|
||||
if(turfs.len < minimum_free_space)
|
||||
continue // Not enough free space.
|
||||
return turfs
|
||||
|
||||
return list()
|
||||
|
||||
/datum/event2/event/proc/find_random_areas(list/specific_areas = list(), ignore_occupancy = FALSE)
|
||||
if(!LAZYLEN(specific_areas))
|
||||
specific_areas = global.the_station_areas.Copy()
|
||||
|
||||
/*
|
||||
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()
|
||||
*/
|
||||
var/list/area/grand_list_of_areas = get_all_existing_areas_of_types(specific_areas)
|
||||
. = list()
|
||||
for(var/thing in shuffle(grand_list_of_areas))
|
||||
var/area/A = thing
|
||||
if(A.forbid_events)
|
||||
continue
|
||||
if(!(A.z in get_location_z_levels()))
|
||||
continue
|
||||
if(!ignore_occupancy && is_area_occupied(A))
|
||||
continue // Occupied.
|
||||
. += A
|
||||
|
||||
// Starts the event.
|
||||
/datum/event2/event/proc/execute()
|
||||
time_started = world.time
|
||||
|
||||
if(announce_delay_lower_bound)
|
||||
time_to_announce = world.time + rand(announce_delay_lower_bound, announce_delay_upper_bound ? announce_delay_upper_bound : announce_delay_lower_bound)
|
||||
announce_delay = rand(announce_delay_lower_bound, announce_delay_upper_bound ? announce_delay_upper_bound : announce_delay_lower_bound)
|
||||
time_to_announce = world.time + announce_delay
|
||||
|
||||
if(start_delay_lower_bound)
|
||||
time_to_start = world.time + rand(start_delay_lower_bound, start_delay_upper_bound ? start_delay_upper_bound : start_delay_lower_bound)
|
||||
start_delay = rand(start_delay_lower_bound, start_delay_upper_bound ? start_delay_upper_bound : start_delay_lower_bound)
|
||||
time_to_start = world.time + start_delay
|
||||
|
||||
if(length_lower_bound)
|
||||
time_to_end = world.time + rand(length_lower_bound, length_upper_bound ? length_upper_bound : length_lower_bound)
|
||||
var/starting_point = time_to_start ? time_to_start : world.time
|
||||
length = rand(length_lower_bound, length_upper_bound ? length_upper_bound : length_lower_bound)
|
||||
time_to_end = starting_point + length
|
||||
|
||||
set_up()
|
||||
|
||||
@@ -150,21 +143,21 @@ This allows for events that have their announcement happen after the end itself.
|
||||
/datum/event2/event/process()
|
||||
// Handle announcement track.
|
||||
if(!announced && should_announce())
|
||||
announce()
|
||||
announced = TRUE
|
||||
announce()
|
||||
|
||||
// Handle event track.
|
||||
if(!started)
|
||||
if(should_start())
|
||||
start()
|
||||
started = TRUE
|
||||
start()
|
||||
else
|
||||
wait_tick()
|
||||
|
||||
if(started && !ended)
|
||||
if(should_end())
|
||||
end()
|
||||
ended = TRUE
|
||||
end()
|
||||
else
|
||||
event_tick()
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/datum/event2/meta/manifest_malfunction
|
||||
name = "manifest_malfunction"
|
||||
departments = list(DEPARTMENT_COMMAND, DEPARTMENT_SECURITY, DEPARTMENT_EVERYONE)
|
||||
chaos = 10
|
||||
chaotic_threshold = EVENT_CHAOS_THRESHOLD_LOW_IMPACT
|
||||
event_type = /datum/event2/event/manifest_malfunction
|
||||
|
||||
/datum/event2/meta/manifest_malfunction/get_weight()
|
||||
var/security = metric.count_people_in_department(DEPARTMENT_SECURITY)
|
||||
|
||||
if(!security || !data_core)
|
||||
return 0
|
||||
|
||||
var/command = metric.count_people_with_job(/datum/job/hop) + metric.count_people_with_job(/datum/job/captain)
|
||||
var/synths = metric.count_people_in_department(DEPARTMENT_SYNTHETIC)
|
||||
var/everyone = metric.count_people_in_department(DEPARTMENT_EVERYONE) - (synths + security + command) // So they don't get counted twice.
|
||||
|
||||
return (security * 10) + (synths * 20) + (command * 20) + (everyone * 5)
|
||||
|
||||
|
||||
|
||||
/datum/event2/event/manifest_malfunction
|
||||
announce_delay_lower_bound = 5 MINUTES
|
||||
announce_delay_upper_bound = 10 MINUTES
|
||||
var/records_to_delete = 2
|
||||
var/record_class_to_delete = null
|
||||
|
||||
/datum/event2/event/manifest_malfunction/set_up()
|
||||
record_class_to_delete = pickweight(list("medical" = 10, "security" = 30))
|
||||
|
||||
/datum/event2/event/manifest_malfunction/announce()
|
||||
if(prob(30))
|
||||
var/message = null
|
||||
var/author = null
|
||||
var/rng = rand(1, 2)
|
||||
switch(rng)
|
||||
if(1)
|
||||
author = "Data Breach Alert"
|
||||
message = "The [record_class_to_delete] record database has suffered from an attack by one or more hackers. \
|
||||
They appear to have wiped several records, before disconnecting."
|
||||
if(2)
|
||||
author = "Downtime Alert"
|
||||
message = "The [record_class_to_delete] record database server has suffered a hardware failure, and is no longer functional. \
|
||||
A temporary replacement server has been activated, containing recovered data from the main server. \
|
||||
A few records became corrupted, and could not be transferred."
|
||||
command_announcement.Announce(message, author)
|
||||
|
||||
/datum/event2/event/manifest_malfunction/start()
|
||||
for(var/i = 1 to records_to_delete)
|
||||
var/datum/data/record/R
|
||||
|
||||
switch(record_class_to_delete)
|
||||
if("security")
|
||||
R = safepick(data_core.security)
|
||||
|
||||
if("medical")
|
||||
R = safepick(data_core.medical)
|
||||
|
||||
if(R)
|
||||
log_debug("Manifest malfunction event is now deleting [R.fields["name"]]'s [record_class_to_delete] record.")
|
||||
qdel(R)
|
||||
@@ -0,0 +1,110 @@
|
||||
/datum/event2/meta/money_hacker
|
||||
name = "money hacker"
|
||||
departments = list(DEPARTMENT_COMMAND)
|
||||
chaos = 10
|
||||
chaotic_threshold = EVENT_CHAOS_THRESHOLD_LOW_IMPACT
|
||||
event_type = /datum/event2/event/money_hacker
|
||||
|
||||
/datum/event2/meta/money_hacker/get_weight()
|
||||
var/command = metric.count_people_with_job(/datum/job/hop) + metric.count_people_with_job(/datum/job/captain)
|
||||
|
||||
if(!command)
|
||||
return 0
|
||||
return 30 + (command * 20) + (all_money_accounts.len * 5)
|
||||
|
||||
|
||||
|
||||
/datum/event2/event/money_hacker
|
||||
length_lower_bound = 8 MINUTES
|
||||
length_upper_bound = 12 MINUTES
|
||||
var/datum/money_account/targeted_account = null
|
||||
|
||||
/datum/event2/event/money_hacker/set_up()
|
||||
if(LAZYLEN(all_money_accounts))
|
||||
targeted_account = pick(all_money_accounts)
|
||||
|
||||
if(!targeted_account)
|
||||
log_debug("Money hacker event could not find an account to hack. Aborting.")
|
||||
abort()
|
||||
return
|
||||
|
||||
/datum/event2/event/money_hacker/announce()
|
||||
var/message = "A brute force hack has been detected (in progress since [stationtime2text()]). The target of the attack is: Financial account #[targeted_account.account_number], \
|
||||
without intervention this attack will succeed in approximately 10 minutes. Required intervention: temporary suspension of affected accounts until the attack has ceased. \
|
||||
Notifications will be sent as updates occur."
|
||||
var/my_department = "[location_name()] Firewall Subroutines"
|
||||
|
||||
for(var/obj/machinery/message_server/MS in machines)
|
||||
if(!MS.active)
|
||||
continue
|
||||
MS.send_rc_message("Head of Personnel's Desk", my_department, "[message]<br>", "", "", 2)
|
||||
|
||||
// Nobody reads the requests consoles so lets use the radio as well.
|
||||
global_announcer.autosay(message, my_department, DEPARTMENT_COMMAND)
|
||||
|
||||
/datum/event2/event/money_hacker/end()
|
||||
var/message = null
|
||||
if(targeted_account && !targeted_account.suspended) // Hacker wins.
|
||||
message = "The hack attempt has succeeded."
|
||||
hack_account(targeted_account)
|
||||
log_debug("Money hacker event managed to hack the targeted account.")
|
||||
|
||||
else // Crew wins.
|
||||
message = "The attack has ceased, the affected accounts can now be brought online."
|
||||
log_debug("Money hacker event failed to hack the targeted account due to intervention by the crew.")
|
||||
|
||||
var/my_department = "[location_name()] Firewall Subroutines"
|
||||
|
||||
for(var/obj/machinery/message_server/MS in machines)
|
||||
if(!MS.active) continue
|
||||
MS.send_rc_message("Head of Personnel's Desk", my_department, message, "", "", 2)
|
||||
|
||||
global_announcer.autosay(message, my_department, DEPARTMENT_COMMAND)
|
||||
|
||||
/datum/event2/event/money_hacker/proc/hack_account(datum/money_account/A)
|
||||
// Subtract the money.
|
||||
var/lost = A.money * 0.8 + (rand(2,4) - 2) / 10
|
||||
A.money -= lost
|
||||
|
||||
// Create a taunting log entry.
|
||||
var/datum/transaction/T = new()
|
||||
T.target_name = pick(list(
|
||||
"",
|
||||
"yo brotha from anotha motha",
|
||||
"el Presidente",
|
||||
"chieF smackDowN",
|
||||
"Nobody"
|
||||
))
|
||||
|
||||
T.purpose = pick(list(
|
||||
"Ne$ ---ount fu%ds init*&lisat@*n",
|
||||
"PAY BACK YOUR MUM",
|
||||
"Funds withdrawal",
|
||||
"pWnAgE",
|
||||
"l33t hax",
|
||||
"liberationez",
|
||||
"Hit",
|
||||
"Nothing"
|
||||
))
|
||||
|
||||
T.amount = pick(list(
|
||||
"",
|
||||
"([rand(0,99999)])",
|
||||
"alla money",
|
||||
"9001$",
|
||||
"HOLLA HOLLA GET DOLLA",
|
||||
"([lost])",
|
||||
"69,420t"
|
||||
))
|
||||
|
||||
var/date1 = "31 December, 1999"
|
||||
var/date2 = "[num2text(rand(1,31))] [pick("January","February","March","April","May","June","July","August","September","October","November","December")], [rand(1000,3000)]"
|
||||
T.date = pick("", current_date_string, date1, date2,"Nowhen")
|
||||
|
||||
var/time1 = rand(0, 99999999)
|
||||
var/time2 = "[round(time1 / 36000)+12]:[(time1 / 600 % 60) < 10 ? add_zero(time1 / 600 % 60, 1) : time1 / 600 % 60]"
|
||||
T.time = pick("", stationtime2text(), time2, "Never")
|
||||
|
||||
T.source_terminal = pick("","[pick("Biesel","New Gibson")] GalaxyNet Terminal #[rand(111,999)]","your mums place","nantrasen high CommanD","Angessa's Pearl","Nowhere")
|
||||
|
||||
A.transaction_log.Add(T)
|
||||
@@ -0,0 +1,102 @@
|
||||
/datum/event2/meta/raise_funds
|
||||
name = "local funding drive"
|
||||
enabled = FALSE // There isn't really any suitable way for the crew to generate thalers right now, if that gets fixed feel free to turn this event on.
|
||||
departments = list(DEPARTMENT_COMMAND, DEPARTMENT_CARGO)
|
||||
chaotic_threshold = EVENT_CHAOS_THRESHOLD_LOW_IMPACT
|
||||
event_type = /datum/event2/event/raise_funds
|
||||
|
||||
/datum/event2/meta/raise_funds/get_weight()
|
||||
var/command = metric.count_people_in_department(DEPARTMENT_COMMAND)
|
||||
if(!command) // Need someone to read the centcom message.
|
||||
return 0
|
||||
|
||||
var/cargo = metric.count_people_in_department(DEPARTMENT_CARGO)
|
||||
return (command * 20) + (cargo * 20)
|
||||
|
||||
|
||||
|
||||
/datum/event2/event/raise_funds
|
||||
length_lower_bound = 30 MINUTES
|
||||
length_upper_bound = 45 MINUTES
|
||||
var/money_at_start = 0
|
||||
|
||||
/datum/event2/event/raise_funds/announce()
|
||||
var/message = "Due to [pick("recent", "unfortunate", "possible future")] budget \
|
||||
[pick("changes", "issues")], in-system stations are now advised to increase funding income."
|
||||
|
||||
send_command_report("Budget Advisement", message)
|
||||
|
||||
/datum/event2/event/raise_funds/start()
|
||||
// Note that the event remembers the amount of money when it started. If an issue develops where people try to scam centcom by
|
||||
// taking out loads of money before the event, then depositing it back in after the event fires, feel free to make this check for
|
||||
// roundstart money instead.
|
||||
money_at_start = count_money()
|
||||
log_debug("Funding Drive event logged a sum of [money_at_start] thalers in all station accounts at the start of the event.")
|
||||
|
||||
/datum/event2/event/raise_funds/end()
|
||||
var/money_at_end = count_money()
|
||||
log_debug("Funding Drive event logged a sum of [money_at_end] thalers in all station accounts at the end of the event, compared \
|
||||
to [money_at_start] thalers. A difference of [money_at_end / money_at_start] was calculated.")
|
||||
|
||||
// A number above 1 indicates money was made, while below 1 does the opposite.
|
||||
var/budget_shift = money_at_end / money_at_start
|
||||
|
||||
// Centcom will say different things based on if they gained or lost money.
|
||||
var/message = null
|
||||
switch(budget_shift)
|
||||
if(0 to 0.02) // Abyssmal response.
|
||||
message = "We are very interested in learning where [round(money_at_start, 1000)] thaler went in \
|
||||
just half an hour. We highly recommend rectifying this issue before the end of the shift, otherwise a \
|
||||
discussion regarding your future employment prospects will occur.<br><br>\
|
||||
Your facility's current balance of requisition tokens has been revoked, and the rate of further \
|
||||
tokens will be severely slowed."
|
||||
supply_controller.points = 0
|
||||
supply_controller.points_per_process = max(supply_controller.points_per_process - 1, 0.5)
|
||||
log_debug("Funding Drive event ended with an abyssmal response, and the loss of all cargo points.")
|
||||
|
||||
if(0.02 to 0.98) // Bad response.
|
||||
message = "We're very disappointed that \the [location_name()] has ran a deficit since our request. \
|
||||
As such, we will be taking away some requisition tokens to cover the cost of operating your facility, \
|
||||
as well as decreasing the amount of tokens your facility will receive over time.."
|
||||
var/points_lost = round(supply_controller.points * rand(0.5, 0.8))
|
||||
supply_controller.points -= points_lost
|
||||
supply_controller.points_per_process = max(supply_controller.points_per_process - 0.5, 0.5)
|
||||
log_debug("Funding Drive event ended with a bad response, and [points_lost] cargo points was taken away.")
|
||||
|
||||
if(0.98 to 1.02) // Neutral response.
|
||||
message = "It is unfortunate that \the [location_name()]'s finances remain at a standstill, however \
|
||||
that is still preferred over having a decicit. We hope that in the future, your facility will be able to be \
|
||||
more profitable."
|
||||
log_debug("Funding Drive event ended with a neutral response.")
|
||||
|
||||
if(1.02 to INFINITY) // Good response.
|
||||
message = "We appreciate the efforts made by \the [location_name()] to run at a surplus. \
|
||||
Together, along with the other facilities present in the [using_map.starsys_name] system, \
|
||||
the company is expected to meet the quota.<br><br>\
|
||||
We will allocate additional requisition tokens for the cargo department as a reward. The rate \
|
||||
of further tokens has also been increased."
|
||||
|
||||
// If cargo is ever made to use station funds instead of cargo points, then a new kind of reward will be needed.
|
||||
// Otherwise it would be weird for centcom to go 'thanks for not spending money, your reward is money to spend'.
|
||||
var/point_reward = rand(100, 200)
|
||||
supply_controller.points += point_reward
|
||||
supply_controller.points_per_process += 0.5
|
||||
log_debug("Funding Drive event ended with a good response and a bonus of [point_reward] cargo points.")
|
||||
|
||||
send_command_report("Budget Followup", message)
|
||||
|
||||
|
||||
|
||||
// Returns the sum of the station account and all the departmental accounts.
|
||||
/datum/event2/event/raise_funds/proc/count_money()
|
||||
. = 0
|
||||
. += station_account.money
|
||||
for(var/i = 1 to SSjob.department_datums.len)
|
||||
var/datum/money_account/account = LAZYACCESS(department_accounts, SSjob.department_datums[i])
|
||||
if(istype(account))
|
||||
. += account.money
|
||||
|
||||
/datum/event2/event/raise_funds/proc/send_command_report(title, message)
|
||||
post_comm_message(title, message)
|
||||
to_world(span("danger", "New [using_map.company_name] Update available at all communication consoles."))
|
||||
SEND_SOUND(world, 'sound/AI/commandreport.ogg')
|
||||
@@ -0,0 +1,181 @@
|
||||
/datum/event2/meta/airlock_failure
|
||||
event_class = "airlock failure"
|
||||
departments = list(DEPARTMENT_ENGINEERING, DEPARTMENT_MEDICAL)
|
||||
chaos = 15
|
||||
chaotic_threshold = EVENT_CHAOS_THRESHOLD_MEDIUM_IMPACT
|
||||
event_type = /datum/event2/event/airlock_failure
|
||||
|
||||
/datum/event2/meta/airlock_failure/emag
|
||||
name = "airlock failure - emag"
|
||||
event_type = /datum/event2/event/airlock_failure/emag
|
||||
|
||||
/datum/event2/meta/airlock_failure/door_crush
|
||||
name = "airlock failure - crushing"
|
||||
event_type = /datum/event2/event/airlock_failure/door_crush
|
||||
|
||||
/datum/event2/meta/airlock_failure/shock
|
||||
name = "airlock failure - shock"
|
||||
chaos = 30
|
||||
event_type = /datum/event2/event/airlock_failure/shock
|
||||
|
||||
|
||||
/datum/event2/meta/airlock_failure/get_weight()
|
||||
var/engineering = metric.count_people_in_department(DEPARTMENT_ENGINEERING)
|
||||
|
||||
// Synths are good both for fixing the doors and getting blamed for the doors zapping people.
|
||||
var/synths = metric.count_people_in_department(DEPARTMENT_SYNTHETIC)
|
||||
if(!engineering && !synths) // Nobody's around to fix the door.
|
||||
return 0
|
||||
|
||||
// Medical might be needed for some of the more violent airlock failures.
|
||||
var/medical = metric.count_people_in_department(DEPARTMENT_MEDICAL)
|
||||
|
||||
return (engineering * 20) + (medical * 20) + (synths * 20)
|
||||
|
||||
|
||||
|
||||
/datum/event2/event/airlock_failure
|
||||
announce_delay_lower_bound = 20 SECONDS
|
||||
announce_delay_upper_bound = 40 SECONDS
|
||||
var/announce_odds = 0
|
||||
var/doors_to_break = 1
|
||||
var/list/affected_areas = list()
|
||||
|
||||
/datum/event2/event/airlock_failure/emag
|
||||
announce_odds = 10 // To make people wonder if the emagged door was from a baddie or from this event.
|
||||
doors_to_break = 2 // Replacing emagged doors really sucks for engineering so don't overdo it.
|
||||
|
||||
/datum/event2/event/airlock_failure/door_crush
|
||||
announce_odds = 30
|
||||
doors_to_break = 5
|
||||
|
||||
/datum/event2/event/airlock_failure/shock
|
||||
announce_odds = 70
|
||||
|
||||
/datum/event2/event/airlock_failure/start()
|
||||
var/list/areas = find_random_areas()
|
||||
if(!LAZYLEN(areas))
|
||||
log_debug("Airlock Failure event could not find any areas. Aborting.")
|
||||
abort()
|
||||
return
|
||||
|
||||
while(areas.len)
|
||||
var/area/area = pick(areas)
|
||||
areas -= area
|
||||
|
||||
for(var/obj/machinery/door/airlock/door in area.contents)
|
||||
if(can_break_door(door))
|
||||
addtimer(CALLBACK(src, .proc/break_door, door), 1) // Emagging proc is actually a blocking proc and that's bad for the ticker.
|
||||
door.visible_message(span("danger", "\The [door]'s panel sparks!"))
|
||||
playsound(door, "sparks", 50, 1)
|
||||
log_debug("Airlock Failure event has broken \the [door] airlock in [area].")
|
||||
affected_areas |= area
|
||||
doors_to_break--
|
||||
|
||||
if(doors_to_break <= 0)
|
||||
return
|
||||
|
||||
/datum/event2/event/airlock_failure/announce()
|
||||
if(prob(announce_odds))
|
||||
command_announcement.Announce("An electrical issue has been detected in the airlock grid at [english_list(affected_areas)]. \
|
||||
Some airlocks may require servicing by a qualified technician.", "Electrical Alert")
|
||||
|
||||
|
||||
/datum/event2/event/airlock_failure/proc/can_break_door(obj/machinery/door/airlock/door)
|
||||
if(istype(door, /obj/machinery/door/airlock/lift))
|
||||
return FALSE
|
||||
return door.arePowerSystemsOn()
|
||||
|
||||
// Override this for door busting.
|
||||
/datum/event2/event/airlock_failure/proc/break_door(obj/machinery/door/airlock/door)
|
||||
|
||||
/datum/event2/event/airlock_failure/emag/break_door(obj/machinery/door/airlock/door)
|
||||
door.emag_act(1)
|
||||
|
||||
/datum/event2/event/airlock_failure/door_crush/break_door(obj/machinery/door/airlock/door)
|
||||
door.normalspeed = FALSE
|
||||
door.safe = FALSE
|
||||
|
||||
/datum/event2/event/airlock_failure/shock/break_door(obj/machinery/door/airlock/door)
|
||||
door.electrify(-1)
|
||||
|
||||
|
||||
|
||||
/*
|
||||
/datum/gm_action/electrified_door
|
||||
name = "airlock short-circuit"
|
||||
departments = list(DEPARTMENT_ENGINEERING, DEPARTMENT_MEDICAL)
|
||||
chaotic = 10
|
||||
var/obj/machinery/door/airlock/chosen_door
|
||||
var/area/target_area
|
||||
var/list/area/excluded = list(
|
||||
/area/submap,
|
||||
/area/shuttle,
|
||||
/area/crew_quarters
|
||||
)
|
||||
|
||||
/datum/gm_action/electrified_door/set_up()
|
||||
var/list/area/grand_list_of_areas = get_station_areas(excluded)
|
||||
|
||||
severity = pickweight(EVENT_LEVEL_MUNDANE = 10,
|
||||
EVENT_LEVEL_MODERATE = 5,
|
||||
EVENT_LEVEL_MAJOR = 1
|
||||
)
|
||||
|
||||
//try 10 times
|
||||
for(var/i in 1 to 10)
|
||||
target_area = pick(grand_list_of_areas)
|
||||
var/list/obj/machinery/door/airlock/target_doors = list()
|
||||
for(var/obj/machinery/door/airlock/target_door in target_area.contents)
|
||||
target_doors += target_door
|
||||
target_doors = shuffle(target_doors)
|
||||
|
||||
for(var/obj/machinery/door/airlock/target_door in target_doors)
|
||||
if(!target_door.isElectrified() && target_door.arePowerSystemsOn() && target_door.maxhealth == target_door.health)
|
||||
chosen_door = target_door
|
||||
return
|
||||
|
||||
/datum/gm_action/electrified_door/start()
|
||||
..()
|
||||
if(!chosen_door)
|
||||
return
|
||||
command_announcement.Announce("An electrical issue has been detected in your area, please repair potential electronic overloads.", "Electrical Alert")
|
||||
chosen_door.visible_message("<span class='danger'>\The [chosen_door]'s panel sparks!</span>")
|
||||
chosen_door.set_safeties(0)
|
||||
playsound(get_turf(chosen_door), 'sound/machines/buzz-sigh.ogg', 50, 1)
|
||||
if(severity >= EVENT_LEVEL_MODERATE)
|
||||
chosen_door.electrify(-1)
|
||||
spawn(rand(10 SECONDS, 2 MINUTES))
|
||||
if(chosen_door && chosen_door.arePowerSystemsOn() && prob(25 + 25 * severity))
|
||||
command_announcement.Announce("Overload has been localized to \the [target_area].", "Electrical Alert")
|
||||
|
||||
if(severity >= EVENT_LEVEL_MAJOR) // New Major effect. Hydraulic boom.
|
||||
spawn()
|
||||
chosen_door.visible_message("<span class='warning'>\The [chosen_door] buzzes.</span>")
|
||||
playsound(get_turf(chosen_door), 'sound/machines/buzz-sigh.ogg', 50, 1)
|
||||
sleep(rand(10 SECONDS, 3 MINUTES))
|
||||
if(!chosen_door || !chosen_door.arePowerSystemsOn())
|
||||
return
|
||||
chosen_door.visible_message("<span class='warning'>\The [chosen_door]'s hydraulics creak.</span>")
|
||||
playsound(get_turf(chosen_door), 'sound/machines/airlock_creaking.ogg', 50, 1)
|
||||
sleep(rand(30 SECONDS, 10 MINUTES))
|
||||
if(!chosen_door || !chosen_door.arePowerSystemsOn())
|
||||
return
|
||||
chosen_door.visible_message("<span class='danger'>\The [chosen_door] emits a hydraulic shriek!</span>")
|
||||
playsound(get_turf(chosen_door), 'sound/machines/airlock.ogg', 80, 1)
|
||||
spawn(rand(5 SECONDS, 30 SECONDS))
|
||||
if(!chosen_door || !chosen_door.arePowerSystemsOn())
|
||||
return
|
||||
chosen_door.visible_message("<span class='critical'>\The [chosen_door]'s hydraulics detonate!</span>")
|
||||
chosen_door.fragmentate(get_turf(chosen_door), rand(5, 10), rand(3, 5), list(/obj/item/projectile/bullet/pellet/fragment/tank/small))
|
||||
explosion(get_turf(chosen_door),-1,-1,2,3)
|
||||
|
||||
chosen_door.lock()
|
||||
chosen_door.health = chosen_door.maxhealth / 6
|
||||
chosen_door.aiControlDisabled = 1
|
||||
chosen_door.update_icon()
|
||||
|
||||
/datum/gm_action/electrified_door/get_weight()
|
||||
return 10 + (metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 5 + metric.count_people_in_department(DEPARTMENT_MEDICAL) * 10)
|
||||
|
||||
*/
|
||||
@@ -106,7 +106,7 @@
|
||||
continue
|
||||
var/list/turfs = list()
|
||||
for(var/turf/simulated/floor/F in A)
|
||||
if(turf_clear(F))
|
||||
if(!F.check_density())
|
||||
turfs += F
|
||||
if(turfs.len < 5 + number_of_blobs)
|
||||
log_debug("Blob infestation event: Rejected [A] because it has too little clear turfs.")
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/datum/event2/meta/brand_intelligence
|
||||
name = "vending machine malware"
|
||||
departments = list(DEPARTMENT_EVERYONE, DEPARTMENT_EVERYONE)
|
||||
chaos = 10
|
||||
chaotic_threshold = EVENT_CHAOS_THRESHOLD_LOW_IMPACT
|
||||
event_type = /datum/event2/event/brand_intelligence
|
||||
|
||||
/datum/event2/meta/brand_intelligence/get_weight()
|
||||
return 10 + (metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 20)
|
||||
|
||||
|
||||
|
||||
/datum/event2/event/brand_intelligence
|
||||
var/malware_spread_cooldown = 30 SECONDS
|
||||
|
||||
var/list/vending_machines = list() // List of venders that can potentially be infected.
|
||||
var/list/infected_vending_machines = list() // List of venders that have been infected.
|
||||
var/obj/machinery/vending/vender_zero = null // The first vending machine infected. If that one gets fixed, all other infected machines will be cured.
|
||||
var/last_malware_spread_time = null
|
||||
|
||||
/datum/event2/event/brand_intelligence/set_up()
|
||||
for(var/obj/machinery/vending/V in machines)
|
||||
if(!(V.z in using_map.station_levels))
|
||||
continue
|
||||
vending_machines += V
|
||||
|
||||
if(!vending_machines.len)
|
||||
log_debug("Could not find any vending machines on station Z levels. Aborting.")
|
||||
abort()
|
||||
return
|
||||
|
||||
vender_zero = pick(vending_machines)
|
||||
|
||||
/datum/event2/event/brand_intelligence/announce()
|
||||
if(prob(90))
|
||||
command_announcement.Announce("An ongoing mass upload of malware for venders has been detected onboard \the [location_name()], \
|
||||
which appears to transmit to nearby vendors. The original infected machine is believed to be \a [vender_zero].", "Vender Service Alert")
|
||||
|
||||
/datum/event2/event/brand_intelligence/start()
|
||||
infect_vender(vender_zero)
|
||||
|
||||
/datum/event2/event/brand_intelligence/event_tick()
|
||||
if(last_malware_spread_time + malware_spread_cooldown > world.time)
|
||||
return // Still on cooldown.
|
||||
last_malware_spread_time = world.time
|
||||
|
||||
if(vending_machines.len)
|
||||
var/next_victim = pick(vending_machines)
|
||||
infect_vender(next_victim)
|
||||
|
||||
// Every time Vender Zero infects, it says something.
|
||||
vender_zero.speak(pick("Try our aggressive new marketing strategies!", \
|
||||
"You should buy products to feed your lifestyle obsession!", \
|
||||
"Consume!", \
|
||||
"Your money can buy happiness!", \
|
||||
"Engage direct marketing!", \
|
||||
"Advertising is legalized lying! But don't let that put you off our great deals!", \
|
||||
"You don't want to buy anything? Yeah, well I didn't want to buy your mom either."))
|
||||
|
||||
|
||||
/datum/event2/event/brand_intelligence/should_end()
|
||||
if(!vending_machines.len)
|
||||
return TRUE
|
||||
if(!can_propagate(vender_zero))
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/datum/event2/event/brand_intelligence/end()
|
||||
if(can_propagate(vender_zero)) // The crew failed and all the machines are infected!
|
||||
return
|
||||
// Otherwise Vender Zero was taken out in some form.
|
||||
if(vender_zero)
|
||||
vender_zero.visible_message(span("notice", "\The [vender_zero]'s network activity light flickers wildly \
|
||||
for a few seconds as a small screen reads: 'Rolling out firmware reset to networked machines'."))
|
||||
for(var/obj/machinery/vending/vender in infected_vending_machines)
|
||||
cure_vender(vender)
|
||||
|
||||
/datum/event2/event/brand_intelligence/proc/infect_vender(obj/machinery/vending/V)
|
||||
vending_machines -= V
|
||||
infected_vending_machines += V
|
||||
V.shut_up = FALSE
|
||||
V.shoot_inventory = TRUE
|
||||
|
||||
/datum/event2/event/brand_intelligence/proc/cure_vender(obj/machinery/vending/V)
|
||||
infected_vending_machines -= V
|
||||
V.shut_up = TRUE
|
||||
V.shoot_inventory = FALSE
|
||||
|
||||
/datum/event2/event/brand_intelligence/proc/can_propagate(obj/machinery/vending/V)
|
||||
return V && V.shut_up == FALSE
|
||||
@@ -1,142 +0,0 @@
|
||||
/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)
|
||||
@@ -0,0 +1,46 @@
|
||||
/datum/event2/meta/wallrot
|
||||
name = "wall-rot"
|
||||
departments = list(DEPARTMENT_ENGINEERING)
|
||||
reusable = TRUE
|
||||
event_type = /datum/event2/event/wallrot
|
||||
|
||||
/datum/event2/meta/wallrot/get_weight()
|
||||
return (10 + metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 10) / (times_ran + 1)
|
||||
|
||||
|
||||
|
||||
|
||||
/datum/event2/event/wallrot
|
||||
var/turf/simulated/wall/origin = null
|
||||
|
||||
/datum/event2/event/wallrot/set_up()
|
||||
for(var/i = 1 to 100)
|
||||
var/turf/candidate = locate(rand(1, world.maxx), rand(1, world.maxy), pick(get_location_z_levels()) )
|
||||
if(istype(candidate, /turf/simulated/wall))
|
||||
origin = candidate
|
||||
log_debug("Wall-rot event has chosen \the [origin] ([origin.loc]) as the origin for the wallrot infestation.")
|
||||
return
|
||||
|
||||
log_debug("Wall-rot event failed to find a valid wall after one hundred tries. Aborting.")
|
||||
abort()
|
||||
|
||||
/datum/event2/event/wallrot/announce()
|
||||
if(origin && prob(80))
|
||||
command_announcement.Announce("Harmful fungi detected on \the [location_name()], near \the [origin.loc]. \
|
||||
Station structural integrity may be compromised.", "Biohazard Alert")
|
||||
|
||||
/datum/event2/event/wallrot/start()
|
||||
if(origin)
|
||||
origin.rot()
|
||||
|
||||
var/rot_count = 0
|
||||
var/target_rot = rand(5, 20)
|
||||
for(var/turf/simulated/wall/W in range(7, origin))
|
||||
if(prob(50))
|
||||
if(W.rot())
|
||||
rot_count++
|
||||
if(rot_count >= target_rot)
|
||||
break
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
// This event causes a random window near space to become damaged.
|
||||
// If that window is not fixed in a certain amount of time,
|
||||
// that window and nearby windows will shatter, causing a breach.
|
||||
|
||||
/datum/event2/meta/window_break
|
||||
name = "window break"
|
||||
departments = list(DEPARTMENT_ENGINEERING)
|
||||
chaos = 10
|
||||
reusable = TRUE
|
||||
chaotic_threshold = EVENT_CHAOS_THRESHOLD_MEDIUM_IMPACT
|
||||
event_type = /datum/event2/event/window_break
|
||||
|
||||
/datum/event2/meta/window_break/get_weight()
|
||||
return metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 20
|
||||
|
||||
|
||||
|
||||
/datum/event2/event/window_break
|
||||
announce_delay_lower_bound = 10 SECONDS
|
||||
announce_delay_upper_bound = 20 SECONDS
|
||||
length_lower_bound = 8 MINUTES
|
||||
length_upper_bound = 12 MINUTES
|
||||
var/turf/chosen_turf_with_windows = null
|
||||
var/obj/structure/window/chosen_window = null
|
||||
var/list/collateral_windows = list()
|
||||
|
||||
/datum/event2/event/window_break/set_up()
|
||||
var/list/areas = find_random_areas()
|
||||
if(!LAZYLEN(areas))
|
||||
log_debug("Window Break event could not find any areas. Aborting.")
|
||||
abort()
|
||||
return
|
||||
|
||||
while(areas.len)
|
||||
var/area/area = pick(areas)
|
||||
areas -= area
|
||||
|
||||
for(var/obj/structure/window/W in area.contents)
|
||||
if(!is_window_to_space(W))
|
||||
continue
|
||||
chosen_turf_with_windows = get_turf(W)
|
||||
collateral_windows = gather_collateral_windows(W)
|
||||
break // Break out of the inner loop.
|
||||
|
||||
if(chosen_turf_with_windows)
|
||||
log_debug("Window Break event has chosen turf '[chosen_turf_with_windows.name]' in [chosen_turf_with_windows.loc].")
|
||||
break // Then the outer loop.
|
||||
|
||||
if(!chosen_turf_with_windows)
|
||||
log_debug("Window Break event could not find a turf with valid windows to break. Aborting.")
|
||||
abort()
|
||||
return
|
||||
|
||||
/datum/event2/event/window_break/announce()
|
||||
if(chosen_window)
|
||||
command_announcement.Announce("Structural integrity of space-facing windows at \the [get_area(chosen_turf_with_windows)] are failing. \
|
||||
Repair of the damaged window is advised. Personnel without EVA suits in the area should leave until repairs are complete.", "Structural Alert")
|
||||
|
||||
/datum/event2/event/window_break/start()
|
||||
if(!chosen_turf_with_windows)
|
||||
return
|
||||
|
||||
for(var/obj/structure/window/W in chosen_turf_with_windows.contents)
|
||||
if(W.is_fulltile()) // Full tile windows are simple and can always be used.
|
||||
chosen_window = W
|
||||
break
|
||||
else // Otherwise we only want the window that is on the inside side of the station.
|
||||
var/turf/T = get_step(W, W.dir)
|
||||
if(T.is_space())
|
||||
continue
|
||||
if(T.check_density())
|
||||
continue
|
||||
chosen_window = W
|
||||
break
|
||||
|
||||
if(!chosen_window)
|
||||
return
|
||||
|
||||
chosen_window.take_damage(chosen_window.maxhealth * 0.8)
|
||||
playsound(chosen_window, 'sound/effects/Glasshit.ogg', 100, 1)
|
||||
chosen_window.visible_message(span("danger", "\The [chosen_window] suddenly begins to crack!"))
|
||||
|
||||
/datum/event2/event/window_break/should_end()
|
||||
. = ..()
|
||||
if(!.) // If the timer didn't expire, we can still end it early if someone messes up.
|
||||
if(!chosen_window || !chosen_window.anchored || chosen_window.health == chosen_window.maxhealth)
|
||||
// If the window got deconstructed/moved/etc, immediately end and make the breach happen.
|
||||
// Also end early if it was repaired.
|
||||
return TRUE
|
||||
|
||||
/datum/event2/event/window_break/end()
|
||||
// If someone fixed the window, then everything is fine.
|
||||
if(chosen_window && chosen_window.anchored && chosen_window.health == chosen_window.maxhealth)
|
||||
log_debug("Window Break event ended with window repaired.")
|
||||
return
|
||||
|
||||
// Otherwise a bunch of windows shatter.
|
||||
chosen_window?.shatter()
|
||||
|
||||
var/windows_to_shatter = min(rand(4, 10), collateral_windows.len)
|
||||
for(var/i = 1 to windows_to_shatter)
|
||||
var/obj/structure/window/W = collateral_windows[i]
|
||||
W?.shatter()
|
||||
|
||||
log_debug("Window Break event ended with [windows_to_shatter] shattered windows and a breach.")
|
||||
|
||||
// Checks if a window is adjacent to a space tile, and also that the opposite direction is open.
|
||||
// This is done to avoid getting caught in corner parts of windows.
|
||||
/datum/event2/event/window_break/proc/is_window_to_space(obj/structure/window/W)
|
||||
for(var/direction in GLOB.cardinal)
|
||||
var/turf/T = get_step(W, direction)
|
||||
if(T.is_space())
|
||||
var/turf/opposite_T = get_step(W, GLOB.reverse_dir[direction])
|
||||
if(!opposite_T.check_density())
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
//TL;DR: breadth first search for all connected turfs with windows
|
||||
/datum/event2/event/window_break/proc/gather_collateral_windows(var/obj/structure/window/target_window)
|
||||
var/list/turf/frontier_set = list(target_window.loc)
|
||||
var/list/obj/structure/window/result_set = list()
|
||||
var/list/turf/explored_set = list()
|
||||
|
||||
while(frontier_set.len > 0)
|
||||
var/turf/current = frontier_set[1]
|
||||
frontier_set -= current
|
||||
explored_set += current
|
||||
|
||||
var/contains_windows = 0
|
||||
for(var/obj/structure/window/to_add in current.contents)
|
||||
contains_windows = 1
|
||||
result_set += to_add
|
||||
|
||||
if(contains_windows)
|
||||
//add adjacent turfs to be checked for windows as well
|
||||
var/turf/neighbor = locate(current.x + 1, current.y, current.z)
|
||||
if(!(neighbor in frontier_set) && !(neighbor in explored_set))
|
||||
frontier_set += neighbor
|
||||
neighbor = locate(current.x - 1, current.y, current.z)
|
||||
if(!(neighbor in frontier_set) && !(neighbor in explored_set))
|
||||
frontier_set += neighbor
|
||||
neighbor = locate(current.x, current.y + 1, current.z)
|
||||
if(!(neighbor in frontier_set) && !(neighbor in explored_set))
|
||||
frontier_set += neighbor
|
||||
neighbor = locate(current.x, current.y - 1, current.z)
|
||||
if(!(neighbor in frontier_set) && !(neighbor in explored_set))
|
||||
frontier_set += neighbor
|
||||
return result_set
|
||||
@@ -12,13 +12,18 @@
|
||||
|
||||
|
||||
/datum/event2/event/comms_blackout/announce()
|
||||
var/alert = pick("Ionospheric anomalies detected. Temporary telecommunication failure imminent. Please contact you*%fj00)`5vc-BZZT", \
|
||||
"Ionospheric anomalies detected. Temporary telecommunication failu*3mga;b4;'1v¬-BZZZT", \
|
||||
"Ionospheric anomalies detected. Temporary telec#MCi46:5.;@63-BZZZZT", \
|
||||
"Ionospheric anomalies dete'fZ\\kg5_0-BZZZZZT", \
|
||||
"Ionospheri:%£ MCayj^j<.3-BZZZZZZT", \
|
||||
"#4nd%;f4y6,>£%-BZZZZZZZT")
|
||||
if(prob(33))
|
||||
command_announcement.Announce("Ionospheric anomalies detected. \
|
||||
Temporary telecommunication failure imminent. Please contact you-BZZT", new_sound = 'sound/misc/interference.ogg')
|
||||
command_announcement.Announce(alert, new_sound = 'sound/misc/interference.ogg')
|
||||
// AIs will always know if there's a comm blackout, rogue AIs could then lie about comm blackouts in the future while they shutdown comms
|
||||
for(var/mob/living/silicon/ai/A in player_list)
|
||||
to_chat(A, "<br>")
|
||||
to_chat(A, "<span class='warning'><b>Ionospheric anomalies detected. Temporary telecommunication failure imminent. Please contact you-BZZT</b></span>")
|
||||
to_chat(A, "<span class='warning'><b>[alert]</b></span>")
|
||||
to_chat(A, "<br>")
|
||||
|
||||
/datum/event2/event/comms_blackout/start()
|
||||
@@ -32,3 +37,7 @@
|
||||
log_debug("Doing complete outage of telecomms.")
|
||||
for(var/obj/machinery/telecomms/T in telecomms_list)
|
||||
T.emp_act(1)
|
||||
|
||||
// Communicators go down no matter what.
|
||||
for(var/obj/machinery/exonet_node/N in machines)
|
||||
N.emp_act(1)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// 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.
|
||||
// Since this is an 'external' thing, the Grid Checker can't stop it.
|
||||
|
||||
/datum/event2/meta/electrical_fault
|
||||
name = "electrical fault"
|
||||
@@ -17,16 +18,20 @@
|
||||
start_delay_upper_bound = 1 MINUTE
|
||||
length_lower_bound = 20 SECONDS
|
||||
length_upper_bound = 40 SECONDS
|
||||
var/max_apcs_per_tick = 2
|
||||
var/max_apcs_per_tick = 6
|
||||
|
||||
var/list/valid_apcs = null
|
||||
var/list/valid_z_levels = null
|
||||
|
||||
var/apcs_disabled = 0
|
||||
var/apcs_overloaded = 0
|
||||
var/apcs_emagged = 0
|
||||
|
||||
/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")
|
||||
command_announcement.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 the situation passes.", "[location_name()] Sensor Array")
|
||||
|
||||
/datum/event2/event/electrical_fault/set_up()
|
||||
valid_z_levels = get_location_z_levels()
|
||||
@@ -37,9 +42,13 @@
|
||||
if(A.z in valid_z_levels)
|
||||
valid_apcs += A
|
||||
|
||||
/datum/event2/event/electrical_fault/start()
|
||||
command_announcement.Announce("Irregularities detected in \the [location_name()] power grid.", "[location_name()] Power Grid Monitoring")
|
||||
|
||||
/datum/event2/event/electrical_fault/event_tick()
|
||||
if(!valid_apcs.len)
|
||||
log_debug("No valid APCs found for electrical fault event.")
|
||||
log_debug("ELECTRICAL EVENT: No valid APCs found for electrical fault event. Aborting.")
|
||||
abort()
|
||||
return
|
||||
|
||||
var/list/picked_apcs = list()
|
||||
@@ -50,62 +59,41 @@
|
||||
affect_apc(A)
|
||||
|
||||
/datum/event2/event/electrical_fault/end()
|
||||
command_announce.Announce("External conditions have returned to normal around \the [location_name()].")
|
||||
command_announcement.Announce("The irregular electrical conditions inside \the [location_name()] power grid has ceased.", "[location_name()] Power Grid Monitoring")
|
||||
log_debug("Electrical Fault event caused [apcs_disabled] APC\s to shut off, \
|
||||
[apcs_overloaded] APC\s to overload lighting, and [apcs_emagged] APC\s to be emagged.")
|
||||
|
||||
/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)
|
||||
// Important APCs like the AI or the engine core shouldn't get shut off by this event.
|
||||
if((!A.operating || A.failure_timer > 0) || A.is_critical)
|
||||
return
|
||||
|
||||
playsound(
|
||||
// In reality this would probably make the lights get brighter but oh well.
|
||||
for(var/obj/machinery/light/L in get_area(A))
|
||||
L.flicker(rand(10, 20))
|
||||
|
||||
// Chance to make the APC turn off for awhile.
|
||||
// This will actually protect it from further damage.
|
||||
if(prob(25))
|
||||
A.energy_fail(rand(60, 120))
|
||||
log_debug("ELECTRICAL EVENT: Disabled \the [A]'s power for a temporary amount of time.")
|
||||
playsound(A, 'sound/machines/defib_success.ogg', 50, 1)
|
||||
apcs_disabled++
|
||||
return
|
||||
|
||||
// Decent chance to overload lighting circuit.
|
||||
if(prob(6))
|
||||
if(prob(30))
|
||||
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))
|
||||
log_debug("ELECTRICAL EVENT: Overloaded \the [A]'s lighting.")
|
||||
playsound(A, 'sound/effects/lightningshock.ogg', 50, 1)
|
||||
apcs_overloaded++
|
||||
|
||||
// Relatively small chance to emag the apc as apc_damage event does.
|
||||
if(prob(1))
|
||||
if(prob(5))
|
||||
A.emagged = TRUE
|
||||
A.update_icon()
|
||||
log_debug("ELECTRICAL EVENT: Emagged \the [A].")
|
||||
playsound(A, 'sound/machines/chime.ogg', 50, 1)
|
||||
apcs_emagged++
|
||||
|
||||
/*
|
||||
/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()
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/datum/event2/meta/gravity
|
||||
name = "gravity failure"
|
||||
departments = list(DEPARTMENT_EVERYONE)
|
||||
chaos = 20
|
||||
chaotic_threshold = EVENT_CHAOS_THRESHOLD_LOW_IMPACT
|
||||
reusable = TRUE
|
||||
event_type = /datum/event2/event/gravity
|
||||
|
||||
/datum/event2/meta/gravity/get_weight()
|
||||
return (20 + (metric.count_people_in_department(DEPARTMENT_EVERYONE) * 20)) / (times_ran + 1)
|
||||
|
||||
|
||||
|
||||
|
||||
/datum/event2/event/gravity
|
||||
length_lower_bound = 5 MINUTES
|
||||
length_upper_bound = 10 MINUTES
|
||||
|
||||
/datum/event2/event/gravity/announce()
|
||||
command_announcement.Announce("Feedback surge detected in mass-distributions systems. \
|
||||
Artificial gravity has been disabled whilst the system reinitializes. \
|
||||
Please stand by while the gravity system reinitializes.", "Gravity Failure")
|
||||
|
||||
/datum/event2/event/gravity/start()
|
||||
for(var/area/A in all_areas)
|
||||
if(A.z in get_location_z_levels())
|
||||
A.gravitychange(FALSE, A)
|
||||
|
||||
/datum/event2/event/gravity/end()
|
||||
for(var/area/A in all_areas)
|
||||
if(A.z in get_location_z_levels())
|
||||
A.gravitychange(TRUE, A)
|
||||
|
||||
command_announcement.Announce("Gravity generators are again functioning within normal parameters. Sorry for any inconvenience.", "Gravity Restored")
|
||||
@@ -0,0 +1,72 @@
|
||||
/datum/event2/meta/infestation
|
||||
event_class = "infestation"
|
||||
departments = list(DEPARTMENT_EVERYONE)
|
||||
|
||||
/datum/event2/meta/infestation/get_weight()
|
||||
return metric.count_people_in_department(DEPARTMENT_EVERYONE) * 10
|
||||
|
||||
/datum/event2/meta/infestation/rodents
|
||||
name = "infestation - rodents"
|
||||
event_type = /datum/event2/event/infestation/rodents
|
||||
|
||||
/datum/event2/meta/infestation/lizards
|
||||
name = "infestation - lizards"
|
||||
event_type = /datum/event2/event/infestation/lizards
|
||||
|
||||
/datum/event2/meta/infestation/spiderlings
|
||||
name = "infestation - spiders"
|
||||
event_type = /datum/event2/event/infestation/spiderlings
|
||||
|
||||
|
||||
/datum/event2/event/infestation
|
||||
var/vermin_string = null
|
||||
var/max_vermin = 0
|
||||
var/list/things_to_spawn = list()
|
||||
|
||||
var/list/turfs = list()
|
||||
|
||||
/datum/event2/event/infestation/rodents
|
||||
vermin_string = "rodents"
|
||||
max_vermin = 12
|
||||
things_to_spawn = list(
|
||||
/mob/living/simple_mob/animal/passive/mouse/gray,
|
||||
/mob/living/simple_mob/animal/passive/mouse/brown,
|
||||
/mob/living/simple_mob/animal/passive/mouse/white,
|
||||
/mob/living/simple_mob/animal/passive/mouse/rat
|
||||
)
|
||||
|
||||
/datum/event2/event/infestation/lizards
|
||||
vermin_string = "lizards"
|
||||
max_vermin = 6
|
||||
things_to_spawn = list(
|
||||
/mob/living/simple_mob/animal/passive/lizard,
|
||||
/mob/living/simple_mob/animal/passive/lizard/large,
|
||||
/mob/living/simple_mob/animal/passive/lizard/large/defensive
|
||||
)
|
||||
|
||||
/datum/event2/event/infestation/spiderlings
|
||||
vermin_string = "spiders"
|
||||
max_vermin = 3
|
||||
things_to_spawn = list(/obj/effect/spider/spiderling/non_growing)
|
||||
|
||||
|
||||
/datum/event2/event/infestation/set_up()
|
||||
turfs = find_random_turfs(max_vermin)
|
||||
if(!turfs.len)
|
||||
log_debug("Infestation event failed to find any valid turfs. Aborting.")
|
||||
abort()
|
||||
return
|
||||
|
||||
/datum/event2/event/infestation/announce()
|
||||
var/turf/T = turfs[1]
|
||||
command_announcement.Announce("Bioscans indicate that [vermin_string] have been breeding \
|
||||
in \the [T.loc]. Clear them out, before this starts to affect productivity.", "Vermin infestation")
|
||||
|
||||
|
||||
/datum/event2/event/infestation/start()
|
||||
var/vermin_to_spawn = rand(2, max_vermin)
|
||||
for(var/i = 1 to vermin_to_spawn)
|
||||
var/turf/T = pick(turfs)
|
||||
turfs -= T
|
||||
var/spawn_type = pick(things_to_spawn)
|
||||
new spawn_type(T)
|
||||
@@ -0,0 +1,268 @@
|
||||
/datum/event2/meta/pda_spam
|
||||
name = "pda spam"
|
||||
departments = list(DEPARTMENT_EVERYONE)
|
||||
event_type = /datum/event2/event/pda_spam
|
||||
|
||||
/datum/event2/meta/pda_spam/get_weight()
|
||||
return metric.count_people_in_department(DEPARTMENT_EVERYONE) * 2
|
||||
|
||||
|
||||
/datum/event2/event/pda_spam
|
||||
length_lower_bound = 30 MINUTES
|
||||
length_upper_bound = 1 HOUR
|
||||
var/spam_debug = FALSE // If true, notices of the event sending spam go to `log_debug()`.
|
||||
var/last_spam_time = null // world.time of most recent spam.
|
||||
var/next_spam_attempt_time = 0 // world.time of next attempt to try to spam.
|
||||
var/give_up_after = 5 MINUTES
|
||||
var/obj/machinery/message_server/MS = null
|
||||
var/obj/machinery/exonet_node/node = null
|
||||
|
||||
/datum/event2/event/pda_spam/set_up()
|
||||
last_spam_time = world.time // So it won't immediately give up.
|
||||
MS = pick_message_server()
|
||||
node = get_exonet_node()
|
||||
|
||||
/datum/event2/event/pda_spam/event_tick()
|
||||
if(!can_spam())
|
||||
return
|
||||
|
||||
if(world.time < next_spam_attempt_time)
|
||||
return
|
||||
|
||||
next_spam_attempt_time = world.time + rand(30 SECONDS, 2 MINUTES)
|
||||
|
||||
var/obj/item/device/pda/P = null
|
||||
var/list/viables = list()
|
||||
|
||||
for(var/obj/item/device/pda/check_pda in sortAtom(PDAs))
|
||||
if(!check_pda.owner || check_pda.toff || check_pda.hidden || check_pda.spam_proof)
|
||||
continue
|
||||
viables += check_pda
|
||||
|
||||
if(!viables.len)
|
||||
return
|
||||
|
||||
P = pick(viables)
|
||||
var/list/spam = generate_spam()
|
||||
|
||||
if(MS.send_pda_message("[P.owner]", spam[1], spam[2])) // Message been filtered by spam filter.
|
||||
return
|
||||
|
||||
send_spam(P, spam[1], spam[2])
|
||||
|
||||
|
||||
/datum/event2/event/pda_spam/should_end()
|
||||
. = ..()
|
||||
if(!.)
|
||||
// Give up if nobody was reachable for five minutes.
|
||||
if(last_spam_time + give_up_after < world.time)
|
||||
log_debug("PDA Spam event giving up after not being able to spam for awhile.")
|
||||
return TRUE
|
||||
|
||||
/datum/event2/event/pda_spam/proc/can_spam()
|
||||
if(!node || !node.on || !node.allow_external_PDAs)
|
||||
node = get_exonet_node()
|
||||
return FALSE
|
||||
|
||||
if(!MS || !MS.active)
|
||||
MS = pick_message_server()
|
||||
return FALSE
|
||||
|
||||
return TRUE
|
||||
|
||||
// Returns a list containing two items, the sender and message.
|
||||
/datum/event2/event/pda_spam/proc/generate_spam()
|
||||
var/sender = null
|
||||
var/message = null
|
||||
switch(rand(1, 7))
|
||||
if(1)
|
||||
sender = pick("MaxBet","MaxBet Online Casino","There is no better time to register","I'm excited for you to join us")
|
||||
message = pick("Triple deposits are waiting for you at MaxBet Online when you register to play with us.",\
|
||||
"You can qualify for a 200% Welcome Bonus at MaxBet Online when you sign up today.",\
|
||||
"Once you are a player with MaxBet, you will also receive lucrative weekly and monthly promotions.",\
|
||||
"You will be able to enjoy over 450 top-flight casino games at MaxBet.")
|
||||
if(2)
|
||||
sender = pick(300;"QuickDatingSystem",200;"Find your russian bride",50;"Tajaran beauties are waiting",50;"Find your secret skrell crush",50;"Beautiful unathi brides")
|
||||
message = pick("Your profile caught my attention and I wanted to write and say hello (QuickDating).",\
|
||||
"If you will write to me on my email [pick(first_names_female)]@[pick(last_names)].[pick("ru","ck","tj","ur","nt")] I shall necessarily send you a photo (QuickDating).",\
|
||||
"I want that we write each other and I hope, that you will like my profile and you will answer me (QuickDating).",\
|
||||
"You have (1) new message!",\
|
||||
"You have (2) new profile views!")
|
||||
if(3)
|
||||
sender = pick("Galactic Payments Association","Better Business Bureau","[using_map.starsys_name] E-Payments","NAnoTransen Finance Deparmtent","Luxury Replicas")
|
||||
message = pick("Luxury watches for Blowout sale prices!",\
|
||||
"Watches, Jewelry & Accessories, Bags & Wallets !",\
|
||||
"Deposit 100$ and get 300$ totally free!",\
|
||||
" 100K NT.|WOWGOLD õnly $89 <HOT>",\
|
||||
"We have been filed with a complaint from one of your customers in respect of their business relations with you.",\
|
||||
"We kindly ask you to open the COMPLAINT REPORT (attached) to reply on this complaint..")
|
||||
if(4)
|
||||
sender = pick("Buy Dr. Maxman","Having dysfuctional troubles?")
|
||||
message = pick("DR MAXMAN: REAL Doctors, REAL Science, REAL Results!",\
|
||||
"Dr. Maxman was created by George Acuilar, M.D, a [using_map.boss_short] Certified Urologist who has treated over 70,000 patients sector wide with 'male problems'.",\
|
||||
"After seven years of research, Dr Acuilar and his team came up with this simple breakthrough male enhancement formula.",\
|
||||
"Men of all species report AMAZING increases in length, width and stamina.")
|
||||
if(5)
|
||||
sender = pick("Dr","Crown prince","King Regent","Professor","Captain")
|
||||
sender += " " + pick("Robert","Alfred","Josephat","Kingsley","Sehi","Zbahi")
|
||||
sender += " " + pick("Mugawe","Nkem","Gbatokwia","Nchekwube","Ndim","Ndubisi")
|
||||
message = pick("YOUR FUND HAS BEEN MOVED TO [uppertext(pick("Salusa","Segunda","Cepheus","Andromeda","Gruis","Corona","Aquila","ARES","Asellus"))] DEVELOPMENTARY BANK FOR ONWARD REMITTANCE.",\
|
||||
"We are happy to inform you that due to the delay, we have been instructed to IMMEDIATELY deposit all funds into your account",\
|
||||
"Dear fund beneficiary, We have please to inform you that overdue funds payment has finally been approved and released for payment",\
|
||||
"Due to my lack of agents I require an off-world financial account to immediately deposit the sum of 1 POINT FIVE MILLION credits.",\
|
||||
"Greetings sir, I regretfully to inform you that as I lay dying here due to my lack ofheirs I have chosen you to recieve the full sum of my lifetime savings of 1.5 billion credits")
|
||||
if(6)
|
||||
sender = pick("[using_map.company_name] Morale Divison","Feeling Lonely?","Bored?","www.wetskrell.nt")
|
||||
message = pick("The [using_map.company_name] Morale Division wishes to provide you with quality entertainment sites.",\
|
||||
"WetSkrell.nt is a xenophillic website endorsed by NT for the use of male crewmembers among it's many stations and outposts.",\
|
||||
"Wetskrell.nt only provides the higest quality of male entertaiment to [using_map.company_name] Employees.",\
|
||||
"Simply enter your [using_map.company_name] Bank account system number and pin. With three easy steps this service could be yours!")
|
||||
if(7)
|
||||
sender = pick("You have won free tickets!","Click here to claim your prize!","You are the 1000th vistor!","You are our lucky grand prize winner!")
|
||||
message = pick("You have won tickets to the newest ACTION JAXSON MOVIE!",\
|
||||
"You have won tickets to the newest crime drama DETECTIVE MYSTERY IN THE CLAMITY CAPER!",\
|
||||
"You have won tickets to the newest romantic comedy 16 RULES OF LOVE!",\
|
||||
"You have won tickets to the newest thriller THE CULT OF THE SLEEPING ONE!")
|
||||
return list(sender, message)
|
||||
|
||||
/datum/event2/event/pda_spam/proc/send_spam(obj/item/device/pda/P, sender, message)
|
||||
last_spam_time = world.time
|
||||
P.spam_message(sender, message)
|
||||
if(spam_debug)
|
||||
log_debug("PDA Spam event sent spam to \the [P].")
|
||||
|
||||
|
||||
/datum/event2/event/pda_spam/proc/pick_message_server()
|
||||
if(LAZYLEN(message_servers))
|
||||
return pick(message_servers)
|
||||
|
||||
|
||||
/*
|
||||
/datum/event/pda_spam
|
||||
endWhen = 36000
|
||||
var/last_spam_time = 0
|
||||
var/obj/machinery/message_server/useMS
|
||||
var/obj/machinery/exonet_node/node
|
||||
|
||||
/datum/event/pda_spam/setup()
|
||||
last_spam_time = world.time
|
||||
pick_message_server()
|
||||
|
||||
/datum/event/pda_spam/proc/pick_message_server()
|
||||
if(message_servers)
|
||||
for (var/obj/machinery/message_server/MS in message_servers)
|
||||
if(MS.active)
|
||||
useMS = MS
|
||||
break
|
||||
|
||||
/datum/event/pda_spam/tick()
|
||||
if(world.time > last_spam_time + 3000)
|
||||
//if there's no spam managed to get to receiver for five minutes, give up
|
||||
kill()
|
||||
return
|
||||
if(!node)
|
||||
node = get_exonet_node()
|
||||
|
||||
if(!node || !node.on || !node.allow_external_PDAs)
|
||||
return
|
||||
|
||||
if(!useMS || !useMS.active)
|
||||
useMS = null
|
||||
pick_message_server()
|
||||
|
||||
if(useMS)
|
||||
if(prob(5))
|
||||
// /obj/machinery/message_server/proc/send_pda_message(var/recipient = "",var/sender = "",var/message = "")
|
||||
var/obj/item/device/pda/P
|
||||
var/list/viables = list()
|
||||
for(var/obj/item/device/pda/check_pda in sortAtom(PDAs))
|
||||
if (!check_pda.owner||check_pda.toff||check_pda == src||check_pda.hidden)
|
||||
continue
|
||||
viables.Add(check_pda)
|
||||
|
||||
if(!viables.len)
|
||||
return
|
||||
P = pick(viables)
|
||||
|
||||
var/sender
|
||||
var/message
|
||||
switch(pick(1,2,3,4,5,6,7))
|
||||
if(1)
|
||||
sender = pick("MaxBet","MaxBet Online Casino","There is no better time to register","I'm excited for you to join us")
|
||||
message = pick("Triple deposits are waiting for you at MaxBet Online when you register to play with us.",\
|
||||
"You can qualify for a 200% Welcome Bonus at MaxBet Online when you sign up today.",\
|
||||
"Once you are a player with MaxBet, you will also receive lucrative weekly and monthly promotions.",\
|
||||
"You will be able to enjoy over 450 top-flight casino games at MaxBet.")
|
||||
if(2)
|
||||
sender = pick(300;"QuickDatingSystem",200;"Find your russian bride",50;"Tajaran beauties are waiting",50;"Find your secret skrell crush",50;"Beautiful unathi brides")
|
||||
message = pick("Your profile caught my attention and I wanted to write and say hello (QuickDating).",\
|
||||
"If you will write to me on my email [pick(first_names_female)]@[pick(last_names)].[pick("ru","ck","tj","ur","nt")] I shall necessarily send you a photo (QuickDating).",\
|
||||
"I want that we write each other and I hope, that you will like my profile and you will answer me (QuickDating).",\
|
||||
"You have (1) new message!",\
|
||||
"You have (2) new profile views!")
|
||||
if(3)
|
||||
sender = pick("Galactic Payments Association","Better Business Bureau","[using_map.starsys_name] E-Payments","NAnoTransen Finance Deparmtent","Luxury Replicas")
|
||||
message = pick("Luxury watches for Blowout sale prices!",\
|
||||
"Watches, Jewelry & Accessories, Bags & Wallets !",\
|
||||
"Deposit 100$ and get 300$ totally free!",\
|
||||
" 100K NT.|WOWGOLD õnly $89 <HOT>",\
|
||||
"We have been filed with a complaint from one of your customers in respect of their business relations with you.",\
|
||||
"We kindly ask you to open the COMPLAINT REPORT (attached) to reply on this complaint..")
|
||||
if(4)
|
||||
sender = pick("Buy Dr. Maxman","Having dysfuctional troubles?")
|
||||
message = pick("DR MAXMAN: REAL Doctors, REAL Science, REAL Results!",\
|
||||
"Dr. Maxman was created by George Acuilar, M.D, a [using_map.boss_short] Certified Urologist who has treated over 70,000 patients sector wide with 'male problems'.",\
|
||||
"After seven years of research, Dr Acuilar and his team came up with this simple breakthrough male enhancement formula.",\
|
||||
"Men of all species report AMAZING increases in length, width and stamina.")
|
||||
if(5)
|
||||
sender = pick("Dr","Crown prince","King Regent","Professor","Captain")
|
||||
sender += " " + pick("Robert","Alfred","Josephat","Kingsley","Sehi","Zbahi")
|
||||
sender += " " + pick("Mugawe","Nkem","Gbatokwia","Nchekwube","Ndim","Ndubisi")
|
||||
message = pick("YOUR FUND HAS BEEN MOVED TO [pick("Salusa","Segunda","Cepheus","Andromeda","Gruis","Corona","Aquila","ARES","Asellus")] DEVELOPMENTARY BANK FOR ONWARD REMITTANCE.",\
|
||||
"We are happy to inform you that due to the delay, we have been instructed to IMMEDIATELY deposit all funds into your account",\
|
||||
"Dear fund beneficiary, We have please to inform you that overdue funds payment has finally been approved and released for payment",\
|
||||
"Due to my lack of agents I require an off-world financial account to immediately deposit the sum of 1 POINT FIVE MILLION credits.",\
|
||||
"Greetings sir, I regretfully to inform you that as I lay dying here due to my lack ofheirs I have chosen you to recieve the full sum of my lifetime savings of 1.5 billion credits")
|
||||
if(6)
|
||||
sender = pick("[using_map.company_name] Morale Divison","Feeling Lonely?","Bored?","www.wetskrell.nt")
|
||||
message = pick("The [using_map.company_name] Morale Division wishes to provide you with quality entertainment sites.",\
|
||||
"WetSkrell.nt is a xenophillic website endorsed by NT for the use of male crewmembers among it's many stations and outposts.",\
|
||||
"Wetskrell.nt only provides the higest quality of male entertaiment to [using_map.company_name] Employees.",\
|
||||
"Simply enter your [using_map.company_name] Bank account system number and pin. With three easy steps this service could be yours!")
|
||||
if(7)
|
||||
sender = pick("You have won free tickets!","Click here to claim your prize!","You are the 1000th vistor!","You are our lucky grand prize winner!")
|
||||
message = pick("You have won tickets to the newest ACTION JAXSON MOVIE!",\
|
||||
"You have won tickets to the newest crime drama DETECTIVE MYSTERY IN THE CLAMITY CAPER!",\
|
||||
"You have won tickets to the newest romantic comedy 16 RULES OF LOVE!",\
|
||||
"You have won tickets to the newest thriller THE CULT OF THE SLEEPING ONE!")
|
||||
|
||||
if (useMS.send_pda_message("[P.owner]", sender, message)) //Message been filtered by spam filter.
|
||||
return
|
||||
|
||||
last_spam_time = world.time
|
||||
|
||||
if (prob(50)) //Give the AI an increased chance to intercept the message
|
||||
for(var/mob/living/silicon/ai/ai in mob_list)
|
||||
// Allows other AIs to intercept the message but the AI won't intercept their own message.
|
||||
if(ai.aiPDA != P && ai.aiPDA != src)
|
||||
ai.show_message("<i>Intercepted message from <b>[sender]</b></i> (Unknown / spam?) <i>to <b>[P:owner]</b>: [message]</i>")
|
||||
|
||||
//Commented out because we don't send messages like this anymore. Instead it will just popup in their chat window.
|
||||
//P.tnote += "<i><b>← From [sender] (Unknown / spam?):</b></i><br>[message]<br>"
|
||||
|
||||
if (!P.message_silent)
|
||||
playsound(P.loc, 'sound/machines/twobeep.ogg', 50, 1)
|
||||
for (var/mob/O in hearers(3, P.loc))
|
||||
if(!P.message_silent) O.show_message(text("\icon[P] *[P.ttone]*"))
|
||||
//Search for holder of the PDA.
|
||||
var/mob/living/L = null
|
||||
if(P.loc && isliving(P.loc))
|
||||
L = P.loc
|
||||
//Maybe they are a pAI!
|
||||
else
|
||||
L = get(P, /mob/living/silicon)
|
||||
|
||||
if(L)
|
||||
to_chat(L, "\icon[P] <b>Message from [sender] (Unknown / spam?), </b>\"[message]\" (Unable to Reply)")
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,49 @@
|
||||
/datum/event2/meta/radiation_storm
|
||||
name = "radiation storm"
|
||||
departments = list(DEPARTMENT_EVERYONE)
|
||||
chaos = 20
|
||||
chaotic_threshold = EVENT_CHAOS_THRESHOLD_MEDIUM_IMPACT
|
||||
event_type = /datum/event2/event/radiation_storm
|
||||
|
||||
/datum/event2/meta/radiation_storm/get_weight()
|
||||
var/medical_factor = metric.count_people_in_department(DEPARTMENT_MEDICAL) * 10
|
||||
var/population_factor = metric.count_people_in_department(DEPARTMENT_EVERYONE) * 5 // Note medical people will get counted twice at 25 weight.
|
||||
return 20 + medical_factor + population_factor
|
||||
|
||||
|
||||
|
||||
/datum/event2/event/radiation_storm
|
||||
start_delay_lower_bound = 1 MINUTE
|
||||
length_lower_bound = 1 MINUTE
|
||||
|
||||
/datum/event2/event/radiation_storm/announce()
|
||||
command_announcement.Announce("High levels of radiation detected near \the [location_name()]. \
|
||||
Please evacuate into one of the shielded maintenance tunnels.", "Anomaly Alert", new_sound = 'sound/AI/radiation.ogg')
|
||||
make_maint_all_access()
|
||||
|
||||
/datum/event2/event/radiation_storm/start()
|
||||
command_announcement.Announce("The station has entered the radiation belt. \
|
||||
Please remain in a sheltered area until we have passed the radiation belt.", "Anomaly Alert")
|
||||
|
||||
/datum/event2/event/radiation_storm/event_tick()
|
||||
radiate()
|
||||
|
||||
/datum/event2/event/radiation_storm/proc/radiate()
|
||||
var/radiation_level = rand(15, 35)
|
||||
for(var/z in using_map.station_levels)
|
||||
SSradiation.z_radiate(locate(1, 1, z), radiation_level, 1)
|
||||
|
||||
/datum/event2/event/radiation_storm/end()
|
||||
command_announcement.Announce("The station has passed the radiation belt. \
|
||||
Please allow for up to one minute while radiation levels dissipate, and report to \
|
||||
medbay if you experience any unusual symptoms. Maintenance will lose all \
|
||||
access again shortly.", "Anomaly Alert")
|
||||
addtimer(CALLBACK(src, .proc/maint_callback), 2 MINUTES)
|
||||
|
||||
/datum/event2/event/radiation_storm/proc/maint_callback()
|
||||
revoke_maint_all_access()
|
||||
|
||||
|
||||
// There is no actual radiation during a fake storm.
|
||||
/datum/event2/event/radiation_storm/fake/radiate()
|
||||
return
|
||||
@@ -0,0 +1,31 @@
|
||||
// No idea if this is needed for autotraitor or not.
|
||||
// If it is, it shouldn't depend on the event system, but fixing that would be it's own project.
|
||||
// If not, it can stay off until an admin wants to play with it.
|
||||
|
||||
/datum/event2/meta/random_antagonist
|
||||
name = "random antagonist"
|
||||
enabled = FALSE
|
||||
reusable = TRUE
|
||||
chaos = 0 // This is zero due to the event system not being able to know if an antag actually got spawned or not.
|
||||
departments = list(DEPARTMENT_EVERYONE)
|
||||
chaotic_threshold = EVENT_CHAOS_THRESHOLD_MEDIUM_IMPACT
|
||||
event_type = /datum/event2/event/random_antagonist
|
||||
|
||||
// This has an abnormally high weight due to antags being very important for the round,
|
||||
// however the weight will decay with more antags, and more attempts to add antags.
|
||||
/datum/event2/meta/random_antagonist/get_weight()
|
||||
var/antags = metric.count_all_antags()
|
||||
return 200 / (antags + times_ran + 1)
|
||||
|
||||
|
||||
|
||||
// The random spawn proc on the antag datum will handle announcing the spawn and whatnot, in theory.
|
||||
/datum/event2/event/random_antagonist/start()
|
||||
var/list/valid_types = list()
|
||||
for(var/antag_type in all_antag_types)
|
||||
var/datum/antagonist/antag = all_antag_types[antag_type]
|
||||
if(antag.flags & ANTAG_RANDSPAWN)
|
||||
valid_types |= antag
|
||||
if(valid_types.len)
|
||||
var/datum/antagonist/antag = pick(valid_types)
|
||||
antag.attempt_random_spawn()
|
||||
@@ -0,0 +1,82 @@
|
||||
/datum/event2/meta/sudden_weather_shift
|
||||
name = "sudden weather shift"
|
||||
departments = list(DEPARTMENT_EVERYONE)
|
||||
reusable = TRUE
|
||||
event_type = /datum/event2/event/sudden_weather_shift
|
||||
|
||||
/datum/event2/meta/sudden_weather_shift/get_weight()
|
||||
// The proc name is a bit misleading, it only counts players outside, not all mobs.
|
||||
return (metric.count_all_outdoor_mobs() * 20) / (times_ran + 1)
|
||||
|
||||
/datum/event2/event/sudden_weather_shift
|
||||
start_delay_lower_bound = 30 SECONDS
|
||||
start_delay_upper_bound = 1 MINUTE
|
||||
var/datum/planet/chosen_planet = null
|
||||
|
||||
/datum/event2/event/sudden_weather_shift/set_up()
|
||||
if(!LAZYLEN(SSplanets.planets))
|
||||
log_debug("Weather shift event was ran when no planets exist. Aborting.")
|
||||
abort()
|
||||
return
|
||||
|
||||
chosen_planet = pick(SSplanets.planets)
|
||||
|
||||
/datum/event2/event/sudden_weather_shift/announce()
|
||||
if(!chosen_planet)
|
||||
return
|
||||
command_announcement.Announce("Local weather patterns on [chosen_planet.name] suggest that a \
|
||||
sudden atmospheric fluctuation has occurred. All groundside personnel should be wary of \
|
||||
rapidly deteriorating conditions.", "Weather Alert")
|
||||
|
||||
/datum/event2/event/sudden_weather_shift/start()
|
||||
// Using the roundstart weather list is handy, because it avoids the chance of choosing a bus-only weather.
|
||||
// It also makes this event generic and suitable for other planets besides the main one, with no additional code needed.
|
||||
// Only flaw is that roundstart weathers are -usually- safe ones, but we can fix that by tweaking a copy of it.
|
||||
var/list/weather_choices = chosen_planet.weather_holder.roundstart_weather_chances.Copy()
|
||||
var/list/new_weather_weights = list()
|
||||
|
||||
// A lazy way of inverting the odds is to use some division.
|
||||
for(var/weather in weather_choices)
|
||||
new_weather_weights[weather] = 100 / weather_choices[weather]
|
||||
|
||||
// Now choose a new weather.
|
||||
var/new_weather = pickweight(new_weather_weights)
|
||||
log_debug("Sudden weather shift event is now changing [chosen_planet.name]'s weather to [new_weather].")
|
||||
chosen_planet.weather_holder.change_weather(new_weather)
|
||||
|
||||
/*
|
||||
/datum/gm_action/planet_weather_shift
|
||||
name = "sudden weather shift"
|
||||
enabled = TRUE
|
||||
departments = list(DEPARTMENT_EVERYONE)
|
||||
reusable = TRUE
|
||||
var/datum/planet/target_planet
|
||||
|
||||
var/list/banned_weathers = list(
|
||||
/datum/weather/sif/ash_storm,
|
||||
/datum/weather/sif/emberfall,
|
||||
/datum/weather/sif/blood_moon,
|
||||
/datum/weather/sif/fallout)
|
||||
var/list/possible_weathers = list()
|
||||
|
||||
/datum/gm_action/planet_weather_shift/set_up()
|
||||
if(!target_planet || isnull(target_planet))
|
||||
target_planet = pick(SSplanets.planets)
|
||||
possible_weathers |= target_planet.weather_holder.allowed_weather_types
|
||||
possible_weathers -= banned_weathers
|
||||
return
|
||||
|
||||
/datum/gm_action/planet_weather_shift/get_weight()
|
||||
return max(0, -15 + (metric.count_all_outdoor_mobs() * 20))
|
||||
|
||||
/datum/gm_action/planet_weather_shift/start()
|
||||
..()
|
||||
var/new_weather = pick(possible_weathers)
|
||||
target_planet.weather_holder.change_weather(new_weather)
|
||||
|
||||
/datum/gm_action/planet_weather_shift/announce()
|
||||
spawn(rand(3 SECONDS, 2 MINUTES))
|
||||
command_announcement.Announce("Local weather patterns on [target_planet.name] suggest that a sudden atmospheric fluctuation has occurred. All groundside personnel should be wary of rapidly deteriorating conditions.", "Weather Alert")
|
||||
return
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,21 @@
|
||||
// Generic subtype for events that make ghost pods.
|
||||
|
||||
/datum/event2/event/ghost_pod_spawner
|
||||
var/pod_type = null
|
||||
var/list/desired_turf_areas = list() // If this is left empty, it will default to a global list of 'station' turfs.
|
||||
var/list/free_turfs = list()
|
||||
|
||||
/datum/event2/event/ghost_pod_spawner/set_up()
|
||||
free_turfs = find_random_turfs(5, desired_turf_areas)
|
||||
|
||||
if(!free_turfs.len)
|
||||
log_debug("Ghost Pod Spawning event failed to find a place to spawn. Aborting.")
|
||||
abort()
|
||||
return
|
||||
|
||||
/datum/event2/event/ghost_pod_spawner/start()
|
||||
var/obj/structure/ghost_pod/pod = new pod_type(pick(free_turfs))
|
||||
post_pod_creation(pod)
|
||||
|
||||
// Override to do things to the pod after it's spawned.
|
||||
/datum/event2/event/ghost_pod_spawner/proc/post_pod_creation(obj/structure/ghost_pod/pod)
|
||||
@@ -1,54 +0,0 @@
|
||||
// This is a somewhat special type of event, that bridges to the old event datum and makes it work with the new system.
|
||||
// This is possible because the new datum is mostly superset of the old one.
|
||||
/datum/event2/event/legacy
|
||||
var/datum/event/legacy_event = null
|
||||
var/tick_count = 0 // Used to emulate legacy's `activeFor` tick counter.
|
||||
|
||||
/datum/event2/meta/legacy/get_weight()
|
||||
return 50
|
||||
|
||||
/datum/event2/event/legacy/process()
|
||||
..()
|
||||
tick_count++
|
||||
|
||||
/datum/event2/event/legacy/set_up()
|
||||
legacy_event = new legacy_event(null, external_use = TRUE)
|
||||
legacy_event.setup()
|
||||
|
||||
/datum/event2/event/legacy/should_announce()
|
||||
return tick_count >= legacy_event.announceWhen
|
||||
|
||||
/datum/event2/event/legacy/announce()
|
||||
legacy_event.announce()
|
||||
|
||||
|
||||
// Legacy events don't tick before they start, so we don't need to do `wait_tick()`.
|
||||
|
||||
/datum/event2/event/legacy/should_start()
|
||||
return tick_count >= legacy_event.startWhen
|
||||
|
||||
/datum/event2/event/legacy/start()
|
||||
legacy_event.start()
|
||||
|
||||
/datum/event2/event/legacy/event_tick()
|
||||
legacy_event.tick()
|
||||
|
||||
|
||||
/datum/event2/event/legacy/should_end()
|
||||
return tick_count >= legacy_event.endWhen
|
||||
|
||||
/datum/event2/event/legacy/end()
|
||||
legacy_event.end()
|
||||
|
||||
/datum/event2/event/legacy/finish()
|
||||
legacy_event.kill(external_use = TRUE)
|
||||
..()
|
||||
|
||||
// Testing.
|
||||
/datum/event2/meta/legacy_gravity
|
||||
name = "gravity (legacy)"
|
||||
reusable = TRUE
|
||||
event_type = /datum/event2/event/legacy/gravity
|
||||
|
||||
/datum/event2/event/legacy/gravity
|
||||
legacy_event = /datum/event/gravity
|
||||
@@ -51,11 +51,13 @@
|
||||
legacy_event.kill(external_use = TRUE)
|
||||
..()
|
||||
|
||||
// Testing.
|
||||
// Proof of concept.
|
||||
/*
|
||||
/datum/event2/meta/legacy_gravity
|
||||
name = "gravity (legacy)"
|
||||
reusable = TRUE
|
||||
event_type = /datum/event2/event/legacy/gravity
|
||||
|
||||
/datum/event2/event/legacy/gravity
|
||||
legacy_event = /datum/event/gravity
|
||||
legacy_event = /datum/event/gravity
|
||||
*/
|
||||
@@ -0,0 +1,36 @@
|
||||
/datum/event2/meta/appendicitis
|
||||
name = "appendicitis"
|
||||
departments = list(DEPARTMENT_MEDICAL)
|
||||
chaos = 40
|
||||
chaotic_threshold = EVENT_CHAOS_THRESHOLD_MEDIUM_IMPACT
|
||||
event_type = /datum/event2/event/appendicitis
|
||||
|
||||
/datum/event2/meta/appendicitis/get_weight()
|
||||
var/list/doctors = metric.get_people_with_job(/datum/job/doctor)
|
||||
|
||||
doctors -= metric.get_people_with_alt_title(/datum/job/doctor, /datum/alt_title/nurse)
|
||||
doctors -= metric.get_people_with_alt_title(/datum/job/doctor, /datum/alt_title/virologist)
|
||||
doctors += metric.get_people_with_job(/datum/job/cmo)
|
||||
|
||||
return doctors.len * 10
|
||||
|
||||
|
||||
|
||||
/datum/event2/event/appendicitis/start()
|
||||
for(var/mob/living/carbon/human/H in shuffle(living_mob_list))
|
||||
// Don't do it to SSD people.
|
||||
if(!H.client)
|
||||
continue
|
||||
|
||||
// Or antags.
|
||||
if(player_is_antag(H.mind))
|
||||
continue
|
||||
|
||||
// Or doctors (otherwise it could be possible for the only surgeon to need surgery).
|
||||
if(H in metric.get_people_with_job(/datum/job/doctor) )
|
||||
continue
|
||||
|
||||
if(H.appendicitis())
|
||||
log_debug("Appendicitis event gave appendicitis to \the [H].")
|
||||
return
|
||||
log_debug("Appendicitis event could not find a valid victim.")
|
||||
@@ -0,0 +1,69 @@
|
||||
/datum/event2/meta/virus
|
||||
name = "viral infection"
|
||||
event_class = "virus"
|
||||
departments = list(DEPARTMENT_MEDICAL, DEPARTMENT_EVERYONE)
|
||||
chaos = 40
|
||||
chaotic_threshold = EVENT_CHAOS_THRESHOLD_HIGH_IMPACT
|
||||
event_type = /datum/event2/event/virus
|
||||
|
||||
/datum/event2/meta/virus/superbug
|
||||
name = "viral superbug"
|
||||
chaos = 60
|
||||
event_type = /datum/event2/event/virus/superbug
|
||||
|
||||
/datum/event2/meta/virus/outbreak
|
||||
name = "viral outbreak"
|
||||
chaos = 60
|
||||
event_type = /datum/event2/event/virus/outbreak
|
||||
|
||||
/datum/event2/meta/virus/get_weight()
|
||||
var/list/virologists = metric.get_people_with_alt_title(/datum/job/doctor, /datum/alt_title/virologist)
|
||||
virologists += metric.get_people_with_job(/datum/job/cmo)
|
||||
|
||||
return virologists.len * 25
|
||||
|
||||
|
||||
|
||||
/datum/event2/event/virus
|
||||
announce_delay_lower_bound = 1 MINUTE
|
||||
announce_delay_upper_bound = 3 MINUTES
|
||||
var/number_of_viruses = 1
|
||||
var/virus_power = 2 // Ranges from 1 to 3, with 1 being the weakest.
|
||||
var/list/candidates = list()
|
||||
|
||||
// A single powerful virus.
|
||||
/datum/event2/event/virus/superbug
|
||||
virus_power = 3
|
||||
|
||||
// A lot of weaker viruses.
|
||||
/datum/event2/event/virus/outbreak
|
||||
virus_power = 1
|
||||
number_of_viruses = 3
|
||||
|
||||
|
||||
|
||||
/datum/event2/event/virus/set_up()
|
||||
for(var/mob/living/carbon/human/H in player_list)
|
||||
if(H.client && !H.isSynthetic() && H.stat != DEAD && !player_is_antag(H.mind))
|
||||
candidates += H
|
||||
candidates = shuffle(candidates)
|
||||
|
||||
/datum/event2/event/virus/announce()
|
||||
command_announcement.Announce("Confirmed outbreak of level 7 biohazard aboard \the [location_name()]. \
|
||||
All personnel must contain the outbreak.", "Biohazard Alert", new_sound = 'sound/AI/outbreak7.ogg')
|
||||
|
||||
/datum/event2/event/virus/start()
|
||||
if(!candidates.len)
|
||||
log_debug("Virus event could not find any valid targets to infect. Aborting.")
|
||||
abort()
|
||||
return
|
||||
|
||||
for(var/i = 1 to number_of_viruses)
|
||||
var/mob/living/carbon/human/H = LAZYACCESS(candidates, 1)
|
||||
if(!H)
|
||||
return
|
||||
var/datum/disease2/disease/D = new()
|
||||
D.makerandom(virus_power)
|
||||
log_debug("Virus event is now infecting \the [H] with a new random virus.")
|
||||
infect_mob(H, D)
|
||||
candidates -= H
|
||||
@@ -0,0 +1,99 @@
|
||||
// A subtype that involves spawning mobs like carp, rogue drones, spiders, etc.
|
||||
|
||||
/datum/event2/event/mob_spawning
|
||||
var/list/spawned_mobs = list()
|
||||
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/landmark_name = "carpspawn" // Which landmark to use for spawning.
|
||||
|
||||
// Spawns a specific mob from the "edge" of the map, and makes them go towards the station.
|
||||
// Can also use landmarks, if desired.
|
||||
/datum/event2/event/mob_spawning/proc/spawn_mobs_in_space(mob_type, number_of_groups, min_size_of_group, max_size_of_group, dir)
|
||||
if(isnull(dir))
|
||||
dir = pick(GLOB.cardinal)
|
||||
|
||||
var/list/valid_z_levels = get_location_z_levels()
|
||||
valid_z_levels -= using_map.sealed_levels // Space levels only please!
|
||||
|
||||
// Check if any landmarks exist!
|
||||
var/list/spawn_locations = list()
|
||||
for(var/obj/effect/landmark/C in landmarks_list)
|
||||
if(C.name == landmark_name && (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_mob(spawn_locations[i], mob_type)
|
||||
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 mob'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 mob 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 simple_mob go towards the edge of the station.
|
||||
var/mob/living/simple_mob/M = spawn_one_mob(spawn_turf, mob_type)
|
||||
if(edge_of_station)
|
||||
M.ai_holder?.give_destination(edge_of_station) // Ask simple_mobs to fly towards the edge of the station.
|
||||
i++
|
||||
|
||||
/datum/event2/event/mob_spawning/proc/spawn_one_mob(new_loc, mob_type)
|
||||
var/mob/living/simple_mob/M = new mob_type(new_loc)
|
||||
GLOB.destroyed_event.register(M, src, .proc/on_mob_destruction)
|
||||
spawned_mobs += M
|
||||
return M
|
||||
|
||||
// Counts living simple_mobs spawned by this event.
|
||||
/datum/event2/event/mob_spawning/proc/count_spawned_mobs()
|
||||
. = 0
|
||||
for(var/I in spawned_mobs)
|
||||
var/mob/living/simple_mob/M = I
|
||||
if(!QDELETED(M) && M.stat != DEAD)
|
||||
. += 1
|
||||
|
||||
// If simple_mob is bomphed, remove it from the list.
|
||||
/datum/event2/event/mob_spawning/proc/on_mob_destruction(mob/M)
|
||||
spawned_mobs -= M
|
||||
GLOB.destroyed_event.unregister(M, src, .proc/on_mob_destruction)
|
||||
@@ -0,0 +1,49 @@
|
||||
/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/mob_spawning/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/mob_spawning/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/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/last_carp_wave_time = null // Last world.time we spawned a carp wave.
|
||||
|
||||
/datum/event2/event/mob_spawning/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/mob_spawning/carp_migration/event_tick()
|
||||
if(last_carp_wave_time + carp_wave_cooldown > world.time)
|
||||
return
|
||||
last_carp_wave_time = world.time
|
||||
|
||||
if(count_spawned_mobs() < carp_cap)
|
||||
spawn_mobs_in_space(
|
||||
mob_type = /mob/living/simple_mob/animal/space/carp/event,
|
||||
number_of_groups = rand(1, 4),
|
||||
min_size_of_group = carp_smallest_group,
|
||||
max_size_of_group = carp_largest_group
|
||||
)
|
||||
|
||||
/datum/event2/event/mob_spawning/carp_migration/end()
|
||||
// Clean up carp that died in space for some reason.
|
||||
for(var/mob/living/simple_mob/SM in spawned_mobs)
|
||||
if(SM.stat == DEAD)
|
||||
var/turf/T = get_turf(SM)
|
||||
if(istype(T, /turf/space))
|
||||
if(prob(75))
|
||||
qdel(SM)
|
||||
@@ -0,0 +1,227 @@
|
||||
|
||||
// Type for inheritence.
|
||||
// It has a null name, so it won't be ran.
|
||||
/datum/event2/meta/prison_break
|
||||
chaos = 10
|
||||
chaotic_threshold = EVENT_CHAOS_THRESHOLD_LOW_IMPACT
|
||||
// The weight system can check if people are in these areas.
|
||||
// This isn't the same list as what the event itself will break, as the event will also
|
||||
// break open areas inbetween the holding area and the public hallway, like the brig area verses
|
||||
// the prison area.
|
||||
var/list/relevant_areas = list()
|
||||
var/list/irrelevant_areas = list()
|
||||
|
||||
/datum/event2/meta/prison_break/get_weight()
|
||||
// First, don't do this if nobody can fix the doors.
|
||||
var/door_fixers = metric.count_people_in_department(DEPARTMENT_ENGINEERING) + metric.count_people_in_department(DEPARTMENT_SYNTHETIC)
|
||||
if(!door_fixers)
|
||||
return 0
|
||||
var/list/afflicted_departments = departments.Copy()
|
||||
var/afflicted_crew = 0
|
||||
|
||||
afflicted_departments -= DEPARTMENT_SYNTHETIC
|
||||
for(var/D in afflicted_departments)
|
||||
afflicted_crew += metric.count_people_in_department(D)
|
||||
|
||||
// Don't do it if nobody is around to ""appreciate"" it.
|
||||
if(!afflicted_crew)
|
||||
return 0
|
||||
|
||||
var/trapped = get_odds_from_trapped_mobs()
|
||||
|
||||
return 10 + (door_fixers * 20) + (afflicted_crew * 10) + trapped
|
||||
|
||||
// This is overriden to have specific events trigger more often based on who is trapped in where, if applicable.
|
||||
/datum/event2/meta/prison_break/proc/get_odds_from_trapped_mobs()
|
||||
return 0
|
||||
|
||||
/datum/event2/meta/prison_break/proc/is_mob_in_relevant_area(mob/living/L)
|
||||
var/area/A = get_area(L)
|
||||
if(!A)
|
||||
return FALSE
|
||||
if(is_type_in_list(A, relevant_areas) && !is_type_in_list(A, irrelevant_areas))
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/datum/event2/meta/prison_break/brig
|
||||
name = "prison break - brig"
|
||||
departments = list(DEPARTMENT_SECURITY, DEPARTMENT_SYNTHETIC)
|
||||
event_type = /datum/event2/event/prison_break/brig
|
||||
relevant_areas = list(
|
||||
/area/security/prison,
|
||||
/area/security/security_cell_hallway,
|
||||
/area/security/security_processing,
|
||||
/area/security/interrogation
|
||||
)
|
||||
|
||||
/datum/event2/meta/prison_break/brig/get_odds_from_trapped_mobs()
|
||||
. = 0
|
||||
for(var/mob/living/L in player_list)
|
||||
if(is_mob_in_relevant_area(L))
|
||||
// Don't count them if they're in security.
|
||||
if(!(L in metric.count_people_in_department(DEPARTMENT_SECURITY)))
|
||||
. += 40
|
||||
|
||||
|
||||
/datum/event2/meta/prison_break/armory
|
||||
name = "prison break - armory"
|
||||
departments = list(DEPARTMENT_SECURITY, DEPARTMENT_SYNTHETIC)
|
||||
chaos = 40 // Potentially free guns.
|
||||
chaotic_threshold = EVENT_CHAOS_THRESHOLD_MEDIUM_IMPACT
|
||||
event_type = /datum/event2/event/prison_break/armory
|
||||
|
||||
/datum/event2/meta/prison_break/bridge
|
||||
name = "prison break - bridge"
|
||||
departments = list(DEPARTMENT_COMMAND, DEPARTMENT_SYNTHETIC)
|
||||
chaos = 40 // Potentially free spare ID.
|
||||
chaotic_threshold = EVENT_CHAOS_THRESHOLD_MEDIUM_IMPACT
|
||||
event_type = /datum/event2/event/prison_break/bridge
|
||||
|
||||
/datum/event2/meta/prison_break/xenobio
|
||||
name = "prison break - xenobio"
|
||||
departments = list(DEPARTMENT_RESEARCH, DEPARTMENT_SYNTHETIC)
|
||||
chaos = 20 // This one is more likely to actually kill someone.
|
||||
chaotic_threshold = EVENT_CHAOS_THRESHOLD_MEDIUM_IMPACT
|
||||
event_type = /datum/event2/event/prison_break/xenobio
|
||||
relevant_areas = list(/area/rnd/xenobiology)
|
||||
irrelevant_areas = list(
|
||||
/area/rnd/xenobiology/xenoflora,
|
||||
/area/rnd/xenobiology/xenoflora_storage
|
||||
)
|
||||
|
||||
/datum/event2/meta/prison_break/xenobio/get_odds_from_trapped_mobs()
|
||||
. = 0
|
||||
for(var/mob/living/simple_mob/slime/xenobio/X in living_mob_list)
|
||||
if(is_mob_in_relevant_area(X))
|
||||
. += 5
|
||||
|
||||
|
||||
/datum/event2/meta/prison_break/virology
|
||||
name = "prison break - virology"
|
||||
departments = list(DEPARTMENT_MEDICAL, DEPARTMENT_SYNTHETIC)
|
||||
event_type = /datum/event2/event/prison_break/virology
|
||||
relevant_areas = list(
|
||||
/area/medical/virology,
|
||||
/area/medical/virologyaccess
|
||||
)
|
||||
|
||||
/datum/event2/meta/prison_break/virology/get_odds_from_trapped_mobs()
|
||||
. = 0
|
||||
for(var/mob/living/L in player_list)
|
||||
if(is_mob_in_relevant_area(L))
|
||||
// Don't count them if they're in medical.
|
||||
if(!(L in metric.count_people_in_department(DEPARTMENT_MEDICAL)))
|
||||
. += 40
|
||||
|
||||
|
||||
|
||||
|
||||
/datum/event2/event/prison_break
|
||||
start_delay_lower_bound = 3 MINUTES
|
||||
start_delay_upper_bound = 4 MINUTES
|
||||
length_lower_bound = 40 SECONDS
|
||||
length_upper_bound = 1 MINUTE
|
||||
var/area_display_name = null // A string used to describe the area being messed with.
|
||||
var/containment_display_desc = null
|
||||
var/list/areas_to_break = list()
|
||||
var/list/area_types_to_break = null // Area types to include.
|
||||
var/list/area_types_to_ignore = null // Area types to exclude, usually due to undesired inclusion from inheritence.
|
||||
|
||||
/datum/event2/event/prison_break/brig
|
||||
area_display_name = "Brig"
|
||||
containment_display_desc = "imprisonment"
|
||||
area_types_to_break = list(
|
||||
/area/security/prison,
|
||||
/area/security/brig,
|
||||
/area/security/security_cell_hallway,
|
||||
/area/security/security_processing,
|
||||
/area/security/interrogation
|
||||
)
|
||||
|
||||
/datum/event2/event/prison_break/armory
|
||||
area_display_name = "Armory"
|
||||
containment_display_desc = "protection"
|
||||
area_types_to_break = list(
|
||||
/area/security/brig,
|
||||
/area/security/warden,
|
||||
/area/security/evidence_storage,
|
||||
/area/security/security_equiptment_storage,
|
||||
/area/security/armoury,
|
||||
/area/security/tactical
|
||||
)
|
||||
|
||||
/datum/event2/event/prison_break/bridge
|
||||
area_display_name = "Bridge"
|
||||
containment_display_desc = "isolation"
|
||||
area_types_to_break = list(
|
||||
/area/bridge,
|
||||
/area/bridge_hallway
|
||||
)
|
||||
|
||||
/datum/event2/event/prison_break/xenobio
|
||||
area_display_name = "Xenobiology"
|
||||
containment_display_desc = "containment"
|
||||
area_types_to_break = list(/area/rnd/xenobiology)
|
||||
area_types_to_ignore = list(
|
||||
/area/rnd/xenobiology/xenoflora,
|
||||
/area/rnd/xenobiology/xenoflora_storage
|
||||
)
|
||||
|
||||
/datum/event2/event/prison_break/virology
|
||||
area_display_name = "Virology"
|
||||
containment_display_desc = "quarantine"
|
||||
area_types_to_break = list(
|
||||
/area/medical/virology,
|
||||
/area/medical/virologyaccess
|
||||
)
|
||||
|
||||
|
||||
/datum/event2/event/prison_break/set_up()
|
||||
for(var/area/A in all_areas)
|
||||
if(is_type_in_list(A, area_types_to_break) && !is_type_in_list(A, area_types_to_ignore))
|
||||
areas_to_break += A
|
||||
|
||||
if(!areas_to_break.len)
|
||||
log_debug("Prison Break event failed to find any areas to break. Aborting.")
|
||||
abort()
|
||||
return
|
||||
|
||||
/datum/event2/event/prison_break/announce()
|
||||
var/my_department = "[location_name()] Firewall Subroutines"
|
||||
var/message = "An unknown malicious program has been detected in the [area_display_name] \
|
||||
lighting and airlock control systems at [stationtime2text()]. Systems will be fully compromised \
|
||||
within approximately three minutes. Direct intervention is required immediately. Disabling the \
|
||||
main breaker in the APCs will protect the APC's room from being compromised."
|
||||
|
||||
for(var/obj/machinery/message_server/MS in machines)
|
||||
MS.send_rc_message(DEPARTMENT_ENGINEERING, my_department, "[message]<br>", "", "", 2)
|
||||
|
||||
// Nobody reads the requests consoles so lets use the radio as well.
|
||||
global_announcer.autosay(message, my_department, DEPARTMENT_ENGINEERING)
|
||||
|
||||
for(var/mob/living/silicon/ai/A in player_list)
|
||||
to_chat(A, span("danger", "Malicious program detected in the [area_display_name] lighting and airlock control systems by [my_department]. \
|
||||
Disabling the main breaker in the APCs will protect the APC's room from being compromised."))
|
||||
|
||||
var/time_to_flicker = start_delay - 10 SECONDS
|
||||
addtimer(CALLBACK(src, .proc/flicker_area), time_to_flicker)
|
||||
|
||||
|
||||
/datum/event2/event/prison_break/proc/flicker_area()
|
||||
for(var/area/A in areas_to_break)
|
||||
var/obj/machinery/power/apc/apc = A.get_apc()
|
||||
if(apc.operating) //If the apc's off, it's a little hard to overload the lights.
|
||||
for(var/obj/machinery/light/L in A)
|
||||
L.flicker(10)
|
||||
|
||||
/datum/event2/event/prison_break/start()
|
||||
for(var/area/A in areas_to_break)
|
||||
spawn(0) // So we don't block the ticker.
|
||||
A.prison_break()
|
||||
|
||||
// There's between 40 seconds and one minute before the whole station knows.
|
||||
// If there's a baddie engineer, they can choose to keep their early announcement to themselves and get a minute to exploit it.
|
||||
/datum/event2/event/prison_break/end()
|
||||
command_announcement.Announce("[pick("Gr3y.T1d3 virus","Malignant trojan")] was detected \
|
||||
in \the [location_name()] [area_display_name] [containment_display_desc] subroutines. Secure any compromised \
|
||||
areas immediately. AI involvement is recommended.", "[capitalize(containment_display_desc)] Alert")
|
||||
@@ -0,0 +1,70 @@
|
||||
/datum/event2/meta/rogue_drones
|
||||
name = "rogue drones"
|
||||
departments = list(DEPARTMENT_SECURITY, DEPARTMENT_EVERYONE)
|
||||
chaos = 40
|
||||
chaotic_threshold = EVENT_CHAOS_THRESHOLD_MEDIUM_IMPACT
|
||||
event_type = /datum/event2/event/mob_spawning/rogue_drones
|
||||
|
||||
/datum/event2/meta/rogue_drones/get_weight()
|
||||
. = 10 // Start with a base weight, since this event does provide some value even if no sec is around.
|
||||
. += metric.count_people_in_department(DEPARTMENT_SECURITY) * 20
|
||||
. += metric.count_all_space_mobs() * 40
|
||||
|
||||
|
||||
/datum/event2/event/mob_spawning/rogue_drones
|
||||
length_lower_bound = 15 MINUTES
|
||||
length_upper_bound = 20 MINUTES
|
||||
var/drones_to_spawn = 6
|
||||
|
||||
/datum/event2/event/mob_spawning/rogue_drones/set_up()
|
||||
if(prob(10)) // Small chance for a false alarm.
|
||||
drones_to_spawn = 0
|
||||
|
||||
/datum/event2/event/mob_spawning/rogue_drones/announce()
|
||||
var/msg = null
|
||||
var/rng = rand(1,5)
|
||||
switch(rng)
|
||||
if(1)
|
||||
msg = "A combat drone wing operating in close orbit above Sif has failed to return from a anti-piracy sweep. \
|
||||
If any are sighted, approach with caution."
|
||||
if(2)
|
||||
msg = "Contact has been lost with a combat drone wing in Sif orbit. \
|
||||
If any are sighted in the area, approach with caution."
|
||||
if(3)
|
||||
msg = "Unidentified hackers have targeted a combat drone wing deployed around Sif. \
|
||||
If any are sighted in the area, approach with caution."
|
||||
if(4)
|
||||
msg = "A passing derelict ship's drone defense systems have just activated. \
|
||||
If any are sighted in the area, use caution."
|
||||
if(5)
|
||||
msg = "We're detecting a swarm of small objects approaching your station. \
|
||||
Most likely a bunch of drones. Please exercise caution if you see any."
|
||||
|
||||
command_announcement.Announce(msg, "Rogue drone alert")
|
||||
|
||||
/datum/event2/event/mob_spawning/rogue_drones/start()
|
||||
for(var/i = 1 to drones_to_spawn)
|
||||
spawn_mobs_in_space(
|
||||
mob_type = /mob/living/simple_mob/mechanical/combat_drone/event,
|
||||
number_of_groups = 1,
|
||||
min_size_of_group = 1,
|
||||
max_size_of_group = 1
|
||||
)
|
||||
|
||||
/datum/event2/event/mob_spawning/rogue_drones/end()
|
||||
if(drones_to_spawn)
|
||||
var/number_recovered = 0
|
||||
for(var/mob/living/simple_mob/mechanical/combat_drone/D in spawned_mobs)
|
||||
var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread()
|
||||
sparks.set_up(3, 0, D.loc)
|
||||
sparks.start()
|
||||
D.z = using_map.admin_levels[1]
|
||||
D.loot_list = list()
|
||||
|
||||
qdel(D)
|
||||
number_recovered++
|
||||
|
||||
if(number_recovered > spawned_mobs.len * 0.75)
|
||||
command_announcement.Announce("The drones that were malfunctioning have been recovered safely.", "Rogue drone alert")
|
||||
else
|
||||
command_announcement.Announce("We're disappointed at the loss of the drones, but the survivors have been recovered.", "Rogue drone alert")
|
||||
@@ -0,0 +1,49 @@
|
||||
/datum/event2/meta/spider_infestation
|
||||
name = "spider infestation"
|
||||
event_class = "spiders"
|
||||
departments = list(DEPARTMENT_SECURITY, DEPARTMENT_MEDICAL, DEPARTMENT_EVERYONE)
|
||||
chaos = 30
|
||||
chaotic_threshold = EVENT_CHAOS_THRESHOLD_MEDIUM_IMPACT
|
||||
event_type = /datum/event2/event/spider_infestation
|
||||
|
||||
/datum/event2/meta/spider_infestation/weak
|
||||
name = "weak spider infestation"
|
||||
chaos = 20
|
||||
event_type = /datum/event2/event/spider_infestation/weak
|
||||
|
||||
|
||||
/datum/event2/meta/spider_infestation/get_weight()
|
||||
. = 10
|
||||
. += metric.count_people_in_department(DEPARTMENT_SECURITY) * 20
|
||||
. += metric.count_people_in_department(DEPARTMENT_MEDICAL) * 10
|
||||
|
||||
|
||||
// This isn't a /mob_spawning subtype since spiderlings aren't actually mobs.
|
||||
|
||||
/datum/event2/event/spider_infestation
|
||||
var/spiders_to_spawn = 8
|
||||
var/spiderling_to_spawn = /obj/effect/spider/spiderling
|
||||
|
||||
/datum/event2/event/spider_infestation/weak
|
||||
spiders_to_spawn = 5
|
||||
spiderling_to_spawn = /obj/effect/spider/spiderling/stunted
|
||||
|
||||
|
||||
|
||||
/datum/event2/event/spider_infestation/announce()
|
||||
command_announcement.Announce("Unidentified lifesigns detected coming aboard \the [location_name()]. \
|
||||
Secure any exterior access, including ducting and ventilation.", "Lifesign Alert", new_sound = 'sound/AI/aliens.ogg')
|
||||
|
||||
/datum/event2/event/spider_infestation/start()
|
||||
var/list/vents = list()
|
||||
for(var/obj/machinery/atmospherics/unary/vent_pump/temp_vent in machines)
|
||||
if(!temp_vent.welded && temp_vent.network && temp_vent.loc.z in get_location_z_levels())
|
||||
if(temp_vent.network.normal_members.len > 50)
|
||||
vents += temp_vent
|
||||
|
||||
while((spiders_to_spawn >= 1) && vents.len)
|
||||
var/obj/vent = pick(vents)
|
||||
new spiderling_to_spawn(vent.loc)
|
||||
log_debug("Spider infestation event spawned a spiderling at [get_area(vent)].")
|
||||
vents -= vent
|
||||
spiders_to_spawn--
|
||||
@@ -0,0 +1,64 @@
|
||||
// Base type used for inheritence.
|
||||
/datum/event2/meta/stowaway
|
||||
event_class = "stowaway"
|
||||
departments = list(DEPARTMENT_SECURITY, DEPARTMENT_EVERYONE)
|
||||
chaos = 10
|
||||
chaotic_threshold = EVENT_CHAOS_THRESHOLD_LOW_IMPACT
|
||||
event_type = /datum/event2/event/ghost_pod_spawner/stowaway
|
||||
var/safe_for_extended = FALSE
|
||||
|
||||
/datum/event2/meta/stowaway/normal
|
||||
name = "stowaway - normal"
|
||||
safe_for_extended = TRUE
|
||||
|
||||
/datum/event2/meta/stowaway/renegade
|
||||
name = "stowaway - renegade"
|
||||
chaos = 30
|
||||
event_type = /datum/event2/event/ghost_pod_spawner/stowaway/renegade
|
||||
|
||||
/datum/event2/meta/stowaway/infiltrator
|
||||
name = "stowaway - infiltrator"
|
||||
chaos = 60
|
||||
chaotic_threshold = EVENT_CHAOS_THRESHOLD_MEDIUM_IMPACT
|
||||
event_type = /datum/event2/event/ghost_pod_spawner/stowaway/infiltrator
|
||||
|
||||
/datum/event2/meta/stowaway/get_weight()
|
||||
if(istype(ticker.mode, /datum/game_mode/extended) && !safe_for_extended)
|
||||
return 0
|
||||
|
||||
var/security = metric.count_people_in_department(DEPARTMENT_SECURITY)
|
||||
var/everyone = metric.count_people_in_department(DEPARTMENT_EVERYONE) - security
|
||||
var/ghost_activity = metric.assess_all_dead_mobs() / 100
|
||||
|
||||
return ( (security * 20) + (everyone * 2) ) * ghost_activity
|
||||
|
||||
|
||||
/datum/event2/event/ghost_pod_spawner/stowaway
|
||||
pod_type = /obj/structure/ghost_pod/ghost_activated/human
|
||||
desired_turf_areas = list(/area/maintenance)
|
||||
announce_delay_lower_bound = 15 MINUTES
|
||||
announce_delay_upper_bound = 30 MINUTES
|
||||
var/antag_type = MODE_STOWAWAY
|
||||
var/announce_odds = 20
|
||||
|
||||
/datum/event2/event/ghost_pod_spawner/stowaway/renegade
|
||||
antag_type = MODE_RENEGADE
|
||||
announce_odds = 33
|
||||
|
||||
/datum/event2/event/ghost_pod_spawner/stowaway/infiltrator
|
||||
antag_type = MODE_INFILTRATOR
|
||||
announce_odds = 50
|
||||
|
||||
/datum/event2/event/ghost_pod_spawner/stowaway/post_pod_creation(obj/structure/ghost_pod/ghost_activated/human/pod)
|
||||
pod.make_antag = antag_type
|
||||
pod.occupant_type = "[pod.make_antag] [pod.occupant_type]"
|
||||
|
||||
say_dead_object("[span("notice", pod.occupant_type)] pod is now available in \the [get_area(pod)].", pod)
|
||||
|
||||
/datum/event2/event/ghost_pod_spawner/stowaway/announce()
|
||||
if(prob(announce_odds))
|
||||
if(atc?.squelched)
|
||||
return
|
||||
atc.msg("Attention civilian vessels in [using_map.starsys_name] shipping lanes, caution is advised as \
|
||||
[pick("an unidentified vessel", "a known criminal's vessel", "a derelict vessel")] \
|
||||
has been detected passing multiple local stations.")
|
||||
@@ -0,0 +1,71 @@
|
||||
// This event sends a few carp after someone hanging around in space, unannounced.
|
||||
|
||||
/datum/event2/meta/surprise_carp
|
||||
name = "surprise carp"
|
||||
departments = list(DEPARTMENT_EVERYONE)
|
||||
chaos = 20
|
||||
chaotic_threshold = EVENT_CHAOS_THRESHOLD_MEDIUM_IMPACT
|
||||
event_type = /datum/event2/event/surprise_carp
|
||||
|
||||
/datum/event2/meta/surprise_carp/get_weight()
|
||||
return metric.count_all_space_mobs() * 50
|
||||
|
||||
|
||||
/datum/event2/event/surprise_carp
|
||||
var/mob/living/victim = null
|
||||
|
||||
/datum/event2/event/surprise_carp/set_up()
|
||||
var/list/potential_victims = list()
|
||||
for(var/mob/living/L in player_list)
|
||||
if(!(L.z in get_location_z_levels()))
|
||||
continue // Not on the right z-level.
|
||||
if(L.stat)
|
||||
continue // Don't want dead people.
|
||||
if(istype(get_turf(L), /turf/space) && istype(get_area(L),/area/space))
|
||||
potential_victims += L
|
||||
|
||||
if(potential_victims.len)
|
||||
victim = pick(potential_victims)
|
||||
|
||||
/datum/event2/event/surprise_carp/start()
|
||||
if(!victim)
|
||||
log_debug("Failed to find a target for surprise carp attack. Aborting.")
|
||||
abort()
|
||||
return
|
||||
|
||||
var/number_of_carp = rand(1, 2)
|
||||
log_debug("Sending [number_of_carp] carp\s after \the [victim].")
|
||||
// Getting off screen tiles is kind of tricky due to potential edge cases that could arise.
|
||||
// The method we're gonna do is make a big square around the victim, then
|
||||
// subtract a smaller square in the middle for the default vision range.
|
||||
var/list/outer_square = get_safe_square(victim, world.view + 3)
|
||||
var/list/inner_square = get_safe_square(victim, world.view)
|
||||
|
||||
var/list/donut = outer_square - inner_square
|
||||
for(var/T in donut)
|
||||
if(!istype(T, /turf/space))
|
||||
donut -= T
|
||||
|
||||
for(var/i = 1 to number_of_carp)
|
||||
var/turf/spawning_turf = pick(donut)
|
||||
|
||||
if(spawning_turf)
|
||||
var/mob/living/simple_mob/animal/space/carp/C = new(spawning_turf)
|
||||
// Ask carp to swim onto the victim's screen. The AI will then switch to hostile and try to eat them.
|
||||
C.ai_holder?.give_destination(get_turf(victim))
|
||||
else
|
||||
log_debug("Surprise carp attack failed to find any space turfs offscreen to the victim.")
|
||||
|
||||
// Gets suitable spots for carp to spawn, without risk of going off the edge of the map.
|
||||
// If there is demand for this proc, then it can easily be made independant and moved into one of the helper files.
|
||||
/datum/event2/event/surprise_carp/proc/get_safe_square(atom/center, radius)
|
||||
var/lower_left_x = max(center.x - radius, 1 + TRANSITIONEDGE)
|
||||
var/lower_left_y = max(center.y - radius, 1 + TRANSITIONEDGE)
|
||||
|
||||
var/upper_right_x = min(center.x + radius, world.maxx - TRANSITIONEDGE)
|
||||
var/upper_right_y = min(center.y + radius, world.maxy - TRANSITIONEDGE)
|
||||
|
||||
var/turf/lower_left = locate(lower_left_x, lower_left_y, victim.z)
|
||||
var/turf/upper_right = locate(upper_right_x, upper_right_y, victim.z)
|
||||
|
||||
return block(lower_left, upper_right)
|
||||
@@ -0,0 +1,55 @@
|
||||
// This is just porting the event to the new new event system, it's not been balanced in any way
|
||||
// so don't @ me if these things are grossly OP.
|
||||
/datum/event2/meta/swarm_boarder
|
||||
event_class = "swarm boarder"
|
||||
departments = list(DEPARTMENT_EVERYONE, DEPARTMENT_SECURITY, DEPARTMENT_ENGINEERING)
|
||||
chaos = 60
|
||||
chaotic_threshold = EVENT_CHAOS_THRESHOLD_HIGH_IMPACT
|
||||
var/safe_for_extended = FALSE
|
||||
|
||||
/datum/event2/meta/swarm_boarder/get_weight()
|
||||
if(istype(ticker.mode, /datum/game_mode/extended) && !safe_for_extended)
|
||||
return 0
|
||||
|
||||
var/security = metric.count_people_in_department(DEPARTMENT_SECURITY)
|
||||
var/engineering = metric.count_people_in_department(DEPARTMENT_ENGINEERING)
|
||||
var/everyone = metric.count_people_in_department(DEPARTMENT_EVERYONE) - (engineering + security)
|
||||
|
||||
var/ghost_activity = metric.assess_all_dead_mobs() / 100
|
||||
|
||||
return ( (security * 20) + (engineering * 10) + (everyone * 2) ) * ghost_activity
|
||||
|
||||
/datum/event2/meta/swarm_boarder/normal
|
||||
name = "swarmer shell - normal"
|
||||
event_type = /datum/event2/event/ghost_pod_spawner/swarm_boarder
|
||||
|
||||
/datum/event2/meta/swarm_boarder/melee
|
||||
name = "swarmer shell - melee"
|
||||
event_type = /datum/event2/event/ghost_pod_spawner/swarm_boarder/melee
|
||||
|
||||
/datum/event2/meta/swarm_boarder/gunner
|
||||
name = "swarmer shell - gunner"
|
||||
event_type = /datum/event2/event/ghost_pod_spawner/swarm_boarder/gunner
|
||||
|
||||
|
||||
|
||||
|
||||
/datum/event2/event/ghost_pod_spawner/swarm_boarder
|
||||
announce_delay_lower_bound = 5 MINUTES
|
||||
announce_delay_upper_bound = 15 MINUTES
|
||||
pod_type = /obj/structure/ghost_pod/ghost_activated/swarm_drone/event
|
||||
desired_turf_areas = list(/area/maintenance)
|
||||
var/announce_odds = 80
|
||||
|
||||
/datum/event2/event/ghost_pod_spawner/swarm_boarder/melee
|
||||
pod_type = /obj/structure/ghost_pod/ghost_activated/swarm_drone/event/melee
|
||||
|
||||
/datum/event2/event/ghost_pod_spawner/swarm_boarder/gunner
|
||||
pod_type = /obj/structure/ghost_pod/ghost_activated/swarm_drone/event/gunner
|
||||
|
||||
/datum/event2/event/ghost_pod_spawner/swarm_boarder/announce()
|
||||
if(prob(announce_odds))
|
||||
if(atc?.squelched)
|
||||
atc.msg("Attention civilian vessels in [using_map.starsys_name] shipping lanes, caution \
|
||||
is advised as [pick("an unidentified vessel", "a known criminal's vessel", "a derelict vessel")] \
|
||||
has been detected passing multiple local stations.")
|
||||
@@ -15,6 +15,7 @@
|
||||
announce_delay_lower_bound = 7 MINUTES
|
||||
announce_delay_upper_bound = 15 MINUTES
|
||||
var/bot_emag_chance = 30 // This is rolled once, instead of once a second for a minute like the old version.
|
||||
var/announce_odds = 50 // Probability of an announcement actually happening after the delay.
|
||||
|
||||
/datum/event2/event/ion_storm/start()
|
||||
// Ion laws.
|
||||
@@ -55,6 +56,16 @@
|
||||
"director", "Hello", "Hi!"," ","nuke","crate","taj","xeno")
|
||||
|
||||
/datum/event2/event/ion_storm/announce()
|
||||
if(prob(50))
|
||||
command_announcement.Announce("An ion storm was detected within proximity to \the [location_name()]. \
|
||||
if(prob(announce_odds))
|
||||
command_announcement.Announce("An ion storm was detected within proximity to \the [location_name()] recently. \
|
||||
Check all AI controlled equipment for corruption.", "Anomaly Alert", new_sound = 'sound/AI/ionstorm.ogg')
|
||||
|
||||
// Fake variant used by traitors.
|
||||
/datum/event2/event/ion_storm/fake
|
||||
// Fake ion storms announce instantly, so the traitor can time it to make the AI look suspicious.
|
||||
announce_delay_lower_bound = 0
|
||||
announce_delay_upper_bound = 0
|
||||
announce_odds = 100
|
||||
|
||||
/datum/event2/event/ion_storm/fake/start()
|
||||
return
|
||||
@@ -7,7 +7,7 @@
|
||||
for(var/areapath in typesof(/area/hallway))
|
||||
var/area/A = locate(areapath)
|
||||
for(var/turf/simulated/floor/F in A.contents)
|
||||
if(turf_clear(F))
|
||||
if(!F.check_density())
|
||||
turfs += F
|
||||
|
||||
if(turfs.len) //Pick a turf to spawn at if we can
|
||||
|
||||
@@ -49,8 +49,19 @@
|
||||
return num
|
||||
|
||||
// Like above, but for all FBPs.
|
||||
/datum/metric/proc/count_all_FBPs(desired_FBP_class, cutoff = 75, respect_z = TRUE)
|
||||
/datum/metric/proc/count_all_FBPs(cutoff = 75, respect_z = TRUE)
|
||||
var/num = count_all_FBPs_of_kind(FBP_CYBORG, cutoff, respect_z)
|
||||
num += count_all_FBPs_of_kind(FBP_POSI, cutoff, respect_z)
|
||||
num += count_all_FBPs_of_kind(FBP_DRONE, cutoff, respect_z)
|
||||
return num
|
||||
return num
|
||||
|
||||
|
||||
/datum/metric/proc/get_all_antags(cutoff = 75)
|
||||
. = list()
|
||||
for(var/mob/living/L in player_list)
|
||||
if(L.mind && player_is_antag(L.mind) && assess_player_activity(L) >= cutoff)
|
||||
. += L
|
||||
|
||||
/datum/metric/proc/count_all_antags(cutoff = 75)
|
||||
var/list/L = get_all_antags(cutoff)
|
||||
return L.len
|
||||
@@ -70,7 +70,7 @@
|
||||
if(!department)
|
||||
return
|
||||
for(var/mob/M in player_list)
|
||||
if(guess_department(M) != department) // Ignore people outside the department we're counting.
|
||||
if(department != DEPARTMENT_EVERYONE && guess_department(M) != department) // Ignore people outside the department we're counting.
|
||||
continue
|
||||
if(assess_player_activity(M) < cutoff)
|
||||
continue
|
||||
@@ -94,7 +94,23 @@
|
||||
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
|
||||
|
||||
|
||||
|
||||
/datum/metric/proc/get_people_with_alt_title(job_type, alt_title_type, cutoff = 75)
|
||||
. = list()
|
||||
|
||||
var/list/people_with_jobs = get_people_with_job(job_type, cutoff)
|
||||
var/datum/job/J = SSjob.get_job_type(job_type)
|
||||
var/datum/alt_title/A = new alt_title_type()
|
||||
|
||||
for(var/M in people_with_jobs)
|
||||
if(J.has_alt_title(M, null, A.title))
|
||||
. += M
|
||||
|
||||
/datum/metric/proc/count_people_with_alt_title(job_type, alt_title_type, cutoff = 75)
|
||||
var/list/L = get_people_with_alt_title(job_type, alt_title_type, cutoff)
|
||||
return L.len
|
||||
@@ -26,6 +26,8 @@ GLOBAL_LIST_EMPTY(available_ai_shells)
|
||||
shell = TRUE
|
||||
braintype = "AI Shell"
|
||||
SetName("[modtype] AI Shell [num2text(ident)]")
|
||||
rbPDA = new /obj/item/device/pda/ai/shell(src)
|
||||
setup_PDA()
|
||||
GLOB.available_ai_shells |= src
|
||||
if(!QDELETED(camera))
|
||||
camera.c_tag = real_name //update the camera name too
|
||||
|
||||
@@ -520,6 +520,8 @@ var/global/list/light_type_cache = list()
|
||||
if(status != LIGHT_OK) break
|
||||
on = !on
|
||||
update(0)
|
||||
if(!on) // Only play when the light turns off.
|
||||
playsound(src, 'sound/effects/light_flicker.ogg', 50, 1)
|
||||
sleep(rand(5, 15))
|
||||
on = (status == LIGHT_OK)
|
||||
update(0)
|
||||
|
||||
Reference in New Issue
Block a user