diff --git a/_maps/templates/lazy_templates/wizard_den.dmm b/_maps/templates/lazy_templates/wizard_den.dmm
index 025f9e8cdd00..75ee8f52454b 100644
--- a/_maps/templates/lazy_templates/wizard_den.dmm
+++ b/_maps/templates/lazy_templates/wizard_den.dmm
@@ -427,7 +427,7 @@
/turf/open/floor/wood,
/area/centcom/wizard_station)
"SD" = (
-/obj/structure/bookcase/random/reference,
+/obj/structure/bookcase/random/reference/wizard,
/turf/open/floor/engine/cult,
/area/centcom/wizard_station)
"Tl" = (
diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm
index 040930e52056..03b8514e0cf1 100644
--- a/code/__DEFINES/combat.dm
+++ b/code/__DEFINES/combat.dm
@@ -67,12 +67,27 @@
#define EFFECT_PARALYZE "paralyze"
#define EFFECT_IMMOBILIZE "immobilize"
//Bitflags defining which status effects could be or are inflicted on a mob
+/// If set, this mob can be stunned.
#define CANSTUN (1<<0)
+/// If set, this mob can be knocked down (or stamcrit)
#define CANKNOCKDOWN (1<<1)
+/// If set, this mob can be knocked unconscious via status effect.
+/// NOTE, does not mean immune to sleep. Unconscious and sleep are two different things.
+/// NOTE, does not relate to the unconscious stat either. Only the status effect.
#define CANUNCONSCIOUS (1<<2)
+/// If set, this mob can be grabbed or pushed when bumped into
#define CANPUSH (1<<3)
+/// Mob godmode. Prevents most statuses and damage from being taken, but is more often than not a crapshoot. Use with caution.
#define GODMODE (1<<4)
+DEFINE_BITFIELD(status_flags, list(
+ "CAN STUN" = CANSTUN,
+ "CAN KNOCKDOWN" = CANKNOCKDOWN,
+ "CAN UNCONSCIOUS" = CANUNCONSCIOUS,
+ "CAN PUSH" = CANPUSH,
+ "GOD MODE" = GODMODE,
+))
+
//Health Defines
#define HEALTH_THRESHOLD_CRIT 0
#define HEALTH_THRESHOLD_FULLCRIT -30
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm
index d737e448ecb3..63fd640d77f9 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm
@@ -73,10 +73,14 @@
#define COMSIG_LIVING_STATUS_PARALYZE "living_paralyze"
///from base of mob/living/Immobilize() (amount, ignore_canstun)
#define COMSIG_LIVING_STATUS_IMMOBILIZE "living_immobilize"
+///from base of mob/living/incapacitate() (amount, ignore_canstun)
+#define COMSIG_LIVING_STATUS_INCAPACITATE "living_incapacitate"
///from base of mob/living/Unconscious() (amount, ignore_canstun)
#define COMSIG_LIVING_STATUS_UNCONSCIOUS "living_unconscious"
///from base of mob/living/Sleeping() (amount, ignore_canstun)
#define COMSIG_LIVING_STATUS_SLEEP "living_sleeping"
+/// from mob/living/check_stun_immunity(): (check_flags)
+#define COMSIG_LIVING_GENERIC_STUN_CHECK "living_check_stun"
#define COMPONENT_NO_STUN (1<<0) //For all of them
///from base of /mob/living/can_track(): (mob/user)
#define COMSIG_LIVING_CAN_TRACK "mob_cantrack"
diff --git a/code/__DEFINES/dcs/signals/signals_wizard.dm b/code/__DEFINES/dcs/signals/signals_wizard.dm
index a4b8f0755c42..5cdebf4f0622 100644
--- a/code/__DEFINES/dcs/signals/signals_wizard.dm
+++ b/code/__DEFINES/dcs/signals/signals_wizard.dm
@@ -1,4 +1,4 @@
-/// Signal sent when we finish invoking a rune
+/// Signal sent when we finish invoking a rune. Will also send the amount of cheese sacrificed on the rune : (cheese_sacrificed)
#define COMSIG_GRAND_RUNE_COMPLETE "grand rune complete"
/// Signal sent when we finish 7 grand rituals
#define COMSIG_GRAND_RITUAL_FINAL_COMPLETE "grand ritual finale complete"
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index d18c8785b40d..91b5b6c2e323 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -191,6 +191,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_DISFIGURED "disfigured"
/// Tracks whether we're gonna be a baby alien's mummy.
#define TRAIT_XENO_HOST "xeno_host"
+/// This mob is immune to stun causing status effects and stamcrit.
+/// Prefer to use [/mob/living/proc/check_stun_immunity] over checking for this trait exactly.
#define TRAIT_STUNIMMUNE "stun_immunity"
#define TRAIT_BATON_RESISTANCE "baton_resistance"
/// Anti Dual-baton cooldown bypass exploit.
@@ -671,6 +673,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_CUSTOM_TAP_SOUND "no_tap_sound"
/// Makes the feedback message when someone else is putting this item on you more noticeable
#define TRAIT_DANGEROUS_OBJECT "dangerous_object"
+/// determines whether or not objects are haunted and teleport/attack randomly
+#define TRAIT_HAUNTED "haunted"
//quirk traits
#define TRAIT_ALCOHOL_TOLERANCE "alcohol_tolerance"
@@ -911,7 +915,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define CHANGELING_DRAIN "drain"
/// changelings with this trait can no longer talk over the hivemind
#define CHANGELING_HIVEMIND_MUTE "ling_mute"
-#define HIGHLANDER "highlander"
#define TRAIT_HULK "hulk"
#define STASIS_MUTE "stasis"
#define GENETICS_SPELL "genetics_spell"
@@ -921,6 +924,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define NANITES_TRAIT "nanites"
#define FLASHLIGHT_EYES "flashlight_eyes"
#define IMPURE_OCULINE "impure_oculine"
+#define HAUNTIUM_REAGENT_TRAIT "hauntium_reagent_trait"
#define TRAIT_SANTA "santa"
#define SCRYING_ORB "scrying-orb"
#define ABDUCTOR_ANTAGONIST "abductor-antagonist"
@@ -1019,6 +1023,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define SPEED_TRAIT "speed_trait"
/// Trait given to mobs that have been autopsied
#define AUTOPSY_TRAIT "autopsy_trait"
+/// Trait given by [/datum/status_effect/blessing_of_insanity]
+#define MAD_WIZARD_TRAIT "mad_wizard_trait"
/**
diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm
index ee663ac6c6fe..55326d5633ef 100644
--- a/code/_globalvars/traits.dm
+++ b/code/_globalvars/traits.dm
@@ -219,6 +219,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_APC_SHOCKING" = TRAIT_APC_SHOCKING,
"TRAIT_UNCATCHABLE" = TRAIT_UNCATCHABLE,
"TRAIT_DANGEROUS_OBJECT" = TRAIT_DANGEROUS_OBJECT,
+ "TRAIT_HAUNTED" = TRAIT_HAUNTED,
),
/atom = list(
"TRAIT_KEEP_TOGETHER" = TRAIT_KEEP_TOGETHER,
diff --git a/code/datums/ai/monkey/monkey_behaviors.dm b/code/datums/ai/monkey/monkey_behaviors.dm
index bce880ced7e9..e2a2a4ae1f75 100644
--- a/code/datums/ai/monkey/monkey_behaviors.dm
+++ b/code/datums/ai/monkey/monkey_behaviors.dm
@@ -318,7 +318,7 @@
if(possible_enemy == controller.pawn)
continue // don't target ourselves
if(!enemies[possible_enemy]) //We don't hate this creature! But we might still attack it!
- if(!controller.blackboard[BB_MONKEY_AGGRESSIVE]) //We are not aggressive either, so we won't attack!
+ if(faction_check(possible_enemy.faction, list(FACTION_MONKEY, FACTION_JUNGLE), exact_match = FALSE) && !controller.blackboard[BB_MONKEY_TARGET_MONKEYS]) // do not target your team. includes monkys gorillas etc.
continue
if(HAS_AI_CONTROLLER_TYPE(possible_enemy, /datum/ai_controller/monkey) && !controller.blackboard[BB_MONKEY_TARGET_MONKEYS]) //Do not target poor monkes
continue
diff --git a/code/datums/ai/monkey/monkey_controller.dm b/code/datums/ai/monkey/monkey_controller.dm
index e365a2f3bcb1..fdd82c0d3bd7 100644
--- a/code/datums/ai/monkey/monkey_controller.dm
+++ b/code/datums/ai/monkey/monkey_controller.dm
@@ -52,7 +52,9 @@ have ways of interacting with a specific mob and control it.
. = ..()
if(. & AI_CONTROLLER_INCOMPATIBLE)
return
+ pawn = new_pawn
set_blackboard_key(BB_MONKEY_AGGRESSIVE, TRUE) //Angry
+ set_trip_mode(mode = FALSE)
/datum/ai_controller/monkey/TryPossessPawn(atom/new_pawn)
if(!isliving(new_pawn))
diff --git a/code/datums/components/twohanded.dm b/code/datums/components/twohanded.dm
index a0c491a27a50..756f30bd644d 100644
--- a/code/datums/components/twohanded.dm
+++ b/code/datums/components/twohanded.dm
@@ -148,13 +148,6 @@
/datum/component/two_handed/proc/wield(mob/living/carbon/user)
if(wielded)
return
- if(ismonkey(user))
- if(require_twohands)
- to_chat(user, span_notice("[parent] is too heavy and cumbersome for you to carry!"))
- user.dropItemToGround(parent, force=TRUE)
- else
- to_chat(user, span_notice("It's too heavy for you to wield fully."))
- return
if(user.get_inactive_held_item())
if(require_twohands)
to_chat(user, span_notice("[parent] is too cumbersome to carry in one hand!"))
diff --git a/code/datums/elements/haunted.dm b/code/datums/elements/haunted.dm
index 1ed7f325294b..d678083dd199 100644
--- a/code/datums/elements/haunted.dm
+++ b/code/datums/elements/haunted.dm
@@ -24,3 +24,13 @@
REMOVE_TRAIT(master, TRAIT_MOVE_FLYING, ELEMENT_TRAIT(type))
master.RemoveElement(/datum/element/movetype_handler)
return ..()
+
+/atom/movable/proc/make_haunted(source, color) //if not haunted, make haunted
+ if(!HAS_TRAIT(src, TRAIT_HAUNTED))
+ AddElement(/datum/element/haunted, color)
+ ADD_TRAIT(src, TRAIT_HAUNTED, source)
+
+/atom/movable/proc/remove_haunted(source) //if haunted, make not haunted
+ REMOVE_TRAIT(src, TRAIT_HAUNTED, source)
+ if(!HAS_TRAIT(src, TRAIT_HAUNTED))
+ RemoveElement(/datum/element/haunted)
diff --git a/code/datums/materials/hauntium.dm b/code/datums/materials/hauntium.dm
index 9a4e09f90818..79e254417208 100644
--- a/code/datums/materials/hauntium.dm
+++ b/code/datums/materials/hauntium.dm
@@ -15,10 +15,8 @@
/datum/material/hauntium/on_applied_obj(obj/o, amount, material_flags)
. = ..()
- if(isitem(o))
- o.AddElement(/datum/element/haunted)
+ o.make_haunted(INNATE_TRAIT, "#f8f8ff")
/datum/material/hauntium/on_removed_obj(obj/o, amount, material_flags)
. = ..()
- if(isitem(o))
- o.RemoveElement(/datum/element/haunted)
+ o.remove_haunted(INNATE_TRAIT)
diff --git a/code/datums/mood_events/drug_events.dm b/code/datums/mood_events/drug_events.dm
index 66518199122f..1ca2f5abbe3c 100644
--- a/code/datums/mood_events/drug_events.dm
+++ b/code/datums/mood_events/drug_events.dm
@@ -107,3 +107,8 @@
/datum/mood_event/nicotine_withdrawal_severe
description = "Head pounding. Cold sweating. Feeling anxious. Need a smoke to calm down!"
mood_change = -8
+
+/datum/mood_event/hauntium_spirits
+ description = "I feel my soul degrading!"
+ mood_change = -8
+ timeout = 8 MINUTES
diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm
index d4d50d5a29a6..ef4cec4ea467 100644
--- a/code/datums/mood_events/generic_negative_events.dm
+++ b/code/datums/mood_events/generic_negative_events.dm
@@ -409,3 +409,9 @@
/datum/mood_event/unsatisfied_nomad
description = "I've been here too long! I want to go out and explore space!"
mood_change = -3
+
+///Wizard cheesy grand finale - what everyone but the wizard gets
+/datum/mood_event/madness_despair
+ description = "UNWORTHY, UNWORTHY, UNWORTHY!!!"
+ mood_change = -200
+ special_screen_obj = "mood_despair"
diff --git a/code/datums/mood_events/generic_positive_events.dm b/code/datums/mood_events/generic_positive_events.dm
index ebac71a6d4cb..18432d12ef4d 100644
--- a/code/datums/mood_events/generic_positive_events.dm
+++ b/code/datums/mood_events/generic_positive_events.dm
@@ -359,3 +359,17 @@
mood_change = 2
special_screen_obj = "birthday"
special_screen_replace = FALSE
+
+
+
+
+
+
+
+
+
+
+///Wizard cheesy grand finale - what the wizard gets
+/datum/mood_event/madness_elation
+ description = "Madness truly is the greatest of blessings..."
+ mood_change = 200
diff --git a/code/datums/mood_events/morbid_events.dm b/code/datums/mood_events/morbid_events.dm
new file mode 100644
index 000000000000..cfb8771bff1a
--- /dev/null
+++ b/code/datums/mood_events/morbid_events.dm
@@ -0,0 +1,4 @@
+/datum/mood_event/morbid_hauntium
+ description = "I feel a better connection with the spirits, I love this!"
+ mood_change = 3
+ timeout = 6 MINUTES
diff --git a/code/datums/mutations/hulk.dm b/code/datums/mutations/hulk.dm
index 5344cdeaba11..e1784f009c8f 100644
--- a/code/datums/mutations/hulk.dm
+++ b/code/datums/mutations/hulk.dm
@@ -12,7 +12,7 @@
var/scream_delay = 50
var/last_scream = 0
/// List of traits to add/remove when someone gets this mutation.
- var/static/list/mutation_traits = list(
+ var/list/mutation_traits = list(
TRAIT_CHUNKYFINGERS,
TRAIT_HULK,
TRAIT_IGNOREDAMAGESLOWDOWN,
@@ -256,4 +256,17 @@
yeeted_person.throw_at(T, 10, 6, the_hulk, TRUE, TRUE)
log_combat(the_hulk, yeeted_person, "has thrown by tail")
+/datum/mutation/human/hulk/wizardly
+ species_allowed = null //yes skeleton/lizard hulk - note that species that dont have skintone changing (like skellies) get custom handling
+ health_req = 0
+ instability = 0
+ scream_delay = 2.5 SECONDS // halved to be more annoying (spell doesn't last long anyways)
+ /// List of traits to add/remove when someone gets this mutation.
+ mutation_traits = list(
+ TRAIT_HULK,
+ TRAIT_IGNOREDAMAGESLOWDOWN,
+ TRAIT_PUSHIMMUNE,
+ TRAIT_STUNIMMUNE,
+ ) // no chunk
+
#undef HULK_TAILTHROW_STEPS
diff --git a/code/datums/mutations/sight.dm b/code/datums/mutations/sight.dm
index 233e9ca338d2..b71c6cdd6a14 100644
--- a/code/datums/mutations/sight.dm
+++ b/code/datums/mutations/sight.dm
@@ -166,7 +166,7 @@
to_chat(source, span_warning("You shoot with your laser eyes!"))
source.changeNext_move(CLICK_CD_RANGE)
source.newtonian_move(get_dir(target, source))
- var/obj/projectile/beam/laser_eyes/LE = new(source.loc)
+ var/obj/projectile/beam/laser/laser_eyes/LE = new(source.loc)
LE.firer = source
LE.def_zone = ran_zone(source.zone_selected)
LE.preparePixelProjectile(target, source, modifiers)
@@ -174,7 +174,7 @@
playsound(source, 'sound/weapons/taser2.ogg', 75, TRUE)
///Projectile type used by laser eyes
-/obj/projectile/beam/laser_eyes
+/obj/projectile/beam/laser/laser_eyes
name = "beam"
icon = 'icons/effects/genetics.dmi'
icon_state = "eyelasers"
diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm
index f0046785a94c..72dece18b8bd 100644
--- a/code/datums/status_effects/buffs.dm
+++ b/code/datums/status_effects/buffs.dm
@@ -22,10 +22,16 @@
return ..()
/datum/status_effect/his_grace/on_apply()
- owner.log_message("gained His Grace's stun immunity", LOG_ATTACK)
- owner.add_stun_absorption("hisgrace", INFINITY, 3, null, "His Grace protects you from the stun!")
+ owner.add_stun_absorption(
+ source = id,
+ priority = 3,
+ self_message = span_boldwarning("His Grace protects you from the stun!"),
+ )
return ..()
+/datum/status_effect/his_grace/on_remove()
+ owner.remove_stun_absorption(id)
+
/datum/status_effect/his_grace/tick()
bloodlust = 0
var/graces = 0
@@ -45,11 +51,6 @@
owner.adjustOxyLoss(-(grace_heal * 2))
owner.adjustCloneLoss(-grace_heal)
-/datum/status_effect/his_grace/on_remove()
- owner.log_message("lost His Grace's stun immunity", LOG_ATTACK)
- if(islist(owner.stun_absorption) && owner.stun_absorption["hisgrace"])
- owner.stun_absorption -= "hisgrace"
-
/datum/status_effect/wish_granters_gift //Fully revives after ten seconds.
id = "wish_granters_gift"
@@ -112,34 +113,30 @@
icon_state = "blooddrunk"
/datum/status_effect/blooddrunk/on_apply()
- . = ..()
- if(.)
- ADD_TRAIT(owner, TRAIT_IGNOREDAMAGESLOWDOWN, BLOODDRUNK_TRAIT)
- if(ishuman(owner))
- var/mob/living/carbon/human/H = owner
- H.physiology.brute_mod *= 0.1
- H.physiology.burn_mod *= 0.1
- H.physiology.tox_mod *= 0.1
- H.physiology.oxy_mod *= 0.1
- H.physiology.clone_mod *= 0.1
- H.physiology.stamina_mod *= 0.1
- owner.log_message("gained blood-drunk stun immunity", LOG_ATTACK)
- owner.add_stun_absorption("blooddrunk", INFINITY, 4)
- owner.playsound_local(get_turf(owner), 'sound/effects/singlebeat.ogg', 40, 1, use_reverb = FALSE)
+ ADD_TRAIT(owner, TRAIT_IGNOREDAMAGESLOWDOWN, BLOODDRUNK_TRAIT)
+ if(ishuman(owner))
+ var/mob/living/carbon/human/human_owner = owner
+ human_owner.physiology.brute_mod *= 0.1
+ human_owner.physiology.burn_mod *= 0.1
+ human_owner.physiology.tox_mod *= 0.1
+ human_owner.physiology.oxy_mod *= 0.1
+ human_owner.physiology.clone_mod *= 0.1
+ human_owner.physiology.stamina_mod *= 0.1
+ owner.add_stun_absorption(source = id, priority = 4)
+ owner.playsound_local(get_turf(owner), 'sound/effects/singlebeat.ogg', 40, 1, use_reverb = FALSE)
+ return TRUE
/datum/status_effect/blooddrunk/on_remove()
if(ishuman(owner))
- var/mob/living/carbon/human/H = owner
- H.physiology.brute_mod *= 10
- H.physiology.burn_mod *= 10
- H.physiology.tox_mod *= 10
- H.physiology.oxy_mod *= 10
- H.physiology.clone_mod *= 10
- H.physiology.stamina_mod *= 10
- owner.log_message("lost blood-drunk stun immunity", LOG_ATTACK)
- REMOVE_TRAIT(owner, TRAIT_IGNOREDAMAGESLOWDOWN, BLOODDRUNK_TRAIT);
- if(islist(owner.stun_absorption) && owner.stun_absorption["blooddrunk"])
- owner.stun_absorption -= "blooddrunk"
+ var/mob/living/carbon/human/human_owner = owner
+ human_owner.physiology.brute_mod *= 10
+ human_owner.physiology.burn_mod *= 10
+ human_owner.physiology.tox_mod *= 10
+ human_owner.physiology.oxy_mod *= 10
+ human_owner.physiology.clone_mod *= 10
+ human_owner.physiology.stamina_mod *= 10
+ REMOVE_TRAIT(owner, TRAIT_IGNOREDAMAGESLOWDOWN, BLOODDRUNK_TRAIT)
+ owner.remove_stun_absorption(id)
//Used by changelings to rapidly heal
//Heals 10 brute and oxygen damage every second, and 5 fire
@@ -486,3 +483,52 @@
name = "Nest Vitalization"
desc = "The resin seems to pulsate around you. It seems to be sustaining your vital functions. You feel ill..."
icon_state = "nest_life"
+
+/**
+ * Granted to wizards upon satisfying the cheese sacrifice during grand rituals.
+ * Halves incoming damage and makes the owner stun immune, damage slow immune, levitating(even in space and hyperspace!) and glowing.
+ */
+/datum/status_effect/blessing_of_insanity
+ id = "blessing_of_insanity"
+ duration = -1
+ tick_interval = -1
+ alert_type = /atom/movable/screen/alert/status_effect/blessing_of_insanity
+
+/atom/movable/screen/alert/status_effect/blessing_of_insanity
+ name = "Blessing of Insanity"
+ desc = "Your devotion to madness has improved your resilience to all damage and you gain the power to levitate!"
+ //no screen alert - the gravity already throws one
+
+/datum/status_effect/blessing_of_insanity/on_apply()
+ if(ishuman(owner))
+ var/mob/living/carbon/human/human_owner = owner
+ var/datum/physiology/owner_physiology = human_owner.physiology
+ owner_physiology.brute_mod *= 0.5
+ owner_physiology.burn_mod *= 0.5
+ owner_physiology.tox_mod *= 0.5
+ owner_physiology.oxy_mod *= 0.5
+ owner_physiology.clone_mod *= 0.5
+ owner_physiology.stamina_mod *= 0.5
+ owner.add_filter("mad_glow", 2, list("type" = "outline", "color" = "#eed811c9", "size" = 2))
+ owner.AddElement(/datum/element/forced_gravity, 0)
+ owner.AddElement(/datum/element/simple_flying)
+ owner.add_stun_absorption(source = id, priority = 4)
+ add_traits(list(TRAIT_IGNOREDAMAGESLOWDOWN, TRAIT_FREE_HYPERSPACE_MOVEMENT), MAD_WIZARD_TRAIT)
+ owner.playsound_local(get_turf(owner), 'sound/chemistry/ahaha.ogg', vol = 100, vary = TRUE, use_reverb = TRUE)
+ return TRUE
+
+/datum/status_effect/blessing_of_insanity/on_remove()
+ if(ishuman(owner))
+ var/mob/living/carbon/human/human_owner = owner
+ var/datum/physiology/owner_physiology = human_owner.physiology
+ owner_physiology.brute_mod *= 2
+ owner_physiology.burn_mod *= 2
+ owner_physiology.tox_mod *= 2
+ owner_physiology.oxy_mod *= 2
+ owner_physiology.clone_mod *= 2
+ owner_physiology.stamina_mod *= 2
+ owner.remove_filter("mad_glow")
+ owner.RemoveElement(/datum/element/forced_gravity, 0)
+ owner.RemoveElement(/datum/element/simple_flying)
+ owner.remove_stun_absorption(id)
+ remove_traits(list(TRAIT_IGNOREDAMAGESLOWDOWN, TRAIT_FREE_HYPERSPACE_MOVEMENT), MAD_WIZARD_TRAIT)
diff --git a/code/datums/status_effects/buffs/stun_asorption.dm b/code/datums/status_effects/buffs/stun_asorption.dm
new file mode 100644
index 000000000000..d68f2f7408cc
--- /dev/null
+++ b/code/datums/status_effects/buffs/stun_asorption.dm
@@ -0,0 +1,235 @@
+/**
+ * # Stun absorption
+ *
+ * A status effect effectively functions as [TRAIT_STUNIMMUNE], but with additional effects tied to it,
+ * such as showing a message on trigger / examine, or only blocking a limited amount of stuns.
+ *
+ * Apply this via [/mob/living/proc/add_stun_absorption]. If you do not supply a duration,
+ * remove this via [/mob/living/proc/remove_stun_absorption].
+ */
+/datum/status_effect/stun_absorption
+ id = "absorb_stun"
+ tick_interval = -1
+ alert_type = null
+ status_type = STATUS_EFFECT_MULTIPLE
+
+ /// The string key sourcer of the stun absorption, used for logging
+ var/source
+ /// The priority of the stun absorption. Used so that multiple sources will not trigger at once.
+ /// This number is arbitrary but try to keep in sane / in line with other sources that exist.
+ var/priority = -1
+ /// How many total seconds of stuns that have been blocked.
+ var/seconds_of_stuns_absorbed = 0 SECONDS
+ /// The max number of seconds we can block before self-deleting.
+ var/max_seconds_of_stuns_blocked = INFINITY
+ /// The message shown via visible message to all nearby mobs when the effect triggers.
+ var/shown_message
+ /// The message shown to the owner when the effect triggers.
+ var/self_message
+ /// Message shown on anyone examining the owner.
+ var/examine_message
+
+ /// Static list of all generic "stun received " signals that we will react to and block.
+ /// These all have the same arguments sent, so we can handle them all via the same signal handler.
+ /// Note though that we can register other signals to block effects outside of these if we want.
+ var/static/list/incapacitation_effect_signals = list(
+ COMSIG_LIVING_STATUS_IMMOBILIZE,
+ COMSIG_LIVING_STATUS_INCAPACITATE,
+ COMSIG_LIVING_STATUS_KNOCKDOWN,
+ COMSIG_LIVING_STATUS_PARALYZE,
+ COMSIG_LIVING_STATUS_STUN,
+ )
+
+/datum/status_effect/stun_absorption/on_creation(
+ mob/living/new_owner,
+ source,
+ duration,
+ priority = -1,
+ shown_message,
+ self_message,
+ examine_message,
+ max_seconds_of_stuns_blocked = INFINITY,
+)
+
+ if(isnum(duration))
+ src.duration = duration
+
+ src.source = source
+ src.priority = priority
+ src.shown_message = shown_message
+ src.self_message = self_message
+ src.examine_message = examine_message
+ src.max_seconds_of_stuns_blocked = max_seconds_of_stuns_blocked
+
+ return ..()
+
+/datum/status_effect/stun_absorption/on_apply()
+ if(owner.mind || owner.client)
+ owner.log_message("gained stun absorption (from: [source || "Unknown"])", LOG_ATTACK)
+
+ RegisterSignals(owner, incapacitation_effect_signals, PROC_REF(try_absorb_incapacitating_effect))
+ RegisterSignal(owner, COMSIG_LIVING_GENERIC_STUN_CHECK, PROC_REF(try_absorb_generic_effect))
+ return TRUE
+
+/datum/status_effect/stun_absorption/on_remove()
+ if(owner.mind || owner.client)
+ owner.log_message("lost stun absorption (from: [source || "Unknown"])", LOG_ATTACK)
+
+ UnregisterSignal(owner, incapacitation_effect_signals)
+ UnregisterSignal(owner, COMSIG_LIVING_GENERIC_STUN_CHECK)
+
+/datum/status_effect/stun_absorption/get_examine_text()
+ return replacetext(examine_message, "%EFFECT_OWNER_THEYRE", owner.p_theyre(TRUE))
+
+/**
+ * Signal proc for generic stun signals being sent, such as [COMSIG_LIVING_STATUS_STUN] or [COMSIG_LIVING_STATUS_KNOCKDOWN].
+ *
+ * When we get stunned, we will try to absorb a number of seconds from the stun, and return [COMPONENT_NO_STUN] if we succeed.
+ */
+/datum/status_effect/stun_absorption/proc/try_absorb_incapacitating_effect(mob/living/source, amount = 0, ignore_canstun = FALSE)
+ SIGNAL_HANDLER
+
+ // we blocked a stun this tick that resulting is us qdeling, so stop
+ if(QDELING(src))
+ return NONE
+
+ // Amount less than (or equal to) zero is removing stuns, so we don't want to block that
+ if(amount <= 0 || ignore_canstun)
+ return NONE
+
+ if(!absorb_stun(amount))
+ return NONE
+
+ return COMPONENT_NO_STUN
+
+/**
+ * Signal proc for [COMSIG_LIVING_GENERIC_STUN_CHECK]. (Note, this includes being stamcrit)
+ *
+ * Whenever a generic stun check is done against us, we'll just try to block it with "0 second" stun.
+ * This prevents spam us from showing feedback messages, and is for the generic "can be stunned" check.
+ */
+/datum/status_effect/stun_absorption/proc/try_absorb_generic_effect(mob/living/source, check_flags)
+ SIGNAL_HANDLER
+
+ if(QDELING(src))
+ return NONE
+
+ // "0 amount" / "0 seconds of stun" is used so no feedback is sent on success
+ if(!absorb_stun(0))
+ return NONE
+
+ return COMPONENT_NO_STUN
+
+/**
+ * Absorb a number of seconds of stuns.
+ * If we hit the max amount of absorption, we will qdel ourself in this proc.
+ *
+ * * amount - this is the number of deciseconds being absorbed at once.
+ *
+ * Returns TRUE on successful absorption, or FALSE otherwise.
+ */
+/datum/status_effect/stun_absorption/proc/absorb_stun(amount)
+ if(owner.stat != CONSCIOUS)
+ return FALSE
+
+ // Now we gotta check that no other stun absorption we have is blocking us
+ for(var/datum/status_effect/stun_absorption/similar_effect in owner.status_effects)
+ if(similar_effect == src)
+ continue
+ // they blocked a stun this tick that resulted in them qdeling, so disregard
+ if(QDELING(similar_effect))
+ continue
+ // if we have another stun absorption with higher priority,
+ // don't do anything, let them handle it instead
+ if(similar_effect.priority > priority)
+ return FALSE
+
+ // At this point, a stun was successfully absorbed
+
+ // Only do effects if the amount was > 0 seconds
+ if(amount > 0 SECONDS)
+ // Show the message
+ if(shown_message)
+ // We do this replacement meme, instead of just setting it up in creation,
+ // so that we respect indentity changes done while active
+ var/really_shown_message = replacetext(shown_message, "%EFFECT_OWNER", "[owner]")
+ owner.visible_message(really_shown_message, ignored_mobs = owner)
+
+ // Send the self message
+ if(self_message)
+ to_chat(owner, self_message)
+
+ // Count seconds absorbed
+ seconds_of_stuns_absorbed += amount
+ if(seconds_of_stuns_absorbed >= max_seconds_of_stuns_blocked)
+ qdel(src)
+
+ return TRUE
+
+/**
+ * [proc/apply_status_effect] wrapper specifically for [/datum/status_effect/stun_absorption],
+ * specifically so that it's easier to apply stun absorptions with named arguments.
+ *
+ * If the mob already has a stun absorption from the same source, will not re-apply the effect,
+ * unless the new effect's priority is higher than the old effect's priority.
+ *
+ * Arguments
+ * * source - the source of the stun absorption.
+ * * duration - how long does the stun absorption last before it ends? -1 or null = infinite duration
+ * * priority - what is this effect's priority to other stun absorptions? higher = more priority
+ * * message - optional, "other message" arg of visible message, shown on trigger. Use %EFFECT_OWNER if you want the owner's name to be inserted.
+ * * self_message - optional, "self message" arg of visible message, shown on trigger
+ * * examine_message - optional, what is shown on examine of the mob.
+ * * max_seconds_of_stuns_blocked - optional, how many seconds of stuns can it block before deleting? the stun that breaks over this number is still blocked, even if it is much higher.
+ *
+ * Returns an instance of a stun absorption effect, or NULL if failure
+ */
+/mob/living/proc/add_stun_absorption(
+ source,
+ duration,
+ priority = -1,
+ message,
+ self_message,
+ examine_message,
+ max_seconds_of_stuns_blocked = INFINITY,
+)
+
+ // Handle duplicate sources
+ for(var/datum/status_effect/stun_absorption/existing_effect in status_effects)
+ if(existing_effect.source != source)
+ continue
+
+ // If an existing effect's priority is greater or equal to our passed priority...
+ if(existing_effect.priority >= priority)
+ // don't bother re-applying the effect, and return
+ return
+
+ // otherwise, delete existing and replace with new
+ qdel(existing_effect)
+
+ return apply_status_effect(
+ /datum/status_effect/stun_absorption,
+ source,
+ duration,
+ priority,
+ message,
+ self_message,
+ examine_message,
+ max_seconds_of_stuns_blocked,
+ )
+
+/**
+ * Removes all stub absorptions with the passed source.
+ *
+ * Returns TRUE if an effect was deleted, FALSE otherwise
+ */
+/mob/living/proc/remove_stun_absorption(source)
+ . = FALSE
+ for(var/datum/status_effect/stun_absorption/effect in status_effects)
+ if(effect.source != source)
+ continue
+
+ qdel(effect)
+ . = TRUE
+
+ return .
diff --git a/code/game/objects/items/food/cheese.dm b/code/game/objects/items/food/cheese.dm
index 4652017c65b8..ed980bd2a57e 100644
--- a/code/game/objects/items/food/cheese.dm
+++ b/code/game/objects/items/food/cheese.dm
@@ -56,6 +56,15 @@
/obj/item/food/cheese/wheel/make_bakeable()
AddComponent(/datum/component/bakeable, /obj/item/food/baked_cheese, rand(20 SECONDS, 25 SECONDS), TRUE, TRUE)
+/**
+ * Whiffs away cheese that was touched by the chaos entity byond the realm. In layman's terms, deletes the cheese and throws sparks.
+ * Used in wizard grand rituals' optional cheesy alternative.
+ */
+/obj/item/food/cheese/wheel/proc/consume_cheese()
+ visible_message(span_revenwarning("...and is consumed in a vortex of chaos!"))
+ do_sparks(number = 1, cardinal_only = TRUE, source = get_turf(src))
+ qdel(src)
+
/obj/item/food/cheese/royal
name = "royal cheese"
desc = "Ascend the throne. Consume the wheel. Feel the POWER."
diff --git a/code/game/objects/items/granters/magic/summon_cheese.dm b/code/game/objects/items/granters/magic/summon_cheese.dm
new file mode 100644
index 000000000000..668d3be8f9ac
--- /dev/null
+++ b/code/game/objects/items/granters/magic/summon_cheese.dm
@@ -0,0 +1,28 @@
+/obj/item/book/granter/action/spell/summon_cheese
+ name = "Lusty Xenomorph Maid vol. III - Cheese Bakery"
+ desc = "Wonderful! Time for a celebration... Cheese for everyone!"
+ icon_state = "bookcheese"
+ action_name = "summon cheese"
+ granted_action = /datum/action/cooldown/spell/conjure/cheese
+ remarks = list(
+ "Always forward, never back...",
+ "Are these pages... cheese slices?..",
+ "Healthy snacks for unsuspecting victims...",
+ "I never knew so many types of cheese existed...",
+ "Madness reeks of goat cheese...",
+ "Madness tastes of gouda...",
+ "Madness tastes of parmesan...",
+ "Time is an artificial construct...",
+ "Was it order or biscuits?..",
+ "What's this about sacrificing cheese?!..",
+ "Who wouldn't like that?..",
+ "Why cheese, of all things?..",
+ "Why do I need a reason for everything?..",
+ )
+
+/obj/item/book/granter/action/spell/summon_cheese/recoil(mob/living/user)
+ to_chat(user, span_warning("\The [src] turns into a wedge of cheese!"))
+ var/obj/item/food/cheese/wedge/book_cheese = new
+ user.temporarilyRemoveItemFromInventory(src, force = TRUE)
+ user.put_in_hands(book_cheese)
+ qdel(src)
diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm
index b983a4ab5746..5d7a3715850f 100644
--- a/code/game/objects/items/stacks/sheets/sheet_types.dm
+++ b/code/game/objects/items/stacks/sheets/sheet_types.dm
@@ -861,6 +861,7 @@ new /datum/stack_recipe("paper frame door", /obj/structure/mineral_door/paperfra
merge_type = /obj/item/stack/sheet/hauntium
material_type = /datum/material/hauntium
material_modifier = 1 //None of that wussy stuff
+ grind_results = list(/datum/reagent/hauntium = 20)
/obj/item/stack/sheet/hauntium/fifty
amount = 50
diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm
index b2fdb93407c6..a5c22653266a 100644
--- a/code/game/objects/items/weaponry.dm
+++ b/code/game/objects/items/weaponry.dm
@@ -150,12 +150,18 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
/obj/item/claymore/highlander/pickup(mob/living/user)
. = ..()
to_chat(user, span_notice("The power of Scotland protects you! You are shielded from all stuns and knockdowns."))
- user.add_stun_absorption("highlander", INFINITY, 1, " is protected by the power of Scotland!", "The power of Scotland absorbs the stun!", " is protected by the power of Scotland!")
- user.ignore_slowdown(HIGHLANDER)
+ user.ignore_slowdown(HIGHLANDER_TRAIT)
+ user.add_stun_absorption(
+ source = HIGHLANDER_TRAIT,
+ message = span_warning("%EFFECT_OWNER is protected by the power of Scotland!"),
+ self_message = span_boldwarning("The power of Scotland absorbs the stun!"),
+ examine_message = span_warning("%EFFECT_OWNER_THEYRE protected by the power of Scotland!"),
+ )
/obj/item/claymore/highlander/dropped(mob/living/user)
. = ..()
- user.unignore_slowdown(HIGHLANDER)
+ user.unignore_slowdown(HIGHLANDER_TRAIT)
+ user.remove_stun_absorption(HIGHLANDER_TRAIT)
/obj/item/claymore/highlander/examine(mob/user)
. = ..()
@@ -573,6 +579,7 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301
gender = PLURAL
icon = 'icons/obj/wizard.dmi'
icon_state = "ectoplasm"
+ grind_results = list(/datum/reagent/hauntium = 25) //can be ground into hauntium
/obj/item/ectoplasm/suicide_act(mob/living/user)
user.visible_message(span_suicide("[user] is inhaling [src]! It looks like [user.p_theyre()] trying to visit the astral plane!"))
diff --git a/code/modules/antagonists/cult/cult_bastard_sword.dm b/code/modules/antagonists/cult/cult_bastard_sword.dm
index 30fe655f69ba..5ae594207db7 100644
--- a/code/modules/antagonists/cult/cult_bastard_sword.dm
+++ b/code/modules/antagonists/cult/cult_bastard_sword.dm
@@ -45,7 +45,14 @@
/obj/item/cult_bastard/proc/on_spin(mob/living/user, duration)
var/oldcolor = user.color
user.color = "#ff0000"
- user.add_stun_absorption("bloody bastard sword", duration, 2, "doesn't even flinch as the sword's power courses through them!", "You shrug off the stun!", " glowing with a blazing red aura!")
+ user.add_stun_absorption(
+ source = name,
+ duration = duration,
+ priority = 2,
+ message = span_warning("%EFFECT_OWNER doesn't even flinch as the sword's power courses through [user.p_them()]!"),
+ self_message = span_boldwarning("You shrug off the stun!"),
+ examine_message = span_warning("%EFFECT_OWNER_THEYRE glowing with a blazing red aura!"),
+ )
user.spin(duration, 1)
animate(user, color = oldcolor, time = duration, easing = EASE_IN)
addtimer(CALLBACK(user, TYPE_PROC_REF(/atom, update_atom_colour)), duration)
diff --git a/code/modules/antagonists/highlander/highlander.dm b/code/modules/antagonists/highlander/highlander.dm
index c740de9279fd..98659ef19419 100644
--- a/code/modules/antagonists/highlander/highlander.dm
+++ b/code/modules/antagonists/highlander/highlander.dm
@@ -66,7 +66,7 @@
P.attack_self(H)
var/obj/item/card/id/advanced/highlander/W = new(H)
W.registered_name = H.real_name
- ADD_TRAIT(W, TRAIT_NODROP, HIGHLANDER)
+ ADD_TRAIT(W, TRAIT_NODROP, HIGHLANDER_TRAIT)
W.update_label()
W.update_icon()
H.equip_to_slot_or_del(W, ITEM_SLOT_ID)
diff --git a/code/modules/antagonists/revenant/haunted_item.dm b/code/modules/antagonists/revenant/haunted_item.dm
index a4772912a61f..4bee2f6e8f12 100644
--- a/code/modules/antagonists/revenant/haunted_item.dm
+++ b/code/modules/antagonists/revenant/haunted_item.dm
@@ -34,7 +34,7 @@
if(istype(haunted_item.ai_controller, /datum/ai_controller/haunted)) // already spooky
return COMPONENT_INCOMPATIBLE
- haunted_item.AddElement(/datum/element/haunted, haunt_color)
+ haunted_item.make_haunted(MAGIC_TRAIT, haunt_color)
if(isnull(haunted_item.ai_controller)) // failed to make spooky! don't go on
return COMPONENT_INCOMPATIBLE
@@ -71,7 +71,7 @@
// because we want to make sure they always get dealt with no matter how the component is removed
if(!isnull(pre_haunt_throwforce))
haunted_item.throwforce = pre_haunt_throwforce
- haunted_item.RemoveElement(/datum/element/haunted)
+ haunted_item.remove_haunted(MAGIC_TRAIT)
return ..()
/datum/component/haunted_item/RegisterWithParent()
diff --git a/code/modules/antagonists/wizard/equipment/soulstone.dm b/code/modules/antagonists/wizard/equipment/soulstone.dm
index f93ed8abcff2..0a0746e52992 100644
--- a/code/modules/antagonists/wizard/equipment/soulstone.dm
+++ b/code/modules/antagonists/wizard/equipment/soulstone.dm
@@ -24,6 +24,7 @@
var/theme = THEME_CULT
/// Role check, if any needed
var/required_role = /datum/antagonist/cult
+ grind_results = list(/datum/reagent/hauntium = 25, /datum/reagent/silicon = 10) //can be ground into hauntium
/obj/item/soulstone/Initialize(mapload)
. = ..()
diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm
index b23a1461c49a..57aa2ad7af18 100644
--- a/code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm
+++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm
@@ -23,6 +23,14 @@
spell_type = /datum/action/cooldown/spell/touch/smite
category = "Offensive"*/
+/datum/spellbook_entry/summon_simians
+ name = "Summon Simians"
+ desc = "This spell reaches deep into the elemental plane of bananas (the monkey one, not the clown one), and \
+ summons primal monkeys and lesser gorillas that will promptly flip out and attack everything in sight. Fun! \
+ Their lesser, easily manipulable minds will be convinced you are one of their allies, but only for a minute. Unless you also are a monkey."
+ spell_type = /datum/action/cooldown/spell/conjure/simian
+ category = "Offensive"
+
/datum/spellbook_entry/blind
name = "Blind"
desc = "Temporarily blinds a single target."
diff --git a/code/modules/antagonists/wizard/grand_ritual/fluff.dm b/code/modules/antagonists/wizard/grand_ritual/fluff.dm
new file mode 100644
index 000000000000..8a1e340a84bf
--- /dev/null
+++ b/code/modules/antagonists/wizard/grand_ritual/fluff.dm
@@ -0,0 +1,25 @@
+/**
+ * Fluff book to hint at the cheesy grand ritual.
+ */
+/obj/item/book/manual/ancient_parchment
+ name = "ancient parchment"
+ icon = 'icons/obj/wizard.dmi'
+ icon_state = "scroll-ancient"
+ unique = TRUE
+ w_class = WEIGHT_CLASS_SMALL
+ starting_author = "Pelagius the Mad"
+ starting_title = "Worship and Reverence of the Divine Insanity"
+ starting_content = {"
+
+
+
+
+ Most of the scroll's contents are unintelligible, plagued with mold, milk stains and a stench of spolied goat cheese so potent,
+ you can barely resist turning your head to retch. What's left of the writings is vague and abstract, as if the author
+ was in a mad dash to pass on their findings.
+ However, the runes they have managed to scribe onto the parchment are oddly untouched by time, and remain distinct.
+ You also discover a schema for a more widely-used Grand Ritual rune, however it is dotted with yellow circles, which in turn are
+ filled with black dots. Are these supposed to be... cheese wheels?..
+ As you finish skimming through the wreck that is this scroll, you hear a faint snicker somewhere beyond your mind's eye...
+
+ "}
diff --git a/code/modules/antagonists/wizard/grand_ritual/grand_ritual.dm b/code/modules/antagonists/wizard/grand_ritual/grand_ritual.dm
index ce1d89edcc61..f3ac9b010710 100644
--- a/code/modules/antagonists/wizard/grand_ritual/grand_ritual.dm
+++ b/code/modules/antagonists/wizard/grand_ritual/grand_ritual.dm
@@ -1,3 +1,10 @@
+/**
+ * Total cheese goal to sacrifice to [REDACTED] during wizard grand rituals.
+ * The easiest way for a wizard to procure cheese is with Summon Cheese spell, which summons 9 per cast.
+ * The wizard needs to complete 7 rituals, so let's give them some breathing room with cheese offerings.
+ * 7 * 9 = 63, so the wizard can potentially miss two casts worth of cheese if they summon cheese on each rune.
+*/
+#define CHEESE_SACRIFICE_GOAL 50
/**
* The Grand Ritual is the Wizard's alternate victory condition
* and also a tool to make funny distractions and progress the round state.
@@ -30,6 +37,10 @@
var/drew_finale = FALSE
/// True while you are drawing a rune, prevents action spamming
var/drawing_rune = FALSE
+ /// Number of cheese sacrificed on previously drawn runes
+ var/total_cheese_sacrificed = 0
+ /// Whether we have sacrificed enough cheese or not
+ var/total_cheese_goal_met = FALSE
/// Weakref to a rune drawn in the current area, if there is one
var/datum/weakref/rune
@@ -150,13 +161,20 @@
/// Draws the ritual rune
/datum/action/cooldown/grand_ritual/proc/draw_rune(turf/target_turf)
drawing_rune = TRUE
+ var/next_rune_typepath = get_appropriate_rune_typepath()
target_turf.balloon_alert(owner, "conjuring rune...")
- var/obj/effect/temp_visual/wizard_rune/drawing/draw_effect = new(target_turf)
+ var/draw_effect_typepath = /obj/effect/temp_visual/wizard_rune/drawing
+ if(next_rune_typepath == /obj/effect/grand_rune/finale/cheesy)
+ draw_effect_typepath = /obj/effect/temp_visual/wizard_rune/drawing/cheese
+ var/obj/effect/temp_visual/wizard_rune/drawing/draw_effect = new draw_effect_typepath(target_turf)
if(!do_after(owner, 4 SECONDS, target_turf))
target_turf.balloon_alert(owner, "interrupted!")
drawing_rune = FALSE
qdel(draw_effect)
- new /obj/effect/temp_visual/wizard_rune/failed(target_turf)
+ var/fail_effect_typepath = /obj/effect/temp_visual/wizard_rune/failed
+ if(next_rune_typepath == /obj/effect/grand_rune/finale/cheesy)
+ fail_effect_typepath = /obj/effect/temp_visual/wizard_rune/failed/cheese
+ new fail_effect_typepath(target_turf)
return
var/evaporated_obstacles = FALSE
@@ -176,25 +194,37 @@
playsound(target_turf, 'sound/magic/blind.ogg', 100, TRUE)
target_turf.balloon_alert(owner, "rune created")
- var/obj/effect/grand_rune/new_rune = create_appropriate_rune(target_turf)
+ var/obj/effect/grand_rune/new_rune = new next_rune_typepath(target_turf, times_completed)
+ if(istype(new_rune, /obj/effect/grand_rune/finale))
+ drew_finale = TRUE
rune = WEAKREF(new_rune)
RegisterSignal(new_rune, COMSIG_GRAND_RUNE_COMPLETE, PROC_REF(on_rune_complete))
drawing_rune = FALSE
StartCooldown(2 MINUTES) // To put a damper on wizards who have 5 ranks of Teleport
/// The seventh rune we spawn is special
-/datum/action/cooldown/grand_ritual/proc/create_appropriate_rune(turf/target_turf)
+/datum/action/cooldown/grand_ritual/proc/get_appropriate_rune_typepath()
if (times_completed < GRAND_RITUAL_FINALE_COUNT - 1)
- return new /obj/effect/grand_rune(target_turf, times_completed)
+ return /obj/effect/grand_rune
if (drew_finale)
- return new /obj/effect/grand_rune(target_turf, times_completed)
- drew_finale = TRUE
- return new /obj/effect/grand_rune/finale(target_turf, times_completed)
+ return /obj/effect/grand_rune
+ if (total_cheese_sacrificed >= CHEESE_SACRIFICE_GOAL)
+ return /obj/effect/grand_rune/finale/cheesy
+ return /obj/effect/grand_rune/finale
/// Called when you finish invoking a rune you drew, get ready for another one.
-/datum/action/cooldown/grand_ritual/proc/on_rune_complete(atom/source)
+/datum/action/cooldown/grand_ritual/proc/on_rune_complete(atom/source, cheese_sacrificed)
SIGNAL_HANDLER
UnregisterSignal(source, COMSIG_GRAND_RUNE_COMPLETE)
+ total_cheese_sacrificed += cheese_sacrificed
+ if(total_cheese_sacrificed >= CHEESE_SACRIFICE_GOAL)
+ if(!total_cheese_goal_met)
+ total_cheese_goal_met = TRUE
+ to_chat(owner, span_revenbignotice("YES! CHEESE! CHEESE FOR EVERYONE! SUCH A GRAND FEAST! YOU SHALL HAVE YOUR PRIZE, MY CHAMPION!!"))
+ else
+ to_chat(owner, span_revennotice("You hear maddening laughter as you are hit with an overwhelming odor of fine cheddar..."))
+ else if (total_cheese_sacrificed)
+ to_chat(owner, span_revendanger("You please me, mortal. Do continue to send cheese, my feast still needs [CHEESE_SACRIFICE_GOAL - total_cheese_sacrificed] more to be magnificent..."))
rune = null
times_completed++
set_new_area()
@@ -203,8 +233,12 @@
to_chat(owner, span_warning("Your collected power is growing, \
but further rituals will alert your enemies to your position."))
if (GRAND_RITUAL_IMMINENT_FINALE_POTENCY)
- to_chat(owner, span_warning("You are overflowing with power! \
- Your next Grand Ritual will allow you to choose a powerful effect, and grant you victory."))
+ var/message = "You are overflowing with power! \
+ Your next Grand Ritual will allow you to choose a powerful effect, and grant you victory."
+ if(total_cheese_sacrificed >= CHEESE_SACRIFICE_GOAL)
+ message = "You are overflowing with chaotic energies! \
+ Your next Grand Ritual will conjure a powerful artefact for your use, and grant you victory."
+ to_chat(owner, span_warning(message))
if (GRAND_RITUAL_FINALE_COUNT)
SEND_SIGNAL(src, COMSIG_GRAND_RITUAL_FINAL_COMPLETE)
@@ -283,3 +317,13 @@
/obj/effect/temp_visual/wizard_rune/failed
icon_state = "wizard_rune_fail"
duration = 0.5 SECONDS
+
+/// Cheese drawing
+/obj/effect/temp_visual/wizard_rune/drawing/cheese
+ icon_state = "wizard_rune_cheese_draw"
+
+/// Cheese fail
+/obj/effect/temp_visual/wizard_rune/failed/cheese
+ icon_state = "wizard_rune_cheese_fail"
+
+#undef CHEESE_SACRIFICE_GOAL
diff --git a/code/modules/antagonists/wizard/grand_ritual/grand_ritual_finale.dm b/code/modules/antagonists/wizard/grand_ritual/grand_ritual_finale.dm
index 82ed11910f98..d0cc58ff0f59 100644
--- a/code/modules/antagonists/wizard/grand_ritual/grand_ritual_finale.dm
+++ b/code/modules/antagonists/wizard/grand_ritual/grand_ritual_finale.dm
@@ -33,12 +33,13 @@
/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)
- return
+ 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
+ choice.info = desc + time_remaining_desc
return choice
/**
@@ -321,7 +322,8 @@
/datum/dimension_theme/natural,
/datum/dimension_theme/clown, //monkestation edit: HONK!
/datum/dimension_theme/fancy, //monkestation edit
- /datum/dimension_theme/disco,, //monkestation edit
+ /datum/dimension_theme/disco, //monkestation edit
+ /datum/dimension_theme/clockwork, //monkestation edit: clock cult W
)
var/datum/dimension_theme/chosen_theme
@@ -392,24 +394,28 @@
var/static/list/doom_options = list()
if (!length(doom_options))
- doom_options = list(DOOM_SINGULARITY, DOOM_TESLA, DOOM_EVENTS, DOOM_ANTAGS, DOOM_ROD) //monkestation edit: added DOOM_EVENTS, DOOM_ANTAGS and DOOM_ROD
+// doom_options = list(DOOM_SINGULARITY, DOOM_TESLA) //monkestation removal
+ doom_options = list(DOOM_EVENTS, DOOM_ANTAGS, DOOM_ROD) //monkestation edit
if (!SSmapping.config.planetary)
doom_options += DOOM_METEORS
switch(pick(doom_options))
- if (DOOM_SINGULARITY)
+//monkestation removal start
+ /*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
+ tesla.energy = 200*/
+//monkestation removal end
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)
- if (DOOM_EVENTS) //monkestation edit start: triggers a MASSIVE amount of events pretty quickly
+//monkestation edit start
+ if (DOOM_EVENTS) //triggers a MASSIVE amount of events pretty quickly
summon_events() //wont effect the events created directly from this, but it will effect any events that happen after
var/list/possible_events = list()
for(var/datum/round_event_control/possible_event as anything in SSevents.control)
@@ -438,7 +444,58 @@
var/obj/effect/immovablerod/rod = new(current_location)
rod.loopy_rod = TRUE
rod.can_suplex = FALSE
- rod.deadchat_plays(ANARCHY_MODE, 4 SECONDS)//monkestation edit end
+ rod.deadchat_plays(ANARCHY_MODE, 4 SECONDS)
+//monkestation edit end
+
+/**
+ * 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
diff --git a/code/modules/antagonists/wizard/grand_ritual/grand_rune.dm b/code/modules/antagonists/wizard/grand_ritual/grand_rune.dm
index 80eb6cccd1de..88ec1686693f 100644
--- a/code/modules/antagonists/wizard/grand_ritual/grand_rune.dm
+++ b/code/modules/antagonists/wizard/grand_ritual/grand_rune.dm
@@ -31,6 +31,10 @@
var/times_invoked = 0
/// What colour you glow while channeling
var/spell_colour = "#de3aff48"
+ /// How much cheese was sacrificed to the other realm, if any
+ var/cheese_sacrificed = 0
+ /// What kind of remains this rune leaves behind after completing invokation
+ var/remains_typepath = /obj/effect/decal/cleanable/grand_remains
/// Magic words you say to invoke the ritual
var/list/magic_words = list()
/// Things you might yell when invoking a rune
@@ -67,7 +71,8 @@
. = ..()
src.potency = potency
invoke_time = get_invoke_time()
- magic_words = pick(possible_magic_words)
+ if(!length(magic_words))
+ magic_words = pick(possible_magic_words)
var/image/silicon_image = image(icon = 'icons/effects/eldritch.dmi', icon_state = null, loc = src)
silicon_image.override = TRUE
add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/silicons, "wizard_rune", silicon_image)
@@ -114,6 +119,7 @@
is_in_use = TRUE
add_channel_effect(user)
user.balloon_alert(user, "invoking rune...")
+
if(!do_after(user, invoke_time, src))
remove_channel_effect(user)
user.balloon_alert(user, "interrupted!")
@@ -121,6 +127,30 @@
return
times_invoked++
+
+ //fetch cheese on the rune
+ var/list/obj/item/food/cheese/wheel/cheese_list = list()
+ for(var/obj/item/food/cheese/wheel/nearby_cheese in range(1, src))
+ if(HAS_TRAIT(nearby_cheese, TRAIT_HAUNTED)) //already haunted
+ continue
+ cheese_list += nearby_cheese
+ //handle cheese sacrifice - haunt a part of all cheese on the rune with each invocation, then delete it
+ var/list/obj/item/food/cheese/wheel/cheese_to_haunt = list()
+ cheese_list = shuffle(cheese_list)
+ //the intent here is to sacrifice cheese in parts, roughly in thirds since we invoke the rune three times
+ //so hopefully this will properly do that, and on the third invocation it will just eat all remaining cheese
+ cheese_to_haunt = cheese_list.Copy(1, min(round(length(cheese_list) * times_invoked * 0.4), max(length(cheese_list), 3)))
+ for(var/obj/item/food/cheese/wheel/sacrifice as anything in cheese_to_haunt)
+ sacrifice.AddComponent(\
+ /datum/component/haunted_item, \
+ haunt_color = spell_colour, \
+ haunt_duration = 10 SECONDS, \
+ aggro_radius = 0, \
+ spawn_message = span_revenwarning("[sacrifice] begins to float and twirl into the air as it becomes enveloped in otherworldy energies..."), \
+ )
+ addtimer(CALLBACK(sacrifice, TYPE_PROC_REF(/obj/item/food/cheese/wheel, consume_cheese)), 10 SECONDS)
+ cheese_sacrificed += length(cheese_to_haunt)
+
user.say(magic_words[times_invoked], forced = "grand ritual invocation")
remove_channel_effect(user)
@@ -130,7 +160,7 @@
if(times_invoked >= GRAND_RUNE_INVOKES_TO_COMPLETE)
on_invocation_complete(user)
return
- flick("wizard_rune_flash", src)
+ flick("[icon_state]_flash", src)
playsound(src,'sound/magic/staff_animation.ogg', 75, TRUE)
INVOKE_ASYNC(src, PROC_REF(invoke_rune), user)
@@ -159,7 +189,7 @@
addtimer(CALLBACK(src, PROC_REF(remove_rune)), 6)
/obj/effect/grand_rune/proc/remove_rune()
- new /obj/effect/decal/cleanable/grand_remains(get_turf(src))
+ new remains_typepath(get_turf(src))
qdel(src)
/// Triggers some form of event somewhere on the station
@@ -272,9 +302,14 @@
return ..()
/obj/effect/grand_rune/finale/interact(mob/living/user)
- if (chosen_effect)
- return ..()
- select_finale(user)
+ if (!chosen_effect)
+ select_finale(user)
+ return
+ var/round_time_passed = world.time - SSticker.round_start_time
+ if (chosen_effect && finale_effect.minimum_time >= round_time_passed)
+ to_chat(user, span_warning("The chosen grand finale will only be available in [DisplayTimeText(finale_effect.minimum_time - round_time_passed)]!"))
+ return
+ return ..()
#define PICK_NOTHING "Continuation"
@@ -307,6 +342,7 @@
invoke_time = get_invoke_time()
if (finale_effect.glow_colour)
spell_colour = finale_effect.glow_colour
+ add_filter("finale_picked_glow", 2, list("type" = "outline", "color" = spell_colour, "size" = 2))
/obj/effect/grand_rune/finale/summon_round_event(mob/living/user)
if (!finale_effect)
@@ -318,6 +354,25 @@
return ..()
return finale_effect.ritual_invoke_time
+/**
+ * Spawned when 50 or more cheese was sacrificed during previous grand rituals.
+ * Will spawn instead of the usual grand ritual rune, and its effect is already set and can't be changed.
+ * Sorry, no narwal fighting on the open ocean this time.
+ */
+/obj/effect/grand_rune/finale/cheesy
+ name = "especially grand rune"
+ desc = "A ritual circle of maddening shapes and outlines, its mere presence an insult to reason."
+ icon_state = "wizard_rune_cheese"
+ magic_words = list("Greetings! Salutations!", "Welcome! Now go away.", "Leave. Run. Or die.")
+ remains_typepath = /obj/effect/decal/cleanable/grand_remains/cheese
+
+/obj/effect/grand_rune/finale/cheesy/Initialize(mapload, potency)
+ . = ..()
+ finale_effect = new /datum/grand_finale/cheese()
+ chosen_effect = TRUE
+ add_filter("finale_picked_glow", 2, list("type" = "outline", "color" = spell_colour, "size" = 2))
+
+
/**
* Spawned when we are done with the rune
*/
@@ -334,4 +389,9 @@
clean_type = CLEAN_TYPE_HARD_DECAL
layer = SIGIL_LAYER
+/obj/effect/decal/cleanable/grand_remains/cheese
+ name = "cheese soot marks"
+ desc = "The bizarre shapes on the ground turn out to be a cheese crust burned to black tar."
+ icon_state = "wizard_rune_cheese_burned"
+
#undef PICK_NOTHING
diff --git a/code/modules/clothing/head/jobs.dm b/code/modules/clothing/head/jobs.dm
index 40dbc8f4ffea..fc018e15a0dc 100644
--- a/code/modules/clothing/head/jobs.dm
+++ b/code/modules/clothing/head/jobs.dm
@@ -458,7 +458,7 @@
/obj/item/clothing/head/beret/highlander/Initialize(mapload)
. = ..()
- ADD_TRAIT(src, TRAIT_NODROP, HIGHLANDER)
+ ADD_TRAIT(src, TRAIT_NODROP, HIGHLANDER_TRAIT)
//CentCom
/obj/item/clothing/head/beret/centcom_formal
diff --git a/code/modules/clothing/head/mind_monkey_helmet.dm b/code/modules/clothing/head/mind_monkey_helmet.dm
index 83fa277d2258..4df509e951ba 100644
--- a/code/modules/clothing/head/mind_monkey_helmet.dm
+++ b/code/modules/clothing/head/mind_monkey_helmet.dm
@@ -81,7 +81,9 @@
if(prob(10))
switch(rand(1,4))
if(1) //blood rage
- magnification.ai_controller.set_blackboard_key(BB_MONKEY_AGGRESSIVE, TRUE)
+ var/datum/ai_controller/monkey/monky_controller = magnification.ai_controller
+ monky_controller.set_trip_mode(mode = FALSE)
+ monky_controller.set_blackboard_key(BB_MONKEY_AGGRESSIVE, TRUE)
if(2) //brain death
magnification.apply_damage(500,BRAIN,BODY_ZONE_HEAD,FALSE,FALSE,FALSE)
if(3) //primal gene (gorilla)
diff --git a/code/modules/clothing/under/costume.dm b/code/modules/clothing/under/costume.dm
index fd7db5abb120..ef1f0002c1b8 100644
--- a/code/modules/clothing/under/costume.dm
+++ b/code/modules/clothing/under/costume.dm
@@ -90,7 +90,7 @@
/obj/item/clothing/under/costume/kilt/highlander/Initialize(mapload)
. = ..()
- ADD_TRAIT(src, TRAIT_NODROP, HIGHLANDER)
+ ADD_TRAIT(src, TRAIT_NODROP, HIGHLANDER_TRAIT)
/obj/item/clothing/under/costume/gladiator
name = "gladiator uniform"
diff --git a/code/modules/hallucination/stray_bullet.dm b/code/modules/hallucination/stray_bullet.dm
index f04c718f90b8..f3c5253b53cb 100644
--- a/code/modules/hallucination/stray_bullet.dm
+++ b/code/modules/hallucination/stray_bullet.dm
@@ -242,7 +242,7 @@
";AAAAAAARRRGH!"),
forced = "hulk (hallucinating)",
)
- else if((afflicted.status_flags & CANKNOCKDOWN) && !HAS_TRAIT(afflicted, TRAIT_STUNIMMUNE))
+ else if(!afflicted.check_stun_immunity(CANKNOCKDOWN))
addtimer(CALLBACK(afflicted, TYPE_PROC_REF(/mob/living/carbon, do_jitter_animation), 20), 0.5 SECONDS)
/obj/projectile/hallucination/disabler
diff --git a/code/modules/library/random_books.dm b/code/modules/library/random_books.dm
index 1b15eeb5e975..3dcdff50a580 100644
--- a/code/modules/library/random_books.dm
+++ b/code/modules/library/random_books.dm
@@ -94,6 +94,7 @@
/obj/structure/bookcase/random/reference
name = "bookcase (Reference)"
random_category = "Reference"
+ ///Chance to spawn a random manual book
var/ref_book_prob = 20
/obj/structure/bookcase/random/reference/Initialize(mapload)
@@ -101,3 +102,15 @@
while(books_to_load > 0 && prob(ref_book_prob))
books_to_load--
new /obj/item/book/manual/random(src)
+
+/obj/structure/bookcase/random/reference/wizard
+ desc = "It reeks of cheese..."
+ ///Whether this shelf has spawned a cheese granter
+ var/static/cheese_granter_spawned = FALSE
+
+/obj/structure/bookcase/random/reference/wizard/after_random_load()
+ if(cheese_granter_spawned)
+ return
+ cheese_granter_spawned = TRUE
+ new /obj/item/book/granter/action/spell/summon_cheese(src)
+ new /obj/item/book/manual/ancient_parchment(src)
diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm
index 6054b5dc865f..5f884023135b 100644
--- a/code/modules/mob/living/carbon/human/examine.dm
+++ b/code/modules/mob/living/carbon/human/examine.dm
@@ -291,11 +291,6 @@
if(reagents.has_reagent(/datum/reagent/teslium, needs_metabolizing = TRUE))
msg += "[t_He] [t_is] emitting a gentle blue glow!\n"
- if(islist(stun_absorption))
- for(var/i in stun_absorption)
- if(stun_absorption[i]["end_time"] > world.time && stun_absorption[i]["examine_message"])
- msg += "[t_He] [t_is][stun_absorption[i]["examine_message"]]\n"
-
if(just_sleeping)
msg += "[t_He] [t_is]n't responding to anything around [t_him] and seem[p_s()] to be asleep.\n"
diff --git a/code/modules/mob/living/carbon/status_procs.dm b/code/modules/mob/living/carbon/status_procs.dm
index af2ef1cb1eb6..91230207fccf 100644
--- a/code/modules/mob/living/carbon/status_procs.dm
+++ b/code/modules/mob/living/carbon/status_procs.dm
@@ -13,11 +13,9 @@
return
/mob/living/carbon/stamina_stun()
- if(!(status_flags & CANKNOCKDOWN) || HAS_TRAIT(src, TRAIT_STUNIMMUNE))
- return
if(HAS_TRAIT_FROM(src, TRAIT_INCAPACITATED, STAMINA)) //Already in stamcrit
return
- if(absorb_stun(0)) //continuous effect, so we don't want it to increment the stuns absorbed.
+ if(check_stun_immunity(CANKNOCKDOWN))
return
var/chance = STAMINA_SCALING_STUN_BASE + (STAMINA_SCALING_STUN_SCALER * stamina.current * STAMINA_STUN_THRESHOLD_MODIFIER)
if(!prob(chance))
diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm
index 399a52bb5d09..b686d92e840c 100644
--- a/code/modules/mob/living/damage_procs.dm
+++ b/code/modules/mob/living/damage_procs.dm
@@ -150,7 +150,7 @@
adjust_drowsiness(drowsy)
if(eyeblur)
adjust_eye_blur(eyeblur)
- if(jitter && (status_flags & CANSTUN) && !HAS_TRAIT(src, TRAIT_STUNIMMUNE))
+ if(jitter && !check_stun_immunity(CANSTUN))
adjust_jitter(jitter)
if(slur)
adjust_slurring(slur)
diff --git a/code/modules/mob/living/silicon/robot/robot_model.dm b/code/modules/mob/living/silicon/robot/robot_model.dm
index b3e403248498..bbc4890dd69c 100644
--- a/code/modules/mob/living/silicon/robot/robot_model.dm
+++ b/code/modules/mob/living/silicon/robot/robot_model.dm
@@ -952,7 +952,7 @@
robot.equip_module_to_slot(locate(/obj/item/claymore/highlander/robot) in basic_modules, 1)
robot.equip_module_to_slot(locate(/obj/item/pinpointer/nuke) in basic_modules, 2)
robot.place_on_head(new /obj/item/clothing/head/beret/highlander(robot)) //THE ONLY PART MORE IMPORTANT THAN THE SWORD IS THE HAT
- ADD_TRAIT(robot.hat, TRAIT_NODROP, HIGHLANDER)
+ ADD_TRAIT(robot.hat, TRAIT_NODROP, HIGHLANDER_TRAIT)
// ------------------------------------------ Storages
diff --git a/code/modules/mob/living/simple_animal/hostile/gorilla/gorilla.dm b/code/modules/mob/living/simple_animal/hostile/gorilla/gorilla.dm
index fa86cc950e51..55007a1121ce 100644
--- a/code/modules/mob/living/simple_animal/hostile/gorilla/gorilla.dm
+++ b/code/modules/mob/living/simple_animal/hostile/gorilla/gorilla.dm
@@ -114,6 +114,23 @@
oogas = rand(2,6)
playsound(src, 'sound/creatures/gorilla.ogg', 50)
+/mob/living/simple_animal/hostile/gorilla/lesser
+ name = "lesser Gorilla"
+ desc = "An adolescent Gorilla. It may not be fully grown but, much like a banana, that just means it's sturdier and harder to chew!"
+ speak_chance = 100 // compensating for something
+ maxHealth = 120
+ health = 120
+ butcher_results = list(/obj/item/food/meat/slab/gorilla = 2)
+ speed = 0.35
+ melee_damage_lower = 10
+ melee_damage_upper = 15
+ obj_damage = 15
+ stat_attack = SOFT_CRIT
+ unique_name = TRUE
+
+/mob/living/simple_animal/hostile/gorilla/lesser/Initialize(mapload)
+ . = ..()
+ transform *= 0.75 // smolrilla
/mob/living/simple_animal/hostile/gorilla/cargo_domestic
name = "Cargorilla" // Overriden, normally
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm
index 35c892c76a08..e62c1eac7c5b 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm
@@ -97,9 +97,8 @@ Difficulty: Medium
open_force = 10
/obj/item/melee/cleaving_saw/miner/attack(mob/living/target, mob/living/carbon/human/user)
- target.add_stun_absorption("miner", 10, INFINITY)
- . = ..()
- target.stun_absorption -= "miner"
+ target.add_stun_absorption(source = "miner", duration = 1 SECONDS, priority = INFINITY)
+ return ..()
/obj/projectile/kinetic/miner
damage = 20
diff --git a/code/modules/mob/living/status_procs.dm b/code/modules/mob/living/status_procs.dm
index 4e54d46b3da2..691003fc4f12 100644
--- a/code/modules/mob/living/status_procs.dm
+++ b/code/modules/mob/living/status_procs.dm
@@ -1,7 +1,34 @@
-//Here are the procs used to modify status effects of a mob.
-//The effects include: stun, knockdown, unconscious, sleeping, resting
-#define IS_STUN_IMMUNE(source, ignore_canstun) ((source.status_flags & GODMODE) || (!ignore_canstun && (!(source.status_flags & CANKNOCKDOWN) || HAS_TRAIT(source, TRAIT_STUNIMMUNE))))
+
+/**
+ * Checks if we have stun immunity. Godmode always passes this check.
+ *
+ * * check_flags - bitflag of status flags that must be set in order for the stun to succeed. Passing NONE will always return false.
+ * * force_stun - whether we ignore stun immunity with the exception of godmode
+ *
+ * returns TRUE if stun immune, FALSE otherwise
+ */
+/mob/living/proc/check_stun_immunity(check_flags = CANSTUN, force_stun = FALSE)
+ SHOULD_CALL_PARENT(TRUE)
+
+ if(status_flags & GODMODE)
+ return TRUE
+
+ if(force_stun) // Does not take priority over god mode? I guess
+ return FALSE
+
+ if(SEND_SIGNAL(src, COMSIG_LIVING_GENERIC_STUN_CHECK, check_flags, force_stun) & COMPONENT_NO_STUN)
+ return TRUE
+
+ if(HAS_TRAIT(src, TRAIT_STUNIMMUNE))
+ return TRUE
+
+ // Do we have the correct flag set to allow this status?
+ // This checks that ALL flags are set, not just one of them.
+ if((status_flags & check_flags) == check_flags)
+ return FALSE
+
+ return TRUE
/* STUN */
/mob/living/proc/IsStun() //If we're stunned
@@ -17,9 +44,7 @@
amount *= stun_diminish
if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_STUN, amount, ignore_canstun) & COMPONENT_NO_STUN)
return
- if(IS_STUN_IMMUNE(src, ignore_canstun))
- return
- if(absorb_stun(amount, ignore_canstun))
+ if(check_stun_immunity(CANSTUN, ignore_canstun))
return
var/datum/status_effect/incapacitating/stun/S = IsStun()
if(S)
@@ -33,15 +58,13 @@
/mob/living/proc/SetStun(amount, ignore_canstun = FALSE) //Sets remaining duration
if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_STUN, amount, ignore_canstun) & COMPONENT_NO_STUN)
return
- if(IS_STUN_IMMUNE(src, ignore_canstun))
+ if(check_stun_immunity(CANSTUN, ignore_canstun))
return
var/datum/status_effect/incapacitating/stun/S = IsStun()
if(amount <= 0)
if(S)
qdel(S)
else
- if(absorb_stun(amount, ignore_canstun))
- return
if(S)
S.duration = world.time + amount
else
@@ -52,9 +75,7 @@
amount *= stun_diminish
if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_STUN, amount, ignore_canstun) & COMPONENT_NO_STUN)
return
- if(IS_STUN_IMMUNE(src, ignore_canstun))
- return
- if(absorb_stun(amount, ignore_canstun))
+ if(check_stun_immunity(CANSTUN, ignore_canstun))
return
var/datum/status_effect/incapacitating/stun/S = IsStun()
if(S)
@@ -79,9 +100,7 @@
amount *= knockdown_diminish
if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_KNOCKDOWN, amount, ignore_canstun) & COMPONENT_NO_STUN)
return
- if(IS_STUN_IMMUNE(src, ignore_canstun))
- return
- if(absorb_stun(amount, ignore_canstun))
+ if(check_stun_immunity(CANKNOCKDOWN, ignore_canstun))
return
var/datum/status_effect/incapacitating/knockdown/K = IsKnockdown()
if(K)
@@ -95,15 +114,13 @@
/mob/living/proc/SetKnockdown(amount, ignore_canstun = FALSE) //Sets remaining duration
if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_KNOCKDOWN, amount, ignore_canstun) & COMPONENT_NO_STUN)
return
- if(IS_STUN_IMMUNE(src, ignore_canstun))
+ if(check_stun_immunity(CANKNOCKDOWN, ignore_canstun))
return
var/datum/status_effect/incapacitating/knockdown/K = IsKnockdown()
if(amount <= 0)
if(K)
qdel(K)
else
- if(absorb_stun(amount, ignore_canstun))
- return
if(K)
K.duration = world.time + amount
else
@@ -114,9 +131,7 @@
amount *= knockdown_diminish
if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_KNOCKDOWN, amount, ignore_canstun) & COMPONENT_NO_STUN)
return
- if(IS_STUN_IMMUNE(src, ignore_canstun))
- return
- if(absorb_stun(amount, ignore_canstun))
+ if(check_stun_immunity(CANKNOCKDOWN, ignore_canstun))
return
var/datum/status_effect/incapacitating/knockdown/K = IsKnockdown()
if(K)
@@ -140,9 +155,7 @@
/mob/living/proc/Immobilize(amount, ignore_canstun = FALSE) //Can't go below remaining duration
if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_IMMOBILIZE, amount, ignore_canstun) & COMPONENT_NO_STUN)
return
- if(IS_STUN_IMMUNE(src, ignore_canstun))
- return
- if(absorb_stun(amount, ignore_canstun))
+ if(check_stun_immunity(CANSTUN, ignore_canstun))
return
var/datum/status_effect/incapacitating/immobilized/I = IsImmobilized()
if(I)
@@ -154,15 +167,13 @@
/mob/living/proc/SetImmobilized(amount, ignore_canstun = FALSE) //Sets remaining duration
if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_IMMOBILIZE, amount, ignore_canstun) & COMPONENT_NO_STUN)
return
- if(IS_STUN_IMMUNE(src, ignore_canstun))
+ if(check_stun_immunity(CANSTUN, ignore_canstun))
return
var/datum/status_effect/incapacitating/immobilized/I = IsImmobilized()
if(amount <= 0)
if(I)
qdel(I)
else
- if(absorb_stun(amount, ignore_canstun))
- return
if(I)
I.duration = world.time + amount
else
@@ -172,9 +183,7 @@
/mob/living/proc/AdjustImmobilized(amount, ignore_canstun = FALSE) //Adds to remaining duration
if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_IMMOBILIZE, amount, ignore_canstun) & COMPONENT_NO_STUN)
return
- if(IS_STUN_IMMUNE(src, ignore_canstun))
- return
- if(absorb_stun(amount, ignore_canstun))
+ if(check_stun_immunity(CANSTUN, ignore_canstun))
return
var/datum/status_effect/incapacitating/immobilized/I = IsImmobilized()
if(I)
@@ -197,9 +206,7 @@
amount *= paralyze_diminish
if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_PARALYZE, amount, ignore_canstun) & COMPONENT_NO_STUN)
return
- if(IS_STUN_IMMUNE(src, ignore_canstun))
- return
- if(absorb_stun(amount, ignore_canstun))
+ if(check_stun_immunity(CANSTUN|CANKNOCKDOWN, ignore_canstun)) // this requires both can stun and can knockdown
return
var/datum/status_effect/incapacitating/paralyzed/P = IsParalyzed(FALSE)
if(P)
@@ -213,15 +220,13 @@
/mob/living/proc/SetParalyzed(amount, ignore_canstun = FALSE) //Sets remaining duration
if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_PARALYZE, amount, ignore_canstun) & COMPONENT_NO_STUN)
return
- if(IS_STUN_IMMUNE(src, ignore_canstun))
+ if(check_stun_immunity(CANSTUN|CANKNOCKDOWN, ignore_canstun))
return
var/datum/status_effect/incapacitating/paralyzed/P = IsParalyzed(FALSE)
if(amount <= 0)
if(P)
qdel(P)
else
- if(absorb_stun(amount, ignore_canstun))
- return
if(P)
P.duration = world.time + amount
else
@@ -232,9 +237,7 @@
amount *= paralyze_diminish
if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_PARALYZE, amount, ignore_canstun) & COMPONENT_NO_STUN)
return
- if(IS_STUN_IMMUNE(src, ignore_canstun))
- return
- if(absorb_stun(amount, ignore_canstun))
+ if(check_stun_immunity(CANSTUN|CANKNOCKDOWN, ignore_canstun))
return
var/datum/status_effect/incapacitating/paralyzed/P = IsParalyzed(FALSE)
if(P)
@@ -262,9 +265,9 @@
* * ignore_canstun - If TRUE, the mob's resistance to stuns is ignored.
*/
/mob/living/proc/incapacitate(amount, ignore_canstun = FALSE)
- if(IS_STUN_IMMUNE(src, ignore_canstun))
+ if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_INCAPACITATE, amount, ignore_canstun) & COMPONENT_NO_STUN)
return
- if(absorb_stun(amount, ignore_canstun))
+ if(check_stun_immunity(CANSTUN, ignore_canstun))
return
var/datum/status_effect/incapacitating/incapacitated/incapacitated_status_effect = has_status_effect(/datum/status_effect/incapacitating/incapacitated)
if(incapacitated_status_effect)
@@ -280,15 +283,15 @@
* * ignore_canstun - If TRUE, the mob's resistance to stuns is ignored.
*/
/mob/living/proc/set_incapacitated(amount, ignore_canstun = FALSE)
- if(IS_STUN_IMMUNE(src, ignore_canstun))
+ if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_INCAPACITATE, amount, ignore_canstun) & COMPONENT_NO_STUN)
+ return
+ if(check_stun_immunity(CANSTUN, ignore_canstun))
return
var/datum/status_effect/incapacitating/incapacitated/incapacitated_status_effect = has_status_effect(/datum/status_effect/incapacitating/incapacitated)
if(amount <= 0)
if(incapacitated_status_effect)
qdel(incapacitated_status_effect)
else
- if(absorb_stun(amount, ignore_canstun))
- return
if(incapacitated_status_effect)
incapacitated_status_effect.duration = world.time + amount
else
@@ -302,9 +305,9 @@
* * ignore_canstun - If TRUE, the mob's resistance to stuns is ignored.
*/
/mob/living/proc/adjust_incapacitated(amount, ignore_canstun = FALSE) //Adds to remaining duration
- if(IS_STUN_IMMUNE(src, ignore_canstun))
+ if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_INCAPACITATE, amount, ignore_canstun) & COMPONENT_NO_STUN)
return
- if(absorb_stun(amount, ignore_canstun))
+ if(check_stun_immunity(CANSTUN, ignore_canstun))
return
var/datum/status_effect/incapacitating/incapacitated/incapacitated_status_effect = has_status_effect(/datum/status_effect/incapacitating/incapacitated)
if(incapacitated_status_effect)
@@ -348,7 +351,7 @@
/mob/living/proc/Unconscious(amount, ignore_canstun = FALSE) //Can't go below remaining duration
if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_UNCONSCIOUS, amount, ignore_canstun) & COMPONENT_NO_STUN)
return
- if(IS_STUN_IMMUNE(src, ignore_canstun))
+ if(check_stun_immunity(CANUNCONSCIOUS, ignore_canstun))
return
var/datum/status_effect/incapacitating/unconscious/U = IsUnconscious()
if(U)
@@ -360,7 +363,7 @@
/mob/living/proc/SetUnconscious(amount, ignore_canstun = FALSE) //Sets remaining duration
if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_UNCONSCIOUS, amount, ignore_canstun) & COMPONENT_NO_STUN)
return
- if(IS_STUN_IMMUNE(src, ignore_canstun))
+ if(check_stun_immunity(CANUNCONSCIOUS, ignore_canstun))
return
var/datum/status_effect/incapacitating/unconscious/U = IsUnconscious()
if(amount <= 0)
@@ -375,7 +378,7 @@
/mob/living/proc/AdjustUnconscious(amount, ignore_canstun = FALSE) //Adds to remaining duration
if(SEND_SIGNAL(src, COMSIG_LIVING_STATUS_UNCONSCIOUS, amount, ignore_canstun) & COMPONENT_NO_STUN)
return
- if(IS_STUN_IMMUNE(src, ignore_canstun))
+ if(check_stun_immunity(CANUNCONSCIOUS, ignore_canstun))
return
var/datum/status_effect/incapacitating/unconscious/U = IsUnconscious()
if(U)
@@ -463,43 +466,6 @@
/mob/living/proc/IsFrozen()
return has_status_effect(/datum/status_effect/freon)
-
-/* STUN ABSORPTION*/
-/mob/living/proc/add_stun_absorption(key, duration, priority, message, self_message, examine_message)
-//adds a stun absorption with a key, a duration in deciseconds, its priority, and the messages it makes when you're stun/examined, if any
- if(!islist(stun_absorption))
- stun_absorption = list()
- if(stun_absorption[key])
- stun_absorption[key]["end_time"] = world.time + duration
- stun_absorption[key]["priority"] = priority
- stun_absorption[key]["stuns_absorbed"] = 0
- else
- stun_absorption[key] = list("end_time" = world.time + duration, "priority" = priority, "stuns_absorbed" = 0, \
- "visible_message" = message, "self_message" = self_message, "examine_message" = examine_message)
-
-/mob/living/proc/absorb_stun(amount, ignoring_flag_presence)
- if(amount < 0 || stat || ignoring_flag_presence || !islist(stun_absorption))
- return FALSE
- if(!amount)
- amount = 0
- var/priority_absorb_key
- var/highest_priority
- for(var/i in stun_absorption)
- if(stun_absorption[i]["end_time"] > world.time && (!priority_absorb_key || stun_absorption[i]["priority"] > highest_priority))
- priority_absorb_key = stun_absorption[i]
- highest_priority = priority_absorb_key["priority"]
- if(priority_absorb_key)
- if(amount) //don't spam up the chat for continuous stuns
- if(priority_absorb_key["visible_message"] || priority_absorb_key["self_message"])
- if(priority_absorb_key["visible_message"] && priority_absorb_key["self_message"])
- visible_message(span_warning("[src][priority_absorb_key["visible_message"]]"), span_boldwarning("[priority_absorb_key["self_message"]]"))
- else if(priority_absorb_key["visible_message"])
- visible_message(span_warning("[src][priority_absorb_key["visible_message"]]"))
- else if(priority_absorb_key["self_message"])
- to_chat(src, span_boldwarning("[priority_absorb_key["self_message"]]"))
- priority_absorb_key["stuns_absorbed"] += amount
- return TRUE
-
/**
* Adds the passed quirk to the mob
*
@@ -777,5 +743,3 @@
/// Helper to check if we seem to be alive or not
/mob/living/proc/appears_alive()
return health >= 0 && !HAS_TRAIT(src, TRAIT_FAKEDEATH)
-
-#undef IS_STUN_IMMUNE
diff --git a/code/modules/photography/photos/photo.dm b/code/modules/photography/photos/photo.dm
index e83553d35ab5..7bc248d59190 100644
--- a/code/modules/photography/photos/photo.dm
+++ b/code/modules/photography/photos/photo.dm
@@ -43,6 +43,7 @@
if(!isobserver(seen))
continue
set_custom_materials(list(/datum/material/hauntium = 2000))
+ grind_results = list(/datum/reagent/hauntium = 20)
break
/obj/item/photo/update_icon_state()
diff --git a/code/modules/projectiles/guns/magic/staff.dm b/code/modules/projectiles/guns/magic/staff.dm
index bfcd18baa220..4f83561ef645 100644
--- a/code/modules/projectiles/guns/magic/staff.dm
+++ b/code/modules/projectiles/guns/magic/staff.dm
@@ -127,9 +127,9 @@
no_den_usage = 1
school = SCHOOL_FORBIDDEN //this staff is evil. okay? it just is. look at this projectile type list. this is wrong.
- /// Static list of all projectiles we can fire from our staff.
+ /// List of all projectiles we can fire from our staff.
/// Doesn't contain all subtypes of magic projectiles, unlike what it looks like
- var/static/list/allowed_projectile_types = list(
+ var/list/allowed_projectile_types = list(
/obj/projectile/magic/animate,
/obj/projectile/magic/antimagic,
/obj/projectile/magic/arcane_barrage,
@@ -169,6 +169,49 @@
process_fire(user, user, FALSE)
return FALSE
+/**
+ * Staff of chaos given to the wizard upon completing a cheesy grand ritual. Is completely evil and if something
+ * breaks, it's completely intended. Fuck off.
+ * Also can be used by everyone, because why not.
+ */
+/obj/item/gun/magic/staff/chaos/true_wabbajack
+ name = "\proper Wabbajack"
+ desc = "If there is some deity out there, they've definitely skipped their psych appointment before creating this."
+ icon_state = "the_wabbajack"
+ inhand_icon_state = "the_wabbajack"
+ resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF //fuck you
+ max_charges = 999999 //fuck you
+ recharge_rate = 1
+ allow_intruder_use = TRUE
+
+/obj/item/gun/magic/staff/chaos/true_wabbajack/Initialize(mapload)
+ . = ..()
+ allowed_projectile_types |= subtypesof(/obj/projectile/bullet/cannonball)
+ allowed_projectile_types |= subtypesof(/obj/projectile/bullet/rocket)
+ allowed_projectile_types |= subtypesof(/obj/projectile/energy/tesla)
+ allowed_projectile_types |= subtypesof(/obj/projectile/magic)
+ allowed_projectile_types |= subtypesof(/obj/projectile/temp)
+ allowed_projectile_types |= list(
+ /obj/projectile/beam/mindflayer,
+ /obj/projectile/bullet/gyro,
+ /obj/projectile/bullet/honker,
+ /obj/projectile/bullet/mime,
+ /obj/projectile/curse_hand,
+ /obj/projectile/energy/declone,
+ /obj/projectile/energy/electrode,
+ /obj/projectile/energy/net,
+ /obj/projectile/energy/nuclear_particle,
+ /obj/projectile/gravityattract,
+ /obj/projectile/gravitychaos,
+ /obj/projectile/gravityrepulse,
+ /obj/projectile/ion,
+ /obj/projectile/meteor,
+ /obj/projectile/neurotoxin,
+ /obj/projectile/plasma,
+ ) //if you ever try to expand this list, avoid adding bullets/energy projectiles, this ain't supposed to be a gun... unless it's funny
+
+
+
/obj/item/gun/magic/staff/door
name = "staff of door creation"
desc = "An artefact that spits bolts of transformative magic that can create doors in walls."
diff --git a/code/modules/projectiles/projectile/energy/stun.dm b/code/modules/projectiles/projectile/energy/stun.dm
index 261dab29b27c..03cf5f85d84d 100644
--- a/code/modules/projectiles/projectile/energy/stun.dm
+++ b/code/modules/projectiles/projectile/energy/stun.dm
@@ -21,7 +21,7 @@
SEND_SIGNAL(C, COMSIG_LIVING_MINOR_SHOCK)
if(C.dna && C.dna.check_mutation(/datum/mutation/human/hulk))
C.say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" ), forced = "hulk")
- else if((C.status_flags & CANKNOCKDOWN) && !HAS_TRAIT(C, TRAIT_STUNIMMUNE))
+ else if(!C.check_stun_immunity(CANKNOCKDOWN))
addtimer(CALLBACK(C, TYPE_PROC_REF(/mob/living/carbon, do_jitter_animation), 20), 5)
/obj/projectile/energy/electrode/on_range() //to ensure the bolt sparks when it reaches the end of its range if it didn't hit a target yet
diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm
index cd1840b1ab0c..c1243cfbdc68 100644
--- a/code/modules/reagents/chemistry/reagents/other_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm
@@ -3037,3 +3037,42 @@
if(SPT_PROB(10, seconds_per_tick))
carbon_metabolizer.set_heartattack(TRUE)
+
+/datum/reagent/hauntium
+ name = "Hauntium"
+ color = "#3B3B3BA3"
+ description = "An eerie liquid created by purifying the prescence of ghosts. If it happens to get in your body, it starts hurting your soul." //soul as in mood and heart
+ taste_description = "evil spirits"
+ metabolization_rate = 0.75 * REAGENTS_METABOLISM
+ material = /datum/material/hauntium
+ ph = 10
+ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
+
+/datum/reagent/hauntium/expose_obj(obj/exposed_obj, volume) //gives 15 seconds of haunting effect for every unit of it that touches an object
+ . = ..()
+ if(HAS_TRAIT_FROM(exposed_obj, TRAIT_HAUNTED, HAUNTIUM_REAGENT_TRAIT))
+ return
+ exposed_obj.make_haunted(HAUNTIUM_REAGENT_TRAIT, "#f8f8ff")
+ addtimer(CALLBACK(exposed_obj, TYPE_PROC_REF(/atom/movable/, remove_haunted), HAUNTIUM_REAGENT_TRAIT), volume * 20 SECONDS)
+
+/datum/reagent/hauntium/on_mob_metabolize(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
+ to_chat(affected_mob, span_userdanger("You feel an evil presence inside you!"))
+ if(affected_mob.mob_biotypes & MOB_UNDEAD)
+ affected_mob.add_mood_event("morbid_hauntium", /datum/mood_event/morbid_hauntium, name) //8 minutes of slight mood buff if undead or morbid
+ else
+ affected_mob.add_mood_event("hauntium_spirits", /datum/mood_event/hauntium_spirits, name) //8 minutes of mood debuff
+
+/datum/reagent/hauntium/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
+ if(affected_mob.mob_biotypes & MOB_UNDEAD) //if morbid or undead, acts like an addiction-less drug
+ affected_mob.remove_status_effect(/datum/status_effect/jitter)
+ affected_mob.AdjustStun(-50 * REM * seconds_per_tick)
+ affected_mob.AdjustKnockdown(-50 * REM * seconds_per_tick)
+ affected_mob.AdjustUnconscious(-50 * REM * seconds_per_tick)
+ affected_mob.AdjustParalyzed(-50 * REM * seconds_per_tick)
+ affected_mob.AdjustImmobilized(-50 * REM * seconds_per_tick)
+ ..()
+ else
+ affected_mob.adjustOrganLoss(ORGAN_SLOT_HEART, REM * seconds_per_tick) //1 heart damage per tick
+ if(SPT_PROB(10, seconds_per_tick))
+ affected_mob.emote(pick("twitch","choke","shiver","gag"))
+ ..()
diff --git a/code/modules/reagents/chemistry/recipes/others.dm b/code/modules/reagents/chemistry/recipes/others.dm
index 0da89eb08131..ef30d43ce8aa 100644
--- a/code/modules/reagents/chemistry/recipes/others.dm
+++ b/code/modules/reagents/chemistry/recipes/others.dm
@@ -951,3 +951,14 @@
for(var/i in rand(1, created_volume) to created_volume)
new /mob/living/basic/ant(location)
..()
+
+/datum/chemical_reaction/hauntium_solidification
+ required_reagents = list(/datum/reagent/water/holywater = 10, /datum/reagent/hauntium = 20, /datum/reagent/iron = 1)
+ mob_react = FALSE
+// reaction_flags = REACTION_INSTANT monkestation removal
+ reaction_tags = REACTION_TAG_EASY | REACTION_TAG_UNIQUE | REACTION_TAG_OTHER
+
+/datum/chemical_reaction/hauntium_solidification/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume)
+ var/location = get_turf(holder.my_atom)
+ for(var/i in 1 to created_volume)
+ new /obj/item/stack/sheet/hauntium(location)
diff --git a/code/modules/reagents/reagent_containers/syringes.dm b/code/modules/reagents/reagent_containers/syringes.dm
index 5a1da3222245..046c1320a0d3 100644
--- a/code/modules/reagents/reagent_containers/syringes.dm
+++ b/code/modules/reagents/reagent_containers/syringes.dm
@@ -245,6 +245,16 @@
possible_transfer_amounts = list(1,5)
volume = 5
+// Used by monkeys from the elemental plane of bananas. Reagents come from bungo pit, death berries, destroying angel, jupiter cups, and jumping beans.
+/obj/item/reagent_containers/syringe/crude/tribal
+ name = "tribal syringe"
+ desc = "A crudely made syringe. Smells like bananas."
+
+/obj/item/reagent_containers/syringe/crude/tribal/Initialize(mapload)
+ var/toxin_to_get = pick(/datum/reagent/toxin/bungotoxin, /datum/reagent/toxin/coniine, /datum/reagent/toxin/amanitin, /datum/reagent/consumable/liquidelectricity, /datum/reagent/ants)
+ list_reagents = list((toxin_to_get) = 5)
+ return ..()
+
/obj/item/reagent_containers/syringe/spider_extract
name = "spider extract syringe"
desc = "Contains crikey juice - makes any gold core create the most deadly companions in the world."
diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm
index 5bed28d1bb06..da38fb49fcb2 100644
--- a/code/modules/spells/spell.dm
+++ b/code/modules/spells/spell.dm
@@ -188,7 +188,7 @@
if(ishuman(owner))
if(spell_requirements & SPELL_REQUIRES_WIZARD_GARB)
var/mob/living/carbon/human/human_owner = owner
- if(!(human_owner.wear_suit?.clothing_flags & CASTING_CLOTHES))
+ if(!(human_owner.wear_suit?.clothing_flags & CASTING_CLOTHES) && !ismonkey(human_owner)) // Monkeys don't need robes to cast as they are inherently imbued with power from the banana dimension
if(feedback)
to_chat(owner, span_warning("You don't feel strong enough without your robe!"))
return FALSE
@@ -198,12 +198,21 @@
return FALSE
else
- // If the spell requires wizard equipment and we're not a human (can't wear robes or hats), that's just a given
- if(spell_requirements & (SPELL_REQUIRES_WIZARD_GARB|SPELL_REQUIRES_HUMAN))
+ // If you strictly need to be a human, well, goodbye.
+ if(spell_requirements & SPELL_REQUIRES_HUMAN)
if(feedback)
to_chat(owner, span_warning("[src] can only be cast by humans!"))
return FALSE
+ // Otherwise, we can check for contents if they have wizardly apparel. This isn't *quite* perfect, but it'll do, especially since many of the edge cases (gorilla holding a wizard hat) still more or less make sense.
+ if(spell_requirements & SPELL_REQUIRES_WIZARD_GARB)
+ for(var/atom/movable/item in owner.contents)
+ var/obj/item/clothing/clothem = item
+ if(istype(clothem) && clothem.clothing_flags & CASTING_CLOTHES)
+ return TRUE
+ to_chat(owner, span_warning("You don't feel strong enough without your hat!"))
+ return FALSE
+
if(!(spell_requirements & SPELL_CASTABLE_AS_BRAIN) && isbrain(owner))
if(feedback)
to_chat(owner, span_warning("[src] can't be cast in this state!"))
diff --git a/code/modules/spells/spell_types/conjure/cheese.dm b/code/modules/spells/spell_types/conjure/cheese.dm
new file mode 100644
index 000000000000..d9c90d1dbac3
--- /dev/null
+++ b/code/modules/spells/spell_types/conjure/cheese.dm
@@ -0,0 +1,17 @@
+/datum/action/cooldown/spell/conjure/cheese
+ name = "Summon Cheese"
+ desc = "This spell conjures a bunch of cheese wheels. What the hell?"
+ sound = 'sound/magic/summonitems_generic.ogg'
+ button_icon_state = "cheese"
+
+ school = SCHOOL_CONJURATION
+ cooldown_time = 1 MINUTES
+ spell_requirements = null
+
+ invocation = "PL`YR DOT PL`CTM` OOO`BEE G" //player.placeatme 00064B33 9
+ invocation_type = INVOCATION_SHOUT
+ garbled_invocation_prob = 0 //i'd rather it remain like this
+
+ summon_radius = 1
+ summon_amount = 9
+ summon_type = list(/obj/item/food/cheese/wheel)
diff --git a/code/modules/spells/spell_types/conjure/simian.dm b/code/modules/spells/spell_types/conjure/simian.dm
new file mode 100644
index 000000000000..556a78e50127
--- /dev/null
+++ b/code/modules/spells/spell_types/conjure/simian.dm
@@ -0,0 +1,98 @@
+/datum/action/cooldown/spell/conjure/simian
+ name = "Summon Simians"
+ desc = "This spell reaches deep into the elemental plane of bananas (the monkey one, not the clown one), and \
+ summons monkeys and gorillas that will promptly flip out and attack everything in sight. Fun! \
+ Their lesser, easily manipulable minds will be convinced you are one of their allies, but only for a minute. Unless you also are a monkey."
+ button_icon_state = "simian"
+ sound = 'sound/ambience/antag/monkey.ogg'
+
+ school = SCHOOL_CONJURATION
+ cooldown_time = 1.5 MINUTES
+ cooldown_reduction_per_rank = 15 SECONDS
+
+ invocation = "OOGA OOGA OOGA!!!!"
+ invocation_type = INVOCATION_SHOUT
+
+ summon_radius = 2
+ summon_type = list(/mob/living/carbon/human/species/monkey/angry, /mob/living/carbon/human/species/monkey/angry, /mob/living/simple_animal/hostile/gorilla/lesser)
+ summon_amount = 4
+
+/datum/action/cooldown/spell/conjure/simian/level_spell(bypass_cap)
+ . = ..()
+ summon_amount++ // MORE, MOOOOORE
+ if(spell_level == spell_max_level) // We reward the faithful.
+ summon_type = list(/mob/living/carbon/human/species/monkey/angry, /mob/living/simple_animal/hostile/gorilla)
+ spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC // Max level lets you cast it naked, for monkey larp.
+ to_chat(owner, span_notice("Your simian power has reached maximum capacity! You can now cast this spell naked, and you will create adult Gorillas with each cast."))
+
+/datum/action/cooldown/spell/conjure/simian/cast(atom/cast_on)
+ . = ..()
+ var/mob/living/cast_mob = cast_on
+ if(!istype(cast_mob))
+ return
+ if(FACTION_MONKEY in cast_mob.faction)
+ return
+ cast_mob.faction |= FACTION_MONKEY
+ addtimer(CALLBACK(src, PROC_REF(remove_monky_faction), cast_mob), 1 MINUTES)
+
+/datum/action/cooldown/spell/conjure/simian/proc/remove_monky_faction(mob/cast_mob)
+ cast_mob.faction -= FACTION_MONKEY
+
+/datum/action/cooldown/spell/conjure/simian/post_summon(atom/summoned_object, atom/cast_on)
+ var/mob/living/alive_dude = summoned_object
+ alive_dude.faction |= list(FACTION_MONKEY)
+ if(ismonkey(alive_dude))
+ equip_monky(alive_dude)
+ return
+
+/** Equips summoned monky with gear depending on how the roll plays out, affected by spell lvl.
+ * Can give them bananas and garland or gatfruit and axes. Monkeys are comically inept, which balances out what might otherwise be a little crazy.
+ */
+/datum/action/cooldown/spell/conjure/simian/proc/equip_monky(mob/living/carbon/human/species/monkey/summoned_monkey)
+
+ // These are advanced monkeys we're talking about
+ var/datum/ai_controller/monkey/monky_controller = summoned_monkey.ai_controller
+ monky_controller.set_trip_mode(mode = FALSE)
+ summoned_monkey.fully_replace_character_name(summoned_monkey.real_name, "primal " + summoned_monkey.name)
+
+ // Monkeys get a random gear tier, but it's more likely to be good the more leveled the spell is!
+ var/monkey_gear_tier = rand(0, 5) + (spell_level - 1)
+ monkey_gear_tier = min(monkey_gear_tier, 5)
+
+ // Monkey weapons, ordered by tier
+ var/static/list/monky_weapon = list(
+ list(/obj/item/food/grown/banana, /obj/item/grown/bananapeel),
+ list(/obj/item/tailclub, /obj/item/knife/combat/bone),
+ list(/obj/item/shovel/serrated, /obj/item/spear/bamboospear),
+ list(/obj/item/spear/bonespear, /obj/item/fireaxe/boneaxe),
+ list(/obj/item/gun/syringe/blowgun, /obj/item/gun/ballistic/revolver),
+ )
+
+ var/list/options = monky_weapon[min(monkey_gear_tier, length(monky_weapon))]
+
+ var/obj/item/weapon
+ if(monkey_gear_tier != 0)
+ var/weapon_type = pick(options)
+ weapon = new weapon_type(summoned_monkey)
+ summoned_monkey.equip_to_slot_or_del(weapon, ITEM_SLOT_HANDS)
+
+ // Load the ammo
+ if(istype(weapon, /obj/item/gun/syringe/blowgun))
+ var/obj/item/reagent_containers/syringe/crude/tribal/syring = new(summoned_monkey)
+ weapon.attackby(syring, summoned_monkey)
+
+ // Wield the weapon!
+ if(is_type_in_list(weapon, list(/obj/item/spear, /obj/item/fireaxe)))
+ weapon.attack_self(summoned_monkey)
+
+ // Fashionable ape wear, organised by tier
+ var/list/static/monky_hats = list(
+ null, // nothin here
+ /obj/item/clothing/head/costume/garland,
+ /obj/item/clothing/head/helmet/durathread,
+ /obj/item/clothing/head/helmet/skull,
+ )
+
+ var/stylish_monkey_hat = monky_hats[min(monkey_gear_tier, length(monky_hats))]
+ if(!isnull(stylish_monkey_hat))
+ summoned_monkey.equip_to_slot_or_del(new stylish_monkey_hat(summoned_monkey), ITEM_SLOT_HEAD)
diff --git a/code/modules/spells/spell_types/self/mutate.dm b/code/modules/spells/spell_types/self/mutate.dm
index 5368ce0b7ab6..612a95164e5d 100644
--- a/code/modules/spells/spell_types/self/mutate.dm
+++ b/code/modules/spells/spell_types/self/mutate.dm
@@ -38,12 +38,25 @@
/datum/action/cooldown/spell/apply_mutations/mutate
name = "Mutate"
- desc = "This spell causes you to turn into a hulk and gain laser vision for a short while."
+ desc = "This spell causes you to turn into a gigantic hulk and gain laser vision for a short while. Unlike the lesser nonmagical version, it works on non-humans and mantains hand dexterity as well!"
cooldown_time = 60 SECONDS //monkestation edit: from 40 to 60 seconds
cooldown_reduction_per_rank = 5 SECONDS //monkestation edit: from 2.5 to 5 seconds
invocation = "BIRUZ BENNAR"
invocation_type = INVOCATION_SHOUT
- mutations_to_add = list(/datum/mutation/human/laser_eyes, /datum/mutation/human/hulk)
+ mutations_to_add = list(/datum/mutation/human/laser_eyes, /datum/mutation/human/hulk/wizardly, /datum/mutation/human/gigantism)
mutation_duration = 30 SECONDS
+
+/datum/action/cooldown/spell/apply_mutations/mutate/cast(mob/living/carbon/human/cast_on)
+ ..()
+ /*if(HAS_TRAIT(cast_on, TRAIT_USES_SKINTONES) || HAS_TRAIT(cast_on, TRAIT_MUTANT_COLORS))
+ return*/ //monkestation removal, we dont have this refactor yet
+ // Our caster has a species that doesn't greenify when hulked, so we will do it manually.
+ cast_on.add_atom_colour("#00FF00", TEMPORARY_COLOUR_PRIORITY)
+
+/datum/action/cooldown/spell/apply_mutations/mutate/remove_mutations(mob/living/carbon/human/cast_on)
+ if(QDELETED(cast_on) || !is_valid_target(cast_on))
+ return
+
+ cast_on.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY)
diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm
index 4ad8b3dc449e..4143f1b8e5bc 100644
--- a/code/modules/unit_tests/_unit_tests.dm
+++ b/code/modules/unit_tests/_unit_tests.dm
@@ -208,6 +208,7 @@
#include "stomach.dm"
#include "strange_reagent.dm"
#include "strippable.dm"
+#include "stuns.dm"
#include "subsystem_init.dm"
#include "suit_storage_icons.dm"
#include "surgeries.dm"
diff --git a/code/modules/unit_tests/stuns.dm b/code/modules/unit_tests/stuns.dm
new file mode 100644
index 000000000000..68110e72e559
--- /dev/null
+++ b/code/modules/unit_tests/stuns.dm
@@ -0,0 +1,72 @@
+/// Tests stun and the canstun flag
+/datum/unit_test/stun
+
+/datum/unit_test/stun/Run()
+ var/mob/living/carbon/human/gets_stunned = allocate(/mob/living/carbon/human/consistent)
+
+ gets_stunned.Stun(1 SECONDS)
+ TEST_ASSERT(gets_stunned.IsStun(), "Stun() failed to apply stun")
+
+ gets_stunned.SetStun(0 SECONDS)
+ TEST_ASSERT(!gets_stunned.IsStun(), "SetStun(0) failed to clear stun")
+
+ gets_stunned.status_flags &= ~CANSTUN
+ gets_stunned.Stun(1 SECONDS)
+ TEST_ASSERT(!gets_stunned.IsStun(), "Stun() stunned despite not having CANSTUN flag")
+
+/// Tests knockdown and the canknockdown flag
+/datum/unit_test/knockdown
+
+/datum/unit_test/knockdown/Run()
+ var/mob/living/carbon/human/gets_knockdown = allocate(/mob/living/carbon/human/consistent)
+
+ gets_knockdown.Knockdown(1 SECONDS)
+ TEST_ASSERT(gets_knockdown.IsKnockdown(), "Knockdown() failed to apply knockdown")
+
+ gets_knockdown.SetKnockdown(0 SECONDS)
+ TEST_ASSERT(!gets_knockdown.IsKnockdown(), "SetKnockdown(0) failed to clear knockdown")
+
+ gets_knockdown.status_flags &= ~CANKNOCKDOWN
+ gets_knockdown.Knockdown(1 SECONDS)
+ TEST_ASSERT(!gets_knockdown.IsKnockdown(), "Knockdown() knocked over despite not having CANKNOCKDOWN flag")
+
+/// Tests paralyze and stuns that have two flags checked (in this case, canstun and canknockdown)
+/datum/unit_test/paralyze
+
+/datum/unit_test/paralyze/Run()
+ var/mob/living/carbon/human/gets_paralyzed = allocate(/mob/living/carbon/human/consistent)
+
+ gets_paralyzed.Paralyze(1 SECONDS)
+ TEST_ASSERT(gets_paralyzed.IsParalyzed(), "Paralyze() failed to apply paralyze")
+
+ gets_paralyzed.SetParalyzed(0 SECONDS)
+ TEST_ASSERT(!gets_paralyzed.IsParalyzed(), "SetParalyzed(0) failed to clear paralyze")
+
+ gets_paralyzed.status_flags &= ~CANSTUN // paralyze needs both CANSTUN and CANKNOCKDOWN to succeed
+ gets_paralyzed.Paralyze(1 SECONDS)
+ TEST_ASSERT(!gets_paralyzed.IsParalyzed(), "Paralyze() paralyzed a mob despite not having CANSTUN flag (but still having CANKNOCKDOWN)")
+
+/// Tests unconsciousness and the canunconscious flag
+/datum/unit_test/unconsciousness
+
+/datum/unit_test/unconsciousness/Run()
+ var/mob/living/carbon/human/gets_unconscious = allocate(/mob/living/carbon/human/consistent)
+
+ gets_unconscious.Unconscious(1 SECONDS)
+ TEST_ASSERT(gets_unconscious.IsUnconscious(), "Unconscious() failed to apply unconsciousness")
+
+ gets_unconscious.SetUnconscious(0 SECONDS)
+ TEST_ASSERT(!gets_unconscious.IsUnconscious(), "SetUnconscious(0) failed to clear unconsciousness")
+
+ gets_unconscious.status_flags &= ~CANUNCONSCIOUS
+ gets_unconscious.Unconscious(1 SECONDS)
+ TEST_ASSERT(!gets_unconscious.IsUnconscious(), "Unconscious() knocked unconscious despite not having CANUNCONSCIOUS flag")
+
+/// Tests for stun absorption
+/datum/unit_test/stun_absorb
+
+/datum/unit_test/stun_absorb/Run()
+ var/mob/living/carbon/human/doesnt_get_stunned = allocate(/mob/living/carbon/human/consistent)
+ doesnt_get_stunned.add_stun_absorption(source = TRAIT_SOURCE_UNIT_TESTS)
+ doesnt_get_stunned.Stun(1 SECONDS)
+ TEST_ASSERT(!doesnt_get_stunned.IsStun(), "Stun() stunned despite having stun absorption")
diff --git a/icons/effects/96x96.dmi b/icons/effects/96x96.dmi
index 1c59abec3b28..a0d7ccfc35c3 100644
Binary files a/icons/effects/96x96.dmi and b/icons/effects/96x96.dmi differ
diff --git a/icons/hud/screen_gen.dmi b/icons/hud/screen_gen.dmi
index d80a2dbe762a..1484e3f4042a 100644
Binary files a/icons/hud/screen_gen.dmi and b/icons/hud/screen_gen.dmi differ
diff --git a/icons/mob/actions/actions_spells.dmi b/icons/mob/actions/actions_spells.dmi
index 9d657c6b88cf..39ea58104173 100644
Binary files a/icons/mob/actions/actions_spells.dmi and b/icons/mob/actions/actions_spells.dmi differ
diff --git a/icons/mob/clothing/back.dmi b/icons/mob/clothing/back.dmi
index 96e97899c66e..3421f1d485fd 100644
Binary files a/icons/mob/clothing/back.dmi and b/icons/mob/clothing/back.dmi differ
diff --git a/icons/mob/inhands/weapons/staves_lefthand.dmi b/icons/mob/inhands/weapons/staves_lefthand.dmi
index 82c552b50382..d75ee393d1a5 100644
Binary files a/icons/mob/inhands/weapons/staves_lefthand.dmi and b/icons/mob/inhands/weapons/staves_lefthand.dmi differ
diff --git a/icons/mob/inhands/weapons/staves_righthand.dmi b/icons/mob/inhands/weapons/staves_righthand.dmi
index 769709f53da1..8143819f807a 100644
Binary files a/icons/mob/inhands/weapons/staves_righthand.dmi and b/icons/mob/inhands/weapons/staves_righthand.dmi differ
diff --git a/icons/obj/library.dmi b/icons/obj/library.dmi
index b079fd32949b..3b79434df294 100644
Binary files a/icons/obj/library.dmi and b/icons/obj/library.dmi differ
diff --git a/icons/obj/weapons/guns/magic.dmi b/icons/obj/weapons/guns/magic.dmi
index 3a97682ef661..92210a4e0542 100644
Binary files a/icons/obj/weapons/guns/magic.dmi and b/icons/obj/weapons/guns/magic.dmi differ
diff --git a/icons/obj/wizard.dmi b/icons/obj/wizard.dmi
index 6b8309b90382..7fe2af730582 100644
Binary files a/icons/obj/wizard.dmi and b/icons/obj/wizard.dmi differ
diff --git a/monkestation/code/modules/antagonists/wizard/equipment/artefact.dm b/monkestation/code/modules/antagonists/wizard/equipment/artefact.dm
index b4876facf8e0..c36be4bbb47c 100644
--- a/monkestation/code/modules/antagonists/wizard/equipment/artefact.dm
+++ b/monkestation/code/modules/antagonists/wizard/equipment/artefact.dm
@@ -43,7 +43,7 @@
var/static/list/spell_list = list(/datum/action/cooldown/spell/rod_form, /datum/action/cooldown/spell/aoe/magic_missile,
/datum/action/cooldown/spell/emp/disable_tech, /datum/action/cooldown/spell/aoe/repulse/wizard,
/datum/action/cooldown/spell/timestop, /datum/action/cooldown/spell/forcewall, /datum/action/cooldown/spell/conjure/the_traps,
- /datum/action/cooldown/spell/conjure/bee, /datum/action/cooldown/spell/conjure/lesser_summonapes,
+ /datum/action/cooldown/spell/conjure/bee, /datum/action/cooldown/spell/conjure/simian,
/datum/action/cooldown/spell/teleport/radius_turf/blink)
COOLDOWN_DECLARE(armor_cooldown) //unsure if I should use a world.time instead of this
diff --git a/monkestation/code/modules/antagonists/wizard/equipment/spellbook_entries/defensive.dm b/monkestation/code/modules/antagonists/wizard/equipment/spellbook_entries/defensive.dm
index 5f7c02ee5503..27e95736d103 100644
--- a/monkestation/code/modules/antagonists/wizard/equipment/spellbook_entries/defensive.dm
+++ b/monkestation/code/modules/antagonists/wizard/equipment/spellbook_entries/defensive.dm
@@ -5,13 +5,6 @@
category = "Defensive"
cost = 1
-/datum/spellbook_entry/lesser_summon_apes
- name = "Lesser Summon Apes"
- desc = "This spell conjures a group of hostile apes, they WILL be hostile to you."
- spell_type = /datum/action/cooldown/spell/conjure/lesser_summonapes
- category = "Defensive"
- cost = 2
-
/datum/spellbook_entry/item/reactive_talisman
name = "Reactive Talisman"
desc = "An enchanted talisman that has a chance to cast a spell if it's wearer is hit."
diff --git a/monkestation/code/modules/spells/spell_types/conjure/lesser_summon_apes.dm b/monkestation/code/modules/spells/spell_types/conjure/lesser_summon_apes.dm
deleted file mode 100644
index 3afc05e5a875..000000000000
--- a/monkestation/code/modules/spells/spell_types/conjure/lesser_summon_apes.dm
+++ /dev/null
@@ -1,16 +0,0 @@
-/datum/action/cooldown/spell/conjure/lesser_summonapes
- name = "Lesser Summon Apes"
- desc = "This spell conjures a group of hostile apes, they WILL be hostile to you."
- invocation = "MON'KE"
- button_icon = 'icons/mob/actions/actions_silicon.dmi'
- button_icon_state = "monkey_down"
- invocation_type = INVOCATION_SHOUT
- summon_radius = 2
- sound = 'sound/creatures/monkey/monkey_screech_1.ogg'
- cooldown_time = 2 MINUTES
- cooldown_reduction_per_rank = 15 SECONDS
-
- summon_type = list(/mob/living/carbon/human/species/monkey/angry, /mob/living/simple_animal/hostile/gorilla)
-
- summon_lifespan = 90 SECONDS
- summon_amount = 4
diff --git a/tgstation.dme b/tgstation.dme
index 663fb1bbe32a..3f4c30ea187d 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1439,6 +1439,7 @@
#include "code\datums\mood_events\drug_events.dm"
#include "code\datums\mood_events\generic_negative_events.dm"
#include "code\datums\mood_events\generic_positive_events.dm"
+#include "code\datums\mood_events\morbid_events.dm"
#include "code\datums\mood_events\needs_events.dm"
#include "code\datums\mutations\_combined.dm"
#include "code\datums\mutations\_mutations.dm"
@@ -1502,6 +1503,7 @@
#include "code\datums\status_effects\song_effects.dm"
#include "code\datums\status_effects\stacking_effect.dm"
#include "code\datums\status_effects\wound_effects.dm"
+#include "code\datums\status_effects\buffs\stun_asorption.dm"
#include "code\datums\status_effects\debuffs\blindness.dm"
#include "code\datums\status_effects\debuffs\choke.dm"
#include "code\datums\status_effects\debuffs\confusion.dm"
@@ -2087,6 +2089,7 @@
#include "code\game\objects\items\granters\magic\mindswap.dm"
#include "code\game\objects\items\granters\magic\sacred_flame.dm"
#include "code\game\objects\items\granters\magic\smoke.dm"
+#include "code\game\objects\items\granters\magic\summon_cheese.dm"
#include "code\game\objects\items\granters\magic\summon_item.dm"
#include "code\game\objects\items\granters\martial_arts\_martial_arts.dm"
#include "code\game\objects\items\granters\martial_arts\cqc.dm"
@@ -2821,6 +2824,7 @@
#include "code\modules\antagonists\wizard\equipment\spellbook_entries\mobility.dm"
#include "code\modules\antagonists\wizard\equipment\spellbook_entries\offensive.dm"
#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"
@@ -4956,6 +4960,7 @@
#include "code\modules\spells\spell_types\conjure\_conjure.dm"
#include "code\modules\spells\spell_types\conjure\bees.dm"
#include "code\modules\spells\spell_types\conjure\carp.dm"
+#include "code\modules\spells\spell_types\conjure\cheese.dm"
#include "code\modules\spells\spell_types\conjure\constructs.dm"
#include "code\modules\spells\spell_types\conjure\creatures.dm"
#include "code\modules\spells\spell_types\conjure\cult_turfs.dm"
@@ -4964,6 +4969,7 @@
#include "code\modules\spells\spell_types\conjure\invisible_wall.dm"
#include "code\modules\spells\spell_types\conjure\link_worlds.dm"
#include "code\modules\spells\spell_types\conjure\presents.dm"
+#include "code\modules\spells\spell_types\conjure\simian.dm"
#include "code\modules\spells\spell_types\conjure\soulstone.dm"
#include "code\modules\spells\spell_types\conjure\the_traps.dm"
#include "code\modules\spells\spell_types\conjure_item\_conjure_item.dm"
@@ -6108,7 +6114,6 @@
#include "monkestation\code\modules\skyrat_snipes\vending_machines\vendors.dm"
#include "monkestation\code\modules\slash_commands\world_topics.dm"
#include "monkestation\code\modules\spells\spell_types\aoe_spell\mind_swap.dm"
-#include "monkestation\code\modules\spells\spell_types\conjure\lesser_summon_apes.dm"
#include "monkestation\code\modules\spells\spell_types\conjure_item\summon_mjollnir.dm"
#include "monkestation\code\modules\spells\spell_types\pointed\smite.dm"
#include "monkestation\code\modules\store\admin\admin_coin_modification.dm"