diff --git a/code/__DEFINES/preferences_defines.dm b/code/__DEFINES/preferences_defines.dm
index 22b06dec3cb..9c6a0f54ba2 100644
--- a/code/__DEFINES/preferences_defines.dm
+++ b/code/__DEFINES/preferences_defines.dm
@@ -84,8 +84,9 @@
// toggles_3 variables. These MUST be prefixed with PREFTOGGLE_3
#define PREFTOGGLE_3_COGBAR_ANIMATIONS (1<<0) // 1
#define PREFTOGGLE_3_DARK_FLASH (1<<1) // 2
+#define PREFTOGGLE_3_POSTCREDS (1<<2) // 4
-#define TOGGLES_3_TOTAL 3 // If you add or remove a preference toggle above, make sure you update this define with the total value of the toggles combined.
+#define TOGGLES_3_TOTAL 7 // If you add or remove a preference toggle above, make sure you update this define with the total value of the toggles combined.
#define TOGGLES_3_DEFAULT (PREFTOGGLE_3_COGBAR_ANIMATIONS)
diff --git a/code/controllers/subsystem/SSticker.dm b/code/controllers/subsystem/SSticker.dm
index aac46d6585d..6c096da2c4c 100644
--- a/code/controllers/subsystem/SSticker.dm
+++ b/code/controllers/subsystem/SSticker.dm
@@ -693,6 +693,9 @@ SUBSYSTEM_DEF(ticker)
kudos_message = pick(length(kudos) > 5 ? special_encouragement_messages : base_encouragement_messages)
to_chat(M, "You received [length(kudos)] kudos from other players this round! [kudos_message]")
+ // Roll credits!
+ generate_credits()
+
// Seal the blackbox, stop collecting info
SSblackbox.Seal()
SSdbcore.SetRoundEnd()
diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm
index be558fbe601..bef46316a6c 100644
--- a/code/modules/client/client_defines.dm
+++ b/code/modules/client/client_defines.dm
@@ -118,6 +118,9 @@
/// Is the client watchlisted
var/watchlisted = FALSE
+ /// list of all credit object bound to this client
+ var/list/credits = list()
+
/// Client's pAI save
var/datum/pai_save/pai_save
diff --git a/code/modules/client/preference/link_processing.dm b/code/modules/client/preference/link_processing.dm
index 1642a093403..67c79403800 100644
--- a/code/modules/client/preference/link_processing.dm
+++ b/code/modules/client/preference/link_processing.dm
@@ -1068,6 +1068,9 @@
if("dark_flash")
toggles3 ^= PREFTOGGLE_3_DARK_FLASH
+ if("toggle_post_credits")
+ toggles3 ^= PREFTOGGLE_3_POSTCREDS
+
if("be_special")
var/r = href_list["role"]
if(r in GLOB.special_roles)
diff --git a/code/modules/client/preference/preferences_toggles.dm b/code/modules/client/preference/preferences_toggles.dm
index 698286bece9..c21ae5dc804 100644
--- a/code/modules/client/preference/preferences_toggles.dm
+++ b/code/modules/client/preference/preferences_toggles.dm
@@ -307,6 +307,16 @@
if(user.prefs.sound & ~SOUND_DISCO)
usr.stop_sound_channel(CHANNEL_JUKEBOX)
+/datum/preference_toggle/toggle_post_credits
+ name = "Toggle Post-Round Credits"
+ description = "Toggle seeing the post-round credit popup."
+ preftoggle_bitflag = PREFTOGGLE_3_POSTCREDS
+ preftoggle_toggle = PREFTOGGLE_TOGGLE2
+ preftoggle_category = PREFTOGGLE_CATEGORY_GENERAL
+ enable_message = "You will now see post-round credits."
+ disable_message = "You will no longer see post-round credits."
+ blackbox_message = "Toggle Post-Round Credits"
+
/datum/preference_toggle/toggle_ghost_pda
name = "Toggle Ghost PDA messages"
description = "Toggle seeing PDA messages as an observer"
diff --git a/code/modules/credits/credits.dm b/code/modules/credits/credits.dm
new file mode 100644
index 00000000000..3648b86b30b
--- /dev/null
+++ b/code/modules/credits/credits.dm
@@ -0,0 +1,184 @@
+#define CREDIT_ROLL_SPEED 100
+#define CREDIT_SPAWN_SPEED 20
+#define CREDIT_ANIMATE_HEIGHT (14 * world.icon_size)
+#define CREDIT_EASE_DURATION 22
+
+GLOBAL_VAR_INIT(end_credits_song, null)
+GLOBAL_VAR_INIT(end_credits_title, null)
+GLOBAL_LIST(end_titles)
+
+/datum/controller/subsystem/ticker/proc/generate_credits()
+
+ if(!GLOB.end_titles)
+ GLOB.end_titles = generate_titles()
+
+ for(var/client/C as anything in GLOB.clients)
+ if(!C)
+ continue
+ if(!length(C.credits))
+ C.roll_credits()
+
+/client/proc/roll_credits()
+ if(!mob.get_preference(PREFTOGGLE_3_POSTCREDS))
+ return
+
+ verbs += /client/proc/clear_credits
+ for(var/I in GLOB.end_titles)
+ if(!length(credits))
+ return
+ var/atom/movable/screen/credit/T = new(null, null, I, src)
+ credits += T
+ T.rollem()
+ sleep(CREDIT_SPAWN_SPEED)
+ addtimer(CALLBACK(src, PROC_REF(end_credits)), CREDIT_ROLL_SPEED - CREDIT_SPAWN_SPEED)
+
+/client/proc/end_credits()
+ clear_credits()
+ verbs -= /client/proc/clear_credits
+
+/client/proc/clear_credits()
+ set name = "Stop End Titles"
+ set category = "OOC"
+ verbs -= /client/proc/clear_credits
+ QDEL_LIST_CONTENTS(credits)
+
+/atom/movable/screen/credit
+ icon_state = "blank"
+ mouse_opacity = 0
+ alpha = 0
+ screen_loc = "CENTER-7,CENTER-7"
+ layer = ABOVE_ALL_MOB_LAYER
+ var/client/parent
+ var/matrix/target
+
+/atom/movable/screen/credit/Initialize(mapload, datum/hud/hud_owner, credited, client/attached_client)
+ . = ..()
+ parent = attached_client
+ maptext = {"
[credited]
"}
+ maptext_height = world.icon_size * 2
+ maptext_width = world.icon_size * 14
+
+/atom/movable/screen/credit/proc/rollem()
+ var/matrix/direction = matrix(transform)
+ direction.Translate(0, CREDIT_ANIMATE_HEIGHT)
+ animate(src, transform = direction, time = CREDIT_ROLL_SPEED)
+ animate(src, alpha = 255, time = CREDIT_EASE_DURATION, flags = ANIMATION_PARALLEL)
+ UNLINT(animate(src, alpha = 0, flags = ANIMATION_PARALLEL, time = CREDIT_EASE_DURATION, delay = CREDIT_ROLL_SPEED - CREDIT_EASE_DURATION))
+ parent?.screen += src
+
+/atom/movable/screen/credit/proc/fadeout(matrix/direction)
+ QDEL_IN(src, CREDIT_EASE_DURATION)
+
+/atom/movable/screen/credit/Destroy()
+ var/client/P = parent
+ if(parent)
+ P.screen -= src
+ P.credits -= src
+ parent = null
+ return ..()
+
+/proc/generate_titles()
+ var/list/titles = list()
+ var/list/cast = list()
+ var/list/chunk = list()
+ var/list/possible_titles = list()
+ var/chunksize = 0
+ if(!GLOB.end_credits_title)
+ /* Establish a big-ass list of potential titles for the "episode". */
+ possible_titles += "THE [pick("DOWNFALL OF ", "RISE OF ", "TROUBLE WITH ", "FINAL STAND OF ", "DARK SIDE OF ", "DESOLATION OF ", "DESTRUCTION OF ", "CRISIS OF ")]\
+ [pick("SPACEMEN", "HUMANITY", "DIGNITY", "SANITY", "THE CHIMPANZEES", "THE VENDOMAT PRICES", "GIANT ARMORED", "THE GAS JANITOR",\
+ "THE SUPERMATTER CRYSTAL", "MEDICAL", "ENGINEERING", "SECURITY", "RESEARCH", "THE SERVICE DEPARTMENT", "COMMAND", "THE CLOWNS", "CARGO")]"
+
+ possible_titles += "THE CREW GETS [pick("TINGLED", "PICKLED", "AN INCURABLE DISEASE", "PIZZA", "A VALUABLE HISTORY LESSON", "A BREAK", "HIGH", "TO LIVE", "TO RELIVE THEIR CHILDHOOD", "EMBROILED IN CIVIL WAR", "A BAD HANGOVER", "SERIOUS ABOUT [pick("DRUG ABUSE", "CRIME", "PRODUCTIVITY", "ANCIENT AMERICAN CARTOONS", "SPACEBALL", "DECOMPRESSION PROCEDURES")]")]"
+ possible_titles += "THE CREW LEARNS ABOUT [pick("LOVE", "DRUGS", "THE DANGERS OF MONEY LAUNDERING", "XENIC SENSITIVITY", "INVESTMENT FRAUD", "KELOTANE ABUSE", "RADIATION PROTECTION", "SACRED GEOMETRY", "STRING THEORY", "ABSTRACT MATHEMATICS", "ANCIENT SKRELLIAN MEDICINE")]"
+ possible_titles += "A VERY [pick("CORPORATE", "NANOTRASEN", "FLEET", "CLOWN", "SYNDICATE", "EXPEDITIONARY", "EXPLORATORY", "PLASMA", "MARTIAN", "UNITED")] [pick("CHRISTMAS", "EASTER", "HOLIDAY", "WEEKEND", "THURSDAY", "VACATION")]"
+ possible_titles += "[pick("GUNS, GUNS EVERYWHERE", "THE LITTLEST PRIMALIS", "WHAT HAPPENS WHEN YOU MIX MAINTENANCE DRONES AND COMMERCIAL-GRADE PACKING FOAM", "ATTACK! ATTACK! ATTACK!", "HOLY SHIT A BOMB", "THE LEGEND OF THE ALIEN ARTIFACT: PART [pick("I","II","III","IV","V","VI","VII","VIII","IX", "X", "C","M","L")]")]"
+ possible_titles += "[pick("SPACE", "TERRIFYING", "DRAGON", "WARLOCK", "LAUNDRY", "GUN", "ADVERTISING", "DOG", "CARBON MONOXIDE", "NINJA", "WIZARD", "SOCRATIC", "JUVENILE DELIQUENCY", "POLITICALLY MOTIVATED", "RADTACULAR SICKNASTY")] [pick("QUEST", "FORCE", "ADVENTURE")]"
+ possible_titles += "[pick("THE DAY [pick("NANOTRASEN", "THE SYNDICATE", "ARMADYNE CORPORATION", "THE CLOWN",)] STOOD STILL", "HUNT FOR THE GREEN WEENIE", "ALIEN VS VENDOMAT", "SPACE TRACK")]"
+ titles += "EPISODE [rand(1, 1000)]
[pick(possible_titles)]
"
+ else
+ titles += "EPISODE [rand(1, 1000)]
[GLOB.end_credits_title]
"
+
+ // The Living
+ for(var/mob/living/carbon/human/H in GLOB.human_list)
+ if(!H.mind)
+ continue
+ if(!length(cast) && !chunksize)
+ chunk += "CREW:"
+ var/jobtitle = H.mind?.assigned_role || "No title"
+ var/used_name = H.mind?.current?.name
+ var/antag_string
+ for(var/datum/antagonist/antagonist as anything in H.mind?.antag_datums)
+ antag_string ? (antag_string += ", ") : (antag_string += "...")
+ antag_string += "[antagonist?.name]"
+ chunk += "[used_name] as the [H.mind?.antag_datums ? "[antag_string] and [jobtitle]" : jobtitle]"
+ chunksize++
+
+ if(chunksize > 2)
+ cast += "[jointext(chunk,"
")]"
+ chunk.Cut()
+ chunksize = 0
+ if(length(chunk))
+ cast += "[jointext(chunk,"
")]"
+
+ titles += cast
+
+ // The Dead
+ var/list/corpses = list()
+ var/list/monkies = list()
+ for(var/mob/living/carbon/human/H in GLOB.dead_mob_list)
+ if(H.timeofdeath < 5 MINUTES) // no prespawned corpses
+ continue
+ if(ismonkeybasic(H))
+ monkies[H?.name] += 1
+ else if(H?.real_name in GLOB.crew_list) // Only crew deaths
+ corpses += H?.real_name
+ if(length(corpses))
+ titles += "BASED ON REAL EVENTS
In memory of [english_list(corpses)]."
+
+ // CKEYS
+ var/list/staff = list("PRODUCTION STAFF:")
+ var/static/list/staffjobs = list("Coffee Fetcher", "Cameraman", "Angry Yeller", "Chair Operator", "Choreographer", "Historical Consultant", "Costume Designer", "Chief Editor", "Executive Assistant")
+ var/list/goodboys = list()
+ for(var/client/C in GLOB.clients)
+ if(!C?.holder)
+ continue
+ if(C?.holder?.fakekey) // No stealthmins
+ continue
+ if(C?.holder?.big_brother) // No big brother admins
+ continue
+ if(C?.holder)
+ staff += "[uppertext(pick(staffjobs))] a.k.a. '[C?.key]'"
+
+ titles += "[jointext(staff,"
")]"
+
+ if(length(goodboys))
+ titles += "STAFF'S GOOD BOYS:
[english_list(goodboys)]
"
+
+ var/disclaimer = "
Sponsored by [pick("Nanotrasen", "The Syndicate", "The Changeling Hivemind", "The Vampire Coven", "The Cultist Library", "YOU, THE PLAYER",)].
All rights reserved.
\
+ This motion picture is protected under the copyright laws of the Trans-Solar Federation,
and other nations throughout the galaxy.
\
+ Planet of First Publication: [pick("Earth", "Luna", "Mars", "Moghes", "Hoorlm", "Kelune", "Mauna-b", "Votum-Accorium", "Ahdomai", "New Canaan", "Boron 2", \
+ "Qerballak", "Xarxis", "Altam", "Dõm", "Strend", "Tirstein", "Isueth", "Skkula-Accorium", "Blackgate Prime", "Nova Cygni")].
"
+ disclaimer += pick("Use for parody prohibited. PROHIBITED.",
+ "All stunts were performed by underpaid interns. Do NOT try at home.",
+ "[pick("Nanotrasen", "The Syndicate", "The Trans-Solar Federation", "The USSP", "The Technocracy", "The Changeling Hivemind", "The Vampire Coven", "The Cultist Library", )] \
+ does not endorse behaviour depicted. Attempt at your own risk.",
+ "Any unauthorized exhibition, distribution, or copying of this film or any part thereof (including soundtrack)
\
+ may result in an ERT being called to storm your home and take it back by force.",
+ "The story, all names, characters, and incidents portrayed in this production are fictitious. No identification with actual
\
+ persons (living or deceased), places, buildings, and products is intended or should be inferred.
\
+ This film is based on a true story and all individuals depicted are based on real people, despite what we just said.",
+ "No person or entity associated with this film received payment or anything of value, or entered into any agreement, in connection
\
+ with the depiction of tobacco products, despite the copious amounts of smoking depicted within.
\
+ (This disclaimer sponsored by [pick("Nanotrasen - Have a Nanotrasen day!", "The Syndicate - kill first, ask questions never!", "The Changeling Hivemind - we see, hear, and observe all!", \
+ "The Vampire Coven - operating from the shadows!", "The Cultist Library - spreading true knowledge!", "You, and other players like you!",)](TM)).",
+ "No animals were harmed in the making of this motion picture except for those listed previously as dead. Do not try this at home before signing a waiver.")
+ titles += "
"
+ titles += "[disclaimer]"
+
+ return titles
+
+#undef CREDIT_ROLL_SPEED
+#undef CREDIT_SPAWN_SPEED
+#undef CREDIT_ANIMATE_HEIGHT
+#undef CREDIT_EASE_DURATION
diff --git a/paradise.dme b/paradise.dme
index b2f5f9b6680..b7d876dfd58 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -2063,6 +2063,7 @@
#include "code\modules\crafting\guncrafting.dm"
#include "code\modules\crafting\recipes.dm"
#include "code\modules\crafting\tailoring.dm"
+#include "code\modules\credits\credits.dm"
#include "code\modules\customitems\item_defines.dm"
#include "code\modules\detective_work\detective_work.dm"
#include "code\modules\detective_work\evidence.dm"