Events ported, now for cleanup if needed.

This commit is contained in:
Neerti
2020-03-26 03:20:16 -04:00
parent dd2edef196
commit f5383410ea
51 changed files with 2325 additions and 312 deletions
@@ -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.")
+1 -1
View File
@@ -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.")
+38 -45
View File
@@ -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>&larr; 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
+13 -2
View File
@@ -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
+18 -2
View File
@@ -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
+2
View File
@@ -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)