diff --git a/code/controllers/master_controller.dm b/code/controllers/master_controller.dm index c2ba3db8151..8cafa13345a 100644 --- a/code/controllers/master_controller.dm +++ b/code/controllers/master_controller.dm @@ -21,6 +21,7 @@ datum/controller/game_controller var/objects_cost = 0 var/networks_cost = 0 var/powernets_cost = 0 + var/events_cost = 0 var/ticker_cost = 0 var/total_cost = 0 @@ -218,6 +219,20 @@ datum/controller/game_controller/proc/process() sleep(breather_ticks) + //EVENTS + timer = world.timeofday + last_thing_processed = /datum/event + i = 1 + while(i<=events.len) + var/datum/event/Event = events[i] + if(Event) + Event.process() + i++ + continue + events.Cut(i,i+1) + checkEvent() + events_cost = (world.timeofday - timer) / 10 + //TICKER timer = world.timeofday last_thing_processed = ticker.type @@ -225,7 +240,7 @@ datum/controller/game_controller/proc/process() ticker_cost = (world.timeofday - timer) / 10 //TIMING - total_cost = air_cost + sun_cost + mobs_cost + diseases_cost + machines_cost + objects_cost + networks_cost + powernets_cost + ticker_cost + total_cost = air_cost + sun_cost + mobs_cost + diseases_cost + machines_cost + objects_cost + networks_cost + powernets_cost + events_cost + ticker_cost var/end_time = world.timeofday if(end_time < start_time) diff --git a/code/game/gamemodes/gameticker.dm b/code/game/gamemodes/gameticker.dm index 4183ac322da..df1674f3152 100644 --- a/code/game/gamemodes/gameticker.dm +++ b/code/game/gamemodes/gameticker.dm @@ -122,7 +122,8 @@ var/global/datum/controller/gameticker/ticker //Holiday Round-start stuff ~Carn Holiday_Game_Start() - start_events() //handles random events and space dust. +// start_events() //handles random events and space dust. +//new random event system is handled from the MC. var/admins_number = 0 for(var/client/C) diff --git a/code/global.dm b/code/global.dm index cd9570b5d87..e887fea82f0 100644 --- a/code/global.dm +++ b/code/global.dm @@ -9,6 +9,7 @@ var/global/obj/effect/overlay/slmaster = null var/global/list/machines = list() var/global/list/processing_objects = list() var/global/list/active_diseases = list() +var/global/list/events = list() //items that ask to be called every cycle var/global/defer_powernet_rebuild = 0 // true if net rebuild will be called manually after an event diff --git a/code/modules/events/alien_infestation.dm b/code/modules/events/alien_infestation.dm new file mode 100644 index 00000000000..1a0cb0d220b --- /dev/null +++ b/code/modules/events/alien_infestation.dm @@ -0,0 +1,32 @@ +/datum/event/alien_infestation + announceWhen = 75 + endWhen = 75 + oneShot = 1 + + var/spawncount = 1 + +/datum/event/alien_infestation/announce() + command_alert("Unidentified lifesigns detected coming aboard [station_name()]. Secure any exterior access, including ducting and ventilation.", "Lifesign Alert") + world << sound('sound/AI/aliens.ogg') + + +/datum/event/alien_infestation/start() + var/list/vents = list() + for(var/obj/machinery/atmospherics/unary/vent_pump/temp_vent in world) + if(temp_vent.loc.z == 1 && !temp_vent.welded && temp_vent.network) + if(temp_vent.network.normal_members.len > 50) //Stops Aliens getting stuck in small networks. See: Security, Virology + vents += temp_vent + + var/list/candidates = get_alien_candidates() + + if(prob(50)) spawncount++ //sometimes, have two larvae spawn instead of one + while((spawncount >= 1) && vents.len && candidates.len) + var/obj/vent = pick(vents) + var/candidate = pick(candidates) + + var/mob/living/carbon/alien/larva/new_xeno = new(vent.loc) + new_xeno.key = candidate + + candidates -= candidate + vents -= vent + spawncount-- \ No newline at end of file diff --git a/code/modules/events/blob.dm b/code/modules/events/blob.dm new file mode 100644 index 00000000000..9766757d29a --- /dev/null +++ b/code/modules/events/blob.dm @@ -0,0 +1,25 @@ +/datum/event/blob + announceWhen = 12 + endWhen = 120 + + var/obj/effect/blob/core/Blob + + +/datum/event/blob/announce() + command_alert("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert") + world << sound('sound/AI/outbreak5.ogg') + + +/datum/event/blob/start() + var/turf/T = pick(blobstart) + if(!T) kill() + Blob = new /obj/effect/blob/core(T, 200) + Blob.Life() + Blob.Life() + Blob.Life() + + +/datum/event/blob/tick() + if(!Blob) kill() + if(IsMultiple(activeFor, 3)) + Blob.Life() \ No newline at end of file diff --git a/code/modules/events/carp_migration.dm b/code/modules/events/carp_migration.dm new file mode 100644 index 00000000000..e6c9a34dde2 --- /dev/null +++ b/code/modules/events/carp_migration.dm @@ -0,0 +1,14 @@ +/datum/event/carp_migration + announceWhen = 50 + endWhen = 50 + oneShot = 1 + + +/datum/event/carp_migration/announce() + command_alert("Unknown biological entities have been detected near [station_name()], please stand-by.", "Lifesign Alert") + + +/datum/event/carp_migration/start() + for(var/obj/effect/landmark/C in landmarks_list) + if(C.name == "carpspawn") + new /mob/living/simple_animal/hostile/carp(C.loc) \ No newline at end of file diff --git a/code/modules/events/communications_blackout.dm b/code/modules/events/communications_blackout.dm new file mode 100644 index 00000000000..18d0c13f979 --- /dev/null +++ b/code/modules/events/communications_blackout.dm @@ -0,0 +1,17 @@ +/datum/event/communications_blackout + var/silent = 1 //most of the time, we don't want an announcement, so as to allow AIs to fake blackouts. + +/datum/event/communications_blackout/announce() + if(!silent) + command_alert("Ionospheric anomalies detected. Temporary telecommunication failure imminent. Please contact you-BZZT") + + +/datum/event/communications_blackout/start() + if(prob(25)) + silent = 0 + for(var/mob/living/silicon/ai/A in player_list) //AIs are always aware of communication blackouts. + A << "
" + A << "Ionospheric anomalies detected. Temporary telecommunication failure imminent. Please contact you-BZZT" + A << "
" + for(var/obj/machinery/telecomms/T in telecomms_list) + T.emp_act(1) \ No newline at end of file diff --git a/code/modules/events/disease_outbreak.dm b/code/modules/events/disease_outbreak.dm new file mode 100644 index 00000000000..9e035a8e274 --- /dev/null +++ b/code/modules/events/disease_outbreak.dm @@ -0,0 +1,44 @@ +/datum/event/disease_outbreak + announceWhen = 15 + endWhen = 15 + oneShot = 1 + + +/datum/event/disease_outbreak/announce() + command_alert("Confirmed outbreak of level 7 viral biohazard aboard [station_name()]. All personnel must contain the outbreak.", "Biohazard Alert") + world << sound('sound/AI/outbreak7.ogg') + +/datum/event/disease_outbreak/start() + var/virus_type = pick(/datum/disease/dnaspread, /datum/disease/advance/flu, /datum/disease/advance/cold, /datum/disease/brainrot, /datum/disease/magnitis) + + for(var/mob/living/carbon/human/H in shuffle(living_mob_list)) + var/foundAlready = 0 // don't infect someone that already has the virus + var/turf/T = get_turf(H) + if(!T) + continue + if(T.z != 1) + continue + for(var/datum/disease/D in H.viruses) + foundAlready = 1 + if(H.stat == 2 || foundAlready) + continue + + if(virus_type == /datum/disease/dnaspread) //Dnaspread needs strain_data set to work. + if((!H.dna) || (H.sdisabilities & BLIND)) //A blindness disease would be the worst. + continue + var/datum/disease/dnaspread/D = new + D.strain_data["name"] = H.real_name + D.strain_data["UI"] = H.dna.uni_identity + D.strain_data["SE"] = H.dna.struc_enzymes + D.carrier = 1 + D.holder = H + D.affected_mob = H + H.viruses += D + break + else + var/datum/disease/D = new virus_type + D.carrier = 1 + D.holder = H + D.affected_mob = H + H.viruses += D + break \ No newline at end of file diff --git a/code/modules/events/electrical_storm.dm b/code/modules/events/electrical_storm.dm new file mode 100644 index 00000000000..dacaa09c47a --- /dev/null +++ b/code/modules/events/electrical_storm.dm @@ -0,0 +1,28 @@ +/datum/event/electrical_storm + var/lightsoutAmount = 1 + var/lightsoutRange = 25 + + +/datum/event/electrical_storm/announce() + command_alert("An electrical storm has been detected in your area, please repair potential electronic overloads.", "Electrical Storm Alert") + + +/datum/event/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() \ No newline at end of file diff --git a/code/modules/events/event.dm b/code/modules/events/event.dm new file mode 100644 index 00000000000..b6c64b89aac --- /dev/null +++ b/code/modules/events/event.dm @@ -0,0 +1,82 @@ +/datum/event //NOTE: Times are measured in master controller ticks! + var/startWhen = 0 //When in the lifetime to call start(). + var/announceWhen = 0 //When in the lifetime to call announce(). + var/endWhen = 0 //When in the lifetime the event should end. Include time for delayed announcements. + var/oneShot = 0 //If true, then the event removes itself from the list of potential events on creation. + + var/activeFor = 0 //How long the event has existed. You don't need to change this. + + + + +//Called when the tick is equal to the startWhen variable. +//Allows you to announce before starting or vice versa. +//Only called once. +/datum/event/proc/start() + return + +//Called when the tick is equal to the announceWhen variable. +//Allows you to start before announcing or vice versa. +//Only called once. +/datum/event/proc/announce() + return + +//Called on or after the tick counter is equal to startWhen. +//You can include code related to your event or add your own +//time stamped events. +//Called more than once. +/datum/event/proc/tick() + return + +//Called on or after the tick is equal or more than endWhen +//You can include code related to the event ending. +//Do not place spawn() in here, instead use tick() to check for +//the activeFor variable. +//For example: if(activeFor == myOwnVariable + 30) doStuff() +//Only called once. +/datum/event/proc/end() + return + + + + +//Do not override this proc, instead use the appropiate procs. +//This proc will handle the calls to the appropiate procs. +/datum/event/proc/process() + if(activeFor == startWhen) + start() + + if(activeFor > startWhen) + tick() + + if(activeFor == announceWhen) + announce() + + if(activeFor >= endWhen) + end() + kill() + + activeFor++ + + +//Garbage collects the event by removing it from the global events list, +//which should be the only place it's referenced. +/datum/event/proc/kill() + events.Remove(src) + + +//Adds the event to the global events list, and removes it from the list +//of potential events. +/datum/event/New() + events.Add(src) + + if(oneShot) + potentialRandomEvents.Remove(type) + + +//This shouldn't be called, but it ensures that the event doesn't persist in +//the events list if it'd deleted instead of garbage collected with kill(). +//The master controller will also remove it from the events list if it +//doesn't exist, so there's redundancy here. +/datum/event/Del() + events.Remove(src) \ No newline at end of file diff --git a/code/modules/events/event_manager.dm b/code/modules/events/event_manager.dm new file mode 100644 index 00000000000..78e89cac7e8 --- /dev/null +++ b/code/modules/events/event_manager.dm @@ -0,0 +1,39 @@ +var/list/allEvents = typesof(/datum/event) - /datum/event +var/list/potentialRandomEvents = typesof(/datum/event) - /datum/event + +var/eventTimeLower = 5000 +var/eventTimeUpper = 10000 + +var/scheduledEvent = null + + +//Currently unused. Needs an admin panel for messing with events. +/proc/addPotentialEvent(var/type) + potentialRandomEvents |= type + +/proc/removePotentialEvent(var/type) + potentialRandomEvents -= type + + +/proc/checkEvent() + if(!scheduledEvent) + scheduledEvent = world.timeofday + rand(eventTimeLower, eventTimeUpper) + + else if(world.timeofday > scheduledEvent) + spawnEvent() + + scheduledEvent = null + checkEvent() + + +/proc/spawnEvent() + if(!config.allow_random_events) + return + + var/Type = pick(potentialRandomEvents) + if(!Type) + return + + //The event will add itself to the MC's event list + //and start working via the constructor. + new Type \ No newline at end of file diff --git a/code/modules/events/meteor_wave.dm b/code/modules/events/meteor_wave.dm new file mode 100644 index 00000000000..fb52bc0582d --- /dev/null +++ b/code/modules/events/meteor_wave.dm @@ -0,0 +1,12 @@ +/datum/event/meteor_wave + startWhen = 6 + endWhen = 66 + +/datum/event/meteor_wave/announce() + command_alert("Meteors have been detected on collision course with the station.", "Meteor Alert") + world << sound('sound/AI/meteors.ogg') + + +/datum/event/meteor_wave/tick() + if(IsMultiple(activeFor, 3)) + spawn_meteors(5) \ No newline at end of file diff --git a/code/modules/events/prison_break.dm b/code/modules/events/prison_break.dm new file mode 100644 index 00000000000..783dc6c8fb2 --- /dev/null +++ b/code/modules/events/prison_break.dm @@ -0,0 +1,46 @@ +/datum/event/prison_break + announceWhen = 50 + endWhen = 50 + oneShot = 1 + + var/releaseWhen = 25 + var/list/area/prisonAreas = list() + + +/datum/event/prison_break/announce() + if(prisonAreas && prisonAreas.len > 0) + command_alert("Gr3y.T1d3 virus detected in [station_name()] imprisonment subroutines. Recommend station AI involvement.", "Security Alert") + else + world.log << "ERROR: Could not initate grey-tide. Unable find prison or brig area." + + +/datum/event/prison_break/start() + for(var/area/A in world) + if(istype(A, /area/security/prison) || istype(A, /area/security/brig)) + prisonAreas += A + + if(prisonAreas && prisonAreas.len > 0) + for(var/area/A in prisonAreas) + for(var/obj/machinery/light/L in A) + L.flicker(10) + + +/datum/event/prison_break/tick() + if(activeFor == releaseWhen) + if(prisonAreas && prisonAreas.len > 0) + for(var/area/A in prisonAreas) + for(var/obj/machinery/power/apc/temp_apc in A) + temp_apc.overload_lighting() + + for(var/obj/structure/closet/secure_closet/brig/temp_closet in A) + temp_closet.locked = 0 + temp_closet.icon_state = temp_closet.icon_closed + + for(var/obj/machinery/door/airlock/security/temp_airlock in A) + temp_airlock.prison_open() + + for(var/obj/machinery/door/airlock/glass_security/temp_glassairlock in A) + temp_glassairlock.prison_open() + + for(var/obj/machinery/door_timer/temp_timer in A) + temp_timer.releasetime = 1 \ No newline at end of file diff --git a/code/modules/events/radiation_storm.dm b/code/modules/events/radiation_storm.dm new file mode 100644 index 00000000000..d709438428d --- /dev/null +++ b/code/modules/events/radiation_storm.dm @@ -0,0 +1,37 @@ +/datum/event/radiation_storm + announceWhen = 5 + endWhen = 5 + oneShot = 1 + + +/datum/event/radiation_storm/announce() + command_alert("High levels of radiation detected near the station. Please report to the Med-bay if you feel strange.", "Anomaly Alert") + world << sound('sound/AI/radiation.ogg') + + +/datum/event/radiation_storm/start() + for(var/mob/living/carbon/human/H in living_mob_list) + var/turf/T = get_turf(H) + if(!T) + continue + if(T.z != 1) + continue + if(istype(H,/mob/living/carbon/human)) + H.apply_effect((rand(15,75)),IRRADIATE,0) + if(prob(5)) + H.apply_effect((rand(90,150)),IRRADIATE,0) + if(prob(25)) + if (prob(75)) + randmutb(H) + domutcheck(H,null,1) + else + randmutg(H) + domutcheck(H,null,1) + + for(var/mob/living/carbon/monkey/M in living_mob_list) + var/turf/T = get_turf(M) + if(!T) + continue + if(T.z != 1) + continue + M.apply_effect((rand(15,75)),IRRADIATE,0) \ No newline at end of file diff --git a/code/modules/events/spacevine.dm b/code/modules/events/spacevine.dm new file mode 100644 index 00000000000..ee47cc0abbc --- /dev/null +++ b/code/modules/events/spacevine.dm @@ -0,0 +1,5 @@ +/datum/event/spacevine + oneShot = 1 + +/datum/event/spacevine/start() + spacevine_infestation() \ No newline at end of file diff --git a/code/modules/events/spontaneous_appendicitis.dm b/code/modules/events/spontaneous_appendicitis.dm new file mode 100644 index 00000000000..470439a29c8 --- /dev/null +++ b/code/modules/events/spontaneous_appendicitis.dm @@ -0,0 +1,13 @@ +/datum/event/spontaneous_appendicitis/start() + for(var/mob/living/carbon/human/H in living_mob_list) + var/foundAlready = 0 //don't infect someone that already has the virus + for(var/datum/disease/D in H.viruses) + foundAlready = 1 + if(H.stat == 2 || foundAlready) + continue + + var/datum/disease/D = new /datum/disease/appendicitis + D.holder = H + D.affected_mob = H + H.viruses += D + break \ No newline at end of file diff --git a/tgstation.dme b/tgstation.dme index 0589a10585c..ffc9133a64a 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -719,6 +719,19 @@ #include "code\modules\detectivework\evidence.dm" #include "code\modules\detectivework\footprints_and_rag.dm" #include "code\modules\detectivework\scanner.dm" +#include "code\modules\events\alien_infestation.dm" +#include "code\modules\events\blob.dm" +#include "code\modules\events\carp_migration.dm" +#include "code\modules\events\communications_blackout.dm" +#include "code\modules\events\disease_outbreak.dm" +#include "code\modules\events\electrical_storm.dm" +#include "code\modules\events\event.dm" +#include "code\modules\events\event_manager.dm" +#include "code\modules\events\meteor_wave.dm" +#include "code\modules\events\prison_break.dm" +#include "code\modules\events\radiation_storm.dm" +#include "code\modules\events\spacevine.dm" +#include "code\modules\events\spontaneous_appendicitis.dm" #include "code\modules\flufftext\Dreaming.dm" #include "code\modules\flufftext\Hallucination.dm" #include "code\modules\flufftext\TextFilters.dm"