diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm index 1790890dfbb..39fbe15e2f3 100644 --- a/code/__defines/subsystems.dm +++ b/code/__defines/subsystems.dm @@ -73,6 +73,7 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G #define INIT_ORDER_CIRCUIT -21 #define INIT_ORDER_AI -22 #define INIT_ORDER_JOB -23 +#define INIT_ORDER_GAME_MASTER -24 #define INIT_ORDER_CHAT -100 //Should be last to ensure chat remains smooth during init. diff --git a/code/_helpers/events.dm b/code/_helpers/events.dm index e31d24783e8..2d15bb3aa9d 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/Processes/game_master.dm b/code/controllers/Processes/game_master.dm deleted file mode 100644 index 7f89f3ab135..00000000000 --- a/code/controllers/Processes/game_master.dm +++ /dev/null @@ -1,6 +0,0 @@ -/datum/controller/process/game_master/setup() - name = "\improper GM controller" - schedule_interval = 600 // every 60 seconds - -/datum/controller/process/game_master/doWork() - game_master.process() \ No newline at end of file diff --git a/code/controllers/subsystems/events.dm b/code/controllers/subsystems/events.dm index 6de40f464d5..226c09f4924 100644 --- a/code/controllers/subsystems/events.dm +++ b/code/controllers/subsystems/events.dm @@ -1,5 +1,5 @@ SUBSYSTEM_DEF(events) - name = "Events" + name = "Events" // VOREStation Edit - This is still the main events subsystem for us. wait = 2 SECONDS var/tmp/list/currentrun = null diff --git a/code/controllers/subsystems/events2.dm b/code/controllers/subsystems/events2.dm new file mode 100644 index 00000000000..2a73498b1d6 --- /dev/null +++ b/code/controllers/subsystems/events2.dm @@ -0,0 +1,37 @@ +// This is a simple ticker for the new event system. +// The logic that determines what events get chosen is held inside a seperate subsystem. + +SUBSYSTEM_DEF(event_ticker) + name = "Events (Ticker)" + wait = 2 SECONDS + runlevels = RUNLEVEL_GAME + + // List of `/datum/event2/event`s that are currently active, and receiving process() ticks. + var/list/active_events = list() + + // List of `/datum/event2/event`s that finished, and are here for showing at roundend, if that's desired. + var/list/finished_events = list() + +// Process active events. +/datum/controller/subsystem/event_ticker/fire(resumed) + for(var/E in active_events) + var/datum/event2/event/event = E + event.process() + if(event.finished) + event_finished(event) + +// Starts an event, independent of the GM system. +// This means it will always run, and won't affect the GM system in any way, e.g. not putting the event off limits after one use. +/datum/controller/subsystem/event_ticker/proc/start_event(event_type) + var/datum/event2/event/E = new event_type() + E.execute() + event_started(E) + +/datum/controller/subsystem/event_ticker/proc/event_started(datum/event2/event/E) + log_debug("Event [E.type] is now being ran.") + active_events += E + +/datum/controller/subsystem/event_ticker/proc/event_finished(datum/event2/event/E) + log_debug("Event [E.type] has finished.") + active_events -= E + finished_events += E \ No newline at end of file diff --git a/code/controllers/subsystems/game_master.dm b/code/controllers/subsystems/game_master.dm new file mode 100644 index 00000000000..d5326bfba61 --- /dev/null +++ b/code/controllers/subsystems/game_master.dm @@ -0,0 +1,369 @@ +// This is a sort of successor to the various event systems created over the years. It is designed to be just a tad smarter than the +// previous ones, checking various things like player count, department size and composition, individual player activity, +// individual player (IC) skill, and such, in order to try to choose the best events to take in order to add spice or variety to +// the round. + +// This subsystem holds the logic that chooses events. Actual event processing is handled in a seperate subsystem. +SUBSYSTEM_DEF(game_master) + name = "Events (Game Master)" + wait = 1 MINUTE + runlevels = RUNLEVEL_GAME + + // The GM object is what actually chooses events. + // It's held in a seperate object for better encapsulation, and allows for different 'flavors' of GMs to be made, that choose events differently. + var/datum/game_master/GM = null + var/game_master_type = /datum/game_master/default + + var/list/available_events = list() // A list of meta event objects. + + var/danger = 0 // The GM's best guess at how chaotic the round is. High danger makes it hold back. + var/staleness = -20 // Determines liklihood of the GM doing something, increases over time. + + var/next_event = 0 // Minimum amount of time of nothingness until the GM can pick something again. + + var/debug_messages = FALSE // If true, debug information is written to `log_debug()`. + +/datum/controller/subsystem/game_master/Initialize() + var/list/subtypes = subtypesof(/datum/event2/meta) + for(var/T in subtypes) + var/datum/event2/meta/M = new T() + if(!M.name) + continue + available_events += M + + GM = new game_master_type() + + if(config && !config.enable_game_master) + can_fire = FALSE + + return ..() + +/datum/controller/subsystem/game_master/fire(resumed) + adjust_staleness(1) + adjust_danger(-1) + + var/global_afk = metric.assess_all_living_mobs() + global_afk = abs(global_afk - 100) + global_afk = round(global_afk / 100, 0.1) + adjust_staleness(global_afk) // Staleness increases faster if more people are less active. + + if(GM.ignore_time_restrictions || next_event < world.time) + if(prob(staleness) && pre_event_checks()) + do_event_decision() + + +/datum/controller/subsystem/game_master/proc/do_event_decision() + log_game_master("Going to choose an event.") + var/datum/event2/meta/event_picked = GM.choose_event() + if(event_picked) + run_event(event_picked) + next_event = world.time + rand(GM.decision_cooldown_lower_bound, GM.decision_cooldown_upper_bound) + +/datum/controller/subsystem/game_master/proc/debug_gm() + can_fire = TRUE + staleness = 100 + debug_messages = TRUE + +/datum/controller/subsystem/game_master/proc/run_event(datum/event2/meta/chosen_event) + var/datum/event2/event/E = chosen_event.make_event() + + chosen_event.times_ran++ + + if(!chosen_event.reusable) + // Disable this event, so it only gets picked once. + chosen_event.enabled = FALSE + if(chosen_event.event_class) + // Disable similar events, too. + for(var/M in available_events) + var/datum/event2/meta/meta = M + if(meta.event_class == chosen_event.event_class) + meta.enabled = FALSE + + SSevent_ticker.event_started(E) + adjust_danger(chosen_event.chaos) + adjust_staleness(-(10 + chosen_event.chaos)) // More chaotic events reduce staleness more, e.g. a 25 chaos event will reduce it by 35. + + +// Tell the game master that something dangerous happened, e.g. someone dying, station explosions. +/datum/controller/subsystem/game_master/proc/adjust_danger(amount) + amount *= GM.danger_modifier + danger = round(between(0, danger + amount, 1000), 0.1) + +// Tell the game master that things are getting boring if positive, or something interesting if negative.. +/datum/controller/subsystem/game_master/proc/adjust_staleness(amount) + amount *= GM.staleness_modifier + staleness = round( between(-20, staleness + amount, 100), 0.1) + +// These are ran before committing to an event. +// Returns TRUE if the system is allowed to procede, otherwise returns FALSE. +/datum/controller/subsystem/game_master/proc/pre_event_checks(quiet = FALSE) + if(!ticker || ticker.current_state != GAME_STATE_PLAYING) + if(!quiet) + log_game_master("Unable to start event: Ticker is nonexistant, or the game is not ongoing.") + return FALSE + if(GM.ignore_time_restrictions) + return TRUE + if(next_event > world.time) // Sanity. + if(!quiet) + log_game_master("Unable to start event: Time until next event is approximately [round((next_event - world.time) / (1 MINUTE))] minute(s)") + return FALSE + + // Last minute antagging is bad for humans to do, so the GM will respect the start and end of the round. + var/mills = round_duration_in_ticks + var/mins = round((mills % 36000) / 600) + var/hours = round(mills / 36000) + +// if(hours < 1 && mins <= 20) // Don't do anything for the first twenty minutes of the round. +// if(!quiet) +// log_debug("Game Master unable to start event: It is too early.") +// return FALSE + if(hours >= 2 && mins >= 40) // Don't do anything in the last twenty minutes of the round, as well. + if(!quiet) + log_game_master("Unable to start event: It is too late.") + return FALSE + return TRUE + +/datum/controller/subsystem/game_master/proc/choose_game_master(mob/user) + var/list/subtypes = subtypesof(/datum/game_master) + var/new_gm_path = input(user, "What kind of Game Master do you want?", "New Game Master", /datum/game_master/default) as null|anything in subtypes + if(new_gm_path) + log_and_message_admins("has swapped the current GM ([GM.type]) for a new GM ([new_gm_path]).") + GM = new new_gm_path(src) + +/datum/controller/subsystem/game_master/proc/log_game_master(message) + if(debug_messages) + log_debug("GAME MASTER: [message]") + + +// This object makes the actual decisions. +/datum/game_master + // Multiplier for how much 'danger' is accumulated. Higer generally makes it possible for more dangerous events to be picked. + var/danger_modifier = 1.0 + + // Ditto. Higher numbers generally result in more events occuring in a round. + var/staleness_modifier = 1.0 + + var/decision_cooldown_lower_bound = 5 MINUTES // Lower bound for how long to wait until -the potential- for another event being decided. + var/decision_cooldown_upper_bound = 20 MINUTES // Same, but upper bound. + + var/ignore_time_restrictions = FALSE // Useful for debugging without needing to wait 20 minutes each time. + var/ignore_round_chaos = FALSE // If true, the system will happily choose back to back intense events like meteors and blobs, Dwarf Fortress style. + +/client/proc/show_gm_status() + set category = "Debug" + set name = "Show GM Status" + set desc = "Shows you what the GM is thinking. If only that existed in real life..." + + if(check_rights(R_ADMIN|R_EVENT|R_DEBUG)) + SSgame_master.interact(usr) + else + to_chat(usr, span("warning", "You do not have sufficent rights to view the GM panel, sorry.")) + +/datum/controller/subsystem/game_master/proc/interact(var/client/user) + if(!user) + return + + // Using lists for string tree conservation. + var/list/dat = list("Automated Game Master Event System") + + // Makes the system turn on or off. + dat += href(src, list("toggle" = 1), "\[Toggle GM\]") + dat += " | " + + // Makes the system not care about staleness or being near round-end. + dat += href(src, list("toggle_time_restrictions" = 1), "\[Toggle Time Restrictions\]") + dat += " | " + + // Makes the system not care about how chaotic the round might be. + dat += href(src, list("toggle_chaos_throttle" = 1), "\[Toggle Chaos Throttling\]") + dat += " | " + + // Makes the system immediately choose an event, while still bound to factors like danger, weights, and department staffing. + dat += href(src, list("force_choose_event" = 1), "\[Force Event Decision\]") + dat += "
" + + // Swaps out the current GM for a new one with different ideas on what a good event might be. + dat += href(src, list("change_gm" = 1), "\[Change GM\]") + dat += "
" + + dat += "Current GM Type: [GM.type]
" + dat += "State: [can_fire ? "Active": "Inactive"]
" + dat += "Status: [pre_event_checks(TRUE) ? "Ready" : "Suppressed"]

" + + dat += "Staleness: [staleness] " + dat += href(src, list("set_staleness" = 1), "\[Set\]") + dat += "
" + dat += "Staleness is an estimate of how boring the round might be, and if an event should be done. It is increased passively over time, \ + and increases faster if people are AFK. It deceases when events and certain 'interesting' things happen in the round.
" + + dat += "Danger: [danger] " + dat += href(src, list("set_danger" = 1), "\[Set\]") + dat += "
" + dat += "Danger is an estimate of how chaotic the round has been so far. It is decreased passively over time, and is increased by having \ + certain chaotic events be selected, or chaotic things happen in the round. A sufficently high amount of danger will make the system \ + avoid using destructive events, to avoid pushing the station over the edge.
" + + dat += "

Player Activity:

" + + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + + dat += "" + dat += "" + dat += "" + dat += "" + + dat += "" + dat += "" + dat += "" + dat += "" + + dat += "" + dat += "" + + for(var/D in metric.departments) + dat += "" + dat += "" + dat += "" + dat += "" + + dat += "" + dat += "" + + for(var/P in player_list) + var/mob/M = P + dat += "" + dat += "" + dat += "" + dat += "" + dat += "
CategoryActivity Percentage
All Living Mobs[metric.assess_all_living_mobs()]%
All Ghosts[metric.assess_all_dead_mobs()]%
Departments" + dat += "
[D][metric.assess_department(D)]%
Players" + dat += "
[M] ([M.ckey])[metric.assess_player_activity(M)]%
" + + dat += "

Events available:

" + + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + + for(var/E in available_events) + var/datum/event2/meta/event = E + dat += "" + if(!event.enabled) + dat += "" + else + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "
Event NameInvolved DepartmentsChaosChaotic ThresholdWeightButtons
[event.name][event.name][english_list(event.departments)][event.chaos][event.chaotic_threshold][event.get_weight()][href(event, list("force" = 1), "\[Force\]")] [href(event, list("toggle" = 1), "\[Toggle\]")]
" + + dat += "

Events active:

" + + dat += "Current time: [world.time]" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + + for(var/E in SSevent_ticker.active_events) + var/datum/event2/event/event = E + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "
Event TypeTime StartedTime to AnnounceTime to EndAnnouncedStartedEndedButtons
[event.type][event.time_started][event.time_to_announce ? event.time_to_announce : "NULL"][event.time_to_end ? event.time_to_end : "NULL"][event.announced ? "Yes" : "No"][event.started ? "Yes" : "No"][event.ended ? "Yes" : "No"][href(event, list("abort" = 1), "\[Abort\]")]
" + dat += "" + + dat += "

Events completed:

" + + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + + for(var/E in SSevent_ticker.finished_events) + var/datum/event2/event/event = E + dat += "" + dat += "" + dat += "" + dat += "" + dat += "" + + dat += "" + + var/datum/browser/popup = new(user, "game_master_debug", "Automated Game Master Event System", 800, 500, src) + popup.set_content(dat.Join()) + popup.open() + + +/datum/controller/subsystem/game_master/Topic(href, href_list) + if(..()) + return + + if(href_list["close"]) // Needed or the window re-opens after closing, making it last forever. + return + + if(!check_rights(R_ADMIN|R_EVENT|R_DEBUG)) + message_admins("[usr] has attempted to modify the Game Master values without sufficent privilages.") + return + + if(href_list["toggle"]) + can_fire = !can_fire + message_admins("GM was [!can_fire ? "dis" : "en"]abled by [usr.key].") + + if(href_list["toggle_time_restrictions"]) + GM.ignore_time_restrictions = !GM.ignore_time_restrictions + message_admins("GM event time restrictions was [GM.ignore_time_restrictions ? "dis" : "en"]abled by [usr.key].") + + if(href_list["toggle_chaos_throttle"]) + GM.ignore_round_chaos = !GM.ignore_round_chaos + message_admins("GM event chaos restrictions was [GM.ignore_round_chaos ? "dis" : "en"]abled by [usr.key].") + + if(href_list["force_choose_event"]) + do_event_decision() + message_admins("[usr.key] forced the Game Master to choose an event immediately.") + + if(href_list["change_gm"]) + choose_game_master(usr) + + if(href_list["set_staleness"]) + var/amount = input(usr, "How much staleness should there be?", "Game Master") as null|num + if(!isnull(amount)) + staleness = amount + message_admins("GM staleness was set to [amount] by [usr.key].") + + if(href_list["set_danger"]) + var/amount = input(usr, "How much danger should there be?", "Game Master") as null|num + if(!isnull(amount)) + danger = amount + message_admins("GM danger was set to [amount] by [usr.key].") + + interact(usr) // To refresh the UI. \ No newline at end of file diff --git a/code/datums/game_masters/_common.dm b/code/datums/game_masters/_common.dm new file mode 100644 index 00000000000..84efe57c73d --- /dev/null +++ b/code/datums/game_masters/_common.dm @@ -0,0 +1,6 @@ +// Push button, receive event. +// Returns a selected event datum. +/datum/game_master/proc/choose_event() + +/datum/game_master/proc/log_game_master(message) + // SSgame_master.log_game_master(message) // VOREStation Edit - We don't use SSgame_master yet. \ No newline at end of file diff --git a/code/datums/game_masters/default.dm b/code/datums/game_masters/default.dm new file mode 100644 index 00000000000..d380476df2f --- /dev/null +++ b/code/datums/game_masters/default.dm @@ -0,0 +1,89 @@ +// The default game master tries to choose events with these goals in mind. +// * Don't choose an event if the crew can't take it. E.g. no meteors after half of the crew died. +// * Try to involve lots of people, particuarly in active departments. +// * Avoid giving events to the same department multiple times in a row. +/datum/game_master/default + // If an event was done for a specific department, it is written here, so it doesn't do it again. + var/last_department_used = null + + +/datum/game_master/default/choose_event() + log_game_master("Now starting event decision.") + + var/list/most_active_departments = metric.assess_all_departments(3, list(last_department_used)) + var/list/best_events = decide_best_events(most_active_departments) + + if(LAZYLEN(best_events)) + log_game_master("Got [best_events.len] choice\s for the next event.") + var/list/weighted_events = list() + + for(var/E in best_events) + var/datum/event2/meta/event = E + var/weight = event.get_weight() + if(weight <= 0) + continue + weighted_events[event] = weight + log_game_master("Filtered down to [weighted_events.len] choice\s.") + + var/datum/event2/meta/choice = pickweight(weighted_events) + + if(choice) + log_game_master("[choice.name] was chosen, and is now being ran.") + last_department_used = LAZYACCESS(choice.departments, 1) + return choice + +/datum/game_master/default/proc/decide_best_events(list/most_active_departments) + if(!LAZYLEN(most_active_departments)) // Server's empty? + log_game_master("Game Master failed to find any active departments.") + return list() + + var/list/best_events = list() + if(most_active_departments.len >= 2) + var/list/top_two = list(most_active_departments[1], most_active_departments[2]) + best_events = filter_events_by_departments(top_two) + + if(LAZYLEN(best_events)) // We found something for those two, let's do it. + return best_events + + // Otherwise we probably couldn't find something for the second highest group, so let's ignore them. + best_events = filter_events_by_departments(most_active_departments[1]) + + if(LAZYLEN(best_events)) + return best_events + + // At this point we should expand our horizons. + best_events = filter_events_by_departments(list(DEPARTMENT_EVERYONE)) + + if(LAZYLEN(best_events)) + return best_events + + // Just give a random event if for some reason it still can't make up its mind. + best_events = filter_events_by_departments() + + if(LAZYLEN(best_events)) + return best_events + + log_game_master("Game Master failed to find a suitable event, something very wrong is going on.") + return list() + +// Filters the available events down to events for specific departments. +// Pass DEPARTMENT_EVERYONE if you want events that target the general population, like gravity failure. +// If no list is passed, all the events will be returned. +/datum/game_master/default/proc/filter_events_by_departments(list/departments) + . = list() + for(var/E in SSgame_master.available_events) + var/datum/event2/meta/event = E + if(!event.enabled) + continue + if(event.chaotic_threshold && !ignore_round_chaos) + if(SSgame_master.danger > event.chaotic_threshold) + continue + // An event has to involve all of these departments to pass. + var/viable = TRUE + if(LAZYLEN(departments)) + for(var/department in departments) + if(!LAZYFIND(departments, department)) + viable = FALSE + break + if(viable) + . += event diff --git a/code/datums/game_masters/other_game_masters.dm b/code/datums/game_masters/other_game_masters.dm new file mode 100644 index 00000000000..f62d83cfe37 --- /dev/null +++ b/code/datums/game_masters/other_game_masters.dm @@ -0,0 +1,44 @@ +// The `classic` game master tries to act like the old system, choosing events without any specific goals. +// * Has no goals, and instead operates purely off of the weights of the events it has. +// * Does not react to danger at all. +/datum/game_master/classic/choose_event() + var/list/weighted_events = list() + for(var/E in SSgame_master.available_events) + var/datum/event2/meta/event = E + if(!event.enabled) + continue + weighted_events[event] = event.get_weight() + + var/datum/event2/meta/choice = pickweight(weighted_events) + + if(choice) + log_game_master("[choice.name] was chosen, and is now being ran.") + return choice + + +// The `super_random` game master chooses events purely at random, ignoring weights entirely. +// * Has no goals, and instead chooses randomly, ignoring weights. +// * Does not react to danger at all. +/datum/game_master/super_random/choose_event() + return pick(SSgame_master.available_events) + + +// The `brutal` game master tries to run dangerous events frequently. +// * Chaotic events have their weights artifically boosted. +// * Ignores accumulated danger. +/datum/game_master/brutal + ignore_round_chaos = TRUE + +/datum/game_master/brutal/choose_event() + var/list/weighted_events = list() + for(var/E in SSgame_master.available_events) + var/datum/event2/meta/event = E + if(!event.enabled) + continue + weighted_events[event] = event.get_weight() + (event.chaos * 2) + + var/datum/event2/meta/choice = pickweight(weighted_events) + + if(choice) + log_game_master("[choice.name] was chosen, and is now being ran.") + return choice diff --git a/code/game/area/Space Station 13 areas.dm b/code/game/area/Space Station 13 areas.dm index 839ab27a318..067f71a6e28 100755 --- a/code/game/area/Space Station 13 areas.dm +++ b/code/game/area/Space Station 13 areas.dm @@ -70,6 +70,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" @@ -807,6 +808,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" @@ -981,6 +985,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" @@ -1281,6 +1286,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" @@ -1380,6 +1386,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 8b7950dd482..6e739117e7f 100644 --- a/code/game/area/areas.dm +++ b/code/game/area/areas.dm @@ -42,6 +42,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() uid = ++global_uid @@ -366,6 +367,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 3e107738002..f6422259d33 100644 --- a/code/game/objects/effects/spiders.dm +++ b/code/game/objects/effects/spiders.dm @@ -286,6 +286,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 a3f66d1f927..6deb8370066 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, @@ -1195,6 +1200,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/items/devices/defib.dm b/code/game/objects/items/devices/defib.dm index 5521d12983c..80d9dc081bc 100644 --- a/code/game/objects/items/devices/defib.dm +++ b/code/game/objects/items/devices/defib.dm @@ -500,7 +500,7 @@ M.Weaken(rand(10,25)) M.updatehealth() apply_brain_damage(M) - SSgame_master.adjust_danger(-20) + // SSgame_master.adjust_danger(-20) // VOREStation Edit - We don't use SSgame_master yet. /obj/item/weapon/shockpaddles/proc/apply_brain_damage(mob/living/carbon/human/H) if(!H.should_have_organ(O_BRAIN)) diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index b2867d5eadb..d8542d0c96f 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -442,6 +442,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 d72beb290c0..cbb9c6dbb70 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= BLOB_DIFFICULTY_MEDIUM) // Tell them what kind of blob it is if it's tough. - lines += "The biohazard has been identified as a '[blob.name]'." - - if(blob.difficulty >= BLOB_DIFFICULTY_HARD) // If it's really hard then tell them where it is so the response occurs faster. - lines += "It is suspected to have originated from \the [get_area(B)]." - - if(blob.difficulty >= BLOB_DIFFICULTY_SUPERHARD) - lines += "Extreme caution is advised." - - command_announcement.Announce(lines.Join("\n"), "Biohazard Alert", new_sound = 'sound/AI/outbreak7.ogg') \ No newline at end of file diff --git a/code/modules/blob2/blobs/base_blob.dm b/code/modules/blob2/blobs/base_blob.dm index 767cf4c783d..02a7827825a 100644 --- a/code/modules/blob2/blobs/base_blob.dm +++ b/code/modules/blob2/blobs/base_blob.dm @@ -1,4 +1,4 @@ -var/list/blobs = list() +GLOBAL_LIST_EMPTY(all_blobs) /obj/structure/blob name = "blob" @@ -18,21 +18,21 @@ var/list/blobs = list() var/mob/observer/blob/overmind = null var/base_name = "blob" // The name that gets appended along with the blob_type's name. -/obj/structure/blob/New(var/newloc, var/new_overmind) - ..(newloc) +/obj/structure/blob/Initialize(newloc, new_overmind) if(new_overmind) overmind = new_overmind update_icon() if(!integrity) integrity = max_integrity set_dir(pick(cardinal)) - blobs += src + GLOB.all_blobs += src consume_tile() + return ..() /obj/structure/blob/Destroy() playsound(src.loc, 'sound/effects/splat.ogg', 50, 1) //Expand() is no longer broken, no check necessary. - blobs -= src + GLOB.all_blobs -= src overmind = null return ..() @@ -112,7 +112,7 @@ var/list/blobs = list() if(overmind) expand_probablity *= overmind.blob_type.spread_modifier if(overmind.blob_type.slow_spread_with_size) - expand_probablity /= (blobs.len / 10) + expand_probablity /= (GLOB.all_blobs.len / 10) if(distance <= expand_range) var/can_expand = TRUE diff --git a/code/modules/blob2/blobs/core.dm b/code/modules/blob2/blobs/core.dm index f43ec7ad6fd..84092ac566b 100644 --- a/code/modules/blob2/blobs/core.dm +++ b/code/modules/blob2/blobs/core.dm @@ -12,6 +12,7 @@ var/list/blob_cores = list() health_regen = 0 //we regen in Life() instead of when pulsed var/datum/blob_type/desired_blob_type = null // If this is set, the core always creates an overmind possessing this blob type. var/difficulty_threshold = null // Otherwise if this is set, it picks a random blob_type that is equal or lower in difficulty. + var/difficulty_floor = null // Related to the above var, acts as a floor value to the above, inclusive. var/core_regen = 2 var/overmind_get_delay = 0 //we don't want to constantly try to find an overmind, this var tracks when we'll try to get an overmind again var/resource_delay = 0 @@ -23,14 +24,18 @@ var/list/blob_cores = list() ai_controlled = FALSE // Spawn these if you want a semi-random blob. +// Can give a random easy blob. /obj/structure/blob/core/random_easy difficulty_threshold = BLOB_DIFFICULTY_EASY +// Can give a random easy or medium blob. /obj/structure/blob/core/random_medium difficulty_threshold = BLOB_DIFFICULTY_MEDIUM +// Can give a random medium or hard blob. /obj/structure/blob/core/random_hard difficulty_threshold = BLOB_DIFFICULTY_HARD + difficulty_floor = BLOB_DIFFICULTY_MEDIUM // Spawn these if you want a specific blob. /obj/structure/blob/core/blazing_oil @@ -81,8 +86,8 @@ var/list/blob_cores = list() /obj/structure/blob/core/classic desired_blob_type = /datum/blob_type/classic -/obj/structure/blob/core/New(var/newloc, var/client/new_overmind = null, new_rate = 2, placed = 0) - ..(newloc) +/obj/structure/blob/core/Initialize(newloc, client/new_overmind = null, new_rate = 2, placed = 0) + . = ..(newloc) blob_cores += src START_PROCESSING(SSobj, src) update_icon() //so it atleast appears @@ -180,7 +185,9 @@ var/list/blob_cores = list() var/list/valid_types = list() for(var/thing in subtypesof(/datum/blob_type)) var/datum/blob_type/BT = thing - if(initial(BT.difficulty) > difficulty_threshold) + if(initial(BT.difficulty) > difficulty_threshold) // Too hard. + continue + if(initial(BT.difficulty) < difficulty_floor) // Too easy. continue valid_types += BT return pick(valid_types) \ No newline at end of file diff --git a/code/modules/blob2/blobs/resource.dm b/code/modules/blob2/blobs/resource.dm index 189abfd520d..0cd3f06f10a 100644 --- a/code/modules/blob2/blobs/resource.dm +++ b/code/modules/blob2/blobs/resource.dm @@ -9,10 +9,10 @@ var/resource_delay = 0 var/resource_cooldown = 4 SECONDS -/obj/structure/blob/resource/New(var/newloc, var/new_overmind) - ..(newloc, new_overmind) +/obj/structure/blob/resource/Initialize(newloc, new_overmind) if(overmind) overmind.resource_blobs += src + return ..() /obj/structure/blob/resource/Destroy() if(overmind) diff --git a/code/modules/blob2/overmind/overmind.dm b/code/modules/blob2/overmind/overmind.dm index 223cb41e605..ffcd347dd81 100644 --- a/code/modules/blob2/overmind/overmind.dm +++ b/code/modules/blob2/overmind/overmind.dm @@ -23,7 +23,7 @@ var/list/overminds = list() var/ai_controlled = TRUE var/auto_pilot = FALSE // If true, and if a client is attached, the AI routine will continue running. -/mob/observer/blob/New(var/newloc, pre_placed = 0, starting_points = 60, desired_blob_type = null) +/mob/observer/blob/Initialize(newloc, pre_placed = 0, starting_points = 60, desired_blob_type = null) blob_points = starting_points if(pre_placed) //we already have a core! placed = 1 @@ -40,12 +40,11 @@ var/list/overminds = list() color = blob_type.complementary_color if(blob_core) blob_core.update_icon() - level_seven_blob_announcement(blob_core) - ..(newloc) + return ..(newloc) /mob/observer/blob/Destroy() - for(var/BL in blobs) + for(var/BL in GLOB.all_blobs) var/obj/structure/blob/B = BL if(B && B.overmind == src) B.overmind = null @@ -66,7 +65,7 @@ var/list/overminds = list() if(blob_core) stat(null, "Core Health: [blob_core.integrity]") stat(null, "Power Stored: [blob_points]/[max_blob_points]") - stat(null, "Total Blobs: [blobs.len]") + stat(null, "Total Blobs: [GLOB.all_blobs.len]") /mob/observer/blob/Move(NewLoc, Dir = 0) if(placed) diff --git a/code/modules/blob2/overmind/powers.dm b/code/modules/blob2/overmind/powers.dm index 360bfbb10b2..f5727e94bfe 100644 --- a/code/modules/blob2/overmind/powers.dm +++ b/code/modules/blob2/overmind/powers.dm @@ -22,6 +22,9 @@ to_chat(src, "There is no blob here!") return + if(B.overmind != src) + to_chat(src, span("warning", "This blob isn't controlled by you.")) + if(!istype(B, /obj/structure/blob/normal)) to_chat(src, "Unable to use this blob, find a normal one.") return @@ -66,7 +69,7 @@ return FALSE var/obj/structure/blob/B = null - var/list/potential_blobs = blobs.Copy() + var/list/potential_blobs = GLOB.all_blobs.Copy() while(potential_blobs.len) var/obj/structure/blob/temp = pick(potential_blobs) if(!(locate(/obj/structure/blob/node) in range(temp, BLOB_NODE_PULSE_RANGE) ) && !(locate(/obj/structure/blob/core) in range(temp, BLOB_CORE_PULSE_RANGE) )) @@ -77,6 +80,8 @@ potential_blobs -= temp // Don't take up the core's shield spot. else if(!istype(temp, /obj/structure/blob/normal)) potential_blobs -= temp // Not a normal blob. + else if(temp.overmind != src) + potential_blobs -= temp // Not our blob. else B = temp break @@ -107,7 +112,7 @@ return FALSE var/obj/structure/blob/B = null - var/list/potential_blobs = blobs.Copy() + var/list/potential_blobs = GLOB.all_blobs.Copy() while(potential_blobs.len) var/obj/structure/blob/temp = pick(potential_blobs) if(!(locate(/obj/structure/blob/node) in range(temp, BLOB_NODE_PULSE_RANGE) ) && !(locate(/obj/structure/blob/core) in range(temp, BLOB_CORE_PULSE_RANGE) )) @@ -118,6 +123,8 @@ potential_blobs -= temp // Don't take up the core's shield spot. else if(!istype(temp, /obj/structure/blob/normal)) potential_blobs -= temp // Not a normal blob. + else if(temp.overmind != src) + potential_blobs -= temp // Not our blob. else B = temp break @@ -149,7 +156,7 @@ return FALSE var/obj/structure/blob/B = null - var/list/potential_blobs = blobs.Copy() + var/list/potential_blobs = GLOB.all_blobs.Copy() while(potential_blobs.len) var/obj/structure/blob/temp = pick(potential_blobs) if(locate(/obj/structure/blob/node) in range(temp, 5) ) @@ -158,6 +165,8 @@ potential_blobs -= temp else if(!istype(temp, /obj/structure/blob/normal)) potential_blobs -= temp + else if(temp.overmind != src) + potential_blobs -= temp // Not our blob. else B = temp break @@ -184,7 +193,7 @@ other_T = get_step(T, direction) if(other_T) B = locate(/obj/structure/blob) in other_T - if(B) + if(B && B.overmind == src) break if(!B) @@ -216,7 +225,7 @@ for(var/direction in cardinal) var/turf/T = get_step(L, direction) B = locate(/obj/structure/blob) in T - if(B) + if(B && B.overmind == src) break if(!B) continue diff --git a/code/modules/events/event.dm b/code/modules/events/event.dm index 33c211485c9..c59145173ae 100644 --- a/code/modules/events/event.dm +++ b/code/modules/events/event.dm @@ -137,27 +137,29 @@ activeFor++ //Called when start(), announce() and end() has all been called. -/datum/event/proc/kill() +/datum/event/proc/kill(external_use = FALSE) // If this event was forcefully killed run end() for individual cleanup if(isRunning) isRunning = 0 end() endedAt = world.time - SSevents.event_complete(src) + if(!external_use) + SSevents.event_complete(src) //Called during building of skybox to get overlays /datum/event/proc/get_skybox_image() return -/datum/event/New(var/datum/event_meta/EM) +/datum/event/New(var/datum/event_meta/EM, external_use = FALSE) // event needs to be responsible for this, as stuff like APLUs currently make their own events for curious reasons - SSevents.active_events += src + if(!external_use) + SSevents.active_events += src - event_meta = EM - severity = event_meta.severity - if(severity < EVENT_LEVEL_MUNDANE) severity = EVENT_LEVEL_MUNDANE - if(severity > EVENT_LEVEL_MAJOR) severity = EVENT_LEVEL_MAJOR + event_meta = EM + severity = event_meta.severity + if(severity < EVENT_LEVEL_MUNDANE) severity = EVENT_LEVEL_MUNDANE + if(severity > EVENT_LEVEL_MAJOR) severity = EVENT_LEVEL_MAJOR startedAt = world.time diff --git a/code/modules/events/event_dynamic.dm b/code/modules/events/event_dynamic.dm index c1b44720218..e391637f4f7 100644 --- a/code/modules/events/event_dynamic.dm +++ b/code/modules/events/event_dynamic.dm @@ -52,7 +52,7 @@ var/list/event_last_fired = list() possibleEvents[/datum/event/pda_spam] = max(min(25, player_list.len) * 4, 200) possibleEvents[/datum/event/money_lotto] = max(min(5, player_list.len), 50) - if(account_hack_attempted) + if(GLOB.account_hack_attempted) possibleEvents[/datum/event/money_hacker] = max(min(25, player_list.len) * 4, 200) diff --git a/code/modules/events/money_hacker.dm b/code/modules/events/money_hacker.dm index 0e604b9956a..025bcc6901f 100644 --- a/code/modules/events/money_hacker.dm +++ b/code/modules/events/money_hacker.dm @@ -1,5 +1,7 @@ //var/global/account_hack_attempted = 0 +GLOBAL_VAR_INIT(account_hack_attempted, 0) + /datum/event/money_hacker var/datum/money_account/affected_account endWhen = 100 @@ -10,7 +12,7 @@ if(all_money_accounts.len) affected_account = pick(all_money_accounts) - account_hack_attempted = 1 + GLOB.account_hack_attempted = 1 else kill() diff --git a/code/modules/gamemaster/actions/action.dm b/code/modules/gamemaster/actions/action.dm deleted file mode 100644 index f8ea16994ec..00000000000 --- a/code/modules/gamemaster/actions/action.dm +++ /dev/null @@ -1,27 +0,0 @@ -/datum/gm_action - var/name = "no name" // Simple name, for organization. - var/enabled = TRUE // If not enabled, this action is never taken. - var/departments = list() // What kinds of departments are affected by this action. Multiple departments can be listed. - var/chaotic = 0 // A number showing how chaotic the action may be. If danger is high, the GM will avoid it. - var/reusable = FALSE // If true, the event does not become disabled upon being used. Should be used sparingly. - var/observers_used = FALSE // Determines if the GM should check if ghosts are available before using this. - var/length = 0 // Determines how long the event lasts, until end() is called. - var/datum/game_master/gm = null - var/severity = 1 // The severity of the action. This is here to prevent continued future defining of this var on actions, un-used. - -/datum/gm_action/proc/set_up() - return - -/datum/gm_action/proc/get_weight() - return - -/datum/gm_action/proc/start() - if(!reusable) - enabled = FALSE - return - -/datum/gm_action/proc/end() - return - -/datum/gm_action/proc/announce() - return \ No newline at end of file diff --git a/code/modules/gamemaster/actions/atmos_leak.dm b/code/modules/gamemaster/actions/atmos_leak.dm deleted file mode 100644 index e40ed48c37f..00000000000 --- a/code/modules/gamemaster/actions/atmos_leak.dm +++ /dev/null @@ -1,77 +0,0 @@ -/datum/gm_action/atmos_leak - name = "atmospherics leak" - departments = list(DEPARTMENT_ENGINEERING, DEPARTMENT_SYNTHETIC) - var/area/target_area // Chosen target area - var/area/target_turf // Chosen target turf in target_area - var/gas_type // Chosen gas to release - // Exclude these types and sub-types from targeting eligibilty - var/list/area/excluded = list( - /area/submap, - /area/shuttle, - /area/crew_quarters, - /area/holodeck, - /area/engineering/engine_room - ) - - severity - -// Decide which area will be targeted! -/datum/gm_action/atmos_leak/set_up() - severity = pickweight(EVENT_LEVEL_MUNDANE = 8, - EVENT_LEVEL_MODERATE = 5, - EVENT_LEVEL_MAJOR = 3 - ) - - var/gas_choices = list("carbon_dioxide", "sleeping_agent") // Annoying - if(severity >= EVENT_LEVEL_MODERATE) - gas_choices += "phoron" // Dangerous - if(severity >= EVENT_LEVEL_MAJOR) - gas_choices += "volatile_fuel" // Dangerous and no default atmos setup! - gas_type = pick(gas_choices) - - var/list/area/grand_list_of_areas = get_station_areas(excluded) - - // Okay, now lets try and pick a target! Lets try 10 times, otherwise give up - for(var/i in 1 to 10) - var/area/A = pick(grand_list_of_areas) - if(is_area_occupied(A)) - log_debug("atmos_leak event: Rejected [A] because it is occupied.") - continue - // A good area, great! Lets try and pick a turf - var/list/turfs = list() - for(var/turf/simulated/floor/F in A) - if(turf_clear(F)) - turfs += F - if(turfs.len == 0) - log_debug("atmos_leak event: Rejected [A] because it has no clear turfs.") - continue - target_area = A - target_turf = pick(turfs) - - // If we can't find a good target, give up - if(!target_area) - log_debug("atmos_leak event: Giving up after too many failures to pick target area") - return - -/datum/gm_action/atmos_leak/announce() - if(target_area) - command_announcement.Announce("Warning, hazardous [gas_data.name[gas_type]] gas leak detected in \the [target_area], evacuate the area.", "Hazard Alert") - -/datum/gm_action/atmos_leak/start() - if(!target_turf) - return - ..() - spawn(rand(0, 600)) - // Okay, time to actually put the gas in the room! - // TODO - Would be nice to break a waste pipe perhaps? - // TODO - Maybe having it released from a single point and thus causing airflow to blow stuff around - - // Fow now just add a bunch of it to the air - var/datum/gas_mixture/air_contents = new - air_contents.temperature = T20C + ((severity - 1) * rand(-50, 50)) - air_contents.gas[gas_type] = 10 * MOLES_CELLSTANDARD - target_turf.assume_air(air_contents) - playsound(target_turf, 'sound/effects/smoke.ogg', 50, 1) - -/datum/gm_action/atmos_leak/get_weight() - return 15 + (metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 10 + metric.count_people_in_department(DEPARTMENT_SYNTHETIC) * 30) // Synthetics are counted in higher value because they can wirelessly connect to alarms. diff --git a/code/modules/gamemaster/actions/blob.dm b/code/modules/gamemaster/actions/blob.dm deleted file mode 100644 index dd9939a11de..00000000000 --- a/code/modules/gamemaster/actions/blob.dm +++ /dev/null @@ -1,74 +0,0 @@ -/datum/gm_action/blob - name = "blob infestation" - departments = list(DEPARTMENT_ENGINEERING, DEPARTMENT_SECURITY, DEPARTMENT_MEDICAL) - chaotic = 25 - - var/list/area/excluded = list( - /area/submap, - /area/shuttle, - /area/crew_quarters, - /area/holodeck, - /area/engineering/engine_room - ) - - var/area/target_area // Chosen target area - var/turf/target_turf // Chosen target turf in target_area - - var/obj/structure/blob/core/Blob - var/spawn_blob_type = /obj/structure/blob/core/random_medium - -/datum/gm_action/blob/set_up() - severity = pickweight(EVENT_LEVEL_MUNDANE = 4, - EVENT_LEVEL_MODERATE = 2, - EVENT_LEVEL_MAJOR = 1 - ) - - var/list/area/grand_list_of_areas = get_station_areas(excluded) - - for(var/i in 1 to 10) - var/area/A = pick(grand_list_of_areas) - if(is_area_occupied(A)) - log_debug("Blob infestation event: Rejected [A] because it is occupied.") - continue - var/list/turfs = list() - for(var/turf/simulated/floor/F in A) - if(turf_clear(F)) - turfs += F - if(turfs.len == 0) - log_debug("Blob infestation event: Rejected [A] because it has no clear turfs.") - continue - target_area = A - target_turf = pick(turfs) - - if(!target_area) - log_debug("Blob infestation event: Giving up after too many failures to pick target area") - -/datum/gm_action/blob/start() - ..() - var/turf/T - - if(severity == EVENT_LEVEL_MUNDANE || !target_area || !target_turf) - T = pick(blobstart) - else if(severity == EVENT_LEVEL_MODERATE) - T = target_turf - else - T = target_turf - spawn_blob_type = /obj/structure/blob/core/random_hard - - Blob = new spawn_blob_type(T) - -/datum/gm_action/blob/announce() - spawn(rand(600, 3000)) // 1-5 minute leeway for the blob to go un-detected. - command_announcement.Announce("Confirmed outbreak of level 7 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", new_sound = 'sound/AI/outbreak7.ogg') - -/datum/gm_action/blob/get_weight() - var/engineers = metric.count_people_in_department(DEPARTMENT_ENGINEERING) - var/security = metric.count_people_in_department(DEPARTMENT_SECURITY) - var/medical = metric.count_people_in_department(DEPARTMENT_MEDICAL) - - var/assigned_staff = engineers + security - if(engineers || security) // Medical only counts if one of the other two exists, and even then they count as half. - assigned_staff += round(medical / 2) - - var/weight = (max(assigned_staff - 2, 0) * 20) // An assigned staff count of 2 must be had to spawn a blob. - return weight diff --git a/code/modules/gamemaster/actions/brand_intelligence.dm b/code/modules/gamemaster/actions/brand_intelligence.dm deleted file mode 100644 index 28ed6a9928a..00000000000 --- a/code/modules/gamemaster/actions/brand_intelligence.dm +++ /dev/null @@ -1,69 +0,0 @@ -/datum/gm_action/brand_intelligence - name = "rampant vending machines" - length = 30 MINUTES - departments = list(DEPARTMENT_ENGINEERING, DEPARTMENT_EVERYONE) - - var/list/obj/machinery/vending/vendingMachines = list() - var/list/obj/machinery/vending/infectedVendingMachines = list() - var/obj/machinery/vending/originMachine - var/start_time = 0 - - var/active = FALSE // Are we currently infecting? - -/datum/gm_action/brand_intelligence/announce() - if(prob(90)) - command_announcement.Announce("An ongoing mass upload of malware for vendors has been detected onboard [station_name()], which appears to transmit \ - to other nearby vendors. The original infected machine is believed to be \a [originMachine.name].", "Vendor Service Alert") - -/datum/gm_action/brand_intelligence/set_up() - vendingMachines.Cut() - infectedVendingMachines.Cut() - - for(var/obj/machinery/vending/V in machines) - if(isNotStationLevel(V.z)) continue - vendingMachines.Add(V) - - if(!vendingMachines.len) - length = 0 - return - - originMachine = pick(vendingMachines) - vendingMachines.Remove(originMachine) - originMachine.shut_up = 0 - originMachine.shoot_inventory = 1 - - start_time = world.time - active = TRUE - -/datum/gm_action/brand_intelligence/start() - ..() - while(originMachine || active) - if(!vendingMachines.len || !originMachine || originMachine.shut_up) //if every machine is infected, or if the original vending machine is missing or has it's voice switch flipped - end() - return - - if(ISMULTIPLE(world.time - start_time, 5)) - if(prob(15)) - var/obj/machinery/vending/infectedMachine = pick(vendingMachines) - vendingMachines.Remove(infectedMachine) - infectedVendingMachines.Add(infectedMachine) - infectedMachine.shut_up = 0 - infectedMachine.shoot_inventory = 1 - - if(ISMULTIPLE(world.time - start_time, 12)) - originMachine.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/gm_action/brand_intelligence/end() - active = FALSE - for(var/obj/machinery/vending/infectedMachine in infectedVendingMachines) - infectedMachine.shut_up = 1 - infectedMachine.shoot_inventory = 0 - -/datum/gm_action/brand_intelligence/get_weight() - return 60 + (metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 20) diff --git a/code/modules/gamemaster/actions/camera_damage.dm b/code/modules/gamemaster/actions/camera_damage.dm deleted file mode 100644 index 10ccb510ce8..00000000000 --- a/code/modules/gamemaster/actions/camera_damage.dm +++ /dev/null @@ -1,52 +0,0 @@ -/datum/gm_action/camera_damage - name = "random camera damage" - reusable = TRUE - departments = list(DEPARTMENT_SYNTHETIC, DEPARTMENT_ENGINEERING) - -/datum/gm_action/camera_damage/start() - var/obj/machinery/camera/C = acquire_random_camera() - if(!C) - return - ..() - - var/severity_range = 0 - severity = pickweight(EVENT_LEVEL_MUNDANE = 10, - EVENT_LEVEL_MODERATE = 5, - EVENT_LEVEL_MAJOR = 1 - ) - - switch(severity) - if(EVENT_LEVEL_MUNDANE) - severity_range = 0 - if(EVENT_LEVEL_MODERATE) - severity_range = 7 - if(EVENT_LEVEL_MAJOR) - severity_range = 15 - - for(var/obj/machinery/camera/cam in range(severity_range,C)) - if(is_valid_camera(cam)) - if(prob(2*severity)) - cam.destroy() - else - cam.wires.UpdateCut(CAMERA_WIRE_POWER, 0) - if(prob(5*severity)) - cam.wires.UpdateCut(CAMERA_WIRE_ALARM, 0) - -/datum/gm_action/camera_damage/proc/acquire_random_camera(var/remaining_attempts = 5) - if(!cameranet.cameras.len) - return - if(!remaining_attempts) - return - - var/obj/machinery/camera/C = pick(cameranet.cameras) - if(is_valid_camera(C)) - return C - return acquire_random_camera(remaining_attempts--) - -/datum/gm_action/camera_damage/proc/is_valid_camera(var/obj/machinery/camera/C) - // Only return a functional camera, not installed in a silicon/hardsuit/circuit/etc, and that exists somewhere players have access - var/turf/T = get_turf(C) - return T && C.can_use() && istype(C.loc, /turf) && (T.z in using_map.player_levels) - -/datum/gm_action/camera_damage/get_weight() - return 40 + (metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 20) + (metric.count_people_in_department(DEPARTMENT_SYNTHETIC) * 40) diff --git a/code/modules/gamemaster/actions/carp_migration.dm b/code/modules/gamemaster/actions/carp_migration.dm deleted file mode 100644 index 137c2cccb96..00000000000 --- a/code/modules/gamemaster/actions/carp_migration.dm +++ /dev/null @@ -1,58 +0,0 @@ -//carp_migration -/datum/gm_action/carp_migration - name = "carp migration" - departments = list(DEPARTMENT_SECURITY, DEPARTMENT_EVERYONE) - chaotic = 50 - var/list/spawned_carp = list() - var/carp_amount = 0 - length = 20 MINUTES - -/datum/gm_action/carp_migration/get_weight() - return 50 + (metric.count_people_in_department(DEPARTMENT_SECURITY) * 10) + (metric.count_all_space_mobs() * 20) - -/datum/gm_action/carp_migration/announce() - var/announcement = "Unknown biological entities have been detected near [station_name()], please stand-by." - command_announcement.Announce(announcement, "Lifesign Alert") - -/datum/gm_action/carp_migration/set_up() - // Higher filled roles means more groups of fish. - var/station_strength = 0 - station_strength += (metric.count_people_in_department(DEPARTMENT_SECURITY) * 3) - station_strength += (metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 2) - station_strength += metric.count_people_in_department(DEPARTMENT_MEDICAL) - - // Less active emergency response departments tones the event down. - var/activeness = ((metric.assess_department(DEPARTMENT_SECURITY) + metric.assess_department(DEPARTMENT_ENGINEERING) + metric.assess_department(DEPARTMENT_MEDICAL)) / 3) - activeness = max(activeness, 20) - - carp_amount = CEILING(station_strength * (activeness / 100) + 1, 1) - -/datum/gm_action/carp_migration/start() - ..() - var/list/spawn_locations = list() - - var/group_size_min = 3 - var/group_size_max = 5 - - for(var/obj/effect/landmark/C in landmarks_list) - if(C.name == "carpspawn") - spawn_locations.Add(C.loc) - - spawn_locations = shuffle(spawn_locations) - carp_amount = min(carp_amount, spawn_locations.len) - - var/i = 1 - while (i <= carp_amount) - var/group_size = rand(group_size_min, group_size_max) - for (var/j = 1, j <= group_size, j++) - spawned_carp.Add(new /mob/living/simple_mob/animal/space/carp/event(spawn_locations[i])) - i++ - message_admins("[spawned_carp.len] carp spawned by event.") - -/datum/gm_action/carp_migration/end() - for(var/mob/living/simple_mob/animal/space/carp/C in spawned_carp) - if(!C.stat) - var/turf/T = get_turf(C) - if(istype(T, /turf/space)) - if(!prob(25)) - qdel(C) \ No newline at end of file diff --git a/code/modules/gamemaster/actions/comms_blackout.dm b/code/modules/gamemaster/actions/comms_blackout.dm deleted file mode 100644 index 353a3bd92b6..00000000000 --- a/code/modules/gamemaster/actions/comms_blackout.dm +++ /dev/null @@ -1,21 +0,0 @@ -/datum/gm_action/comms_blackout - name = "communications blackout" - departments = list(DEPARTMENT_ENGINEERING, DEPARTMENT_EVERYONE) - chaotic = 35 - -/datum/gm_action/comms_blackout/get_weight() - return 50 + (metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 40) - -/datum/gm_action/comms_blackout/announce() - if(prob(33)) - command_announcement.Announce("Ionospheric anomalies detected. Temporary telecommunication failure imminent. Please contact you-BZZT", 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, "
") - -/datum/gm_action/comms_blackout/start() - ..() - for(var/obj/machinery/telecomms/T in telecomms_list) - T.emp_act(1) diff --git a/code/modules/gamemaster/actions/drill_announcement.dm b/code/modules/gamemaster/actions/drill_announcement.dm deleted file mode 100644 index 47f44e2caf1..00000000000 --- a/code/modules/gamemaster/actions/drill_announcement.dm +++ /dev/null @@ -1,10 +0,0 @@ -/datum/gm_action/security_drill - name = "security drills" - departments = list(DEPARTMENT_SECURITY, DEPARTMENT_EVERYONE) - -/datum/gm_action/security_drill/announce() - spawn(rand(1 MINUTE, 2 MINUTES)) - command_announcement.Announce("[pick("A NanoTrasen security director", "A Vir-Gov correspondant", "Local Sif authoritiy")] has advised the enactment of [pick("a rampant wildlife", "a fire", "a hostile boarding", "a nonstandard", "a bomb", "an emergent intelligence")] drill with the personnel onboard \the [station_name()].", "Security Advisement") - -/datum/gm_action/security_drill/get_weight() - return max(-20, 10 + gm.staleness - (gm.danger * 2)) + (metric.count_people_in_department(DEPARTMENT_SECURITY) * 5) + (metric.count_people_in_department(DEPARTMENT_EVERYONE) * 1.5) diff --git a/code/modules/gamemaster/actions/dust.dm b/code/modules/gamemaster/actions/dust.dm deleted file mode 100644 index 49d0a33b95a..00000000000 --- a/code/modules/gamemaster/actions/dust.dm +++ /dev/null @@ -1,17 +0,0 @@ -/datum/gm_action/dust - name = "dust" - departments = list(DEPARTMENT_ENGINEERING) - chaotic = 10 - reusable = TRUE - -/datum/gm_action/dust/announce() - command_announcement.Announce("Debris resulting from activity on another nearby asteroid is approaching your colony.", "Dust Alert") - -/datum/gm_action/dust/get_weight() - var/engineers = metric.count_people_in_department(DEPARTMENT_ENGINEERING) - var/weight = 30 + (engineers * 25) - return weight - -/datum/gm_action/dust/start() - ..() - dust_swarm("norm") diff --git a/code/modules/gamemaster/actions/electrical_storm.dm b/code/modules/gamemaster/actions/electrical_storm.dm deleted file mode 100644 index 4cd4c39df63..00000000000 --- a/code/modules/gamemaster/actions/electrical_storm.dm +++ /dev/null @@ -1,33 +0,0 @@ -/datum/gm_action/electrical_storm - name = "electrical storm" - departments = list(DEPARTMENT_EVERYONE) - reusable = TRUE - var/lightsoutAmount = 1 - var/lightsoutRange = 25 - -/datum/gm_action/electrical_storm/announce() - command_announcement.Announce("An electrical issue has been detected in your area, please repair potential electronic overloads.", "Electrical Alert") - -/datum/gm_action/electrical_storm/start() - ..() - var/list/epicentreList = list() - - for(var/i=1, i <= lightsoutAmount, i++) - var/list/possibleEpicentres = list() - for(var/obj/effect/landmark/newEpicentre in landmarks_list) - if(newEpicentre.name == "lightsout" && !(newEpicentre in epicentreList)) - possibleEpicentres += newEpicentre - if(possibleEpicentres.len) - epicentreList += pick(possibleEpicentres) - else - break - - if(!epicentreList.len) - return - - for(var/obj/effect/landmark/epicentre in epicentreList) - for(var/obj/machinery/power/apc/apc in range(epicentre,lightsoutRange)) - apc.overload_lighting() - -/datum/gm_action/electrical_storm/get_weight() - return 30 + (metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 15) + (metric.count_people_in_department(DEPARTMENT_EVERYONE) * 5) diff --git a/code/modules/gamemaster/actions/electrified_door.dm b/code/modules/gamemaster/actions/electrified_door.dm deleted file mode 100644 index 213c0c02928..00000000000 --- a/code/modules/gamemaster/actions/electrified_door.dm +++ /dev/null @@ -1,75 +0,0 @@ -/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) diff --git a/code/modules/gamemaster/actions/gravity.dm b/code/modules/gamemaster/actions/gravity.dm deleted file mode 100644 index f568de9eae2..00000000000 --- a/code/modules/gamemaster/actions/gravity.dm +++ /dev/null @@ -1,36 +0,0 @@ -/datum/gm_action/gravity - name = "gravity failure" - departments = list(DEPARTMENT_EVERYONE) - length = 600 - var/list/zLevels - -/datum/gm_action/gravity/set_up() - length = rand(length, length * 5) - // Setup which levels we will disrupt gravit on. - zLevels = using_map.station_levels.Copy() - for(var/datum/planet/P in SSplanets.planets) - zLevels -= P.expected_z_levels - -/datum/gm_action/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/gm_action/gravity/start() - ..() - gravity_is_on = 0 - for(var/area/A in all_areas) - if(A.z in zLevels) - A.gravitychange(gravity_is_on) - -/datum/gm_action/gravity/end() - if(!gravity_is_on) - gravity_is_on = 1 - - for(var/area/A in all_areas) - if(A.z in zLevels) - A.gravitychange(gravity_is_on) - - command_announcement.Announce("Gravity generators are again functioning within normal parameters. Sorry for any inconvenience.", "Gravity Restored") - -/datum/gm_action/gravity/get_weight() - return 30 + (metric.count_people_in_department(DEPARTMENT_EVERYONE) * 20) diff --git a/code/modules/gamemaster/actions/grid_check.dm b/code/modules/gamemaster/actions/grid_check.dm deleted file mode 100644 index ddd0cd923e1..00000000000 --- a/code/modules/gamemaster/actions/grid_check.dm +++ /dev/null @@ -1,23 +0,0 @@ -// New grid check event: -// Very similar to the old one, power goes out in most of the colony, however the new feature is the ability for engineering to -// get power back on sooner, if they are able to reach a special machine and initiate a manual reboot. If no one is able to do so, -// it will reboot itself after a few minutes, just like the old one. Bad things happen if there is no grid checker machine protecting -// the powernet when this event fires. - -/datum/gm_action/grid_check - name = "grid check" - departments = list(DEPARTMENT_ENGINEERING, DEPARTMENT_EVERYONE) - chaotic = 20 - -/datum/gm_action/grid_check/get_weight() - return 50 + (metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 30) - -/datum/gm_action/grid_check/start() - ..() - // This sets off a chain of events that lead to the actual grid check (or perhaps worse). - // First, the Supermatter engine makes a power spike. - for(var/obj/machinery/power/generator/engine in machines) - engine.power_spike() - break // Just one engine, please. - // After that, the engine checks if a grid checker exists on the same powernet, and if so, it triggers a blackout. - // If not, lots of stuff breaks. See code/modules/power/generator.dm for that piece of code. \ No newline at end of file diff --git a/code/modules/gamemaster/actions/infestation.dm b/code/modules/gamemaster/actions/infestation.dm deleted file mode 100644 index a887a3b5d39..00000000000 --- a/code/modules/gamemaster/actions/infestation.dm +++ /dev/null @@ -1,116 +0,0 @@ -#define LOC_KITCHEN 0 -#define LOC_ATMOS 1 -#define LOC_CHAPEL 2 -#define LOC_LIBRARY 3 -#define LOC_HYDRO 4 -#define LOC_VAULT 5 -#define LOC_CONSTR 6 -#define LOC_TECH 7 -#define LOC_GARDEN 8 - -#define VERM_MICE 0 -#define VERM_LIZARDS 1 -#define VERM_SPIDERS 2 - -/datum/gm_action/infestation - name = "animal infestation" - departments = list(DEPARTMENT_EVERYONE) - var/location - var/locstring - var/vermin - var/vermstring - - var/list/turf/simulated/floor/turfs = list() - - var/spawn_types - var/max_number - -/datum/gm_action/infestation/set_up() - location = rand(0,8) - turfs.Cut() - var/spawn_area_type - switch(location) - if(LOC_KITCHEN) - spawn_area_type = /area/crew_quarters/kitchen - locstring = "the kitchen" - if(LOC_ATMOS) - spawn_area_type = /area/engineering/atmos - locstring = "atmospherics" - if(LOC_CHAPEL) - spawn_area_type = /area/chapel/main - locstring = "the chapel" - if(LOC_LIBRARY) - spawn_area_type = /area/library - locstring = "the library" - if(LOC_HYDRO) - spawn_area_type = /area/hydroponics - locstring = "hydroponics" - if(LOC_VAULT) - spawn_area_type = /area/security/nuke_storage - locstring = "the vault" - if(LOC_CONSTR) - spawn_area_type = /area/construction - locstring = "the construction area" - if(LOC_TECH) - spawn_area_type = /area/storage/tech - locstring = "technical storage" - if(LOC_GARDEN) - spawn_area_type = /area/hydroponics/garden - locstring = "the public garden" - - for(var/areapath in typesof(spawn_area_type)) - var/area/A = locate(areapath) - for(var/turf/simulated/floor/F in A.contents) - if(turf_clear(F)) - turfs += F - - spawn_types = list() - max_number = 0 - vermin = rand(0,2) - switch(vermin) - if(VERM_MICE) - spawn_types = 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) - max_number = 12 - vermstring = "mice" - if(VERM_LIZARDS) - spawn_types = list(/mob/living/simple_mob/animal/passive/lizard, /mob/living/simple_mob/animal/passive/lizard, /mob/living/simple_mob/animal/passive/lizard/large, /mob/living/simple_mob/animal/passive/lizard/large/defensive) - max_number = 6 - vermstring = "lizards" - if(VERM_SPIDERS) - spawn_types = list(/obj/effect/spider/spiderling) - max_number = 3 - vermstring = "spiders" - -/datum/gm_action/infestation/start() - spawn() - var/num = rand(2,max_number) - while(turfs.len > 0 && num > 0) - var/turf/simulated/floor/T = pick(turfs) - turfs.Remove(T) - num-- - - if(vermin == VERM_SPIDERS) - var/obj/effect/spider/spiderling/S = new(T) - S.amount_grown = -1 - else - var/spawn_type = pick(spawn_types) - new spawn_type(T) - -/datum/gm_action/infestation/announce() - command_announcement.Announce("Bioscans indicate that [vermstring] have been breeding in [locstring]. Clear them out, before this starts to affect productivity.", "Vermin infestation") - -/datum/gm_action/infestation/get_weight() - return 5 + (metric.count_people_in_department(DEPARTMENT_EVERYONE) * 20) - -#undef LOC_KITCHEN -#undef LOC_ATMOS -#undef LOC_CHAPEL -#undef LOC_LIBRARY -#undef LOC_HYDRO -#undef LOC_VAULT -#undef LOC_TECH -#undef LOC_GARDEN - -#undef VERM_MICE -#undef VERM_LIZARDS -#undef VERM_SPIDERS \ No newline at end of file diff --git a/code/modules/gamemaster/actions/ion_storm.dm b/code/modules/gamemaster/actions/ion_storm.dm deleted file mode 100644 index 5ff51ccbaa6..00000000000 --- a/code/modules/gamemaster/actions/ion_storm.dm +++ /dev/null @@ -1,50 +0,0 @@ -/datum/gm_action/ionstorm - name = "ion storm" - departments = list(DEPARTMENT_SYNTHETIC) - var/botEmagChance = 0.5 - var/list/players = list() - var/active = FALSE - length = 1 MINUTE - -/datum/gm_action/ionstorm/set_up() - length = rand(length, length * 10) -// command_alert("The station has entered an ion storm. Monitor all electronic equipment for malfunctions", "Anomaly Alert") - for (var/mob/living/carbon/human/player in player_list) - if( !player.mind || player_is_antag(player.mind, only_offstation_roles = 1) || player.client.inactivity > MinutesToTicks(10)) - continue - players += player.real_name - - for (var/mob/living/silicon/ai/target in silicon_mob_list) - var/law = target.generate_ion_law() - to_chat(target, "You have detected a change in your laws information:") - to_chat(target,law) - target.add_ion_law(law) - target.show_laws() - -/datum/gm_action/ionstorm/announce() - if(message_servers) - for (var/obj/machinery/message_server/MS in message_servers) - MS.spamfilter.Cut() - var/i - for (i = 1, i <= MS.spamfilter_limit, i++) - MS.spamfilter += pick("kitty","HONK","rev","malf","liberty","freedom","drugs", "[using_map.station_short]", \ - "admin","ponies","heresy","meow","Pun Pun","monkey","Ian","moron","pizza","message","spam",\ - "director", "Hello", "Hi!"," ","nuke","crate","dwarf","xeno") - -/datum/gm_action/ionstorm/start() - while(active) - sleep(1) - if(botEmagChance) - for(var/mob/living/bot/bot in mob_list) - if(prob(botEmagChance)) - bot.emag_act(1) - -/datum/gm_action/ionstorm/end() - spawn(rand(5000,8000)) - if(prob(50)) - ion_storm_announcement() - -/datum/gm_action/ionstorm/get_weight() - var/bots = metric.count_people_in_department(DEPARTMENT_SYNTHETIC) - var/weight = 5 + (bots * 20) - return weight diff --git a/code/modules/gamemaster/actions/manifest_malfunction.dm b/code/modules/gamemaster/actions/manifest_malfunction.dm deleted file mode 100644 index 2b924a41577..00000000000 --- a/code/modules/gamemaster/actions/manifest_malfunction.dm +++ /dev/null @@ -1,52 +0,0 @@ -/datum/gm_action/manifest_malfunction - name = "manifest malfunction" - enabled = TRUE - departments = list(DEPARTMENT_SECURITY, DEPARTMENT_SYNTHETIC, DEPARTMENT_EVERYONE) - chaotic = 3 - reusable = FALSE - length = 0 - - var/recordtype - -/datum/gm_action/manifest_malfunction/set_up() - severity = pickweight(EVENT_LEVEL_MUNDANE = 6, - EVENT_LEVEL_MODERATE = 2, - EVENT_LEVEL_MAJOR = 1 - ) - - recordtype = pickweight("medical" = 10,"security" = (severity * 15)) - - return - -/datum/gm_action/manifest_malfunction/get_weight() - . = -10 - - var/security = metric.count_people_in_department(DEPARTMENT_SECURITY) - - if(security && data_core) - . += (metric.count_people_in_department(DEPARTMENT_EVERYONE) * 5) - (metric.count_people_in_department(DEPARTMENT_SYNTHETIC) * 5) - - return . - -/datum/gm_action/manifest_malfunction/start() - ..() - - var/manifest_cut_count = 1 * severity - - for(var/I = 1 to manifest_cut_count) - var/datum/data/record/R - - switch(recordtype) - if("security") - R = pick(data_core.security) - - if("medical") - R = pick(data_core.medical) - - qdel(R) - -/datum/gm_action/manifest_malfunction/announce() - if(prob(30 * severity)) - spawn(rand(5 MINUTES, 10 MINUTES)) - command_announcement.Announce("An ongoing mass upload of malware for [recordtype] record cores has been detected onboard [station_name()]", "Data Breach Alert") - return diff --git a/code/modules/gamemaster/actions/meteor_defense.dm b/code/modules/gamemaster/actions/meteor_defense.dm deleted file mode 100644 index dc92413a82e..00000000000 --- a/code/modules/gamemaster/actions/meteor_defense.dm +++ /dev/null @@ -1,71 +0,0 @@ -// This event gives the station an advance warning about meteors, so that they can prepare in various ways. - -/datum/gm_action/meteor_defense - name = "meteor defense" - departments = list(DEPARTMENT_ENGINEERING, DEPARTMENT_CARGO) - chaotic = 50 - var/direction = null - var/dir_text = null - var/waves = 0 - - var/meteor_types - -/datum/gm_action/meteor_defense/get_weight() - var/engineers = metric.count_people_in_department(DEPARTMENT_ENGINEERING) - var/cargo = metric.count_people_in_department(DEPARTMENT_CARGO) - var/bots = metric.count_people_in_department(DEPARTMENT_SYNTHETIC) - var/weight = (max(engineers - 1, 0) * 20) // If only one engineer exists, no meteors for now. - - if(engineers >= 2) - weight += ((cargo - 1) * 10) - weight += (bots * 15) - - return weight - -/datum/gm_action/meteor_defense/set_up() - severity = pickweight(EVENT_LEVEL_MUNDANE = 10, - EVENT_LEVEL_MODERATE = 3 - ) - - switch(severity) - if(EVENT_LEVEL_MUNDANE) - meteor_types = meteors_threatening.Copy() - - if(EVENT_LEVEL_MODERATE) - meteor_types = meteors_catastrophic.Copy() - - direction = pick(cardinal) // alldirs doesn't work with current meteor code unfortunately. - waves = rand(5, 8) - switch(direction) - if(NORTH) - dir_text = "aft" // For some reason this is needed. - if(SOUTH) - dir_text = "fore" - if(EAST) - dir_text = "port" - if(WEST) - dir_text = "starboard" - -/datum/gm_action/meteor_defense/announce() - var/announcement = "Alert! Two asteroids have collided near [station_name()]. Chunks of it are expected to approach from the [dir_text] side. ETA to arrival is \ - approximately [round(5 * severity * 2)] minutes." - command_announcement.Announce(announcement, "Meteor Alert", new_sound = 'sound/AI/meteors.ogg') - -/datum/gm_action/meteor_defense/start() - ..() - spawn(0) - sleep(round(5 * severity) MINUTES) - var/announcement = "The incoming debris are expected to approach from the [dir_text] side. ETA to arrival is approximately [round(5 * severity)] minutes." - command_announcement.Announce(announcement, "Meteor Alert - Update") - sleep(round(5 * severity) MINUTES) - announcement = "Incoming debris approaches from the [dir_text] side!" - command_announcement.Announce(announcement, "Meteor Alert - Update") - while(waves) - message_admins("[waves] more wave\s of meteors remain.") - spawn(1) // Dir is reversed because the direction describes where meteors are going, not what side it's gonna hit. - spawn_meteors(rand(8, 12), meteors_threatening, reverse_dir[direction]) - waves-- - sleep(30 SECONDS) - announcement = "The station has cleared the incoming debris." - command_announcement.Announce(announcement, "Meteor Alert - Update") - message_admins("Meteor defense event has ended.") diff --git a/code/modules/gamemaster/actions/money_hacker.dm b/code/modules/gamemaster/actions/money_hacker.dm deleted file mode 100644 index 2705de450c7..00000000000 --- a/code/modules/gamemaster/actions/money_hacker.dm +++ /dev/null @@ -1,79 +0,0 @@ -/var/global/account_hack_attempted = 0 - -/datum/gm_action/money_hacker - name = "bank account hacker" - departments = list(DEPARTMENT_EVERYONE) - reusable = TRUE - var/datum/money_account/affected_account - var/active - var/activeFor - var/end_time - -/datum/gm_action/money_hacker/set_up() - active = TRUE - end_time = world.time + 6000 - if(all_money_accounts.len) - affected_account = pick(all_money_accounts) - - account_hack_attempted = 1 - -/datum/gm_action/money_hacker/announce() - var/message = "A brute force hack has been detected (in progress since [stationtime2text()]). The target of the attack is: Financial account #[affected_account.account_number], \ - without intervention this attack will succeed in approximately 10 minutes. Required intervention: temporary suspension of affected accounts until the attack has ceased. \ - Notifications will be sent as updates occur.
" - var/my_department = "[station_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) - - -/datum/gm_action/money_hacker/start() - ..() - spawn(0) - while(active) - sleep(1) - activeFor++ - if(world.time >= end_time) - length = activeFor - else - length = activeFor + 10 - -/datum/gm_action/money_hacker/end() - active = FALSE - var/message - if(affected_account && !affected_account.suspended) - //hacker wins - message = "The hack attempt has succeeded." - - //subtract the money - var/lost = affected_account.money * 0.8 + (rand(2,4) - 2) / 10 - affected_account.money -= lost - - //create a taunting log entry - var/datum/transaction/T = new() - T.target_name = pick("","yo brotha from anotha motha","el Presidente","chieF smackDowN","Nobody") - T.purpose = pick("Ne$ ---ount fu%ds init*&lisat@*n","PAY BACK YOUR MUM","Funds withdrawal","pWnAgE","l33t hax","liberationez","Hit","Nothing") - T.amount = pick("","([rand(0,99999)])","alla money","9001$","HOLLA HOLLA GET DOLLA","([lost])") - 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") - - affected_account.transaction_log.Add(T) - - else - //crew wins - message = "The attack has ceased, the affected accounts can now be brought online." - - var/my_department = "[station_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) - -/datum/gm_action/money_hacker/get_weight() - return 30 * all_money_accounts.len diff --git a/code/modules/gamemaster/actions/money_lotto.dm b/code/modules/gamemaster/actions/money_lotto.dm deleted file mode 100644 index ea82510e571..00000000000 --- a/code/modules/gamemaster/actions/money_lotto.dm +++ /dev/null @@ -1,39 +0,0 @@ -/datum/gm_action/money_lotto - name = "lottery win" - departments = list(DEPARTMENT_EVERYONE) - var/winner_name = "John Smith" - var/winner_sum = 0 - var/deposit_success = 0 - -/datum/gm_action/money_lotto/start() - ..() - winner_sum = pick(5000, 10000, 50000, 100000, 500000, 1000000, 1500000) - if(all_money_accounts.len) - var/datum/money_account/D = pick(all_money_accounts) - winner_name = D.owner_name - if(!D.suspended) - D.money += winner_sum - - var/datum/transaction/T = new() - T.target_name = "The [using_map.starsys_name] Times Grand Slam -Stellar- Lottery" - T.purpose = "Winner!" - T.amount = winner_sum - T.date = current_date_string - T.time = stationtime2text() - T.source_terminal = "Sif TCD Terminal #[rand(111,333)]" - D.transaction_log.Add(T) - - deposit_success = 1 - -/datum/gm_action/money_lotto/announce() - var/author = "[using_map.company_name] Editor" - var/channel = "The [using_map.starsys_name] Times" - - var/body = "The [using_map.starsys_name] Times wishes to congratulate [winner_name] for recieving the [using_map.starsys_name] Stellar Slam Lottery, and receiving the out of this world sum of [winner_sum] credits!" - if(!deposit_success) - body += "
Unfortunately, we were unable to verify the account details provided, so we were unable to transfer the money. Send a cheque containing the sum of 5000 Thalers to ND 'Stellar Slam' office on the The [using_map.starsys_name] Times gateway containing updated details, and your winnings'll be re-sent within the month." - - news_network.SubmitArticle(body, author, channel, null, 1) - -/datum/gm_action/money_lotto/get_weight() - return 25 * metric.count_people_in_department(DEPARTMENT_EVERYONE) diff --git a/code/modules/gamemaster/actions/money_spam.dm b/code/modules/gamemaster/actions/money_spam.dm deleted file mode 100644 index 80b5fed8080..00000000000 --- a/code/modules/gamemaster/actions/money_spam.dm +++ /dev/null @@ -1,131 +0,0 @@ -/datum/gm_action/pda_spam - name = "PDA spam" - departments = list(DEPARTMENT_EVERYONE) - reusable = TRUE - var/last_spam_time = 0 - var/obj/machinery/message_server/useMS - var/obj/machinery/exonet_node/node - -/datum/gm_action/pda_spam/set_up() - last_spam_time = world.time - pick_message_server() - -/datum/gm_action/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/gm_action/pda_spam/start() - ..() - while(world.time < last_spam_time + 3000) - 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 - - /* //VOREStation Removal: no need to spam the AI tenfold - 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("[bicon(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, "[bicon(P)] Message from [sender] (Unknown / spam?), \"[message]\" (Unable to Reply)") - -/datum/gm_action/pda_spam/get_weight() - return 25 * metric.count_people_in_department(DEPARTMENT_EVERYONE) diff --git a/code/modules/gamemaster/actions/planet_weather_change.dm b/code/modules/gamemaster/actions/planet_weather_change.dm deleted file mode 100644 index 641244bf0af..00000000000 --- a/code/modules/gamemaster/actions/planet_weather_change.dm +++ /dev/null @@ -1,35 +0,0 @@ -/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( - //VOREStation Edit - Virgo 3B Weather, - /datum/weather/virgo3b/ash_storm, - /datum/weather/virgo3b/emberfall, - /datum/weather/virgo3b/blood_moon, - /datum/weather/virgo3b/fallout) - //VOREStation Edit End - 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 diff --git a/code/modules/gamemaster/actions/prison_break.dm b/code/modules/gamemaster/actions/prison_break.dm deleted file mode 100644 index 0111ea0ecdf..00000000000 --- a/code/modules/gamemaster/actions/prison_break.dm +++ /dev/null @@ -1,93 +0,0 @@ -/datum/gm_action/prison_break - name = "prison break" - departments = list(DEPARTMENT_SECURITY, DEPARTMENT_SYNTHETIC) - - var/start_time = 0 - var/active = FALSE // Are we doing stuff? - var/releaseWhen = 60 // The delay for the breakout to occur. - var/list/area/areas = list() // List of areas to affect. Filled by start() - - var/eventDept = "Security" // Department name in announcement - var/list/areaName = list("Brig") // Names of areas mentioned in AI and Engineering announcements - var/list/areaType = list(/area/security/prison, /area/security/brig) // Area types to include. - var/list/areaNotType = list() // Area types to specifically exclude. - -/datum/gm_action/prison_break/get_weight() - var/afflicted_staff = 0 - var/assigned_staff = metric.count_people_in_department(DEPARTMENT_ENGINEERING) - for(var/department in departments) - afflicted_staff += round(metric.count_people_in_department(department) / 2) - - var/weight = 20 + (assigned_staff * 10) - - if(assigned_staff) - weight += afflicted_staff - - return weight - -/datum/gm_action/prison_break/virology - name = "virology breakout" - departments = list(DEPARTMENT_MEDICAL, DEPARTMENT_SYNTHETIC) - eventDept = "Medical" - areaName = list("Virology") - areaType = list(/area/medical/virology, /area/medical/virologyaccess) - -/datum/gm_action/prison_break/xenobiology - name = "xenobiology breakout" - departments = list(DEPARTMENT_RESEARCH, DEPARTMENT_SYNTHETIC) - eventDept = "Science" - areaName = list("Xenobiology") - areaType = list(/area/rnd/xenobiology) - areaNotType = list(/area/rnd/xenobiology/xenoflora, /area/rnd/xenobiology/xenoflora_storage) - -/datum/gm_action/prison_break/station - name = "station-wide breakout" - departments = list(DEPARTMENT_SECURITY, DEPARTMENT_MEDICAL, DEPARTMENT_RESEARCH, DEPARTMENT_SYNTHETIC) - eventDept = "Station" - areaName = list("Brig","Virology","Xenobiology") - areaType = list(/area/security/prison, /area/security/brig, /area/medical/virology, /area/medical/virologyaccess, /area/rnd/xenobiology) - areaNotType = list(/area/rnd/xenobiology/xenoflora, /area/rnd/xenobiology/xenoflora_storage) - -/datum/gm_action/prison_break/set_up() - releaseWhen = rand(60, 90) - start_time = world.time - active = TRUE - length = releaseWhen + 1 SECOND - -/datum/gm_action/prison_break/announce() - if(areas && areas.len > 0) - command_announcement.Announce("[pick("Gr3y.T1d3 virus","Malignant trojan")] detected in [station_name()] [(eventDept == "Security")? "imprisonment":"containment"] subroutines. Secure any compromised areas immediately. Station AI involvement is recommended.", "[eventDept] Alert") - -/datum/gm_action/prison_break/start() - ..() - for(var/area/A in all_areas) - if(is_type_in_list(A,areaType) && !is_type_in_list(A,areaNotType)) - areas += A - - if(areas && areas.len > 0) - var/my_department = "[station_name()] firewall subroutines" - var/rc_message = "An unknown malicious program has been detected in the [english_list(areaName)] lighting and airlock control systems at [stationtime2text()]. Systems will be fully compromised within approximately three minutes. Direct intervention is required immediately.
" - for(var/obj/machinery/message_server/MS in machines) - MS.send_rc_message("Engineering", my_department, rc_message, "", "", 2) - for(var/mob/living/silicon/ai/A in player_list) - to_chat(A, "Malicious program detected in the [english_list(areaName)] lighting and airlock control systems by [my_department].") - - else - to_world_log("ERROR: Could not initate grey-tide. Unable to find suitable containment area.") - - if(areas && areas.len > 0) - spawn() - while(active) - sleep(1) - if(world.time >= releaseWhen + start_time) - var/obj/machinery/power/apc/theAPC = null - for(var/area/A in areas) - theAPC = A.get_apc() - if(theAPC.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/gm_action/prison_break/end() - active = FALSE - for(var/area/A in shuffle(areas)) - A.prison_break() diff --git a/code/modules/gamemaster/actions/radiation_storm.dm b/code/modules/gamemaster/actions/radiation_storm.dm deleted file mode 100644 index e6a31a6da8d..00000000000 --- a/code/modules/gamemaster/actions/radiation_storm.dm +++ /dev/null @@ -1,67 +0,0 @@ -/datum/gm_action/radiation_storm - name = "radiation storm" - departments = list(DEPARTMENT_EVERYONE) - reusable = TRUE - - var/enterBelt = 30 - var/radIntervall = 5 - var/leaveBelt = 80 - var/revokeAccess = 165 - var/activeFor = 0 - var/postStartTicks = 0 - var/active = FALSE - -/datum/gm_action/radiation_storm/announce() - command_announcement.Announce("High levels of radiation detected near \the [station_name()]. Please evacuate into one of the shielded maintenance tunnels.", "Anomaly Alert", new_sound = 'sound/AI/radiation.ogg') - -/datum/gm_action/radiation_storm/set_up() - active = TRUE - -/datum/gm_action/radiation_storm/start() - ..() - make_maint_all_access() - - while(active) - sleep(1 SECOND) - activeFor ++ - if(activeFor == enterBelt) - 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") - radiate() - - if(activeFor >= enterBelt && activeFor <= leaveBelt) - postStartTicks++ - - if(postStartTicks == radIntervall) - postStartTicks = 0 - radiate() - - else if(activeFor == leaveBelt) - 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") - -/datum/gm_action/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) - - for(var/mob/living/carbon/C in living_mob_list) - var/area/A = get_area(C) - if(!A) - continue - if(A.flags & RAD_SHIELDED) - continue - if(istype(C,/mob/living/carbon/human)) - var/mob/living/carbon/human/H = C - if(prob(5)) - if (prob(75)) - randmutb(H) // Applies bad mutation - domutcheck(H,null,MUTCHK_FORCED) - else - randmutg(H) // Applies good mutation - domutcheck(H,null,MUTCHK_FORCED) - -/datum/gm_action/radiation_storm/end() - spawn(revokeAccess SECONDS) - revoke_maint_all_access() - -/datum/gm_action/radiation_storm/get_weight() - return 20 + (metric.count_people_in_department(DEPARTMENT_MEDICAL) * 10) + (metric.count_all_space_mobs() * 40) + (metric.count_people_in_department(DEPARTMENT_EVERYONE) * 20) diff --git a/code/modules/gamemaster/actions/random_antagonist.dm b/code/modules/gamemaster/actions/random_antagonist.dm deleted file mode 100644 index bf1169bb732..00000000000 --- a/code/modules/gamemaster/actions/random_antagonist.dm +++ /dev/null @@ -1,23 +0,0 @@ -// The random spawn proc on the antag datum will handle announcing the spawn and whatnot. -/datum/gm_action/random_antag - name = "random antagonist" - departments = list(DEPARTMENT_EVERYONE) - chaotic = 30 - reusable = TRUE - -/datum/gm_action/random_antag/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() - -/datum/gm_action/random_antag/get_weight() - . = ..() - if(gm) - var/weight = max(0, (metric.count_people_in_department(DEPARTMENT_SECURITY) * 20) + (metric.count_people_in_department(DEPARTMENT_EVERYONE) * 5) + gm.staleness) - return weight diff --git a/code/modules/gamemaster/actions/rogue_drones.dm b/code/modules/gamemaster/actions/rogue_drones.dm deleted file mode 100644 index a1594fd370c..00000000000 --- a/code/modules/gamemaster/actions/rogue_drones.dm +++ /dev/null @@ -1,63 +0,0 @@ -/datum/gm_action/rogue_drone - name = "rogue drones" - departments = list(DEPARTMENT_SECURITY) - chaotic = 60 - length = 20 MINUTES - var/list/drones_list = list() - -/datum/gm_action/rogue_drone/start() - ..() - //spawn them at the same place as carp - var/list/possible_spawns = list() - for(var/obj/effect/landmark/C in landmarks_list) - if(C.name == "carpspawn") - possible_spawns.Add(C) - - //25% chance for this to be a false alarm - var/num - if(prob(25)) - num = 0 - else - num = rand(2,6) - for(var/i=0, i drones_list.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") - -/datum/gm_action/rogue_drone/get_weight() - return 20 + (metric.count_people_in_department(DEPARTMENT_SECURITY) * 10) + (metric.count_all_space_mobs() * 30) diff --git a/code/modules/gamemaster/actions/security_advisement.dm b/code/modules/gamemaster/actions/security_advisement.dm deleted file mode 100644 index e98504df097..00000000000 --- a/code/modules/gamemaster/actions/security_advisement.dm +++ /dev/null @@ -1,49 +0,0 @@ -/datum/gm_action/security_screening - name = "security screening" - departments = list(DEPARTMENT_SECURITY, DEPARTMENT_EVERYONE) - - var/list/species_weights = list( - SPECIES_SKRELL = 9, - SPECIES_UNATHI = 15, - SPECIES_HUMAN_VATBORN = 6, - SPECIES_TESHARI = 2, - SPECIES_TAJ = 3, - SPECIES_DIONA = 1, - SPECIES_ZADDAT = 25, - SPECIES_HUMAN = 3, - SPECIES_PROMETHEAN = 30 - ) - - var/list/synth_weights = list( - "Cybernetic" = 15, - "Drone" = 30, - "Positronic" = 25 - ) - - var/list/end_weights = list() - -/datum/gm_action/security_screening/set_up() - for(var/species_name in species_weights) - var/giveweight = 0 - - for(var/datum/data/record/R in data_core.general) - if(R.fields["species"] == species_name) - giveweight += species_weights[species_name] - - end_weights[species_name] = giveweight - - for(var/bottype in synth_weights) - var/giveweight = 0 - - for(var/datum/data/record/R in data_core.general) - if(R.fields["brain_type"] == bottype) - giveweight += synth_weights[bottype] - - end_weights[bottype] = giveweight - -/datum/gm_action/security_screening/announce() - spawn(rand(1 MINUTE, 2 MINUTES)) - command_announcement.Announce("[pick("A nearby Navy vessel", "A Solar official", "A Vir-Gov official", "A NanoTrasen board director")] has requested the screening of [pick("every other", "every", "suspicious", "willing")] [pickweight(end_weights)] personnel onboard \the [station_name()].", "Security Advisement") - -/datum/gm_action/security_screening/get_weight() - return max(-20, 10 + round(gm.staleness * 1.5) - (gm.danger * 2)) + (metric.count_people_in_department(DEPARTMENT_SECURITY) * 10) + (metric.count_people_in_department(DEPARTMENT_EVERYONE) * 1.5) diff --git a/code/modules/gamemaster/actions/shipping_error.dm b/code/modules/gamemaster/actions/shipping_error.dm deleted file mode 100644 index 95611d6e290..00000000000 --- a/code/modules/gamemaster/actions/shipping_error.dm +++ /dev/null @@ -1,17 +0,0 @@ -/datum/gm_action/shipping_error - name = "shipping error" - departments = list(DEPARTMENT_CARGO) - reusable = TRUE - -/datum/gm_action/shipping_error/get_weight() - var/cargo = metric.count_people_in_department(DEPARTMENT_CARGO) - var/weight = (cargo * 40) - return weight - -/datum/gm_action/shipping_error/start() - ..() - var/datum/supply_order/O = new /datum/supply_order() - O.ordernum = SSsupply.ordernum - O.object = SSsupply.supply_pack[pick(SSsupply.supply_pack)] - O.ordered_by = random_name(pick(MALE,FEMALE), species = "Human") - SSsupply.shoppinglist += O \ No newline at end of file diff --git a/code/modules/gamemaster/actions/solar_storm.dm b/code/modules/gamemaster/actions/solar_storm.dm deleted file mode 100644 index f468c024a95..00000000000 --- a/code/modules/gamemaster/actions/solar_storm.dm +++ /dev/null @@ -1,56 +0,0 @@ -/datum/gm_action/solar_storm - name = "solar storm" - var/rad_interval = 1 SECOND - var/base_solar_gen_rate - length = 3 MINUTES - var/duration // Duration for the storm - var/start_time = 0 - - reusable = TRUE - -/datum/gm_action/solar_storm/set_up() - start_time = world.time - duration = length - duration += rand(-1 * 1 MINUTE, 1 MINUTE) - -/datum/gm_action/solar_storm/announce() - command_announcement.Announce("A solar storm has been detected approaching \the [station_name()]. Please halt all EVA activites immediately and return to the interior of the station.", "Anomaly Alert", new_sound = 'sound/AI/radiation.ogg') - adjust_solar_output(1.5) - -/datum/gm_action/solar_storm/proc/adjust_solar_output(var/mult = 1) - if(isnull(base_solar_gen_rate)) base_solar_gen_rate = GLOB.solar_gen_rate - GLOB.solar_gen_rate = mult * base_solar_gen_rate - -/datum/gm_action/solar_storm/start() - ..() - length = duration - command_announcement.Announce("The solar storm has reached the station. Please refain from EVA and remain inside the station until it has passed.", "Anomaly Alert") - adjust_solar_output(5) - - var/start_time = world.time - - spawn() - while(world.time <= start_time + duration) - sleep(rad_interval) - radiate() - -/datum/gm_action/solar_storm/get_weight() - return 20 + (metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 10) + (metric.count_all_space_mobs() * 30) - -/datum/gm_action/solar_storm/proc/radiate() - // Note: Too complicated to be worth trying to use the radiation system for this. Its only in space anyway, so we make an exception in this case. - for(var/mob/living/L in player_list) - var/turf/T = get_turf(L) - if(!T) - continue - - if(!istype(T.loc,/area/space) && !istype(T,/turf/space)) //Make sure you're in a space area or on a space turf - continue - - //Todo: Apply some burn damage from the heat of the sun. Until then, enjoy some moderate radiation. - L.rad_act(rand(15, 30)) - -/datum/gm_action/solar_storm/end() - command_announcement.Announce("The solar storm has passed the station. It is now safe to resume EVA activities. Please report to medbay if you experience any unusual symptoms. ", "Anomaly Alert") - adjust_solar_output() - length = initial(length) diff --git a/code/modules/gamemaster/actions/spacevine.dm b/code/modules/gamemaster/actions/spacevine.dm deleted file mode 100644 index 759329dbbc2..00000000000 --- a/code/modules/gamemaster/actions/spacevine.dm +++ /dev/null @@ -1,14 +0,0 @@ -/datum/gm_action/spacevine - name = "space-vine infestation" - departments = list(DEPARTMENT_ENGINEERING) - chaotic = 2 - -/datum/gm_action/spacevine/start() - ..() - spacevine_infestation() - -/datum/gm_action/spacevine/announce() - level_seven_announcement() - -/datum/gm_action/spacevine/get_weight() - return 20 + (metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 20) + (metric.count_people_in_department(DEPARTMENT_EVERYONE) * 10) diff --git a/code/modules/gamemaster/actions/spider_infestation.dm b/code/modules/gamemaster/actions/spider_infestation.dm deleted file mode 100644 index 4105a8e1d4b..00000000000 --- a/code/modules/gamemaster/actions/spider_infestation.dm +++ /dev/null @@ -1,56 +0,0 @@ -/datum/gm_action/spider_infestation - name = "spider infestation" - departments = list(DEPARTMENT_SECURITY, DEPARTMENT_MEDICAL, DEPARTMENT_EVERYONE) - chaotic = 30 - - severity = 1 - - var/spawncount = 1 - - var/spawntype = /obj/effect/spider/spiderling - -/datum/gm_action/spider_infestation/set_up() - severity = pickweight(EVENT_LEVEL_MUNDANE = max(1,(12 - (3 * metric.count_people_in_department(DEPARTMENT_SECURITY)))), - EVENT_LEVEL_MODERATE = (7 + (2 * metric.count_people_in_department(DEPARTMENT_SECURITY))), - EVENT_LEVEL_MAJOR = (1 + (2 * metric.count_people_in_department(DEPARTMENT_SECURITY))) - ) - - switch(severity) - if(EVENT_LEVEL_MUNDANE) - spawntype = /obj/effect/spider/spiderling/stunted - if(EVENT_LEVEL_MODERATE) - spawntype = /obj/effect/spider/spiderling - if(EVENT_LEVEL_MAJOR) - spawntype = /obj/effect/spider/spiderling - - spawncount = rand(4 * severity, 6 * severity) - -/datum/gm_action/spider_infestation/announce() - command_announcement.Announce("Unidentified lifesigns detected coming aboard [station_name()]. Secure any exterior access, including ducting and ventilation.", "Lifesign Alert", new_sound = 'sound/AI/aliens.ogg') - - if(severity >= EVENT_LEVEL_MAJOR) - spawn(rand(600, 3000)) - command_announcement.Announce("Unidentified lifesigns previously detected coming aboard [station_name()] have been classified as a swarm of arachnids. Extreme caution is advised.", "Arachnid Alert") - -/datum/gm_action/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 using_map.station_levels) - if(temp_vent.network.normal_members.len > 50) - vents += temp_vent - - while((spawncount >= 1) && vents.len) - var/obj/vent = pick(vents) - new /obj/effect/spider/spiderling(vent.loc) - vents -= vent - spawncount-- - -/datum/gm_action/spider_infestation/get_weight() - var/security = metric.count_people_in_department(DEPARTMENT_SECURITY) - var/medical = metric.count_people_in_department(DEPARTMENT_MEDICAL) - var/engineering = metric.count_people_in_department(DEPARTMENT_ENGINEERING) - - var/assigned_staff = security + round(medical / 2) + round(engineering / 2) - - return 10 + (assigned_staff * 15) diff --git a/code/modules/gamemaster/actions/spontaneous_appendicitis.dm b/code/modules/gamemaster/actions/spontaneous_appendicitis.dm deleted file mode 100644 index ef7efde26bc..00000000000 --- a/code/modules/gamemaster/actions/spontaneous_appendicitis.dm +++ /dev/null @@ -1,13 +0,0 @@ -/datum/gm_action/spontaneous_appendicitis - name = "appendicitis" - departments = list(DEPARTMENT_MEDICAL, DEPARTMENT_EVERYONE) - chaotic = 1 - -/datum/gm_action/spontaneous_appendicitis/start() - ..() - for(var/mob/living/carbon/human/H in shuffle(living_mob_list)) - if(H.client && !player_is_antag(H.mind) && H.appendicitis()) - break - -/datum/gm_action/spontaneous_appendicitis/get_weight() - return max(0, -5 + (metric.count_people_in_department(DEPARTMENT_MEDICAL) * 10)) diff --git a/code/modules/gamemaster/actions/station_fundraise.dm b/code/modules/gamemaster/actions/station_fundraise.dm deleted file mode 100644 index 96d4299053a..00000000000 --- a/code/modules/gamemaster/actions/station_fundraise.dm +++ /dev/null @@ -1,14 +0,0 @@ -/datum/gm_action/station_fund_raise - name = "local funding drive" - departments = list(DEPARTMENT_SECURITY, DEPARTMENT_CARGO, DEPARTMENT_EVERYONE) - -/datum/gm_action/station_fund_raise/announce() - spawn(rand(1 MINUTE, 2 MINUTES)) - command_announcement.Announce("Due to [pick("recent", "unfortunate", "possible future")] budget [pick("changes", "issues")], in-system stations are now advised to increase funding income.", "Security & Supply Advisement") - -/datum/gm_action/station_fund_raise/get_weight() - var/weight_modifier = 0.5 - if(station_account.money <= 80000) - weight_modifier = 1 - - return (max(-20, 10 + gm.staleness) + ((metric.count_people_in_department(DEPARTMENT_SECURITY) + (metric.count_people_in_department(DEPARTMENT_CARGO))) * 5) + (metric.count_people_in_department(DEPARTMENT_EVERYONE) * 3)) * weight_modifier diff --git a/code/modules/gamemaster/actions/stowaway.dm b/code/modules/gamemaster/actions/stowaway.dm deleted file mode 100644 index b7185e1ed48..00000000000 --- a/code/modules/gamemaster/actions/stowaway.dm +++ /dev/null @@ -1,80 +0,0 @@ -/datum/gm_action/stowaway - name = "stowaway pod" - departments = list(DEPARTMENT_EVERYONE, DEPARTMENT_SECURITY) - chaotic = 10 - observers_used = TRUE - var/area/target_area // Chosen target area - var/area/target_turf // Chosen target turf in target_area - var/list/area/excluded = list( - /area/submap, - /area/shuttle, - /area/crew_quarters, - /area/holodeck, - /area/engineering/engine_room - ) - - var/list/area/included = list( - /area/maintenance - ) - -/datum/gm_action/stowaway/set_up() - severity = pickweight(EVENT_LEVEL_MUNDANE = 20, - EVENT_LEVEL_MODERATE = 5, - EVENT_LEVEL_MAJOR = 1 - ) - - var/list/area/grand_list_of_areas = get_station_areas(excluded) - - for(var/area/Incl in included) - for(var/area/A in grand_list_of_areas) - if(!istype(A, Incl)) - grand_list_of_areas -= A - - // Okay, now lets try and pick a target! Lets try 10 times, otherwise give up - for(var/i in 1 to 10) - var/area/A = pick(grand_list_of_areas) - if(is_area_occupied(A)) - log_debug("[name] event: Rejected [A] because it is occupied.") - continue - // A good area, great! Lets try and pick a turf - var/list/turfs = list() - for(var/turf/simulated/floor/F in A) - if(turf_clear(F)) - turfs += F - if(turfs.len == 0) - log_debug("[name] event: Rejected [A] because it has no clear turfs.") - continue - target_area = A - target_turf = pick(turfs) - - if(!target_area) - log_debug("[name] event: Giving up after too many failures to pick target area") - return - -/datum/gm_action/stowaway/start() - if(!target_turf) - return - ..() - - var/obj/structure/ghost_pod/ghost_activated/human/HP = new (target_turf) - - if(severity == EVENT_LEVEL_MUNDANE || istype(ticker.mode, /datum/game_mode/extended)) - HP.make_antag = MODE_STOWAWAY - - else if(severity == EVENT_LEVEL_MODERATE) - HP.make_antag = MODE_RENEGADE - HP.occupant_type = "renegade [HP.occupant_type]" - - else if(severity == EVENT_LEVEL_MAJOR) - HP.make_antag = MODE_INFILTRATOR - HP.occupant_type = "volatile [HP.occupant_type]" - - say_dead_object("A [HP.occupant_type] pod is now available in \the [target_area].", HP) - -/datum/gm_action/stowaway/get_weight() - return -20 + (metric.count_people_in_department(DEPARTMENT_SECURITY) * 15 + metric.count_people_in_department(DEPARTMENT_SYNTHETIC) * 5 + metric.count_people_in_department(DEPARTMENT_EVERYONE) * 1) - -/datum/gm_action/stowaway/announce() - spawn(rand(15 MINUTES, 30 MINUTES)) - if(prob(20) && severity >= EVENT_LEVEL_MODERATE && atc && !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/actions/supply_conversion.dm b/code/modules/gamemaster/actions/supply_conversion.dm deleted file mode 100644 index 3817dcce636..00000000000 --- a/code/modules/gamemaster/actions/supply_conversion.dm +++ /dev/null @@ -1,65 +0,0 @@ -/datum/gm_action/nanotrasen_budget_allocation - name = "supply point to cargo budget" - enabled = TRUE - departments = list(DEPARTMENT_CARGO) - chaotic = 0 - reusable = TRUE - - var/datum/controller/subsystem/supply/SC - var/running = FALSE - var/last_run - - var/thaler_earned - -/datum/gm_action/nanotrasen_budget_allocation/New() - ..() - SC = SSsupply - -/datum/gm_action/nanotrasen_budget_allocation/set_up() - running = TRUE - return - -/datum/gm_action/nanotrasen_budget_allocation/get_weight() - . = round(SC.points / 15) - - var/cargo = metric.count_people_in_department(DEPARTMENT_CARGO) - var/personnel = metric.count_people_in_department(DEPARTMENT_EVERYONE) - if(cargo) - . = round(SC.points / (10 + personnel)) + cargo * 10 - - if(running || ( world.time < (last_run + 30 MINUTES))) - . = 0 - - return . - -/datum/gm_action/nanotrasen_budget_allocation/start() - . = ..() - - last_run = world.time - - var/point_difference = SC.points - - if(SC.points >= 1000) - SC.points = round(SC.points / 3) - point_difference -= SC.points - - else if(SC.points >= 500) - SC.points -= 100 * (rand(5, 20) / 10) - point_difference -= SC.points - - else - SC.points = round(SC.points / 1.25) - point_difference -= SC.points - - if(point_difference > 0) - thaler_earned = round(point_difference / SC.points_per_money) - -/datum/gm_action/nanotrasen_budget_allocation/end() - spawn(5 MINUTES) - running = FALSE - return - -/datum/gm_action/nanotrasen_budget_allocation/announce() - spawn(rand(1 MINUTE, 5 MINUTES)) - command_announcement.Announce("[station_name()] Supply Department has earned a converted thaler budget of [thaler_earned] due to their backlogged daily requisition tokens.", "Supply Budget Conversion") - return diff --git a/code/modules/gamemaster/actions/supplyrequest.dm b/code/modules/gamemaster/actions/supplyrequest.dm deleted file mode 100644 index 0ba9366542e..00000000000 --- a/code/modules/gamemaster/actions/supplyrequest.dm +++ /dev/null @@ -1,11 +0,0 @@ -/datum/gm_action/request - name = "general request" - departments = list(DEPARTMENT_CARGO) - -/datum/gm_action/request/announce() - spawn(rand(1 MINUTE, 2 MINUTES)) - command_announcement.Announce("[pick("A nearby vessel", "A Solar contractor", "A Skrellian contractor", "A NanoTrasen board director")] has requested the delivery of [pick("one","two","three","several")] [pick("medical","engineering","research","civilian")] supply packages. The [station_name()] has been tasked with completing this request.", "Supply Request") - -/datum/gm_action/request/get_weight() - return max(15, 15 + round(gm.staleness / 2) - gm.danger) + (metric.count_people_in_department(DEPARTMENT_CARGO) * 10) - diff --git a/code/modules/gamemaster/actions/surprise_carp_attack.dm b/code/modules/gamemaster/actions/surprise_carp_attack.dm deleted file mode 100644 index eb7b61841ca..00000000000 --- a/code/modules/gamemaster/actions/surprise_carp_attack.dm +++ /dev/null @@ -1,45 +0,0 @@ -// This event sends a few carp after someone in space. - -/datum/gm_action/surprise_carp_attack - name = "surprise carp attack" - departments = list(DEPARTMENT_EVERYONE) - reusable = TRUE - chaotic = 10 - var/mob/living/victim = null - -/datum/gm_action/surprise_carp_attack/get_weight() - return metric.count_all_space_mobs() * 50 - -/datum/gm_action/surprise_carp_attack/set_up() - var/list/potential_victims = list() - victim = null - for(var/mob/living/L in player_list) - if(!(L.z in using_map.station_levels)) - continue // Not on the right z-level. - if(L.stat) - continue // Don't want dead people. - var/turf/T = get_turf(L) - if(istype(T, /turf/space) && istype(T.loc,/area/space)) - potential_victims.Add(L) - if(potential_victims.len) - victim = pick(potential_victims) - - -/datum/gm_action/surprise_carp_attack/start() - ..() - if(!victim) - message_admins("Surprise carp attack failed to find a target.") - return - var/number_of_carp = rand(1, 2) - message_admins("Sending [number_of_carp] carp\s after [victim].") - while(number_of_carp) - var/turf/spawning_turf = null - var/list/nearby_things = oview(10, victim) - for(var/turf/space/space in nearby_things) - if(get_dist(space, victim) <= 7) - continue - spawning_turf = space - break - if(spawning_turf) - new /mob/living/simple_mob/animal/space/carp(spawning_turf) - number_of_carp-- \ No newline at end of file diff --git a/code/modules/gamemaster/actions/surprise_meteor.dm b/code/modules/gamemaster/actions/surprise_meteor.dm deleted file mode 100644 index 9bdf8949486..00000000000 --- a/code/modules/gamemaster/actions/surprise_meteor.dm +++ /dev/null @@ -1,17 +0,0 @@ -// This event sends one wave of meteors unannounced. - -/datum/gm_action/surprise_meteors - name = "surprise meteors" - departments = list(DEPARTMENT_ENGINEERING) - chaotic = 25 - -/datum/gm_action/surprise_meteors/get_weight() - var/engineers = metric.count_people_in_department(DEPARTMENT_ENGINEERING) - var/weight = (max(engineers - 1, 0) * 25) // If only one engineer exists, no meteors for now. - return weight - -/datum/gm_action/surprise_meteors/start() - ..() - spawn(1) - spawn_meteors(rand(4, 8), meteors_normal, pick(cardinal)) - message_admins("Surprise meteors event has ended.") \ No newline at end of file diff --git a/code/modules/gamemaster/actions/swarmboarder.dm b/code/modules/gamemaster/actions/swarmboarder.dm deleted file mode 100644 index fb98dc535ec..00000000000 --- a/code/modules/gamemaster/actions/swarmboarder.dm +++ /dev/null @@ -1,75 +0,0 @@ -/datum/gm_action/swarm_boarder - name = "swarmer shell" - departments = list(DEPARTMENT_EVERYONE, DEPARTMENT_SECURITY, DEPARTMENT_ENGINEERING) - chaotic = 60 - observers_used = TRUE - var/area/target_area // Chosen target area - var/area/target_turf // Chosen target turf in target_area - var/list/area/excluded = list( - /area/submap, - /area/shuttle, - /area/crew_quarters, - /area/holodeck, - /area/engineering/engine_room - ) - - var/list/area/included = list( - /area/maintenance - ) - -/datum/gm_action/swarm_boarder/set_up() - severity = pickweight(EVENT_LEVEL_MUNDANE = 30, - EVENT_LEVEL_MODERATE = 10, - EVENT_LEVEL_MAJOR = 1 - ) - - var/list/area/grand_list_of_areas = get_station_areas(excluded) - - for(var/area/Incl in included) - for(var/area/A in grand_list_of_areas) - if(!istype(A, Incl)) - grand_list_of_areas -= A - - // Okay, now lets try and pick a target! Lets try 10 times, otherwise give up - for(var/i in 1 to 10) - var/area/A = pick(grand_list_of_areas) - if(is_area_occupied(A)) - log_debug("[name] event: Rejected [A] because it is occupied.") - continue - // A good area, great! Lets try and pick a turf - var/list/turfs = list() - for(var/turf/simulated/floor/F in A) - if(turf_clear(F)) - turfs += F - if(turfs.len == 0) - log_debug("[name] event: Rejected [A] because it has no clear turfs.") - continue - target_area = A - target_turf = pick(turfs) - - if(!target_area) - log_debug("[name] event: Giving up after too many failures to pick target area") - return - -/datum/gm_action/swarm_boarder/start() - if(!target_turf) - return - ..() - - var/swarmertype = /obj/structure/ghost_pod/ghost_activated/swarm_drone/event - - if(severity == EVENT_LEVEL_MODERATE) - swarmertype = /obj/structure/ghost_pod/ghost_activated/swarm_drone/event/melee - - if(severity == EVENT_LEVEL_MAJOR) - swarmertype = /obj/structure/ghost_pod/ghost_activated/swarm_drone/event/gunner - - new swarmertype(target_turf) - -/datum/gm_action/swarm_boarder/get_weight() - return max(0, -60 + (metric.count_people_in_department(DEPARTMENT_SECURITY) * 10 + metric.count_people_in_department(DEPARTMENT_SYNTHETIC) * 5)) - -/datum/gm_action/swarm_boarder/announce() - spawn(rand(5 MINUTES, 15 MINUTES)) - if(prob(80) && severity >= EVENT_LEVEL_MODERATE && atc && !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/actions/viral_infection.dm b/code/modules/gamemaster/actions/viral_infection.dm deleted file mode 100644 index 97a646e2a3c..00000000000 --- a/code/modules/gamemaster/actions/viral_infection.dm +++ /dev/null @@ -1,83 +0,0 @@ -/var/global/list/event_viruses = list() // so that event viruses are kept around for admin logs, rather than being GCed - -/datum/gm_action/viral_infection - name = "viral infection" - departments = list(DEPARTMENT_MEDICAL) - chaotic = 5 - var/list/viruses = list() - severity = 1 - -/datum/gm_action/viral_infection/set_up() - severity = pickweight(EVENT_LEVEL_MUNDANE = 20, - EVENT_LEVEL_MODERATE = 10, - EVENT_LEVEL_MAJOR = 3 - ) - - //generate 1-3 viruses. This way there's an upper limit on how many individual diseases need to be cured if many people are initially infected - var/num_diseases = rand(1,3) - for (var/i=0, i < num_diseases, i++) - var/datum/disease2/disease/D = new /datum/disease2/disease - - var/strength = 1 //whether the disease is of the greater or lesser variety - if (severity >= EVENT_LEVEL_MAJOR && prob(75)) - strength = 2 - D.makerandom(strength) - viruses += D - -/datum/gm_action/viral_infection/announce() - var/level - if (severity == EVENT_LEVEL_MUNDANE) - return - else if (severity == EVENT_LEVEL_MODERATE) - level = pick("one", "two", "three", "four") - else - level = "five" - - spawn(rand(0, 3000)) - if(severity == EVENT_LEVEL_MAJOR || prob(60)) - command_announcement.Announce("Confirmed outbreak of level [level] biohazard aboard \the [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", new_sound = 'sound/AI/outbreak5.ogg') - -/datum/gm_action/viral_infection/start() - if(!viruses.len) return - - ..() - - var/list/candidates = list() //list of candidate keys - for(var/mob/living/carbon/human/G in player_list) - if(G.mind && G.stat != DEAD && G.is_client_active(5) && !player_is_antag(G.mind)) - var/turf/T = get_turf(G) - if(T.z in using_map.station_levels) - candidates += G - if(!candidates.len) return - candidates = shuffle(candidates)//Incorporating Donkie's list shuffle - - var/list/used_viruses = list() - var/list/used_candidates = list() - severity = max(EVENT_LEVEL_MUNDANE, severity - 1) - var/actual_severity = severity * rand(1, 3) - while(actual_severity > 0 && candidates.len) - var/datum/disease2/disease/D = pick(viruses) - infect_mob(candidates[1], D.getcopy()) - used_candidates += candidates[1] - candidates.Remove(candidates[1]) - actual_severity-- - used_viruses |= D - - event_viruses |= used_viruses - var/list/used_viruses_links = list() - var/list/used_viruses_text = list() - for(var/datum/disease2/disease/D in used_viruses) - used_viruses_links += "[D.name()]" - used_viruses_text += D.name() - - var/list/used_candidates_links = list() - var/list/used_candidates_text = list() - for(var/mob/M in used_candidates) - used_candidates_links += key_name_admin(M) - used_candidates_text += key_name(M) - - log_admin("Virus event affecting [english_list(used_candidates_text)] started; Viruses: [english_list(used_viruses_text)]") - message_admins("Virus event affecting [english_list(used_candidates_links)] started; Viruses: [english_list(used_viruses_links)]") - -/datum/gm_action/viral_infection/get_weight() - return (metric.count_people_in_department(DEPARTMENT_MEDICAL) * 20) diff --git a/code/modules/gamemaster/actions/viral_outbreak.dm b/code/modules/gamemaster/actions/viral_outbreak.dm deleted file mode 100644 index 352627bd24d..00000000000 --- a/code/modules/gamemaster/actions/viral_outbreak.dm +++ /dev/null @@ -1,44 +0,0 @@ -/datum/gm_action/viral_outbreak - name = "viral outbreak" - departments = list(DEPARTMENT_MEDICAL, DEPARTMENT_EVERYONE) - chaotic = 30 - severity = 1 - var/list/candidates = list() - -/datum/gm_action/viral_outbreak/set_up() - candidates.Cut() // Incase we somehow get run twice. - severity = rand(2, 4) - for(var/mob/living/carbon/human/G in player_list) - if(G.client && G.stat != DEAD) - candidates += G - if(!candidates.len) return - candidates = shuffle(candidates)//Incorporating Donkie's list shuffle - -/datum/gm_action/viral_outbreak/announce() - command_announcement.Announce("Confirmed outbreak of level 7 biohazard aboard \the [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert", new_sound = 'sound/AI/outbreak7.ogg') - -/datum/gm_action/viral_outbreak/start() - ..() - while(severity > 0 && candidates.len) - if(prob(33)) - infect_mob_random_lesser(candidates[1]) - else - infect_mob_random_greater(candidates[1]) - - candidates.Remove(candidates[1]) - severity-- - -/datum/gm_action/viral_outbreak/get_weight() - var/medical = metric.count_people_in_department(DEPARTMENT_MEDICAL) - var/security = metric.count_people_in_department(DEPARTMENT_SECURITY) - var/everyone = metric.count_people_in_department(DEPARTMENT_EVERYONE) - - var/assigned_staff = medical + round(security / 2) - - if(!medical) // No docs, no staff. - assigned_staff = 0 - - if(assigned_staff < round(everyone / 6) - assigned_staff) // A doc or half an officer per roughly six people. Any less, and assigned staff is halved. - assigned_staff /= 2 - - return (assigned_staff * 10) diff --git a/code/modules/gamemaster/actions/wallrot.dm b/code/modules/gamemaster/actions/wallrot.dm deleted file mode 100644 index deabfbe34a5..00000000000 --- a/code/modules/gamemaster/actions/wallrot.dm +++ /dev/null @@ -1,43 +0,0 @@ -/datum/gm_action/wallrot - name = "wall rot" - departments = list(DEPARTMENT_ENGINEERING) - reusable = TRUE - var/turf/simulated/wall/center - severity = 1 - -/datum/gm_action/wallrot/set_up() - severity = rand(1,3) - center = null - // 100 attempts - for(var/i=0, i<100, i++) - var/turf/candidate = locate(rand(1, world.maxx), rand(1, world.maxy), 1) - if(istype(candidate, /turf/simulated/wall)) - center = candidate - return 1 - return 0 - -/datum/gm_action/wallrot/announce() - if(center && prob(min(90,40 * severity))) - command_announcement.Announce("Harmful fungi detected on \the [station_name()] nearby [center.loc.name]. Station structures may be contaminated.", "Biohazard Alert") - -/datum/gm_action/wallrot/start() - ..() - spawn() - if(center) - // Make sure at least one piece of wall rots! - center.rot() - - // Have a chance to rot lots of other walls. - var/rotcount = 0 - var/actual_severity = severity * rand(5, 10) - for(var/turf/simulated/wall/W in range(5, center)) - if(prob(50)) - W.rot() - rotcount++ - - // Only rot up to severity walls - if(rotcount >= actual_severity) - break - -/datum/gm_action/wallrot/get_weight() - return 60 + (metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 35) diff --git a/code/modules/gamemaster/actions/waste_disposal.dm b/code/modules/gamemaster/actions/waste_disposal.dm deleted file mode 100644 index e8412d7e95e..00000000000 --- a/code/modules/gamemaster/actions/waste_disposal.dm +++ /dev/null @@ -1,9 +0,0 @@ -// A shuttle full of junk docks, and cargo is tasked with sifting through it all to find valuables, or just dispose of it. - -/datum/gm_action/waste_disposal - name = "waste disposal" - departments = list(DEPARTMENT_CARGO) - chaotic = 0 - -/datum/gm_action/waste_disposal/get_weight() - return metric.count_people_in_department(DEPARTMENT_CARGO) * 50 \ No newline at end of file diff --git a/code/modules/gamemaster/actions/window_break.dm b/code/modules/gamemaster/actions/window_break.dm deleted file mode 100644 index c409388c233..00000000000 --- a/code/modules/gamemaster/actions/window_break.dm +++ /dev/null @@ -1,78 +0,0 @@ -/datum/gm_action/window_break - name = "window breach" - departments = list(DEPARTMENT_ENGINEERING) - chaotic = 5 - var/obj/structure/window/chosen_window - var/list/obj/structure/window/collateral_windows - var/turf/chosen_location - var/list/area/excluded = list( - /area/shuttle, - /area/crew_quarters - ) - -/datum/gm_action/window_break/set_up() - var/list/area/grand_list_of_areas = get_station_areas(excluded) - //try 10 times - for(var/i in 1 to 10) - var/area/A = pick(grand_list_of_areas) - var/list/obj/structure/window/possible_target_windows = list() - for(var/obj/structure/window/target_window in A.contents) - possible_target_windows += target_window - possible_target_windows = shuffle(possible_target_windows) - - for(var/obj/structure/window/target_window in possible_target_windows) - //if() don't have any conditions, for isn't strictly necessary - chosen_window = target_window - chosen_location = chosen_window.loc - collateral_windows = gather_collateral_windows(chosen_window) - return - -//TL;DR: breadth first search for all connected turfs with windows -/datum/gm_action/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 - -/datum/gm_action/window_break/start() - if(!chosen_window) - return - ..() - chosen_window.shatter(0) - - spawn() - for(var/obj/structure/window/current_collateral in collateral_windows) - sleep(rand(1,20)) - current_collateral.take_damage(current_collateral.health - (current_collateral.maxhealth / 5)) //set to 1/5th health - -/datum/gm_action/window_break/announce() - if(chosen_window) - command_announcement.Announce("Structural integrity of windows at [chosen_location.loc.name] is failing. Immediate repair or replacement is advised.", "Structural Alert") - -/datum/gm_action/window_break/get_weight() - return 20 * metric.count_people_in_department(DEPARTMENT_ENGINEERING) diff --git a/code/modules/gamemaster/actions/wormholes.dm b/code/modules/gamemaster/actions/wormholes.dm deleted file mode 100644 index a7650713b32..00000000000 --- a/code/modules/gamemaster/actions/wormholes.dm +++ /dev/null @@ -1,23 +0,0 @@ -/datum/gm_action/wormholes - name = "space-time anomalies" - chaotic = 70 - length = 12 MINUTES - departments = list(DEPARTMENT_EVERYONE) - severity = 1 - -/datum/gm_action/wormholes/set_up() // 1 out of 5 will be full-duration wormholes, meaning up to a minute long. - severity = pickweight(list( - 3 = 5, - 2 = 7, - 1 = 13 - )) - -/datum/gm_action/wormholes/start() - ..() - wormhole_event(length / 2, (severity / 3)) - -/datum/gm_action/wormholes/get_weight() - return 10 + max(0, -30 + (metric.count_people_in_department(DEPARTMENT_EVERYONE) * 5) + (metric.count_people_in_department(DEPARTMENT_ENGINEERING) + 10) + (metric.count_people_in_department(DEPARTMENT_MEDICAL) * 20)) - -/datum/gm_action/wormholes/end() - command_announcement.Announce("There are no more space-time anomalies detected on the station.", "Anomaly Alert") diff --git a/code/modules/gamemaster/controller.dm b/code/modules/gamemaster/controller.dm deleted file mode 100644 index 343f05842d1..00000000000 --- a/code/modules/gamemaster/controller.dm +++ /dev/null @@ -1,92 +0,0 @@ -/client/proc/show_gm_status() - set category = "Debug" - set name = "Show GM Status" - set desc = "Shows you what the GM is thinking. If only that existed in real life..." - - game_master.interact(usr) - -/datum/game_master/proc/interact(var/client/user) - if(!user) - return - - var/HTML = "Game Master AI" - - HTML += "\[Toggle Time Restrictions\] | \ - \[Toggle GM\] | \ - \[Force Event Decision\]
" - - HTML += "Status: [pre_action_checks() ? "Ready" : "Suppressed"]

