diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 73fbe6723e6..4af9f4396be 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -892,6 +892,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
/// For clarity, this trait should always be associated/tied to a reference to the mob that suicided- not anything else.
#define TRAIT_SUICIDED "committed_suicide"
+/// Trait given to a living mob to prevent wizards from making it immortal
+#define TRAIT_PERMANENTLY_MORTAL "permanently_mortal"
+
///Trait given to a mob with a ckey currently in a temporary body, allowing people to know someone will re-enter the round later.
#define TRAIT_MIND_TEMPORARILY_GONE "temporarily_gone"
@@ -993,6 +996,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define MOTOR_LACK_TRAIT "motor-lack"
/// Trait associated with mafia
#define MAFIA_TRAIT "mafia"
+/// Trait associated with ctf
+#define CTF_TRAIT "ctf"
/// Trait associated with highlander
#define HIGHLANDER_TRAIT "highlander"
/// Trait given from playing pretend with baguettes
diff --git a/code/datums/actions/action.dm b/code/datums/actions/action.dm
index 843998ff50e..d81c72cc920 100644
--- a/code/datums/actions/action.dm
+++ b/code/datums/actions/action.dm
@@ -117,7 +117,7 @@
if(!hud.mymob)
continue
HideFrom(hud.mymob)
- LAZYREMOVE(remove_from.actions, src) // We aren't always properly inserted into the viewers list, gotta make sure that action's cleared
+ LAZYREMOVE(remove_from?.actions, src) // We aren't always properly inserted into the viewers list, gotta make sure that action's cleared
viewers = list()
if(owner)
diff --git a/code/datums/components/chasm.dm b/code/datums/components/chasm.dm
index fc93123921c..18420016c54 100644
--- a/code/datums/components/chasm.dm
+++ b/code/datums/components/chasm.dm
@@ -8,28 +8,29 @@
/// List of refs to falling objects -> how many levels deep we've fallen
var/static/list/falling_atoms = list()
var/static/list/forbidden_types = typecacheof(list(
- /obj/singularity,
- /obj/energy_ball,
- /obj/narsie,
/obj/docking_port,
- /obj/structure/lattice,
- /obj/structure/stone_tile,
- /obj/projectile,
- /obj/effect/projectile,
- /obj/effect/portal,
/obj/effect/abstract,
- /obj/effect/hotspot,
- /obj/effect/landmark,
- /obj/effect/temp_visual,
- /obj/effect/light_emitter/tendril,
/obj/effect/collapse,
- /obj/effect/particle_effect/ion_trails,
+ /obj/effect/constructing_effect,
/obj/effect/dummy/phased_mob,
- /obj/effect/mapping_helpers,
- /obj/effect/wisp,
/obj/effect/ebeam,
/obj/effect/fishing_lure,
- /obj/effect/constructing_effect,
+ /obj/effect/hotspot,
+ /obj/effect/landmark,
+ /obj/effect/light_emitter/tendril,
+ /obj/effect/mapping_helpers,
+ /obj/effect/particle_effect/ion_trails,
+ /obj/effect/portal,
+ /obj/effect/projectile,
+ /obj/effect/spectre_of_resurrection,
+ /obj/effect/temp_visual,
+ /obj/effect/wisp,
+ /obj/energy_ball,
+ /obj/narsie,
+ /obj/projectile,
+ /obj/singularity,
+ /obj/structure/lattice,
+ /obj/structure/stone_tile,
))
/datum/component/chasm/Initialize(turf/target, mapload)
diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm
index 3b2a13c2cb5..062412f70e9 100644
--- a/code/modules/antagonists/cult/runes.dm
+++ b/code/modules/antagonists/cult/runes.dm
@@ -886,7 +886,7 @@ structure_check() searches for nearby cultist structures required for the invoca
new_human.equipOutfit(/datum/outfit/ghost_cultist) //give them armor
new_human.apply_status_effect(/datum/status_effect/cultghost) //ghosts can't summon more ghosts
new_human.set_invis_see(SEE_INVISIBLE_OBSERVER)
- ADD_TRAIT(new_human, TRAIT_NOBREATH, INNATE_TRAIT)
+ new_human.add_traits(list(TRAIT_NOBREATH, TRAIT_PERMANENTLY_MORTAL), INNATE_TRAIT) // permanently mortal can be removed once this is a bespoke kind of mob
ghosts++
playsound(src, 'sound/magic/exit_blood.ogg', 50, TRUE)
visible_message(span_warning("A cloud of red mist forms above [src], and from within steps... a [new_human.gender == FEMALE ? "wo":""]man."))
diff --git a/code/modules/antagonists/wizard/grand_ritual/finales/all_access.dm b/code/modules/antagonists/wizard/grand_ritual/finales/all_access.dm
new file mode 100644
index 00000000000..07958ed94a7
--- /dev/null
+++ b/code/modules/antagonists/wizard/grand_ritual/finales/all_access.dm
@@ -0,0 +1,17 @@
+/// Open all of the doors
+/datum/grand_finale/all_access
+ name = "Connection"
+ desc = "The ultimate use of your gathered power! Unlock every single door that they have! Nobody will be able to keep you out now, or anyone else for that matter!"
+ icon = 'icons/mob/actions/actions_spells.dmi'
+ icon_state = "knock"
+
+/datum/grand_finale/all_access/trigger(mob/living/carbon/human/invoker)
+ message_admins("[key_name(invoker)] removed all door access requirements")
+ for(var/obj/machinery/door/target_door as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/door))
+ if(is_station_level(target_door.z))
+ target_door.unlock()
+ target_door.req_access = list()
+ target_door.req_one_access = list()
+ INVOKE_ASYNC(target_door, TYPE_PROC_REF(/obj/machinery/door/airlock, open))
+ CHECK_TICK
+ priority_announce("AULIE OXIN FIERA!!", null, 'sound/magic/knock.ogg', sender_override = "[invoker.real_name]")
diff --git a/code/modules/antagonists/wizard/grand_ritual/finales/armageddon.dm b/code/modules/antagonists/wizard/grand_ritual/finales/armageddon.dm
new file mode 100644
index 00000000000..876f2475d55
--- /dev/null
+++ b/code/modules/antagonists/wizard/grand_ritual/finales/armageddon.dm
@@ -0,0 +1,60 @@
+#define DOOM_SINGULARITY "singularity"
+#define DOOM_TESLA "tesla"
+#define DOOM_METEORS "meteors"
+
+/// Kill yourself and probably a bunch of other people
+/datum/grand_finale/armageddon
+ name = "Annihilation"
+ desc = "This crew have offended you beyond the realm of pranks. Make the ultimate sacrifice to teach them a lesson your elders can really respect. \
+ YOU WILL NOT SURVIVE THIS."
+ icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi'
+ icon_state = "legion_head"
+ minimum_time = 90 MINUTES // This will probably immediately end the round if it gets finished.
+ ritual_invoke_time = 60 SECONDS // Really give the crew some time to interfere with this one.
+ dire_warning = TRUE
+ glow_colour = "#be000048"
+ /// Things to yell before you die
+ var/static/list/possible_last_words = list(
+ "Flames and ruin!",
+ "Dooooooooom!!",
+ "HAHAHAHAHAHA!! AHAHAHAHAHAHAHAHAA!!",
+ "Hee hee hee!! Hoo hoo hoo!! Ha ha haaa!!",
+ "Ohohohohohoho!!",
+ "Cower in fear, puny mortals!",
+ "Tremble before my glory!",
+ "Pick a god and pray!",
+ "It's no use!",
+ "If the gods wanted you to live, they would not have created me!",
+ "God stays in heaven out of fear of what I have created!",
+ "Ruination is come!",
+ "All of creation, bend to my will!",
+ )
+
+/datum/grand_finale/armageddon/trigger(mob/living/carbon/human/invoker)
+ priority_announce(pick(possible_last_words), null, 'sound/magic/voidblink.ogg', sender_override = "[invoker.real_name]")
+ var/turf/current_location = get_turf(invoker)
+ invoker.gib()
+
+ var/static/list/doom_options = list()
+ if (!length(doom_options))
+ doom_options = list(DOOM_SINGULARITY, DOOM_TESLA)
+ if (!SSmapping.config.planetary)
+ doom_options += DOOM_METEORS
+
+ switch(pick(doom_options))
+ if (DOOM_SINGULARITY)
+ var/obj/singularity/singulo = new(current_location)
+ singulo.energy = 300
+ if (DOOM_TESLA)
+ var/obj/energy_ball/tesla = new (current_location)
+ tesla.energy = 200
+ if (DOOM_METEORS)
+ var/datum/dynamic_ruleset/roundstart/meteor/meteors = new()
+ meteors.meteordelay = 0
+ var/datum/game_mode/dynamic/mode = SSticker.mode
+ mode.execute_roundstart_rule(meteors) // Meteors will continue until morale is crushed.
+ priority_announce("Meteors have been detected on collision course with the station.", "Meteor Alert", ANNOUNCER_METEORS)
+
+#undef DOOM_SINGULARITY
+#undef DOOM_TESLA
+#undef DOOM_METEORS
diff --git a/code/modules/antagonists/wizard/grand_ritual/finales/captaincy.dm b/code/modules/antagonists/wizard/grand_ritual/finales/captaincy.dm
new file mode 100644
index 00000000000..d1a3c1afaf7
--- /dev/null
+++ b/code/modules/antagonists/wizard/grand_ritual/finales/captaincy.dm
@@ -0,0 +1,113 @@
+/// Become the official Captain of the station
+/datum/grand_finale/usurp
+ name = "Usurpation"
+ desc = "The ultimate use of your gathered power! Rewrite time such that you have been Captain of this station the whole time."
+ icon = 'icons/obj/card.dmi'
+ icon_state = "card_gold"
+
+/datum/grand_finale/usurp/trigger(mob/living/carbon/human/invoker)
+ message_admins("[key_name(invoker)] has replaced the Captain")
+ var/list/former_captains = list()
+ var/list/other_crew = list()
+ SEND_SOUND(world, sound('sound/magic/timeparadox2.ogg'))
+
+ for (var/mob/living/carbon/human/crewmate as anything in GLOB.human_list)
+ if (!crewmate.mind)
+ continue
+ crewmate.Unconscious(3 SECONDS) // Everyone falls unconscious but not everyone gets told about a new captain
+ if (crewmate == invoker || IS_HUMAN_INVADER(crewmate))
+ continue
+ to_chat(crewmate, span_notice("The world spins and dissolves. Your past flashes before your eyes, backwards.\n\
+ Life strolls back into the ocean and shrinks into nothingness, planets explode into storms of solar dust, \
+ the stars rush back to greet each other at the beginning of things and then... you snap back to the present. \n\
+ Everything is just as it was and always has been. \n\n\
+ A stray thought sticks in the forefront of your mind. \n\
+ [span_hypnophrase("I'm so glad that [invoker.real_name] is our legally appointed Captain!")] \n\
+ Is... that right?"))
+ if (is_captain_job(crewmate.mind.assigned_role))
+ former_captains += crewmate
+ demote_to_assistant(crewmate)
+ continue
+ if (crewmate.stat != DEAD)
+ other_crew += crewmate
+
+ dress_candidate(invoker)
+ GLOB.manifest.modify(invoker.real_name, JOB_CAPTAIN, JOB_CAPTAIN)
+ minor_announce("Captain [invoker.real_name] on deck!")
+
+ // Enlist some crew to try and restore the natural order
+ for (var/mob/living/carbon/human/former_captain as anything in former_captains)
+ create_vendetta(former_captain.mind, invoker.mind)
+ for (var/mob/living/carbon/human/random_crewmate as anything in other_crew)
+ if (prob(10))
+ create_vendetta(random_crewmate.mind, invoker.mind)
+
+/**
+ * Anyone who thought they were Captain is in for a nasty surprise, and won't be very happy about it
+ */
+/datum/grand_finale/usurp/proc/demote_to_assistant(mob/living/carbon/human/former_captain)
+ var/obj/effect/particle_effect/fluid/smoke/exit_poof = new(get_turf(former_captain))
+ exit_poof.lifetime = 2 SECONDS
+
+ former_captain.unequip_everything()
+ if(isplasmaman(former_captain))
+ former_captain.equipOutfit(/datum/outfit/plasmaman)
+ former_captain.internal = former_captain.get_item_for_held_index(2)
+ else
+ former_captain.equipOutfit(/datum/outfit/job/assistant)
+
+ GLOB.manifest.modify(former_captain.real_name, JOB_ASSISTANT, JOB_ASSISTANT)
+ var/list/valid_turfs = list()
+ // Used to be into prison but that felt a bit too mean
+ for (var/turf/exile_turf as anything in get_area_turfs(/area/station/maintenance, subtypes = TRUE))
+ if (isspaceturf(exile_turf) || exile_turf.is_blocked_turf())
+ continue
+ valid_turfs += exile_turf
+ do_teleport(former_captain, pick(valid_turfs), no_effects = TRUE)
+ var/obj/effect/particle_effect/fluid/smoke/enter_poof = new(get_turf(former_captain))
+ enter_poof.lifetime = 2 SECONDS
+
+/**
+ * Does some item juggling to try to dress you as both a Wizard and Captain without deleting any items you have bought.
+ * ID, headset, and uniform are forcibly replaced. Other slots are only filled if unoccupied.
+ * We could forcibly replace shoes and gloves too but people might miss their insuls or... meown shoes?
+ */
+/datum/grand_finale/usurp/proc/dress_candidate(mob/living/carbon/human/invoker)
+ // Won't be needing these
+ var/obj/id = invoker.get_item_by_slot(ITEM_SLOT_ID)
+ QDEL_NULL(id)
+ var/obj/headset = invoker.get_item_by_slot(ITEM_SLOT_EARS)
+ QDEL_NULL(headset)
+ // We're about to take off your pants so those are going to fall out
+ var/obj/item/pocket_L = invoker.get_item_by_slot(ITEM_SLOT_LPOCKET)
+ var/obj/item/pocket_R = invoker.get_item_by_slot(ITEM_SLOT_RPOCKET)
+ // In case we try to put a PDA there
+ var/obj/item/belt = invoker.get_item_by_slot(ITEM_SLOT_BELT)
+ belt?.moveToNullspace()
+
+ var/obj/pants = invoker.get_item_by_slot(ITEM_SLOT_ICLOTHING)
+ QDEL_NULL(pants)
+ invoker.equipOutfit(/datum/outfit/job/wizard_captain)
+ // And put everything back!
+ equip_to_slot_then_hands(invoker, ITEM_SLOT_BELT, belt)
+ equip_to_slot_then_hands(invoker, ITEM_SLOT_LPOCKET, pocket_L)
+ equip_to_slot_then_hands(invoker, ITEM_SLOT_RPOCKET, pocket_R)
+
+/// An outfit which replaces parts of a wizard's clothes with captain's clothes but keeps the robes
+/datum/outfit/job/wizard_captain
+ name = "Captain (Wizard Transformation)"
+ jobtype = /datum/job/captain
+ id = /obj/item/card/id/advanced/gold
+ id_trim = /datum/id_trim/job/captain
+ uniform = /obj/item/clothing/under/rank/captain/parade
+ belt = /obj/item/modular_computer/pda/heads/captain
+ ears = /obj/item/radio/headset/heads/captain/alt
+ glasses = /obj/item/clothing/glasses/sunglasses
+ gloves = /obj/item/clothing/gloves/captain
+ shoes = /obj/item/clothing/shoes/laceup
+ accessory = /obj/item/clothing/accessory/medal/gold/captain
+ backpack_contents = list(
+ /obj/item/melee/baton/telescopic = 1,
+ /obj/item/station_charter = 1,
+ )
+ box = null
diff --git a/code/modules/antagonists/wizard/grand_ritual/finales/cheese.dm b/code/modules/antagonists/wizard/grand_ritual/finales/cheese.dm
new file mode 100644
index 00000000000..714cd62659b
--- /dev/null
+++ b/code/modules/antagonists/wizard/grand_ritual/finales/cheese.dm
@@ -0,0 +1,49 @@
+/**
+ * Gives the wizard a defensive/mood buff and a Wabbajack, a juiced up chaos staff that will surely break something.
+ * Everyone but the wizard goes crazy, suffers major brain damage, and is given a vendetta against the wizard.
+ * Already insane people are instead cured of their madness, ignoring any other effects as the station around them loses its marbles.
+ */
+/datum/grand_finale/cheese
+ // we don't set name, desc and others, thus we won't appear in the radial choice of a normal finale rune
+ dire_warning = TRUE
+ minimum_time = 45 MINUTES //i'd imagine speedrunning this would be crummy, but the wizard's average lifespan is barely reaching this point
+
+/datum/grand_finale/cheese/trigger(mob/living/invoker)
+ message_admins("[key_name(invoker)] has summoned forth The Wabbajack and cursed the crew with madness!")
+ priority_announce("Danger: Extremely potent reality altering object has been summoned on station. Immediate evacuation advised. Brace for impact.", "Central Command Higher Dimensional Affairs", 'sound/effects/glassbr1.ogg')
+
+ for (var/mob/living/carbon/human/crewmate as anything in GLOB.human_list)
+ if (isnull(crewmate.mind))
+ continue
+ if (crewmate == invoker) //everyone but the wizard is royally fucked, no matter who they are
+ continue
+ if (crewmate.has_trauma_type(/datum/brain_trauma/mild/hallucinations)) //for an already insane person, this is retribution
+ to_chat(crewmate, span_boldwarning("Your surroundings suddenly fill with a cacophony of manic laughter and psychobabble..."))
+ to_chat(crewmate, span_nicegreen("...but as the moment passes, you realise that whatever eldritch power behind the event happened to affect you \
+ has resonated within the ruins of your already shattered mind, creating a singularity of mental instability! \
+ As it collapses unto itself, you feel... at peace, finally."))
+ if(crewmate.has_quirk(/datum/quirk/insanity))
+ crewmate.remove_quirk(/datum/quirk/insanity)
+ else
+ crewmate.cure_trauma_type(/datum/brain_trauma/mild/hallucinations, TRAUMA_RESILIENCE_ABSOLUTE)
+ else
+ //everyone else gets to relish in madness
+ //yes killing their mood will also trigger mood hallucinations
+ create_vendetta(crewmate.mind, invoker.mind)
+ to_chat(crewmate, span_boldwarning("Your surroundings suddenly fill with a cacophony of manic laughter and psychobabble. \n\
+ You feel your inner psyche shatter into a myriad pieces of jagged glass of colors unknown to the universe, \
+ infinitely reflecting a blinding, maddening light coming from the innermost sanctums of your destroyed mind. \n\
+ After a brief pause which felt like a millenia, one phrase rebounds ceaselessly in your head, imbued with the false hope of absolution... \n\
+ [invoker] must die."))
+ var/datum/brain_trauma/mild/hallucinations/added_trauma = new()
+ added_trauma.resilience = TRAUMA_RESILIENCE_ABSOLUTE
+ crewmate.adjustOrganLoss(ORGAN_SLOT_BRAIN, BRAIN_DAMAGE_DEATH - 25, BRAIN_DAMAGE_DEATH - 25) //you'd better hope chap didn't pick a hypertool
+ crewmate.gain_trauma(added_trauma)
+ crewmate.add_mood_event("wizard_ritual_finale", /datum/mood_event/madness_despair)
+
+ //drip our wizard out
+ invoker.apply_status_effect(/datum/status_effect/blessing_of_insanity)
+ invoker.add_mood_event("wizard_ritual_finale", /datum/mood_event/madness_elation)
+ var/obj/item/gun/magic/staff/chaos/true_wabbajack/the_wabbajack = new
+ invoker.put_in_active_hand(the_wabbajack)
+ to_chat(invoker, span_mind_control("Your every single instinct and rational thought is screaming at you as [the_wabbajack] appears in your firm grip..."))
diff --git a/code/modules/antagonists/wizard/grand_ritual/finales/clown.dm b/code/modules/antagonists/wizard/grand_ritual/finales/clown.dm
new file mode 100644
index 00000000000..bda79c908c0
--- /dev/null
+++ b/code/modules/antagonists/wizard/grand_ritual/finales/clown.dm
@@ -0,0 +1,76 @@
+/// Dress the crew as magical clowns
+/datum/grand_finale/clown
+ name = "Jubilation"
+ desc = "The ultimate use of your gathered power! Rewrite time so that everyone went to clown college! Now they'll prank each other for you!"
+ icon = 'icons/obj/clothing/masks.dmi'
+ icon_state = "clown"
+ glow_colour = "#ffff0048"
+
+/datum/grand_finale/clown/trigger(mob/living/carbon/human/invoker)
+ for(var/mob/living/carbon/human/victim as anything in GLOB.human_list)
+ victim.Unconscious(3 SECONDS)
+ if (!victim.mind || IS_HUMAN_INVADER(victim) || victim == invoker)
+ continue
+ if (HAS_TRAIT(victim, TRAIT_CLOWN_ENJOYER))
+ victim.add_mood_event("clown_world", /datum/mood_event/clown_world)
+ to_chat(victim, span_notice("The world spins and dissolves. Your past flashes before your eyes, backwards.\n\
+ Life strolls back into the ocean and shrinks into nothingness, planets explode into storms of solar dust, \
+ the stars rush back to greet each other at the beginning of things and then... you snap back to the present. \n\
+ Everything is just as it was and always has been. \n\n\
+ A stray thought sticks in the forefront of your mind. \n\
+ [span_hypnophrase("I'm so glad that I work at Clown Research Station [station_name()]!")] \n\
+ Is... that right?"))
+ if (is_clown_job(victim.mind.assigned_role))
+ var/datum/action/cooldown/spell/conjure_item/clown_pockets/new_spell = new(victim)
+ new_spell.Grant(victim)
+ continue
+ if (!ismonkey(victim)) // Monkeys cannot yet wear clothes
+ dress_as_magic_clown(victim)
+ if (prob(15))
+ create_vendetta(victim.mind, invoker.mind)
+
+/**
+ * Clown enjoyers who are effected by this become ecstatic, they have achieved their life's dream.
+ * This moodlet is equivalent to the one for simply being a traitor.
+ */
+/datum/mood_event/clown_world
+ mood_change = 4
+
+/datum/mood_event/clown_world/add_effects(param)
+ description = "I LOVE working at Clown Research Station [station_name()]!!"
+
+/// Dress the passed mob as a magical clown, self-explanatory
+/datum/grand_finale/clown/proc/dress_as_magic_clown(mob/living/carbon/human/victim)
+ var/obj/effect/particle_effect/fluid/smoke/poof = new(get_turf(victim))
+ poof.lifetime = 2 SECONDS
+
+ var/obj/item/tank/internal = victim.internal
+ // We're about to take off your pants so those are going to fall out
+ var/obj/item/pocket_L = victim.get_item_by_slot(ITEM_SLOT_LPOCKET)
+ var/obj/item/pocket_R = victim.get_item_by_slot(ITEM_SLOT_RPOCKET)
+ var/obj/item/id = victim.get_item_by_slot(ITEM_SLOT_ID)
+ var/obj/item/belt = victim.get_item_by_slot(ITEM_SLOT_BELT)
+
+ var/obj/pants = victim.get_item_by_slot(ITEM_SLOT_ICLOTHING)
+ var/obj/mask = victim.get_item_by_slot(ITEM_SLOT_MASK)
+ QDEL_NULL(pants)
+ QDEL_NULL(mask)
+ if(isplasmaman(victim))
+ victim.equip_to_slot_if_possible(new /obj/item/clothing/under/plasmaman/clown/magic(), ITEM_SLOT_ICLOTHING, disable_warning = TRUE)
+ victim.equip_to_slot_if_possible(new /obj/item/clothing/mask/gas/clown_hat/plasmaman(), ITEM_SLOT_MASK, disable_warning = TRUE)
+ else
+ victim.equip_to_slot_if_possible(new /obj/item/clothing/under/rank/civilian/clown/magic(), ITEM_SLOT_ICLOTHING, disable_warning = TRUE)
+ victim.equip_to_slot_if_possible(new /obj/item/clothing/mask/gas/clown_hat(), ITEM_SLOT_MASK, disable_warning = TRUE)
+
+ var/obj/item/clothing/mask/gas/clown_hat/clown_mask = victim.get_item_by_slot(ITEM_SLOT_MASK)
+ if (clown_mask)
+ var/list/options = GLOB.clown_mask_options
+ clown_mask.icon_state = options[pick(clown_mask.clownmask_designs)]
+ victim.update_worn_mask()
+ clown_mask.update_item_action_buttons()
+
+ equip_to_slot_then_hands(victim, ITEM_SLOT_LPOCKET, pocket_L)
+ equip_to_slot_then_hands(victim, ITEM_SLOT_RPOCKET, pocket_R)
+ equip_to_slot_then_hands(victim, ITEM_SLOT_ID, id)
+ equip_to_slot_then_hands(victim, ITEM_SLOT_BELT, belt)
+ victim.internal = internal
diff --git a/code/modules/antagonists/wizard/grand_ritual/finales/grand_ritual_finale.dm b/code/modules/antagonists/wizard/grand_ritual/finales/grand_ritual_finale.dm
new file mode 100644
index 00000000000..b92ae4d2f20
--- /dev/null
+++ b/code/modules/antagonists/wizard/grand_ritual/finales/grand_ritual_finale.dm
@@ -0,0 +1,88 @@
+/**
+ * A big final event to run when you complete seven rituals
+ */
+/datum/grand_finale
+ /// Friendly name for selection menu
+ var/name
+ /// Tooltip description for selection menu
+ var/desc
+ /// An icon to display to represent the choice
+ var/icon/icon
+ /// Icon state to use to represent the choice
+ var/icon_state
+ /// Prevent especially dangerous options from being chosen until we're fine with the round ending
+ var/minimum_time = 0
+ /// Override the rune invocation time
+ var/ritual_invoke_time = 30 SECONDS
+ /// Provide an extremely loud radio message when this one starts
+ var/dire_warning = FALSE
+ /// Overrides the default colour you glow while channeling the rune, optional
+ var/glow_colour
+
+/**
+ * Returns an entry for a radial menu for this choice.
+ * Returns null if entry is abstract or invalid for current circumstances.
+ */
+/datum/grand_finale/proc/get_radial_choice()
+ if (!name || !desc || !icon || !icon_state)
+ return
+ var/time_remaining_desc = ""
+ if (minimum_time >= world.time - SSticker.round_start_time)
+ time_remaining_desc = "This ritual will be available to begin invoking in [DisplayTimeText(minimum_time - world.time - SSticker.round_start_time)]"
+ var/datum/radial_menu_choice/choice = new()
+ choice.name = name
+ choice.image = image(icon = icon, icon_state = icon_state)
+ choice.info = desc + time_remaining_desc
+ return choice
+
+/**
+ * Actually do the thing.
+ * Arguments
+ * * invoker - The wizard casting this.
+ */
+/datum/grand_finale/proc/trigger(mob/living/invoker)
+ // Do something cool.
+
+/// Tries to equip something into an inventory slot, then hands, then the floor.
+/datum/grand_finale/proc/equip_to_slot_then_hands(mob/living/carbon/human/invoker, slot, obj/item/item)
+ if(!item)
+ return
+ if(!invoker.equip_to_slot_if_possible(item, slot, disable_warning = TRUE))
+ invoker.put_in_hands(item)
+
+/// They are not going to take this lying down.
+/datum/grand_finale/proc/create_vendetta(datum/mind/aggrieved_crewmate, datum/mind/wizard)
+ aggrieved_crewmate.add_antag_datum(/datum/antagonist/wizard_prank_vendetta)
+ var/datum/antagonist/wizard_prank_vendetta/antag_datum = aggrieved_crewmate.has_antag_datum(/datum/antagonist/wizard_prank_vendetta)
+ var/datum/objective/assassinate/wizard_murder = new
+ wizard_murder.owner = aggrieved_crewmate
+ wizard_murder.target = wizard
+ wizard_murder.explanation_text = "Kill [wizard.current.name], the one who did this."
+ antag_datum.objectives += wizard_murder
+
+ to_chat(aggrieved_crewmate.current, span_warning("No! This isn't right!"))
+ aggrieved_crewmate.announce_objectives()
+
+/**
+ * Antag datum to give to people who want to kill the wizard.
+ * This doesn't preclude other people choosing to want to kill the wizard, just these people are rewarded for it.
+ */
+/datum/antagonist/wizard_prank_vendetta
+ name = "\improper Wizard Prank Victim"
+ roundend_category = "wizard prank victims"
+ show_in_antagpanel = FALSE
+ antagpanel_category = "Other"
+ show_name_in_check_antagonists = TRUE
+ count_against_dynamic_roll_chance = FALSE
+ silent = TRUE
+
+/// Give everyone magic items, its so simple it feels pointless to give it its own file
+/datum/grand_finale/magic
+ name = "Evolution"
+ desc = "The ultimate use of your gathered power! Give the crew their own magic, they'll surely realise that right and wrong have no meaning when you hold ultimate power!"
+ icon = 'icons/obj/scrolls.dmi'
+ icon_state = "scroll"
+
+/datum/grand_finale/magic/trigger(mob/living/carbon/human/invoker)
+ message_admins("[key_name(invoker)] summoned magic")
+ summon_magic(survivor_probability = 20) // Wow, this one was easy!
diff --git a/code/modules/antagonists/wizard/grand_ritual/finales/immortality.dm b/code/modules/antagonists/wizard/grand_ritual/finales/immortality.dm
new file mode 100644
index 00000000000..d20ca06752b
--- /dev/null
+++ b/code/modules/antagonists/wizard/grand_ritual/finales/immortality.dm
@@ -0,0 +1,277 @@
+/// Amount of time to wait after someone dies to steal their body from their killers
+#define IMMORTAL_PRE_ACTIVATION_TIME 10 SECONDS
+/// Amount of time it takes a mob to return to the living world
+#define IMMORTAL_RESURRECT_TIME 50 SECONDS
+
+/**
+ * Nobody will ever die ever again
+ * Or if they do, they will be back
+ */
+/datum/grand_finale/immortality
+ name = "Perpetuation"
+ desc = "The ultimate use of your gathered power! Share with the crew the gift, or curse, of eternal life! \
+ And why not just the crew? How about their pets too? And any other animals around here! \
+ What if nobody died ever again!?"
+ icon = 'icons/obj/mining_zones/artefacts.dmi'
+ icon_state = "asclepius_active"
+ glow_colour = COLOR_PALE_GREEN
+ minimum_time = 30 MINUTES // This is enormously disruptive but doesn't technically in of itself end the round.
+
+/datum/grand_finale/immortality/trigger(mob/living/carbon/human/invoker)
+ new /obj/effect/temp_visual/immortality_blast(get_turf(invoker))
+ SEND_SOUND(world, sound('sound/magic/teleport_diss.ogg'))
+ for (var/mob/living/alive_guy as anything in GLOB.mob_living_list)
+ new /obj/effect/temp_visual/immortality_pulse(get_turf(alive_guy))
+ if (!alive_guy.mind)
+ continue
+ to_chat(alive_guy, span_notice("You feel extremely healthy."))
+ RegisterSignal(SSdcs, COMSIG_GLOB_MOB_DEATH, PROC_REF(something_died))
+
+/// Called when something passes into the great beyond, make it not do that
+/datum/grand_finale/immortality/proc/something_died(datum/source, mob/living/died, gibbed)
+ SIGNAL_HANDLER
+ if (died.stat != DEAD || HAS_TRAIT(died, TRAIT_PERMANENTLY_MORTAL) || died.flags_1 & HOLOGRAM_1)
+ return
+ var/body_type = died.type
+
+ var/turf/died_turf = get_turf(died)
+ var/list/nearby_turfs = circle_view_turfs(died_turf, 2)
+ var/list/nearby_safe_turfs = list()
+ for (var/turf/check_turf as anything in nearby_turfs)
+ if (check_turf.is_blocked_turf(exclude_mobs = TRUE, source_atom = died))
+ nearby_turfs -= check_turf
+ continue
+ if (islava(check_turf) || ischasm(check_turf))
+ continue
+ nearby_safe_turfs += check_turf
+ if (length(nearby_safe_turfs)) // If you're in the middle of a 5x5 chasm, tough luck I guess
+ died_turf = pick(nearby_safe_turfs)
+ else if (length(nearby_turfs))
+ died_turf = pick(nearby_turfs)
+
+ var/saved_appearance = ishuman(died) ? new /datum/human_appearance_profile(died) : null
+
+ var/datum/mind/dead_mind = HAS_TRAIT(died, TRAIT_SUICIDED) ? null : died.mind // There is a way out of the cycle
+ if (!isnull(dead_mind))
+ to_chat(died, span_boldnotice("Your spirit surges! You will return to life in [DisplayTimeText(IMMORTAL_PRE_ACTIVATION_TIME + IMMORTAL_RESURRECT_TIME)]."))
+ animate(died, alpha = died.alpha, time = IMMORTAL_PRE_ACTIVATION_TIME / 2, flags = ANIMATION_PARALLEL)
+ animate(alpha = 0, time = IMMORTAL_PRE_ACTIVATION_TIME / 2, easing = SINE_EASING | EASE_IN)
+ addtimer(CALLBACK(src, PROC_REF(reverse_death), died, dead_mind, died_turf, body_type, saved_appearance), IMMORTAL_PRE_ACTIVATION_TIME, TIMER_DELETE_ME)
+
+/// Create a ghost ready for revival
+/datum/grand_finale/immortality/proc/reverse_death(mob/living/died, datum/mind/dead_mind, turf/died_turf, body_type, datum/human_appearance_profile/human_appearance)
+ if (died.stat != DEAD)
+ return
+ var/ghost_type = ispath(body_type, /mob/living/carbon/human) ? /obj/effect/spectre_of_resurrection/human : /obj/effect/spectre_of_resurrection
+ var/obj/effect/spectre_of_resurrection/ghost = new ghost_type(died_turf)
+ var/mob/living/corpse = QDELETED(died) ? new body_type(ghost) : died
+ if (!isnull(human_appearance))
+ corpse.real_name = human_appearance.name
+ corpse.alpha = initial(corpse.alpha)
+ corpse.add_traits(list(TRAIT_NO_TELEPORT, TRAIT_AI_PAUSED), MAGIC_TRAIT)
+ corpse.apply_status_effect(/datum/status_effect/grouped/stasis, MAGIC_TRAIT)
+ ghost.set_up_resurrection(corpse, dead_mind, human_appearance)
+
+
+/// Store of data we use to recreate someone who was gibbed, like a simplified version of changeling profiles
+/datum/human_appearance_profile
+ /// The name of the profile / the name of whoever this profile source.
+ var/name = "human"
+ /// The DNA datum associated with our profile from the profile source
+ var/datum/dna/dna
+ /// The age of the profile source.
+ var/age
+ /// The body type of the profile source.
+ var/physique
+ /// The quirks of the profile source.
+ var/list/quirks = list()
+ /// The hair and facial hair gradient styles of the profile source.
+ var/list/hair_gradient_style = list("None", "None")
+ /// The hair and facial hair gradient colours of the profile source.
+ var/list/hair_gradient_colours = list(null, null)
+ /// The TTS voice of the profile source
+ var/voice
+ /// The TTS filter of the profile filter
+ var/voice_filter = ""
+
+/datum/human_appearance_profile/New(mob/living/carbon/human/target)
+ copy_from(target)
+
+/// Copy the appearance data of the target
+/datum/human_appearance_profile/proc/copy_from(mob/living/carbon/human/target)
+ target.dna.real_name = target.real_name
+ dna = new target.dna.type()
+ target.dna.copy_dna(dna)
+ name = target.real_name
+ age = target.age
+ physique = target.physique
+
+ for(var/datum/quirk/target_quirk as anything in target.quirks)
+ LAZYADD(quirks, new target_quirk.type)
+
+ hair_gradient_style = LAZYLISTDUPLICATE(target.grad_style)
+ hair_gradient_colours = LAZYLISTDUPLICATE(target.grad_color)
+
+ voice = target.voice
+ voice_filter = target.voice_filter
+
+/// Make the targetted human look like this
+/datum/human_appearance_profile/proc/apply_to(mob/living/carbon/human/target)
+ target.real_name = name
+ target.age = age
+ target.physique = physique
+ target.grad_style = LAZYLISTDUPLICATE(hair_gradient_style)
+ target.grad_color = LAZYLISTDUPLICATE(hair_gradient_colours)
+ target.voice = voice
+ target.voice_filter = voice_filter
+
+ for(var/datum/quirk/target_quirk as anything in quirks)
+ target_quirk.add_to_holder(target)
+
+ dna.transfer_identity(target, TRUE)
+ for(var/obj/item/bodypart/limb as anything in target.bodyparts)
+ limb.update_limb(is_creating = TRUE)
+ target.updateappearance(mutcolor_update = TRUE)
+ target.domutcheck()
+ target.regenerate_icons()
+
+
+/// A ghostly image of a mob showing where and what is going to respawn
+/obj/effect/spectre_of_resurrection
+ name = "spectre"
+ desc = "A frightening apparition, slowly growing more solid."
+ icon_state = "blank_white"
+ anchored = TRUE
+ layer = MOB_LAYER
+ plane = GAME_PLANE
+ alpha = 0
+ color = COLOR_PALE_GREEN
+ light_range = 2
+ light_color = COLOR_PALE_GREEN
+ /// Who are we reviving?
+ var/mob/living/corpse
+ /// Who if anyone is playing as them?
+ var/datum/mind/dead_mind
+
+/obj/effect/spectre_of_resurrection/Initialize(mapload)
+ . = ..()
+ animate(src, alpha = 150, time = 2 SECONDS)
+
+/// Prepare to revive someone
+/obj/effect/spectre_of_resurrection/proc/set_up_resurrection(mob/living/corpse, datum/mind/dead_mind, datum/human_appearance_profile/human_appearance)
+ if (isnull(corpse))
+ qdel(src)
+ return
+
+ src.corpse = corpse
+ src.dead_mind = dead_mind
+ corpse.forceMove(src)
+ name = "spectre of [corpse]"
+ setup_icon(corpse)
+ DO_FLOATING_ANIM(src)
+
+ RegisterSignal(corpse, COMSIG_LIVING_REVIVE, PROC_REF(on_corpse_revived))
+ RegisterSignal(corpse, COMSIG_QDELETING, PROC_REF(on_corpse_deleted))
+ RegisterSignal(dead_mind, COMSIG_QDELETING, PROC_REF(on_mind_lost))
+ addtimer(CALLBACK(src, PROC_REF(revive)), IMMORTAL_RESURRECT_TIME, TIMER_DELETE_ME)
+
+/// Copy appearance from ressurecting mob
+/obj/effect/spectre_of_resurrection/proc/setup_icon(mob/living/corpse)
+ icon = initial(corpse.icon)
+ icon_state = initial(corpse.icon_state)
+
+/obj/effect/spectre_of_resurrection/Destroy(force)
+ QDEL_NULL(corpse)
+ dead_mind = null
+ return ..()
+
+/obj/effect/spectre_of_resurrection/Exited(atom/movable/gone, direction)
+ . = ..()
+ if (gone != corpse)
+ return // Weird but ok
+ UnregisterSignal(corpse, list(COMSIG_LIVING_REVIVE, COMSIG_QDELETING))
+ corpse = null
+ qdel(src)
+
+/// Bring our body back to life
+/obj/effect/spectre_of_resurrection/proc/revive()
+ if (!isnull(dead_mind))
+ if (dead_mind.current == corpse)
+ dead_mind.grab_ghost(force = TRUE)
+ else
+ dead_mind.transfer_to(corpse, force_key_move = TRUE)
+ corpse.revive(HEAL_ALL) // The signal is sent even if they weren't actually dead
+
+/// Remove our stored corpse back to the living world
+/obj/effect/spectre_of_resurrection/proc/on_corpse_revived()
+ SIGNAL_HANDLER
+ if (isnull(corpse))
+ return
+ visible_message(span_boldnotice("[corpse] suddenly shudders to life!"))
+ corpse.remove_traits(list(TRAIT_NO_TELEPORT, TRAIT_AI_PAUSED), MAGIC_TRAIT)
+ corpse.remove_status_effect(/datum/status_effect/grouped/stasis, MAGIC_TRAIT)
+ corpse.forceMove(loc)
+
+/// If the body is destroyed then we can't come back, F
+/obj/effect/spectre_of_resurrection/proc/on_corpse_deleted()
+ SIGNAL_HANDLER
+ qdel(src)
+
+/// If the mind is deleted somehow we just don't transfer it on revival
+/obj/effect/spectre_of_resurrection/proc/on_mind_lost()
+ SIGNAL_HANDLER
+ dead_mind = null
+
+/// A ressurection spectre with extra behaviour for humans
+/obj/effect/spectre_of_resurrection/human
+ /// Stored data used to restore someone to a fascimile of what they were before
+ var/datum/human_appearance_profile/human_appearance
+
+/obj/effect/spectre_of_resurrection/human/set_up_resurrection(mob/living/corpse, datum/mind/dead_mind, datum/human_appearance_profile/human_appearance)
+ . = ..()
+ src.human_appearance = human_appearance
+
+// We just use a generic floating human appearance to save unecessary costly icon operations
+/obj/effect/spectre_of_resurrection/human/setup_icon(mob/living/corpse)
+ return
+
+// Apply stored human details
+/obj/effect/spectre_of_resurrection/human/on_corpse_revived()
+ if (isnull(corpse))
+ return
+ human_appearance?.apply_to(corpse)
+ return ..()
+
+
+/// Visual flair on the wizard when cast
+/obj/effect/temp_visual/immortality_blast
+ name = "immortal wave"
+ duration = 2.5 SECONDS
+ icon = 'icons/effects/96x96.dmi'
+ icon_state = "boh_tear"
+ color = COLOR_PALE_GREEN
+ pixel_x = -32
+ pixel_y = -32
+
+/obj/effect/temp_visual/immortality_blast/Initialize(mapload)
+ . = ..()
+ transform *= 0
+ animate(src, transform = matrix(), time = 1.5 SECONDS, easing = ELASTIC_EASING)
+ animate(transform = matrix() * 3, time = 1 SECONDS, alpha = 0, easing = SINE_EASING | EASE_OUT)
+
+
+/// Visual flair on living creatures who have become immortal
+/obj/effect/temp_visual/immortality_pulse
+ name = "immortal pulse"
+ duration = 1 SECONDS
+ icon = 'icons/effects/anomalies.dmi'
+ icon_state = "dimensional_overlay"
+ color = COLOR_PALE_GREEN
+
+/obj/effect/temp_visual/immortality_pulse/Initialize(mapload)
+ . = ..()
+ transform *= 0
+ animate(src, transform = matrix() * 1.5, alpha = 0, time = 1 SECONDS, easing = SINE_EASING | EASE_OUT)
+
+#undef IMMORTAL_PRE_ACTIVATION_TIME
+#undef IMMORTAL_RESURRECT_TIME
diff --git a/code/modules/antagonists/wizard/grand_ritual/finales/midas.dm b/code/modules/antagonists/wizard/grand_ritual/finales/midas.dm
new file mode 100644
index 00000000000..b2e3329261f
--- /dev/null
+++ b/code/modules/antagonists/wizard/grand_ritual/finales/midas.dm
@@ -0,0 +1,46 @@
+/// Completely transform the station
+/datum/grand_finale/midas
+ name = "Transformation"
+ desc = "The ultimate use of your gathered power! Turn their precious station into something much MORE precious, materially speaking!"
+ icon = 'icons/obj/stack_objects.dmi'
+ icon_state = "sheet-gold_2"
+ glow_colour = "#dbdd4c48"
+ var/static/list/permitted_transforms = list( // Non-dangerous only
+ /datum/dimension_theme/gold,
+ /datum/dimension_theme/meat,
+ /datum/dimension_theme/pizza,
+ /datum/dimension_theme/natural,
+ )
+ var/datum/dimension_theme/chosen_theme
+
+// I sure hope this doesn't have performance implications
+/datum/grand_finale/midas/trigger(mob/living/carbon/human/invoker)
+ var/theme_path = pick(permitted_transforms)
+ chosen_theme = new theme_path()
+ var/turf/start_turf = get_turf(invoker)
+ var/greatest_dist = 0
+ var/list/turfs_to_transform = list()
+ for (var/turf/transform_turf as anything in GLOB.station_turfs)
+ if (!chosen_theme.can_convert(transform_turf))
+ continue
+ var/dist = get_dist(start_turf, transform_turf)
+ if (dist > greatest_dist)
+ greatest_dist = dist
+ if (!turfs_to_transform["[dist]"])
+ turfs_to_transform["[dist]"] = list()
+ turfs_to_transform["[dist]"] += transform_turf
+
+ if (chosen_theme.can_convert(start_turf))
+ chosen_theme.apply_theme(start_turf)
+
+ for (var/iterator in 1 to greatest_dist)
+ if(!turfs_to_transform["[iterator]"])
+ continue
+ addtimer(CALLBACK(src, PROC_REF(transform_area), turfs_to_transform["[iterator]"]), (5 SECONDS) * iterator)
+
+/datum/grand_finale/midas/proc/transform_area(list/turfs)
+ for (var/turf/transform_turf as anything in turfs)
+ if (!chosen_theme.can_convert(transform_turf))
+ continue
+ chosen_theme.apply_theme(transform_turf)
+ CHECK_TICK
diff --git a/code/modules/antagonists/wizard/grand_ritual/grand_ritual_finale.dm b/code/modules/antagonists/wizard/grand_ritual/grand_ritual_finale.dm
deleted file mode 100644
index 9a59b1f1a79..00000000000
--- a/code/modules/antagonists/wizard/grand_ritual/grand_ritual_finale.dm
+++ /dev/null
@@ -1,456 +0,0 @@
-#define DOOM_SINGULARITY "singularity"
-#define DOOM_TESLA "tesla"
-#define DOOM_METEORS "meteors"
-
-/**
- * A big final event to run when you complete seven rituals
- */
-/datum/grand_finale
- /// Friendly name for selection menu
- var/name
- /// Tooltip description for selection menu
- var/desc
- /// An icon to display to represent the choice
- var/icon/icon
- /// Icon state to use to represent the choice
- var/icon_state
- /// Prevent especially dangerous options from being chosen until we're fine with the round ending
- var/minimum_time = 0
- /// Override the rune invocation time
- var/ritual_invoke_time = 30 SECONDS
- /// Provide an extremely loud radio message when this one starts
- var/dire_warning = FALSE
- /// Overrides the default colour you glow while channeling the rune, optional
- var/glow_colour
-
-/**
- * Returns an entry for a radial menu for this choice.
- * Returns null if entry is abstract or invalid for current circumstances.
- */
-/datum/grand_finale/proc/get_radial_choice()
- if (!name || !desc || !icon || !icon_state)
- return
- var/time_remaining_desc = ""
- if (minimum_time >= world.time - SSticker.round_start_time)
- time_remaining_desc = "This ritual will be available to begin invoking in [DisplayTimeText(minimum_time - world.time - SSticker.round_start_time)]"
- var/datum/radial_menu_choice/choice = new()
- choice.name = name
- choice.image = image(icon = icon, icon_state = icon_state)
- choice.info = desc + time_remaining_desc
- return choice
-
-/**
- * Actually do the thing.
- * Arguments
- * * invoker - The wizard casting this.
- */
-/datum/grand_finale/proc/trigger(mob/living/invoker)
- // Do something cool.
-
-/// Tries to equip something into an inventory slot, then hands, then the floor.
-/datum/grand_finale/proc/equip_to_slot_then_hands(mob/living/carbon/human/invoker, slot, obj/item/item)
- if(!item)
- return
- if(!invoker.equip_to_slot_if_possible(item, slot, disable_warning = TRUE))
- invoker.put_in_hands(item)
-
-/// They are not going to take this lying down.
-/datum/grand_finale/proc/create_vendetta(datum/mind/aggrieved_crewmate, datum/mind/wizard)
- aggrieved_crewmate.add_antag_datum(/datum/antagonist/wizard_prank_vendetta)
- var/datum/antagonist/wizard_prank_vendetta/antag_datum = aggrieved_crewmate.has_antag_datum(/datum/antagonist/wizard_prank_vendetta)
- var/datum/objective/assassinate/wizard_murder = new
- wizard_murder.owner = aggrieved_crewmate
- wizard_murder.target = wizard
- wizard_murder.explanation_text = "Kill [wizard.current.name], the one who did this."
- antag_datum.objectives += wizard_murder
-
- to_chat(aggrieved_crewmate.current, span_warning("No! This isn't right!"))
- aggrieved_crewmate.announce_objectives()
-
-/**
- * Antag datum to give to people who want to kill the wizard.
- * This doesn't preclude other people choosing to want to kill the wizard, just these people are rewarded for it.
- */
-/datum/antagonist/wizard_prank_vendetta
- name = "\improper Wizard Prank Victim"
- roundend_category = "wizard prank victims"
- show_in_antagpanel = FALSE
- antagpanel_category = "Other"
- show_name_in_check_antagonists = TRUE
- count_against_dynamic_roll_chance = FALSE
- silent = TRUE
-
-/// Become the official Captain of the station
-/datum/grand_finale/usurp
- name = "Usurpation"
- desc = "The ultimate use of your gathered power! Rewrite time such that you have been Captain of this station the whole time."
- icon = 'icons/obj/card.dmi'
- icon_state = "card_gold"
-
-/datum/grand_finale/usurp/trigger(mob/living/carbon/human/invoker)
- message_admins("[key_name(invoker)] has replaced the Captain")
- var/list/former_captains = list()
- var/list/other_crew = list()
- SEND_SOUND(world, sound('sound/magic/timeparadox2.ogg'))
-
- for (var/mob/living/carbon/human/crewmate as anything in GLOB.human_list)
- if (!crewmate.mind)
- continue
- crewmate.Unconscious(3 SECONDS) // Everyone falls unconscious but not everyone gets told about a new captain
- if (crewmate == invoker || IS_HUMAN_INVADER(crewmate))
- continue
- to_chat(crewmate, span_notice("The world spins and dissolves. Your past flashes before your eyes, backwards.\n\
- Life strolls back into the ocean and shrinks into nothingness, planets explode into storms of solar dust, \
- the stars rush back to greet each other at the beginning of things and then... you snap back to the present. \n\
- Everything is just as it was and always has been. \n\n\
- A stray thought sticks in the forefront of your mind. \n\
- [span_hypnophrase("I'm so glad that [invoker.real_name] is our legally appointed Captain!")] \n\
- Is... that right?"))
- if (is_captain_job(crewmate.mind.assigned_role))
- former_captains += crewmate
- demote_to_assistant(crewmate)
- continue
- if (crewmate.stat != DEAD)
- other_crew += crewmate
-
- dress_candidate(invoker)
- GLOB.manifest.modify(invoker.real_name, JOB_CAPTAIN, JOB_CAPTAIN)
- minor_announce("Captain [invoker.real_name] on deck!")
-
- // Enlist some crew to try and restore the natural order
- for (var/mob/living/carbon/human/former_captain as anything in former_captains)
- create_vendetta(former_captain.mind, invoker.mind)
- for (var/mob/living/carbon/human/random_crewmate as anything in other_crew)
- if (prob(10))
- create_vendetta(random_crewmate.mind, invoker.mind)
-
-/**
- * Anyone who thought they were Captain is in for a nasty surprise, and won't be very happy about it
- */
-/datum/grand_finale/usurp/proc/demote_to_assistant(mob/living/carbon/human/former_captain)
- var/obj/effect/particle_effect/fluid/smoke/exit_poof = new(get_turf(former_captain))
- exit_poof.lifetime = 2 SECONDS
-
- former_captain.unequip_everything()
- if(isplasmaman(former_captain))
- former_captain.equipOutfit(/datum/outfit/plasmaman)
- former_captain.internal = former_captain.get_item_for_held_index(2)
- else
- former_captain.equipOutfit(/datum/outfit/job/assistant)
-
- GLOB.manifest.modify(former_captain.real_name, JOB_ASSISTANT, JOB_ASSISTANT)
- var/list/valid_turfs = list()
- // Used to be into prison but that felt a bit too mean
- for (var/turf/exile_turf as anything in get_area_turfs(/area/station/maintenance, subtypes = TRUE))
- if (isspaceturf(exile_turf) || exile_turf.is_blocked_turf())
- continue
- valid_turfs += exile_turf
- do_teleport(former_captain, pick(valid_turfs), no_effects = TRUE)
- var/obj/effect/particle_effect/fluid/smoke/enter_poof = new(get_turf(former_captain))
- enter_poof.lifetime = 2 SECONDS
-
-/**
- * Does some item juggling to try to dress you as both a Wizard and Captain without deleting any items you have bought.
- * ID, headset, and uniform are forcibly replaced. Other slots are only filled if unoccupied.
- * We could forcibly replace shoes and gloves too but people might miss their insuls or... meown shoes?
- */
-/datum/grand_finale/usurp/proc/dress_candidate(mob/living/carbon/human/invoker)
- // Won't be needing these
- var/obj/id = invoker.get_item_by_slot(ITEM_SLOT_ID)
- QDEL_NULL(id)
- var/obj/headset = invoker.get_item_by_slot(ITEM_SLOT_EARS)
- QDEL_NULL(headset)
- // We're about to take off your pants so those are going to fall out
- var/obj/item/pocket_L = invoker.get_item_by_slot(ITEM_SLOT_LPOCKET)
- var/obj/item/pocket_R = invoker.get_item_by_slot(ITEM_SLOT_RPOCKET)
- // In case we try to put a PDA there
- var/obj/item/belt = invoker.get_item_by_slot(ITEM_SLOT_BELT)
- belt?.moveToNullspace()
-
- var/obj/pants = invoker.get_item_by_slot(ITEM_SLOT_ICLOTHING)
- QDEL_NULL(pants)
- invoker.equipOutfit(/datum/outfit/job/wizard_captain)
- // And put everything back!
- equip_to_slot_then_hands(invoker, ITEM_SLOT_BELT, belt)
- equip_to_slot_then_hands(invoker, ITEM_SLOT_LPOCKET, pocket_L)
- equip_to_slot_then_hands(invoker, ITEM_SLOT_RPOCKET, pocket_R)
-
-/// An outfit which replaces parts of a wizard's clothes with captain's clothes but keeps the robes
-/datum/outfit/job/wizard_captain
- name = "Captain (Wizard Transformation)"
- jobtype = /datum/job/captain
- id = /obj/item/card/id/advanced/gold
- id_trim = /datum/id_trim/job/captain
- uniform = /obj/item/clothing/under/rank/captain/parade
- belt = /obj/item/modular_computer/pda/heads/captain
- ears = /obj/item/radio/headset/heads/captain/alt
- glasses = /obj/item/clothing/glasses/sunglasses
- gloves = /obj/item/clothing/gloves/captain
- shoes = /obj/item/clothing/shoes/laceup
- accessory = /obj/item/clothing/accessory/medal/gold/captain
- backpack_contents = list(
- /obj/item/melee/baton/telescopic = 1,
- /obj/item/station_charter = 1,
- )
- box = null
-
-/// Dress the crew as magical clowns
-/datum/grand_finale/clown
- name = "Jubilation"
- desc = "The ultimate use of your gathered power! Rewrite time so that everyone went to clown college! Now they'll prank each other for you!"
- icon = 'icons/obj/clothing/masks.dmi'
- icon_state = "clown"
- glow_colour = "#ffff0048"
-
-/datum/grand_finale/clown/trigger(mob/living/carbon/human/invoker)
- for(var/mob/living/carbon/human/victim as anything in GLOB.human_list)
- victim.Unconscious(3 SECONDS)
- if (!victim.mind || IS_HUMAN_INVADER(victim) || victim == invoker)
- continue
- if (HAS_TRAIT(victim, TRAIT_CLOWN_ENJOYER))
- victim.add_mood_event("clown_world", /datum/mood_event/clown_world)
- to_chat(victim, span_notice("The world spins and dissolves. Your past flashes before your eyes, backwards.\n\
- Life strolls back into the ocean and shrinks into nothingness, planets explode into storms of solar dust, \
- the stars rush back to greet each other at the beginning of things and then... you snap back to the present. \n\
- Everything is just as it was and always has been. \n\n\
- A stray thought sticks in the forefront of your mind. \n\
- [span_hypnophrase("I'm so glad that I work at Clown Research Station [station_name()]!")] \n\
- Is... that right?"))
- if (is_clown_job(victim.mind.assigned_role))
- var/datum/action/cooldown/spell/conjure_item/clown_pockets/new_spell = new(victim)
- new_spell.Grant(victim)
- continue
- if (!ismonkey(victim)) // Monkeys cannot yet wear clothes
- dress_as_magic_clown(victim)
- if (prob(15))
- create_vendetta(victim.mind, invoker.mind)
-
-/**
- * Clown enjoyers who are effected by this become ecstatic, they have achieved their life's dream.
- * This moodlet is equivalent to the one for simply being a traitor.
- */
-/datum/mood_event/clown_world
- mood_change = 4
-
-/datum/mood_event/clown_world/add_effects(param)
- description = "I LOVE working at Clown Research Station [station_name()]!!"
-
-/// Dress the passed mob as a magical clown, self-explanatory
-/datum/grand_finale/clown/proc/dress_as_magic_clown(mob/living/carbon/human/victim)
- var/obj/effect/particle_effect/fluid/smoke/poof = new(get_turf(victim))
- poof.lifetime = 2 SECONDS
-
- var/obj/item/tank/internal = victim.internal
- // We're about to take off your pants so those are going to fall out
- var/obj/item/pocket_L = victim.get_item_by_slot(ITEM_SLOT_LPOCKET)
- var/obj/item/pocket_R = victim.get_item_by_slot(ITEM_SLOT_RPOCKET)
- var/obj/item/id = victim.get_item_by_slot(ITEM_SLOT_ID)
- var/obj/item/belt = victim.get_item_by_slot(ITEM_SLOT_BELT)
-
- var/obj/pants = victim.get_item_by_slot(ITEM_SLOT_ICLOTHING)
- var/obj/mask = victim.get_item_by_slot(ITEM_SLOT_MASK)
- QDEL_NULL(pants)
- QDEL_NULL(mask)
- if(isplasmaman(victim))
- victim.equip_to_slot_if_possible(new /obj/item/clothing/under/plasmaman/clown/magic(), ITEM_SLOT_ICLOTHING, disable_warning = TRUE)
- victim.equip_to_slot_if_possible(new /obj/item/clothing/mask/gas/clown_hat/plasmaman(), ITEM_SLOT_MASK, disable_warning = TRUE)
- else
- victim.equip_to_slot_if_possible(new /obj/item/clothing/under/rank/civilian/clown/magic(), ITEM_SLOT_ICLOTHING, disable_warning = TRUE)
- victim.equip_to_slot_if_possible(new /obj/item/clothing/mask/gas/clown_hat(), ITEM_SLOT_MASK, disable_warning = TRUE)
-
- var/obj/item/clothing/mask/gas/clown_hat/clown_mask = victim.get_item_by_slot(ITEM_SLOT_MASK)
- if (clown_mask)
- var/list/options = GLOB.clown_mask_options
- clown_mask.icon_state = options[pick(clown_mask.clownmask_designs)]
- victim.update_worn_mask()
- clown_mask.update_item_action_buttons()
-
- equip_to_slot_then_hands(victim, ITEM_SLOT_LPOCKET, pocket_L)
- equip_to_slot_then_hands(victim, ITEM_SLOT_RPOCKET, pocket_R)
- equip_to_slot_then_hands(victim, ITEM_SLOT_ID, id)
- equip_to_slot_then_hands(victim, ITEM_SLOT_BELT, belt)
- victim.internal = internal
-
-/// Give everyone magic items
-/datum/grand_finale/magic
- name = "Evolution"
- desc = "The ultimate use of your gathered power! Give the crew their own magic, they'll surely realise that right and wrong have no meaning when you hold ultimate power!"
- icon = 'icons/obj/scrolls.dmi'
- icon_state = "scroll"
-
-/datum/grand_finale/magic/trigger(mob/living/carbon/human/invoker)
- message_admins("[key_name(invoker)] summoned magic")
- summon_magic(survivor_probability = 20) // Wow, this one was easy!
-
-/// Open all of the doors
-/datum/grand_finale/all_access
- name = "Connection"
- desc = "The ultimate use of your gathered power! Unlock every single door that they have! Nobody will be able to keep you out now, or anyone else for that matter!"
- icon = 'icons/mob/actions/actions_spells.dmi'
- icon_state = "knock"
-
-/datum/grand_finale/all_access/trigger(mob/living/carbon/human/invoker)
- message_admins("[key_name(invoker)] removed all door access requirements")
- for(var/obj/machinery/door/target_door as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/door))
- if(is_station_level(target_door.z))
- target_door.unlock()
- target_door.req_access = list()
- target_door.req_one_access = list()
- INVOKE_ASYNC(target_door, TYPE_PROC_REF(/obj/machinery/door/airlock, open))
- CHECK_TICK
- priority_announce("AULIE OXIN FIERA!!", null, 'sound/magic/knock.ogg', sender_override = "[invoker.real_name]")
-
-/// Completely transform the station
-/datum/grand_finale/midas
- name = "Transformation"
- desc = "The ultimate use of your gathered power! Turn their precious station into something much MORE precious, materially speaking!"
- icon = 'icons/obj/stack_objects.dmi'
- icon_state = "sheet-gold_2"
- glow_colour = "#dbdd4c48"
- var/static/list/permitted_transforms = list( // Non-dangerous only
- /datum/dimension_theme/gold,
- /datum/dimension_theme/meat,
- /datum/dimension_theme/pizza,
- /datum/dimension_theme/natural,
- )
- var/datum/dimension_theme/chosen_theme
-
-// I sure hope this doesn't have performance implications
-/datum/grand_finale/midas/trigger(mob/living/carbon/human/invoker)
- var/theme_path = pick(permitted_transforms)
- chosen_theme = new theme_path()
- var/turf/start_turf = get_turf(invoker)
- var/greatest_dist = 0
- var/list/turfs_to_transform = list()
- for (var/turf/transform_turf as anything in GLOB.station_turfs)
- if (!chosen_theme.can_convert(transform_turf))
- continue
- var/dist = get_dist(start_turf, transform_turf)
- if (dist > greatest_dist)
- greatest_dist = dist
- if (!turfs_to_transform["[dist]"])
- turfs_to_transform["[dist]"] = list()
- turfs_to_transform["[dist]"] += transform_turf
-
- if (chosen_theme.can_convert(start_turf))
- chosen_theme.apply_theme(start_turf)
-
- for (var/iterator in 1 to greatest_dist)
- if(!turfs_to_transform["[iterator]"])
- continue
- addtimer(CALLBACK(src, PROC_REF(transform_area), turfs_to_transform["[iterator]"]), (5 SECONDS) * iterator)
-
-/datum/grand_finale/midas/proc/transform_area(list/turfs)
- for (var/turf/transform_turf as anything in turfs)
- if (!chosen_theme.can_convert(transform_turf))
- continue
- chosen_theme.apply_theme(transform_turf)
- CHECK_TICK
-
-/// Kill yourself and probably a bunch of other people
-/datum/grand_finale/armageddon
- name = "Annihilation"
- desc = "This crew have offended you beyond the realm of pranks. Make the ultimate sacrifice to teach them a lesson your elders can really respect. \
- YOU WILL NOT SURVIVE THIS."
- icon = 'icons/hud/screen_alert.dmi'
- icon_state = "wounded"
- minimum_time = 90 MINUTES // This will probably immediately end the round if it gets finished.
- ritual_invoke_time = 60 SECONDS // Really give the crew some time to interfere with this one.
- dire_warning = TRUE
- glow_colour = "#be000048"
- /// Things to yell before you die
- var/static/list/possible_last_words = list(
- "Flames and ruin!",
- "Dooooooooom!!",
- "HAHAHAHAHAHA!! AHAHAHAHAHAHAHAHAA!!",
- "Hee hee hee!! Hoo hoo hoo!! Ha ha haaa!!",
- "Ohohohohohoho!!",
- "Cower in fear, puny mortals!",
- "Tremble before my glory!",
- "Pick a god and pray!",
- "It's no use!",
- "If the gods wanted you to live, they would not have created me!",
- "God stays in heaven out of fear of what I have created!",
- "Ruination is come!",
- "All of creation, bend to my will!",
- )
-
-/datum/grand_finale/armageddon/trigger(mob/living/carbon/human/invoker)
- priority_announce(pick(possible_last_words), null, 'sound/magic/voidblink.ogg', sender_override = "[invoker.real_name]")
- var/turf/current_location = get_turf(invoker)
- invoker.gib()
-
- var/static/list/doom_options = list()
- if (!length(doom_options))
- doom_options = list(DOOM_SINGULARITY, DOOM_TESLA)
- if (!SSmapping.config.planetary)
- doom_options += DOOM_METEORS
-
- switch(pick(doom_options))
- if (DOOM_SINGULARITY)
- var/obj/singularity/singulo = new(current_location)
- singulo.energy = 300
- if (DOOM_TESLA)
- var/obj/energy_ball/tesla = new (current_location)
- tesla.energy = 200
- if (DOOM_METEORS)
- var/datum/dynamic_ruleset/roundstart/meteor/meteors = new()
- meteors.meteordelay = 0
- var/datum/game_mode/dynamic/mode = SSticker.mode
- mode.execute_roundstart_rule(meteors) // Meteors will continue until morale is crushed.
- priority_announce("Meteors have been detected on collision course with the station.", "Meteor Alert", ANNOUNCER_METEORS)
-
-/**
- * Gives the wizard a defensive/mood buff and a Wabbajack, a juiced up chaos staff that will surely break something.
- * Everyone but the wizard goes crazy, suffers major brain damage, and is given a vendetta against the wizard.
- * Already insane people are instead cured of their madness, ignoring any other effects as the station around them loses its marbles.
- */
-/datum/grand_finale/cheese
- // we don't set name, desc and others, thus we won't appear in the radial choice of a normal finale rune
- dire_warning = TRUE
- minimum_time = 45 MINUTES //i'd imagine speedrunning this would be crummy, but the wizard's average lifespan is barely reaching this point
-
-/datum/grand_finale/cheese/trigger(mob/living/invoker)
- message_admins("[key_name(invoker)] has summoned forth The Wabbajack and cursed the crew with madness!")
- priority_announce("Danger: Extremely potent reality altering object has been summoned on station. Immediate evacuation advised. Brace for impact.", "Central Command Higher Dimensional Affairs", 'sound/effects/glassbr1.ogg')
-
- for (var/mob/living/carbon/human/crewmate as anything in GLOB.human_list)
- if (isnull(crewmate.mind))
- continue
- if (crewmate == invoker) //everyone but the wizard is royally fucked, no matter who they are
- continue
- if (crewmate.has_trauma_type(/datum/brain_trauma/mild/hallucinations)) //for an already insane person, this is retribution
- to_chat(crewmate, span_boldwarning("Your surroundings suddenly fill with a cacophony of manic laughter and psychobabble..."))
- to_chat(crewmate, span_nicegreen("...but as the moment passes, you realise that whatever eldritch power behind the event happened to affect you \
- has resonated within the ruins of your already shattered mind, creating a singularity of mental instability! \
- As it collapses unto itself, you feel... at peace, finally."))
- if(crewmate.has_quirk(/datum/quirk/insanity))
- crewmate.remove_quirk(/datum/quirk/insanity)
- else
- crewmate.cure_trauma_type(/datum/brain_trauma/mild/hallucinations, TRAUMA_RESILIENCE_ABSOLUTE)
- else
- //everyone else gets to relish in madness
- //yes killing their mood will also trigger mood hallucinations
- create_vendetta(crewmate.mind, invoker.mind)
- to_chat(crewmate, span_boldwarning("Your surroundings suddenly fill with a cacophony of manic laughter and psychobabble. \n\
- You feel your inner psyche shatter into a myriad pieces of jagged glass of colors unknown to the universe, \
- infinitely reflecting a blinding, maddening light coming from the innermost sanctums of your destroyed mind. \n\
- After a brief pause which felt like a millenia, one phrase rebounds ceaselessly in your head, imbued with the false hope of absolution... \n\
- [invoker] must die."))
- var/datum/brain_trauma/mild/hallucinations/added_trauma = new()
- added_trauma.resilience = TRAUMA_RESILIENCE_ABSOLUTE
- crewmate.adjustOrganLoss(ORGAN_SLOT_BRAIN, BRAIN_DAMAGE_DEATH - 25, BRAIN_DAMAGE_DEATH - 25) //you'd better hope chap didn't pick a hypertool
- crewmate.gain_trauma(added_trauma)
- crewmate.add_mood_event("wizard_ritual_finale", /datum/mood_event/madness_despair)
-
- //drip our wizard out
- invoker.apply_status_effect(/datum/status_effect/blessing_of_insanity)
- invoker.add_mood_event("wizard_ritual_finale", /datum/mood_event/madness_elation)
- var/obj/item/gun/magic/staff/chaos/true_wabbajack/the_wabbajack = new
- invoker.put_in_active_hand(the_wabbajack)
- to_chat(invoker, span_mind_control("Your every single instinct and rational thought is screaming at you as [the_wabbajack] appears in your firm grip..."))
-
-#undef DOOM_SINGULARITY
-#undef DOOM_TESLA
-#undef DOOM_METEORS
-
diff --git a/code/modules/capture_the_flag/ctf_player_component.dm b/code/modules/capture_the_flag/ctf_player_component.dm
index 0bf37b979de..d3abc0f2571 100644
--- a/code/modules/capture_the_flag/ctf_player_component.dm
+++ b/code/modules/capture_the_flag/ctf_player_component.dm
@@ -8,7 +8,7 @@
var/can_respawn = TRUE
///Reference to the game this player is participating in.
var/datum/ctf_controller/ctf_game
- ///Item dropped on death,
+ ///Item dropped on death,
var/death_drop = /obj/effect/powerup/ammo/ctf
///Reference to players ckey, used for sending messages to them relating to CTF.
var/ckey_reference
@@ -22,19 +22,20 @@
var/datum/mind/true_parent = parent
player_mob = true_parent.current
ckey_reference = player_mob.ckey
- setup_dusting()
-
+ register_mob()
+
/datum/component/ctf_player/PostTransfer()
if(!istype(parent, /datum/mind))
return COMPONENT_INCOMPATIBLE
var/datum/mind/true_parent = parent
player_mob = true_parent.current
- setup_dusting()
+ register_mob()
-///CTF players are dusted upon taking damage that puts them into critical or leaving their body.
-/datum/component/ctf_player/proc/setup_dusting()
+/// Called when we get a new player mob, register signals and set up the mob.
+/datum/component/ctf_player/proc/register_mob()
RegisterSignal(player_mob, COMSIG_MOB_AFTER_APPLY_DAMAGE, PROC_REF(damage_type_check))
RegisterSignal(player_mob, COMSIG_MOB_GHOSTIZED, PROC_REF(ctf_dust))
+ ADD_TRAIT(player_mob, TRAIT_PERMANENTLY_MORTAL, CTF_TRAIT)
///Stamina and oxygen damage will not dust a player by themself.
/datum/component/ctf_player/proc/damage_type_check(datum/source, damage, damage_type)
diff --git a/code/modules/holodeck/holo_effect.dm b/code/modules/holodeck/holo_effect.dm
index 02502c1e6fb..a8d51c87728 100644
--- a/code/modules/holodeck/holo_effect.dm
+++ b/code/modules/holodeck/holo_effect.dm
@@ -61,6 +61,7 @@
mobtype = pick(mobtype)
our_mob = new mobtype(loc)
our_mob.flags_1 |= HOLOGRAM_1
+ ADD_TRAIT(our_mob, TRAIT_PERMANENTLY_MORTAL, INNATE_TRAIT)
// these vars are not really standardized but all would theoretically create stuff on death
for(var/v in list("butcher_results","corpse","weapon1","weapon2","blood_volume") & our_mob.vars)
diff --git a/code/modules/mafia/controller.dm b/code/modules/mafia/controller.dm
index 1916a65f7b1..f8d9db13106 100644
--- a/code/modules/mafia/controller.dm
+++ b/code/modules/mafia/controller.dm
@@ -578,7 +578,7 @@ GLOBAL_LIST_INIT(mafia_role_by_alignment, setup_mafia_role_by_alignment())
for(var/datum/mafia_role/role as anything in all_roles)
var/mob/living/carbon/human/H = new(get_turf(role.assigned_landmark))
- H.add_traits(list(TRAIT_NOFIRE, TRAIT_NOBREATH, TRAIT_CANNOT_CRYSTALIZE), MAFIA_TRAIT)
+ H.add_traits(list(TRAIT_NOFIRE, TRAIT_NOBREATH, TRAIT_CANNOT_CRYSTALIZE, TRAIT_PERMANENTLY_MORTAL), MAFIA_TRAIT)
H.equipOutfit(outfit_to_distribute)
H.status_flags |= GODMODE
RegisterSignal(H, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(display_votes))
diff --git a/code/modules/mob/living/basic/blob_minions/blob_spore.dm b/code/modules/mob/living/basic/blob_minions/blob_spore.dm
index f13a6a70f84..e8c3acc8b97 100644
--- a/code/modules/mob/living/basic/blob_minions/blob_spore.dm
+++ b/code/modules/mob/living/basic/blob_minions/blob_spore.dm
@@ -88,6 +88,11 @@
/// If the blob changes to distributed neurons then you can control the spores
/mob/living/basic/blob_minion/spore/minion/on_strain_updated(mob/camera/blob/overmind, datum/blobstrain/new_strain)
+ if (isnull(overmind))
+ REMOVE_TRAIT(src, TRAIT_PERMANENTLY_MORTAL, INNATE_TRAIT)
+ else
+ ADD_TRAIT(src, TRAIT_PERMANENTLY_MORTAL, INNATE_TRAIT)
+
if (istype(new_strain, /datum/blobstrain/reagent/distributed_neurons))
AddComponent(\
/datum/component/ghost_direct_control,\
diff --git a/code/modules/mob/living/basic/blob_minions/blob_zombie.dm b/code/modules/mob/living/basic/blob_minions/blob_zombie.dm
index 40b73091abf..c9bf3b7346a 100644
--- a/code/modules/mob/living/basic/blob_minions/blob_zombie.dm
+++ b/code/modules/mob/living/basic/blob_minions/blob_zombie.dm
@@ -28,6 +28,7 @@
/mob/living/basic/blob_minion/zombie/Initialize(mapload)
. = ..()
+ ADD_TRAIT(src, TRAIT_PERMANENTLY_MORTAL, INNATE_TRAIT) // This mob doesn't function visually without a corpse and wouldn't respawn with one
AddElement(/datum/element/swabable, CELL_LINE_TABLE_BLOBSPORE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
/mob/living/basic/blob_minion/zombie/death(gibbed)
diff --git a/code/modules/mob/living/basic/lavaland/hivelord/hivelord.dm b/code/modules/mob/living/basic/lavaland/hivelord/hivelord.dm
index 256ab1fbd8f..11043e58d11 100644
--- a/code/modules/mob/living/basic/lavaland/hivelord/hivelord.dm
+++ b/code/modules/mob/living/basic/lavaland/hivelord/hivelord.dm
@@ -102,7 +102,7 @@
/mob/living/basic/hivelord_brood/Initialize(mapload)
. = ..()
- add_traits(list(TRAIT_LAVA_IMMUNE, TRAIT_ASHSTORM_IMMUNE), INNATE_TRAIT)
+ add_traits(list(TRAIT_LAVA_IMMUNE, TRAIT_ASHSTORM_IMMUNE, TRAIT_PERMANENTLY_MORTAL), INNATE_TRAIT)
AddElement(/datum/element/simple_flying)
AddComponent(/datum/component/swarming)
AddComponent(/datum/component/clickbox, icon_state = "hivelord", max_scale = INFINITY)
diff --git a/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm b/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm
index bc21bd0e506..962d232c5ef 100644
--- a/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm
+++ b/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm
@@ -34,7 +34,7 @@
/mob/living/basic/legion_brood/Initialize(mapload)
. = ..()
- add_traits(list(TRAIT_LAVA_IMMUNE, TRAIT_ASHSTORM_IMMUNE), INNATE_TRAIT)
+ add_traits(list(TRAIT_LAVA_IMMUNE, TRAIT_ASHSTORM_IMMUNE, TRAIT_PERMANENTLY_MORTAL), INNATE_TRAIT)
AddElement(/datum/element/simple_flying)
AddComponent(/datum/component/swarming)
AddComponent(/datum/component/clickbox, icon_state = "sphere", max_scale = 2)
diff --git a/code/modules/mob/living/simple_animal/hostile/heretic_monsters.dm b/code/modules/mob/living/simple_animal/hostile/heretic_monsters.dm
index 2c0b9ba983a..1b8cfe9de68 100644
--- a/code/modules/mob/living/simple_animal/hostile/heretic_monsters.dm
+++ b/code/modules/mob/living/simple_animal/hostile/heretic_monsters.dm
@@ -198,6 +198,7 @@
for(var/i in 1 to worm_length)
current = new type(drop_location(), FALSE)
+ ADD_TRAIT(current, TRAIT_PERMANENTLY_MORTAL, INNATE_TRAIT)
current.icon_state = "armsy_mid"
current.icon_living = "armsy_mid"
current.AIStatus = AI_OFF
diff --git a/code/modules/mob/living/simple_animal/hostile/mimic.dm b/code/modules/mob/living/simple_animal/hostile/mimic.dm
index 2fec84385b2..d07775b42bd 100644
--- a/code/modules/mob/living/simple_animal/hostile/mimic.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mimic.dm
@@ -113,6 +113,7 @@ GLOBAL_LIST_INIT(animatable_blacklist, list(/obj/structure/table, /obj/structure
/mob/living/simple_animal/hostile/mimic/copy/Initialize(mapload, obj/copy, mob/living/creator, destroy_original = 0, no_googlies = FALSE)
. = ..()
+ ADD_TRAIT(src, TRAIT_PERMANENTLY_MORTAL, INNATE_TRAIT) // They won't remember their original contents upon ressurection and would just be floating eyes
if (no_googlies)
overlay_googly_eyes = FALSE
CopyObject(copy, creator, destroy_original)
diff --git a/code/modules/surgery/organs/internal/heart/heart_ethereal.dm b/code/modules/surgery/organs/internal/heart/heart_ethereal.dm
index f29ee2e731d..bb0b30ddd62 100644
--- a/code/modules/surgery/organs/internal/heart/heart_ethereal.dm
+++ b/code/modules/surgery/organs/internal/heart/heart_ethereal.dm
@@ -108,7 +108,7 @@
if(!COOLDOWN_FINISHED(src, crystalize_cooldown) || ethereal.stat != DEAD)
return //Should probably not happen, but lets be safe.
- if(ismob(location) || isitem(location) || HAS_TRAIT_FROM(src, TRAIT_HUSK, CHANGELING_DRAIN)) //Stops crystallization if they are eaten by a dragon, turned into a legion, consumed by his grace, etc.
+ if(ismob(location) || isitem(location) || iseffect(location) || HAS_TRAIT_FROM(src, TRAIT_HUSK, CHANGELING_DRAIN)) //Stops crystallization if they are eaten by a dragon, turned into a legion, consumed by his grace, etc.
to_chat(ethereal, span_warning("You were unable to finish your crystallization, for obvious reasons."))
stop_crystalization_process(ethereal, FALSE)
return
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index 29e59dc6c71..d59f065da28 100644
Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 2fe8275a014..57b5fe15138 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -3047,9 +3047,16 @@
#include "code\modules\antagonists\wizard\equipment\spellbook_entries\summons.dm"
#include "code\modules\antagonists\wizard\grand_ritual\fluff.dm"
#include "code\modules\antagonists\wizard\grand_ritual\grand_ritual.dm"
-#include "code\modules\antagonists\wizard\grand_ritual\grand_ritual_finale.dm"
#include "code\modules\antagonists\wizard\grand_ritual\grand_rune.dm"
#include "code\modules\antagonists\wizard\grand_ritual\grand_side_effect.dm"
+#include "code\modules\antagonists\wizard\grand_ritual\finales\all_access.dm"
+#include "code\modules\antagonists\wizard\grand_ritual\finales\armageddon.dm"
+#include "code\modules\antagonists\wizard\grand_ritual\finales\captaincy.dm"
+#include "code\modules\antagonists\wizard\grand_ritual\finales\cheese.dm"
+#include "code\modules\antagonists\wizard\grand_ritual\finales\clown.dm"
+#include "code\modules\antagonists\wizard\grand_ritual\finales\grand_ritual_finale.dm"
+#include "code\modules\antagonists\wizard\grand_ritual\finales\immortality.dm"
+#include "code\modules\antagonists\wizard\grand_ritual\finales\midas.dm"
#include "code\modules\antagonists\xeno\xeno.dm"
#include "code\modules\art\paintings.dm"
#include "code\modules\art\statues.dm"