diff --git a/code/_helpers/events.dm b/code/_helpers/events.dm index e31d24783e..2d15bb3aa9 100644 --- a/code/_helpers/events.dm +++ b/code/_helpers/events.dm @@ -24,4 +24,16 @@ if(A == myarea) //The loc of a turf is the area it is in. return 1 return 0 - \ No newline at end of file + +// Returns a list of area instances, or a subtypes of them, that are mapped in somewhere. +// Avoid feeding it `/area`, as it will likely cause a lot of lag as it evaluates every single area coded in. +/proc/get_all_existing_areas_of_types(list/area_types) + . = list() + for(var/area_type in area_types) + var/list/types = typesof(area_type) + for(var/T in types) + // Test for existance. + var/area/A = locate(T) + if(!istype(A) || !A.contents.len) // Empty contents list means it's not on the map. + continue + . += A \ No newline at end of file diff --git a/code/controllers/subsystems/game_master.dm b/code/controllers/subsystems/game_master.dm index 7c402e14b5..b27b6e578e 100644 --- a/code/controllers/subsystems/game_master.dm +++ b/code/controllers/subsystems/game_master.dm @@ -36,9 +36,6 @@ SUBSYSTEM_DEF(game_master) if(config && !config.enable_game_master) can_fire = FALSE - // Remove when finished. - debug_gm() - return ..() /datum/controller/subsystem/game_master/fire(resumed) diff --git a/code/game/area/Space Station 13 areas.dm b/code/game/area/Space Station 13 areas.dm index 81c74f3ba3..2a4b933edf 100644 --- a/code/game/area/Space Station 13 areas.dm +++ b/code/game/area/Space Station 13 areas.dm @@ -71,6 +71,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station flags = RAD_SHIELDED sound_env = SMALL_ENCLOSED base_turf = /turf/space + forbid_events = TRUE /area/shuttle/arrival name = "\improper Arrival Shuttle" @@ -952,6 +953,9 @@ NOTE: there are two lists of areas in the end of this file: centcom and station name = "\improper Construction Area" icon_state = "construction" +/area/hallway/secondary/entry + forbid_events = TRUE + /area/hallway/secondary/entry/fore name = "\improper Shuttle Dock Hallway - Mid" icon_state = "entry_1" @@ -1126,6 +1130,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station icon_state = "Sleep" flags = RAD_SHIELDED ambience = AMBIENCE_GENERIC + forbid_events = TRUE /area/crew_quarters/toilet name = "\improper Dormitory Toilets" @@ -1425,6 +1430,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station icon_state = "Holodeck" dynamic_lighting = 0 sound_env = LARGE_ENCLOSED + forbid_events = TRUE /area/holodeck/alphadeck name = "\improper Holodeck Alpha" @@ -1524,6 +1530,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station name = "\improper Engine Room" icon_state = "engine" sound_env = LARGE_ENCLOSED + forbid_events = TRUE /area/engineering/engine_airlock name = "\improper Engine Room Airlock" diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm index 0f9b208d77..ef6e13f711 100644 --- a/code/game/area/areas.dm +++ b/code/game/area/areas.dm @@ -41,6 +41,7 @@ var/turf/base_turf //The base turf type of the area, which can be used to override the z-level's base turf var/global/global_uid = 0 var/uid + var/forbid_events = FALSE // If true, random events will not start inside this area. /area/New() icon_state = "" @@ -335,6 +336,8 @@ var/list/mob/living/forced_ambiance_list = new temp_airlock.prison_open() for(var/obj/machinery/door/window/temp_windoor in src) temp_windoor.open() + for(var/obj/machinery/door/blast/temp_blast in src) + temp_blast.open() /area/has_gravity() return has_gravity diff --git a/code/game/objects/effects/spiders.dm b/code/game/objects/effects/spiders.dm index 3bf7284214..8b74a26246 100644 --- a/code/game/objects/effects/spiders.dm +++ b/code/game/objects/effects/spiders.dm @@ -278,6 +278,9 @@ grow_as = list(/mob/living/simple_mob/animal/giant_spider, /mob/living/simple_mob/animal/giant_spider/hunter) +/obj/effect/spider/spiderling/non_growing + amount_grown = -1 + /obj/effect/decal/cleanable/spiderling_remains name = "spiderling remains" desc = "Green squishy mess." diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm index e5acb5c4e7..a8b3d8f430 100644 --- a/code/game/objects/items/devices/PDA/PDA.dm +++ b/code/game/objects/items/devices/PDA/PDA.dm @@ -65,6 +65,8 @@ var/global/list/obj/item/device/pda/PDAs = list() var/obj/item/device/paicard/pai = null // A slot for a personal AI device + var/spam_proof = FALSE // If true, it can't be spammed by random events. + /obj/item/device/pda/examine(mob/user) if(..(user, 1)) to_chat(user, "The time [stationtime2text()] is displayed in the corner of the screen.") @@ -334,6 +336,8 @@ var/global/list/obj/item/device/pda/PDAs = list() /obj/item/device/pda/ai/pai ttone = "assist" +/obj/item/device/pda/ai/shell + spam_proof = TRUE // Since empty shells get a functional PDA. // Used for the PDA multicaster, which mirrors messages sent to it to a specific department, /obj/item/device/pda/multicaster @@ -342,6 +346,7 @@ var/global/list/obj/item/device/pda/PDAs = list() ttone = "data" detonate = 0 news_silent = 1 + spam_proof = TRUE // Spam messages don't actually work and its difficult to disable these. var/list/cartridges_to_send_to = list() // This is what actually mirrors the message, @@ -1194,6 +1199,15 @@ var/global/list/obj/item/device/pda/PDAs = list() log_pda("(PDA: [sending_unit]) sent \"[message]\" to [name]",usr) new_message = 1 +/obj/item/device/pda/proc/spam_message(sender, message) + var/reception_message = "\icon[src] Message from [sender] (Unknown / spam?), \"[message]\" (Unable to Reply)" + new_info(message_silent, ttone, reception_message) + + if(prob(50)) // Give the AI an increased chance to intercept the message + for(var/mob/living/silicon/ai/ai in mob_list) + if(ai.aiPDA != src) + ai.show_message("Intercepted message from [sender] (Unknown / spam?) to [owner]: [message]") + /obj/item/device/pda/verb/verb_reset_pda() set category = "Object" set name = "Reset PDA" diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index 344428c1f2..208c8473ab 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -441,6 +441,13 @@ //this way it will only update full-tile ones overlays.Cut() if(!is_fulltile()) + // Rotate the sprite somewhat so non-fulltiled windows can be seen as needing repair. + var/full_tilt_degrees = 15 + var/tilt_to_apply = abs((health / maxhealth) - 1) + if(tilt_to_apply && prob(50)) + tilt_to_apply = -tilt_to_apply + adjust_rotation(LERP(0, full_tilt_degrees, tilt_to_apply)) + icon_state = "[basestate]" return var/list/dirs = list() diff --git a/code/game/turfs/simulated/walls.dm b/code/game/turfs/simulated/walls.dm index bf5f711b20..3c8ab70ede 100644 --- a/code/game/turfs/simulated/walls.dm +++ b/code/game/turfs/simulated/walls.dm @@ -231,10 +231,25 @@ // Wall-rot effect, a nasty fungus that destroys walls. /turf/simulated/wall/proc/rot() if(locate(/obj/effect/overlay/wallrot) in src) - return + return FALSE + + // Wall-rot can't go onto walls that are surrounded in all four cardinal directions. + // Because of spores, or something. It's actually to avoid the pain that is removing wallrot surrounded by + // four r-walls. + var/at_least_one_open_turf = FALSE + for(var/direction in GLOB.cardinal) + var/turf/T = get_step(src, direction) + if(!T.check_density()) + at_least_one_open_turf = TRUE + break + + if(!at_least_one_open_turf) + return FALSE + var/number_rots = rand(2,3) for(var/i=0, i", "", "", 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) diff --git a/code/modules/gamemaster/event2/events/command/raise_funds.dm b/code/modules/gamemaster/event2/events/command/raise_funds.dm new file mode 100644 index 0000000000..33053c0996 --- /dev/null +++ b/code/modules/gamemaster/event2/events/command/raise_funds.dm @@ -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.