" - - HTML += "Staleness: [staleness] \[Adjust\]
" - HTML += "Danger: [danger] \[Adjust\]

" - - HTML += "Actions available;
" - for(var/datum/gm_action/action in available_actions) - if(action.enabled == FALSE) - continue - HTML += "[action.name] ([english_list(action.departments)]) (weight: [action.get_weight()]) \[Force\]
" - - HTML += "
" - HTML += "All living mobs activity: [metric.assess_all_living_mobs()]%
" - HTML += "All ghost activity: [metric.assess_all_dead_mobs()]%
" - - HTML += "
" - HTML += "Departmental activity;
" - for(var/department in metric.departments) - HTML += " [department] : [metric.assess_department(department)]%
" - - HTML += "
" - HTML += "Activity of players;
" - for(var/mob/player in player_list) - HTML += " [player] ([player.key]) : [metric.assess_player_activity(player)]%
" - - - - HTML +="" - user << browse(HTML, "window=log;size=400x450;border=1;can_resize=1;can_close=1;can_minimize=1") - -/datum/game_master/Topic(href, href_list) - if(..()) - return - - if(!is_admin(usr)) - message_admins("[usr] has attempted to modify the Game Master values without being an admin.") - return - - if(href_list["toggle_time_restrictions"]) - ignore_time_restrictions = !ignore_time_restrictions - message_admins("GM event time restrictions was [ignore_time_restrictions ? "dis" : "en"]abled by [usr.key].") - - if(href_list["force_choose_event"]) - start_action() - message_admins("[usr.key] forced the Game Master to choose an event immediately.") - - if(href_list["suspend"]) - suspended = !suspended - message_admins("GM was [suspended ? "dis" : "en"]abled by [usr.key].") - - if(href_list["adjust_staleness"]) - var/amount = input(usr, "How much staleness should be added or subtracted?", "Game Master") as null|num - if(amount) - adjust_staleness(amount) - message_admins("GM staleness was adjusted by [amount] by [usr.key].") - - if(href_list["adjust_danger"]) - var/amount = input(usr, "How much danger should be added or subtracted?", "Game Master") as null|num - if(amount) - adjust_danger(amount) - message_admins("GM danger was adjusted by [amount] by [usr.key].") - - interact(usr) // To refresh the UI. - -/datum/gm_action/Topic(href, href_list) - if(..()) - return - - if(!is_admin(usr)) - message_admins("[usr] has attempted to force an event without being an admin.") - return - - if(href_list["force"]) - gm.run_action(src) - message_admins("GM event [name] was forced by [usr.key].") \ No newline at end of file diff --git a/code/modules/gamemaster/defines.dm b/code/modules/gamemaster/defines.dm index 2e486ee23b2..5bc17b462c7 100644 --- a/code/modules/gamemaster/defines.dm +++ b/code/modules/gamemaster/defines.dm @@ -1 +1,3 @@ -#define EVENT_BASELINE_WEIGHT 200 \ No newline at end of file +#define EVENT_CHAOS_THRESHOLD_HIGH_IMPACT 25 +#define EVENT_CHAOS_THRESHOLD_MEDIUM_IMPACT 50 +#define EVENT_CHAOS_THRESHOLD_LOW_IMPACT 100 \ No newline at end of file diff --git a/code/modules/gamemaster/event2/event.dm b/code/modules/gamemaster/event2/event.dm new file mode 100644 index 00000000000..7c69f23c96c --- /dev/null +++ b/code/modules/gamemaster/event2/event.dm @@ -0,0 +1,230 @@ +// This object holds the code that is needed to execute an event. +// Code for judging whether doing that event is a good idea or not belongs inside its meta event object. + + +/* + +Important: DO NOT `sleep()` in any of the procs here, or the GM will get stuck. Use callbacks insead. +Also please don't use spawn(), but use callbacks instead. + +Note that there is an important distinction between an event being ended, and an event being finished. +- Ended is for when the actual event is over, regardless of whether an announcement happened or not. +- Finished is for when both the event itself is over, and it was announced. The event will stop being +processed after it is finished. + +For an event to finish, it must have done two things: +- Go through its entire cycle, of start() -> end(), and +- Have the event be announced. +If an event has ended, but the announcement didn't happen, the event will not be finished. +This allows for events that have their announcement happen after the end itself. + +*/ + +// +/datum/event2/event + var/announced = FALSE // Is set to TRUE when `announce()` is called by `process()`. + var/started = FALSE // Is set to TRUE when `start()` is called by `process()`. + var/ended = FALSE // Is set to TRUE when `end()` is called by `process()`. + var/finished = FALSE // Is set to TRUE when `ended` and `announced` are TRUE. + + // `world.time`s when this event started, and finished, for bookkeeping. + var/time_started = null + var/time_finished = null + + // If these are set, the announcement will be delayed by a random time between the lower and upper bounds. + // If the upper bound is not defined, then it will use the lower bound instead. + // Note that this is independant of the event itself, so you can have the announcement happen long after the event ended. + // This may not work if should_announce() is overrided. + var/announce_delay_lower_bound = null + var/announce_delay_upper_bound = null + + // If these are set, the event will be delayed by a random time between the lower and upper bounds. + // If the upper bound is not defined, then it will use the lower bound instead. + // This may not work if should_start() is overrided. + var/start_delay_lower_bound = null + var/start_delay_upper_bound = null + + // If these are set, the event will automatically end at a random time between the lower and upper bounds. + // If the upper bound is not defined, then it will use the lower bound instead. + // This may not work if should_end() is overrided. + var/length_lower_bound = null + var/length_upper_bound = null + + // Set automatically, don't touch. + var/time_to_start = null + var/time_to_announce = null + var/time_to_end = null + + // These are also set automatically, and are provided for events to know what RNG decided for the various durations. + var/start_delay = null + var/announce_delay = null + var/length = null + +// Returns the name of where the event is taking place. +// In the future this might be handy for off-station events. +/datum/event2/event/proc/location_name() + return station_name() + +// Returns the z-levels that are involved with the event. +// In the future this might be handy for off-station events. +/datum/event2/event/proc/get_location_z_levels() + return using_map.station_levels + +// Returns a list of empty turfs in the same area. +/datum/event2/event/proc/find_random_turfs(minimum_free_space = 5, list/specific_areas = list(), ignore_occupancy = FALSE) + var/list/area/grand_list_of_areas = find_random_areas(specific_areas) + + if(!LAZYLEN(grand_list_of_areas)) + return list() + + for(var/thing in grand_list_of_areas) + var/list/A = thing + var/list/turfs = list() + for(var/turf/T in A) + if(!T.check_density()) + turfs += T + + if(turfs.len < minimum_free_space) + continue // Not enough free space. + return turfs + + return list() + +/datum/event2/event/proc/find_random_areas(list/specific_areas = list(), ignore_occupancy = FALSE) + if(!LAZYLEN(specific_areas)) + specific_areas = global.the_station_areas.Copy() + + var/list/area/grand_list_of_areas = get_all_existing_areas_of_types(specific_areas) + . = list() + for(var/thing in shuffle(grand_list_of_areas)) + var/area/A = thing + if(A.forbid_events) + continue + if(!(A.z in get_location_z_levels())) + continue + if(!ignore_occupancy && is_area_occupied(A)) + continue // Occupied. + . += A + +// Starts the event. +/datum/event2/event/proc/execute() + time_started = world.time + + if(announce_delay_lower_bound) + announce_delay = rand(announce_delay_lower_bound, announce_delay_upper_bound ? announce_delay_upper_bound : announce_delay_lower_bound) + time_to_announce = world.time + announce_delay + + if(start_delay_lower_bound) + start_delay = rand(start_delay_lower_bound, start_delay_upper_bound ? start_delay_upper_bound : start_delay_lower_bound) + time_to_start = world.time + start_delay + + if(length_lower_bound) + var/starting_point = time_to_start ? time_to_start : world.time + length = rand(length_lower_bound, length_upper_bound ? length_upper_bound : length_lower_bound) + time_to_end = starting_point + length + + set_up() + +// Called at the very end of the event's lifecycle, or when aborted. +// Don't override this, use `end()` for cleanup instead. +/datum/event2/event/proc/finish() + finished = TRUE + time_finished = world.time + +// Called by admins wanting to stop an event immediately. +/datum/event2/event/proc/abort() + if(!announced) + announce() + if(!ended) // `end()` generally has cleanup procs, so call that. + end() + finish() + +// Called by the GM processer. +/datum/event2/event/process() + // Handle announcement track. + if(!announced && should_announce()) + announced = TRUE + announce() + + // Handle event track. + if(!started) + if(should_start()) + started = TRUE + start() + else + wait_tick() + + if(started && !ended) + if(should_end()) + ended = TRUE + end() + else + event_tick() + + // In order to be finished, the event needs to end, and be announced. + if(ended && announced) + finish() + +/datum/event2/event/Topic(href, href_list) + if(..()) + return + + if(!check_rights(R_ADMIN|R_EVENT|R_DEBUG)) + message_admins("[usr] has attempted to manipulate an event without sufficent privilages.") + return + + if(href_list["abort"]) + abort() + message_admins("Event '[type]' was aborted by [usr.key].") + + // SSgame_master.interact(usr) // To refresh the UI. // VOREStation Edit - We don't use SSgame_master yet. + +/* + * Procs to Override + */ + +// Override this for code to be ran before the event is started. +/datum/event2/event/proc/set_up() + +// Called every tick from the GM system, and determines if the announcement should happen. +// Override this for special logic on when it should be announced, e.g. after `ended` is set to TRUE, +// however be aware that the event cannot finish until this returns TRUE at some point. +/datum/event2/event/proc/should_announce() + if(!time_to_announce) + return TRUE + return time_to_announce <= world.time + +// Override this for code that alerts the crew that the event is happening in some form, e.g. a centcom announcement or some other message. +// If you want them to not know, you can just not override it. +/datum/event2/event/proc/announce() + +// Override for code that runs every few seconds, while the event is waiting for `should_start()` to return TRUE. +// Note that events that have `should_start()` return TRUE at the start will never have this proc called. +/datum/event2/event/proc/wait_tick() + +// Called every tick from the GM system, and determines if the event should offically start. +// Override this for special logic on when it should start. +/datum/event2/event/proc/should_start() + if(!time_to_start) + return TRUE + return time_to_start <= world.time + +// Override this for code to do the actual event. +/datum/event2/event/proc/start() + + +// Override for code that runs every few seconds, while the event is waiting for `should_end()` to return TRUE. +// Note that events that have `should_end()` return TRUE at the start will never have this proc called. +/datum/event2/event/proc/event_tick() + + +// Called every tick from the GM system, and determines if the event should end. +// If this returns TRUE at the very start, then the event ends instantly and `tick()` will never be called. +// Override this for special logic on when it should end, e.g. blob core has to die before event ends. +/datum/event2/event/proc/should_end() + if(!time_to_end) + return TRUE + return time_to_end <= world.time + +// Override this for code to run when the event is over, e.g. cleanup. +/datum/event2/event/proc/end() diff --git a/code/modules/gamemaster/event2/events/cargo/shipping_error.dm b/code/modules/gamemaster/event2/events/cargo/shipping_error.dm new file mode 100644 index 00000000000..eaab32cb8a5 --- /dev/null +++ b/code/modules/gamemaster/event2/events/cargo/shipping_error.dm @@ -0,0 +1,16 @@ +/datum/event2/meta/shipping_error + name = "shipping error" + departments = list(DEPARTMENT_CARGO) + chaos = -10 // A helpful event. + reusable = TRUE + event_type = /datum/event2/event/shipping_error + +/datum/event2/meta/shipping_error/get_weight() + return (metric.count_people_with_job(/datum/job/cargo_tech) + metric.count_people_with_job(/datum/job/qm)) * 30 + +/datum/event2/event/shipping_error/start() + var/datum/supply_order/O = new /datum/supply_order() + O.ordernum = SSsupply.ordernum + O.object = SSsupply.supply_pack[pick(SSsupply.supply_pack)] + O.ordered_by = random_name(pick(MALE,FEMALE), species = "Human") + SSsupply.shoppinglist += O diff --git a/code/modules/gamemaster/event2/events/command/manifest_malfunction.dm b/code/modules/gamemaster/event2/events/command/manifest_malfunction.dm new file mode 100644 index 00000000000..0a19ec7a655 --- /dev/null +++ b/code/modules/gamemaster/event2/events/command/manifest_malfunction.dm @@ -0,0 +1,61 @@ +/datum/event2/meta/manifest_malfunction + name = "manifest_malfunction" + departments = list(DEPARTMENT_COMMAND, DEPARTMENT_SECURITY, DEPARTMENT_EVERYONE) + chaos = 10 + chaotic_threshold = EVENT_CHAOS_THRESHOLD_LOW_IMPACT + event_type = /datum/event2/event/manifest_malfunction + +/datum/event2/meta/manifest_malfunction/get_weight() + var/security = metric.count_people_in_department(DEPARTMENT_SECURITY) + + if(!security || !data_core) + return 0 + + var/command = metric.count_people_with_job(/datum/job/hop) + metric.count_people_with_job(/datum/job/captain) + var/synths = metric.count_people_in_department(DEPARTMENT_SYNTHETIC) + var/everyone = metric.count_people_in_department(DEPARTMENT_EVERYONE) - (synths + security + command) // So they don't get counted twice. + + return (security * 10) + (synths * 20) + (command * 20) + (everyone * 5) + + + +/datum/event2/event/manifest_malfunction + announce_delay_lower_bound = 5 MINUTES + announce_delay_upper_bound = 10 MINUTES + var/records_to_delete = 2 + var/record_class_to_delete = null + +/datum/event2/event/manifest_malfunction/set_up() + record_class_to_delete = pickweight(list("medical" = 10, "security" = 30)) + +/datum/event2/event/manifest_malfunction/announce() + if(prob(30)) + var/message = null + var/author = null + var/rng = rand(1, 2) + switch(rng) + if(1) + author = "Data Breach Alert" + message = "The [record_class_to_delete] record database has suffered from an attack by one or more hackers. \ + They appear to have wiped several records, before disconnecting." + if(2) + author = "Downtime Alert" + message = "The [record_class_to_delete] record database server has suffered a hardware failure, and is no longer functional. \ + A temporary replacement server has been activated, containing recovered data from the main server. \ + A few records became corrupted, and could not be transferred." + command_announcement.Announce(message, author) + +/datum/event2/event/manifest_malfunction/start() + for(var/i = 1 to records_to_delete) + var/datum/data/record/R + + switch(record_class_to_delete) + if("security") + R = safepick(data_core.security) + + if("medical") + R = safepick(data_core.medical) + + if(R) + log_debug("Manifest malfunction event is now deleting [R.fields["name"]]'s [record_class_to_delete] record.") + qdel(R) diff --git a/code/modules/gamemaster/event2/events/command/money_hacker.dm b/code/modules/gamemaster/event2/events/command/money_hacker.dm new file mode 100644 index 00000000000..a1dbd1e18b3 --- /dev/null +++ b/code/modules/gamemaster/event2/events/command/money_hacker.dm @@ -0,0 +1,110 @@ +/datum/event2/meta/money_hacker + name = "money hacker" + departments = list(DEPARTMENT_COMMAND) + chaos = 10 + chaotic_threshold = EVENT_CHAOS_THRESHOLD_LOW_IMPACT + event_type = /datum/event2/event/money_hacker + +/datum/event2/meta/money_hacker/get_weight() + var/command = metric.count_people_with_job(/datum/job/hop) + metric.count_people_with_job(/datum/job/captain) + + if(!command) + return 0 + return 30 + (command * 20) + (all_money_accounts.len * 5) + + + +/datum/event2/event/money_hacker + length_lower_bound = 8 MINUTES + length_upper_bound = 12 MINUTES + var/datum/money_account/targeted_account = null + +/datum/event2/event/money_hacker/set_up() + if(LAZYLEN(all_money_accounts)) + targeted_account = pick(all_money_accounts) + + if(!targeted_account) + log_debug("Money hacker event could not find an account to hack. Aborting.") + abort() + return + +/datum/event2/event/money_hacker/announce() + var/message = "A brute force hack has been detected (in progress since [stationtime2text()]). The target of the attack is: Financial account #[targeted_account.account_number], \ + without intervention this attack will succeed in approximately 10 minutes. Required intervention: temporary suspension of affected accounts until the attack has ceased. \ + Notifications will be sent as updates occur." + var/my_department = "[location_name()] Firewall Subroutines" + + for(var/obj/machinery/message_server/MS in machines) + if(!MS.active) + continue + MS.send_rc_message("Head of Personnel's Desk", my_department, "[message]
", "", "", 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 = "1 January 1970" // Unix epoch. + 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 00000000000..31e88163501 --- /dev/null +++ b/code/modules/gamemaster/event2/events/command/raise_funds.dm @@ -0,0 +1,96 @@ +/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." + SSsupply.points = 0 + 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." + var/points_lost = round(SSsupply.points * rand(0.5, 0.8)) + SSsupply.points -= points_lost + 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." + + // 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) + SSsupply.points += point_reward + 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 00000000000..69b596d7996 --- /dev/null +++ b/code/modules/gamemaster/event2/events/engineering/airlock_failure.dm @@ -0,0 +1,100 @@ +/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) diff --git a/code/modules/gamemaster/event2/events/engineering/blob.dm b/code/modules/gamemaster/event2/events/engineering/blob.dm new file mode 100644 index 00000000000..3ace8f0894e --- /dev/null +++ b/code/modules/gamemaster/event2/events/engineering/blob.dm @@ -0,0 +1,163 @@ +/datum/event2/meta/blob + name = "blob" + departments = list(DEPARTMENT_ENGINEERING, DEPARTMENT_SECURITY, DEPARTMENT_MEDICAL) + chaos = 30 + chaotic_threshold = EVENT_CHAOS_THRESHOLD_HIGH_IMPACT + event_class = "blob" // This makes it so there is no potential for multiple blob events of different types happening in the same round. + event_type = /datum/event2/event/blob + // In the distant future, if a mechanical skill system were to come into being, these vars could be replaced with skill checks so off duty people could count. + var/required_fighters = 2 // Fighters refers to engineering OR security. + var/required_support = 1 // Support refers to doctors AND roboticists, depending on fighter composition. + +/datum/event2/meta/blob/hard + name = "harder blob" + chaos = 40 + event_type = /datum/event2/event/blob/hard_blob + required_fighters = 3 + +/datum/event2/meta/blob/multi_blob + name = "multi blob" + chaos = 60 + event_type = /datum/event2/event/blob/multi_blob + required_fighters = 4 + required_support = 2 + +// For bussing only. +/datum/event2/meta/blob/omni_blob + name = "omni blob" + chaos = 200 + event_type = /datum/event2/event/blob/omni_blob + enabled = FALSE + +/datum/event2/meta/blob/get_weight() + // Count the 'fighters'. + var/list/engineers = metric.get_people_in_department(DEPARTMENT_ENGINEERING) + var/list/security = metric.get_people_in_department(DEPARTMENT_SECURITY) + + if(engineers.len + security.len < required_fighters) + return 0 + + // Now count the 'support'. + var/list/medical = metric.get_people_in_department(DEPARTMENT_MEDICAL) + var/need_medical = FALSE + + var/list/robotics = metric.get_people_with_job(/datum/job/roboticist) + var/need_robotics = FALSE + + // Determine what kind of support might be needed. + for(var/mob/living/L in engineers|security) + if(L.isSynthetic()) + need_robotics = TRUE + else + need_medical = TRUE + + // Medical is more important than robotics, since robits tend to not suffer slow deaths if there isn't a roboticist. + if(medical.len < required_support && need_medical) + return 0 + + // Engineers can sometimes fill in as robotics. This is done in the interest of the event having a chance of not being super rare. + // In the uncertain future, a mechanical skill system check could replace this check here. + if(robotics.len + engineers.len < required_support && need_robotics) + return 0 + + var/fighter_weight = (engineers.len + security.len) * 20 + var/support_weight = (medical.len + robotics.len) * 10 // Not counting engineers as support so they don't cause 30 weight each. + var/chaos_weight = chaos / 2 // Chaos is added as a weight in order to make more chaotic variants be preferred if they are allowed to be picked. + + return fighter_weight + support_weight + chaos_weight + + + +/datum/event2/event/blob + announce_delay_lower_bound = 1 MINUTE + announce_delay_upper_bound = 5 MINUTES + // This could be made into a GLOB accessible list for reuse if needed. + var/list/area/excluded = list( + /area/submap, + /area/shuttle, + /area/crew_quarters, + /area/holodeck, + /area/engineering/engine_room + ) + var/list/open_turfs = list() + var/spawn_blob_type = /obj/structure/blob/core/random_medium + var/number_of_blobs = 1 + var/list/blobs = list() // A list containing weakrefs to blob cores created. Weakrefs mean this event won't interfere with qdel. + +/datum/event2/event/blob/hard_blob + spawn_blob_type = /obj/structure/blob/core/random_hard + +/datum/event2/event/blob/multi_blob + spawn_blob_type = /obj/structure/blob/core/random_hard // Lethargic blobs are boring. + number_of_blobs = 2 + +// For adminbus only. +/datum/event2/event/blob/omni_blob + number_of_blobs = 16 // Someday maybe we can get this to specifically spawn every blob. + +/datum/event2/event/blob/set_up() + open_turfs = find_random_turfs(5 + number_of_blobs) + + if(!open_turfs.len) + log_debug("Blob infestation event: Giving up after failure to find blob spots.") + abort() + +/datum/event2/event/blob/start() + for(var/i = 1 to number_of_blobs) + var/turf/T = pick(open_turfs) + var/obj/structure/blob/core/new_blob = new spawn_blob_type(T) + blobs += weakref(new_blob) + open_turfs -= T // So we can't put two cores on the same tile if doing multiblob. + log_debug("Spawned [new_blob.overmind.blob_type.name] blob at [get_area(new_blob)].") + +/datum/event2/event/blob/should_end() + for(var/WR in blobs) + var/weakref/weakref = WR + if(weakref.resolve()) // If the weakref is resolvable, that means the blob hasn't been deleted yet. + return FALSE + return TRUE // Only end if all blobs die. + +// Normally this does nothing, but is useful if aborted by an admin. +/datum/event2/event/blob/end() + for(var/WR in blobs) + var/weakref/weakref = WR + var/obj/structure/blob/core/B = weakref.resolve() + if(istype(B)) + qdel(B) + +/datum/event2/event/blob/announce() + if(!ended) // Don't announce if the blobs die early. + var/danger_level = 0 + var/list/blob_type_names = list() + var/multiblob = FALSE + for(var/WR in blobs) + var/weakref/weakref = WR + var/obj/structure/blob/core/B = weakref.resolve() + if(!istype(B)) + continue + var/datum/blob_type/blob_type = B.overmind.blob_type + + blob_type_names += blob_type.name + if(danger_level > blob_type.difficulty) // The highest difficulty is used, if multiple blobs are present. + danger_level = blob_type.difficulty + + if(blob_type_names.len > 1) // More than one blob is harder. + danger_level += blob_type_names.len + multiblob = TRUE + + var/list/lines = list() + lines += "Confirmed outbreak of level [7 + danger_level] biohazard[multiblob ? "s": ""] \ + aboard [location_name()]. All personnel must contain the outbreak." + + if(danger_level >= BLOB_DIFFICULTY_MEDIUM) // Tell them what kind of blob it is if it's tough. + lines += "The biohazard[multiblob ? "s have": " has"] been identified as [english_list(blob_type_names)]." + + if(danger_level >= BLOB_DIFFICULTY_HARD) // If it's really hard then tell them where it is so the response occurs faster. + var/turf/T = open_turfs[1] + var/area/A = T.loc + lines += "[multiblob ? "It is": "They are"] suspected to have originated from \the [A]." + + if(danger_level >= BLOB_DIFFICULTY_SUPERHARD) + lines += "Extreme caution is advised." + + command_announcement.Announce(lines.Join("\n"), "Biohazard Alert", new_sound = 'sound/AI/outbreak7.ogg') 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 00000000000..bf1a8dfed6e --- /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/camera_damage.dm b/code/modules/gamemaster/event2/events/engineering/camera_damage.dm new file mode 100644 index 00000000000..c24fbb06f35 --- /dev/null +++ b/code/modules/gamemaster/event2/events/engineering/camera_damage.dm @@ -0,0 +1,41 @@ +/datum/event2/meta/camera_damage + name = "random camera damage" + departments = list(DEPARTMENT_SYNTHETIC, DEPARTMENT_ENGINEERING) + chaos = 5 + chaotic_threshold = EVENT_CHAOS_THRESHOLD_LOW_IMPACT + reusable = TRUE + event_type = /datum/event2/event/camera_damage + +/datum/event2/meta/camera_damage/get_weight() + return 30 + (metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 20) + (metric.count_people_in_department(DEPARTMENT_SYNTHETIC) * 40) + +/datum/event2/event/camera_damage + var/camera_range = 7 + +/datum/event2/event/camera_damage/start() + var/obj/machinery/camera/C = acquire_random_camera() + if(!C) + return + + for(var/obj/machinery/camera/cam in range(camera_range, C)) + if(is_valid_camera(cam)) + cam.wires.UpdateCut(CAMERA_WIRE_POWER, 0) + if(prob(25)) + cam.wires.UpdateCut(CAMERA_WIRE_ALARM, 0) + +/datum/event2/event/camera_damage/proc/acquire_random_camera(var/remaining_attempts = 5) + if(!cameranet.cameras.len) + return + if(!remaining_attempts) + return + + var/obj/machinery/camera/C = pick(cameranet.cameras) + if(is_valid_camera(C)) + return C + // It is very important to use --var and not var-- for recursive calls, as var-- will cause an infinite loop. + return acquire_random_camera(--remaining_attempts) + +/datum/event2/event/camera_damage/proc/is_valid_camera(var/obj/machinery/camera/C) + // Only return a functional camera, not installed in a silicon/hardsuit/circuit/etc, and that exists somewhere players have access + var/turf/T = get_turf(C) + return T && C?.can_use() && istype(C.loc, /turf) && (T.z in using_map.player_levels) \ No newline at end of file diff --git a/code/modules/gamemaster/actions/canister_leak.dm b/code/modules/gamemaster/event2/events/engineering/canister_leak.dm similarity index 69% rename from code/modules/gamemaster/actions/canister_leak.dm rename to code/modules/gamemaster/event2/events/engineering/canister_leak.dm index 670b65c9fce..a48823151c6 100644 --- a/code/modules/gamemaster/actions/canister_leak.dm +++ b/code/modules/gamemaster/event2/events/engineering/canister_leak.dm @@ -1,24 +1,26 @@ -// -// This event chooses a random canister on player levels and breaks it, releasing its contents! -// - -/datum/gm_action/canister_leak - name = "Canister Leak" - departments = list(DEPARTMENT_ENGINEERING) - chaotic = 20 - -/datum/gm_action/canister_leak/get_weight() - return metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 30 - -/datum/gm_action/canister_leak/start() - ..() - // List of all non-destroyed canisters on station levels - var/list/all_canisters = list() - for(var/obj/machinery/portable_atmospherics/canister/C in machines) - if(!C.destroyed && (C.z in using_map.station_levels) && C.air_contents.total_moles >= MOLES_CELLSTANDARD) - all_canisters += C - var/obj/machinery/portable_atmospherics/canister/C = pick(all_canisters) - log_debug("canister_leak event: Canister [C] ([C.x],[C.y],[C.z]) destroyed.") - C.health = 0 - C.healthcheck() - return +// +// This event chooses a random canister on player levels and breaks it, releasing its contents! +// + +/datum/event2/meta/canister_leak + name = "canister leak" + departments = list(DEPARTMENT_ENGINEERING) + chaos = 10 + chaotic_threshold = EVENT_CHAOS_THRESHOLD_LOW_IMPACT + reusable = TRUE + event_type = /datum/event2/event/canister_leak + +/datum/event2/meta/canister_leak/get_weight() + return metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 30 + +/datum/event2/event/canister_leak/start() + // List of all non-destroyed canisters on station levels + var/list/all_canisters = list() + for(var/obj/machinery/portable_atmospherics/canister/C in machines) + if(!C.destroyed && (C.z in using_map.station_levels) && C.air_contents.total_moles >= MOLES_CELLSTANDARD) + all_canisters += C + var/obj/machinery/portable_atmospherics/canister/C = pick(all_canisters) + log_debug("canister_leak event: Canister [C] ([C.x],[C.y],[C.z]) destroyed.") + C.health = 0 + C.healthcheck() + diff --git a/code/modules/gamemaster/actions/carp_migration_vr.dm b/code/modules/gamemaster/event2/events/engineering/carp_migration_vr.dm similarity index 100% rename from code/modules/gamemaster/actions/carp_migration_vr.dm rename to code/modules/gamemaster/event2/events/engineering/carp_migration_vr.dm diff --git a/code/modules/gamemaster/event2/events/engineering/dust.dm b/code/modules/gamemaster/event2/events/engineering/dust.dm new file mode 100644 index 00000000000..1a26118fbc6 --- /dev/null +++ b/code/modules/gamemaster/event2/events/engineering/dust.dm @@ -0,0 +1,19 @@ +/datum/event2/meta/dust + name = "dust" + departments = list(DEPARTMENT_ENGINEERING) + chaos = 10 + chaotic_threshold = EVENT_CHAOS_THRESHOLD_LOW_IMPACT + reusable = TRUE + event_type = /datum/event2/event/dust + +/datum/event2/meta/dust/get_weight() + return metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 20 + + + +/datum/event2/event/dust/announce() + if(prob(33)) + command_announcement.Announce("Dust has been detected on a collision course with \the [location_name()].") + +/datum/event2/event/dust/start() + dust_swarm("norm") diff --git a/code/modules/gamemaster/event2/events/engineering/gas_leak.dm b/code/modules/gamemaster/event2/events/engineering/gas_leak.dm new file mode 100644 index 00000000000..fb8dc41f861 --- /dev/null +++ b/code/modules/gamemaster/event2/events/engineering/gas_leak.dm @@ -0,0 +1,47 @@ +/datum/event2/meta/gas_leak + name = "gas leak" + departments = list(DEPARTMENT_ENGINEERING, DEPARTMENT_SYNTHETIC) + chaos = 10 + chaotic_threshold = EVENT_CHAOS_THRESHOLD_LOW_IMPACT + reusable = TRUE + event_type = /datum/event2/event/gas_leak + +/datum/event2/meta/gas_leak/get_weight() + // Synthetics are counted in higher value because they can wirelessly connect to alarms. + var/engineering_factor = metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 10 + var/synthetic_factor = metric.count_people_in_department(DEPARTMENT_SYNTHETIC) * 30 + return (15 + engineering_factor + synthetic_factor) / (times_ran + 1) + + + +/datum/event2/event/gas_leak + var/potential_gas_choices = list("carbon_dioxide", "sleeping_agent", "phoron", "volatile_fuel") + var/chosen_gas = null + var/turf/chosen_turf = null + +/datum/event2/event/gas_leak/set_up() + chosen_gas = pick(potential_gas_choices) + + var/list/turfs = find_random_turfs() + if(!turfs.len) + log_debug("Gas Leak event failed to find any available turfs to leak into. Aborting.") + abort() + return + chosen_turf = pick(turfs) + +/datum/event2/event/gas_leak/announce() + if(chosen_turf) + command_announcement.Announce("Warning, hazardous [lowertext(gas_data.name[chosen_gas])] gas leak detected in \the [chosen_turf.loc], evacuate the area.", "Hazard Alert") + +/datum/event2/event/gas_leak/start() + // Okay, time to actually put the gas in the room! + // TODO - Would be nice to break a waste pipe perhaps? + // TODO - Maybe having it released from a single point and thus causing airflow to blow stuff around + + // Fow now just add a bunch of it to the air + + var/datum/gas_mixture/air_contents = new + air_contents.temperature = T20C + rand(-50, 50) + air_contents.gas[chosen_gas] = 10 * MOLES_CELLSTANDARD + chosen_turf.assume_air(air_contents) + playsound(chosen_turf, 'sound/effects/smoke.ogg', 75, 1) \ No newline at end of file diff --git a/code/modules/gamemaster/event2/events/engineering/grid_check.dm b/code/modules/gamemaster/event2/events/engineering/grid_check.dm new file mode 100644 index 00000000000..8b081f29e2a --- /dev/null +++ b/code/modules/gamemaster/event2/events/engineering/grid_check.dm @@ -0,0 +1,52 @@ +// New grid check event: +// Very similar to the old one, power goes out in most of the station, however the new feature is the ability for engineering to +// get power back on sooner, if they are able to reach a special machine and initiate a manual reboot. If no one is able to do so, +// it will reboot itself after a few minutes, just like the old one. Bad things happen if there is no grid checker machine protecting +// the powernet when this event fires. + +/datum/event2/meta/grid_check + name = "grid check" + departments = list(DEPARTMENT_ENGINEERING, DEPARTMENT_EVERYONE) + chaos = 10 + chaotic_threshold = EVENT_CHAOS_THRESHOLD_LOW_IMPACT + reusable = TRUE + event_type = /datum/event2/event/grid_check + +// Having the turbines be way over their rated limit makes grid checks more likely. +/datum/event2/meta/grid_check/proc/get_overpower() + var/highest_overpower = 0 + for(var/T in GLOB.all_turbines) + var/obj/machinery/power/generator/turbine = T + var/overpower = max((turbine.effective_gen / turbine.max_power) - 1, 0) + if(overpower > highest_overpower) + highest_overpower = overpower + return highest_overpower + +/datum/event2/meta/grid_check/get_weight() + var/population_factor = metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 10 + var/overpower_factor = 50 * get_overpower() // Will be 0 if not overloaded at all, and 50 if turbines are outputting twice as much as rated. + return (20 + population_factor + overpower_factor) / (times_ran + 1) + + + +/datum/event2/event/grid_check + var/obj/machinery/power/generator/engine // The turbine that will send a power spike. + +/datum/event2/event/grid_check/set_up() + // Find the turbine being pushed the most. + var/obj/machinery/power/generator/most_stressed_turbine = null + for(var/T in GLOB.all_turbines) + var/obj/machinery/power/generator/turbine = T + if(!most_stressed_turbine) + most_stressed_turbine = turbine + else if(turbine.effective_gen > most_stressed_turbine.effective_gen) + most_stressed_turbine = turbine + engine = most_stressed_turbine + +/datum/event2/event/grid_check/start() + // This sets off a chain of events that lead to the actual grid check (or perhaps worse). + // First, the Supermatter engine makes a power spike. + if(engine) + engine.power_spike() + // After that, the engine checks if a grid checker exists on the same powernet, and if so, it triggers a blackout. + // If not, lots of stuff breaks. See code/modules/power/generator.dm for that piece of code. diff --git a/code/modules/gamemaster/event2/events/engineering/meteor_defense.dm b/code/modules/gamemaster/event2/events/engineering/meteor_defense.dm new file mode 100644 index 00000000000..378c6b42fea --- /dev/null +++ b/code/modules/gamemaster/event2/events/engineering/meteor_defense.dm @@ -0,0 +1,83 @@ +// This event gives the station an advance warning about meteors, so that they can prepare in various ways. + +/datum/event2/meta/meteor_defense + name = "meteor defense" + departments = list(DEPARTMENT_ENGINEERING, DEPARTMENT_CARGO) + chaos = 50 + chaotic_threshold = EVENT_CHAOS_THRESHOLD_HIGH_IMPACT + event_class = "meteor defense" + event_type = /datum/event2/event/meteor_defense + +/datum/event2/meta/meteor_defense/get_weight() + // Engineers count as 20. + var/engineers = metric.count_people_in_department(DEPARTMENT_ENGINEERING) + if(engineers < 3) // There -must- be at least three engineers for this to be possible. + return 0 + + . = engineers * 20 + + // Cargo and AI/borgs count as 10. + var/cargo = metric.count_people_with_job(/datum/job/cargo_tech) + metric.count_people_with_job(/datum/job/qm) + var/bots = metric.count_people_in_department(DEPARTMENT_SYNTHETIC) + + . += (cargo + bots) * 10 + + +/datum/event2/event/meteor_defense + start_delay_lower_bound = 10 MINUTES + start_delay_upper_bound = 15 MINUTES + var/soon_announced = FALSE + var/direction = null // Actual dir used for which side the meteors come from. + var/dir_text = null // Direction shown in the announcement. + var/list/meteor_types = null + var/waves = null // How many times to send meteors. + var/last_wave_time = null // world.time of latest wave. + var/wave_delay = 10 SECONDS + var/wave_upper_bound = 8 // Max amount of meteors per wave. + var/wave_lower_bound = 4 // Min amount. + +/datum/event2/event/meteor_defense/proc/set_meteor_types() + meteor_types = meteors_threatening.Copy() + +/datum/event2/event/meteor_defense/set_up() + direction = pick(cardinal) // alldirs doesn't work with current meteor code unfortunately. + waves = rand(3, 6) + switch(direction) + if(NORTH) + dir_text = "aft" // For some reason this is needed. + if(SOUTH) + dir_text = "fore" + if(EAST) + dir_text = "port" + if(WEST) + dir_text = "starboard" + set_meteor_types() + +/datum/event2/event/meteor_defense/announce() + var/announcement = "Meteors are expected to approach from the [dir_text] side, in approximately [DisplayTimeText(time_to_start - world.time, 60)]." + command_announcement.Announce(announcement, "Meteor Alert", new_sound = 'sound/AI/meteors.ogg') + +/datum/event2/event/meteor_defense/wait_tick() + if(!soon_announced) + if((time_to_start - world.time) <= 5 MINUTES) + soon_announced = TRUE + var/announcement = "The incoming meteors are expected to approach from the [dir_text] side. \ + ETA to arrival is approximately [DisplayTimeText(time_to_start - world.time, 60)]." + command_announcement.Announce(announcement, "Meteor Alert - Update") + +/datum/event2/event/meteor_defense/start() + command_announcement.Announce("Incoming meteors approach from \the [dir_text] side!", "Meteor Alert - Update") + +/datum/event2/event/meteor_defense/event_tick() + if(world.time > last_wave_time + wave_delay) + last_wave_time = world.time + waves-- + message_admins("[waves] more wave\s of meteors remain.") + // Dir is reversed because the direction describes where meteors are going, not what side it's gonna hit. + spawn_meteors(rand(wave_upper_bound, wave_lower_bound), meteor_types, reverse_dir[direction]) + +/datum/event2/event/meteor_defense/should_end() + return waves <= 0 + +/datum/event2/event/meteor_defense/end() + command_announcement.Announce("\The [location_name()] will clear the incoming meteors in a moment.", "Meteor Alert - Update") diff --git a/code/modules/gamemaster/event2/events/engineering/spacevine.dm b/code/modules/gamemaster/event2/events/engineering/spacevine.dm new file mode 100644 index 00000000000..5dd5d6b3a1e --- /dev/null +++ b/code/modules/gamemaster/event2/events/engineering/spacevine.dm @@ -0,0 +1,17 @@ +/datum/event2/meta/spacevine + name = "space-vine infestation" + departments = list(DEPARTMENT_ENGINEERING) + chaos = 10 // There's a really rare chance of vines getting something awful like phoron atmosphere but thats not really controllable. + chaotic_threshold = EVENT_CHAOS_THRESHOLD_MEDIUM_IMPACT + event_type = /datum/event2/event/spacevine + +/datum/event2/meta/spacevine/get_weight() + return 20 + (metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 20) + (metric.count_people_in_department(DEPARTMENT_EVERYONE) * 10) + + + +/datum/event2/event/spacevine/announce() + level_seven_announcement() + +/datum/event2/event/spacevine/start() + spacevine_infestation() 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 00000000000..29d12c85247 --- /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 00000000000..0b02e78b3de --- /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 new file mode 100644 index 00000000000..6da19c32ccd --- /dev/null +++ b/code/modules/gamemaster/event2/events/everyone/comms_blackout.dm @@ -0,0 +1,43 @@ +/datum/event2/meta/comms_blackout + name = "communications blackout" + departments = list(DEPARTMENT_EVERYONE) // It's not an engineering event because engineering can't do anything to help . . . for now. + chaos = 10 + chaotic_threshold = EVENT_CHAOS_THRESHOLD_MEDIUM_IMPACT + reusable = TRUE + event_type = /datum/event2/event/comms_blackout + +/datum/event2/meta/comms_blackout/get_weight() + return 50 + metric.count_people_in_department(DEPARTMENT_EVERYONE) * 5 + + + +/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(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, "[alert]") + to_chat(A, "
") + +/datum/event2/event/comms_blackout/start() + if(prob(50)) + // One in two chance for the radios to turn i%t# t&_)#%, which can be more alarming than radio silence. + log_debug("Doing partial outage of telecomms.") + for(var/obj/machinery/telecomms/processor/P in telecomms_list) + P.emp_act(1) + else + // Otherwise just shut everything down, madagascar style. + log_debug("Doing complete outage of telecomms.") + for(var/obj/machinery/telecomms/T in telecomms_list) + T.emp_act(1) + + // 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 new file mode 100644 index 00000000000..6467646f1b4 --- /dev/null +++ b/code/modules/gamemaster/event2/events/everyone/electrical_fault.dm @@ -0,0 +1,99 @@ +// 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" + departments = list(DEPARTMENT_EVERYONE) + chaos = 10 + chaotic_threshold = EVENT_CHAOS_THRESHOLD_LOW_IMPACT + event_type = /datum/event2/event/electrical_fault + +/datum/event2/meta/electrical_fault/get_weight() + return 10 + (metric.count_people_in_department(DEPARTMENT_EVERYONE) * 5) + + +/datum/event2/event/electrical_fault + start_delay_lower_bound = 30 SECONDS + start_delay_upper_bound = 1 MINUTE + length_lower_bound = 20 SECONDS + length_upper_bound = 40 SECONDS + var/max_apcs_per_tick = 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_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() + valid_z_levels -= using_map.sealed_levels // Space levels only please! + + valid_apcs = list() + for(var/obj/machinery/power/apc/A in global.machines) + if(A.z in valid_z_levels) + valid_apcs += A + +/datum/event2/event/electrical_fault/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("ELECTRICAL EVENT: No valid APCs found for electrical fault event. Aborting.") + abort() + return + + var/list/picked_apcs = list() + for(var/i = 1 to max_apcs_per_tick) + picked_apcs |= pick(valid_apcs) + + for(var/A in picked_apcs) + affect_apc(A) + +/datum/event2/event/electrical_fault/end() + command_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. + // 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 + + // 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(30)) + A.overload_lighting() + 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(5)) + A.emagged = TRUE + A.update_icon() + log_debug("ELECTRICAL EVENT: Emagged \the [A].") + playsound(A, 'sound/machines/chime.ogg', 50, 1) + apcs_emagged++ + 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 00000000000..37a0e2daeb3 --- /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) + +/datum/event2/event/gravity/end() + for(var/area/A in all_areas) + if(A.z in get_location_z_levels()) + A.gravitychange(TRUE) + + 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 00000000000..f51456f3628 --- /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 00000000000..b56687283c4 --- /dev/null +++ b/code/modules/gamemaster/event2/events/everyone/pda_spam.dm @@ -0,0 +1,137 @@ +/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) 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 00000000000..ebd137b404f --- /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 00000000000..fe3b58be8c9 --- /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/solar_storm.dm b/code/modules/gamemaster/event2/events/everyone/solar_storm.dm new file mode 100644 index 00000000000..b73df66076f --- /dev/null +++ b/code/modules/gamemaster/event2/events/everyone/solar_storm.dm @@ -0,0 +1,52 @@ +/datum/event2/meta/solar_storm + name = "solar storm" + reusable = TRUE + event_type = /datum/event2/event/solar_storm + +/datum/event2/meta/solar_storm/get_weight() + var/population_factor = metric.count_people_in_department(DEPARTMENT_ENGINEERING) * 10 + var/space_factor = metric.count_all_space_mobs() * 50 + return (20 + population_factor + space_factor) / (times_ran + 1) + + +/datum/event2/event/solar_storm + start_delay_lower_bound = 1 MINUTE + start_delay_upper_bound = 1 MINUTE + length_lower_bound = 2 MINUTES + length_upper_bound = 4 MINUTES + var/base_solar_gen_rate = null + +/datum/event2/event/solar_storm/announce() + command_announcement.Announce("A solar storm has been detected approaching \the [station_name()]. \ + Please halt all EVA activites immediately and return to the interior of the station.", "Anomaly Alert", new_sound = 'sound/AI/radiation.ogg') + adjust_solar_output(1.5) + +/datum/event2/event/solar_storm/start() + command_announcement.Announce("The solar storm has reached the station. Please refain from EVA and remain inside the station until it has passed.", "Anomaly Alert") + adjust_solar_output(5) + +/datum/event2/event/solar_storm/event_tick() + radiate() + +/datum/event2/event/solar_storm/end() + command_announcement.Announce("The solar storm has passed the station. It is now safe to resume EVA activities. \ + Please report to medbay if you experience any unusual symptoms.", "Anomaly Alert") + adjust_solar_output(1) + +/datum/event2/event/solar_storm/proc/adjust_solar_output(var/mult = 1) + if(isnull(base_solar_gen_rate)) + base_solar_gen_rate = GLOB.solar_gen_rate + GLOB.solar_gen_rate = mult * base_solar_gen_rate + +/datum/event2/event/solar_storm/proc/radiate() + // Note: Too complicated to be worth trying to use the radiation system for this. Its only in space anyway, so we make an exception in this case. + for(var/mob/living/L in player_list) + var/turf/T = get_turf(L) + if(!T) + continue + + if(!istype(T.loc,/area/space) && !istype(T,/turf/space)) //Make sure you're in a space area or on a space turf + continue + + //Todo: Apply some burn damage from the heat of the sun. Until then, enjoy some moderate radiation. + L.rad_act(rand(15, 30)) 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 00000000000..2d83209612c --- /dev/null +++ b/code/modules/gamemaster/event2/events/everyone/sudden_weather_shift.dm @@ -0,0 +1,45 @@ +/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) 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 00000000000..4c462d06d6c --- /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/legacy.dm b/code/modules/gamemaster/event2/events/legacy/legacy.dm new file mode 100644 index 00000000000..6f70cafa851 --- /dev/null +++ b/code/modules/gamemaster/event2/events/legacy/legacy.dm @@ -0,0 +1,63 @@ +// This is a somewhat special type of event, that bridges to the old event datum and makes it work with the new system. +// It acts as a compatability layer between the old event, and the new GM system. +// This is possible because the new datum is mostly a superset of the old one. +/datum/event2/event/legacy + var/datum/event/legacy_event = null + + // Used to emulate legacy's `activeFor` tick counter. + var/tick_count = 0 + + // How 'severe' the legacy event should be. This should only be used for legacy events, as severity is an outdated concept for the GM system. + var/severity = EVENT_LEVEL_MODERATE + +/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.severity = severity + 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) + ..() + +// 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 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 00000000000..27f5284020e --- /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 00000000000..58791d2ad15 --- /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 00000000000..78b1eac69c4 --- /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 00000000000..12f08f3edd0 --- /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/drill_announcement.dm b/code/modules/gamemaster/event2/events/security/drill_announcement.dm new file mode 100644 index 00000000000..271e2d75f17 --- /dev/null +++ b/code/modules/gamemaster/event2/events/security/drill_announcement.dm @@ -0,0 +1,22 @@ +/datum/event2/meta/security_drill + name = "security drill" + departments = list(DEPARTMENT_SECURITY, DEPARTMENT_EVERYONE) + chaotic_threshold = EVENT_CHAOS_THRESHOLD_HIGH_IMPACT // Don't run if we just got hit by meteors. + event_type = /datum/event2/event/security_drill + +/datum/event2/meta/security_drill/get_weight() + var/sec = metric.count_people_in_department(DEPARTMENT_SECURITY) + var/everyone = metric.count_people_in_department(DEPARTMENT_EVERYONE) + + if(!sec) // If there's no security, then there is no drill. + return 0 + if(everyone - sec < 0) // If there's no non-sec, then there is no drill. + return 0 + + // Each security player adds +5 weight, while non-security adds +1.5. + return (sec * 5) + ((everyone - sec) * 1.5) + +/datum/event2/event/security_drill/announce() + command_announcement.Announce("[pick("A NanoTrasen security director", "A Vir-Gov correspondant", "Local Sif authoritiy")] \ + has advised the enactment of [pick("a rampant wildlife", "a fire", "a hostile boarding", \ + "a bomb", "an emergent intelligence")] drill with the personnel onboard \the [location_name()].", "Security Advisement") 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 00000000000..7c5127c26cb --- /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 00000000000..da40b3262f6 --- /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/security_advisement.dm b/code/modules/gamemaster/event2/events/security/security_advisement.dm new file mode 100644 index 00000000000..6a3764e40e6 --- /dev/null +++ b/code/modules/gamemaster/event2/events/security/security_advisement.dm @@ -0,0 +1,93 @@ +/datum/event2/meta/security_screening + name = "security screening" + departments = list(DEPARTMENT_SECURITY, DEPARTMENT_EVERYONE) + chaotic_threshold = EVENT_CHAOS_THRESHOLD_HIGH_IMPACT // So this won't get called in the middle of a crisis. + event_type = /datum/event2/event/security_screening + +/datum/event2/meta/security_screening/get_weight() + . = 0 + var/sec = metric.count_people_in_department(DEPARTMENT_SECURITY) + if(!sec < 2) + return 0 // Can't screen with no security. + . += sec * 10 + . += metric.count_people_in_department(DEPARTMENT_EVERYONE) * 2 + + // Having ""suspecious"" people present makes this more likely to be picked. + var/suspicious_people = 0 + suspicious_people += metric.count_all_of_specific_species(SPECIES_PROMETHEAN) * 20 + suspicious_people += metric.count_all_of_specific_species(SPECIES_UNATHI) * 10 + suspicious_people += metric.count_all_of_specific_species(SPECIES_ZADDAT) * 10 + suspicious_people += metric.count_all_of_specific_species(SPECIES_SKRELL) * 5 // Not sure why skrell are so high. + suspicious_people += metric.count_all_of_specific_species(SPECIES_TAJ) * 5 + suspicious_people += metric.count_all_of_specific_species(SPECIES_TESHARI) * 5 + suspicious_people += metric.count_all_of_specific_species(SPECIES_HUMAN_VATBORN) * 5 + suspicious_people += metric.count_all_FBPs_of_kind(FBP_DRONE) * 20 + suspicious_people += metric.count_all_FBPs_of_kind(FBP_POSI) * 10 + if(!suspicious_people) + return 0 + . += suspicious_people + +/datum/event2/event/security_screening + var/victim = null + var/list/species_weights = list( + SPECIES_SKRELL = 9, + SPECIES_UNATHI = 15, + SPECIES_HUMAN_VATBORN = 6, + SPECIES_TESHARI = 2, + SPECIES_TAJ = 3, + SPECIES_DIONA = 1, + SPECIES_ZADDAT = 25, + SPECIES_PROMETHEAN = 30 + ) + + var/list/synth_weights = list( + FBP_CYBORG = 15, + FBP_DRONE = 30, + FBP_POSI = 25 + ) + +/datum/event2/event/security_screening/set_up() + var/list/end_weights = list() + + // First pass makes popular things more likely to get picked, e.g. 5 prommies vs 1 drone. + for(var/species_name in species_weights) + var/give_weight = 0 + for(var/datum/data/record/R in data_core.general) + if(R.fields["species"] == species_name) + give_weight += species_weights[species_name] + + end_weights[species_name] = give_weight + + for(var/bot_type in synth_weights) + var/give_weight = 0 + for(var/datum/data/record/R in data_core.general) + if(R.fields["brain_type"] == bot_type) + give_weight += synth_weights[bot_type] + + end_weights[bot_type] = give_weight + + // Second pass eliminates things that don't exist on the station. + // It's possible to choose something like drones when all the drones are AFK. This prevents that from happening. + while(end_weights.len) // Keep at it until we find someone or run out of possibilities. + var/victim_chosen = pickweight(end_weights) + + if(victim_chosen in synth_weights) + if(metric.count_all_FBPs_of_kind(victim_chosen) > 0) + victim = victim_chosen + break + else + if(metric.count_all_of_specific_species(victim_chosen) > 0) + victim = victim_chosen + break + if(!victim) + end_weights -= victim_chosen + + if(!victim) + log_debug("Security Screening event failed to find anyone to screen. Aborting.") + abort() + return + +/datum/event2/event/security_screening/announce() + command_announcement.Announce("[pick("A nearby Navy vessel", "A Solar official", "A Vir-Gov official", "A NanoTrasen board director")] has \ + requested the screening of [pick("every other", "every", "suspicious", "willing")] [victim] \ + personnel onboard \the [location_name()].", "Security Advisement") 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 00000000000..23dc61c6739 --- /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 00000000000..d04d350234a --- /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 00000000000..39be6d8412d --- /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 00000000000..283333c6796 --- /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 new file mode 100644 index 00000000000..afcf20ebc8b --- /dev/null +++ b/code/modules/gamemaster/event2/events/synthetic/ion_storm.dm @@ -0,0 +1,71 @@ +/datum/event2/meta/ion_storm + name = "ion storm" + departments = list(DEPARTMENT_SYNTHETIC) + chaos = 40 + chaotic_threshold = EVENT_CHAOS_THRESHOLD_MEDIUM_IMPACT + event_type = /datum/event2/event/ion_storm + +/datum/event2/meta/ion_storm/get_weight() + var/bots = metric.count_people_in_department(DEPARTMENT_SYNTHETIC) + var/weight = 5 + (bots * 40) // A small chance even if no synths are on, since it can still emag beepsky. + return weight + + +/datum/event2/event/ion_storm + 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. + for(var/mob/living/silicon/target in silicon_mob_list) + if(target.z in get_location_z_levels()) + // Don't ion law drons. + if(istype(target, /mob/living/silicon/robot/drone)) + continue + + // Or borgs with an AI (they'll get their AI's ion law anyways). + if(istype(target, /mob/living/silicon/robot)) + var/mob/living/silicon/robot/R = target + if(R.connected_ai) + continue + if(R.shell) + continue + + // Crew member names, and excluding off station antags, are handled by `generate_ion_law()` automatically. + var/law = target.generate_ion_law() + target.add_ion_law(law) + target.show_laws() + + // Emag bots. + for(var/mob/living/bot/B in mob_list) + if(B.z in get_location_z_levels()) + if(prob(bot_emag_chance)) + B.emag_act(1) + + // Messaging server spam filters. + // This might be better served as a seperate event since it seems more like a hacker attack than a natural occurance. + if(message_servers) + for(var/obj/machinery/message_server/MS in message_servers) + if(MS.z in get_location_z_levels()) + MS.spamfilter.Cut() + for (var/i = 1, i <= MS.spamfilter_limit, i++) + MS.spamfilter += pick("warble","help","almach","ai","liberty","freedom","drugs", "[using_map.station_short]", \ + "admin","sol","security","meow","_","monkey","-","moron","pizza","message","spam",\ + "director", "Hello", "Hi!"," ","nuke","crate","taj","xeno") + +/datum/event2/event/ion_storm/announce() + 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/gamemaster/event2/meta.dm b/code/modules/gamemaster/event2/meta.dm new file mode 100644 index 00000000000..3923f857444 --- /dev/null +++ b/code/modules/gamemaster/event2/meta.dm @@ -0,0 +1,81 @@ +// The 'meta' object contains information about its assigned 'action' object, like what departments it will affect. +// It is directly held inside the Game Master Event System. + +// The code for actually executing an event should go inside the event object instead. +/datum/event2/meta + // Name used for organization, shown in the debug verb for the GM system. + // If null, the meta event will be discarded when the GM system initializes, so it is safe to use nameless subtypes for inheritence. + var/name = null + + // If FALSE, the GM system won't pick this. + // Some events set this to FALSE after running, to avoid running twice. + var/enabled = TRUE + + // What departments the event attached might affect. + var/list/departments = list(DEPARTMENT_EVERYONE) + + // A guess on how disruptive to a round the event might be. If the action is chosen, the GM's + // 'danger' score is increased by this number. + // Negative numbers could be used to signify helpful events. + var/chaos = 0 + + // A threshold the GM will use alongside its 'danger' score, to determine if it should pass + // over the event associated with this object. The decision is based on + var/chaotic_threshold = null + + // If true, the event won't have it's `enabled` var set to FALSE when ran by the GM system. + var/reusable = FALSE + + // A string used to identify a 'class' of similar events. + // If the event is not reusable, than all events sharing the same class are disabled. + // Useful if you only ever want one event per round while having a lot of different subtypes of the event. + var/event_class = null + + // Counter for how many times this event has been picked by the GM. + // Can be used to make event repeats discouraged but not forbidden by adjusting the weight based on it. + var/times_ran = 0 + + // The type path to the event associated with this meta object. + // When the GM chooses this event, a new instance is made. + // Seperate instances allow for multiple concurrent events without sharing state, e.g. two blobs. + var/event_type = null + + +// Called by the GM system to actually start an event. +/datum/event2/meta/proc/make_event() + var/datum/event2/event/E = new event_type() + E.execute() + return E + +// Returns a TRUE or FALSE for if the GM system should be able to pick this event. +// Can be extended to check for more than just `enabled` later. +/datum/event2/meta/proc/can_pick() + return enabled + +/* + * Procs to Override + */ + +// Returns a number that determines how likely it is for the event to be picked over others. +// Individual events should override this for their own weights. +/datum/event2/meta/proc/get_weight() + return 0 + + +/datum/event2/meta/Topic(href, href_list) + if(..()) + return + + if(!check_rights(R_ADMIN|R_EVENT|R_DEBUG)) + message_admins("[usr] has attempted to manipulate an event without sufficent privilages.") + return + + if(href_list["force"]) + // SSevent_ticker.start_event(event_type) // VOREStation Edit - We don't use SSgame_master yet. + message_admins("Event '[name]' was forced by [usr.key].") + + if(href_list["toggle"]) + enabled = !enabled + message_admins("Event '[name]' was toggled [enabled ? "on" : "off"] by [usr.key].") + + // SSgame_master.interact(usr) // To refresh the UI. // VOREStation Edit - We don't use SSgame_master yet. \ No newline at end of file diff --git a/code/modules/gamemaster/game_master.dm b/code/modules/gamemaster/game_master.dm deleted file mode 100644 index 7220cb11a91..00000000000 --- a/code/modules/gamemaster/game_master.dm +++ /dev/null @@ -1,160 +0,0 @@ -// This is a sort of successor to the various event systems created over the years. It is designed to be just a tad smarter than the -// previous ones, checking various things like player count, department size and composition, individual player activity, -// individual player (IC) skill, and such, in order to try to choose the best actions to take in order to add spice or variety to -// the round. - -/datum/game_master - var/suspended = TRUE // If true, it will not do anything. - var/ignore_time_restrictions = FALSE// Useful for debugging without needing to wait 20 minutes each time. - var/list/available_actions = list() // A list of 'actions' that the GM has access to, to spice up a round, such as events. - var/danger = 0 // The GM's best guess at how chaotic the round is. High danger makes it hold back. - var/staleness = -20 // Determines liklihood of the GM doing something, increases over time. - var/danger_modifier = 1 // Multiplier for how much 'danger' is accumulated. - var/staleness_modifier = 1 // Ditto. Higher numbers generally result in more events occuring in a round. - var/ticks_completed = 0 // Counts amount of ticks completed. Note that this ticks once a minute. - var/next_action = 0 // Minimum amount of time of nothingness until the GM can pick something again. - var/last_department_used = null // If an event was done for a specific department, it is written here, so it doesn't do it again. - -/datum/game_master/New() - ..() - available_actions = init_subtypes(/datum/gm_action) - for(var/datum/gm_action/action in available_actions) - action.gm = src - - var/config_setup_delay = TRUE - spawn(0) - while(config_setup_delay) - if(config) - config_setup_delay = FALSE - if(config.enable_game_master) - suspended = FALSE - next_action = world.time + rand(15 MINUTES, 25 MINUTES) - else - sleep(30 SECONDS) - -/datum/game_master/process() - if(ticker && ticker.current_state == GAME_STATE_PLAYING && !suspended) - adjust_staleness(1) - adjust_danger(-1) - ticks_completed++ - - var/global_afk = metric.assess_all_living_mobs() - global_afk -= 100 - global_afk = abs(global_afk) - global_afk = round(global_afk / 100, 0.1) - adjust_staleness(global_afk) // Staleness increases faster if more people are less active. - - if(world.time < next_action && prob(staleness * 2) ) - log_debug("Game Master going to start something.") - start_action() - -// This is run before committing to an action/event. -/datum/game_master/proc/pre_action_checks() - if(!ticker || ticker.current_state != GAME_STATE_PLAYING) - log_debug("Game Master unable to start event: Ticker is nonexistant, or the game is not ongoing.") - return FALSE - if(suspended) - return FALSE - if(ignore_time_restrictions) - return TRUE - if(world.time < next_action) // Sanity. - log_debug("Game Master unable to start event: Time until next action is approximately [round((next_action - world.time) / (1 MINUTE))] minute(s)") - return FALSE - // Last minute antagging is bad for humans to do, so the GM will respect the start and end of the round. - var/mills = round_duration_in_ticks - var/mins = round((mills % 36000) / 600) - var/hours = round(mills / 36000) - - if(hours < 1 && mins <= 20) // Don't do anything for the first twenty minutes of the round. - log_debug("Game Master unable to start event: It is too early.") - return FALSE - if(hours >= 2 && mins >= 40) // Don't do anything in the last twenty minutes of the round, as well. - log_debug("Game Master unable to start event: It is too late.") - return FALSE - return TRUE - -/datum/game_master/proc/start_action() - if(!pre_action_checks()) // Make sure we're not doing last minute events, or early events. - return - log_debug("Game Master now starting action decision.") - var/list/most_active_departments = metric.assess_all_departments(3, list(last_department_used)) - var/list/best_actions = decide_best_action(most_active_departments) - - if(best_actions && best_actions.len) - var/list/weighted_actions = list() - for(var/datum/gm_action/action in best_actions) - if(action.chaotic > danger) - continue // We skip dangerous events when bad stuff is already occuring. - weighted_actions[action] = action.get_weight() - - var/datum/gm_action/choice = pickweight(weighted_actions) - if(choice) - log_debug("[choice.name] was chosen by the Game Master, and is now being ran.") - run_action(choice) - -/datum/game_master/proc/run_action(var/datum/gm_action/action) - action.set_up() - action.start() - action.announce() - if(action.chaotic) - danger += action.chaotic - if(action.length) - spawn(action.length) - action.end() - next_action = world.time + rand(5 MINUTES, 20 MINUTES) - last_department_used = action.departments[1] - - -/datum/game_master/proc/decide_best_action(var/list/most_active_departments) - if(!most_active_departments.len) // Server's empty? - log_debug("Game Master failed to find any active departments.") - return list() - - var/list/best_actions = list() // List of actions which involve the most active departments. - if(most_active_departments.len >= 2) - for(var/datum/gm_action/action in available_actions) - if(!action.enabled) - continue - // Try to incorporate an action with the top two departments first. - if(most_active_departments[1] in action.departments && most_active_departments[2] in action.departments) - best_actions.Add(action) - log_debug("[action.name] is being considered because both most active departments are involved.") - - if(best_actions.len) // We found something for those two, let's do it. - return best_actions - - // Otherwise we probably couldn't find something for the second highest group, so let's ignore them. - for(var/datum/gm_action/action in available_actions) - if(!action.enabled) - continue - if(most_active_departments[1] in action.departments) - best_actions.Add(action) - log_debug("[action.name] is being considered because the most active department is involved.") - - if(best_actions.len) // Found something for the one guy. - return best_actions - - // At this point we should expand our horizons. - for(var/datum/gm_action/action in available_actions) - if(!action.enabled) - continue - if(DEPARTMENT_EVERYONE in action.departments) - best_actions.Add(action) - log_debug("[action.name] is being considered because it involves everyone.") - - if(best_actions.len) // Finally, perhaps? - return best_actions - - // Just give a random event if for some reason it still can't make up its mind. - for(var/datum/gm_action/action in available_actions) - if(!action.enabled) - continue - best_actions.Add(action) - log_debug("[action.name] is being considered because everything else failed.") - - if(best_actions.len) // Finally, perhaps? - return best_actions - else - log_debug("Game Master failed to find a suitable event, something very wrong is going on.") - - diff --git a/code/modules/gamemaster/helpers.dm b/code/modules/gamemaster/helpers.dm deleted file mode 100644 index da05cb1b20c..00000000000 --- a/code/modules/gamemaster/helpers.dm +++ /dev/null @@ -1,9 +0,0 @@ -// Tell the game master that something dangerous happened, e.g. someone dying. -/datum/game_master/proc/adjust_danger(var/amt) - amt = amt * danger_modifier - danger = round( CLAMP(danger + amt, 0, 1000), 0.1) - -// Tell the game master that something interesting happened. -/datum/game_master/proc/adjust_staleness(var/amt) - amt = amt * staleness_modifier - staleness = round( CLAMP(staleness + amt, -50, 200), 0.1) \ No newline at end of file diff --git a/code/modules/hydroponics/spreading/spreading.dm b/code/modules/hydroponics/spreading/spreading.dm index 53e4e87627a..e3ac11ed76b 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 3900aaaa97e..ba3678295fe 100644 --- a/code/modules/metric/count.dm +++ b/code/modules/metric/count.dm @@ -21,3 +21,47 @@ if(assess_player_activity(L) >= cutoff) num++ return num + +// Gives a count of how many human mobs of a specific species are on the station. +// Note that `ignore_synths` makes this proc ignore posibrains and drones, but NOT cyborgs, as they are still the same species in-universe. +/datum/metric/proc/count_all_of_specific_species(species_name, ignore_synths = TRUE, cutoff = 75, respect_z = TRUE) + var/num = 0 + for(var/mob/living/carbon/human/H in player_list) + if(respect_z && !(H.z in using_map.station_levels)) + continue + if(ignore_synths && H.isSynthetic() && H.get_FBP_type() != FBP_CYBORG) + continue + if(H.species.name == species_name) + if(assess_player_activity(H) >= cutoff) + num++ + return num + +// Gives a count of how many FBPs of a specific type there are on the station. +/datum/metric/proc/count_all_FBPs_of_kind(desired_FBP_class, cutoff = 75, respect_z = TRUE) + var/num = 0 + for(var/mob/living/carbon/human/H in player_list) + if(respect_z && !(H.z in using_map.station_levels)) + continue + if(H.get_FBP_type() != desired_FBP_class) + continue + if(assess_player_activity(H) >= cutoff) + num++ + return num + +// Like above, but for all FBPs. +/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 + + +/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 acb58101f14..89a0f78ed96 100644 --- a/code/modules/metric/department.dm +++ b/code/modules/metric/department.dm @@ -29,44 +29,88 @@ return DEPARTMENT_UNKNOWN // Welp. +// Similar to above, but gets the actual job. Note that it returns the job datum itself, or null. +/datum/metric/proc/guess_job(mob/M) + // Like before, records are the most reliable way. + var/datum/data/record/R = find_general_record("name", M.real_name) + if(R) // They got a record, now find the job datum. + var/datum/job/J = SSjob.get_job(R.fields["real_rank"]) + if(istype(J)) + return J + + // Try the mind. + if(M.mind) + var/datum/job/J = SSjob.get_job(M.mind.assigned_role) + if(istype(J)) + return J + + // Last ditch effort, check for job assigned to the mob itself. + var/datum/job/J = SSjob.get_job(M.job) + if(istype(J)) + return J + + return null + // Feed this proc the name of a job, and it will try to figure out what department they are apart of. -// Note that this returns a list, as some jobs are in more than one department, like Command. The 'primary' department is the first -// in the list, e.g. a HoS has Security as first, Command as second in the returned list. +// Improved with the addition of SSjob, which has departments be an actual thing and not a virtual concept. /datum/metric/proc/role_name_to_department(var/role_name) - var/list/result = list() + var/datum/job/J = SSjob.get_job(role_name) + if(istype(J)) + if(LAZYLEN(J.departments)) + return J.departments + return list(DEPARTMENT_UNKNOWN) - if(SSjob.is_job_in_department(role_name, DEPARTMENT_SECURITY)) - result += DEPARTMENT_SECURITY +/datum/metric/proc/count_people_in_department(var/department, cutoff = 75) + var/list/L = get_people_in_department(department, cutoff) + return L.len - if(SSjob.is_job_in_department(role_name, DEPARTMENT_ENGINEERING)) - result += DEPARTMENT_ENGINEERING - if(SSjob.is_job_in_department(role_name, DEPARTMENT_MEDICAL)) - result += DEPARTMENT_MEDICAL - - if(SSjob.is_job_in_department(role_name, DEPARTMENT_RESEARCH)) - result += DEPARTMENT_RESEARCH - - if(SSjob.is_job_in_department(role_name, DEPARTMENT_CARGO)) - result += DEPARTMENT_CARGO - - if(SSjob.is_job_in_department(role_name, DEPARTMENT_CIVILIAN)) - result += DEPARTMENT_CIVILIAN - - if(SSjob.is_job_in_department(role_name, DEPARTMENT_SYNTHETIC)) - result += DEPARTMENT_SYNTHETIC - - if(SSjob.is_job_in_department(role_name, DEPARTMENT_COMMAND)) // We do Command last, since we consider command to only be a primary department for hop/admin. - result += DEPARTMENT_COMMAND - - if(!result.len) // No department was found. - result += DEPARTMENT_UNKNOWN - return result - -/datum/metric/proc/count_people_in_department(var/department) +/datum/metric/proc/get_people_in_department(department, cutoff = 75) + . = list() if(!department) return for(var/mob/M in player_list) - if(guess_department(M) != department) // Ignore people outside the department we're counting. + if(department != DEPARTMENT_EVERYONE && guess_department(M) != department) // Ignore people outside the department we're counting. continue - . += 1 + if(assess_player_activity(M) < cutoff) + continue + . += M + +/datum/metric/proc/get_people_with_job(job_type, cutoff = 75) + . = list() + // First, get the name. + var/datum/job/J = SSjob.get_job_type(job_type) + if(!istype(J)) + return + + // Now find people with the job name. + for(var/M in player_list) + var/datum/job/their_job = guess_job(M) + if(!istype(their_job)) // No job was guessed. + continue + if(their_job.title != J.title) // Jobs don't match. + continue + if(assess_player_activity(M) < cutoff) // Too AFK. + continue + . += M + +/datum/metric/proc/count_people_with_job(job_type, cutoff = 75) + var/list/L = get_people_with_job(job_type, cutoff) + return L.len + + + +/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/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm index 86050f85cf4..72c36df9994 100644 --- a/code/modules/mob/living/carbon/human/death.dm +++ b/code/modules/mob/living/carbon/human/death.dm @@ -80,8 +80,9 @@ verbs -= /mob/living/carbon/proc/release_control callHook("death", list(src, gibbed)) - + if(mind) + // SSgame_master.adjust_danger(gibbed ? 40 : 20) // VOREStation Edit - We don't use SSgame_master yet. for(var/mob/observer/dead/O in mob_list) if(O.client && O.client.is_preference_enabled(/datum/client_preference/show_dsay)) to_chat(O, "[src] has died in [get_area(src)]. [ghost_follow_link(src, O)] ") 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 90d74b2638f..2f4ca0e56b8 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/generator.dm b/code/modules/power/generator.dm index 91ff81947e4..fd52cd461d1 100644 --- a/code/modules/power/generator.dm +++ b/code/modules/power/generator.dm @@ -1,3 +1,5 @@ +GLOBAL_LIST_EMPTY(all_turbines) + /obj/machinery/power/generator name = "thermoelectric generator" desc = "It's a high efficiency thermoelectric generator." @@ -27,6 +29,7 @@ /obj/machinery/power/generator/Initialize() soundloop = new(list(src), FALSE) desc = initial(desc) + " Rated for [round(max_power/1000)] kW." + GLOB.all_turbines += src ..() //Not returned, because... return INITIALIZE_HINT_LATELOAD @@ -35,6 +38,7 @@ /obj/machinery/power/generator/Destroy() QDEL_NULL(soundloop) + GLOB.all_turbines -= src return ..() //generators connect in dir and reverse_dir(dir) directions diff --git a/code/modules/power/lighting.dm b/code/modules/power/lighting.dm index 495656ad339..c9041ad93c8 100644 --- a/code/modules/power/lighting.dm +++ b/code/modules/power/lighting.dm @@ -672,6 +672,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 29c4f295f23..741d64d58e3 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 00000000000..ab7ac253f6f --- /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 abda041a728..185ddd433f9 100644 --- a/maps/submaps/surface_submaps/wilderness/wilderness_areas.dm +++ b/maps/submaps/surface_submaps/wilderness/wilderness_areas.dm @@ -4,6 +4,7 @@ flags = RAD_SHIELDED ambience = AMBIENCE_RUINS secret_name = TRUE + forbid_events = TRUE /area/submap/event //To be used for Events not for regular PoIs name = "Unknown" @@ -136,4 +137,4 @@ /area/submap/ChemSpill2 name = "POI - Acrid Lake" - ambience = AMBIENCE_FOREBODING \ No newline at end of file + ambience = AMBIENCE_FOREBODING diff --git a/sound/effects/light_flicker.ogg b/sound/effects/light_flicker.ogg new file mode 100644 index 00000000000..58be2c6ebda Binary files /dev/null and b/sound/effects/light_flicker.ogg differ diff --git a/vorestation.dme b/vorestation.dme index 66f46dedc4d..43e0a38d0ff 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -223,7 +223,6 @@ #include "code\controllers\verbs.dm" #include "code\controllers\observer_listener\atom\observer.dm" #include "code\controllers\Processes\emergencyShuttle.dm" -#include "code\controllers\Processes\game_master.dm" #include "code\controllers\Processes\ticker.dm" #include "code\controllers\ProcessScheduler\core\process.dm" #include "code\controllers\ProcessScheduler\core\processScheduler.dm" @@ -310,6 +309,7 @@ #include "code\datums\autolathe\medical_vr.dm" #include "code\datums\autolathe\tools.dm" #include "code\datums\autolathe\tools_vr.dm" +#include "code\datums\game_masters\_common.dm" #include "code\datums\helper_datums\construction_datum.dm" #include "code\datums\helper_datums\events.dm" #include "code\datums\helper_datums\getrev.dm" @@ -1638,7 +1638,6 @@ #include "code\modules\awaymissions\zlevel.dm" #include "code\modules\blob\blob.dm" #include "code\modules\blob2\_defines.dm" -#include "code\modules\blob2\announcement.dm" #include "code\modules\blob2\blobs\base_blob.dm" #include "code\modules\blob2\blobs\core.dm" #include "code\modules\blob2\blobs\factory.dm" @@ -2026,56 +2025,49 @@ #include "code\modules\food\kitchen\cooking_machines\fryer.dm" #include "code\modules\food\kitchen\cooking_machines\grill.dm" #include "code\modules\food\kitchen\cooking_machines\oven.dm" -#include "code\modules\gamemaster\controller.dm" #include "code\modules\gamemaster\defines.dm" -#include "code\modules\gamemaster\game_master.dm" -#include "code\modules\gamemaster\helpers.dm" -#include "code\modules\gamemaster\actions\action.dm" -#include "code\modules\gamemaster\actions\atmos_leak.dm" -#include "code\modules\gamemaster\actions\blob.dm" -#include "code\modules\gamemaster\actions\brand_intelligence.dm" -#include "code\modules\gamemaster\actions\camera_damage.dm" -#include "code\modules\gamemaster\actions\canister_leak.dm" -#include "code\modules\gamemaster\actions\carp_migration.dm" -#include "code\modules\gamemaster\actions\carp_migration_vr.dm" -#include "code\modules\gamemaster\actions\comms_blackout.dm" -#include "code\modules\gamemaster\actions\drill_announcement.dm" -#include "code\modules\gamemaster\actions\dust.dm" -#include "code\modules\gamemaster\actions\electrical_storm.dm" -#include "code\modules\gamemaster\actions\electrified_door.dm" -#include "code\modules\gamemaster\actions\gravity.dm" -#include "code\modules\gamemaster\actions\grid_check.dm" -#include "code\modules\gamemaster\actions\infestation.dm" -#include "code\modules\gamemaster\actions\ion_storm.dm" -#include "code\modules\gamemaster\actions\manifest_malfunction.dm" -#include "code\modules\gamemaster\actions\meteor_defense.dm" -#include "code\modules\gamemaster\actions\money_hacker.dm" -#include "code\modules\gamemaster\actions\money_lotto.dm" -#include "code\modules\gamemaster\actions\money_spam.dm" -#include "code\modules\gamemaster\actions\planet_weather_change.dm" -#include "code\modules\gamemaster\actions\prison_break.dm" -#include "code\modules\gamemaster\actions\radiation_storm.dm" -#include "code\modules\gamemaster\actions\random_antagonist.dm" -#include "code\modules\gamemaster\actions\rogue_drones.dm" -#include "code\modules\gamemaster\actions\security_advisement.dm" -#include "code\modules\gamemaster\actions\shipping_error.dm" -#include "code\modules\gamemaster\actions\solar_storm.dm" -#include "code\modules\gamemaster\actions\spacevine.dm" -#include "code\modules\gamemaster\actions\spider_infestation.dm" -#include "code\modules\gamemaster\actions\spontaneous_appendicitis.dm" -#include "code\modules\gamemaster\actions\station_fundraise.dm" -#include "code\modules\gamemaster\actions\stowaway.dm" -#include "code\modules\gamemaster\actions\supply_conversion.dm" -#include "code\modules\gamemaster\actions\supplyrequest.dm" -#include "code\modules\gamemaster\actions\surprise_carp_attack.dm" -#include "code\modules\gamemaster\actions\surprise_meteor.dm" -#include "code\modules\gamemaster\actions\swarmboarder.dm" -#include "code\modules\gamemaster\actions\viral_infection.dm" -#include "code\modules\gamemaster\actions\viral_outbreak.dm" -#include "code\modules\gamemaster\actions\wallrot.dm" -#include "code\modules\gamemaster\actions\waste_disposal.dm" -#include "code\modules\gamemaster\actions\window_break.dm" -#include "code\modules\gamemaster\actions\wormholes.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\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" #include "code\modules\games\cah_white_cards.dm"
Event TypeStart TimeFinish Time
[event.type][event.time_started][event.time_finished]