diff --git a/code/__DEFINES/events.dm b/code/__DEFINES/events.dm
index d39932e1a70..0e72379461a 100644
--- a/code/__DEFINES/events.dm
+++ b/code/__DEFINES/events.dm
@@ -7,3 +7,26 @@
#define EVENT_READY 1
#define EVENT_CANCELLED 2
#define EVENT_INTERRUPTED 3
+
+///Events that mess with or create artificial intelligences, such as vending machines and the AI itself
+#define EVENT_CATEGORY_AI "AI issues"
+///Events that spawn anomalies, which might be the source of anomaly cores
+#define EVENT_CATEGORY_ANOMALIES "Anomalies"
+///Events pertaining cargo, messages incoming to the station and job slots
+#define EVENT_CATEGORY_BUREAUCRATIC "Bureaucratic"
+///Events that cause breakages and malfunctions that could be fixed by engineers
+#define EVENT_CATEGORY_ENGINEERING "Engineering"
+///Events that spawn creatures with simple desires, such as to hunt
+#define EVENT_CATEGORY_ENTITIES "Entities"
+///Events that should have no harmful effects, and might be useful to the crew
+#define EVENT_CATEGORY_FRIENDLY "Friendly"
+///Events that affect the body and mind
+#define EVENT_CATEGORY_HEALTH "Health"
+///Events reserved for special occassions
+#define EVENT_CATEGORY_HOLIDAY "Holiday"
+///Events with enemy groups with a more complex plan
+#define EVENT_CATEGORY_INVASION "Invasion"
+///Events that summon meteors and other debris, and stationwide waves of harmful space weather
+#define EVENT_CATEGORY_SPACE "Space Threats"
+///Events summoned by a wizard
+#define EVENT_CATEGORY_WIZARD "Wizard"
diff --git a/code/controllers/subsystem/events.dm b/code/controllers/subsystem/events.dm
index 75a9d109cd9..32c8ef97b14 100644
--- a/code/controllers/subsystem/events.dm
+++ b/code/controllers/subsystem/events.dm
@@ -92,41 +92,6 @@ SUBSYSTEM_DEF(events)
else if(. == EVENT_READY)
E.runEvent(random = TRUE)
-//allows a client to trigger an event
-//aka Badmin Central
-// > Not in modules/admin
-// REEEEEEEEE
-// Why the heck is this here! Took me so damn long to find!
-/client/proc/forceEvent()
- set name = "Trigger Event"
- set category = "Admin.Events"
-
- if(!holder ||!check_rights(R_FUN))
- return
-
- holder.forceEvent()
-
-/datum/admins/proc/forceEvent()
- var/dat = ""
- var/normal = ""
- var/magic = ""
- var/holiday = ""
- for(var/datum/round_event_control/E in SSevents.control)
- dat = "
[E]"
- if(E.holidayID)
- holiday += dat
- else if(E.wizardevent)
- magic += dat
- else
- normal += dat
-
- dat = normal + "
" + magic + "
" + holiday
-
- var/datum/browser/popup = new(usr, "forceevent", "Force Random Event", 300, 750)
- popup.set_content(dat)
- popup.open()
-
-
/*
//////////////
// HOLIDAYS //
diff --git a/code/modules/admin/force_event.dm b/code/modules/admin/force_event.dm
new file mode 100644
index 00000000000..86d400c3c9b
--- /dev/null
+++ b/code/modules/admin/force_event.dm
@@ -0,0 +1,69 @@
+///Allows an admin to force an event
+/client/proc/forceEvent()
+ set name = "Trigger Event"
+ set category = "Admin.Events"
+
+ if(!holder ||!check_rights(R_FUN))
+ return
+
+ holder.forceEvent()
+
+///Opens up the Force Event Panel
+/datum/admins/proc/forceEvent()
+ if(!check_rights(R_FUN))
+ return
+
+ var/datum/force_event/ui = new(usr)
+ ui.ui_interact(usr)
+
+/// Force Event Panel
+/datum/force_event
+
+/datum/force_event/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ForceEvent")
+ ui.open()
+
+/datum/force_event/ui_state(mob/user)
+ return GLOB.fun_state
+
+/datum/force_event/ui_static_data(mob/user)
+ var/list/data = list()
+ data["categories"] = list()
+ for(var/datum/round_event_control/event_control in SSevents.control)
+ if(!data["categories"][event_control.category])
+ data["categories"][event_control.category] = list(
+ "name" = event_control.category,
+ "events" = list()
+ )
+ data["categories"][event_control.category]["events"] += list(list(
+ "name" = event_control.name,
+ "description" = event_control.description,
+ "type" = event_control.type
+ ))
+ return data
+
+/datum/force_event/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ if(..())
+ return
+ if(!check_rights(R_FUN))
+ return
+ switch(action)
+ if("forceevent")
+ var/announce_event = params["announce"]
+ var/string_path = params["type"]
+ if(!string_path)
+ return
+ var/event_to_run_type = text2path(string_path)
+ if(!event_to_run_type)
+ return
+ var/datum/round_event_control/event = locate(event_to_run_type) in SSevents.control
+ if(!event)
+ return
+ event.admin_setup(usr)
+ var/always_announce_chance = 100
+ var/no_announce_chance = 0
+ event.runEvent(announce_chance_override = announce_event ? always_announce_chance : no_announce_chance, admin_forced = TRUE)
+ message_admins("[key_name_admin(usr)] has triggered an event. ([event.name])")
+ log_admin("[key_name(usr)] has triggered an event. ([event.name])")
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index 4d78a84a361..0fce604884c 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -47,30 +47,6 @@
to_chat(usr, span_danger("ERROR: Mob not found."), confidential = TRUE)
return
cmd_show_exp_panel(M.client)
- else if(href_list["forceevent"])
- if(!check_rights(R_FUN))
- return
- var/datum/round_event_control/E = locate(href_list["forceevent"]) in SSevents.control
- if(E)
- E.admin_setup(usr)
- var/datum/round_event/event = E.runEvent()
- if(event.cancel_event)
- return
- if(event.announceWhen>0)
- event.processing = FALSE
- var/prompt = tgui_alert(usr, "Would you like to alert the crew?", "Alert", list("Yes", "No", "Cancel"))
- switch(prompt)
- if("Yes")
- event.announceChance = 100
- if("Cancel")
- event.kill()
- return
- if("No")
- event.announceChance = 0
- event.processing = TRUE
- message_admins("[key_name_admin(usr)] has triggered an event. ([E.name])")
- log_admin("[key_name(usr)] has triggered an event. ([E.name])")
- return
else if(href_list["editrightsbrowser"])
edit_admin_permissions(0)
diff --git a/code/modules/antagonists/disease/disease_event.dm b/code/modules/antagonists/disease/disease_event.dm
index 5206934cf68..66986a99aa2 100644
--- a/code/modules/antagonists/disease/disease_event.dm
+++ b/code/modules/antagonists/disease/disease_event.dm
@@ -5,7 +5,8 @@
weight = 7
max_occurrences = 1
min_players = 25
-
+ category = EVENT_CATEGORY_HEALTH
+ description = "Spawns a sentient disease, who wants to infect as many people as possible."
/datum/round_event/ghost_role/sentient_disease
role_name = "sentient disease"
diff --git a/code/modules/antagonists/morph/morph.dm b/code/modules/antagonists/morph/morph.dm
index b046fc3014e..d046d136130 100644
--- a/code/modules/antagonists/morph/morph.dm
+++ b/code/modules/antagonists/morph/morph.dm
@@ -210,6 +210,8 @@
typepath = /datum/round_event/ghost_role/morph
weight = 0 //Admin only
max_occurrences = 1
+ category = EVENT_CATEGORY_ENTITIES
+ description = "Spawns a hungry shapeshifting blobby creature."
/datum/round_event/ghost_role/morph
minimum_required = 1
diff --git a/code/modules/antagonists/revenant/revenant_spawn_event.dm b/code/modules/antagonists/revenant/revenant_spawn_event.dm
index 5b548e478b2..cb9df58961a 100644
--- a/code/modules/antagonists/revenant/revenant_spawn_event.dm
+++ b/code/modules/antagonists/revenant/revenant_spawn_event.dm
@@ -7,6 +7,8 @@
max_occurrences = 1
min_players = 5
dynamic_should_hijack = TRUE
+ category = EVENT_CATEGORY_ENTITIES
+ description = "Spawns an angry, soul sucking ghost."
/datum/round_event/ghost_role/revenant
diff --git a/code/modules/antagonists/slaughter/slaughterevent.dm b/code/modules/antagonists/slaughter/slaughterevent.dm
index b2463f6b642..15a3746b237 100644
--- a/code/modules/antagonists/slaughter/slaughterevent.dm
+++ b/code/modules/antagonists/slaughter/slaughterevent.dm
@@ -6,6 +6,8 @@
earliest_start = 1 HOURS
min_players = 20
dynamic_should_hijack = TRUE
+ category = EVENT_CATEGORY_ENTITIES
+ description = "Spawns a slaughter demon, to hunt by travelling through pools of blood."
/datum/round_event/ghost_role/slaughter
minimum_required = 1
diff --git a/code/modules/events/_event.dm b/code/modules/events/_event.dm
index e95dec07f69..b9582cf4456 100644
--- a/code/modules/events/_event.dm
+++ b/code/modules/events/_event.dm
@@ -3,6 +3,8 @@
//this singleton datum is used by the events controller to dictate how it selects events
/datum/round_event_control
var/name //The human-readable name of the event
+ var/category //The category of the event
+ var/description //The description of the event
var/typepath //The typepath of the event datum /datum/round_event
var/weight = 10 //The weight this event has in the random-selection process.
@@ -34,6 +36,7 @@
min_players = CEILING(min_players * CONFIG_GET(number/events_min_players_mul), 1)
/datum/round_event_control/wizard
+ category = EVENT_CATEGORY_WIZARD
wizardevent = TRUE
// Checks if the event can be spawned. Used by event controller and "false alarm" event.
@@ -92,7 +95,13 @@
log_admin_private("[key_name(usr)] cancelled event [name].")
SSblackbox.record_feedback("tally", "event_admin_cancelled", 1, typepath)
-/datum/round_event_control/proc/runEvent(random = FALSE)
+/*
+Runs the event
+* Arguments:
+* - random: shows if the event was triggered randomly, or by on purpose by an admin or an item
+* - announce_chance_override: if the value is not null, overrides the announcement chance when an admin calls an event
+*/
+/datum/round_event_control/proc/runEvent(random = FALSE, announce_chance_override = null, admin_forced = FALSE)
/*
* We clear our signals first so we dont cancel a wanted event by accident,
* the majority of time the admin will probably want to cancel a single midround spawned random events
@@ -105,10 +114,13 @@
E.control = src
occurrences++
+ if(announce_chance_override != null)
+ E.announceChance = announce_chance_override
+
testing("[time2text(world.time, "hh:mm:ss")] [E.type]")
triggering = TRUE
- if (alert_observers)
+ if (alert_observers && !admin_forced)
message_admins("Random Event triggering in [DisplayTimeText(RANDOM_EVENT_ADMIN_INTERVENTION_TIME)]: [name] (CANCEL)")
sleep(RANDOM_EVENT_ADMIN_INTERVENTION_TIME)
@@ -142,7 +154,7 @@
var/startWhen = 0 //When in the lifetime to call start().
var/announceWhen = 0 //When in the lifetime to call announce(). If you don't want it to announce use announceChance, below.
- var/announceChance = 100 // Probability of announcing, used in prob(), 0 to 100, default 100. Used in ion storms currently.
+ var/announceChance = 100 // Probability of announcing, used in prob(), 0 to 100, default 100. Called in process, and for a second time in the ion storm event.
var/endWhen = 0 //When in the lifetime the event should end.
var/activeFor = 0 //How long the event has existed. You don't need to change this.
diff --git a/code/modules/events/abductor.dm b/code/modules/events/abductor.dm
index 917f4752b52..8a2ccd0bc51 100755
--- a/code/modules/events/abductor.dm
+++ b/code/modules/events/abductor.dm
@@ -5,6 +5,8 @@
max_occurrences = 1
min_players = 20
dynamic_should_hijack = TRUE
+ category = EVENT_CATEGORY_INVASION
+ description = "One or more abductor teams spawns, and they plan to experiment on the crew."
/datum/round_event/ghost_role/abductor
minimum_required = 2
diff --git a/code/modules/events/alien_infestation.dm b/code/modules/events/alien_infestation.dm
index 222741568fc..81469588055 100644
--- a/code/modules/events/alien_infestation.dm
+++ b/code/modules/events/alien_infestation.dm
@@ -6,6 +6,8 @@
min_players = 10
dynamic_should_hijack = TRUE
+ category = EVENT_CATEGORY_ENTITIES
+ description = "A xenomorph larva spawns on a random vent."
/datum/round_event_control/alien_infestation/canSpawnEvent()
. = ..()
diff --git a/code/modules/events/anomaly.dm b/code/modules/events/anomaly.dm
index e695fa4f177..c8c4fa8ded5 100644
--- a/code/modules/events/anomaly.dm
+++ b/code/modules/events/anomaly.dm
@@ -5,6 +5,8 @@
min_players = 1
max_occurrences = 0 //This one probably shouldn't occur! It'd work, but it wouldn't be very fun.
weight = 15
+ category = EVENT_CATEGORY_ANOMALIES
+ description = "This anomaly shocks and explodes. This is the base type."
/datum/round_event/anomaly
var/area/impact_area
diff --git a/code/modules/events/anomaly_bioscrambler.dm b/code/modules/events/anomaly_bioscrambler.dm
index 7975b31c48a..04c2f78cd61 100644
--- a/code/modules/events/anomaly_bioscrambler.dm
+++ b/code/modules/events/anomaly_bioscrambler.dm
@@ -5,6 +5,7 @@
min_players = 10
max_occurrences = 5
weight = 20
+ description = "This anomaly replaces the limbs of nearby people."
/datum/round_event/anomaly/anomaly_bioscrambler
startWhen = 10
diff --git a/code/modules/events/anomaly_bluespace.dm b/code/modules/events/anomaly_bluespace.dm
index 23a2a1968a8..1716f2ace88 100644
--- a/code/modules/events/anomaly_bluespace.dm
+++ b/code/modules/events/anomaly_bluespace.dm
@@ -4,6 +4,7 @@
max_occurrences = 1
weight = 15
+ description = "This anomaly randomly teleports all items and mobs in a large area."
/datum/round_event/anomaly/anomaly_bluespace
startWhen = 3
diff --git a/code/modules/events/anomaly_flux.dm b/code/modules/events/anomaly_flux.dm
index 4737ad9bb41..d70bb0c659d 100644
--- a/code/modules/events/anomaly_flux.dm
+++ b/code/modules/events/anomaly_flux.dm
@@ -5,6 +5,7 @@
min_players = 10
max_occurrences = 5
weight = 20
+ description = "This anomaly shocks and explodes."
/datum/round_event/anomaly/anomaly_flux
startWhen = 10
diff --git a/code/modules/events/anomaly_grav.dm b/code/modules/events/anomaly_grav.dm
index 575bdc92808..dd70dad724a 100644
--- a/code/modules/events/anomaly_grav.dm
+++ b/code/modules/events/anomaly_grav.dm
@@ -4,6 +4,7 @@
max_occurrences = 5
weight = 25
+ description = "This anomaly throws things around."
/datum/round_event/anomaly/anomaly_grav
startWhen = 3
@@ -16,6 +17,7 @@
weight = 15
max_occurrences = 1
earliest_start = 20 MINUTES
+ description = "This anomaly has an intense gravitational field, and can disable the gravity generator."
/datum/round_event/anomaly/anomaly_grav/high
startWhen = 3
diff --git a/code/modules/events/anomaly_hallucination.dm b/code/modules/events/anomaly_hallucination.dm
index 0f3628c7e0b..8339599f9a5 100644
--- a/code/modules/events/anomaly_hallucination.dm
+++ b/code/modules/events/anomaly_hallucination.dm
@@ -5,6 +5,7 @@
min_players = 10
max_occurrences = 5
weight = 20
+ description = "This anomaly causes you to hallucinate."
/datum/round_event/anomaly/anomaly_hallucination
startWhen = 10
diff --git a/code/modules/events/anomaly_pyro.dm b/code/modules/events/anomaly_pyro.dm
index 29c6e15d282..3df01e867b0 100644
--- a/code/modules/events/anomaly_pyro.dm
+++ b/code/modules/events/anomaly_pyro.dm
@@ -4,6 +4,7 @@
max_occurrences = 5
weight = 20
+ description = "This anomaly sets things on fire, and creates a pyroclastic slime."
/datum/round_event/anomaly/anomaly_pyro
startWhen = 3
diff --git a/code/modules/events/anomaly_vortex.dm b/code/modules/events/anomaly_vortex.dm
index feb32ff13cd..307ea33b933 100644
--- a/code/modules/events/anomaly_vortex.dm
+++ b/code/modules/events/anomaly_vortex.dm
@@ -5,6 +5,7 @@
min_players = 20
max_occurrences = 2
weight = 10
+ description = "This anomaly sucks in and detonates items."
/datum/round_event/anomaly/anomaly_vortex
startWhen = 10
diff --git a/code/modules/events/aurora_caelus.dm b/code/modules/events/aurora_caelus.dm
index 3f15ab2e842..a49a95884d0 100644
--- a/code/modules/events/aurora_caelus.dm
+++ b/code/modules/events/aurora_caelus.dm
@@ -4,6 +4,8 @@
max_occurrences = 1
weight = 1
earliest_start = 5 MINUTES
+ category = EVENT_CATEGORY_FRIENDLY
+ description = "A colourful display can be seen through select windows. And the kitchen."
/datum/round_event_control/aurora_caelus/canSpawnEvent(players)
if(!CONFIG_GET(flag/starlight))
diff --git a/code/modules/events/blob.dm b/code/modules/events/blob.dm
index 32ce24d4fb7..0b805926563 100644
--- a/code/modules/events/blob.dm
+++ b/code/modules/events/blob.dm
@@ -7,6 +7,8 @@
min_players = 20
dynamic_should_hijack = TRUE
+ category = EVENT_CATEGORY_ENTITIES
+ description = "Spawns a new blob overmind."
/datum/round_event_control/blob/canSpawnEvent(players)
if(EMERGENCY_PAST_POINT_OF_NO_RETURN) // no blobs if the shuttle is past the point of no return
diff --git a/code/modules/events/brain_trauma.dm b/code/modules/events/brain_trauma.dm
index ecb0dd00790..efe0d90311c 100644
--- a/code/modules/events/brain_trauma.dm
+++ b/code/modules/events/brain_trauma.dm
@@ -2,6 +2,8 @@
name = "Spontaneous Brain Trauma"
typepath = /datum/round_event/brain_trauma
weight = 25
+ category = EVENT_CATEGORY_HEALTH
+ description = "A crewmember gains a random trauma."
/datum/round_event/brain_trauma
fakeable = FALSE
diff --git a/code/modules/events/brand_intelligence.dm b/code/modules/events/brand_intelligence.dm
index 78469224b8d..2071495c3a6 100644
--- a/code/modules/events/brand_intelligence.dm
+++ b/code/modules/events/brand_intelligence.dm
@@ -2,6 +2,8 @@
name = "Brand Intelligence"
typepath = /datum/round_event/brand_intelligence
weight = 5
+ category = EVENT_CATEGORY_AI
+ description = "Vending machines will attack people until the Patient Zero is disabled."
min_players = 15
max_occurrences = 1
diff --git a/code/modules/events/bureaucratic_error.dm b/code/modules/events/bureaucratic_error.dm
index f46775fc102..2df3a0fb4b7 100644
--- a/code/modules/events/bureaucratic_error.dm
+++ b/code/modules/events/bureaucratic_error.dm
@@ -3,6 +3,8 @@
typepath = /datum/round_event/bureaucratic_error
max_occurrences = 1
weight = 5
+ category = EVENT_CATEGORY_BUREAUCRATIC
+ description = "Randomly opens and closes job slots, along with changing the overflow role."
/datum/round_event/bureaucratic_error
announceWhen = 1
diff --git a/code/modules/events/camerafailure.dm b/code/modules/events/camerafailure.dm
index 8d7ef3204ce..453b919c5b8 100644
--- a/code/modules/events/camerafailure.dm
+++ b/code/modules/events/camerafailure.dm
@@ -4,6 +4,8 @@
weight = 100
max_occurrences = 20
alert_observers = FALSE
+ category = EVENT_CATEGORY_ENGINEERING
+ description = "Turns off a random amount of cameras."
/datum/round_event/camera_failure
fakeable = FALSE
diff --git a/code/modules/events/carp_migration.dm b/code/modules/events/carp_migration.dm
index a8cb9867be1..9a9f560bd8f 100644
--- a/code/modules/events/carp_migration.dm
+++ b/code/modules/events/carp_migration.dm
@@ -5,6 +5,8 @@
min_players = 2
earliest_start = 10 MINUTES
max_occurrences = 6
+ category = EVENT_CATEGORY_ENTITIES
+ description = "Summons a school of space carp."
/datum/round_event_control/carp_migration/New()
. = ..()
diff --git a/code/modules/events/communications_blackout.dm b/code/modules/events/communications_blackout.dm
index d9c45242989..2a85ce817de 100644
--- a/code/modules/events/communications_blackout.dm
+++ b/code/modules/events/communications_blackout.dm
@@ -2,6 +2,8 @@
name = "Communications Blackout"
typepath = /datum/round_event/communications_blackout
weight = 30
+ category = EVENT_CATEGORY_ENGINEERING
+ description = "Heavily emps all telecommunication machines, blocking all communication for a while."
/datum/round_event/communications_blackout
announceWhen = 1
diff --git a/code/modules/events/creep_awakening.dm b/code/modules/events/creep_awakening.dm
index ad2b86720a1..75cf51b53f5 100644
--- a/code/modules/events/creep_awakening.dm
+++ b/code/modules/events/creep_awakening.dm
@@ -3,6 +3,8 @@
typepath = /datum/round_event/obsessed
max_occurrences = 1
min_players = 20
+ category = EVENT_CATEGORY_HEALTH
+ description = "A random crewmember becomes obsessed with another."
/datum/round_event/obsessed
fakeable = FALSE
diff --git a/code/modules/events/disease_outbreak.dm b/code/modules/events/disease_outbreak.dm
index 9cae45b5b8b..56798f2a919 100644
--- a/code/modules/events/disease_outbreak.dm
+++ b/code/modules/events/disease_outbreak.dm
@@ -4,6 +4,8 @@
max_occurrences = 1
min_players = 10
weight = 5
+ category = EVENT_CATEGORY_HEALTH
+ description = "A classic or advanced disease will infect some crewmembers."
/datum/round_event/disease_outbreak
announceWhen = 15
diff --git a/code/modules/events/dust.dm b/code/modules/events/dust.dm
index 06c75c0c07f..20441a53aa2 100644
--- a/code/modules/events/dust.dm
+++ b/code/modules/events/dust.dm
@@ -5,6 +5,8 @@
max_occurrences = 1000
earliest_start = 0 MINUTES
alert_observers = FALSE
+ category = EVENT_CATEGORY_SPACE
+ description = "A single space dust is hurled at the station."
/datum/round_event/space_dust
startWhen = 1
@@ -20,6 +22,8 @@
weight = 0
max_occurrences = 0
earliest_start = 0 MINUTES
+ category = EVENT_CATEGORY_SPACE
+ description = "The station is pelted by an extreme amount of sand for several minutes."
/datum/round_event/sandstorm
startWhen = 1
diff --git a/code/modules/events/electrical_storm.dm b/code/modules/events/electrical_storm.dm
index e56fe25fbe0..b1503cd2275 100644
--- a/code/modules/events/electrical_storm.dm
+++ b/code/modules/events/electrical_storm.dm
@@ -4,7 +4,8 @@
earliest_start = 10 MINUTES
min_players = 5
weight = 20
- alert_observers = FALSE
+ category = EVENT_CATEGORY_ENGINEERING
+ description = "Destroys all lights in a large area."
/datum/round_event/electrical_storm
var/lightsoutAmount = 1
diff --git a/code/modules/events/fake_virus.dm b/code/modules/events/fake_virus.dm
index a582832a0b6..b64080c22ac 100644
--- a/code/modules/events/fake_virus.dm
+++ b/code/modules/events/fake_virus.dm
@@ -2,7 +2,8 @@
name = "Fake Virus"
typepath = /datum/round_event/fake_virus
weight = 20
- alert_observers = FALSE
+ category = EVENT_CATEGORY_HEALTH
+ description = "Some crewmembers suffer from temporary hypochondria."
/datum/round_event/fake_virus/start()
var/list/fake_virus_victims = list()
diff --git a/code/modules/events/false_alarm.dm b/code/modules/events/false_alarm.dm
index 18913c73f12..e8d1a364421 100644
--- a/code/modules/events/false_alarm.dm
+++ b/code/modules/events/false_alarm.dm
@@ -4,7 +4,8 @@
weight = 20
max_occurrences = 5
var/forced_type //Admin abuse
-
+ category = EVENT_CATEGORY_BUREAUCRATIC
+ description = "Fakes an event announcement."
/datum/round_event_control/falsealarm/admin_setup()
if(!check_rights(R_FUN))
diff --git a/code/modules/events/fugitive_spawning.dm b/code/modules/events/fugitive_spawning.dm
index e176a892323..f300f825596 100644
--- a/code/modules/events/fugitive_spawning.dm
+++ b/code/modules/events/fugitive_spawning.dm
@@ -4,6 +4,8 @@
max_occurrences = 1
min_players = 20
earliest_start = 30 MINUTES //deadchat sink, lets not even consider it early on.
+ category = EVENT_CATEGORY_INVASION
+ description = "Fugitives will hide on the station, followed by hunters."
/datum/round_event/ghost_role/fugitives
minimum_required = 1
diff --git a/code/modules/events/gravity_generator_blackout.dm b/code/modules/events/gravity_generator_blackout.dm
index 39bd3c4850a..eb9301f3f78 100644
--- a/code/modules/events/gravity_generator_blackout.dm
+++ b/code/modules/events/gravity_generator_blackout.dm
@@ -2,6 +2,8 @@
name = "Gravity Generator Blackout"
typepath = /datum/round_event/gravity_generator_blackout
weight = 30
+ category = EVENT_CATEGORY_ENGINEERING
+ description = "Turns off the gravity generator."
/datum/round_event_control/gravity_generator_blackout/canSpawnEvent()
var/station_generator_exists = FALSE
diff --git a/code/modules/events/grid_check.dm b/code/modules/events/grid_check.dm
index 9d6913299ad..8b0b0ce72b3 100644
--- a/code/modules/events/grid_check.dm
+++ b/code/modules/events/grid_check.dm
@@ -3,6 +3,8 @@
typepath = /datum/round_event/grid_check
weight = 10
max_occurrences = 3
+ category = EVENT_CATEGORY_ENGINEERING
+ description = "Turns off all APCs for a while, or until they are manually rebooted."
/datum/round_event/grid_check
announceWhen = 1
diff --git a/code/modules/events/heart_attack.dm b/code/modules/events/heart_attack.dm
index 8a29e7d3ac1..1d7757683a9 100644
--- a/code/modules/events/heart_attack.dm
+++ b/code/modules/events/heart_attack.dm
@@ -4,6 +4,8 @@
weight = 20
max_occurrences = 2
min_players = 40 // To avoid shafting lowpop
+ category = EVENT_CATEGORY_HEALTH
+ description = "A random crewmember's heart gives out."
/datum/round_event/heart_attack/start()
var/list/heart_attack_contestants = list()
diff --git a/code/modules/events/holiday/halloween.dm b/code/modules/events/holiday/halloween.dm
index 978b122d777..def150407fb 100644
--- a/code/modules/events/holiday/halloween.dm
+++ b/code/modules/events/holiday/halloween.dm
@@ -5,6 +5,8 @@
weight = -1 //forces it to be called, regardless of weight
max_occurrences = 1
earliest_start = 0 MINUTES
+ category = EVENT_CATEGORY_HOLIDAY
+ description = "Gives everyone treats, and turns Ian and Poly into their festive versions."
/datum/round_event/spooky/start()
..()
diff --git a/code/modules/events/holiday/vday.dm b/code/modules/events/holiday/vday.dm
index 23dc60ef6e0..8f2f6314de6 100644
--- a/code/modules/events/holiday/vday.dm
+++ b/code/modules/events/holiday/vday.dm
@@ -12,6 +12,8 @@
weight = -1 //forces it to be called, regardless of weight
max_occurrences = 1
earliest_start = 0 MINUTES
+ category = EVENT_CATEGORY_HOLIDAY
+ description = "Puts people on dates! They must protect each other. Sometimes a vengeful third wheel spawns."
/datum/round_event/valentines/start()
..()
diff --git a/code/modules/events/holiday/xmas.dm b/code/modules/events/holiday/xmas.dm
index 6535443083e..b0994996183 100644
--- a/code/modules/events/holiday/xmas.dm
+++ b/code/modules/events/holiday/xmas.dm
@@ -75,6 +75,8 @@
weight = 20
max_occurrences = 1
earliest_start = 30 MINUTES
+ category = EVENT_CATEGORY_HOLIDAY
+ description = "Spawns santa, who shall roam the station, handing out gifts."
/datum/round_event/santa
var/mob/living/carbon/human/santa //who is our santa?
diff --git a/code/modules/events/immovable_rod.dm b/code/modules/events/immovable_rod.dm
index ed428182f81..2cf4e59d398 100644
--- a/code/modules/events/immovable_rod.dm
+++ b/code/modules/events/immovable_rod.dm
@@ -14,6 +14,8 @@ In my current plan for it, 'solid' will be defined as anything with density == 1
max_occurrences = 5
var/atom/special_target
var/force_looping = FALSE
+ category = EVENT_CATEGORY_SPACE
+ description = "The station passes through an immovable rod."
/datum/round_event_control/immovable_rod/admin_setup()
if(!check_rights(R_FUN))
diff --git a/code/modules/events/ion_storm.dm b/code/modules/events/ion_storm.dm
index d6b98ec8f49..ef73da6f80d 100644
--- a/code/modules/events/ion_storm.dm
+++ b/code/modules/events/ion_storm.dm
@@ -3,6 +3,8 @@
typepath = /datum/round_event/ion_storm
weight = 15
min_players = 2
+ category = EVENT_CATEGORY_AI
+ description = "Gives the AI a new, randomized law."
/datum/round_event/ion_storm
var/replaceLawsetChance = 25 //chance the AI's lawset is completely replaced with something else per config weights
diff --git a/code/modules/events/major_dust.dm b/code/modules/events/major_dust.dm
index 7fb00124a94..83458761772 100644
--- a/code/modules/events/major_dust.dm
+++ b/code/modules/events/major_dust.dm
@@ -2,6 +2,7 @@
name = "Major Space Dust"
typepath = /datum/round_event/meteor_wave/major_dust
weight = 8
+ description = "The station is pelted by sand."
/datum/round_event/meteor_wave/major_dust
wave_name = "space dust"
diff --git a/code/modules/events/market_crash.dm b/code/modules/events/market_crash.dm
index 960f2ca1401..8a9c7f797a2 100644
--- a/code/modules/events/market_crash.dm
+++ b/code/modules/events/market_crash.dm
@@ -7,6 +7,8 @@
name = "Market Crash"
typepath = /datum/round_event/market_crash
weight = 10
+ category = EVENT_CATEGORY_BUREAUCRATIC
+ description = "Temporarily increases the prices of vending machines."
/datum/round_event/market_crash
var/market_dip = 0
diff --git a/code/modules/events/mass_hallucination.dm b/code/modules/events/mass_hallucination.dm
index 2e40878d633..0844194c5f6 100644
--- a/code/modules/events/mass_hallucination.dm
+++ b/code/modules/events/mass_hallucination.dm
@@ -4,6 +4,8 @@
weight = 10
max_occurrences = 2
min_players = 1
+ category = EVENT_CATEGORY_HEALTH
+ description = "Multiple crewmembers start to hallucinate the same thing."
/datum/round_event/mass_hallucination
fakeable = FALSE
diff --git a/code/modules/events/meateor_wave.dm b/code/modules/events/meateor_wave.dm
index e6859c51977..d13099dbe00 100644
--- a/code/modules/events/meateor_wave.dm
+++ b/code/modules/events/meateor_wave.dm
@@ -3,6 +3,7 @@
typepath = /datum/round_event/meteor_wave/meaty
weight = 2
max_occurrences = 1
+ description = "A meteor wave made of meat."
/datum/round_event/meteor_wave/meaty
wave_name = "meaty"
diff --git a/code/modules/events/meteor_wave.dm b/code/modules/events/meteor_wave.dm
index 204436123c6..af783095842 100644
--- a/code/modules/events/meteor_wave.dm
+++ b/code/modules/events/meteor_wave.dm
@@ -7,6 +7,8 @@
min_players = 15
max_occurrences = 3
earliest_start = 25 MINUTES
+ category = EVENT_CATEGORY_SPACE
+ description = "A regular meteor wave."
/datum/round_event/meteor_wave
startWhen = 6
@@ -60,6 +62,7 @@
min_players = 20
max_occurrences = 3
earliest_start = 35 MINUTES
+ description = "A meteor wave with higher chance of big meteors."
/datum/round_event/meteor_wave/threatening
wave_name = "threatening"
@@ -71,6 +74,7 @@
min_players = 25
max_occurrences = 3
earliest_start = 45 MINUTES
+ description = "A meteor wave that might summon a tunguska class meteor."
/datum/round_event/meteor_wave/catastrophic
wave_name = "catastrophic"
diff --git a/code/modules/events/mice_migration.dm b/code/modules/events/mice_migration.dm
index 19c1c5541c4..e7f31567f4c 100644
--- a/code/modules/events/mice_migration.dm
+++ b/code/modules/events/mice_migration.dm
@@ -2,6 +2,8 @@
name = "Mice Migration"
typepath = /datum/round_event/mice_migration
weight = 10
+ category = EVENT_CATEGORY_ENTITIES
+ description = "A horde of mice arrives, and perhaps even the Rat King themselves."
/datum/round_event/mice_migration
var/minimum_mice = 5
diff --git a/code/modules/events/nightmare.dm b/code/modules/events/nightmare.dm
index 1541b1d2d32..2c65a6b76c9 100644
--- a/code/modules/events/nightmare.dm
+++ b/code/modules/events/nightmare.dm
@@ -4,6 +4,8 @@
max_occurrences = 1
min_players = 20
dynamic_should_hijack = TRUE
+ category = EVENT_CATEGORY_ENTITIES
+ description = "Spawns a nightmare, aiming to darken the station."
/datum/round_event/ghost_role/nightmare
minimum_required = 1
diff --git a/code/modules/events/operative.dm b/code/modules/events/operative.dm
index 79540ffb803..bb3a5b8df1f 100644
--- a/code/modules/events/operative.dm
+++ b/code/modules/events/operative.dm
@@ -3,6 +3,8 @@
typepath = /datum/round_event/ghost_role/operative
weight = 0 //its weight is relative to how much stationary and neglected the nuke disk is. See nuclearbomb.dm. Shouldn't be dynamic hijackable.
max_occurrences = 1
+ category = EVENT_CATEGORY_INVASION
+ description = "A single nuclear operative assaults the station."
/datum/round_event/ghost_role/operative
minimum_required = 1
diff --git a/code/modules/events/pirates.dm b/code/modules/events/pirates.dm
index 2a76e1df709..6a27b480b6e 100644
--- a/code/modules/events/pirates.dm
+++ b/code/modules/events/pirates.dm
@@ -5,6 +5,8 @@
max_occurrences = 1
min_players = 20
dynamic_should_hijack = TRUE
+ category = EVENT_CATEGORY_INVASION
+ description = "The crew will either pay up, or face a pirate assault."
#define PIRATES_ROGUES "Rogues"
#define PIRATES_SILVERSCALES "Silverscales"
diff --git a/code/modules/events/portal_storm.dm b/code/modules/events/portal_storm.dm
index 757b5336805..5fb9dddf89e 100644
--- a/code/modules/events/portal_storm.dm
+++ b/code/modules/events/portal_storm.dm
@@ -4,6 +4,8 @@
weight = 2
min_players = 15
earliest_start = 30 MINUTES
+ category = EVENT_CATEGORY_ENTITIES
+ description = "Syndicate troops pour out of portals."
/datum/round_event/portal_storm/syndicate_shocktroop
boss_types = list(/mob/living/simple_animal/hostile/syndicate/melee/space/stormtrooper = 2)
@@ -15,6 +17,8 @@
typepath = /datum/round_event/portal_storm/portal_storm_narsie
weight = 0
max_occurrences = 0
+ category = EVENT_CATEGORY_ENTITIES
+ description = "Nar'sie constructs pour out of portals."
/datum/round_event/portal_storm/portal_storm_narsie
boss_types = list(/mob/living/simple_animal/hostile/construct/artificer/hostile = 6)
diff --git a/code/modules/events/prison_break.dm b/code/modules/events/prison_break.dm
index 9bb2db6c5a2..04515edffcd 100644
--- a/code/modules/events/prison_break.dm
+++ b/code/modules/events/prison_break.dm
@@ -3,6 +3,8 @@
typepath = /datum/round_event/grey_tide
max_occurrences = 2
min_players = 5
+ category = EVENT_CATEGORY_ENGINEERING
+ description = "Bolts open all doors in one or more departments."
/datum/round_event/grey_tide
announceWhen = 50
diff --git a/code/modules/events/processor_overload.dm b/code/modules/events/processor_overload.dm
index d05badb8656..02551399e71 100644
--- a/code/modules/events/processor_overload.dm
+++ b/code/modules/events/processor_overload.dm
@@ -3,6 +3,8 @@
typepath = /datum/round_event/processor_overload
weight = 15
min_players = 20
+ category = EVENT_CATEGORY_ENGINEERING
+ description = "Emps the telecomm processors, scrambling radio speech. Might blow up a few."
/datum/round_event/processor_overload
announceWhen = 1
diff --git a/code/modules/events/radiation_storm.dm b/code/modules/events/radiation_storm.dm
index f0a447c3c29..f5b2a1b7a04 100644
--- a/code/modules/events/radiation_storm.dm
+++ b/code/modules/events/radiation_storm.dm
@@ -2,6 +2,8 @@
name = "Radiation Storm"
typepath = /datum/round_event/radiation_storm
max_occurrences = 1
+ category = EVENT_CATEGORY_SPACE
+ description = "Radiation storm affects the station, forcing the crew to escape to maintenance."
/datum/round_event/radiation_storm
diff --git a/code/modules/events/scrubber_clog.dm b/code/modules/events/scrubber_clog.dm
index 2a253cacc0d..66b40d18c3a 100644
--- a/code/modules/events/scrubber_clog.dm
+++ b/code/modules/events/scrubber_clog.dm
@@ -4,6 +4,8 @@
weight = 25
max_occurrences = 3
earliest_start = 5 MINUTES
+ category = EVENT_CATEGORY_ENTITIES
+ description = "Harmless mobs climb out of a scrubber."
/datum/round_event/scrubber_clog
announceWhen = 10
@@ -124,6 +126,7 @@
weight = 12
max_occurrences = 3
earliest_start = 10 MINUTES
+ description = "Dangerous mobs climb out of a scrubber."
/datum/round_event/scrubber_clog/major/setup()
. = ..()
@@ -148,6 +151,7 @@
min_players = 15
max_occurrences = 1
earliest_start = 25 MINUTES
+ description = "Really dangerous mobs climb out of a scrubber."
/datum/round_event/scrubber_clog/critical
maximum_spawns = 3
@@ -172,6 +176,7 @@
typepath = /datum/round_event/scrubber_clog/strange
weight = 5
max_occurrences = 1
+ description = "Strange mobs climb out of a scrubber, harmfulness varies."
/datum/round_event/scrubber_clog/strange
maximum_spawns = 3
diff --git a/code/modules/events/sentience.dm b/code/modules/events/sentience.dm
index c968b6a7f0a..9880b32ee15 100644
--- a/code/modules/events/sentience.dm
+++ b/code/modules/events/sentience.dm
@@ -20,6 +20,8 @@ GLOBAL_LIST_INIT(high_priority_sentience, typecacheof(list(
name = "Random Human-level Intelligence"
typepath = /datum/round_event/ghost_role/sentience
weight = 10
+ category = EVENT_CATEGORY_FRIENDLY
+ description = "An animal or robot becomes sentient!"
/datum/round_event/ghost_role/sentience
@@ -105,6 +107,8 @@ GLOBAL_LIST_INIT(high_priority_sentience, typecacheof(list(
name = "Station-wide Human-level Intelligence"
typepath = /datum/round_event/ghost_role/sentience/all
weight = 0
+ category = EVENT_CATEGORY_FRIENDLY
+ description = "ALL animals and robots become sentient, provided there is enough ghosts."
/datum/round_event/ghost_role/sentience/all
one = "all"
diff --git a/code/modules/events/shuttle_catastrophe.dm b/code/modules/events/shuttle_catastrophe.dm
index d33f252129a..b9abf368794 100644
--- a/code/modules/events/shuttle_catastrophe.dm
+++ b/code/modules/events/shuttle_catastrophe.dm
@@ -3,6 +3,8 @@
typepath = /datum/round_event/shuttle_catastrophe
weight = 10
max_occurrences = 1
+ category = EVENT_CATEGORY_BUREAUCRATIC
+ description = "Replaces the emergency shuttle with a random one."
/datum/round_event_control/shuttle_catastrophe/canSpawnEvent(players)
if(SSshuttle.shuttle_purchased == SHUTTLEPURCHASE_FORCED)
diff --git a/code/modules/events/shuttle_insurance.dm b/code/modules/events/shuttle_insurance.dm
index 3e8ba81671c..5d8f0ba61b8 100644
--- a/code/modules/events/shuttle_insurance.dm
+++ b/code/modules/events/shuttle_insurance.dm
@@ -4,6 +4,8 @@
name = "Shuttle Insurance"
typepath = /datum/round_event/shuttle_insurance
max_occurrences = 1
+ category = EVENT_CATEGORY_BUREAUCRATIC
+ description = "A sketchy but legit insurance offer."
/datum/round_event_control/shuttle_insurance/canSpawnEvent(players)
if(!SSeconomy.get_dep_account(ACCOUNT_CAR))
diff --git a/code/modules/events/shuttle_loan.dm b/code/modules/events/shuttle_loan.dm
index bcd4f56550b..52d372cfe6e 100644
--- a/code/modules/events/shuttle_loan.dm
+++ b/code/modules/events/shuttle_loan.dm
@@ -13,6 +13,8 @@
typepath = /datum/round_event/shuttle_loan
max_occurrences = 1
earliest_start = 7 MINUTES
+ category = EVENT_CATEGORY_BUREAUCRATIC
+ description = "If cargo accepts the offer, fills the shuttle with loot and/or enemies."
/datum/round_event/shuttle_loan
announceWhen = 1
diff --git a/code/modules/events/space_dragon.dm b/code/modules/events/space_dragon.dm
index 0f106e7210f..f26d36e9645 100644
--- a/code/modules/events/space_dragon.dm
+++ b/code/modules/events/space_dragon.dm
@@ -5,6 +5,8 @@
max_occurrences = 1
min_players = 20
dynamic_should_hijack = TRUE
+ category = EVENT_CATEGORY_ENTITIES
+ description = "Spawns a space dragon, which will try to take over the station."
/datum/round_event/ghost_role/space_dragon
minimum_required = 1
diff --git a/code/modules/events/space_ninja.dm b/code/modules/events/space_ninja.dm
index c81aa033e0d..ab4aef5bafa 100644
--- a/code/modules/events/space_ninja.dm
+++ b/code/modules/events/space_ninja.dm
@@ -6,6 +6,8 @@
earliest_start = 20 MINUTES
min_players = 20
dynamic_should_hijack = TRUE
+ category = EVENT_CATEGORY_INVASION
+ description = "A space ninja infiltrates the station."
/datum/round_event/ghost_role/space_ninja
minimum_required = 1
diff --git a/code/modules/events/spacevine.dm b/code/modules/events/spacevine.dm
index 08b156c6e8f..3b87e47e49e 100644
--- a/code/modules/events/spacevine.dm
+++ b/code/modules/events/spacevine.dm
@@ -48,6 +48,8 @@
weight = 15
max_occurrences = 3
min_players = 10
+ category = EVENT_CATEGORY_ENTITIES
+ description = "Kudzu begins to overtake the station. Might spawn man-traps."
/datum/round_event/spacevine
fakeable = FALSE
diff --git a/code/modules/events/spider_infestation.dm b/code/modules/events/spider_infestation.dm
index 1e5f5620f96..40646bb5322 100644
--- a/code/modules/events/spider_infestation.dm
+++ b/code/modules/events/spider_infestation.dm
@@ -5,6 +5,8 @@
max_occurrences = 1
min_players = 20
dynamic_should_hijack = TRUE
+ category = EVENT_CATEGORY_ENTITIES
+ description = "Spawns spider eggs, ready to hatch."
/datum/round_event/spider_infestation
announceWhen = 400
diff --git a/code/modules/events/stray_cargo.dm b/code/modules/events/stray_cargo.dm
index 603fe79ba97..74ef48d56d6 100644
--- a/code/modules/events/stray_cargo.dm
+++ b/code/modules/events/stray_cargo.dm
@@ -5,6 +5,8 @@
weight = 20
max_occurrences = 4
earliest_start = 10 MINUTES
+ category = EVENT_CATEGORY_BUREAUCRATIC
+ description = "A pod containing a random supply crate lands on the station."
///Spawns a cargo pod containing a random cargo supply pack on a random area of the station
/datum/round_event/stray_cargo
@@ -89,6 +91,7 @@
weight = 6
max_occurrences = 1
earliest_start = 30 MINUTES
+ description = "A pod containing syndicate gear lands on the station."
/datum/round_event/stray_cargo/syndicate
possible_pack_types = list(/datum/supply_pack/misc/syndicate)
diff --git a/code/modules/events/wisdomcow.dm b/code/modules/events/wisdomcow.dm
index 05acb794cc8..04696a2b5d3 100644
--- a/code/modules/events/wisdomcow.dm
+++ b/code/modules/events/wisdomcow.dm
@@ -3,6 +3,8 @@
typepath = /datum/round_event/wisdomcow
max_occurrences = 1
weight = 20
+ category = EVENT_CATEGORY_FRIENDLY
+ description = "A cow appears to tell you wise words."
/datum/round_event/wisdomcow/announce(fake)
priority_announce("A wise cow has been spotted in the area. Be sure to ask for her advice.", "Nanotrasen Cow Ranching Agency")
diff --git a/code/modules/events/wizard/aid.dm b/code/modules/events/wizard/aid.dm
index 92747d2cd76..bed431cd92b 100644
--- a/code/modules/events/wizard/aid.dm
+++ b/code/modules/events/wizard/aid.dm
@@ -7,6 +7,7 @@
typepath = /datum/round_event/wizard/robelesscasting
max_occurrences = 1
earliest_start = 0 MINUTES
+ description = "Wizard no longer needs robes to cast spells."
/datum/round_event/wizard/robelesscasting/start()
@@ -32,6 +33,7 @@
typepath = /datum/round_event/wizard/improvedcasting
max_occurrences = 4 //because that'd be max level spells
earliest_start = 0 MINUTES
+ description = "Levels up the wizard's spells."
/datum/round_event/wizard/improvedcasting/start()
for(var/mob/living/caster as anything in GLOB.mob_living_list)
diff --git a/code/modules/events/wizard/blobies.dm b/code/modules/events/wizard/blobies.dm
index 7438b462f60..6933847f808 100644
--- a/code/modules/events/wizard/blobies.dm
+++ b/code/modules/events/wizard/blobies.dm
@@ -3,6 +3,7 @@
weight = 3
typepath = /datum/round_event/wizard/blobies
max_occurrences = 3
+ description = "Spawns a blob spore on every corpse."
/datum/round_event/wizard/blobies/start()
diff --git a/code/modules/events/wizard/curseditems.dm b/code/modules/events/wizard/curseditems.dm
index 2384901ef29..a634a6fc934 100644
--- a/code/modules/events/wizard/curseditems.dm
+++ b/code/modules/events/wizard/curseditems.dm
@@ -4,6 +4,7 @@
typepath = /datum/round_event/wizard/cursed_items
max_occurrences = 3
earliest_start = 0 MINUTES
+ description = "Gives everyone a cursed item."
//Note about adding items to this: Because of how NODROP_1 works if an item spawned to the hands can also be equiped to a slot
//it will be able to be put into that slot from the hand, but then get stuck there. To avoid this make a new subtype of any
diff --git a/code/modules/events/wizard/departmentrevolt.dm b/code/modules/events/wizard/departmentrevolt.dm
index 631a76711b8..478d2109670 100644
--- a/code/modules/events/wizard/departmentrevolt.dm
+++ b/code/modules/events/wizard/departmentrevolt.dm
@@ -4,6 +4,7 @@
typepath = /datum/round_event/wizard/deprevolt
max_occurrences = 1
earliest_start = 0 MINUTES
+ description = "A department is turned into an independent state."
///manual choice of what department to revolt for admins to pick
var/datum/job_department/picked_department
diff --git a/code/modules/events/wizard/embeddies.dm b/code/modules/events/wizard/embeddies.dm
index 6ea7b3f5ac6..6412ffb0a7b 100644
--- a/code/modules/events/wizard/embeddies.dm
+++ b/code/modules/events/wizard/embeddies.dm
@@ -4,6 +4,7 @@
typepath = /datum/round_event/wizard/embedpocalypse
max_occurrences = 1
earliest_start = 0 MINUTES
+ description = "Everything becomes pointy enough to embed in people when thrown."
///behold... the only reason sticky is a subtype...
/datum/round_event_control/wizard/embedpocalypse/canSpawnEvent(players_amt, gamemode)
@@ -19,6 +20,7 @@
typepath = /datum/round_event/wizard/embedpocalypse/sticky
max_occurrences = 1
earliest_start = 0 MINUTES
+ description = "Everything becomes sticky enough to be glued to people when thrown."
/datum/round_event/wizard/embedpocalypse/sticky/start()
GLOB.global_funny_embedding = new /datum/global_funny_embedding/sticky
diff --git a/code/modules/events/wizard/fakeexplosion.dm b/code/modules/events/wizard/fakeexplosion.dm
index 3d9be6a565d..4368f5dcc93 100644
--- a/code/modules/events/wizard/fakeexplosion.dm
+++ b/code/modules/events/wizard/fakeexplosion.dm
@@ -4,6 +4,7 @@
typepath = /datum/round_event/wizard/fake_explosion
max_occurrences = 1
earliest_start = 0 MINUTES
+ description = "The nuclear explosion cutscene begins to play to scare the crew."
/datum/round_event/wizard/fake_explosion/start()
sound_to_playing_players('sound/machines/alarm.ogg')
diff --git a/code/modules/events/wizard/ghost.dm b/code/modules/events/wizard/ghost.dm
index 5db01bc93e3..b52bdd69352 100644
--- a/code/modules/events/wizard/ghost.dm
+++ b/code/modules/events/wizard/ghost.dm
@@ -4,6 +4,7 @@
typepath = /datum/round_event/wizard/ghost
max_occurrences = 1
earliest_start = 0 MINUTES
+ description = "Ghosts become visible."
/datum/round_event/wizard/ghost/start()
var/msg = span_warning("You suddenly feel extremely obvious...")
@@ -18,6 +19,7 @@
typepath = /datum/round_event/wizard/possession
max_occurrences = 5
earliest_start = 0 MINUTES
+ description = "Ghosts become visible and gain the power of possession."
/datum/round_event/wizard/possession/start()
for(var/mob/dead/observer/G in GLOB.player_list)
diff --git a/code/modules/events/wizard/greentext.dm b/code/modules/events/wizard/greentext.dm
index 9c35aceedb2..eec68e7f2d9 100644
--- a/code/modules/events/wizard/greentext.dm
+++ b/code/modules/events/wizard/greentext.dm
@@ -4,6 +4,7 @@
typepath = /datum/round_event/wizard/greentext
max_occurrences = 1
earliest_start = 0 MINUTES
+ description = "The Green Text appears on the station, tempting people to try and pick it up."
/datum/round_event/wizard/greentext/start()
diff --git a/code/modules/events/wizard/identity_spoof.dm b/code/modules/events/wizard/identity_spoof.dm
index 3032834b63a..4f562752df8 100644
--- a/code/modules/events/wizard/identity_spoof.dm
+++ b/code/modules/events/wizard/identity_spoof.dm
@@ -3,6 +3,7 @@
weight = 5
typepath = /datum/round_event/wizard/identity_spoof
max_occurrences = 1
+ description = "Makes everyone dressed up like a wizard."
/datum/round_event_control/wizard/identity_spoof/canSpawnEvent(players_amt)
. = ..()
diff --git a/code/modules/events/wizard/imposter.dm b/code/modules/events/wizard/imposter.dm
index 34bca08f3f9..4c69d5252fb 100644
--- a/code/modules/events/wizard/imposter.dm
+++ b/code/modules/events/wizard/imposter.dm
@@ -4,6 +4,7 @@
typepath = /datum/round_event/wizard/imposter
max_occurrences = 1
earliest_start = 0 MINUTES
+ description = "Spawns a doppelganger of the wizard."
/datum/round_event/wizard/imposter/start()
for(var/datum/mind/M as anything in get_antag_minds(/datum/antagonist/wizard))
diff --git a/code/modules/events/wizard/invincible.dm b/code/modules/events/wizard/invincible.dm
index 1bcde77cb37..015781c844c 100644
--- a/code/modules/events/wizard/invincible.dm
+++ b/code/modules/events/wizard/invincible.dm
@@ -4,6 +4,7 @@
typepath = /datum/round_event/wizard/invincible
max_occurrences = 5
earliest_start = 0 MINUTES
+ description = "Everyone is invincible for a short time ticks."
/datum/round_event/wizard/invincible/start()
diff --git a/code/modules/events/wizard/lava.dm b/code/modules/events/wizard/lava.dm
index 9a882b45dfd..2a69b8481e5 100644
--- a/code/modules/events/wizard/lava.dm
+++ b/code/modules/events/wizard/lava.dm
@@ -4,6 +4,7 @@
typepath = /datum/round_event/wizard/lava
max_occurrences = 3
earliest_start = 0 MINUTES
+ description = "Turns the floor into hot lava."
/datum/round_event/wizard/lava
endWhen = 0
diff --git a/code/modules/events/wizard/madness.dm b/code/modules/events/wizard/madness.dm
index a5964de1993..b960b074678 100644
--- a/code/modules/events/wizard/madness.dm
+++ b/code/modules/events/wizard/madness.dm
@@ -3,6 +3,7 @@
weight = 1
typepath = /datum/round_event/wizard/madness
earliest_start = 0 MINUTES
+ description = "Reveals a horrifying truth to everyone, giving them a trauma."
var/forced_secret
diff --git a/code/modules/events/wizard/magicarp.dm b/code/modules/events/wizard/magicarp.dm
index 5c884c56a8a..fb53fdbc3e0 100644
--- a/code/modules/events/wizard/magicarp.dm
+++ b/code/modules/events/wizard/magicarp.dm
@@ -4,6 +4,7 @@
typepath = /datum/round_event/wizard/magicarp
max_occurrences = 1
earliest_start = 0 MINUTES
+ description = "Summons a school of carps with magic projectiles."
/datum/round_event/wizard/magicarp
announceWhen = 3
diff --git a/code/modules/events/wizard/petsplosion.dm b/code/modules/events/wizard/petsplosion.dm
index fb4d433905b..3345d1ef438 100644
--- a/code/modules/events/wizard/petsplosion.dm
+++ b/code/modules/events/wizard/petsplosion.dm
@@ -5,6 +5,7 @@
max_occurrences = 1 //Exponential growth is nothing to sneeze at!
earliest_start = 0 MINUTES
var/mobs_to_dupe = 0
+ description = "Rapidly multiplies the animals on the station."
/datum/round_event_control/wizard/petsplosion/preRunEvent()
for(var/mob/living/simple_animal/F in GLOB.alive_mob_list)
diff --git a/code/modules/events/wizard/race.dm b/code/modules/events/wizard/race.dm
index c00f44ea74e..1ff33ce37d8 100644
--- a/code/modules/events/wizard/race.dm
+++ b/code/modules/events/wizard/race.dm
@@ -4,6 +4,7 @@
typepath = /datum/round_event/wizard/race
max_occurrences = 5
earliest_start = 0 MINUTES
+ description = "Gives everyone a random race."
/datum/round_event/wizard/race/start()
diff --git a/code/modules/events/wizard/rpgloot.dm b/code/modules/events/wizard/rpgloot.dm
index a26be5c09e5..5775115fe46 100644
--- a/code/modules/events/wizard/rpgloot.dm
+++ b/code/modules/events/wizard/rpgloot.dm
@@ -4,6 +4,7 @@
typepath = /datum/round_event/wizard/rpgloot
max_occurrences = 1
earliest_start = 0 MINUTES
+ description = "Every item in the world will have fantastical names."
/datum/round_event/wizard/rpgloot/start()
GLOB.rpgloot_controller = new /datum/rpgloot_controller
diff --git a/code/modules/events/rpgtitles.dm b/code/modules/events/wizard/rpgtitles.dm
similarity index 98%
rename from code/modules/events/rpgtitles.dm
rename to code/modules/events/wizard/rpgtitles.dm
index edde7b87bc0..5d40ed541a9 100644
--- a/code/modules/events/rpgtitles.dm
+++ b/code/modules/events/wizard/rpgtitles.dm
@@ -4,6 +4,7 @@
typepath = /datum/round_event/wizard/rpgtitles
max_occurrences = 1
earliest_start = 0 MINUTES
+ description = "Everyone gains an RPG title hovering above them."
/datum/round_event/wizard/rpgtitles/start()
GLOB.rpgtitle_controller = new /datum/rpgtitle_controller
diff --git a/code/modules/events/wizard/shuffle.dm b/code/modules/events/wizard/shuffle.dm
index dce7d3fa20c..ef3ae4bfb77 100644
--- a/code/modules/events/wizard/shuffle.dm
+++ b/code/modules/events/wizard/shuffle.dm
@@ -7,6 +7,7 @@
typepath = /datum/round_event/wizard/shuffleloc
max_occurrences = 5
earliest_start = 0 MINUTES
+ description = "Shuffles everyone around on the station."
/datum/round_event/wizard/shuffleloc/start()
var/list/moblocs = list()
@@ -43,6 +44,7 @@
typepath = /datum/round_event/wizard/shufflenames
max_occurrences = 5
earliest_start = 0 MINUTES
+ description = "Shuffles the names of everyone around the station."
/datum/round_event/wizard/shufflenames/start()
var/list/mobnames = list()
@@ -77,6 +79,7 @@
typepath = /datum/round_event/wizard/shuffleminds
max_occurrences = 3
earliest_start = 0 MINUTES
+ description = "Shuffles the minds of everyone around the station, except for the wizard."
/datum/round_event/wizard/shuffleminds/start()
var/list/mobs_to_swap = list()
diff --git a/code/modules/events/wizard/summons.dm b/code/modules/events/wizard/summons.dm
index 3a5150e39fc..c36e8bc137b 100644
--- a/code/modules/events/wizard/summons.dm
+++ b/code/modules/events/wizard/summons.dm
@@ -4,6 +4,7 @@
typepath = /datum/round_event/wizard/summonguns
max_occurrences = 1
earliest_start = 0 MINUTES
+ description = "Summons a gun for everyone. Might turn people into survivalists."
/datum/round_event_control/wizard/summonguns/New()
if(CONFIG_GET(flag/no_summon_guns))
@@ -19,6 +20,7 @@
typepath = /datum/round_event/wizard/summonmagic
max_occurrences = 1
earliest_start = 0 MINUTES
+ description = "Summons a magic item for everyone. Might turn people into survivalists."
/datum/round_event_control/wizard/summonmagic/New()
if(CONFIG_GET(flag/no_summon_magic))
diff --git a/code/modules/events/wormholes.dm b/code/modules/events/wormholes.dm
index d5575ce6072..3ee31f96c41 100644
--- a/code/modules/events/wormholes.dm
+++ b/code/modules/events/wormholes.dm
@@ -6,6 +6,8 @@ GLOBAL_LIST_EMPTY(all_wormholes) // So we can pick wormholes to teleport to
max_occurrences = 3
weight = 2
min_players = 2
+ category = EVENT_CATEGORY_SPACE
+ description = "Space time anomalies appear on the station, randomly teleporting people who walk into them."
/datum/round_event/wormholes
diff --git a/code/modules/holiday/easter.dm b/code/modules/holiday/easter.dm
index 3e1ab4d59a3..34b300102e9 100644
--- a/code/modules/holiday/easter.dm
+++ b/code/modules/holiday/easter.dm
@@ -5,6 +5,8 @@
weight = -1
max_occurrences = 1
earliest_start = 0 MINUTES
+ category = EVENT_CATEGORY_HOLIDAY
+ description = "Hides surprise filled easter eggs in maintenance."
/datum/round_event/easter/announce(fake)
priority_announce(pick("Hip-hop into Easter!","Find some Bunny's stash!","Today is National 'Hunt a Wabbit' Day.","Be kind, give Chocolate Eggs!"))
@@ -16,6 +18,8 @@
typepath = /datum/round_event/rabbitrelease
weight = 5
max_occurrences = 10
+ category = EVENT_CATEGORY_HOLIDAY
+ description = "Summons a wave of cute rabbits."
/datum/round_event/rabbitrelease/announce(fake)
priority_announce("Unidentified furry objects detected coming aboard [station_name()]. Beware of Adorable-ness.", "Fluffy Alert", ANNOUNCER_ALIENS)
diff --git a/code/modules/tgui/states/fun.dm b/code/modules/tgui/states/fun.dm
new file mode 100644
index 00000000000..ba72f40fd08
--- /dev/null
+++ b/code/modules/tgui/states/fun.dm
@@ -0,0 +1,17 @@
+/*!
+ * Copyright (c) 2020 Aleksej Komarov
+ * SPDX-License-Identifier: MIT
+ */
+
+/**
+ * tgui state: fun_state
+ *
+ * Checks that the user has the fun privilige.
+ */
+
+GLOBAL_DATUM_INIT(fun_state, /datum/ui_state/fun_state, new)
+
+/datum/ui_state/fun_state/can_use_topic(src_object, mob/user)
+ if(check_rights_for(user.client, R_FUN))
+ return UI_INTERACTIVE
+ return UI_CLOSE
diff --git a/tgstation.dme b/tgstation.dme
index ba79180c525..403469b6231 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1983,6 +1983,7 @@
#include "code\modules\admin\create_mob.dm"
#include "code\modules\admin\create_object.dm"
#include "code\modules\admin\create_turf.dm"
+#include "code\modules\admin\force_event.dm"
#include "code\modules\admin\fun_balloon.dm"
#include "code\modules\admin\greyscale_modify_menu.dm"
#include "code\modules\admin\holder2.dm"
@@ -2867,7 +2868,6 @@
#include "code\modules\events\prison_break.dm"
#include "code\modules\events\processor_overload.dm"
#include "code\modules\events\radiation_storm.dm"
-#include "code\modules\events\rpgtitles.dm"
#include "code\modules\events\scrubber_clog.dm"
#include "code\modules\events\sentience.dm"
#include "code\modules\events\shuttle_catastrophe.dm"
@@ -2900,6 +2900,7 @@
#include "code\modules\events\wizard\petsplosion.dm"
#include "code\modules\events\wizard\race.dm"
#include "code\modules\events\wizard\rpgloot.dm"
+#include "code\modules\events\wizard\rpgtitles.dm"
#include "code\modules\events\wizard\shuffle.dm"
#include "code\modules\events\wizard\summons.dm"
#include "code\modules\experisci\destructive_scanner.dm"
@@ -4402,6 +4403,7 @@
#include "code\modules\tgui\states\debug.dm"
#include "code\modules\tgui\states\deep_inventory.dm"
#include "code\modules\tgui\states\default.dm"
+#include "code\modules\tgui\states\fun.dm"
#include "code\modules\tgui\states\hands.dm"
#include "code\modules\tgui\states\human_adjacent.dm"
#include "code\modules\tgui\states\inventory.dm"
diff --git a/tgui/packages/tgui/interfaces/ForceEvent.js b/tgui/packages/tgui/interfaces/ForceEvent.js
new file mode 100644
index 00000000000..36da2962042
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ForceEvent.js
@@ -0,0 +1,116 @@
+import { useBackend, useLocalState } from '../backend';
+import { Stack, Button, Icon, Input, Section, Table } from '../components';
+import { Window } from '../layouts';
+import { flow } from 'common/fp';
+import { filter, sortBy } from 'common/collections';
+
+export const ForceEvent = (props, context) => {
+ return (
+
+
+
+
+
+
+
+ );
+};
+
+export const EventSearch = (props, context) => {
+ const [searchQuery, setSearchQuery] = useLocalState(
+ context,
+ 'searchQuery',
+ ''
+ );
+
+ return (
+
+ );
+};
+
+export const EventOptionsPanel = (props, context) => {
+ const { data } = useBackend(context);
+
+ const [announce, setAnnounce] = useLocalState(context, 'announce', true);
+
+ return (
+ setAnnounce(!announce)}>
+ Announce event to the crew
+
+ );
+};
+
+export const EventContent = (props, context) => {
+ const { data } = useBackend(context);
+
+ const categories = Object.values(data.categories);
+ const sortCategories = sortBy((category) => category.name);
+
+ return sortCategories(categories).map((category) => (
+
+ ));
+};
+
+export const EventList = (props, context) => {
+ const { act } = useBackend(context);
+ const { category } = props;
+ const [searchQuery] = useLocalState(context, 'searchQuery', '');
+ const [announce] = useLocalState(context, 'announce', true);
+
+ const filtered_events = flow([
+ filter((event) =>
+ event.name?.toLowerCase().includes(searchQuery.toLowerCase())
+ ),
+ sortBy((event) => event.name),
+ ])(category.events || []);
+
+ if (!filtered_events.length) {
+ return null;
+ }
+
+ return (
+
+
+ {filtered_events.map((event) => {
+ return (
+
+ {event.name}
+
+
+
+ );
+ })}
+
+
+ );
+};