\ + 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.

\ + 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') diff --git a/code/modules/gamemaster/event2/events/engineering/airlock_failure.dm b/code/modules/gamemaster/event2/events/engineering/airlock_failure.dm new file mode 100644 index 0000000000..42297ca56c --- /dev/null +++ b/code/modules/gamemaster/event2/events/engineering/airlock_failure.dm @@ -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("\The [chosen_door]'s panel sparks!") + 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("\The [chosen_door] buzzes.") + 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("\The [chosen_door]'s hydraulics creak.") + 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("\The [chosen_door] emits a hydraulic shriek!") + 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("\The [chosen_door]'s hydraulics detonate!") + 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) + +*/ \ No newline at end of file diff --git a/code/modules/gamemaster/event2/events/engineering/blob.dm b/code/modules/gamemaster/event2/events/engineering/blob.dm index badb260114..a62cd6742f 100644 --- a/code/modules/gamemaster/event2/events/engineering/blob.dm +++ b/code/modules/gamemaster/event2/events/engineering/blob.dm @@ -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.") diff --git a/code/modules/gamemaster/event2/events/engineering/brand_intelligence.dm b/code/modules/gamemaster/event2/events/engineering/brand_intelligence.dm new file mode 100644 index 0000000000..bf1a8dfed6 --- /dev/null +++ b/code/modules/gamemaster/event2/events/engineering/brand_intelligence.dm @@ -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 diff --git a/code/modules/gamemaster/event2/events/engineering/carp_migration.dm b/code/modules/gamemaster/event2/events/engineering/carp_migration.dm deleted file mode 100644 index 4a4ceee6ce..0000000000 --- a/code/modules/gamemaster/event2/events/engineering/carp_migration.dm +++ /dev/null @@ -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) diff --git a/code/modules/gamemaster/event2/events/engineering/wallrot.dm b/code/modules/gamemaster/event2/events/engineering/wallrot.dm new file mode 100644 index 0000000000..29d12c8524 --- /dev/null +++ b/code/modules/gamemaster/event2/events/engineering/wallrot.dm @@ -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 + + + diff --git a/code/modules/gamemaster/event2/events/engineering/window_break.dm b/code/modules/gamemaster/event2/events/engineering/window_break.dm new file mode 100644 index 0000000000..0b02e78b3d --- /dev/null +++ b/code/modules/gamemaster/event2/events/engineering/window_break.dm @@ -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 diff --git a/code/modules/gamemaster/event2/events/everyone/comms_blackout.dm b/code/modules/gamemaster/event2/events/everyone/comms_blackout.dm index 69e91d6942..6da19c32cc 100644 --- a/code/modules/gamemaster/event2/events/everyone/comms_blackout.dm +++ b/code/modules/gamemaster/event2/events/everyone/comms_blackout.dm @@ -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, "
") - to_chat(A, "Ionospheric anomalies detected. Temporary telecommunication failure imminent. Please contact you-BZZT") + to_chat(A, "[alert]") to_chat(A, "
") /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) diff --git a/code/modules/gamemaster/event2/events/everyone/electrical_fault.dm b/code/modules/gamemaster/event2/events/everyone/electrical_fault.dm index e43d02fd21..6467646f1b 100644 --- a/code/modules/gamemaster/event2/events/everyone/electrical_fault.dm +++ b/code/modules/gamemaster/event2/events/everyone/electrical_fault.dm @@ -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() -*/ diff --git a/code/modules/gamemaster/event2/events/everyone/gravity.dm b/code/modules/gamemaster/event2/events/everyone/gravity.dm new file mode 100644 index 0000000000..abfa22ec12 --- /dev/null +++ b/code/modules/gamemaster/event2/events/everyone/gravity.dm @@ -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") \ No newline at end of file diff --git a/code/modules/gamemaster/event2/events/everyone/infestation.dm b/code/modules/gamemaster/event2/events/everyone/infestation.dm new file mode 100644 index 0000000000..f51456f362 --- /dev/null +++ b/code/modules/gamemaster/event2/events/everyone/infestation.dm @@ -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) diff --git a/code/modules/gamemaster/event2/events/everyone/pda_spam.dm b/code/modules/gamemaster/event2/events/everyone/pda_spam.dm new file mode 100644 index 0000000000..d8eceffe06 --- /dev/null +++ b/code/modules/gamemaster/event2/events/everyone/pda_spam.dm @@ -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 ",\ + "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 ",\ + "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("Intercepted message from [sender] (Unknown / spam?) to [P:owner]: [message]") + + //Commented out because we don't send messages like this anymore. Instead it will just popup in their chat window. + //P.tnote += "← From [sender] (Unknown / spam?):
[message]
" + + 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] Message from [sender] (Unknown / spam?), \"[message]\" (Unable to Reply)") + +*/ \ No newline at end of file diff --git a/code/modules/gamemaster/event2/events/everyone/radiation_storm.dm b/code/modules/gamemaster/event2/events/everyone/radiation_storm.dm new file mode 100644 index 0000000000..ebd137b404 --- /dev/null +++ b/code/modules/gamemaster/event2/events/everyone/radiation_storm.dm @@ -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 diff --git a/code/modules/gamemaster/event2/events/everyone/random_antag.dm b/code/modules/gamemaster/event2/events/everyone/random_antag.dm new file mode 100644 index 0000000000..fe3b58be8c --- /dev/null +++ b/code/modules/gamemaster/event2/events/everyone/random_antag.dm @@ -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() diff --git a/code/modules/gamemaster/event2/events/everyone/sudden_weather_shift.dm b/code/modules/gamemaster/event2/events/everyone/sudden_weather_shift.dm new file mode 100644 index 0000000000..484d226bb8 --- /dev/null +++ b/code/modules/gamemaster/event2/events/everyone/sudden_weather_shift.dm @@ -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 + +*/ \ No newline at end of file diff --git a/code/modules/gamemaster/event2/events/ghost_pod_spawner.dm b/code/modules/gamemaster/event2/events/ghost_pod_spawner.dm new file mode 100644 index 0000000000..4c462d06d6 --- /dev/null +++ b/code/modules/gamemaster/event2/events/ghost_pod_spawner.dm @@ -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) \ No newline at end of file diff --git a/code/modules/gamemaster/event2/events/legacy.dm b/code/modules/gamemaster/event2/events/legacy.dm deleted file mode 100644 index f1663c546a..0000000000 --- a/code/modules/gamemaster/event2/events/legacy.dm +++ /dev/null @@ -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 \ No newline at end of file diff --git a/code/modules/gamemaster/event2/events/legacy/legacy.dm b/code/modules/gamemaster/event2/events/legacy/legacy.dm index 3b69ab8d7a..6f70cafa85 100644 --- a/code/modules/gamemaster/event2/events/legacy/legacy.dm +++ b/code/modules/gamemaster/event2/events/legacy/legacy.dm @@ -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 \ No newline at end of file + legacy_event = /datum/event/gravity +*/ \ No newline at end of file diff --git a/code/modules/gamemaster/event2/events/medical/appendicitis.dm b/code/modules/gamemaster/event2/events/medical/appendicitis.dm new file mode 100644 index 0000000000..27f5284020 --- /dev/null +++ b/code/modules/gamemaster/event2/events/medical/appendicitis.dm @@ -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.") diff --git a/code/modules/gamemaster/event2/events/medical/virus.dm b/code/modules/gamemaster/event2/events/medical/virus.dm new file mode 100644 index 0000000000..58791d2ad1 --- /dev/null +++ b/code/modules/gamemaster/event2/events/medical/virus.dm @@ -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 diff --git a/code/modules/gamemaster/event2/events/mob_spawning.dm b/code/modules/gamemaster/event2/events/mob_spawning.dm new file mode 100644 index 0000000000..78b1eac69c --- /dev/null +++ b/code/modules/gamemaster/event2/events/mob_spawning.dm @@ -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) \ No newline at end of file diff --git a/code/modules/gamemaster/event2/events/security/carp_migration.dm b/code/modules/gamemaster/event2/events/security/carp_migration.dm new file mode 100644 index 0000000000..12f08f3edd --- /dev/null +++ b/code/modules/gamemaster/event2/events/security/carp_migration.dm @@ -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) diff --git a/code/modules/gamemaster/event2/events/security/prison_break.dm b/code/modules/gamemaster/event2/events/security/prison_break.dm new file mode 100644 index 0000000000..7c5127c26c --- /dev/null +++ b/code/modules/gamemaster/event2/events/security/prison_break.dm @@ -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]
", "", "", 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") diff --git a/code/modules/gamemaster/event2/events/security/rogue_drones.dm b/code/modules/gamemaster/event2/events/security/rogue_drones.dm new file mode 100644 index 0000000000..da40b3262f --- /dev/null +++ b/code/modules/gamemaster/event2/events/security/rogue_drones.dm @@ -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") diff --git a/code/modules/gamemaster/event2/events/security/spider_infestation.dm b/code/modules/gamemaster/event2/events/security/spider_infestation.dm new file mode 100644 index 0000000000..23dc61c673 --- /dev/null +++ b/code/modules/gamemaster/event2/events/security/spider_infestation.dm @@ -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-- diff --git a/code/modules/gamemaster/event2/events/security/stowaway.dm b/code/modules/gamemaster/event2/events/security/stowaway.dm new file mode 100644 index 0000000000..d04d350234 --- /dev/null +++ b/code/modules/gamemaster/event2/events/security/stowaway.dm @@ -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.") diff --git a/code/modules/gamemaster/event2/events/security/surprise_carp.dm b/code/modules/gamemaster/event2/events/security/surprise_carp.dm new file mode 100644 index 0000000000..39be6d8412 --- /dev/null +++ b/code/modules/gamemaster/event2/events/security/surprise_carp.dm @@ -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) diff --git a/code/modules/gamemaster/event2/events/security/swarm_boarder.dm b/code/modules/gamemaster/event2/events/security/swarm_boarder.dm new file mode 100644 index 0000000000..283333c679 --- /dev/null +++ b/code/modules/gamemaster/event2/events/security/swarm_boarder.dm @@ -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.") diff --git a/code/modules/gamemaster/event2/events/synthetic/ion_storm.dm b/code/modules/gamemaster/event2/events/synthetic/ion_storm.dm index e84108d608..afcf20ebc8 100644 --- a/code/modules/gamemaster/event2/events/synthetic/ion_storm.dm +++ b/code/modules/gamemaster/event2/events/synthetic/ion_storm.dm @@ -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 \ No newline at end of file diff --git a/code/modules/hydroponics/spreading/spreading.dm b/code/modules/hydroponics/spreading/spreading.dm index 53e4e87627..e3ac11ed76 100644 --- a/code/modules/hydroponics/spreading/spreading.dm +++ b/code/modules/hydroponics/spreading/spreading.dm @@ -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 diff --git a/code/modules/metric/count.dm b/code/modules/metric/count.dm index 55565016ad..ba3678295f 100644 --- a/code/modules/metric/count.dm +++ b/code/modules/metric/count.dm @@ -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 \ No newline at end of file + 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 \ No newline at end of file diff --git a/code/modules/metric/department.dm b/code/modules/metric/department.dm index e97e0cb17c..89a0f78ed9 100644 --- a/code/modules/metric/department.dm +++ b/code/modules/metric/department.dm @@ -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 \ No newline at end of file diff --git a/code/modules/mob/living/silicon/robot/robot_remote_control.dm b/code/modules/mob/living/silicon/robot/robot_remote_control.dm index 90d74b2638..2f4ca0e56b 100644 --- a/code/modules/mob/living/silicon/robot/robot_remote_control.dm +++ b/code/modules/mob/living/silicon/robot/robot_remote_control.dm @@ -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 diff --git a/code/modules/power/lighting.dm b/code/modules/power/lighting.dm index ab91a82510..6d01a2e445 100644 --- a/code/modules/power/lighting.dm +++ b/code/modules/power/lighting.dm @@ -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) diff --git a/maps/southern_cross/southern_cross.dm b/maps/southern_cross/southern_cross.dm index 29c4f295f2..741d64d58e 100644 --- a/maps/southern_cross/southern_cross.dm +++ b/maps/southern_cross/southern_cross.dm @@ -6,6 +6,7 @@ #include "southern_cross_defines.dm" #include "southern_cross_jobs.dm" #include "southern_cross_elevator.dm" + #include "southern_cross_events.dm" #include "southern_cross_presets.dm" #include "southern_cross_shuttles.dm" diff --git a/maps/southern_cross/southern_cross_events.dm b/maps/southern_cross/southern_cross_events.dm new file mode 100644 index 0000000000..ab7ac253f6 --- /dev/null +++ b/maps/southern_cross/southern_cross_events.dm @@ -0,0 +1,7 @@ +// The Station Director's office is specific to the Southern Cross, so this needs to go here. +/datum/event2/event/prison_break/bridge + area_types_to_break = list( + /area/bridge, + /area/bridge_hallway, + /area/crew_quarters/heads/sc/sd + ) \ No newline at end of file diff --git a/maps/submaps/surface_submaps/wilderness/wilderness_areas.dm b/maps/submaps/surface_submaps/wilderness/wilderness_areas.dm index f7ee7f2542..1607781d91 100644 --- a/maps/submaps/surface_submaps/wilderness/wilderness_areas.dm +++ b/maps/submaps/surface_submaps/wilderness/wilderness_areas.dm @@ -3,6 +3,7 @@ icon_state = "submap" flags = RAD_SHIELDED ambience = AMBIENCE_RUINS + forbid_events = TRUE /area/submap/event //To be used for Events not for regular PoIs name = "Unknown" diff --git a/polaris.dme b/polaris.dme index 7bfbbeb7b2..9c11e26383 100644 --- a/polaris.dme +++ b/polaris.dme @@ -1730,21 +1730,45 @@ #include "code\modules\gamemaster\defines.dm" #include "code\modules\gamemaster\event2\event.dm" #include "code\modules\gamemaster\event2\meta.dm" +#include "code\modules\gamemaster\event2\events\ghost_pod_spawner.dm" +#include "code\modules\gamemaster\event2\events\mob_spawning.dm" #include "code\modules\gamemaster\event2\events\cargo\shipping_error.dm" +#include "code\modules\gamemaster\event2\events\command\manifest_malfunction.dm" +#include "code\modules\gamemaster\event2\events\command\money_hacker.dm" +#include "code\modules\gamemaster\event2\events\command\raise_funds.dm" +#include "code\modules\gamemaster\event2\events\engineering\airlock_failure.dm" #include "code\modules\gamemaster\event2\events\engineering\blob.dm" +#include "code\modules\gamemaster\event2\events\engineering\brand_intelligence.dm" #include "code\modules\gamemaster\event2\events\engineering\camera_damage.dm" #include "code\modules\gamemaster\event2\events\engineering\canister_leak.dm" -#include "code\modules\gamemaster\event2\events\engineering\carp_migration.dm" #include "code\modules\gamemaster\event2\events\engineering\dust.dm" #include "code\modules\gamemaster\event2\events\engineering\gas_leak.dm" #include "code\modules\gamemaster\event2\events\engineering\grid_check.dm" #include "code\modules\gamemaster\event2\events\engineering\meteor_defense.dm" #include "code\modules\gamemaster\event2\events\engineering\spacevine.dm" +#include "code\modules\gamemaster\event2\events\engineering\wallrot.dm" +#include "code\modules\gamemaster\event2\events\engineering\window_break.dm" #include "code\modules\gamemaster\event2\events\everyone\comms_blackout.dm" +#include "code\modules\gamemaster\event2\events\everyone\electrical_fault.dm" +#include "code\modules\gamemaster\event2\events\everyone\gravity.dm" +#include "code\modules\gamemaster\event2\events\everyone\infestation.dm" +#include "code\modules\gamemaster\event2\events\everyone\pda_spam.dm" +#include "code\modules\gamemaster\event2\events\everyone\radiation_storm.dm" +#include "code\modules\gamemaster\event2\events\everyone\random_antag.dm" #include "code\modules\gamemaster\event2\events\everyone\solar_storm.dm" +#include "code\modules\gamemaster\event2\events\everyone\sudden_weather_shift.dm" #include "code\modules\gamemaster\event2\events\legacy\legacy.dm" +#include "code\modules\gamemaster\event2\events\medical\appendicitis.dm" +#include "code\modules\gamemaster\event2\events\medical\virus.dm" +#include "code\modules\gamemaster\event2\events\security\carp_migration.dm" #include "code\modules\gamemaster\event2\events\security\drill_announcement.dm" +#include "code\modules\gamemaster\event2\events\security\prison_break.dm" +#include "code\modules\gamemaster\event2\events\security\rogue_drones.dm" #include "code\modules\gamemaster\event2\events\security\security_advisement.dm" +#include "code\modules\gamemaster\event2\events\security\spider_infestation.dm" +#include "code\modules\gamemaster\event2\events\security\stowaway.dm" +#include "code\modules\gamemaster\event2\events\security\surprise_carp.dm" +#include "code\modules\gamemaster\event2\events\security\swarm_boarder.dm" #include "code\modules\gamemaster\event2\events\synthetic\ion_storm.dm" #include "code\modules\games\cah.dm" #include "code\modules\games\cah_black_cards.dm"