diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm
index b883a54209..a3e5423753 100644
--- a/code/__DEFINES/antagonists.dm
+++ b/code/__DEFINES/antagonists.dm
@@ -63,6 +63,16 @@
#define CONTRACT_UPLINK_PAGE_CONTRACTS "CONTRACTS"
#define CONTRACT_UPLINK_PAGE_HUB "HUB"
+
+///Heretics --
+#define IS_HERETIC(mob) (mob.mind?.has_antag_datum(/datum/antagonist/heretic))
+
+#define PATH_SIDE "Side"
+
+#define PATH_ASH "Ash"
+#define PATH_RUST "Rust"
+#define PATH_FLESH "Flesh"
+
//Overthrow time to update heads obj
#define OBJECTIVE_UPDATING_TIME 300
@@ -91,4 +101,4 @@
#define BLOB_REROLL_TIME 2400 // blob gets a free reroll every X time
#define BLOB_SPREAD_COST 4
#define BLOB_ATTACK_REFUND 2 //blob refunds this much if it attacks and doesn't spread
-#define BLOB_REFLECTOR_COST 15
\ No newline at end of file
+#define BLOB_REFLECTOR_COST 15
diff --git a/code/__DEFINES/atom_hud.dm b/code/__DEFINES/atom_hud.dm
index 01f14c748e..386473a3ae 100644
--- a/code/__DEFINES/atom_hud.dm
+++ b/code/__DEFINES/atom_hud.dm
@@ -60,6 +60,7 @@
#define ANTAG_HUD_BROTHER 23
#define ANTAG_HUD_BLOODSUCKER 24
#define ANTAG_HUD_FUGITIVE 25
+#define ANTAG_HUD_HERETIC 26
// Notification action types
#define NOTIFY_JUMP "jump"
diff --git a/code/__DEFINES/footsteps.dm b/code/__DEFINES/footsteps.dm
index 2dd66b9833..215e287adf 100644
--- a/code/__DEFINES/footsteps.dm
+++ b/code/__DEFINES/footsteps.dm
@@ -7,6 +7,8 @@
#define FOOTSTEP_WATER "water"
#define FOOTSTEP_LAVA "lava"
#define FOOTSTEP_MEAT "meat"
+#define FOOTSTEP_RUST "rust"
+
//barefoot sounds
#define FOOTSTEP_WOOD_BAREFOOT "woodbarefoot"
#define FOOTSTEP_WOOD_CLAW "woodclaw"
@@ -91,7 +93,9 @@ GLOBAL_LIST_INIT(footstep, list(
'sound/effects/footstep/lava2.ogg',
'sound/effects/footstep/lava3.ogg'), 100, 0),
FOOTSTEP_MEAT = list(list(
- 'sound/effects/meatslap.ogg'), 100, 0)
+ 'sound/effects/meatslap.ogg'), 100, 0),
+ FOOTSTEP_RUST = list(list(
+ 'sound/effects/footstep/rustystep1.ogg'), 100, 0)
))
//bare footsteps lists
@@ -135,7 +139,9 @@ GLOBAL_LIST_INIT(barefootstep, list(
'sound/effects/footstep/lava2.ogg',
'sound/effects/footstep/lava3.ogg'), 100, 0),
FOOTSTEP_MEAT = list(list(
- 'sound/effects/meatslap.ogg'), 100, 0)
+ 'sound/effects/meatslap.ogg'), 100, 0),
+ FOOTSTEP_RUST = list(list(
+ 'sound/effects/footstep/rustystep1.ogg'), 100, 0)
))
//claw footsteps lists
@@ -179,7 +185,9 @@ GLOBAL_LIST_INIT(clawfootstep, list(
'sound/effects/footstep/lava2.ogg',
'sound/effects/footstep/lava3.ogg'), 100, 0),
FOOTSTEP_MEAT = list(list(
- 'sound/effects/meatslap.ogg'), 100, 0)
+ 'sound/effects/meatslap.ogg'), 100, 0),
+ FOOTSTEP_RUST = list(list(
+ 'sound/effects/footstep/rustystep1.ogg'), 100, 0)
))
//heavy footsteps list
@@ -197,5 +205,7 @@ GLOBAL_LIST_INIT(heavyfootstep, list(
'sound/effects/footstep/lava2.ogg',
'sound/effects/footstep/lava3.ogg'), 100, 0),
FOOTSTEP_MEAT = list(list(
- 'sound/effects/meatslap.ogg'), 100, 0)
+ 'sound/effects/meatslap.ogg'), 100, 0),
+ FOOTSTEP_RUST = list(list(
+ 'sound/effects/footstep/rustystep1.ogg'), 150, 2)
))
diff --git a/code/__DEFINES/maths.dm b/code/__DEFINES/maths.dm
index 24726b8058..370f869c48 100644
--- a/code/__DEFINES/maths.dm
+++ b/code/__DEFINES/maths.dm
@@ -46,6 +46,8 @@
// Cosecant
#define CSC(x) (1 / sin(x))
+#define ATAN2(x, y) ( !(x) && !(y) ? 0 : (y) >= 0 ? arccos((x) / sqrt((x)*(x) + (y)*(y))) : -arccos((x) / sqrt((x)*(x) + (y)*(y))) )
+
// Greatest Common Divisor - Euclid's algorithm
/proc/Gcd(a, b)
return b ? Gcd(b, (a) % (b)) : a
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index 782095f883..6ddd269b4b 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -332,3 +332,5 @@
/// If you examine the same atom twice in this timeframe, we call examine_more() instead of examine()
#define EXAMINE_MORE_TIME 1 SECONDS
+
+#define SILENCE_RANGED_MESSAGE (1<<0)
diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm
index 7fabb74157..d7487eb54b 100644
--- a/code/__DEFINES/role_preferences.dm
+++ b/code/__DEFINES/role_preferences.dm
@@ -17,6 +17,7 @@
#define ROLE_ALIEN "xenomorph"
#define ROLE_PAI "pAI"
#define ROLE_CULTIST "cultist"
+#define ROLE_HERETIC "Heretic"
#define ROLE_BLOB "blob"
#define ROLE_NINJA "space ninja"
#define ROLE_MONKEY "monkey"
@@ -64,6 +65,7 @@ GLOBAL_LIST_INIT(special_roles, list(
ROLE_INTERNAL_AFFAIRS = /datum/game_mode/traitor/internal_affairs,
ROLE_SENTIENCE,
ROLE_GANG = /datum/game_mode/gang,
+ ROLE_HERETIC = /datum/game_mode/heretics,
ROLE_BLOODSUCKER = /datum/game_mode/bloodsucker
//ROLE_MONSTERHUNTER Disabled for now
))
diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm
index 9a3b5d55f4..bfff1dc629 100644
--- a/code/__DEFINES/status_effects.dm
+++ b/code/__DEFINES/status_effects.dm
@@ -111,6 +111,10 @@
#define STATUS_EFFECT_LIMP /datum/status_effect/limp //For when you have a busted leg (or two!) and want additional slowdown when walking on that leg
+#define STATUS_EFFECT_AMOK /datum/status_effect/amok //Makes the target automatically strike out at adjecent non-heretics.
+
+#define STATUS_EFFECT_CLOUDSTRUCK /datum/status_effect/cloudstruck //blinds and applies an overlay.
+
/// shoves inflict this to indicate the next shove while this is in effect should disarm guns
#define STATUS_EFFECT_OFF_BALANCE /datum/status_effect/off_balance
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 3e5b27d376..d7f0f7308d 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -104,7 +104,9 @@
#define TRAIT_RESISTCOLD "resist_cold"
#define TRAIT_RESISTHIGHPRESSURE "resist_high_pressure"
#define TRAIT_RESISTLOWPRESSURE "resist_low_pressure"
+#define TRAIT_BOMBIMMUNE "bomb_immunity"
#define TRAIT_RADIMMUNE "rad_immunity"
+#define TRAIT_GENELESS "geneless"
#define TRAIT_VIRUSIMMUNE "virus_immunity"
#define TRAIT_PIERCEIMMUNE "pierce_immunity"
#define TRAIT_NODISMEMBER "dismember_immunity"
@@ -256,6 +258,7 @@
#define CURSED_ITEM_TRAIT "cursed-item" // The item is magically cursed
#define ABSTRACT_ITEM_TRAIT "abstract-item"
#define STATUS_EFFECT_TRAIT "status-effect"
+#define CLOTHING_TRAIT "clothing"
#define ROUNDSTART_TRAIT "roundstart" //cannot be removed without admin intervention
#define GHOSTROLE_TRAIT "ghostrole"
#define APHRO_TRAIT "aphro"
@@ -310,4 +313,4 @@
#define ACTIVE_BLOCK_TRAIT "active_block"
/// This trait is added by the parry system.
#define ACTIVE_PARRY_TRAIT "active_parry"
-#define STICKY_NODROP "sticky-nodrop" //sticky nodrop sounds like a bad soundcloud rapper's name
\ No newline at end of file
+#define STICKY_NODROP "sticky-nodrop" //sticky nodrop sounds like a bad soundcloud rapper's name
diff --git a/code/__HELPERS/cmp.dm b/code/__HELPERS/cmp.dm
index a688d20451..4ee622961a 100644
--- a/code/__HELPERS/cmp.dm
+++ b/code/__HELPERS/cmp.dm
@@ -128,6 +128,10 @@ GLOBAL_VAR_INIT(cmp_field, "name")
else
return B.required_temp - A.required_temp //return hottest
+
+/proc/cmp_mob_realname_dsc(mob/A,mob/B)
+ return sorttext(A.real_name,B.real_name)
+
/proc/cmp_job_display_asc(datum/job/A, datum/job/B)
return A.display_order - B.display_order
@@ -135,4 +139,4 @@ GLOBAL_VAR_INIT(cmp_field, "name")
return sorttext(initial(b.name),initial(a.name))
/proc/cmp_typepaths_asc(A, B)
- return sorttext("[B]","[A]")
\ No newline at end of file
+ return sorttext("[B]","[A]")
diff --git a/code/_globalvars/game_modes.dm b/code/_globalvars/game_modes.dm
index 01a34c2ab7..960e1f8d59 100644
--- a/code/_globalvars/game_modes.dm
+++ b/code/_globalvars/game_modes.dm
@@ -9,4 +9,8 @@ GLOBAL_DATUM(start_state, /datum/station_state) // Used in round-end report
//TODO clear this one up too
-GLOBAL_DATUM(cult_narsie, /obj/singularity/narsie/large/cult)
\ No newline at end of file
+GLOBAL_DATUM(cult_narsie, /obj/singularity/narsie/large/cult)
+
+
+///We want reality_smash_tracker to exist only once and be accesable from anywhere.
+GLOBAL_DATUM_INIT(reality_smash_track, /datum/reality_smash_tracker, new)
diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm
index 8d8b8820b2..605b20a4f3 100644
--- a/code/_globalvars/traits.dm
+++ b/code/_globalvars/traits.dm
@@ -37,7 +37,9 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_RESISTCOLD" = TRAIT_RESISTCOLD,
"TRAIT_RESISTHIGHPRESSURE" = TRAIT_RESISTHIGHPRESSURE,
"TRAIT_RESISTLOWPRESSURE" = TRAIT_RESISTLOWPRESSURE,
+ "TRAIT_BOMBIMMUNE" = TRAIT_BOMBIMMUNE,
"TRAIT_RADIMMUNE" = TRAIT_RADIMMUNE,
+ "TRAIT_GENELESS" = TRAIT_GENELESS,
"TRAIT_VIRUSIMMUNE" = TRAIT_VIRUSIMMUNE,
"TRAIT_PIERCEIMMUNE" = TRAIT_PIERCEIMMUNE,
"TRAIT_NODISMEMBER" = TRAIT_NODISMEMBER,
diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm
index 5765f291e9..cc9c067b5d 100644
--- a/code/controllers/configuration/entries/game_options.dm
+++ b/code/controllers/configuration/entries/game_options.dm
@@ -98,6 +98,11 @@
config_entry_value = 6
min_val = 1
+/datum/config_entry/number/ecult_scaling_coeff //how much does the amount of players get divided by to determine e_cult
+ config_entry_value = 6
+ integer = FALSE
+ min_val = 1
+
/datum/config_entry/number/security_scaling_coeff //how much does the amount of players get divided by to determine open security officer positions
config_entry_value = 8
min_val = 1
diff --git a/code/datums/hud.dm b/code/datums/hud.dm
index c1811fb9b3..1c23d5f2a0 100644
--- a/code/datums/hud.dm
+++ b/code/datums/hud.dm
@@ -28,7 +28,8 @@ GLOBAL_LIST_INIT(huds, list(
ANTAG_HUD_CLOCKWORK = new/datum/atom_hud/antag(),
ANTAG_HUD_BROTHER = new/datum/atom_hud/antag/hidden(),
ANTAG_HUD_BLOODSUCKER = new/datum/atom_hud/antag/bloodsucker(),
- ANTAG_HUD_FUGITIVE = new/datum/atom_hud/antag()
+ ANTAG_HUD_FUGITIVE = new/datum/atom_hud/antag(),
+ ANTAG_HUD_HERETIC = new/datum/atom_hud/antag/hidden()
))
/datum/atom_hud
diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm
index 787d6375a7..38b004a657 100644
--- a/code/datums/mood_events/generic_negative_events.dm
+++ b/code/datums/mood_events/generic_negative_events.dm
@@ -162,6 +162,11 @@
mood_change = -8
timeout = 3 MINUTES
+/datum/mood_event/gates_of_mansus
+ description = "LIVING IN A PERFORMANCE IS WORSE THAN DEATH\n"
+ mood_change = -25
+ timeout = 4 MINUTES
+
//These are unused so far but I want to remember them to use them later
/datum/mood_event/cloned_corpse
diff --git a/code/datums/mood_events/generic_positive_events.dm b/code/datums/mood_events/generic_positive_events.dm
index 8f98917ed3..2f4d702849 100644
--- a/code/datums/mood_events/generic_positive_events.dm
+++ b/code/datums/mood_events/generic_positive_events.dm
@@ -70,6 +70,11 @@
mood_change = 40 //maybe being a cultist isnt that bad after all
hidden = TRUE
+/datum/mood_event/heretics
+ description = "THE HIGHER I RISE , THE MORE I SEE.\n"
+ mood_change = 12 //maybe being a cultist isnt that bad after all
+ hidden = TRUE
+
/datum/mood_event/family_heirloom
description = "My family heirloom is safe with me.\n"
mood_change = 1
diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm
index 6e59d4fa2c..bce3fff4d1 100644
--- a/code/datums/status_effects/debuffs.dm
+++ b/code/datums/status_effects/debuffs.dm
@@ -403,6 +403,195 @@
owner.underlays -= marked_underlay //if this is being called, we should have an owner at this point.
..()
+/datum/status_effect/eldritch
+ duration = 15 SECONDS
+ status_type = STATUS_EFFECT_REPLACE
+ alert_type = null
+ on_remove_on_mob_delete = TRUE
+ ///underlay used to indicate that someone is marked
+ var/mutable_appearance/marked_underlay
+ ///path for the underlay
+ var/effect_sprite = ""
+
+/datum/status_effect/eldritch/on_creation(mob/living/new_owner, ...)
+ marked_underlay = mutable_appearance('icons/effects/effects.dmi', effect_sprite,BELOW_MOB_LAYER)
+ return ..()
+
+/datum/status_effect/eldritch/on_apply()
+ if(owner.mob_size >= MOB_SIZE_HUMAN)
+ RegisterSignal(owner,COMSIG_ATOM_UPDATE_OVERLAYS,.proc/update_owner_underlay)
+ owner.update_icon()
+ return TRUE
+ return FALSE
+
+/datum/status_effect/eldritch/on_remove()
+ UnregisterSignal(owner,COMSIG_ATOM_UPDATE_OVERLAYS)
+ owner.update_icon()
+ return ..()
+
+/datum/status_effect/eldritch/proc/update_owner_underlay(atom/source, list/overlays)
+ overlays += marked_underlay
+
+/datum/status_effect/eldritch/Destroy()
+ QDEL_NULL(marked_underlay)
+ return ..()
+
+/**
+ * What happens when this mark gets popped
+ *
+ * Adds actual functionality to each mark
+ */
+/datum/status_effect/eldritch/proc/on_effect()
+ playsound(owner, 'sound/magic/repulse.ogg', 75, TRUE)
+ qdel(src) //what happens when this is procced.
+
+//Each mark has diffrent effects when it is destroyed that combine with the mansus grasp effect.
+/datum/status_effect/eldritch/flesh
+ id = "flesh_mark"
+ effect_sprite = "emark1"
+
+/datum/status_effect/eldritch/flesh/on_effect()
+
+ if(ishuman(owner))
+ var/mob/living/carbon/human/H = owner
+ var/obj/item/bodypart/bodypart = pick(H.bodyparts)
+ var/datum/wound/slash/severe/crit_wound = new
+ crit_wound.apply_wound(bodypart)
+ return ..()
+
+/datum/status_effect/eldritch/ash
+ id = "ash_mark"
+ effect_sprite = "emark2"
+ ///Dictates how much damage and stamina loss this mark will cause.
+ var/repetitions = 1
+
+/datum/status_effect/eldritch/ash/on_creation(mob/living/new_owner, _repetition = 5)
+ . = ..()
+ repetitions = min(1,_repetition)
+
+/datum/status_effect/eldritch/ash/on_effect()
+ if(iscarbon(owner))
+ var/mob/living/carbon/carbon_owner = owner
+ carbon_owner.adjustStaminaLoss(10 * repetitions)
+ carbon_owner.adjustFireLoss(5 * repetitions)
+ for(var/mob/living/carbon/victim in range(1,carbon_owner))
+ if(IS_HERETIC(victim) || victim == carbon_owner)
+ continue
+ victim.apply_status_effect(type,repetitions-1)
+ break
+ return ..()
+
+/datum/status_effect/eldritch/rust
+ id = "rust_mark"
+ effect_sprite = "emark3"
+
+/datum/status_effect/eldritch/rust/on_effect()
+ if(!iscarbon(owner))
+ return
+ var/mob/living/carbon/carbon_owner = owner
+ for(var/obj/item/I in carbon_owner.get_all_gear()) //Affects roughly 75% of items
+ if(!QDELETED(I) && prob(75)) //Just in case
+ I.take_damage(100)
+ return ..()
+
+/datum/status_effect/corrosion_curse
+ id = "corrosion_curse"
+ status_type = STATUS_EFFECT_REPLACE
+ alert_type = null
+ tick_interval = 1 SECONDS
+
+/datum/status_effect/corrosion_curse/on_creation(mob/living/new_owner, ...)
+ . = ..()
+ to_chat(owner, "Your feel your body starting to break apart...")
+
+/datum/status_effect/corrosion_curse/tick()
+ . = ..()
+ if(!ishuman(owner))
+ return
+ var/mob/living/carbon/human/H = owner
+ var/chance = rand(0,100)
+ switch(chance)
+ if(0 to 19)
+ H.vomit()
+ if(20 to 29)
+ H.Dizzy(10)
+ if(30 to 39)
+ H.adjustOrganLoss(ORGAN_SLOT_LIVER,5)
+ if(40 to 49)
+ H.adjustOrganLoss(ORGAN_SLOT_HEART,5)
+ if(50 to 59)
+ H.adjustOrganLoss(ORGAN_SLOT_STOMACH,5)
+ if(60 to 69)
+ H.adjustOrganLoss(ORGAN_SLOT_EYES,10)
+ if(70 to 79)
+ H.adjustOrganLoss(ORGAN_SLOT_EARS,10)
+ if(80 to 89)
+ H.adjustOrganLoss(ORGAN_SLOT_LUNGS,10)
+ if(90 to 99)
+ H.adjustOrganLoss(ORGAN_SLOT_TONGUE,10)
+ if(100)
+ H.adjustOrganLoss(ORGAN_SLOT_BRAIN,20)
+
+/datum/status_effect/amok
+ id = "amok"
+ status_type = STATUS_EFFECT_REPLACE
+ alert_type = null
+ duration = 10 SECONDS
+ tick_interval = 1 SECONDS
+
+/datum/status_effect/amok/on_apply(mob/living/afflicted)
+ . = ..()
+ to_chat(owner, "Your feel filled with a rage that is not your own!")
+
+/datum/status_effect/amok/tick()
+ . = ..()
+ var/prev_intent = owner.a_intent
+ owner.a_intent = INTENT_HARM
+
+ var/list/mob/living/targets = list()
+ for(var/mob/living/potential_target in oview(owner, 1))
+ if(IS_HERETIC(potential_target) || potential_target.mind?.has_antag_datum(/datum/antagonist/heretic_monster))
+ continue
+ targets += potential_target
+ if(LAZYLEN(targets))
+ owner.log_message(" attacked someone due to the amok debuff.", LOG_ATTACK) //the following attack will log itself
+ owner.ClickOn(pick(targets))
+ owner.a_intent = prev_intent
+
+/datum/status_effect/cloudstruck
+ id = "cloudstruck"
+ status_type = STATUS_EFFECT_REPLACE
+ duration = 3 SECONDS
+ on_remove_on_mob_delete = TRUE
+ ///This overlay is applied to the owner for the duration of the effect.
+ var/mutable_appearance/mob_overlay
+
+/datum/status_effect/cloudstruck/on_creation(mob/living/new_owner, set_duration)
+ if(isnum(set_duration))
+ duration = set_duration
+ . = ..()
+
+/datum/status_effect/cloudstruck/on_apply()
+ mob_overlay = mutable_appearance('icons/effects/eldritch.dmi', "cloud_swirl", ABOVE_MOB_LAYER)
+ owner.overlays += mob_overlay
+ owner.update_icon()
+ ADD_TRAIT(owner, TRAIT_BLIND, "cloudstruck")
+ return TRUE
+
+/datum/status_effect/cloudstruck/on_remove()
+ . = ..()
+ if(QDELETED(owner))
+ return
+ REMOVE_TRAIT(owner, TRAIT_BLIND, "cloudstruck")
+ if(owner)
+ owner.overlays -= mob_overlay
+ owner.update_icon()
+
+/datum/status_effect/cloudstruck/Destroy()
+ . = ..()
+ QDEL_NULL(mob_overlay)
+
+
/datum/status_effect/stacking/saw_bleed
id = "saw_bleed"
tick_interval = 6
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index dfa9776489..6e1692c17e 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -1121,3 +1121,11 @@
max_grav = max(G.setting,max_grav)
return max_grav
return SSmapping.level_trait(T.z, ZTRAIT_GRAVITY)
+
+/**
+ * Causes effects when the atom gets hit by a rust effect from heretics
+ *
+ * Override this if you want custom behaviour in whatever gets hit by the rust
+ */
+/atom/proc/rust_heretic_act()
+ return
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
index da33da5f43..63574c6475 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
@@ -191,6 +191,24 @@
SSticker.mode_result = "loss - rev heads killed"
SSticker.news_report = REVS_LOSE
+//////////////////////////////////////////////
+// //
+// HERETIC SMUGGLER //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/latejoin/heretic_smuggler
+ name = "Heretic Smuggler"
+ antag_datum = /datum/antagonist/heretic
+ antag_flag = ROLE_HERETIC
+ protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
+ restricted_roles = list("AI","Cyborg")
+ required_candidates = 1
+ weight = 4
+ cost = 10
+ requirements = list(40,30,20,10,10,10,10,10,10,10)
+ repeatable = TRUE
+
//////////////////////////////////////////////
// //
// BLOODSUCKERS //
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
index edaadeae1c..766ddcefc7 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
@@ -143,6 +143,46 @@
changeling.add_antag_datum(new_antag)
return TRUE
+//////////////////////////////////////////////
+// //
+// ELDRITCH CULT //
+// //
+//////////////////////////////////////////////
+
+/datum/dynamic_ruleset/roundstart/heretics
+ name = "Heretics"
+ antag_flag = ROLE_HERETIC
+ antag_datum = /datum/antagonist/heretic
+ protected_roles = list("Prisoner","Security Officer", "Warden", "Detective", "Head of Security", "Captain")
+ restricted_roles = list("AI", "Cyborg")
+ required_candidates = 1
+ weight = 3
+ cost = 20
+ scaling_cost = 15
+ requirements = list(50,45,45,40,35,20,20,15,10,10)
+ antag_cap = list(1,1,1,1,2,2,2,2,3,3)
+
+
+/datum/dynamic_ruleset/roundstart/heretics/pre_execute()
+ . = ..()
+ var/num_ecult = antag_cap[indice_pop] * (scaled_times + 1)
+
+ for (var/i = 1 to num_ecult)
+ var/mob/picked_candidate = pick_n_take(candidates)
+ assigned += picked_candidate.mind
+ picked_candidate.mind.restricted_roles = restricted_roles
+ picked_candidate.mind.special_role = ROLE_HERETIC
+ return TRUE
+
+/datum/dynamic_ruleset/roundstart/heretics/execute()
+
+ for(var/c in assigned)
+ var/datum/mind/cultie = c
+ var/datum/antagonist/heretic/new_antag = new antag_datum()
+ cultie.add_antag_datum(new_antag)
+
+ return TRUE
+
//////////////////////////////////////////////
// //
// WIZARDS //
diff --git a/code/game/gamemodes/eldritch_cult/eldritch_cult.dm b/code/game/gamemodes/eldritch_cult/eldritch_cult.dm
new file mode 100644
index 0000000000..0a1343a034
--- /dev/null
+++ b/code/game/gamemodes/eldritch_cult/eldritch_cult.dm
@@ -0,0 +1,67 @@
+/datum/game_mode/heretics
+ name = "heresy"
+ config_tag = "heresy"
+ antag_flag = ROLE_HERETIC
+ false_report_weight = 5
+ protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
+ restricted_jobs = list("AI", "Cyborg")
+ required_players = 0
+ required_enemies = 1
+ recommended_enemies = 4
+ reroll_friendly = 0
+ enemy_minimum_age = 0
+ round_ends_with_antag_death = 0
+
+ announce_span = "danger"
+ announce_text = "Heretics have been spotted on the station!\n\
+ Heretics: Accomplish your objectives.\n\
+ Crew: Do not let the madman succeed!"
+
+ var/ecult_possible = 4 //hard limit on culties if scaling is turned off
+ var/num_ecult = 1
+ var/list/culties = list()
+
+/datum/game_mode/heretics/pre_setup()
+
+ if(CONFIG_GET(flag/protect_roles_from_antagonist))
+ restricted_jobs += protected_jobs
+
+ if(CONFIG_GET(flag/protect_assistant_from_antagonist))
+ restricted_jobs += "Assistant"
+
+
+ var/esc = CONFIG_GET(number/ecult_scaling_coeff)
+ if(esc)
+ num_ecult = min(max(1, min(round(num_players() / (esc * 2)) + 2, round(num_players() / esc))),4)
+ else
+ num_ecult = max(1, min(num_players(), ecult_possible))
+
+ for(var/i in 1 to num_ecult)
+ if(!antag_candidates.len)
+ break
+ var/datum/mind/cultie = antag_pick(antag_candidates)
+ antag_candidates -= cultie
+ cultie.special_role = ROLE_HERETIC
+ cultie.restricted_roles = restricted_jobs
+ culties += cultie
+
+ var/enough_heretics = culties.len > 0
+
+ if(!enough_heretics)
+ setup_error = "Not enough heretic candidates"
+ return FALSE
+ else
+ for(var/antag in culties)
+ return TRUE
+
+/datum/game_mode/heretics/post_setup()
+ for(var/c in culties)
+ var/datum/mind/cultie = c
+ log_game("[key_name(cultie)] has been selected as a heretic!")
+ var/datum/antagonist/heretic/new_antag = new()
+ cultie.add_antag_datum(new_antag)
+ return ..()
+
+/datum/game_mode/heretics/generate_report()
+ return "Cybersun Industries has announced that they have successfully raided a high-security library. The library contained a very dangerous book that was \
+ shown to posses anomalous properties. We suspect that the book has been copied over, Stay vigilant!"
diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm
index 3faf6d5727..42324f0f63 100644
--- a/code/game/machinery/_machinery.dm
+++ b/code/game/machinery/_machinery.dm
@@ -546,3 +546,6 @@ Class Procs:
. = . % 9
AM.pixel_x = -8 + ((.%3)*8)
AM.pixel_y = -8 + (round( . / 3)*8)
+
+/obj/machinery/rust_heretic_act()
+ take_damage(500, BRUTE, "melee", 1)
diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm
index 5b691607db..f16a5f9292 100644
--- a/code/game/mecha/mecha.dm
+++ b/code/game/mecha/mecha.dm
@@ -153,6 +153,9 @@
/obj/mecha/get_cell()
return cell
+/obj/mecha/rust_heretic_act()
+ take_damage(500, BRUTE)
+
/obj/mecha/Destroy()
go_out()
var/mob/living/silicon/ai/AI
diff --git a/code/game/objects/items/granters.dm b/code/game/objects/items/granters.dm
index 609cc3b719..979fa7e958 100644
--- a/code/game/objects/items/granters.dm
+++ b/code/game/objects/items/granters.dm
@@ -253,7 +253,7 @@
user.set_nutrition(NUTRITION_LEVEL_STARVING + 50)
/obj/item/book/granter/spell/blind
- spell = /obj/effect/proc_holder/spell/targeted/trigger/blind
+ spell = /obj/effect/proc_holder/spell/pointed/trigger/blind
spellname = "blind"
icon_state ="bookblind"
desc = "This book looks blurry, no matter how you look at it."
@@ -265,7 +265,7 @@
user.blind_eyes(10)
/obj/item/book/granter/spell/mindswap
- spell = /obj/effect/proc_holder/spell/targeted/mind_transfer
+ spell = /obj/effect/proc_holder/spell/pointed/mind_transfer
spellname = "mindswap"
icon_state ="bookmindswap"
desc = "This book's cover is pristine, though its pages look ragged and torn."
@@ -289,7 +289,7 @@
if(stored_swap == user)
to_chat(user,"You stare at the book some more, but there doesn't seem to be anything else to learn...")
return
- var/obj/effect/proc_holder/spell/targeted/mind_transfer/swapper = new
+ var/obj/effect/proc_holder/spell/pointed/mind_transfer/swapper = new
if(swapper.cast(list(stored_swap), user, TRUE, TRUE))
to_chat(user,"You're suddenly somewhere else... and someone else?!")
to_chat(stored_swap,"Suddenly you're staring at [src] again... where are you, who are you?!")
@@ -324,7 +324,7 @@
user.DefaultCombatKnockdown(40)
/obj/item/book/granter/spell/barnyard
- spell = /obj/effect/proc_holder/spell/targeted/barnyardcurse
+ spell = /obj/effect/proc_holder/spell/pointed/barnyardcurse
spellname = "barnyard"
icon_state ="bookhorses"
desc = "This book is more horse than your mind has room for."
diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm
index 39f2d276a1..e462257c10 100644
--- a/code/game/objects/structures.dm
+++ b/code/game/objects/structures.dm
@@ -110,3 +110,6 @@
if(0 to 25)
if(!broken)
return "It's falling apart!"
+
+/obj/structure/rust_heretic_act()
+ take_damage(500, BRUTE, "melee", 1)
diff --git a/code/game/turfs/closed.dm b/code/game/turfs/closed.dm
index b78826b23b..be0c444541 100644
--- a/code/game/turfs/closed.dm
+++ b/code/game/turfs/closed.dm
@@ -29,6 +29,9 @@
icon = 'icons/turf/walls.dmi'
explosion_block = 50
+/turf/closed/indestructible/rust_heretic_act()
+ return
+
/turf/closed/indestructible/TerraformTurf(path, new_baseturf, flags, defer_change = FALSE, ignore_air = FALSE)
return
@@ -198,13 +201,13 @@
/turf/closed/indestructible/rock/glacierrock
name = "unaturally hard ice wall"
- desc = "Ice, hardened over thousands of years, you're not breaking through this."
+ desc = "Ice, hardened over thousands of years, you're not breaking through this."
icon = 'icons/turf/walls.dmi'
icon_state = "snow_rock"
/turf/closed/indestructible/rock/glacierrock/blue
name = "blue ice wall"
- desc = "The incredible compressive forces that formed this sturdy ice wall gave it a blue color."
+ desc = "The incredible compressive forces that formed this sturdy ice wall gave it a blue color."
icon = 'icons/turf/walls.dmi'
icon_state = "ice"
canSmoothWith = list(/turf/closed/indestructible/rock/glacierrock/blue)
diff --git a/code/game/turfs/simulated/floor/fancy_floor.dm b/code/game/turfs/simulated/floor/fancy_floor.dm
index 946401ba18..5024a4bada 100644
--- a/code/game/turfs/simulated/floor/fancy_floor.dm
+++ b/code/game/turfs/simulated/floor/fancy_floor.dm
@@ -63,6 +63,11 @@
to_chat(user, "You forcefully pry off the planks, destroying them in the process.")
return make_plating()
+/turf/open/floor/wood/rust_heretic_act()
+ if(prob(70))
+ new /obj/effect/temp_visual/glowing_rune(src)
+ ChangeTurf(/turf/open/floor/plating/rust)
+
/turf/open/floor/wood/cold
temperature = 255.37
diff --git a/code/game/turfs/simulated/floor/misc_floor.dm b/code/game/turfs/simulated/floor/misc_floor.dm
index f6e70f8ef5..c4fd8139db 100644
--- a/code/game/turfs/simulated/floor/misc_floor.dm
+++ b/code/game/turfs/simulated/floor/misc_floor.dm
@@ -267,3 +267,15 @@
icon = 'icons/turf/floors.dmi'
icon_state = "floor_padded"
floor_tile = /obj/item/stack/tile/padded
+
+/turf/open/floor/plating/rust
+ name = "rusted plating"
+ desc = "Corrupted steel."
+ icon_state = "plating_rust"
+ footstep = FOOTSTEP_RUST
+ barefootstep = FOOTSTEP_RUST
+ clawfootstep = FOOTSTEP_RUST
+ heavyfootstep = FOOTSTEP_RUST
+
+/turf/open/floor/plating/rust/rust_heretic_act()
+ return
diff --git a/code/game/turfs/simulated/floor/plasteel_floor.dm b/code/game/turfs/simulated/floor/plasteel_floor.dm
index af48160ef1..614c63a14c 100644
--- a/code/game/turfs/simulated/floor/plasteel_floor.dm
+++ b/code/game/turfs/simulated/floor/plasteel_floor.dm
@@ -8,6 +8,11 @@
. = ..()
. += "There's a small crack on the edge of it."
+/turf/open/floor/plasteel/rust_heretic_act()
+ if(prob(70))
+ new /obj/effect/temp_visual/glowing_rune(src)
+ ChangeTurf(/turf/open/floor/plating/rust)
+
/turf/open/floor/plasteel/update_icon()
if(!..())
return 0
diff --git a/code/game/turfs/simulated/floor/plating.dm b/code/game/turfs/simulated/floor/plating.dm
index ac80bddd00..b597900cba 100644
--- a/code/game/turfs/simulated/floor/plating.dm
+++ b/code/game/turfs/simulated/floor/plating.dm
@@ -98,6 +98,11 @@
return TRUE
+/turf/open/floor/plating/rust_heretic_act()
+ if(prob(70))
+ new /obj/effect/temp_visual/glowing_rune(src)
+ ChangeTurf(/turf/open/floor/plating/rust)
+
/turf/open/floor/plating/make_plating()
return
diff --git a/code/game/turfs/simulated/wall/misc_walls.dm b/code/game/turfs/simulated/wall/misc_walls.dm
index d445be91cb..86bc5a2fe8 100644
--- a/code/game/turfs/simulated/wall/misc_walls.dm
+++ b/code/game/turfs/simulated/wall/misc_walls.dm
@@ -188,12 +188,20 @@
icon = 'icons/turf/walls/rusty_wall.dmi'
hardness = 45
+/turf/closed/wall/rust/rust_heretic_act()
+ ScrapeAway()
+
/turf/closed/wall/r_wall/rust
name = "rusted reinforced wall"
desc = "A huge chunk of rusted reinforced metal."
icon = 'icons/turf/walls/rusty_reinforced_wall.dmi'
hardness = 15
+/turf/closed/wall/r_wall/rust/rust_heretic_act()
+ if(prob(50))
+ return
+ ScrapeAway()
+
/turf/closed/wall/mineral/bronze
name = "clockwork wall"
desc = "A huge chunk of bronze, decorated like gears and cogs."
diff --git a/code/game/turfs/simulated/wall/reinf_walls.dm b/code/game/turfs/simulated/wall/reinf_walls.dm
index 6b41f41763..5e60f65ccc 100644
--- a/code/game/turfs/simulated/wall/reinf_walls.dm
+++ b/code/game/turfs/simulated/wall/reinf_walls.dm
@@ -235,6 +235,13 @@
if(the_rcd.canRturf)
return ..()
+/turf/closed/wall/r_wall/rust_heretic_act()
+ if(prob(50))
+ return
+ if(prob(70))
+ new /obj/effect/temp_visual/glowing_rune(src)
+ ChangeTurf(/turf/closed/wall/r_wall/rust)
+
/turf/closed/wall/r_wall/syndicate
name = "hull"
desc = "The armored hull of an ominous looking ship."
diff --git a/code/game/turfs/simulated/walls.dm b/code/game/turfs/simulated/walls.dm
index 0623f2b9d2..431678547f 100644
--- a/code/game/turfs/simulated/walls.dm
+++ b/code/game/turfs/simulated/walls.dm
@@ -324,4 +324,9 @@
add_overlay(dent_decals)
+/turf/closed/wall/rust_heretic_act()
+ if(prob(70))
+ new /obj/effect/temp_visual/glowing_rune(src)
+ ChangeTurf(/turf/closed/wall/rust)
+
#undef MAX_DENT_DECALS
diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm
index 8bff37ab03..c5f880c955 100644
--- a/code/modules/antagonists/_common/antag_datum.dm
+++ b/code/modules/antagonists/_common/antag_datum.dm
@@ -14,6 +14,8 @@ GLOBAL_LIST_EMPTY(antagonists)
var/list/objectives = list()
var/antag_memory = ""//These will be removed with antag datum
var/antag_moodlet //typepath of moodlet that the mob will gain with their status
+ var/antag_hud_type
+ var/antag_hud_name
/// If above 0, this is the multiplier for the speed at which we hijack the shuttle. Do not directly read, use hijack_speed().
var/hijack_speed = 0
@@ -77,6 +79,17 @@ GLOBAL_LIST_EMPTY(antagonists)
hud.leave_hud(mob_override)
set_antag_hud(mob_override, null)
+// Handles adding and removing the clumsy mutation from clown antags. Gets called in apply/remove_innate_effects
+/datum/antagonist/proc/handle_clown_mutation(mob/living/mob_override, message, removing = TRUE)
+ var/mob/living/carbon/human/H = mob_override
+ if(H && istype(H) && owner.assigned_role == "Clown")
+ if(removing) // They're a clown becoming an antag, remove clumsy
+ H.dna.remove_mutation(CLOWNMUT)
+ if(!silent && message)
+ to_chat(H, "[message]")
+ else
+ H.dna.add_mutation(CLOWNMUT) // We're removing their antag status, add back clumsy
+
//Assign default team and creates one for one of a kind team antagonists
/datum/antagonist/proc/create_team(datum/team/team)
return
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_antag.dm b/code/modules/antagonists/eldritch_cult/eldritch_antag.dm
new file mode 100644
index 0000000000..9507378198
--- /dev/null
+++ b/code/modules/antagonists/eldritch_cult/eldritch_antag.dm
@@ -0,0 +1,224 @@
+/datum/antagonist/heretic
+ name = "Heretic"
+ roundend_category = "Heretics"
+ antagpanel_category = "Heretic"
+ antag_moodlet = /datum/mood_event/heretics
+ job_rank = ROLE_HERETIC
+ antag_hud_type = ANTAG_HUD_HERETIC
+ antag_hud_name = "heretic"
+ var/give_equipment = TRUE
+ var/list/researched_knowledge = list()
+ var/total_sacrifices = 0
+ var/ascended = FALSE
+
+/datum/antagonist/heretic/admin_add(datum/mind/new_owner,mob/admin)
+ give_equipment = FALSE
+ new_owner.add_antag_datum(src)
+ message_admins("[key_name_admin(admin)] has heresized [key_name_admin(new_owner)].")
+ log_admin("[key_name(admin)] has heresized [key_name(new_owner)].")
+
+/datum/antagonist/heretic/greet()
+ owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/ecult_op.ogg', 100, FALSE, pressure_affected = FALSE)//subject to change
+ to_chat(owner, "You are the Heretic!
\
+ The old ones gave you these tasks to fulfill:")
+ owner.announce_objectives()
+ to_chat(owner, "The book whispers, the forbidden knowledge walks once again!
\
+ Your book allows you to research abilities, read it very carefully! you cannot undo what has been done!
\
+ You gain charges by either collecting influences or sacrificng people tracked by the living heart
\
+ You can find a basic guide at : https://tgstation13.org/wiki/Heresy_101 ")
+
+/datum/antagonist/heretic/on_gain()
+ var/mob/living/current = owner.current
+ if(ishuman(current))
+ forge_primary_objectives()
+ gain_knowledge(/datum/eldritch_knowledge/spell/basic)
+ gain_knowledge(/datum/eldritch_knowledge/living_heart)
+ gain_knowledge(/datum/eldritch_knowledge/codex_cicatrix)
+ gain_knowledge(/datum/eldritch_knowledge/eldritch_blade)
+ current.log_message("has been converted to the cult of the forgotten ones!", LOG_ATTACK, color="#960000")
+ GLOB.reality_smash_track.AddMind(owner)
+ START_PROCESSING(SSprocessing,src)
+ if(give_equipment)
+ equip_cultist()
+ return ..()
+
+/datum/antagonist/heretic/on_removal()
+
+ for(var/X in researched_knowledge)
+ var/datum/eldritch_knowledge/EK = researched_knowledge[X]
+ EK.on_lose(owner.current)
+
+ if(!silent)
+ owner.current.visible_message("[owner.current] looks like [owner.current.p_theyve()] just reverted to [owner.current.p_their()] old faith!", null, null, null, owner.current)
+ to_chat(owner.current, "Your mind begins to flare as the otherwordly knowledge escapes your grasp!")
+ owner.current.log_message("has renounced the cult of the old ones!", LOG_ATTACK, color="#960000")
+ GLOB.reality_smash_track.RemoveMind(owner)
+ STOP_PROCESSING(SSprocessing,src)
+
+ return ..()
+
+
+/datum/antagonist/heretic/proc/equip_cultist()
+ var/mob/living/carbon/H = owner.current
+ if(!istype(H))
+ return
+ . += ecult_give_item(/obj/item/forbidden_book, H)
+ . += ecult_give_item(/obj/item/living_heart, H)
+
+/datum/antagonist/heretic/proc/ecult_give_item(obj/item/item_path, mob/living/carbon/human/H)
+ var/list/slots = list(
+ "backpack" = SLOT_IN_BACKPACK,
+ "left pocket" = SLOT_L_STORE,
+ "right pocket" = SLOT_R_STORE
+ )
+
+ var/T = new item_path(H)
+ var/item_name = initial(item_path.name)
+ var/where = H.equip_in_one_of_slots(T, slots)
+ if(!where)
+ to_chat(H, "Unfortunately, you weren't able to get a [item_name]. This is very bad and you should adminhelp immediately (press F1).")
+ return FALSE
+ else
+ to_chat(H, "You have a [item_name] in your [where].")
+ if(where == "backpack")
+ SEND_SIGNAL(H.back, COMSIG_TRY_STORAGE_SHOW, H)
+ return TRUE
+
+/datum/antagonist/heretic/process()
+
+ for(var/X in researched_knowledge)
+ var/datum/eldritch_knowledge/EK = researched_knowledge[X]
+ EK.on_life(owner.current)
+
+/datum/antagonist/heretic/proc/forge_primary_objectives()
+ var/list/assasination = list()
+ var/list/protection = list()
+ for(var/i in 1 to 2)
+ var/pck = pick("assasinate","stalk","protect")
+ switch(pck)
+ if("assasinate")
+ var/datum/objective/assassinate/A = new
+ A.owner = owner
+ var/list/owners = A.get_owners()
+ A.find_target(owners,protection)
+ assasination += A.target
+ objectives += A
+ if("protect")
+ var/datum/objective/protect/P = new
+ P.owner = owner
+ var/list/owners = P.get_owners()
+ P.find_target(owners,assasination)
+ protection += P.target
+ objectives += P
+
+ var/datum/objective/sacrifice_ecult/SE = new
+ SE.owner = owner
+ SE.update_explanation_text()
+ objectives += SE
+
+/datum/antagonist/heretic/apply_innate_effects(mob/living/mob_override)
+ . = ..()
+ var/mob/living/current = owner.current
+ if(mob_override)
+ current = mob_override
+ add_antag_hud(antag_hud_type, antag_hud_name, current)
+ handle_clown_mutation(current, mob_override ? null : "Knowledge described in the book allowed you to overcome your clownish nature, allowing you to use complex items effectively.")
+ current.faction |= "heretics"
+
+/datum/antagonist/heretic/remove_innate_effects(mob/living/mob_override)
+ . = ..()
+ var/mob/living/current = owner.current
+ if(mob_override)
+ current = mob_override
+ remove_antag_hud(antag_hud_type, current)
+ handle_clown_mutation(current, removing = FALSE)
+ current.faction -= "heretics"
+
+/datum/antagonist/heretic/get_admin_commands()
+ . = ..()
+ .["Equip"] = CALLBACK(src,.proc/equip_cultist)
+
+/datum/antagonist/heretic/roundend_report()
+ var/list/parts = list()
+
+ var/cultiewin = TRUE
+
+ parts += printplayer(owner)
+ parts += "Sacrifices Made: [total_sacrifices]"
+
+ if(length(objectives))
+ var/count = 1
+ for(var/o in objectives)
+ var/datum/objective/objective = o
+ if(objective.check_completion())
+ parts += "Objective #[count]: [objective.explanation_text] Success!"
+ else
+ parts += "Objective #[count]: [objective.explanation_text] Fail."
+ cultiewin = FALSE
+ count++
+ if(ascended)
+ parts += "HERETIC HAS ASCENDED!"
+ else
+ if(cultiewin)
+ parts += "The heretic was successful!"
+ else
+ parts += "The heretic has failed."
+
+ parts += "Knowledge Researched: "
+
+ var/list/knowledge_message = list()
+ var/list/knowledge = get_all_knowledge()
+ for(var/X in knowledge)
+ var/datum/eldritch_knowledge/EK = knowledge[X]
+ knowledge_message += "[EK.name]"
+ parts += knowledge_message.Join(", ")
+
+ return parts.Join("
")
+////////////////
+// Knowledge //
+////////////////
+
+/datum/antagonist/heretic/proc/gain_knowledge(datum/eldritch_knowledge/EK)
+ if(get_knowledge(EK))
+ return FALSE
+ var/datum/eldritch_knowledge/initialized_knowledge = new EK
+ researched_knowledge[initialized_knowledge.type] = initialized_knowledge
+ initialized_knowledge.on_gain(owner.current)
+ return TRUE
+
+/datum/antagonist/heretic/proc/get_researchable_knowledge()
+ var/list/researchable_knowledge = list()
+ var/list/banned_knowledge = list()
+ for(var/X in researched_knowledge)
+ var/datum/eldritch_knowledge/EK = researched_knowledge[X]
+ researchable_knowledge |= EK.next_knowledge
+ banned_knowledge |= EK.banned_knowledge
+ banned_knowledge |= EK.type
+ researchable_knowledge -= banned_knowledge
+ return researchable_knowledge
+
+/datum/antagonist/heretic/proc/get_knowledge(wanted)
+ return researched_knowledge[wanted]
+
+/datum/antagonist/heretic/proc/get_all_knowledge()
+ return researched_knowledge
+
+////////////////
+// Objectives //
+////////////////
+
+/datum/objective/sacrifice_ecult
+ name = "sacrifice"
+
+/datum/objective/sacrifice_ecult/update_explanation_text()
+ . = ..()
+ target_amount = rand(2,4)
+ explanation_text = "Sacrifice at least [target_amount] people."
+
+/datum/objective/sacrifice_ecult/check_completion()
+ if(!owner)
+ return FALSE
+ var/datum/antagonist/heretic/cultie = owner.has_antag_datum(/datum/antagonist/heretic)
+ if(!cultie)
+ return FALSE
+ return cultie.total_sacrifices >= target_amount
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_book.dm b/code/modules/antagonists/eldritch_cult/eldritch_book.dm
new file mode 100644
index 0000000000..5581f37dde
--- /dev/null
+++ b/code/modules/antagonists/eldritch_cult/eldritch_book.dm
@@ -0,0 +1,145 @@
+/obj/item/forbidden_book
+ name = "Codex Cicatrix"
+ desc = "Book describing the secrets of the veil."
+ icon = 'icons/obj/eldritch.dmi'
+ icon_state = "book"
+ item_state = "book"
+ w_class = WEIGHT_CLASS_SMALL
+ ///Last person that touched this
+ var/mob/living/last_user
+ ///how many charges do we have?
+ var/charge = 1
+ ///Where we cannot create the rune?
+ var/static/list/blacklisted_turfs = typecacheof(list(/turf/closed,/turf/open/space,/turf/open/lava))
+
+/obj/item/forbidden_book/Destroy()
+ last_user = null
+ . = ..()
+
+
+/obj/item/forbidden_book/examine(mob/user)
+ . = ..()
+ if(!IS_HERETIC(user))
+ return
+ . += "The Tome holds [charge] charges."
+ . += "Use it on the floor to create a transmutation rune, used to perform rituals."
+ . += "Hit an influence in the black part with it to gain a charge."
+ . += "Hit a transmutation rune to destroy it."
+
+/obj/item/forbidden_book/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ if(!proximity_flag || !IS_HERETIC(user))
+ return
+ if(istype(target,/obj/effect/eldritch))
+ remove_rune(target,user)
+ if(istype(target,/obj/effect/reality_smash))
+ get_power_from_influence(target,user)
+ if(istype(target,/turf/open))
+ draw_rune(target,user)
+
+///Gives you a charge and destroys a corresponding influence
+/obj/item/forbidden_book/proc/get_power_from_influence(atom/target, mob/user)
+ var/obj/effect/reality_smash/RS = target
+ to_chat(target, "You start drawing power from influence...")
+ if(do_after(user,10 SECONDS,TRUE,RS))
+ qdel(RS)
+ charge += 1
+
+///Draws a rune on a selected turf
+/obj/item/forbidden_book/proc/draw_rune(atom/target,mob/user)
+
+ for(var/turf/T in range(1,target))
+ if(is_type_in_typecache(T, blacklisted_turfs))
+ to_chat(target, "The terrain doesn't support runes!")
+ return
+ var/A = get_turf(target)
+ to_chat(user, "You start drawing a rune...")
+
+ if(do_after(user,30 SECONDS,FALSE, user))
+
+ new /obj/effect/eldritch/big(A)
+
+///Removes runes from the selected turf
+/obj/item/forbidden_book/proc/remove_rune(atom/target,mob/user)
+
+ to_chat(user, "You start removing a rune...")
+ if(do_after(user,2 SECONDS,FALSE, user))
+ qdel(target)
+
+/obj/item/forbidden_book/ui_interact(mob/user, datum/tgui/ui = null)
+ if(!IS_HERETIC(user))
+ return FALSE
+ last_user = user
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ icon_state = "book_open"
+ flick("book_opening", src)
+ ui = new(user, src, "ForbiddenLore", name)
+ ui.open()
+
+/obj/item/forbidden_book/ui_data(mob/user)
+ var/datum/antagonist/heretic/cultie = user.mind.has_antag_datum(/datum/antagonist/heretic)
+ var/list/to_know = list()
+ for(var/Y in cultie.get_researchable_knowledge())
+ to_know += new Y
+ var/list/known = cultie.get_all_knowledge()
+ var/list/data = list()
+ var/list/lore = list()
+
+ data["charges"] = charge
+
+ for(var/X in to_know)
+ lore = list()
+ var/datum/eldritch_knowledge/EK = X
+ lore["type"] = EK.type
+ lore["name"] = EK.name
+ lore["cost"] = EK.cost
+ lore["disabled"] = EK.cost <= charge ? FALSE : TRUE
+ lore["path"] = EK.route
+ lore["state"] = "Research"
+ lore["flavour"] = EK.gain_text
+ lore["desc"] = EK.desc
+ data["to_know"] += list(lore)
+
+ for(var/X in known)
+ lore = list()
+ var/datum/eldritch_knowledge/EK = known[X]
+ lore["name"] = EK.name
+ lore["cost"] = EK.cost
+ lore["disabled"] = TRUE
+ lore["path"] = EK.route
+ lore["state"] = "Researched"
+ lore["flavour"] = EK.gain_text
+ lore["desc"] = EK.desc
+ data["to_know"] += list(lore)
+
+ if(!length(data["to_know"]))
+ data["to_know"] = null
+
+ return data
+
+/obj/item/forbidden_book/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+ switch(action)
+ if("research")
+ var/datum/antagonist/heretic/cultie = last_user.mind.has_antag_datum(/datum/antagonist/heretic)
+ var/ekname = params["name"]
+ for(var/X in cultie.get_researchable_knowledge())
+ var/datum/eldritch_knowledge/EK = X
+ if(initial(EK.name) != ekname)
+ continue
+ if(cultie.gain_knowledge(EK))
+ charge -= text2num(params["cost"])
+ return TRUE
+
+ update_icon() // Not applicable to all objects.
+
+/obj/item/forbidden_book/ui_close(mob/user)
+ flick("book_closing",src)
+ icon_state = initial(icon_state)
+ return ..()
+
+/obj/item/forbidden_book/debug
+ charge = 100
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_effects.dm b/code/modules/antagonists/eldritch_cult/eldritch_effects.dm
new file mode 100644
index 0000000000..899e588bda
--- /dev/null
+++ b/code/modules/antagonists/eldritch_cult/eldritch_effects.dm
@@ -0,0 +1,289 @@
+/obj/effect/eldritch
+ name = "Generic rune"
+ desc = "Weird combination of shapes and symbols etched into the floor itself. The indentation is filled with thick black tar-like fluid."
+ anchored = TRUE
+ icon_state = ""
+ resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF
+ layer = SIGIL_LAYER
+ ///Used mainly for summoning ritual to prevent spamming the rune to create millions of monsters.
+ var/is_in_use = FALSE
+
+/obj/effect/eldritch/attack_hand(mob/living/user)
+ . = ..()
+ if(.)
+ return
+ try_activate(user)
+
+/obj/effect/eldritch/proc/try_activate(mob/living/user)
+ if(!IS_HERETIC(user))
+ return
+ if(!is_in_use)
+ INVOKE_ASYNC(src, .proc/activate , user)
+
+/obj/effect/eldritch/attacked_by(obj/item/I, mob/living/user)
+ . = ..()
+ if(istype(I,/obj/item/nullrod))
+ qdel(src)
+
+/obj/effect/eldritch/proc/activate(mob/living/user)
+ is_in_use = TRUE
+ // Have fun trying to read this proc.
+ var/datum/antagonist/heretic/cultie = user.mind.has_antag_datum(/datum/antagonist/heretic)
+ var/list/knowledge = cultie.get_all_knowledge()
+ var/list/atoms_in_range = list()
+
+ for(var/A in range(1, src))
+ var/atom/atom_in_range = A
+ if(istype(atom_in_range,/area))
+ continue
+ if(istype(atom_in_range,/turf)) // we dont want turfs
+ continue
+ if(istype(atom_in_range,/mob/living))
+ var/mob/living/living_in_range = atom_in_range
+ if(living_in_range.stat != DEAD || living_in_range == user) // we only accept corpses, no living beings allowed.
+ continue
+ atoms_in_range += atom_in_range
+ for(var/X in knowledge)
+ var/datum/eldritch_knowledge/current_eldritch_knowledge = knowledge[X]
+
+ //has to be done so that we can freely edit the local_required_atoms without fucking up the eldritch knowledge
+ var/list/local_required_atoms = list()
+
+ if(!current_eldritch_knowledge.required_atoms || current_eldritch_knowledge.required_atoms.len == 0)
+ continue
+
+ local_required_atoms += current_eldritch_knowledge.required_atoms
+
+ var/list/selected_atoms = list()
+
+ if(!current_eldritch_knowledge.recipe_snowflake_check(atoms_in_range,drop_location(),selected_atoms))
+ continue
+
+ for(var/LR in local_required_atoms)
+ var/list/local_required_atom_list = LR
+
+ for(var/LAIR in atoms_in_range)
+ var/atom/local_atom_in_range = LAIR
+ if(is_type_in_list(local_atom_in_range,local_required_atom_list))
+ selected_atoms |= local_atom_in_range
+ local_required_atoms -= list(local_required_atom_list)
+
+ if(length(local_required_atoms) > 0)
+ continue
+
+ flick("[icon_state]_active",src)
+ playsound(user, 'sound/magic/castsummon.ogg', 75, TRUE)
+ //we are doing this since some on_finished_recipe subtract the atoms from selected_atoms making them invisible permanently.
+ var/list/atoms_to_disappear = selected_atoms.Copy()
+ for(var/to_disappear in atoms_to_disappear)
+ var/atom/atom_to_disappear = to_disappear
+ //temporary so we dont have to deal with the bs of someone picking those up when they may be deleted
+ atom_to_disappear.invisibility = INVISIBILITY_ABSTRACT
+ if(current_eldritch_knowledge.on_finished_recipe(user,selected_atoms,loc))
+ current_eldritch_knowledge.cleanup_atoms(selected_atoms)
+ is_in_use = FALSE
+
+ for(var/to_appear in atoms_to_disappear)
+ var/atom/atom_to_appear = to_appear
+ //we need to reappear the item just in case the ritual didnt consume everything... or something.
+ atom_to_appear.invisibility = initial(atom_to_appear.invisibility)
+
+ return
+ is_in_use = FALSE
+ to_chat(user,"Your ritual failed! You used either wrong components or are missing something important!")
+
+/obj/effect/eldritch/big
+ name = "transmutation circle"
+ icon = 'icons/effects/96x96.dmi'
+ icon_state = "eldritch_rune1"
+ pixel_x = -32 //So the big ol' 96x96 sprite shows up right
+ pixel_y = -32
+
+/**
+ * #Reality smash tracker
+ *
+ * Stupid fucking list holder, DONT create new ones, it will break the game, this is automnatically created whenever eldritch cultists are created.
+ *
+ * Tracks relevant data, generates relevant data, useful tool
+ */
+/datum/reality_smash_tracker
+ ///list of tracked reality smashes
+ var/list/smashes = list()
+ ///List of mobs with ability to see the smashes
+ var/list/targets = list()
+
+/datum/reality_smash_tracker/Destroy(force, ...)
+ if(GLOB.reality_smash_track == src)
+ stack_trace("/datum/reality_smash_tracker was deleted. Heretics may no longer access any influences. Fix it or call coder support")
+ QDEL_LIST(smashes)
+ targets.Cut()
+ return ..()
+
+/**
+ * Automatically fixes the target and smash network
+ *
+ * Fixes any bugs that are caused by late Generate() or exchanging clients
+ */
+/datum/reality_smash_tracker/proc/ReworkNetwork()
+ listclearnulls(smashes)
+ for(var/mind in targets)
+ if(isnull(mind))
+ stack_trace("A null somehow landed in a list of minds")
+ continue
+ for(var/X in smashes)
+ var/obj/effect/reality_smash/reality_smash = X
+ reality_smash.AddMind(mind)
+
+/**
+ * Generates a set amount of reality smashes based on the N value
+ *
+ * Automatically creates more reality smashes
+ */
+/datum/reality_smash_tracker/proc/_Generate()
+ var/targ_len = length(targets)
+ var/smash_len = length(smashes)
+ var/number = targ_len * 6 - smash_len
+
+ for(var/i in 0 to number)
+
+ var/turf/chosen_location = get_safe_random_station_turf()
+ //we also dont want them close to each other, at least 1 tile of seperation
+ var/obj/effect/reality_smash/what_if_i_have_one = locate() in range(1, chosen_location)
+ var/obj/effect/broken_illusion/what_if_i_had_one_but_got_used = locate() in range(1, chosen_location)
+ if(what_if_i_have_one || what_if_i_had_one_but_got_used) //we dont want to spawn
+ continue
+ var/obj/effect/reality_smash/RS = new/obj/effect/reality_smash(chosen_location)
+ smashes += RS
+ ReworkNetwork()
+
+
+/**
+ * Adds a mind to the list of people that can see the reality smashes
+ *
+ * Use this whenever you want to add someone to the list
+ */
+/datum/reality_smash_tracker/proc/AddMind(var/datum/mind/M)
+ RegisterSignal(M.current,COMSIG_MOB_CLIENT_LOGIN,.proc/ReworkNetwork)
+ targets |= M
+ _Generate()
+ for(var/X in smashes)
+ var/obj/effect/reality_smash/reality_smash = X
+ reality_smash.AddMind(M)
+
+
+/**
+ * Removes a mind from the list of people that can see the reality smashes
+ *
+ * Use this whenever you want to remove someone from the list
+ */
+/datum/reality_smash_tracker/proc/RemoveMind(var/datum/mind/M)
+ UnregisterSignal(M.current,COMSIG_MOB_CLIENT_LOGIN)
+ targets -= M
+ for(var/obj/effect/reality_smash/RS in smashes)
+ RS.RemoveMind(M)
+
+/obj/effect/broken_illusion
+ name = "pierced reality"
+ icon = 'icons/effects/eldritch.dmi'
+ icon_state = "pierced_illusion"
+ anchored = TRUE
+ resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF
+
+/obj/effect/broken_illusion/attack_hand(mob/living/user)
+ if(!ishuman(user))
+ return ..()
+ var/mob/living/carbon/human/human_user = user
+ if(IS_HERETIC(human_user))
+ to_chat(human_user,"You know better than to tempt forces out of your control.")
+ else
+ var/obj/item/bodypart/arm = human_user.get_active_hand()
+ if(prob(25))
+ to_chat(human_user,"An otherwordly presence tears your arm apart into atoms as you try to touch the hole in the very fabric of reality!")
+ arm.dismember()
+ qdel(arm)
+ else
+ to_chat(human_user,"You pull your hand away from the hole as eldritch energy flails out, trying to latch onto existence itself!")
+
+/obj/effect/broken_illusion/attack_tk(mob/user)
+ if(!ishuman(user))
+ return
+ var/mob/living/carbon/human/human_user = user
+ if(IS_HERETIC(human_user))
+ to_chat(human_user,"You know better than to tempt forces out of your control.")
+ else
+ //a very elaborate way to suicide
+ to_chat(human_user,"Eldritch energy lashes out, piercing your fragile mind, tearing it to pieces!")
+ human_user.ghostize()
+ var/obj/item/bodypart/head/head = locate() in human_user.bodyparts
+ if(head)
+ head.dismember()
+ qdel(head)
+ else
+ human_user.gib()
+
+ var/datum/effect_system/reagents_explosion/explosion = new()
+ explosion.set_up(1, get_turf(human_user), 1, 0)
+ explosion.start()
+
+/obj/effect/broken_illusion/examine(mob/user)
+ if(!IS_HERETIC(user) && ishuman(user))
+ var/mob/living/carbon/human/human_user = user
+ to_chat(human_user,"Your brain hurts when you look at this!")
+ human_user.adjustOrganLoss(ORGAN_SLOT_BRAIN,30)
+ . = ..()
+
+/obj/effect/reality_smash
+ name = "/improper reality smash"
+ icon = 'icons/effects/eldritch.dmi'
+ anchored = TRUE
+ resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF
+ ///We cannot use icon_state since this is invisible, functions the same way but with custom behaviour.
+ var/image_state = "reality_smash"
+ ///Who can see us?
+ var/list/minds = list()
+ ///Tracked image
+ var/image/img
+
+/obj/effect/reality_smash/Initialize()
+ . = ..()
+ img = image(icon, src, image_state, OBJ_LAYER)
+ generate_name()
+
+/obj/effect/reality_smash/Destroy()
+ on_destroy()
+ return ..()
+
+///Custom effect that happens on destruction
+/obj/effect/reality_smash/proc/on_destroy()
+ for(var/cm in minds)
+ var/datum/mind/cultie = cm
+ if(cultie.current?.client)
+ cultie.current.client.images -= img
+ //clear the list
+ minds -= cultie
+ GLOB.reality_smash_track.smashes -= src
+ img = null
+ new /obj/effect/broken_illusion(drop_location())
+
+///Makes the mind able to see this effect
+/obj/effect/reality_smash/proc/AddMind(var/datum/mind/cultie)
+ minds |= cultie
+ if(cultie.current.client)
+ cultie.current.client.images |= img
+
+
+
+///Makes the mind not able to see this effect
+/obj/effect/reality_smash/proc/RemoveMind(var/datum/mind/cultie)
+ minds -= cultie
+ if(cultie.current.client)
+ cultie.current.client.images -= img
+
+
+
+///Generates random name
+/obj/effect/reality_smash/proc/generate_name()
+ var/static/list/prefix = list("Omniscient","Thundering","Enlightening","Intrusive","Rejectful","Atomized","Subtle","Rising","Lowering","Fleeting","Towering","Blissful","Arrogant","Threatening","Peaceful","Aggressive")
+ var/static/list/postfix = list("Flaw","Presence","Crack","Heat","Cold","Memory","Reminder","Breeze","Grasp","Sight","Whisper","Flow","Touch","Veil","Thought","Imperfection","Blemish","Blush")
+
+ name = pick(prefix) + " " + pick(postfix)
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_items.dm b/code/modules/antagonists/eldritch_cult/eldritch_items.dm
new file mode 100644
index 0000000000..af6f5cbd2a
--- /dev/null
+++ b/code/modules/antagonists/eldritch_cult/eldritch_items.dm
@@ -0,0 +1,142 @@
+/obj/item/living_heart
+ name = "living heart"
+ desc = "Link to the worlds beyond."
+ icon = 'icons/obj/eldritch.dmi'
+ icon_state = "living_heart"
+ w_class = WEIGHT_CLASS_SMALL
+ ///Target
+ var/mob/living/carbon/human/target
+
+/obj/item/living_heart/attack_self(mob/user)
+ . = ..()
+ if(!IS_HERETIC(user))
+ return
+ if(!target)
+ to_chat(user,"No target could be found. Put the living heart on the rune and use the rune to recieve a target.")
+ return
+ var/dist = get_dist(user.loc,target.loc)
+ var/dir = get_dir(user.loc,target.loc)
+
+ switch(dist)
+ if(0 to 15)
+ to_chat(user,"[target.real_name] is near you. They are to the [dir2text(dir)] of you!")
+ if(16 to 31)
+ to_chat(user,"[target.real_name] is somewhere in your vicinty. They are to the [dir2text(dir)] of you!")
+ if(32 to 127)
+ to_chat(user,"[target.real_name] is far away from you. They are to the [dir2text(dir)] of you!")
+ else
+ to_chat(user,"[target.real_name] is beyond our reach.")
+
+ if(target.stat == DEAD)
+ to_chat(user,"[target.real_name] is dead. Bring them onto a transmutation rune!")
+
+/obj/item/melee/sickly_blade
+ name = "eldritch blade"
+ desc = "A sickly green crescent blade, decorated with an ornamental eye. You feel like you're being watched..."
+ icon = 'icons/obj/eldritch.dmi'
+ icon_state = "eldritch_blade"
+ item_state = "eldritch_blade"
+ lefthand_file = 'icons/mob/inhands/64x64_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/64x64_righthand.dmi'
+ inhand_x_dimension = 64
+ inhand_y_dimension = 64
+ flags_1 = CONDUCT_1
+ sharpness = SHARP_EDGED
+ w_class = WEIGHT_CLASS_NORMAL
+ force = 17
+ throwforce = 10
+ hitsound = 'sound/weapons/bladeslice.ogg'
+ attack_verb = list("attacked", "slashed", "stabbed", "sliced", "tore", "lacerated", "ripped", "diced", "rended")
+
+/obj/item/melee/sickly_blade/attack(mob/living/M, mob/living/user)
+ if(!IS_HERETIC(user))
+ to_chat(user,"You feel a pulse of some alien intellect lash out at your mind!")
+ var/mob/living/carbon/human/human_user = user
+ human_user.AdjustParalyzed(5 SECONDS)
+ return FALSE
+ return ..()
+
+/obj/item/melee/sickly_blade/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ var/datum/antagonist/heretic/cultie = user.mind.has_antag_datum(/datum/antagonist/heretic)
+ if(!cultie || !proximity_flag)
+ return
+ var/list/knowledge = cultie.get_all_knowledge()
+ for(var/X in knowledge)
+ var/datum/eldritch_knowledge/eldritch_knowledge_datum = knowledge[X]
+ eldritch_knowledge_datum.on_eldritch_blade(target,user,proximity_flag,click_parameters)
+
+/obj/item/melee/sickly_blade/rust
+ name = "rusted blade"
+ desc = "This crescent blade is decrepit, wasting to dust. Yet still it bites, catching flesh with jagged, rotten teeth."
+ icon_state = "rust_blade"
+ item_state = "rust_blade"
+ embedding = list("pain_mult" = 4, "embed_chance" = 75, "fall_chance" = 10, "ignore_throwspeed_threshold" = TRUE)
+ throwforce = 17
+
+/obj/item/melee/sickly_blade/ash
+ name = "ashen blade"
+ desc = "Molten and unwrought, a hunk of metal warped to cinders and slag. Unmade, it aspires to be more than it is, and shears soot-filled wounds with a blunt edge."
+ icon_state = "ash_blade"
+ item_state = "ash_blade"
+ force = 20
+
+/obj/item/melee/sickly_blade/flesh
+ name = "flesh blade"
+ desc = "A crescent blade born from a fleshwarped creature. Keenly aware, it seeks to spread to others the excruciations it has endured from dead origins."
+ icon_state = "flesh_blade"
+ item_state = "flesh_blade"
+ wound_bonus = 5
+ bare_wound_bonus = 15
+
+/obj/item/clothing/neck/eldritch_amulet
+ name = "warm eldritch medallion"
+ desc = "A strange medallion. Peering through the crystalline surface, the world around you melts away. You see your own beating heart, and the pulse of a thousand others."
+ icon = 'icons/obj/eldritch.dmi'
+ icon_state = "eye_medalion"
+ w_class = WEIGHT_CLASS_SMALL
+ ///What trait do we want to add upon equipiing
+ var/trait = TRAIT_THERMAL_VISION
+
+/obj/item/clothing/neck/eldritch_amulet/equipped(mob/user, slot)
+ . = ..()
+ if(ishuman(user) && user.mind && slot == ITEM_SLOT_NECK)
+ ADD_TRAIT(user, trait, CLOTHING_TRAIT)
+ user.update_sight()
+
+/obj/item/clothing/neck/eldritch_amulet/dropped(mob/user)
+ . = ..()
+ REMOVE_TRAIT(user, trait, CLOTHING_TRAIT)
+ user.update_sight()
+
+/obj/item/clothing/neck/eldritch_amulet/piercing
+ name = "piercing eldritch medallion"
+ desc = "A strange medallion. Peering through the crystalline surface, the light refracts into new and terrifying spectrums of color. You see yourself, reflected off cascading mirrors, warped into improbable shapes."
+ trait = TRAIT_XRAY_VISION
+
+/obj/item/clothing/head/hooded/cult_hoodie/eldritch
+ name = "ominous hood"
+ icon_state = "eldritch"
+ desc = "A torn, dust-caked hood. Strange eyes line the inside."
+ flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR
+ flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH
+ flash_protect = 2
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch
+ name = "ominous armor"
+ desc = "A ragged, dusty set of robes. Strange eyes line the inside."
+ icon_state = "eldritch_armor"
+ item_state = "eldritch_armor"
+ flags_inv = HIDESHOES|HIDEJUMPSUIT
+ body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS
+ allowed = list(/obj/item/melee/sickly_blade, /obj/item/forbidden_book)
+ hoodtype = /obj/item/clothing/head/hooded/cult_hoodie/eldritch
+ // slightly better than normal cult robes
+ armor = list("melee" = 50, "bullet" = 50, "laser" = 50,"energy" = 50, "bomb" = 35, "bio" = 20, "rad" = 0, "fire" = 20, "acid" = 20)
+
+/obj/item/reagent_containers/glass/beaker/eldritch
+ name = "flask of eldritch essence"
+ desc = "Toxic to the close minded. Healing to those with knowledge of the beyond."
+ icon = 'icons/obj/eldritch.dmi'
+ icon_state = "eldrich_flask"
+ list_reagents = list(/datum/reagent/eldritch = 50)
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm b/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm
new file mode 100644
index 0000000000..6906c53903
--- /dev/null
+++ b/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm
@@ -0,0 +1,302 @@
+
+/**
+ * #Eldritch Knwoledge
+ *
+ * Datum that makes eldritch cultist interesting.
+ *
+ * Eldritch knowledge aren't instantiated anywhere roundstart, and are initalized and destroyed as the round goes on.
+ */
+/datum/eldritch_knowledge
+ ///Name of the knowledge
+ var/name = "Basic knowledge"
+ ///Description of the knowledge
+ var/desc = "Basic knowledge of forbidden arts."
+ ///What shows up
+ var/gain_text = ""
+ ///Cost of knowledge in souls
+ var/cost = 0
+ ///Next knowledge in the research tree
+ var/list/next_knowledge = list()
+ ///What knowledge is incompatible with this. This will simply make it impossible to research knowledges that are in banned_knowledge once this gets researched.
+ var/list/banned_knowledge = list()
+ ///Used with rituals, how many items this needs
+ var/list/required_atoms = list()
+ ///What do we get out of this
+ var/list/result_atoms = list()
+ ///What path is this on defaults to "Side"
+ var/route = PATH_SIDE
+
+/datum/eldritch_knowledge/New()
+ . = ..()
+ var/list/temp_list
+ for(var/X in required_atoms)
+ var/atom/A = X
+ temp_list += list(typesof(A))
+ required_atoms = temp_list
+
+/**
+ * What happens when this is assigned to an antag datum
+ *
+ * This proc is called whenever a new eldritch knowledge is added to an antag datum
+ */
+/datum/eldritch_knowledge/proc/on_gain(mob/user)
+ to_chat(user, "[gain_text]")
+ return
+/**
+ * What happens when you loose this
+ *
+ * This proc is called whenever antagonist looses his antag datum, put cleanup code in here
+ */
+/datum/eldritch_knowledge/proc/on_lose(mob/user)
+ return
+/**
+ * What happens every tick
+ *
+ * This proc is called on SSprocess in eldritch cultist antag datum. SSprocess happens roughly every second
+ */
+/datum/eldritch_knowledge/proc/on_life(mob/user)
+ return
+
+/**
+ * Special check for recipes
+ *
+ * If you are adding a more complex summoning or something that requires a special check that parses through all the atoms in an area override this.
+ */
+/datum/eldritch_knowledge/proc/recipe_snowflake_check(list/atoms,loc)
+ return TRUE
+
+/**
+ * What happens once the recipe is succesfully finished
+ *
+ * By default this proc creates atoms from result_atoms list. Override this is you want something else to happen.
+ */
+/datum/eldritch_knowledge/proc/on_finished_recipe(mob/living/user,list/atoms,loc)
+ if(result_atoms.len == 0)
+ return FALSE
+
+ for(var/A in result_atoms)
+ new A(loc)
+
+ return TRUE
+
+/**
+ * Used atom cleanup
+ *
+ * Overide this proc if you dont want ALL ATOMS to be destroyed. useful in many situations.
+ */
+/datum/eldritch_knowledge/proc/cleanup_atoms(list/atoms)
+ for(var/X in atoms)
+ var/atom/A = X
+ if(!isliving(A))
+ atoms -= A
+ qdel(A)
+ return
+
+/**
+ * Mansus grasp act
+ *
+ * Gives addtional effects to mansus grasp spell
+ */
+/datum/eldritch_knowledge/proc/on_mansus_grasp(atom/target, mob/user, proximity_flag, click_parameters)
+ return FALSE
+
+
+/**
+ * Sickly blade act
+ *
+ * Gives addtional effects to sickly blade weapon
+ */
+/datum/eldritch_knowledge/proc/on_eldritch_blade(target,user,proximity_flag,click_parameters)
+ return
+
+//////////////
+///Subtypes///
+//////////////
+
+/datum/eldritch_knowledge/spell
+ var/obj/effect/proc_holder/spell/spell_to_add
+
+/datum/eldritch_knowledge/spell/on_gain(mob/user)
+ var/obj/effect/proc_holder/S = new spell_to_add
+ user.mind.AddSpell(S)
+ return ..()
+
+/datum/eldritch_knowledge/spell/on_lose(mob/user)
+ user.mind.RemoveSpell(spell_to_add)
+ return ..()
+
+/datum/eldritch_knowledge/curse
+ var/timer = 5 MINUTES
+ var/list/fingerprints = list()
+
+/datum/eldritch_knowledge/curse/recipe_snowflake_check(list/atoms, loc)
+ fingerprints = list()
+ for(var/X in atoms)
+ var/atom/A = X
+ fingerprints |= A.fingerprints
+ listclearnulls(fingerprints)
+ if(fingerprints.len == 0)
+ return FALSE
+ return TRUE
+
+/datum/eldritch_knowledge/curse/on_finished_recipe(mob/living/user,list/atoms,loc)
+
+ var/list/compiled_list = list()
+
+ for(var/H in GLOB.human_list)
+ var/mob/living/carbon/human/human_to_check = H
+ if(fingerprints[md5(human_to_check.dna.uni_identity)])
+ compiled_list |= human_to_check.real_name
+ compiled_list[human_to_check.real_name] = human_to_check
+
+ if(compiled_list.len == 0)
+ to_chat(user, "The items don't posses required fingerprints.")
+ return FALSE
+
+ var/chosen_mob = input("Select the person you wish to curse","Your target") as null|anything in sortList(compiled_list, /proc/cmp_mob_realname_dsc)
+ if(!chosen_mob)
+ return FALSE
+ curse(compiled_list[chosen_mob])
+ addtimer(CALLBACK(src, .proc/uncurse, compiled_list[chosen_mob]),timer)
+ return TRUE
+
+/datum/eldritch_knowledge/curse/proc/curse(mob/living/chosen_mob)
+ return
+
+/datum/eldritch_knowledge/curse/proc/uncurse(mob/living/chosen_mob)
+ return
+
+/datum/eldritch_knowledge/summon
+ //Mob to summon
+ var/mob/living/mob_to_summon
+
+
+/datum/eldritch_knowledge/summon/on_finished_recipe(mob/living/user,list/atoms,loc)
+ //we need to spawn the mob first so that we can use it in pollCandidatesForMob, we will move it from nullspace down the code
+ var/mob/living/summoned = new mob_to_summon(loc)
+ message_admins("[summoned.name] is being summoned by [user.real_name] in [loc]")
+ var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [summoned.name]", ROLE_HERETIC, null, FALSE, 100, summoned)
+ if(!LAZYLEN(candidates))
+ to_chat(user,"No ghost could be found...")
+ qdel(summoned)
+ return FALSE
+ var/mob/dead/observer/C = pick(candidates)
+ log_game("[key_name_admin(C)] has taken control of ([key_name_admin(summoned)]), their master is [user.real_name]")
+ summoned.ghostize(FALSE)
+ summoned.key = C.key
+ summoned.mind.add_antag_datum(/datum/antagonist/heretic_monster)
+ var/datum/antagonist/heretic_monster/heretic_monster = summoned.mind.has_antag_datum(/datum/antagonist/heretic_monster)
+ var/datum/antagonist/heretic/master = user.mind.has_antag_datum(/datum/antagonist/heretic)
+ heretic_monster.set_owner(master)
+ return TRUE
+
+//Ascension knowledge
+/datum/eldritch_knowledge/final
+ var/finished = FALSE
+
+/datum/eldritch_knowledge/final/recipe_snowflake_check(list/atoms, loc,selected_atoms)
+ if(finished)
+ return FALSE
+ var/counter = 0
+ for(var/mob/living/carbon/human/H in atoms)
+ selected_atoms |= H
+ counter++
+ if(counter == 3)
+ return TRUE
+ return FALSE
+
+/datum/eldritch_knowledge/final/on_finished_recipe( mob/living/user, list/atoms, loc)
+ finished = TRUE
+ return TRUE
+
+/datum/eldritch_knowledge/final/cleanup_atoms(list/atoms)
+ . = ..()
+ for(var/mob/living/carbon/human/H in atoms)
+ atoms -= H
+ H.gib()
+
+
+///////////////
+///Base lore///
+///////////////
+
+/datum/eldritch_knowledge/spell/basic
+ name = "Break of Dawn"
+ desc = "Starts your journey in the mansus. Allows you to select a target using a living heart on a transmutation rune."
+ gain_text = "Gates of Mansus open up to your mind."
+ next_knowledge = list(/datum/eldritch_knowledge/base_rust,/datum/eldritch_knowledge/base_ash,/datum/eldritch_knowledge/base_flesh)
+ cost = 0
+ spell_to_add = /obj/effect/proc_holder/spell/targeted/touch/mansus_grasp
+ required_atoms = list(/obj/item/living_heart)
+ route = "Start"
+
+/datum/eldritch_knowledge/spell/basic/recipe_snowflake_check(list/atoms, loc)
+ . = ..()
+ for(var/obj/item/living_heart/LH in atoms)
+ if(!LH.target)
+ return TRUE
+ if(LH.target in atoms)
+ return TRUE
+ return FALSE
+
+/datum/eldritch_knowledge/spell/basic/on_finished_recipe(mob/living/user, list/atoms, loc)
+ . = TRUE
+ var/mob/living/carbon/carbon_user = user
+ for(var/obj/item/living_heart/LH in atoms)
+
+ if(LH.target && LH.target.stat == UNCONSCIOUS)
+ to_chat(carbon_user,"Your patrons accepts your offer..")
+ var/mob/living/carbon/human/H = LH.target
+ H.death(0)
+ LH.target = null
+ var/datum/antagonist/heretic/EC = carbon_user.mind.has_antag_datum(/datum/antagonist/heretic)
+
+ EC.total_sacrifices++
+ for(var/X in carbon_user.get_all_gear())
+ if(!istype(X,/obj/item/forbidden_book))
+ continue
+ var/obj/item/forbidden_book/FB = X
+ FB.charge++
+ break
+
+ if(!LH.target)
+ var/datum/objective/A = new
+ A.owner = user.mind
+ var/datum/mind/targeted = A.find_target()//easy way, i dont feel like copy pasting that entire block of code
+ LH.target = targeted.current
+ qdel(A)
+ if(LH.target)
+ to_chat(user,"Your new target has been selected, go and sacrifice [LH.target.real_name]!")
+
+ else
+ to_chat(user,"target could not be found for living heart.")
+
+/datum/eldritch_knowledge/spell/basic/cleanup_atoms(list/atoms)
+ return
+
+/datum/eldritch_knowledge/living_heart
+ name = "Living Heart"
+ desc = "Allows you to create additional living hearts, using a heart, a pool of blood and a poppy. Living hearts when used on a transmutation rune will grant you a person to hunt and sacrifice on the rune. Every sacrifice gives you an additional charge in the book."
+ gain_text = "Disconnected, yet it still beats."
+ cost = 0
+ required_atoms = list(/obj/item/organ/heart,/obj/effect/decal/cleanable/blood,/obj/item/reagent_containers/food/snacks/grown/poppy)
+ result_atoms = list(/obj/item/living_heart)
+ route = "Start"
+
+/datum/eldritch_knowledge/codex_cicatrix
+ name = "Codex Cicatrix"
+ desc = "Allows you to create a spare Codex Cicatrix if you have lost one, using a bible, human skin, a pen and a pair of eyes."
+ gain_text = "Their hands are at your throat, yet you see them not."
+ cost = 0
+ required_atoms = list(/obj/item/organ/eyes,/obj/item/stack/sheet/animalhide/human,/obj/item/storage/book/bible,/obj/item/pen)
+ result_atoms = list(/obj/item/forbidden_book)
+ route = "Start"
+
+/datum/eldritch_knowledge/eldritch_blade
+ name = "Eldritch Blade"
+ desc = "Allows you to create a sickly, eldritch blade by transmuting a glass shard and a metal rod atop a transmutation rune."
+ gain_text = "The first step starts with sacrifice."
+ cost = 0
+ required_atoms = list(/obj/item/shard,/obj/item/stack/rods)
+ result_atoms = list(/obj/item/melee/sickly_blade)
+ route = "Start"
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_magic.dm b/code/modules/antagonists/eldritch_cult/eldritch_magic.dm
new file mode 100644
index 0000000000..dce681388b
--- /dev/null
+++ b/code/modules/antagonists/eldritch_cult/eldritch_magic.dm
@@ -0,0 +1,668 @@
+/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash
+ name = "Ashen Passage"
+ desc = "Low range spell allowing you to pass through a few walls."
+ school = "transmutation"
+ invocation = "DULK'ES PRE'ZIMAS"
+ invocation_type = "whisper"
+ charge_max = 150
+ range = -1
+ action_icon = 'icons/mob/actions/actions_ecult.dmi'
+ action_icon_state = "ash_shift"
+ action_background_icon_state = "bg_ecult"
+ jaunt_in_time = 13
+ jaunt_duration = 10
+ jaunt_in_type = /obj/effect/temp_visual/dir_setting/ash_shift
+ jaunt_out_type = /obj/effect/temp_visual/dir_setting/ash_shift/out
+
+/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash/long
+ jaunt_duration = 50
+
+/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash/play_sound()
+ return
+
+/obj/effect/temp_visual/dir_setting/ash_shift
+ name = "ash_shift"
+ icon = 'icons/mob/mob.dmi'
+ icon_state = "ash_shift2"
+ duration = 13
+
+/obj/effect/temp_visual/dir_setting/ash_shift/out
+ icon_state = "ash_shift"
+
+/obj/effect/proc_holder/spell/targeted/touch/mansus_grasp
+ name = "Mansus Grasp"
+ desc = "Touch spell that allows you to channel the power of the Old Gods through you."
+ hand_path = /obj/item/melee/touch_attack/mansus_fist
+ school = "evocation"
+ charge_max = 150
+ clothes_req = FALSE
+ action_icon = 'icons/mob/actions/actions_ecult.dmi'
+ action_icon_state = "mansus_grasp"
+ action_background_icon_state = "bg_ecult"
+
+/obj/item/melee/touch_attack/mansus_fist
+ name = "Mansus Grasp"
+ desc = "A sinister looking aura that distorts the flow of reality around it. Causes knockdown, major stamina damage aswell as some Brute. It gains additional beneficial effects with certain knowledges you can research."
+ icon_state = "disintegrate"
+ item_state = "disintegrate"
+ catchphrase = "T'IESA SIE'KTI VISATA"
+
+/obj/item/melee/touch_attack/mansus_fist/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
+
+ if(!proximity_flag || target == user)
+ return
+ playsound(user, 'sound/items/welder.ogg', 75, TRUE)
+ if(ishuman(target))
+ var/mob/living/carbon/human/tar = target
+ if(tar.anti_magic_check())
+ tar.visible_message("Spell bounces off of [target]!","The spell bounces off of you!")
+ return ..()
+ var/datum/mind/M = user.mind
+ var/datum/antagonist/heretic/cultie = M.has_antag_datum(/datum/antagonist/heretic)
+
+ var/use_charge = FALSE
+ if(iscarbon(target))
+ use_charge = TRUE
+ var/mob/living/carbon/C = target
+ C.adjustBruteLoss(15)
+ C.AdjustKnockdown(5 SECONDS)
+ C.adjustStaminaLoss(40)
+ var/list/knowledge = cultie.get_all_knowledge()
+
+ for(var/X in knowledge)
+ var/datum/eldritch_knowledge/EK = knowledge[X]
+ if(EK.on_mansus_grasp(target, user, proximity_flag, click_parameters))
+ use_charge = TRUE
+ if(use_charge)
+ return ..()
+
+/obj/effect/proc_holder/spell/aoe_turf/rust_conversion
+ name = "Aggressive Spread"
+ desc = "Spreads rust onto nearby turfs."
+ school = "transmutation"
+ charge_max = 300 //twice as long as mansus grasp
+ clothes_req = FALSE
+ invocation = "PLI'STI MINO DOMI'KA"
+ invocation_type = "whisper"
+ range = 3
+ action_icon = 'icons/mob/actions/actions_ecult.dmi'
+ action_icon_state = "corrode"
+ action_background_icon_state = "bg_ecult"
+
+/obj/effect/proc_holder/spell/aoe_turf/rust_conversion/cast(list/targets, mob/user = usr)
+ playsound(user, 'sound/items/welder.ogg', 75, TRUE)
+ for(var/turf/T in targets)
+ ///What we want is the 3 tiles around the user and the tile under him to be rusted, so min(dist,1)-1 causes us to get 0 for these tiles, rest of the tiles are based on chance
+ var/chance = 100 - (max(get_dist(T,user),1)-1)*100/(range+1)
+ if(!prob(chance))
+ continue
+ T.rust_heretic_act()
+
+/obj/effect/proc_holder/spell/aoe_turf/rust_conversion/small
+ name = "Rust Conversion"
+ desc = "Spreads rust onto nearby turfs."
+ range = 2
+
+/obj/effect/proc_holder/spell/targeted/touch/blood_siphon
+ name = "Blood Siphon"
+ desc = "Touch spell that heals you while damaging the enemy, has a chance to transfer wounds between you and your enemy."
+ hand_path = /obj/item/melee/touch_attack/blood_siphon
+ school = "evocation"
+ charge_max = 150
+ clothes_req = FALSE
+ invocation_type = "none"
+ action_icon = 'icons/mob/actions/actions_ecult.dmi'
+ action_icon_state = "blood_siphon"
+ action_background_icon_state = "bg_ecult"
+
+/obj/item/melee/touch_attack/blood_siphon
+ name = "Blood Siphon"
+ desc = "A sinister looking aura that distorts the flow of reality around it."
+ icon_state = "disintegrate"
+ item_state = "disintegrate"
+ catchphrase = "SUN'AI'KINI'MAS"
+
+/obj/item/melee/touch_attack/blood_siphon/afterattack(atom/target, mob/user, proximity_flag, proximity)
+ if(!proximity_flag)
+ return
+ playsound(user, 'sound/effects/curseattack.ogg', 75, TRUE)
+ if(ishuman(target))
+ var/mob/living/carbon/human/tar = target
+ if(tar.anti_magic_check())
+ tar.visible_message("Spell bounces off of [target]!","The spell bounces off of you!")
+ return ..()
+ var/mob/living/carbon/C2 = user
+ if(isliving(target))
+ var/mob/living/L = target
+ L.adjustBruteLoss(20)
+ C2.adjustBruteLoss(-20)
+ if(iscarbon(target))
+ var/mob/living/carbon/C1 = target
+ for(var/obj/item/bodypart/bodypart in C2.bodyparts)
+ for(var/i in bodypart.wounds)
+ var/datum/wound/iter_wound = i
+ if(prob(50))
+ continue
+ var/obj/item/bodypart/target_bodypart = locate(bodypart.type) in C1.bodyparts
+ if(!target_bodypart)
+ continue
+ iter_wound.remove_wound()
+ iter_wound.apply_wound(target_bodypart)
+
+ C1.blood_volume -= 20
+ if(C2.blood_volume < BLOOD_VOLUME_MAXIMUM) //we dont want to explode after all
+ C2.blood_volume += 20
+ return ..()
+
+/obj/effect/proc_holder/spell/aimed/rust_wave
+ name = "Patron's Reach"
+ desc = "Channels energy into your gauntlet - firing it results in a wave of rust being created in it's wake."
+ projectile_type = /obj/item/projectile/magic/spell/rust_wave
+ charge_max = 350
+ clothes_req = FALSE
+ action_icon = 'icons/mob/actions/actions_ecult.dmi'
+ base_icon_state = "rust_wave"
+ action_icon_state = "rust_wave"
+ action_background_icon_state = "bg_ecult"
+ sound = 'sound/effects/curse5.ogg'
+ active_msg = "You extend your hand out, preparing to send out a wave of rust."
+ deactive_msg = "You extinguish that energy, for now..."
+ invocation = "RUD'ZI VAR'ZTAS"
+ invocation_type = "whisper"
+
+/obj/item/projectile/magic/spell/rust_wave
+ name = "rust bolt"
+ icon_state = "eldritch_projectile"
+ alpha = 180
+ damage = 30
+ damage_type = TOX
+ nodamage = 0
+ hitsound = 'sound/effects/curseattack.ogg'
+ range = 15
+
+/obj/item/projectile/magic/spell/rust_wave/Moved(atom/OldLoc, Dir)
+ . = ..()
+ playsound(src, 'sound/items/welder.ogg', 75, TRUE)
+ var/list/turflist = list()
+ var/turf/T1
+ turflist += get_turf(src)
+ T1 = get_step(src,turn(dir,90))
+ turflist += T1
+ turflist += get_step(T1,turn(dir,90))
+ T1 = get_step(src,turn(dir,-90))
+ turflist += T1
+ turflist += get_step(T1,turn(dir,-90))
+ for(var/X in turflist)
+ if(!X || prob(25))
+ continue
+ var/turf/T = X
+ T.rust_heretic_act()
+
+/obj/effect/proc_holder/spell/aimed/rust_wave/short
+ name = "Small Patron's Reach"
+ projectile_type = /obj/item/projectile/magic/spell/rust_wave/short
+
+/obj/item/projectile/magic/spell/rust_wave/short
+ range = 7
+
+/obj/effect/proc_holder/spell/pointed/cleave
+ name = "Cleave"
+ desc = "Causes severe bleeding on a target and people around them"
+ school = "transmutation"
+ charge_max = 350
+ clothes_req = FALSE
+ invocation = "PLES'TI VI'RIBUS"
+ invocation_type = "whisper"
+ range = 9
+ action_icon = 'icons/mob/actions/actions_ecult.dmi'
+ action_icon_state = "cleave"
+ action_background_icon_state = "bg_ecult"
+
+/obj/effect/proc_holder/spell/pointed/cleave/cast(list/targets, mob/user)
+ if(!targets.len)
+ to_chat(user, "No target found in range!")
+ return FALSE
+ if(!can_target(targets[1], user))
+ return FALSE
+
+ for(var/mob/living/carbon/human/C in range(1,targets[1]))
+ targets |= C
+
+
+ for(var/X in targets)
+ var/mob/living/carbon/human/target = X
+ if(target == user)
+ continue
+ if(target.anti_magic_check())
+ to_chat(user, "The spell had no effect!")
+ target.visible_message("[target]'s veins flash with fire, but their magic protection repulses the blaze!", \
+ "Your veins flash with fire, but your magic protection repels the blaze!")
+ continue
+
+ target.visible_message("[target]'s veins are shredded from within as an unholy blaze erupts from their blood!", \
+ "Your veins burst from within and unholy flame erupts from your blood!")
+ var/obj/item/bodypart/bodypart = pick(target.bodyparts)
+ var/datum/wound/slash/critical/crit_wound = new
+ crit_wound.apply_wound(bodypart)
+ target.adjustFireLoss(20)
+ new /obj/effect/temp_visual/cleave(target.drop_location())
+
+/obj/effect/proc_holder/spell/pointed/cleave/can_target(atom/target, mob/user, silent)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(!istype(target,/mob/living/carbon/human))
+ if(!silent)
+ to_chat(user, "You are unable to cleave [target]!")
+ return FALSE
+ return TRUE
+
+/obj/effect/proc_holder/spell/pointed/cleave/long
+ charge_max = 650
+
+/obj/effect/proc_holder/spell/pointed/touch/mad_touch
+ name = "Touch of Madness"
+ desc = "Touch spell that drains your enemies sanity."
+ school = "transmutation"
+ charge_max = 150
+ clothes_req = FALSE
+ invocation_type = "none"
+ range = 2
+ action_icon = 'icons/mob/actions/actions_ecult.dmi'
+ action_icon_state = "mad_touch"
+ action_background_icon_state = "bg_ecult"
+
+/obj/effect/proc_holder/spell/pointed/touch/mad_touch/can_target(atom/target, mob/user, silent)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(!istype(target,/mob/living/carbon/human))
+ if(!silent)
+ to_chat(user, "You are unable to touch [target]!")
+ return FALSE
+ return TRUE
+
+/obj/effect/proc_holder/spell/pointed/touch/mad_touch/cast(list/targets, mob/user)
+ . = ..()
+ for(var/mob/living/carbon/target in targets)
+ if(ishuman(targets))
+ var/mob/living/carbon/human/tar = target
+ if(tar.anti_magic_check())
+ tar.visible_message("Spell bounces off of [target]!","The spell bounces off of you!")
+ return
+ if(target.mind && !target.mind.has_antag_datum(/datum/antagonist/heretic))
+ to_chat(user,"[target.name] has been cursed!")
+ SEND_SIGNAL(target, COMSIG_ADD_MOOD_EVENT, "gates_of_mansus", /datum/mood_event/gates_of_mansus)
+
+/obj/effect/proc_holder/spell/pointed/ash_final
+ name = "Nightwatcher's Rite"
+ desc = "Powerful spell that releases 5 streams of fire away from you."
+ school = "transmutation"
+ invocation = "IGNIS'INTI"
+ invocation_type = "whisper"
+ charge_max = 300
+ range = 15
+ clothes_req = FALSE
+ action_icon = 'icons/mob/actions/actions_ecult.dmi'
+ action_icon_state = "flames"
+ action_background_icon_state = "bg_ecult"
+
+/obj/effect/proc_holder/spell/pointed/ash_final/cast(list/targets, mob/user)
+ for(var/X in targets)
+ var/T
+ T = line_target(-25, range, X, user)
+ INVOKE_ASYNC(src, .proc/fire_line, user,T)
+ T = line_target(10, range, X, user)
+ INVOKE_ASYNC(src, .proc/fire_line, user,T)
+ T = line_target(0, range, X, user)
+ INVOKE_ASYNC(src, .proc/fire_line, user,T)
+ T = line_target(-10, range, X, user)
+ INVOKE_ASYNC(src, .proc/fire_line, user,T)
+ T = line_target(25, range, X, user)
+ INVOKE_ASYNC(src, .proc/fire_line, user,T)
+ return ..()
+
+/obj/effect/proc_holder/spell/pointed/ash_final/proc/line_target(offset, range, atom/at , atom/user)
+ if(!at)
+ return
+ var/angle = ATAN2(at.x - user.x, at.y - user.y) + offset
+ var/turf/T = get_turf(user)
+ for(var/i in 1 to range)
+ var/turf/check = locate(user.x + cos(angle) * i, user.y + sin(angle) * i, user.z)
+ if(!check)
+ break
+ T = check
+ return (getline(user, T) - get_turf(user))
+
+/obj/effect/proc_holder/spell/pointed/ash_final/proc/fire_line(atom/source, list/turfs)
+ var/list/hit_list = list()
+ for(var/turf/T in turfs)
+ if(istype(T, /turf/closed))
+ break
+
+ for(var/mob/living/L in T.contents)
+ if(L.anti_magic_check())
+ L.visible_message("Spell bounces off of [L]!","The spell bounces off of you!")
+ continue
+ if(L in hit_list || L == source)
+ continue
+ hit_list += L
+ L.adjustFireLoss(20)
+ to_chat(L, "You're hit by [source]'s fire breath!")
+
+ new /obj/effect/hotspot(T)
+ T.hotspot_expose(700,50,1)
+ // deals damage to mechs
+ for(var/obj/mecha/M in T.contents)
+ if(M in hit_list)
+ continue
+ hit_list += M
+ M.take_damage(45, BURN, "melee", 1)
+ sleep(1.5)
+
+/obj/effect/proc_holder/spell/targeted/shapeshift/eldritch
+ invocation_type = "none"
+ clothes_req = FALSE
+ action_background_icon_state = "bg_ecult"
+ sound = 'sound/magic/enter_blood.ogg'
+ possible_shapes = list(/mob/living/simple_animal/mouse,\
+ /mob/living/simple_animal/pet/dog/corgi,\
+ /mob/living/simple_animal/hostile/carp,\
+ /mob/living/simple_animal/bot/secbot, \
+ /mob/living/simple_animal/pet/fox,\
+ /mob/living/simple_animal/pet/cat )
+
+/obj/effect/proc_holder/spell/targeted/emplosion/eldritch
+ name = "Energetic Pulse"
+ invocation_type = "none"
+ clothes_req = FALSE
+ action_background_icon_state = "bg_ecult"
+ range = -1
+ include_user = TRUE
+ charge_max = 300
+ emp_heavy = 6
+ emp_light = 10
+ sound = 'sound/effects/lingscreech.ogg'
+
+/obj/effect/proc_holder/spell/aoe_turf/fire_cascade
+ name = "Fire Cascade"
+ desc = "creates hot turfs around you."
+ school = "transmutation"
+ charge_max = 300 //twice as long as mansus grasp
+ clothes_req = FALSE
+ invocation = "IGNIS'SAVARIN"
+ invocation_type = "whisper"
+ range = 4
+ action_icon = 'icons/mob/actions/actions_ecult.dmi'
+ action_icon_state = "fire_ring"
+ action_background_icon_state = "bg_ecult"
+
+/obj/effect/proc_holder/spell/aoe_turf/fire_cascade/cast(list/targets, mob/user = usr)
+ INVOKE_ASYNC(src, .proc/fire_cascade, user,range)
+
+/obj/effect/proc_holder/spell/aoe_turf/fire_cascade/proc/fire_cascade(atom/centre,max_range)
+ playsound(get_turf(centre), 'sound/items/welder.ogg', 75, TRUE)
+ var/_range = 1
+ for(var/i = 0, i <= max_range,i++)
+ for(var/turf/T in spiral_range_turfs(_range,centre))
+ new /obj/effect/hotspot(T)
+ T.hotspot_expose(700,50,1)
+ for(var/mob/living/livies in T.contents - centre)
+ livies.adjustFireLoss(10)
+ _range++
+ sleep(3)
+
+/obj/effect/proc_holder/spell/aoe_turf/fire_cascade/big
+ range = 6
+
+/obj/effect/proc_holder/spell/targeted/telepathy/eldritch
+ invocation = ""
+ invocation_type = "whisper"
+ clothes_req = FALSE
+ action_background_icon_state = "bg_ecult"
+
+/obj/effect/proc_holder/spell/targeted/fire_sworn
+ name = "Oath of Fire"
+ desc = "For a minute you will passively create a ring of fire around you."
+ invocation = "IGNIS'AISTRA'LISTRE"
+ invocation_type = "whisper"
+ clothes_req = FALSE
+ action_background_icon_state = "bg_ecult"
+ range = -1
+ include_user = TRUE
+ charge_max = 700
+ action_icon = 'icons/mob/actions/actions_ecult.dmi'
+ action_icon_state = "fire_ring"
+ ///how long it lasts
+ var/duration = 1 MINUTES
+ ///who casted it right now
+ var/mob/current_user
+ ///Determines if you get the fire ring effect
+ var/has_fire_ring = FALSE
+
+/obj/effect/proc_holder/spell/targeted/fire_sworn/cast(list/targets, mob/user)
+ . = ..()
+ current_user = user
+ has_fire_ring = TRUE
+ addtimer(CALLBACK(src, .proc/remove, user), duration, TIMER_OVERRIDE|TIMER_UNIQUE)
+
+/obj/effect/proc_holder/spell/targeted/fire_sworn/proc/remove()
+ has_fire_ring = FALSE
+
+/obj/effect/proc_holder/spell/targeted/fire_sworn/process()
+ . = ..()
+ if(!has_fire_ring)
+ return
+ for(var/turf/T in range(1,current_user))
+ new /obj/effect/hotspot(T)
+ T.hotspot_expose(700,50,1)
+ for(var/mob/living/livies in T.contents - current_user)
+ livies.adjustFireLoss(5)
+
+
+/obj/effect/proc_holder/spell/targeted/worm_contract
+ name = "Force Contract"
+ desc = "Forces all the worm parts to collapse onto a single turf"
+ invocation_type = "none"
+ clothes_req = FALSE
+ action_background_icon_state = "bg_ecult"
+ range = -1
+ include_user = TRUE
+ charge_max = 300
+ action_icon = 'icons/mob/actions/actions_ecult.dmi'
+ action_icon_state = "worm_contract"
+
+/obj/effect/proc_holder/spell/targeted/worm_contract/cast(list/targets, mob/user)
+ . = ..()
+ if(!istype(user,/mob/living/simple_animal/hostile/eldritch/armsy))
+ to_chat(user, "You try to contract your muscles but nothing happens...")
+ var/mob/living/simple_animal/hostile/eldritch/armsy/armsy = user
+ armsy.contract_next_chain_into_single_tile()
+
+/obj/effect/temp_visual/cleave
+ icon = 'icons/effects/eldritch.dmi'
+ icon_state = "cleave"
+ duration = 6
+
+/obj/effect/temp_visual/eldritch_smoke
+ icon = 'icons/effects/eldritch.dmi'
+ icon_state = "smoke"
+ duration = 10
+
+/obj/effect/proc_holder/spell/targeted/fiery_rebirth
+ name = "Nightwatcher's Rebirth"
+ desc = "Drains nearby alive people that are engulfed in flames. It heals 10 of each damage type per person. If a person is in critical condition it finishes them off."
+ invocation = "PETHRO'MINO'IGNI"
+ invocation_type = "whisper"
+ clothes_req = FALSE
+ action_background_icon_state = "bg_ecult"
+ range = -1
+ include_user = TRUE
+ charge_max = 600
+ action_icon = 'icons/mob/actions/actions_ecult.dmi'
+ action_icon_state = "smoke"
+
+/obj/effect/proc_holder/spell/targeted/fiery_rebirth/cast(list/targets, mob/user)
+ if(!ishuman(user))
+ return
+ var/mob/living/carbon/human/human_user = user
+ for(var/mob/living/carbon/target in view(7,user))
+ if(target.stat == DEAD || !target.on_fire)
+ continue
+ //This is essentially a death mark, use this to finish your opponent quicker.
+ if(target.InCritical())
+ target.death()
+ target.adjustFireLoss(20)
+ new /obj/effect/temp_visual/eldritch_smoke(target.drop_location())
+ human_user.ExtinguishMob()
+ human_user.adjustBruteLoss(-10, FALSE)
+ human_user.adjustFireLoss(-10, FALSE)
+ human_user.adjustStaminaLoss(-10, FALSE)
+ human_user.adjustToxLoss(-10, FALSE)
+ human_user.adjustOxyLoss(-10)
+
+/obj/effect/proc_holder/spell/pointed/manse_link
+ name = "Mansus Link"
+ desc = "Piercing through reality, connecting minds. This spell allows you to add people to a mansus net, allowing them to communicate with eachother"
+ school = "transmutation"
+ charge_max = 300
+ clothes_req = FALSE
+ invocation = "SUSEI' METO MIN'TIS"
+ invocation_type = "whisper"
+ range = 10
+ action_icon = 'icons/mob/actions/actions_ecult.dmi'
+ action_icon_state = "mansus_link"
+ action_background_icon_state = "bg_ecult"
+
+/obj/effect/proc_holder/spell/pointed/manse_link/can_target(atom/target, mob/user, silent)
+ if(!isliving(target))
+ return FALSE
+ return TRUE
+
+/obj/effect/proc_holder/spell/pointed/manse_link/cast(list/targets, mob/user)
+ var/mob/living/simple_animal/hostile/eldritch/raw_prophet/originator = user
+
+ var/mob/living/target = targets[1]
+
+ to_chat(originator, "You begin linking [target]'s mind to yours...")
+ to_chat(target, "You feel your mind being pulled... connected... intertwined with the very fabric of reality...")
+ if(!do_after(originator, 6 SECONDS, target))
+ return
+ if(!originator.link_mob(target))
+ to_chat(originator, "You can't seem to link [target]'s mind...")
+ to_chat(target, "The foreign presence leaves your mind.")
+ return
+ to_chat(originator, "You connect [target]'s mind to your mansus link!")
+
+
+/datum/action/innate/mansus_speech
+ name = "Mansus Link"
+ desc = "Send a psychic message to everyone connected to your mansus link."
+ button_icon_state = "link_speech"
+ icon_icon = 'icons/mob/actions/actions_slime.dmi'
+ background_icon_state = "bg_ecult"
+ var/mob/living/simple_animal/hostile/eldritch/raw_prophet/originator
+
+/datum/action/innate/mansus_speech/New(_originator)
+ . = ..()
+ originator = _originator
+
+/datum/action/innate/mansus_speech/Activate()
+ var/mob/living/living_owner = owner
+ if(!originator?.linked_mobs[living_owner])
+ CRASH("Uh oh the mansus link got somehow activated without it being linked to a raw prophet or the mob not being in a list of mobs that should be able to do it.")
+
+ var/message = sanitize(input("Message:", "Telepathy from the Manse") as text|null)
+
+ if(QDELETED(living_owner))
+ return
+
+ if(!originator?.linked_mobs[living_owner])
+ to_chat(living_owner, "The link seems to have been severed...")
+ Remove(living_owner)
+ return
+ if(message)
+ var/msg = "\[Mansus Link\] [living_owner]: [message]"
+ log_directed_talk(living_owner, originator, msg, LOG_SAY, "Mansus Link")
+ to_chat(originator.linked_mobs, msg)
+
+ for(var/dead_mob in GLOB.dead_mob_list)
+ var/link = FOLLOW_LINK(dead_mob, living_owner)
+ to_chat(dead_mob, "[link] [msg]")
+
+/obj/effect/proc_holder/spell/pointed/trigger/blind/eldritch
+ range = 10
+ invocation = "AK'LIS"
+ action_background_icon_state = "bg_ecult"
+
+/obj/effect/temp_visual/dir_setting/entropic
+ icon = 'icons/effects/160x160.dmi'
+ icon_state = "entropic_plume"
+ duration = 3 SECONDS
+
+/obj/effect/temp_visual/dir_setting/entropic/setDir(dir)
+ . = ..()
+ switch(dir)
+ if(NORTH)
+ pixel_x = -64
+ if(SOUTH)
+ pixel_x = -64
+ pixel_y = -128
+ if(EAST)
+ pixel_y = -64
+ if(WEST)
+ pixel_y = -64
+ pixel_x = -128
+
+/obj/effect/temp_visual/glowing_rune
+ icon = 'icons/effects/eldritch.dmi'
+ icon_state = "small_rune_1"
+ duration = 1 MINUTES
+ layer = LOW_SIGIL_LAYER
+
+/obj/effect/temp_visual/glowing_rune/Initialize()
+ . = ..()
+ pixel_y = rand(-6,6)
+ pixel_x = rand(-6,6)
+ icon_state = "small_rune_[rand(12)]"
+ update_icon()
+
+/obj/effect/proc_holder/spell/cone/staggered/entropic_plume
+ name = "Entropic Plume"
+ desc = "Spews forth a disorienting plume that causes enemies to strike each other, briefly blinds them(increasing with range) and poisons them(decreasing with range). Also spreads rust in the path of the plume."
+ school = "illusion"
+ invocation = "RU'KAS NU'DYTI"
+ invocation_type = "whisper"
+ clothes_req = FALSE
+ action_background_icon_state = "bg_ecult"
+ action_icon = 'icons/mob/actions/actions_ecult.dmi'
+ action_icon_state = "entropic_plume"
+ charge_max = 300
+ cone_levels = 5
+ respect_density = TRUE
+
+/obj/effect/proc_holder/spell/cone/staggered/entropic_plume/cast(list/targets,mob/user = usr)
+ . = ..()
+ new /obj/effect/temp_visual/dir_setting/entropic(get_step(user,user.dir), user.dir)
+
+/obj/effect/proc_holder/spell/cone/staggered/entropic_plume/do_turf_cone_effect(turf/target_turf, level)
+ . = ..()
+ target_turf.rust_heretic_act()
+
+/obj/effect/proc_holder/spell/cone/staggered/entropic_plume/do_mob_cone_effect(mob/living/victim, level)
+ . = ..()
+ if(victim.anti_magic_check() || IS_HERETIC(victim) || victim.mind?.has_antag_datum(/datum/antagonist/heretic_monster))
+ return
+ victim.apply_status_effect(STATUS_EFFECT_AMOK)
+ victim.apply_status_effect(STATUS_EFFECT_CLOUDSTRUCK, (level*10))
+ if(iscarbon(victim))
+ var/mob/living/carbon/carbon_victim = victim
+ carbon_victim.reagents.add_reagent(/datum/reagent/eldritch, min(1, 6-level))
+
+/obj/effect/proc_holder/spell/cone/staggered/entropic_plume/calculate_cone_shape(current_level)
+ if(current_level == cone_levels)
+ return 5
+ else if(current_level == cone_levels-1)
+ return 3
+ else
+ return 2
diff --git a/code/modules/antagonists/eldritch_cult/eldritch_monster_antag.dm b/code/modules/antagonists/eldritch_cult/eldritch_monster_antag.dm
new file mode 100644
index 0000000000..529128fc0a
--- /dev/null
+++ b/code/modules/antagonists/eldritch_cult/eldritch_monster_antag.dm
@@ -0,0 +1,43 @@
+///Tracking reasons
+/datum/antagonist/heretic_monster
+ name = "Eldritch Horror"
+ roundend_category = "Heretics"
+ antagpanel_category = "Heretic Beast"
+ antag_moodlet = /datum/mood_event/heretics
+ job_rank = ROLE_HERETIC
+ antag_hud_type = ANTAG_HUD_HERETIC
+ antag_hud_name = "heretic_beast"
+ var/datum/antagonist/master
+
+/datum/antagonist/heretic_monster/admin_add(datum/mind/new_owner,mob/admin)
+ new_owner.add_antag_datum(src)
+ message_admins("[key_name_admin(admin)] has heresized [key_name_admin(new_owner)].")
+ log_admin("[key_name(admin)] has heresized [key_name(new_owner)].")
+
+/datum/antagonist/heretic_monster/greet()
+ owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/ecult_op.ogg', 100, FALSE, pressure_affected = FALSE)//subject to change
+ to_chat(owner, "You became an Eldritch Horror!")
+
+/datum/antagonist/heretic_monster/on_removal()
+ if(owner)
+ to_chat(owner, "Your master is no longer [master.owner.current.real_name]")
+ owner = null
+ return ..()
+
+/datum/antagonist/heretic_monster/proc/set_owner(datum/antagonist/_master)
+ master = _master
+ var/datum/objective/master_obj = new
+ master_obj.owner = src
+ master_obj.explanation_text = "Assist your master in any way you can!"
+ objectives += master_obj
+ owner.announce_objectives()
+ to_chat(owner, "Your master is [master.owner.current.real_name]")
+ return
+
+/datum/antagonist/heretic_monster/apply_innate_effects(mob/living/mob_override)
+ . = ..()
+ add_antag_hud(antag_hud_type, antag_hud_name, owner.current)
+
+/datum/antagonist/heretic_monster/remove_innate_effects(mob/living/mob_override)
+ . = ..()
+ remove_antag_hud(antag_hud_type, owner.current)
diff --git a/code/modules/antagonists/eldritch_cult/knowledge/ash_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/ash_lore.dm
new file mode 100644
index 0000000000..247adea343
--- /dev/null
+++ b/code/modules/antagonists/eldritch_cult/knowledge/ash_lore.dm
@@ -0,0 +1,183 @@
+/datum/eldritch_knowledge/base_ash
+ name = "Nightwatcher's Secret"
+ desc = "Opens up the path of ash to you. Allows you to transmute a match with a kitchen knife or it's derivatives into an ashen blade."
+ gain_text = "City Guard knows their watch. If you ask them at night, they may just tell you about the Ashy Lantern."
+ banned_knowledge = list(/datum/eldritch_knowledge/base_rust,/datum/eldritch_knowledge/base_flesh,/datum/eldritch_knowledge/final/rust_final,/datum/eldritch_knowledge/final/flesh_final)
+ next_knowledge = list(/datum/eldritch_knowledge/ashen_grasp)
+ required_atoms = list(/obj/item/melee/sickly_blade,/obj/item/match)
+ result_atoms = list(/obj/item/melee/sickly_blade/ash)
+ cost = 1
+ route = PATH_ASH
+
+/datum/eldritch_knowledge/spell/ashen_shift
+ name = "Ashen Shift"
+ gain_text = "Ash is all the same, how can one man master it all?"
+ desc = "Short range jaunt that can help you escape from bad situations."
+ cost = 1
+ spell_to_add = /obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash
+ next_knowledge = list(/datum/eldritch_knowledge/ash_mark,/datum/eldritch_knowledge/essence,/datum/eldritch_knowledge/ashen_eyes)
+ route = PATH_ASH
+
+/datum/eldritch_knowledge/ashen_grasp
+ name = "Grasp of Ash"
+ gain_text = "Gates have opened, minds have flooded, I remain."
+ desc = "Empowers your mansus grasp to throw away enemies."
+ cost = 1
+ next_knowledge = list(/datum/eldritch_knowledge/spell/ashen_shift)
+ route = PATH_ASH
+
+/datum/eldritch_knowledge/ashen_grasp/on_mansus_grasp(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ if(!iscarbon(target))
+ return
+
+ var/mob/living/carbon/C = target
+ var/datum/status_effect/eldritch/E = C.has_status_effect(/datum/status_effect/eldritch/rust) || C.has_status_effect(/datum/status_effect/eldritch/ash) || C.has_status_effect(/datum/status_effect/eldritch/flesh)
+ if(E)
+ . = TRUE
+ E.on_effect()
+ for(var/X in user.mind.spell_list)
+ if(!istype(X,/obj/effect/proc_holder/spell/targeted/touch/mansus_grasp))
+ continue
+ var/obj/effect/proc_holder/spell/targeted/touch/mansus_grasp/MG = X
+ MG.charge_counter = min(round(MG.charge_counter + MG.charge_max * 0.5),MG.charge_max) // refunds 75% of charge.
+ var/atom/throw_target = get_edge_target_turf(C, user.dir)
+ if(!C.anchored)
+ . = TRUE
+ C.throw_at(throw_target, rand(4,8), 14, user)
+ return
+
+/datum/eldritch_knowledge/ashen_eyes
+ name = "Ashen Eyes"
+ gain_text = "Piercing eyes may guide me through the mundane."
+ desc = "Allows you to craft thermal vision amulet by transmutating eyes with a glass shard."
+ cost = 1
+ next_knowledge = list(/datum/eldritch_knowledge/spell/ashen_shift,/datum/eldritch_knowledge/flesh_ghoul)
+ required_atoms = list(/obj/item/organ/eyes,/obj/item/shard)
+ result_atoms = list(/obj/item/clothing/neck/eldritch_amulet)
+
+/datum/eldritch_knowledge/ash_mark
+ name = "Mark of Ash"
+ gain_text = "Spread the famine."
+ desc = "Your sickly blade now applies ash mark on hit. Use your mansus grasp to proc the mark. Mark of Ash causes stamina damage, and fire loss, and spreads to a nearby carbon. Damage decreases with how many times the mark has spread."
+ cost = 2
+ next_knowledge = list(/datum/eldritch_knowledge/curse/blindness)
+ banned_knowledge = list(/datum/eldritch_knowledge/rust_mark,/datum/eldritch_knowledge/flesh_mark)
+ route = PATH_ASH
+
+/datum/eldritch_knowledge/ash_mark/on_eldritch_blade(target,user,proximity_flag,click_parameters)
+ . = ..()
+ if(isliving(target))
+ var/mob/living/living_target = target
+ living_target.apply_status_effect(/datum/status_effect/eldritch/ash,5)
+
+/datum/eldritch_knowledge/curse/blindness
+ name = "Curse of Blindness"
+ gain_text = "The blind man walks through the world, unnoticed by the masses."
+ desc = "Curse someone with 2 minutes of complete blindness by sacrificing a pair of eyes, a screwdriver and a pool of blood, with an object that the victim has touched with their bare hands."
+ cost = 1
+ required_atoms = list(/obj/item/organ/eyes,/obj/item/screwdriver,/obj/effect/decal/cleanable/blood)
+ next_knowledge = list(/datum/eldritch_knowledge/curse/corrosion,/datum/eldritch_knowledge/ash_blade_upgrade,/datum/eldritch_knowledge/curse/paralysis)
+ timer = 2 MINUTES
+ route = PATH_ASH
+
+/datum/eldritch_knowledge/curse/blindness/curse(mob/living/chosen_mob)
+ . = ..()
+ chosen_mob.become_blind(MAGIC_TRAIT)
+
+/datum/eldritch_knowledge/curse/blindness/uncurse(mob/living/chosen_mob)
+ . = ..()
+ chosen_mob.cure_blind(MAGIC_TRAIT)
+
+/datum/eldritch_knowledge/spell/flame_birth
+ name = "Flame Birth"
+ gain_text = "Nightwatcher was a man of principles, and yet he arose from the chaos he vowed to protect from."
+ desc = "Short range spell that allows you to curse someone with massive sanity loss."
+ cost = 1
+ spell_to_add = /obj/effect/proc_holder/spell/targeted/fiery_rebirth
+ next_knowledge = list(/datum/eldritch_knowledge/spell/cleave,/datum/eldritch_knowledge/summon/ashy,/datum/eldritch_knowledge/final/ash_final)
+ route = PATH_ASH
+
+/datum/eldritch_knowledge/ash_blade_upgrade
+ name = "Blazing Steel"
+ gain_text = "May the sun burn the heretics."
+ desc = "Your blade of choice will now add firestacks."
+ cost = 2
+ next_knowledge = list(/datum/eldritch_knowledge/spell/flame_birth)
+ banned_knowledge = list(/datum/eldritch_knowledge/rust_blade_upgrade,/datum/eldritch_knowledge/flesh_blade_upgrade)
+ route = PATH_ASH
+
+/datum/eldritch_knowledge/ash_blade_upgrade/on_eldritch_blade(target,user,proximity_flag,click_parameters)
+ . = ..()
+ if(iscarbon(target))
+ var/mob/living/carbon/C = target
+ C.adjust_fire_stacks(1)
+ C.IgniteMob()
+
+/datum/eldritch_knowledge/curse/corrosion
+ name = "Curse of Corrosion"
+ gain_text = "Cursed land, cursed man, cursed mind."
+ desc = "Curse someone for 2 minutes of vomiting and major organ damage. Using a wirecutter, a spill of blood, a heart, left arm and a right arm, and an item that the victim touched with their bare hands."
+ cost = 1
+ required_atoms = list(/obj/item/wirecutters,/obj/effect/decal/cleanable/blood,/obj/item/organ/heart,/obj/item/bodypart/l_arm,/obj/item/bodypart/r_arm)
+ next_knowledge = list(/datum/eldritch_knowledge/curse/blindness,/datum/eldritch_knowledge/spell/area_conversion)
+ timer = 2 MINUTES
+
+/datum/eldritch_knowledge/curse/corrosion/curse(mob/living/chosen_mob)
+ . = ..()
+ chosen_mob.apply_status_effect(/datum/status_effect/corrosion_curse)
+
+/datum/eldritch_knowledge/curse/corrosion/uncurse(mob/living/chosen_mob)
+ . = ..()
+ chosen_mob.remove_status_effect(/datum/status_effect/corrosion_curse)
+
+/datum/eldritch_knowledge/curse/paralysis
+ name = "Curse of Paralysis"
+ gain_text = "Corrupt their flesh, make them bleed."
+ desc = "Curse someone for 5 minutes of inability to walk. Using a knife, pool of blood, left leg, right leg, a hatchet and an item that the victim touched with their bare hands. "
+ cost = 1
+ required_atoms = list(/obj/item/kitchen/knife,/obj/effect/decal/cleanable/blood,/obj/item/bodypart/l_leg,/obj/item/bodypart/r_leg,/obj/item/hatchet)
+ next_knowledge = list(/datum/eldritch_knowledge/curse/blindness,/datum/eldritch_knowledge/summon/raw_prophet)
+ timer = 5 MINUTES
+
+/datum/eldritch_knowledge/curse/paralysis/curse(mob/living/chosen_mob)
+ . = ..()
+ ADD_TRAIT(chosen_mob,TRAIT_PARALYSIS_L_LEG,MAGIC_TRAIT)
+ ADD_TRAIT(chosen_mob,TRAIT_PARALYSIS_R_LEG,MAGIC_TRAIT)
+ chosen_mob.update_mobility()
+
+/datum/eldritch_knowledge/curse/paralysis/uncurse(mob/living/chosen_mob)
+ . = ..()
+ REMOVE_TRAIT(chosen_mob,TRAIT_PARALYSIS_L_LEG,MAGIC_TRAIT)
+ REMOVE_TRAIT(chosen_mob,TRAIT_PARALYSIS_R_LEG,MAGIC_TRAIT)
+ chosen_mob.update_mobility()
+
+/datum/eldritch_knowledge/spell/cleave
+ name = "Blood Cleave"
+ gain_text = "At first I was unfamiliar with these instruments of war, but the priest told me how to use them."
+ desc = "Gives a ranged spell that causes heavy bleeding and blood loss to a target and any surrounding individuals."
+ cost = 1
+ spell_to_add = /obj/effect/proc_holder/spell/pointed/cleave
+ next_knowledge = list(/datum/eldritch_knowledge/spell/entropic_plume,/datum/eldritch_knowledge/spell/flame_birth)
+
+/datum/eldritch_knowledge/final/ash_final
+ name = "Ashlord's Rite"
+ gain_text = "The forgotten lords have spoken! The Lord of Ash has come! Fear the flame!"
+ desc = "Bring 3 corpses onto a transmutation rune, you will become immune to fire ,space ,cold and other enviromental hazards and become overall sturdier to all other damages. You will gain a spell that passively creates ring of fire around you as well ,as you will gain a powerful abiltiy that let's you create a wave of flames all around you."
+ required_atoms = list(/mob/living/carbon/human)
+ cost = 3
+ route = PATH_ASH
+ var/list/trait_list = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_RADIMMUNE,TRAIT_GENELESS,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_BOMBIMMUNE)
+
+/datum/eldritch_knowledge/final/ash_final/on_finished_recipe(mob/living/user, list/atoms, loc)
+ priority_announce("$^@*$^@(#&$(@^$^@# Fear the blaze, for Ashbringer [user.real_name] has come! $^@*$^@(#&$(@^$^@#","#$^@*$^@(#&$(@^$^@#", 'sound/announcer/classic/spanomalies.ogg')
+ user.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/fire_cascade/big)
+ user.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/fire_sworn)
+ var/mob/living/carbon/human/H = user
+ H.physiology.brute_mod *= 0.5
+ H.physiology.burn_mod *= 0.5
+ var/datum/antagonist/heretic/ascension = H.mind.has_antag_datum(/datum/antagonist/heretic)
+ ascension.ascended = TRUE
+ for(var/X in trait_list)
+ ADD_TRAIT(user,X,MAGIC_TRAIT)
+ return ..()
diff --git a/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm
new file mode 100644
index 0000000000..d9e587edb1
--- /dev/null
+++ b/code/modules/antagonists/eldritch_cult/knowledge/flesh_lore.dm
@@ -0,0 +1,252 @@
+/datum/eldritch_knowledge/base_flesh
+ name = "Principle of Hunger"
+ desc = "Inducts you into the Path of Flesh. Allows you to transmute a pool of blood with your eldritch blade into a Blade of Flesh."
+ gain_text = "Hundred's of us starved, but I.. I found the strength in my greed."
+ banned_knowledge = list(/datum/eldritch_knowledge/base_ash,/datum/eldritch_knowledge/base_rust,/datum/eldritch_knowledge/final/ash_final,/datum/eldritch_knowledge/final/rust_final)
+ next_knowledge = list(/datum/eldritch_knowledge/flesh_grasp)
+ required_atoms = list(/obj/item/melee/sickly_blade,/obj/effect/decal/cleanable/blood)
+ result_atoms = list(/obj/item/melee/sickly_blade/flesh)
+ cost = 1
+ route = PATH_FLESH
+
+/datum/eldritch_knowledge/flesh_ghoul
+ name = "Imperfect Ritual"
+ desc = "Allows you to resurrect the dead as voiceless dead by sacrificing them on the transmutation rune with a poppy. Voiceless dead are mute and have 50 HP. You can only have 2 at a time."
+ gain_text = "I found notes... notes of a ritual, scraps, unfinished, and yet... I still did it."
+ cost = 1
+ required_atoms = list(/mob/living/carbon/human,/obj/item/reagent_containers/food/snacks/grown/poppy)
+ next_knowledge = list(/datum/eldritch_knowledge/flesh_mark,/datum/eldritch_knowledge/armor,/datum/eldritch_knowledge/ashen_eyes)
+ route = PATH_FLESH
+ var/max_amt = 2
+ var/current_amt = 0
+ var/list/ghouls = list()
+
+/datum/eldritch_knowledge/flesh_ghoul/on_finished_recipe(mob/living/user,list/atoms,loc)
+ var/mob/living/carbon/human/humie = locate() in atoms
+ if(QDELETED(humie) || humie.stat != DEAD)
+ return
+
+ if(length(ghouls) >= max_amt)
+ return
+
+ if(HAS_TRAIT(humie,TRAIT_HUSK))
+ return
+
+ humie.grab_ghost()
+
+ if(!humie.mind || !humie.client)
+ var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as a [humie.real_name], a voiceless dead.", ROLE_HERETIC, null, ROLE_HERETIC, 50,humie)
+ if(!LAZYLEN(candidates))
+ return
+ var/mob/dead/observer/C = pick(candidates)
+ message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(humie)]) to replace an AFK player.")
+ humie.ghostize(0)
+ humie.key = C.key
+
+ ADD_TRAIT(humie,TRAIT_MUTE,MAGIC_TRAIT)
+ log_game("[key_name_admin(humie)] has become a voiceless dead, their master is [user.real_name]")
+ humie.revive(full_heal = TRUE, admin_revive = TRUE)
+ humie.setMaxHealth(75)
+ humie.health = 75 // Voiceless dead are much tougher than ghouls
+ humie.become_husk()
+ humie.faction |= "heretics"
+
+ var/datum/antagonist/heretic_monster/heretic_monster = humie.mind.add_antag_datum(/datum/antagonist/heretic_monster)
+ var/datum/antagonist/heretic/master = user.mind.has_antag_datum(/datum/antagonist/heretic)
+ heretic_monster.set_owner(master)
+ atoms -= humie
+ RegisterSignal(humie,COMSIG_MOB_DEATH,.proc/remove_ghoul)
+ ghouls += humie
+
+/datum/eldritch_knowledge/flesh_ghoul/proc/remove_ghoul(datum/source)
+ var/mob/living/carbon/human/humie = source
+ ghouls -= humie
+ humie.mind.remove_antag_datum(/datum/antagonist/heretic_monster)
+ UnregisterSignal(source,COMSIG_MOB_DEATH)
+
+/datum/eldritch_knowledge/flesh_grasp
+ name = "Grasp of Flesh"
+ gain_text = "'My newfound desire, it drove me to do great things,' The Priest said."
+ desc = "Empowers your Mansus Grasp to be able to create a single ghoul out of a dead player. You cannot raise the same person twice. Ghouls have only 50 HP and look like husks."
+ cost = 1
+ next_knowledge = list(/datum/eldritch_knowledge/flesh_ghoul)
+ var/ghoul_amt = 1
+ var/list/spooky_scaries
+ route = PATH_FLESH
+
+/datum/eldritch_knowledge/flesh_grasp/on_mansus_grasp(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ if(!ishuman(target) || target == user)
+ return
+ var/mob/living/carbon/human/human_target = target
+ var/datum/status_effect/eldritch/eldritch_effect = human_target.has_status_effect(/datum/status_effect/eldritch/rust) || human_target.has_status_effect(/datum/status_effect/eldritch/ash) || human_target.has_status_effect(/datum/status_effect/eldritch/flesh)
+ if(eldritch_effect)
+ . = TRUE
+ eldritch_effect.on_effect()
+ if(iscarbon(target))
+ var/mob/living/carbon/carbon_target = target
+ var/obj/item/bodypart/bodypart = pick(carbon_target.bodyparts)
+ var/datum/wound/slash/severe/crit_wound = new
+ crit_wound.apply_wound(bodypart)
+
+ if(QDELETED(human_target) || human_target.stat != DEAD)
+ return
+
+ human_target.grab_ghost()
+
+ if(!human_target.mind || !human_target.client)
+ to_chat(user, "There is no soul connected to this body...")
+ return
+
+ if(HAS_TRAIT(human_target, TRAIT_HUSK))
+ to_chat(user, "You cannot revive a dead ghoul!")
+ return
+
+ if(LAZYLEN(spooky_scaries) >= ghoul_amt)
+ to_chat(user, "Your patron cannot support more ghouls on this plane!")
+ return
+
+ LAZYADD(spooky_scaries, human_target)
+ log_game("[key_name_admin(human_target)] has become a ghoul, their master is [user.real_name]")
+ //we change it to true only after we know they passed all the checks
+ . = TRUE
+ RegisterSignal(human_target,COMSIG_MOB_DEATH,.proc/remove_ghoul)
+ human_target.revive(full_heal = TRUE, admin_revive = TRUE)
+ human_target.setMaxHealth(25)
+ human_target.health = 25
+ human_target.become_husk()
+ human_target.faction |= "heretics"
+ var/datum/antagonist/heretic_monster/heretic_monster = human_target.mind.add_antag_datum(/datum/antagonist/heretic_monster)
+ var/datum/antagonist/heretic/master = user.mind.has_antag_datum(/datum/antagonist/heretic)
+ heretic_monster.set_owner(master)
+ return
+
+
+/datum/eldritch_knowledge/flesh_grasp/proc/remove_ghoul(datum/source)
+ var/mob/living/carbon/human/humie = source
+ spooky_scaries -= humie
+ humie.mind.remove_antag_datum(/datum/antagonist/heretic_monster)
+ UnregisterSignal(source, COMSIG_MOB_DEATH)
+
+/datum/eldritch_knowledge/flesh_mark
+ name = "Mark of Flesh"
+ gain_text = "I saw them, the marked ones. The screams... the silence."
+ desc = "Your sickly blade now applies a mark of flesh to those cut by it. Once marked, using your Mansus Grasp upon them will cause additional bleeding from the target."
+ cost = 2
+ next_knowledge = list(/datum/eldritch_knowledge/summon/raw_prophet)
+ banned_knowledge = list(/datum/eldritch_knowledge/rust_mark,/datum/eldritch_knowledge/ash_mark)
+ route = PATH_FLESH
+
+/datum/eldritch_knowledge/flesh_mark/on_eldritch_blade(target,user,proximity_flag,click_parameters)
+ . = ..()
+ if(isliving(target))
+ var/mob/living/living_target = target
+ living_target.apply_status_effect(/datum/status_effect/eldritch/flesh)
+
+/datum/eldritch_knowledge/flesh_blade_upgrade
+ name = "Bleeding Steel"
+ gain_text = "It rained blood, that's when I understood the gravekeeper's advice."
+ desc = "Your blade will now cause additional bleeding to those hit by it."
+ cost = 2
+ next_knowledge = list(/datum/eldritch_knowledge/summon/stalker)
+ banned_knowledge = list(/datum/eldritch_knowledge/ash_blade_upgrade,/datum/eldritch_knowledge/rust_blade_upgrade)
+ route = PATH_FLESH
+
+/datum/eldritch_knowledge/flesh_blade_upgrade/on_eldritch_blade(target,user,proximity_flag,click_parameters)
+ . = ..()
+ if(iscarbon(target))
+ var/mob/living/carbon/carbon_target = target
+ var/obj/item/bodypart/bodypart = pick(carbon_target.bodyparts)
+ var/datum/wound/slash/severe/crit_wound = new
+ crit_wound.apply_wound(bodypart)
+
+/datum/eldritch_knowledge/summon/raw_prophet
+ name = "Raw Ritual"
+ gain_text = "The uncanny man walks alone in the valley, I was able to call his aid."
+ desc = "You can now summon a Raw Prophet using eyes, a left arm, right arm and a pool of blood using a transmutation circle. Raw prophets have increased seeing range, and can see through walls. They can jaunt long distances, though they are fragile."
+ cost = 1
+ required_atoms = list(/obj/item/organ/eyes,/obj/item/bodypart/l_arm,/obj/item/bodypart/r_arm,/obj/effect/decal/cleanable/blood)
+ mob_to_summon = /mob/living/simple_animal/hostile/eldritch/raw_prophet
+ next_knowledge = list(/datum/eldritch_knowledge/flesh_blade_upgrade,/datum/eldritch_knowledge/spell/blood_siphon,/datum/eldritch_knowledge/curse/paralysis)
+ route = PATH_FLESH
+
+/datum/eldritch_knowledge/summon/stalker
+ name = "Lonely Ritual"
+ gain_text = "I was able to combine my greed and desires to summon an eldritch beast I have not seen before."
+ desc = "You can now summon a Stalker using a knife, a flower, a pen and a piece of paper using a transmutation circle. Stalkers possess the ability to shapeshift into various forms while assuming the vigor and powers of that form."
+ cost = 1
+ required_atoms = list(/obj/item/kitchen/knife,/obj/item/reagent_containers/food/snacks/grown/poppy,/obj/item/pen,/obj/item/paper)
+ mob_to_summon = /mob/living/simple_animal/hostile/eldritch/stalker
+ next_knowledge = list(/datum/eldritch_knowledge/summon/ashy,/datum/eldritch_knowledge/summon/rusty,/datum/eldritch_knowledge/final/flesh_final)
+ route = PATH_FLESH
+
+/datum/eldritch_knowledge/summon/ashy
+ name = "Ashen Ritual"
+ gain_text = "I combined principle of hunger with desire of destruction. The eyeful lords have noticed me."
+ desc = "You can now summon an Ashen One by transmuting a pile of ash, a head and a book using a transmutation circle. They possess the ability to jaunt short distances and create a cascade of flames."
+ cost = 1
+ required_atoms = list(/obj/effect/decal/cleanable/ash,/obj/item/bodypart/head,/obj/item/book)
+ mob_to_summon = /mob/living/simple_animal/hostile/eldritch/ash_spirit
+ next_knowledge = list(/datum/eldritch_knowledge/summon/stalker,/datum/eldritch_knowledge/spell/flame_birth)
+
+/datum/eldritch_knowledge/summon/rusty
+ name = "Rusted Ritual"
+ gain_text = "I combined principle of hunger with desire of corruption. The rusted hills call my name."
+ desc = "You can now summon a Rust Walker transmuting a vomit pool, a head using a transmutation circle. Rust Walkers possess the ability to spread rust and can fire bolts of rust to further corrode the area."
+ cost = 1
+ required_atoms = list(/obj/effect/decal/cleanable/vomit,/obj/item/bodypart/head,/obj/item/book)
+ mob_to_summon = /mob/living/simple_animal/hostile/eldritch/rust_spirit
+ next_knowledge = list(/datum/eldritch_knowledge/summon/stalker,/datum/eldritch_knowledge/spell/entropic_plume)
+
+/datum/eldritch_knowledge/spell/blood_siphon
+ name = "Blood Siphon"
+ gain_text = "Our blood is all the same after all, the owl told me."
+ desc = "You are granted a spell that drains some of the targets health, and returns it to you. It also has a chance to transfer any wounds you possess onto the target."
+ cost = 1
+ spell_to_add = /obj/effect/proc_holder/spell/targeted/touch/blood_siphon
+ next_knowledge = list(/datum/eldritch_knowledge/summon/raw_prophet,/datum/eldritch_knowledge/spell/area_conversion)
+
+/datum/eldritch_knowledge/final/flesh_final
+ name = "Priest's Final Hymn"
+ gain_text = "Man of this world. Hear me! For the time of the lord of arms has come!"
+ desc = "Bring three corpses to a transmutation rune to either ascend as The Lord of the Night or summon a single Terror of the Night, however you cannot ascend more than once."
+ required_atoms = list(/mob/living/carbon/human)
+ cost = 3
+ route = PATH_FLESH
+
+/datum/eldritch_knowledge/final/flesh_final/on_finished_recipe(mob/living/user, list/atoms, loc)
+ var/alert_ = alert(user,"Do you want to ascend as the lord of the night or just summon a terror of the night?","...","Yes","No")
+ user.SetImmobilized(10 HOURS) // no way someone will stand 10 hours in a spot, just so he can move while the alert is still showing.
+ switch(alert_)
+ if("No")
+ var/mob/living/summoned = new /mob/living/simple_animal/hostile/eldritch/armsy(loc)
+ message_admins("[summoned.name] is being summoned by [user.real_name] in [loc]")
+ var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as a [summoned.real_name]", ROLE_HERETIC, null, ROLE_HERETIC, 100,summoned)
+ user.SetImmobilized(0)
+ if(LAZYLEN(candidates) == 0)
+ to_chat(user,"No ghost could be found...")
+ qdel(summoned)
+ return FALSE
+ var/mob/dead/observer/ghost_candidate = pick(candidates)
+ priority_announce("$^@*$^@(#&$(@^$^@# Fear the dark, for vassal of arms has ascended! Terror of the night has come! $^@*$^@(#&$(@^$^@#","#$^@*$^@(#&$(@^$^@#", 'sound/announcer/classic/spanomalies.ogg')
+ log_game("[key_name_admin(ghost_candidate)] has taken control of ([key_name_admin(summoned)]).")
+ summoned.ghostize(FALSE)
+ summoned.key = ghost_candidate.key
+ summoned.mind.add_antag_datum(/datum/antagonist/heretic_monster)
+ var/datum/antagonist/heretic_monster/monster = summoned.mind.has_antag_datum(/datum/antagonist/heretic_monster)
+ var/datum/antagonist/heretic/master = user.mind.has_antag_datum(/datum/antagonist/heretic)
+ monster.set_owner(master)
+ master.ascended = TRUE
+ if("Yes")
+ var/mob/living/summoned = new /mob/living/simple_animal/hostile/eldritch/armsy/prime(loc,TRUE,10)
+ summoned.ghostize(0)
+ user.SetImmobilized(0)
+ priority_announce("$^@*$^@(#&$(@^$^@# Fear the dark, for king of arms has ascended! Lord of the night has come! $^@*$^@(#&$(@^$^@#","#$^@*$^@(#&$(@^$^@#", 'sound/announcer/classic/spanomalies.ogg')
+ log_game("[user.real_name] ascended as [summoned.real_name]")
+ var/mob/living/carbon/carbon_user = user
+ var/datum/antagonist/heretic/ascension = carbon_user.mind.has_antag_datum(/datum/antagonist/heretic)
+ ascension.ascended = TRUE
+ carbon_user.mind.transfer_to(summoned, TRUE)
+ carbon_user.gib()
+
+ return ..()
diff --git a/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm
new file mode 100644
index 0000000000..ea06a5cc06
--- /dev/null
+++ b/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm
@@ -0,0 +1,200 @@
+/datum/eldritch_knowledge/base_rust
+ name = "Blacksmith's Tale"
+ desc = "Opens up the path of rust to you. Allows you to transmute a knife with any trash item into a Rusty Blade."
+ gain_text = "'Let me tell you a story,' The Blacksmith said as he gazed into his rusty blade."
+ banned_knowledge = list(/datum/eldritch_knowledge/base_ash,/datum/eldritch_knowledge/base_flesh,/datum/eldritch_knowledge/final/ash_final,/datum/eldritch_knowledge/final/flesh_final)
+ next_knowledge = list(/datum/eldritch_knowledge/rust_fist)
+ required_atoms = list(/obj/item/melee/sickly_blade,/obj/item/trash)
+ result_atoms = list(/obj/item/melee/sickly_blade/rust)
+ cost = 1
+ route = PATH_RUST
+
+/datum/eldritch_knowledge/rust_fist
+ name = "Grasp of Rust"
+ desc = "Empowers your mansus grasp to deal 500 damage to non-living matter and rust any turf it touches. Destroys already rusted turfs."
+ gain_text = "Rust grows on the ceiling of the mansus."
+ cost = 1
+ next_knowledge = list(/datum/eldritch_knowledge/rust_regen)
+ var/rust_force = 500
+ var/static/list/blacklisted_turfs = typecacheof(list(/turf/closed,/turf/open/space,/turf/open/lava,/turf/open/chasm,/turf/open/floor/plating/rust))
+ route = PATH_RUST
+
+/datum/eldritch_knowledge/rust_fist/on_mansus_grasp(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ if(ishuman(target))
+ var/mob/living/carbon/human/H = target
+ var/datum/status_effect/eldritch/E = H.has_status_effect(/datum/status_effect/eldritch/rust) || H.has_status_effect(/datum/status_effect/eldritch/ash) || H.has_status_effect(/datum/status_effect/eldritch/flesh)
+ if(E)
+ E.on_effect()
+ H.adjustOrganLoss(pick(ORGAN_SLOT_BRAIN,ORGAN_SLOT_EARS,ORGAN_SLOT_EYES,ORGAN_SLOT_LIVER,ORGAN_SLOT_LUNGS,ORGAN_SLOT_STOMACH,ORGAN_SLOT_HEART),25)
+ target.rust_heretic_act()
+ return TRUE
+
+/datum/eldritch_knowledge/spell/area_conversion
+ name = "Agressive Spread"
+ desc = "Spreads rust to nearby turfs. Destroys already rusted walls."
+ gain_text = "All wise men know not to touch the bound king."
+ cost = 1
+ spell_to_add = /obj/effect/proc_holder/spell/aoe_turf/rust_conversion
+ next_knowledge = list(/datum/eldritch_knowledge/rust_blade_upgrade,/datum/eldritch_knowledge/curse/corrosion,/datum/eldritch_knowledge/spell/blood_siphon)
+ route = PATH_RUST
+
+/datum/eldritch_knowledge/rust_regen
+ name = "Leeching Walk"
+ desc = "Passively heals you when you are on rusted tiles."
+ gain_text = "'The strength was unparalleled, unnatural.' The Blacksmith was smiling."
+ cost = 1
+ next_knowledge = list(/datum/eldritch_knowledge/rust_mark,/datum/eldritch_knowledge/armor,/datum/eldritch_knowledge/essence)
+ route = PATH_RUST
+
+/datum/eldritch_knowledge/rust_regen/on_life(mob/user)
+ . = ..()
+ var/turf/user_loc_turf = get_turf(user)
+ if(!istype(user_loc_turf, /turf/open/floor/plating/rust) || !isliving(user))
+ return
+ var/mob/living/living_user = user
+ living_user.adjustBruteLoss(-3, FALSE)
+ living_user.adjustFireLoss(-3, FALSE)
+ living_user.adjustToxLoss(-3, FALSE)
+ living_user.adjustOxyLoss(-1, FALSE)
+ living_user.adjustStaminaLoss(-4)
+
+/datum/eldritch_knowledge/rust_mark
+ name = "Mark of Rust"
+ desc = "Your eldritch blade now applies a rust mark. Rust mark has a chance to deal between 0 to 200 damage to 75% of enemies items. To Detonate the mark use your mansus grasp on it."
+ gain_text = "Lords of the depths help those in dire need at a cost."
+ cost = 2
+ next_knowledge = list(/datum/eldritch_knowledge/spell/area_conversion)
+ banned_knowledge = list(/datum/eldritch_knowledge/ash_mark,/datum/eldritch_knowledge/flesh_mark)
+ route = PATH_RUST
+
+/datum/eldritch_knowledge/rust_mark/on_eldritch_blade(target,user,proximity_flag,click_parameters)
+ . = ..()
+ if(isliving(target))
+ var/mob/living/living_target = target
+ living_target.apply_status_effect(/datum/status_effect/eldritch/rust)
+
+/datum/eldritch_knowledge/rust_blade_upgrade
+ name = "Toxic Steel"
+ gain_text = "Let the blade guide you through the flesh."
+ desc = "Your blade of choice will now add toxin to enemies bloodstream."
+ cost = 2
+ next_knowledge = list(/datum/eldritch_knowledge/spell/entropic_plume)
+ banned_knowledge = list(/datum/eldritch_knowledge/ash_blade_upgrade,/datum/eldritch_knowledge/flesh_blade_upgrade)
+ route = PATH_RUST
+
+/datum/eldritch_knowledge/rust_blade_upgrade/on_eldritch_blade(target,user,proximity_flag,click_parameters)
+ . = ..()
+ if(iscarbon(target))
+ var/mob/living/carbon/carbon_target = target
+ carbon_target.reagents.add_reagent(/datum/reagent/eldritch, 5)
+
+/datum/eldritch_knowledge/spell/entropic_plume
+ name = "Entropic Plume"
+ desc = "You can now send a befuddling plume that blinds, poisons and makes enemies strike each other, while also converting the immediate area into rust."
+ gain_text = "Messengers of hope fear the rustbringer."
+ cost = 1
+ spell_to_add = /obj/effect/proc_holder/spell/cone/staggered/entropic_plume
+ next_knowledge = list(/datum/eldritch_knowledge/final/rust_final,/datum/eldritch_knowledge/spell/cleave,/datum/eldritch_knowledge/summon/rusty)
+ route = PATH_RUST
+
+/datum/eldritch_knowledge/armor
+ name = "Armorer's Ritual"
+ desc = "You can now create eldritch armor using a table and a gas mask."
+ gain_text = "For I am the heir to the throne of doom."
+ cost = 1
+ next_knowledge = list(/datum/eldritch_knowledge/rust_regen,/datum/eldritch_knowledge/flesh_ghoul)
+ required_atoms = list(/obj/structure/table,/obj/item/clothing/mask/gas)
+ result_atoms = list(/obj/item/clothing/suit/hooded/cultrobes/eldritch)
+
+/datum/eldritch_knowledge/essence
+ name = "Priest's Ritual"
+ desc = "You can now transmute a tank of water into a bottle of eldritch water."
+ gain_text = "This is an old recipe, i got it from an owl."
+ cost = 1
+ next_knowledge = list(/datum/eldritch_knowledge/rust_regen,/datum/eldritch_knowledge/spell/ashen_shift)
+ required_atoms = list(/obj/structure/reagent_dispensers/watertank)
+ result_atoms = list(/obj/item/reagent_containers/glass/beaker/eldritch)
+
+/datum/eldritch_knowledge/final/rust_final
+ name = "Rustbringer's Oath"
+ desc = "Bring 3 corpses onto the transmutation rune. After you finish the ritual rust will now automatically spread from the rune. Your healing on rust is also tripled, while you become more resillient overall."
+ gain_text = "Champion of rust. Corruptor of steel. Fear the dark for Rustbringer has come!"
+ cost = 3
+ required_atoms = list(/mob/living/carbon/human)
+ route = PATH_RUST
+
+/datum/eldritch_knowledge/final/rust_final/on_finished_recipe(mob/living/user, list/atoms, loc)
+ var/mob/living/carbon/human/H = user
+ H.physiology.brute_mod *= 0.5
+ H.physiology.burn_mod *= 0.5
+ priority_announce("$^@*$^@(#&$(@^$^@# Fear the decay, for Rustbringer [user.real_name] has come! $^@*$^@(#&$(@^$^@#","#$^@*$^@(#&$(@^$^@#", 'sound/announcer/classic/spanomalies.ogg')
+ new /datum/rust_spread(loc)
+ var/datum/antagonist/heretic/ascension = H.mind.has_antag_datum(/datum/antagonist/heretic)
+ ascension.ascended = TRUE
+ return ..()
+
+
+/datum/eldritch_knowledge/final/rust_final/on_life(mob/user)
+ . = ..()
+ if(!finished)
+ return
+ var/mob/living/carbon/human/human_user = user
+ human_user.adjustBruteLoss(-6, FALSE)
+ human_user.adjustFireLoss(-6, FALSE)
+ human_user.adjustToxLoss(-6, FALSE)
+ human_user.adjustOxyLoss(-6, FALSE)
+ human_user.adjustStaminaLoss(-20)
+
+
+/**
+ * #Rust spread datum
+ *
+ * Simple datum that automatically spreads rust around it
+ *
+ * Simple implementation of automatically growing entity
+ */
+/datum/rust_spread
+ var/list/edge_turfs = list()
+ var/list/turfs = list()
+ var/static/list/blacklisted_turfs = typecacheof(list(/turf/open/indestructible,/turf/closed/indestructible,/turf/open/space,/turf/open/lava,/turf/open/chasm))
+ var/spread_per_tick = 6
+
+
+/datum/rust_spread/New(loc)
+ . = ..()
+ var/turf/turf_loc = get_turf(loc)
+ turf_loc.rust_heretic_act()
+ turfs += turf_loc
+ START_PROCESSING(SSprocessing,src)
+
+
+/datum/rust_spread/Destroy(force, ...)
+ STOP_PROCESSING(SSprocessing,src)
+ return ..()
+
+/datum/rust_spread/process()
+ compile_turfs()
+ var/turf/T
+ for(var/i in 0 to spread_per_tick)
+ T = pick(edge_turfs)
+ T.rust_heretic_act()
+ turfs += get_turf(T)
+
+/**
+ * Compile turfs
+ *
+ * Recreates all edge_turfs as well as normal turfs.
+ */
+/datum/rust_spread/proc/compile_turfs()
+ edge_turfs = list()
+ for(var/X in turfs)
+ if(!istype(X,/turf/closed/wall/rust) && !istype(X,/turf/closed/wall/r_wall/rust) && !istype(X,/turf/open/floor/plating/rust))
+ turfs -=X
+ continue
+ for(var/turf/T in range(1,X))
+ if(T in turfs)
+ continue
+ if(is_type_in_typecache(T,blacklisted_turfs))
+ continue
+ edge_turfs += T
diff --git a/code/modules/antagonists/wizard/equipment/spellbook.dm b/code/modules/antagonists/wizard/equipment/spellbook.dm
index d4d117f8ee..1e98b2f753 100644
--- a/code/modules/antagonists/wizard/equipment/spellbook.dm
+++ b/code/modules/antagonists/wizard/equipment/spellbook.dm
@@ -161,12 +161,12 @@
/datum/spellbook_entry/blind
name = "Blind"
- spell_type = /obj/effect/proc_holder/spell/targeted/trigger/blind
+ spell_type = /obj/effect/proc_holder/spell/pointed/trigger/blind
cost = 1
/datum/spellbook_entry/mindswap
name = "Mindswap"
- spell_type = /obj/effect/proc_holder/spell/targeted/mind_transfer
+ spell_type = /obj/effect/proc_holder/spell/pointed/mind_transfer
category = "Mobility"
/datum/spellbook_entry/forcewall
@@ -246,7 +246,7 @@
/datum/spellbook_entry/barnyard
name = "Barnyard Curse"
- spell_type = /obj/effect/proc_holder/spell/targeted/barnyardcurse
+ spell_type = /obj/effect/proc_holder/spell/pointed/barnyardcurse
/datum/spellbook_entry/charge
name = "Charge"
diff --git a/code/modules/antagonists/wizard/wizard.dm b/code/modules/antagonists/wizard/wizard.dm
index 7263793f7f..42954c3542 100644
--- a/code/modules/antagonists/wizard/wizard.dm
+++ b/code/modules/antagonists/wizard/wizard.dm
@@ -177,7 +177,7 @@
to_chat(owner, "Your service has not gone unrewarded, however. Studying under [master.current.real_name], you have learned livesaving survival spells. You are able to cast charge and forcewall.")
if(APPRENTICE_ROBELESS)
owner.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/knock(null))
- owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/mind_transfer(null))
+ owner.AddSpell(new /obj/effect/proc_holder/spell/pointed/mind_transfer(null))
to_chat(owner, "Your service has not gone unrewarded, however. Studying under [master.current.real_name], you have learned stealthy, robeless spells. You are able to cast knock and mindswap.")
if(APPRENTICE_MARTIAL)
owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/touch/nuclear_fist(null))
diff --git a/code/modules/events/wizard/shuffle.dm b/code/modules/events/wizard/shuffle.dm
index 3b5ea6b20a..18b8c8e21c 100644
--- a/code/modules/events/wizard/shuffle.dm
+++ b/code/modules/events/wizard/shuffle.dm
@@ -94,7 +94,7 @@
shuffle_inplace(mobs)
- var/obj/effect/proc_holder/spell/targeted/mind_transfer/swapper = new /obj/effect/proc_holder/spell/targeted/mind_transfer
+ var/obj/effect/proc_holder/spell/pointed/mind_transfer/swapper = new /obj/effect/proc_holder/spell/pointed/mind_transfer
while(mobs.len > 1)
var/mob/living/carbon/human/H = pick(mobs)
mobs -= H
diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm
index caebb9cf10..84f0a39531 100644
--- a/code/modules/mob/inventory.dm
+++ b/code/modules/mob/inventory.dm
@@ -476,5 +476,19 @@
hand_bodyparts[i] = BP
..() //Don't redraw hands until we have organs for them
+
+//GetAllContents that is reasonable and not stupid
+/mob/living/carbon/proc/get_all_gear()
+ var/list/processing_list = get_equipped_items() + held_items
+ listclearnulls(processing_list) // handles empty hands
+ var/i = 0
+ while(i < length(processing_list) )
+ var/atom/A = processing_list[++i]
+ if(SEND_SIGNAL(A, COMSIG_CONTAINS_STORAGE))
+ var/list/item_stuff = list()
+ SEND_SIGNAL(A, COMSIG_TRY_STORAGE_RETURN_INVENTORY, item_stuff)
+ processing_list += item_stuff
+ return processing_list
+
/mob/canReachInto(atom/user, atom/target, list/next, view_only, obj/item/tool)
return ..() && (user == src)
diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm
index de4b7a659f..18501b4ca2 100644
--- a/code/modules/mob/living/carbon/human/human_defense.dm
+++ b/code/modules/mob/living/carbon/human/human_defense.dm
@@ -299,10 +299,10 @@
/mob/living/carbon/human/ex_act(severity, target, origin)
- if(origin && istype(origin, /datum/spacevine_mutation) && isvineimmune(src))
+ if(TRAIT_BOMBIMMUNE in dna.species.species_traits)
return
..()
- if (!severity)
+ if (!severity || QDELETED(src))
return
var/brute_loss = 0
var/burn_loss = 0
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 0f55d5c408..2a461b5921 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -57,7 +57,7 @@
/mob/living/Bump(atom/A)
if(..()) //we are thrown onto something
return
- if (buckled || now_pushing)
+ if(buckled || now_pushing)
return
if(ismob(A))
var/mob/M = A
diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm
index 7a9610fb53..c6aee397e4 100644
--- a/code/modules/mob/living/silicon/silicon.dm
+++ b/code/modules/mob/living/silicon/silicon.dm
@@ -430,3 +430,6 @@
/mob/living/silicon/handle_high_gravity(gravity)
return
+
+/mob/living/silicon/rust_heretic_act()
+ adjustBruteLoss(500)
diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm
index fe6792b5c2..a1772d9281 100644
--- a/code/modules/mob/living/simple_animal/bot/bot.dm
+++ b/code/modules/mob/living/simple_animal/bot/bot.dm
@@ -1058,3 +1058,6 @@ Pass a positive integer as an argument to override a bot's default speed.
if(I)
I.icon_state = null
path.Cut(1, 2)
+
+/mob/living/silicon/rust_heretic_act()
+ adjustBruteLoss(500)
diff --git a/code/modules/mob/living/simple_animal/eldritch_demons.dm b/code/modules/mob/living/simple_animal/eldritch_demons.dm
new file mode 100644
index 0000000000..1bf67c9d52
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/eldritch_demons.dm
@@ -0,0 +1,386 @@
+/mob/living/simple_animal/hostile/eldritch
+ name = "Demon"
+ real_name = "Demon"
+ desc = ""
+ gender = NEUTER
+ mob_biotypes = NONE
+ speak_emote = list("screams")
+ response_help_continuous = "thinks better of touching"
+ response_help_simple = "think better of touching"
+ response_disarm_continuous = "flails at"
+ response_disarm_simple = "flail at"
+ response_harm_continuous = "reaps"
+ response_harm_simple = "tears"
+ speak_chance = 1
+ icon = 'icons/mob/eldritch_mobs.dmi'
+ speed = 0
+ a_intent = INTENT_HARM
+ stop_automated_movement = 1
+ AIStatus = AI_OFF
+ attack_sound = 'sound/weapons/punch1.ogg'
+ see_in_dark = 7
+ lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
+ damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0)
+ atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
+ minbodytemp = 0
+ maxbodytemp = INFINITY
+ healable = 0
+ movement_type = GROUND
+ pressure_resistance = 100
+ del_on_death = TRUE
+ deathmessage = "implodes into itself"
+ faction = list("heretics")
+ simple_mob_flags = SILENCE_RANGED_MESSAGE
+ ///Innate spells that are supposed to be added when a beast is created
+ var/list/spells_to_add
+
+/mob/living/simple_animal/hostile/eldritch/Initialize()
+ . = ..()
+ add_spells()
+
+/**
+ * Add_spells
+ *
+ * Goes through spells_to_add and adds each spell to the mind.
+ */
+/mob/living/simple_animal/hostile/eldritch/proc/add_spells()
+ for(var/spell in spells_to_add)
+ AddSpell(new spell())
+
+/mob/living/simple_animal/hostile/eldritch/raw_prophet
+ name = "Raw Prophet"
+ real_name = "Raw Prophet"
+ desc = "Abomination made from severed limbs."
+ icon_state = "raw_prophet"
+ status_flags = CANPUSH
+ icon_living = "raw_prophet"
+ melee_damage_lower = 5
+ melee_damage_upper = 10
+ maxHealth = 50
+ health = 50
+ sight = SEE_MOBS|SEE_OBJS|SEE_TURFS
+ spells_to_add = list(/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash/long,/obj/effect/proc_holder/spell/pointed/manse_link,/obj/effect/proc_holder/spell/targeted/telepathy/eldritch,/obj/effect/proc_holder/spell/pointed/trigger/blind/eldritch)
+
+ var/list/linked_mobs = list()
+
+/mob/living/simple_animal/hostile/eldritch/raw_prophet/Initialize()
+ . = ..()
+ link_mob(src)
+
+/mob/living/simple_animal/hostile/eldritch/raw_prophet/Login()
+ . = ..()
+ client.change_view(10)
+
+/mob/living/simple_animal/hostile/eldritch/raw_prophet/proc/link_mob(mob/living/mob_linked)
+ if(QDELETED(mob_linked) || mob_linked.stat == DEAD)
+ return FALSE
+ if(HAS_TRAIT(mob_linked, TRAIT_MINDSHIELD)) //mindshield implant, no dice
+ return FALSE
+ if(mob_linked.anti_magic_check(FALSE, FALSE, TRUE, 0))
+ return FALSE
+ if(linked_mobs[mob_linked])
+ return FALSE
+
+ to_chat(mob_linked, "You feel something new enter your sphere of mind, you hear whispers of people far away, screeches of horror and a humming of welcome to [src]'s Mansus Link.")
+ var/datum/action/innate/mansus_speech/action = new(src)
+ linked_mobs[mob_linked] = action
+ action.Grant(mob_linked)
+ RegisterSignal(mob_linked, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING) , .proc/unlink_mob)
+ return TRUE
+
+/mob/living/simple_animal/hostile/eldritch/raw_prophet/proc/unlink_mob(mob/living/mob_linked)
+ if(!linked_mobs[mob_linked])
+ return
+ UnregisterSignal(mob_linked, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING))
+ var/datum/action/innate/mansus_speech/action = linked_mobs[mob_linked]
+ action.Remove(mob_linked)
+ qdel(action)
+ to_chat(mob_linked, "Your mind shatters as the [src]'s Mansus Link leaves your mind.")
+ mob_linked.emote("Scream")
+ //micro stun
+ mob_linked.AdjustParalyzed(0.5 SECONDS)
+ linked_mobs -= mob_linked
+
+/mob/living/simple_animal/hostile/eldritch/raw_prophet/death(gibbed)
+ for(var/linked_mob in linked_mobs)
+ unlink_mob(linked_mob)
+ return ..()
+
+/mob/living/simple_animal/hostile/eldritch/armsy
+ name = "Terror of the Night"
+ real_name = "Armsy"
+ desc = "Abomination made from severed limbs."
+ icon_state = "armsy_start"
+ icon_living = "armsy_start"
+ maxHealth = 200
+ health = 200
+ obj_damage = 80
+ melee_damage_lower = 10
+ melee_damage_upper = 15
+ move_resist = MOVE_FORCE_OVERPOWERING+1
+ movement_type = GROUND
+ environment_smash = ENVIRONMENT_SMASH_RWALLS
+ sight = SEE_MOBS
+ spells_to_add = list(/obj/effect/proc_holder/spell/targeted/worm_contract)
+ ranged = TRUE
+ ///Previous segment in the chain
+ var/mob/living/simple_animal/hostile/eldritch/armsy/back
+ ///Next segment in the chain
+ var/mob/living/simple_animal/hostile/eldritch/armsy/front
+ ///Your old location
+ var/oldloc
+ ///Allow / disallow pulling
+ var/allow_pulling = FALSE
+ ///How many arms do we have to eat to expand?
+ var/stacks_to_grow = 2
+ ///Currently eaten arms
+ var/current_stacks = 0
+
+//I tried Initalize but it didnt work, like at all. This proc just wouldnt fire if it was Initalize instead of New
+/mob/living/simple_animal/hostile/eldritch/armsy/Initialize(mapload,spawn_more = TRUE,len = 6)
+ . = ..()
+ if(len < 3)
+ stack_trace("Eldritch Armsy created with invalid len ([len]). Reverting to 3.")
+ len = 3 //code breaks below 3, let's just not allow it.
+ oldloc = loc
+ RegisterSignal(src,COMSIG_MOVABLE_MOVED,.proc/update_chain_links)
+ if(!spawn_more)
+ return
+ allow_pulling = TRUE
+ ///next link
+ var/mob/living/simple_animal/hostile/eldritch/armsy/next
+ ///previous link
+ var/mob/living/simple_animal/hostile/eldritch/armsy/prev
+ ///current link
+ var/mob/living/simple_animal/hostile/eldritch/armsy/current
+ for(var/i in 0 to len)
+ prev = current
+ //i tried using switch, but byond is really fucky and it didnt work as intended. Im sorry
+ if(i == 0)
+ current = new type(drop_location(),FALSE)
+ current.icon_state = "armsy_mid"
+ current.icon_living = "armsy_mid"
+ current.front = src
+ current.AIStatus = AI_OFF
+ back = current
+ else if(i < len)
+ current = new type(drop_location(),FALSE)
+ prev.back = current
+ prev.icon_state = "armsy_mid"
+ prev.icon_living = "armsy_mid"
+ prev.front = next
+ prev.AIStatus = AI_OFF
+ else
+ prev.icon_state = "armsy_end"
+ prev.icon_living = "armsy_end"
+ prev.front = next
+ prev.AIStatus = AI_OFF
+ next = prev
+
+//we are literally a vessel of otherworldly destruction, we bring our own gravity unto this plane
+/mob/living/simple_animal/hostile/eldritch/armsy/has_gravity(turf/T)
+ return TRUE
+
+
+/mob/living/simple_animal/hostile/eldritch/armsy/can_be_pulled()
+ return FALSE
+
+///Updates chain links to force move onto a single tile
+/mob/living/simple_animal/hostile/eldritch/armsy/proc/contract_next_chain_into_single_tile()
+ if(back)
+ back.forceMove(loc)
+ back.contract_next_chain_into_single_tile()
+ return
+
+///Updates the next mob in the chain to move to our last location, fixed the worm if somehow broken.
+/mob/living/simple_animal/hostile/eldritch/armsy/proc/update_chain_links()
+ gib_trail()
+ if(back && back.loc != oldloc)
+ back.Move(oldloc)
+ // self fixing properties if somehow broken
+ if(front && loc != front.oldloc)
+ forceMove(front.oldloc)
+ oldloc = loc
+
+/mob/living/simple_animal/hostile/eldritch/armsy/proc/gib_trail()
+ if(front) // head makes gibs
+ return
+ var/chosen_decal = pick(typesof(/obj/effect/decal/cleanable/blood/tracks))
+ var/obj/effect/decal/cleanable/blood/gibs/decal = new chosen_decal(drop_location())
+ decal.setDir(dir)
+
+/mob/living/simple_animal/hostile/eldritch/armsy/Destroy()
+ if(front)
+ front.icon_state = "armsy_end"
+ front.icon_living = "armsy_end"
+ front.back = null
+ if(back)
+ QDEL_NULL(back) // chain destruction baby
+ return ..()
+
+/mob/living/simple_animal/hostile/eldritch/armsy/BiologicalLife(seconds, times_fired)
+ adjustBruteLoss(-2)
+
+/mob/living/simple_animal/hostile/eldritch/armsy/proc/heal()
+ if(health == maxHealth)
+ if(back)
+ back.heal()
+ return
+ else
+ current_stacks++
+ if(current_stacks >= stacks_to_grow)
+ var/mob/living/simple_animal/hostile/eldritch/armsy/prev = new type(drop_location(),spawn_more = FALSE)
+ icon_state = "armsy_mid"
+ icon_living = "armsy_mid"
+ back = prev
+ prev.icon_state = "armsy_end"
+ prev.icon_living = "armsy_end"
+ prev.front = src
+ prev.AIStatus = AI_OFF
+ current_stacks = 0
+
+ adjustBruteLoss(-maxHealth * 0.5, FALSE)
+ adjustFireLoss(-maxHealth * 0.5 ,FALSE)
+
+
+/mob/living/simple_animal/hostile/eldritch/armsy/Shoot(atom/targeted_atom)
+ target = targeted_atom
+ AttackingTarget()
+
+
+/mob/living/simple_animal/hostile/eldritch/armsy/AttackingTarget()
+ if(istype(target,/obj/item/bodypart/r_arm) || istype(target,/obj/item/bodypart/l_arm))
+ qdel(target)
+ heal()
+ return
+ if(target == back || target == front)
+ return
+ if(back)
+ back.target = target
+ back.AttackingTarget()
+ if(!Adjacent(target))
+ return
+ do_attack_animation(target)
+ //have fun
+ //if(istype(target,/turf/closed/wall))
+ //var/turf/closed/wall = target
+ //wall.ScrapeAway()
+
+
+ if(iscarbon(target))
+ var/mob/living/carbon/C = target
+ if(HAS_TRAIT(C, TRAIT_NODISMEMBER))
+ return
+ var/list/parts = list()
+ for(var/X in C.bodyparts)
+ var/obj/item/bodypart/bodypart = X
+ if(bodypart.body_part != HEAD && bodypart.body_part != CHEST)
+ if(bodypart.dismemberable)
+ parts += bodypart
+ if(length(parts) && prob(10))
+ var/obj/item/bodypart/bodypart = pick(parts)
+ bodypart.dismember()
+
+ return ..()
+
+/mob/living/simple_animal/hostile/eldritch/armsy/prime
+ name = "Lord of the Night"
+ real_name = "Master of Decay"
+ maxHealth = 400
+ health = 400
+ melee_damage_lower = 20
+ melee_damage_upper = 25
+
+/mob/living/simple_animal/hostile/eldritch/armsy/prime/Initialize(mapload,spawn_more = TRUE,len = 9)
+ . = ..()
+ var/matrix/matrix_transformation = matrix()
+ matrix_transformation.Scale(1.4,1.4)
+ transform = matrix_transformation
+
+/mob/living/simple_animal/hostile/eldritch/armsy/primeproc/heal()
+ if(health == maxHealth)
+ if(back)
+ back.heal()
+ return
+ else
+ current_stacks++
+ if(current_stacks >= stacks_to_grow)
+ var/mob/living/simple_animal/hostile/eldritch/armsy/prev = new type(drop_location(),spawn_more = FALSE)
+ icon_state = "armsy_mid"
+ icon_living = "armsy_mid"
+ back = prev
+ prev.icon_state = "armsy_end"
+ prev.icon_living = "armsy_end"
+ prev.front = src
+ prev.AIStatus = AI_OFF
+ current_stacks = 0
+ var/matrix/matrix_transformation = matrix()
+ matrix_transformation.Scale(1.4,1.4)
+ transform = matrix_transformation
+
+ adjustBruteLoss(-maxHealth * 0.5, FALSE)
+ adjustFireLoss(-maxHealth * 0.5 ,FALSE)
+
+
+/mob/living/simple_animal/hostile/eldritch/rust_spirit
+ name = "Rust Walker"
+ real_name = "Rusty"
+ desc = "Incomprehensible abomination actively seeping life out of it's surrounding."
+ icon_state = "rust_walker_s"
+ status_flags = CANPUSH
+ icon_living = "rust_walker_s"
+ maxHealth = 75
+ health = 75
+ melee_damage_lower = 15
+ melee_damage_upper = 20
+ sight = SEE_TURFS
+ spells_to_add = list(/obj/effect/proc_holder/spell/aoe_turf/rust_conversion/small,/obj/effect/proc_holder/spell/aimed/rust_wave/short)
+
+/mob/living/simple_animal/hostile/eldritch/rust_spirit/setDir(newdir)
+ . = ..()
+ if(newdir == NORTH)
+ icon_state = "rust_walker_n"
+ else if(newdir == SOUTH)
+ icon_state = "rust_walker_s"
+ update_icon()
+
+/mob/living/simple_animal/hostile/eldritch/rust_spirit/Moved()
+ . = ..()
+ playsound(src, 'sound/effects/footstep/rustystep1.ogg', 100, TRUE)
+
+/mob/living/simple_animal/hostile/eldritch/rust_spirit/Life()
+ if(stat == DEAD)
+ return ..()
+ var/turf/T = get_turf(src)
+ if(istype(T,/turf/open/floor/plating/rust))
+ adjustBruteLoss(-3, FALSE)
+ adjustFireLoss(-3, FALSE)
+ return ..()
+
+/mob/living/simple_animal/hostile/eldritch/ash_spirit
+ name = "Ash Man"
+ real_name = "Ashy"
+ desc = "Incomprehensible abomination actively seeping life out of it's surrounding."
+ icon_state = "ash_walker"
+ status_flags = CANPUSH
+ icon_living = "ash_walker"
+ maxHealth = 75
+ health = 75
+ melee_damage_lower = 15
+ melee_damage_upper = 20
+ sight = SEE_TURFS
+ spells_to_add = list(/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash,/obj/effect/proc_holder/spell/pointed/cleave/long,/obj/effect/proc_holder/spell/aoe_turf/fire_cascade)
+
+/mob/living/simple_animal/hostile/eldritch/stalker
+ name = "Flesh Stalker"
+ real_name = "Flesh Stalker"
+ desc = "Abomination made from severed limbs."
+ icon_state = "stalker"
+ status_flags = CANPUSH
+ icon_living = "stalker"
+ maxHealth = 150
+ health = 150
+ melee_damage_lower = 15
+ melee_damage_upper = 20
+ sight = SEE_MOBS
+ spells_to_add = list(/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash,/obj/effect/proc_holder/spell/targeted/shapeshift/eldritch,/obj/effect/proc_holder/spell/targeted/emplosion/eldritch)
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index 706bcc46f5..be0338a60e 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -146,6 +146,8 @@
var/bare_wound_bonus = 0
//If the attacks from this are sharp
var/sharpness = SHARP_NONE
+ //Generic flags
+ var/simple_mob_flags = NONE
/mob/living/simple_animal/Initialize()
. = ..()
diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm
index b6de7b2eab..329d74b1bf 100644
--- a/code/modules/reagents/chemistry/reagents/other_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm
@@ -2331,6 +2331,32 @@
M.adjustStaminaLoss(-0.25*REM) // the more wounds, the more stamina regen
..()
+datum/reagent/eldritch
+ name = "Eldritch Essence"
+ description = "Strange liquid that defies the laws of physics"
+ taste_description = "Ag'hsj'saje'sh"
+ color = "#1f8016"
+
+/datum/reagent/eldritch/on_mob_life(mob/living/carbon/M)
+ if(IS_HERETIC(M))
+ M.drowsyness = max(M.drowsyness-5, 0)
+ M.AdjustAllImmobility(-40, FALSE)
+ M.adjustStaminaLoss(-10, FALSE)
+ M.adjustToxLoss(-2, FALSE)
+ M.adjustOxyLoss(-2, FALSE)
+ M.adjustBruteLoss(-2, FALSE)
+ M.adjustFireLoss(-2, FALSE)
+ if(ishuman(M) && M.blood_volume < BLOOD_VOLUME_NORMAL)
+ M.blood_volume += 3
+ else
+ M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3, 150)
+ M.adjustToxLoss(2, FALSE)
+ M.adjustFireLoss(2, FALSE)
+ M.adjustOxyLoss(2, FALSE)
+ M.adjustBruteLoss(2, FALSE)
+ holder.remove_reagent(type, 1)
+ return TRUE
+
/datum/reagent/cellulose
name = "Cellulose Fibers"
description = "A crystaline polydextrose polymer, plants swear by this stuff."
diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm
index 57502e2cd5..2272a14612 100644
--- a/code/modules/spells/spell.dm
+++ b/code/modules/spells/spell.dm
@@ -224,7 +224,15 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
/obj/effect/proc_holder/spell/proc/choose_targets(mob/user = usr) //depends on subtype - /targeted or /aoe_turf
return
-/obj/effect/proc_holder/spell/proc/can_target(mob/living/target)
+/**
+ * can_target: Checks if we are allowed to cast the spell on a target.
+ *
+ * Arguments:
+ * * target The atom that is being targeted by the spell.
+ * * user The mob using the spell.
+ * * silent If the checks should not give any feedback messages.
+ */
+/obj/effect/proc_holder/spell/proc/can_target(atom/target, mob/user, silent = FALSE)
return TRUE
/obj/effect/proc_holder/spell/proc/start_recharge()
@@ -296,6 +304,13 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
/obj/effect/proc_holder/spell/proc/cast(list/targets,mob/user = usr)
return
+/obj/effect/proc_holder/spell/proc/view_or_range(distance = world.view, center=usr, type="view")
+ switch(type)
+ if("view")
+ . = view(distance,center)
+ if("range")
+ . = range(distance,center)
+
/obj/effect/proc_holder/spell/proc/revert_cast(mob/user = usr) //resets recharge or readds a charge
switch(charge_type)
if("recharge")
@@ -345,7 +360,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
switch(max_targets)
if(0) //unlimited
for(var/mob/living/target in view_or_range(range, user, selection_type))
- if(!can_target(target))
+ if(!can_target(target, user, TRUE))
continue
targets += target
if(1) //single target can be picked
@@ -357,7 +372,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
for(var/mob/living/M in view_or_range(range, user, selection_type))
if(!include_user && user == M)
continue
- if(!can_target(M))
+ if(!can_target(M, user, TRUE))
continue
possible_targets += M
@@ -365,7 +380,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
//Adds a safety check post-input to make sure those targets are actually in range.
var/mob/M
if(!random_target)
- M = input("Choose the target for the spell.", "Targeting") as null|mob in possible_targets
+ M = input("Choose the target for the spell.", "Targeting") as null|mob in sortNames(possible_targets)
else
switch(random_target_priority)
if(TARGET_RANDOM)
@@ -385,7 +400,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
else
var/list/possible_targets = list()
for(var/mob/living/target in view_or_range(range, user, selection_type))
- if(!can_target(target))
+ if(!can_target(target, user, TRUE))
continue
possible_targets += target
for(var/i=1,i<=max_targets,i++)
@@ -411,7 +426,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for th
var/list/targets = list()
for(var/turf/target in view_or_range(range,user,selection_type))
- if(!can_target(target))
+ if(!can_target(target, user, TRUE))
continue
if(!(target in view_or_range(inner_radius,user,selection_type)))
targets += target
diff --git a/code/modules/spells/spell_types/cone_spells.dm b/code/modules/spells/spell_types/cone_spells.dm
new file mode 100644
index 0000000000..63bae4b7cf
--- /dev/null
+++ b/code/modules/spells/spell_types/cone_spells.dm
@@ -0,0 +1,117 @@
+/obj/effect/proc_holder/spell/cone
+ name = "Cone of Nothing"
+ desc = "Does nothing in a cone! Wow!"
+ school = "evocation"
+ charge_max = 100
+ clothes_req = FALSE
+ invocation = "FUKAN NOTHAN"
+ invocation_type = "shout"
+ sound = 'sound/magic/forcewall.ogg'
+ action_icon_state = "shield"
+ range = -1
+ cooldown_min = 0.5 SECONDS
+ ///This controls how many levels the cone has, increase this value to make a bigger cone.
+ var/cone_levels = 3
+ ///This value determines if the cone penetrates walls.
+ var/respect_density = FALSE
+
+/obj/effect/proc_holder/spell/cone/choose_targets(mob/user = usr)
+ perform(null, user=user)
+
+///This proc creates a list of turfs that are hit by the cone
+/obj/effect/proc_holder/spell/cone/proc/cone_helper(var/turf/starter_turf, var/dir_to_use, var/cone_levels = 3)
+ var/list/turfs_to_return = list()
+ var/turf/turf_to_use = starter_turf
+ var/turf/left_turf
+ var/turf/right_turf
+ var/right_dir
+ var/left_dir
+ switch(dir_to_use)
+ if(NORTH)
+ left_dir = WEST
+ right_dir = EAST
+ if(SOUTH)
+ left_dir = EAST
+ right_dir = WEST
+ if(EAST)
+ left_dir = NORTH
+ right_dir = SOUTH
+ if(WEST)
+ left_dir = SOUTH
+ right_dir = NORTH
+
+
+ for(var/i in 1 to cone_levels)
+ var/list/level_turfs = list()
+ turf_to_use = get_step(turf_to_use, dir_to_use)
+ level_turfs += turf_to_use
+ if(i != 1)
+ left_turf = get_step(turf_to_use, left_dir)
+ level_turfs += left_turf
+ right_turf = get_step(turf_to_use, right_dir)
+ level_turfs += right_turf
+ for(var/left_i in 1 to i -calculate_cone_shape(i))
+ if(left_turf.density && respect_density)
+ break
+ left_turf = get_step(left_turf, left_dir)
+ level_turfs += left_turf
+ for(var/right_i in 1 to i -calculate_cone_shape(i))
+ if(right_turf.density && respect_density)
+ break
+ right_turf = get_step(right_turf, right_dir)
+ level_turfs += right_turf
+ turfs_to_return += list(level_turfs)
+ if(i == cone_levels)
+ continue
+ if(turf_to_use.density && respect_density)
+ break
+ return turfs_to_return
+
+/obj/effect/proc_holder/spell/cone/cast(list/targets,mob/user = usr)
+ var/list/cone_turfs = cone_helper(get_turf(user), user.dir, cone_levels)
+ for(var/list/turf_list in cone_turfs)
+ do_cone_effects(turf_list)
+
+///This proc does obj, mob and turf cone effects on all targets in a list
+/obj/effect/proc_holder/spell/cone/proc/do_cone_effects(list/target_turf_list, level)
+ for(var/target_turf in target_turf_list)
+ if(!target_turf) //if turf is no longer there
+ continue
+ do_turf_cone_effect(target_turf, level)
+ if(isopenturf(target_turf))
+ var/turf/open/open_turf = target_turf
+ for(var/movable_content in open_turf)
+ if(isobj(movable_content))
+ do_obj_cone_effect(movable_content, level)
+ else if(isliving(movable_content))
+ do_mob_cone_effect(movable_content, level)
+
+///This proc deterimines how the spell will affect turfs.
+/obj/effect/proc_holder/spell/cone/proc/do_turf_cone_effect(turf/target_turf, level)
+ return
+
+///This proc deterimines how the spell will affect objects.
+/obj/effect/proc_holder/spell/cone/proc/do_obj_cone_effect(obj/target_obj, level)
+ return
+
+///This proc deterimines how the spell will affect mobs.
+/obj/effect/proc_holder/spell/cone/proc/do_mob_cone_effect(mob/living/target_mob, level)
+ return
+
+///This proc adjusts the cones width depending on the level.
+/obj/effect/proc_holder/spell/cone/proc/calculate_cone_shape(current_level)
+ var/end_taper_start = round(cone_levels * 0.8)
+ if(current_level > end_taper_start)
+ return (current_level % end_taper_start) * 2 //someone more talented and probably come up with a better formula.
+ else
+ return 2
+
+///This type of cone gradually affects each level of the cone instead of affecting the entire area at once.
+/obj/effect/proc_holder/spell/cone/staggered
+
+/obj/effect/proc_holder/spell/cone/staggered/cast(list/targets,mob/user = usr)
+ var/level_counter = 0
+ var/list/cone_turfs = cone_helper(get_turf(user), user.dir, cone_levels)
+ for(var/list/turf_list in cone_turfs)
+ level_counter++
+ addtimer(CALLBACK(src, .proc/do_cone_effects, turf_list, level_counter), 2 * level_counter)
diff --git a/code/modules/spells/spell_types/dumbfire.dm b/code/modules/spells/spell_types/dumbfire.dm
index 6931b4ac31..424c702a31 100644
--- a/code/modules/spells/spell_types/dumbfire.dm
+++ b/code/modules/spells/spell_types/dumbfire.dm
@@ -49,8 +49,8 @@
var/projectile_type = text2path(proj_type)
projectile = new projectile_type(user)
else if(istype(proj_type, /obj/effect/proc_holder/spell))
- projectile = new /obj/effect/proc_holder/spell/targeted/trigger(user)
- var/obj/effect/proc_holder/spell/targeted/trigger/T = projectile
+ projectile = new /obj/effect/proc_holder/spell/pointed/trigger(user)
+ var/obj/effect/proc_holder/spell/pointed/trigger/T = projectile
T.linked_spells += proj_type
else
projectile = new proj_type(user)
diff --git a/code/modules/spells/spell_types/ethereal_jaunt.dm b/code/modules/spells/spell_types/ethereal_jaunt.dm
index 8cf51d45c6..9d91b6534d 100644
--- a/code/modules/spells/spell_types/ethereal_jaunt.dm
+++ b/code/modules/spells/spell_types/ethereal_jaunt.dm
@@ -17,7 +17,7 @@
action_icon_state = "jaunt"
/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/cast(list/targets,mob/user = usr) //magnets, so mostly hardcoded
- playsound(get_turf(user), 'sound/magic/ethereal_enter.ogg', 50, 1, -1)
+ play_sound("enter",user)
for(var/mob/living/target in targets)
INVOKE_ASYNC(src, .proc/do_jaunt, target)
@@ -42,7 +42,7 @@
ADD_TRAIT(target, TRAIT_MOBILITY_NOMOVE, src)
target.update_mobility()
holder.reappearing = 1
- playsound(get_turf(target), 'sound/magic/ethereal_exit.ogg', 50, 1, -1)
+ play_sound("exit",target)
sleep(25 - jaunt_in_time)
new jaunt_in_type(mobloc, holder.dir)
target.setDir(holder.dir)
@@ -63,6 +63,13 @@
steam.set_up(10, 0, mobloc)
steam.start()
+/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/proc/play_sound(type,mob/living/target)
+ switch(type)
+ if("enter")
+ playsound(get_turf(target), 'sound/magic/ethereal_enter.ogg', 50, TRUE, -1)
+ if("exit")
+ playsound(get_turf(target), 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1)
+
/obj/effect/dummy/phased_mob/spell_jaunt
name = "water"
icon = 'icons/effects/effects.dmi'
diff --git a/code/modules/spells/spell_types/mind_transfer.dm b/code/modules/spells/spell_types/mind_transfer.dm
deleted file mode 100644
index d2ef015d1d..0000000000
--- a/code/modules/spells/spell_types/mind_transfer.dm
+++ /dev/null
@@ -1,88 +0,0 @@
-/obj/effect/proc_holder/spell/targeted/mind_transfer
- name = "Mind Transfer"
- desc = "This spell allows the user to switch bodies with a target."
-
- school = "transmutation"
- charge_max = 600
- clothes_req = NONE
- invocation = "GIN'YU CAPAN"
- invocation_type = "whisper"
- range = 1
- cooldown_min = 200 //100 deciseconds reduction per rank
- var/unconscious_amount_caster = 400 //how much the caster is stunned for after the spell
- var/unconscious_amount_victim = 400 //how much the victim is stunned for after the spell
-
- action_icon_state = "mindswap"
-
-/*
-Urist: I don't feel like figuring out how you store object spells so I'm leaving this for you to do.
-Make sure spells that are removed from spell_list are actually removed and deleted when mind transferring.
-Also, you never added distance checking after target is selected. I've went ahead and did that.
-*/
-/obj/effect/proc_holder/spell/targeted/mind_transfer/cast(list/targets, mob/living/user = usr, distanceoverride, silent = FALSE)
- if(!targets.len)
- if(!silent)
- to_chat(user, "No mind found!")
- return
-
- if(targets.len > 1)
- if(!silent)
- to_chat(user, "Too many minds! You're not a hive damnit!")
- return
-
- var/mob/living/target = targets[1]
-
- var/t_He = target.p_they(TRUE)
- var/t_is = target.p_are()
-
- if(!(target in oview(range)) && !distanceoverride)//If they are not in overview after selection. Do note that !() is necessary for in to work because ! takes precedence over it.
- if(!silent)
- to_chat(user, "[t_He] [t_is] too far away!")
- return
-
- if(ismegafauna(target))
- if(!silent)
- to_chat(user, "This creature is too powerful to control!")
- return
-
- if(target.stat == DEAD)
- if(!silent)
- to_chat(user, "You don't particularly want to be dead!")
- return
-
- if(!target.key || !target.mind)
- if(!silent)
- to_chat(user, "[t_He] appear[target.p_s()] to be catatonic! Not even magic can affect [target.p_their()] vacant mind.")
- return
-
- if(user.suiciding)
- if(!silent)
- to_chat(user, "You're killing yourself! You can't concentrate enough to do this!")
- return
-
- var/datum/mind/TM = target.mind
- if(target.anti_magic_check(TRUE, FALSE) || TM.has_antag_datum(/datum/antagonist/wizard) || TM.has_antag_datum(/datum/antagonist/cult) || TM.has_antag_datum(/datum/antagonist/clockcult) || TM.has_antag_datum(/datum/antagonist/changeling) || TM.has_antag_datum(/datum/antagonist/rev) || target.key[1] == "@")
- if(!silent)
- to_chat(user, "[target.p_their(TRUE)] mind is resisting your spell!")
- return
-
- var/mob/living/victim = target//The target of the spell whos body will be transferred to.
- var/mob/living/caster = user//The wizard/whomever doing the body transferring.
-
- //MIND TRANSFER BEGIN
- var/mob/dead/observer/ghost = victim.ghostize(FALSE, TRUE)
- caster.mind.transfer_to(victim)
-
- ghost.mind.transfer_to(caster)
- if(ghost.key)
- ghost.transfer_ckey(caster) //have to transfer the key since the mind was not active
- qdel(ghost)
-
- //MIND TRANSFER END
-
- //Here we knock both mobs out for a time.
- caster.Unconscious(unconscious_amount_caster)
- victim.Unconscious(unconscious_amount_victim)
- SEND_SOUND(caster, sound('sound/magic/mandswap.ogg'))
- SEND_SOUND(victim, sound('sound/magic/mandswap.ogg'))// only the caster and victim hear the sounds, that way no one knows for sure if the swap happened
- return TRUE
diff --git a/code/modules/spells/spell_types/barnyard.dm b/code/modules/spells/spell_types/pointed/barnyard.dm
similarity index 52%
rename from code/modules/spells/spell_types/barnyard.dm
rename to code/modules/spells/spell_types/pointed/barnyard.dm
index 4b972e8030..61e3cf7127 100644
--- a/code/modules/spells/spell_types/barnyard.dm
+++ b/code/modules/spells/spell_types/pointed/barnyard.dm
@@ -1,51 +1,54 @@
-/obj/effect/proc_holder/spell/targeted/barnyardcurse
+/obj/effect/proc_holder/spell/pointed/barnyardcurse
name = "Curse of the Barnyard"
desc = "This spell dooms an unlucky soul to possess the speech and facial attributes of a barnyard animal."
school = "transmutation"
charge_type = "recharge"
charge_max = 150
charge_counter = 0
- clothes_req = NONE
- stat_allowed = 0
+ clothes_req = FALSE
+ stat_allowed = FALSE
invocation = "KN'A FTAGHU, PUCK 'BTHNK!"
invocation_type = "shout"
range = 7
cooldown_min = 30
- selection_type = "range"
- var/list/compatible_mobs = list(/mob/living/carbon/human, /mob/living/carbon/monkey)
-
+ ranged_mousepointer = 'icons/effects/mouse_pointers/barn_target.dmi'
action_icon_state = "barn"
+ active_msg = "You prepare to curse a target..."
+ deactive_msg = "You dispel the curse..."
+ /// List of mobs which are allowed to be a target of the spell
+ var/static/list/compatible_mobs_typecache = typecacheof(list(/mob/living/carbon/human, /mob/living/carbon/monkey))
-/obj/effect/proc_holder/spell/targeted/barnyardcurse/cast(list/targets, mob/user = usr)
+/obj/effect/proc_holder/spell/pointed/barnyardcurse/cast(list/targets, mob/user)
if(!targets.len)
- to_chat(user, "No target found in range.")
- return
+ to_chat(user, "No target found in range!")
+ return FALSE
+ if(!can_target(targets[1], user))
+ return FALSE
var/mob/living/carbon/target = targets[1]
-
- if(!(target.type in compatible_mobs))
- to_chat(user, "You are unable to curse [target]'s head!")
- return
-
- if(!(target in oview(range)))
- to_chat(user, "[target.p_theyre(TRUE)] too far away!")
- return
-
if(target.anti_magic_check())
to_chat(user, "The spell had no effect!")
target.visible_message("[target]'s face bursts into flames, which instantly burst outward, leaving [target] unharmed!", \
- "Your face starts burning up, but the flames are repulsed by your anti-magic protection!")
- return
+ "Your face starts burning up, but the flames are repulsed by your anti-magic protection!")
+ return FALSE
var/list/masks = list(/obj/item/clothing/mask/pig/cursed, /obj/item/clothing/mask/cowmask/cursed, /obj/item/clothing/mask/horsehead/cursed)
-
var/choice = pick(masks)
var/obj/item/clothing/mask/magichead = new choice(get_turf(target))
- magichead.flags_inv = null
+
target.visible_message("[target]'s face bursts into flames, and a barnyard animal's head takes its place!", \
"Your face burns up, and shortly after the fire you realise you have the face of a barnyard animal!")
if(!target.dropItemToGround(target.wear_mask))
qdel(target.wear_mask)
- target.equip_to_slot_if_possible(magichead, SLOT_WEAR_MASK, 1, 1)
-
+ target.equip_to_slot_if_possible(magichead, ITEM_SLOT_MASK, 1, 1)
target.flash_act()
+
+/obj/effect/proc_holder/spell/pointed/barnyardcurse/can_target(atom/target, mob/user, silent)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(!is_type_in_typecache(target, compatible_mobs_typecache))
+ if(!silent)
+ to_chat(user, "You are unable to curse [target]!")
+ return FALSE
+ return TRUE
diff --git a/code/modules/spells/spell_types/pointed/blind.dm b/code/modules/spells/spell_types/pointed/blind.dm
new file mode 100644
index 0000000000..a773c5ad8d
--- /dev/null
+++ b/code/modules/spells/spell_types/pointed/blind.dm
@@ -0,0 +1,35 @@
+/obj/effect/proc_holder/spell/pointed/trigger/blind
+ name = "Blind"
+ desc = "This spell temporarily blinds a single target."
+ school = "transmutation"
+ charge_max = 300
+ clothes_req = FALSE
+ invocation = "STI KALY"
+ invocation_type = "whisper"
+ message = "Your eyes cry out in pain!"
+ cooldown_min = 50 //12 deciseconds reduction per rank
+ starting_spells = list("/obj/effect/proc_holder/spell/targeted/inflict_handler/blind", "/obj/effect/proc_holder/spell/targeted/genetic/blind")
+ ranged_mousepointer = 'icons/effects/mouse_pointers/blind_target.dmi'
+ action_icon_state = "blind"
+ active_msg = "You prepare to blind a target..."
+
+/obj/effect/proc_holder/spell/targeted/inflict_handler/blind
+ amt_eye_blind = 10
+ amt_eye_blurry = 20
+ sound = 'sound/magic/blind.ogg'
+
+/obj/effect/proc_holder/spell/targeted/genetic/blind
+ mutations = list(BLINDMUT)
+ duration = 300
+ charge_max = 400 // needs to be higher than the duration or it'll be permanent
+ sound = 'sound/magic/blind.ogg'
+
+/obj/effect/proc_holder/spell/pointed/trigger/blind/can_target(atom/target, mob/user, silent)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(!isliving(target))
+ if(!silent)
+ to_chat(user, "You can only blind living beings!")
+ return FALSE
+ return TRUE
diff --git a/code/modules/spells/spell_types/pointed/mind_transfer.dm b/code/modules/spells/spell_types/pointed/mind_transfer.dm
new file mode 100644
index 0000000000..28d646f6b6
--- /dev/null
+++ b/code/modules/spells/spell_types/pointed/mind_transfer.dm
@@ -0,0 +1,103 @@
+/obj/effect/proc_holder/spell/pointed/mind_transfer
+ name = "Mind Transfer"
+ desc = "This spell allows the user to switch bodies with a target next to him."
+ school = "transmutation"
+ charge_max = 600
+ clothes_req = FALSE
+ invocation = "GIN'YU CAPAN"
+ invocation_type = "whisper"
+ range = 1
+ cooldown_min = 200 //100 deciseconds reduction per rank
+ ranged_mousepointer = 'icons/effects/mouse_pointers/mindswap_target.dmi'
+ action_icon_state = "mindswap"
+ active_msg = "You prepare to swap minds with a target..."
+ /// For how long is the caster stunned for after the spell
+ var/unconscious_amount_caster = 40 SECONDS
+ /// For how long is the victim stunned for after the spell
+ var/unconscious_amount_victim = 40 SECONDS
+
+/obj/effect/proc_holder/spell/pointed/mind_transfer/cast(list/targets, mob/living/user, silent = FALSE)
+ if(!targets.len)
+ if(!silent)
+ to_chat(user, "No mind found!")
+ return FALSE
+ if(targets.len > 1)
+ if(!silent)
+ to_chat(user, "Too many minds! You're not a hive damnit!")
+ return FALSE
+ if(!can_target(targets[1], user, silent))
+ return FALSE
+
+ var/mob/living/victim = targets[1] //The target of the spell whos body will be transferred to.
+ var/datum/mind/VM = victim.mind
+ if(victim.anti_magic_check(TRUE, FALSE) || VM.has_antag_datum(/datum/antagonist/wizard) || VM.has_antag_datum(/datum/antagonist/cult) || VM.has_antag_datum(/datum/antagonist/changeling) || VM.has_antag_datum(/datum/antagonist/rev) || victim.key[1] == "@")
+ if(!silent)
+ to_chat(user, "[victim.p_their(TRUE)] mind is resisting your spell!")
+ return FALSE
+ if(istype(victim, /mob/living/simple_animal/hostile/guardian))
+ var/mob/living/simple_animal/hostile/guardian/stand = victim
+ if(stand.summoner)
+ victim = stand.summoner
+
+ //You should not be able to enter one of the most powerful side-antags as a fucking wizard.
+ if(istype(victim,/mob/living/simple_animal/slaughter))
+ to_chat(user, "The devilish contract doesn't include the 'mind swappable' package, please try again another lifetime.")
+ return
+
+ //MIND TRANSFER BEGIN
+ var/mob/dead/observer/ghost = victim.ghostize()
+ user.mind.transfer_to(victim)
+
+ ghost.mind.transfer_to(user)
+ if(ghost.key)
+ user.key = ghost.key //have to transfer the key since the mind was not active
+ qdel(ghost)
+ //MIND TRANSFER END
+
+ //Here we knock both mobs out for a time.
+ user.Unconscious(unconscious_amount_caster)
+ victim.Unconscious(unconscious_amount_victim)
+ SEND_SOUND(user, sound('sound/magic/mandswap.ogg'))
+ SEND_SOUND(victim, sound('sound/magic/mandswap.ogg')) // only the caster and victim hear the sounds, that way no one knows for sure if the swap happened
+ return TRUE
+
+/obj/effect/proc_holder/spell/pointed/mind_transfer/can_target(atom/target, mob/user, silent)
+ . = ..()
+ if(!.)
+ return FALSE
+ if(!isliving(target))
+ if(!silent)
+ to_chat(user, "You can only swap minds with living beings!")
+ return FALSE
+ if(user == target)
+ if(!silent)
+ to_chat(user, "You can't swap minds with yourself!")
+ return FALSE
+
+ var/mob/living/victim = target
+ var/t_He = victim.p_they(TRUE)
+
+ if(ismegafauna(victim))
+ if(!silent)
+ to_chat(user, "This creature is too powerful to control!")
+ return FALSE
+ if(victim.stat == DEAD)
+ if(!silent)
+ to_chat(user, "You don't particularly want to be dead!")
+ return FALSE
+ if(!victim.key || !victim.mind)
+ if(!silent)
+ to_chat(user, "[t_He] appear[victim.p_s()] to be catatonic! Not even magic can affect [victim.p_their()] vacant mind.")
+ return FALSE
+ if(user.suiciding)
+ if(!silent)
+ to_chat(user, "You're killing yourself! You can't concentrate enough to do this!")
+ return FALSE
+ if(istype(victim, /mob/living/simple_animal/hostile/guardian))
+ var/mob/living/simple_animal/hostile/guardian/stand = victim
+ if(stand.summoner)
+ if(stand.summoner == user)
+ if(!silent)
+ to_chat(user, "Swapping minds with your own guardian would just put you back into your own head!")
+ return FALSE
+ return TRUE
diff --git a/code/modules/spells/spell_types/pointed/pointed.dm b/code/modules/spells/spell_types/pointed/pointed.dm
new file mode 100644
index 0000000000..7b942dee27
--- /dev/null
+++ b/code/modules/spells/spell_types/pointed/pointed.dm
@@ -0,0 +1,105 @@
+/obj/effect/proc_holder/spell/pointed
+ name = "pointed spell"
+ ranged_mousepointer = 'icons/effects/mouse_pointers/throw_target.dmi'
+ action_icon_state = "projectile"
+ /// Message showing to the spell owner upon deactivating pointed spell.
+ var/deactive_msg = "You dispel the magic..."
+ /// Message showing to the spell owner upon activating pointed spell.
+ var/active_msg = "You prepare to use the spell on a target..."
+ /// Variable dictating if the user is allowed to cast a spell on himself.
+ var/self_castable = FALSE
+ /// Variable dictating if the spell will use turf based aim assist
+ var/aim_assist = TRUE
+
+/obj/effect/proc_holder/spell/pointed/Trigger(mob/user, skip_can_cast = TRUE)
+ if(!istype(user))
+ return
+ var/msg
+ if(!can_cast(user))
+ msg = "You can no longer cast [name]!"
+ remove_ranged_ability(msg)
+ return
+ if(active)
+ msg = "[deactive_msg]"
+ remove_ranged_ability(msg)
+ else
+ msg = "[active_msg] Left-click to activate spell on a target!"
+ add_ranged_ability(user, msg, TRUE)
+ on_activation(user)
+
+/obj/effect/proc_holder/spell/pointed/on_lose(mob/living/user)
+ remove_ranged_ability()
+
+/obj/effect/proc_holder/spell/pointed/remove_ranged_ability(msg)
+ . = ..()
+ on_deactivation(ranged_ability_user)
+
+/obj/effect/proc_holder/spell/pointed/add_ranged_ability(mob/living/user, msg, forced)
+ . = ..()
+ on_activation(user)
+
+/**
+ * on_activation: What happens upon pointed spell activation.
+ *
+ * Arguments:
+ * * user The mob interacting owning the spell.
+ */
+/obj/effect/proc_holder/spell/pointed/proc/on_activation(mob/user)
+ return
+
+/**
+ * on_activation: What happens upon pointed spell deactivation.
+ *
+ * Arguments:
+ * * user The mob interacting owning the spell.
+ */
+/obj/effect/proc_holder/spell/pointed/proc/on_deactivation(mob/user)
+ return
+
+/obj/effect/proc_holder/spell/pointed/update_icon()
+ if(!action)
+ return
+ if(active)
+ action.button_icon_state = "[action_icon_state]1"
+ else
+ action.button_icon_state = "[action_icon_state]"
+ action.UpdateButtonIcon()
+
+/obj/effect/proc_holder/spell/pointed/InterceptClickOn(mob/living/caller, params, atom/target)
+ if(..())
+ return TRUE
+ if(aim_assist && isturf(target))
+ var/list/possible_targets = list()
+ for(var/A in target)
+ if(intercept_check(caller, A, TRUE))
+ possible_targets += A
+ if(possible_targets.len == 1)
+ target = possible_targets[1]
+ if(!intercept_check(caller, target))
+ return TRUE
+ if(!cast_check(FALSE, caller))
+ return TRUE
+ perform(list(target), user = caller)
+ remove_ranged_ability()
+ return TRUE // Do not do any underlying actions after the spell cast
+
+/**
+ * intercept_check: Specific spell checks for InterceptClickOn() targets.
+ *
+ * Arguments:
+ * * user The mob using the ranged spell via intercept.
+ * * target The atom that is being targeted by the spell via intercept.
+ * * silent If the checks should produce not any feedback messages for the user.
+ */
+/obj/effect/proc_holder/spell/pointed/proc/intercept_check(mob/user, atom/target, silent = FALSE)
+ if(!self_castable && target == user)
+ if(!silent)
+ to_chat(user, "You cannot cast the spell on yourself!")
+ return FALSE
+ if(!(target in view_or_range(range, user, selection_type)))
+ if(!silent)
+ to_chat(user, "[target.p_theyre(TRUE)] too far away!")
+ return FALSE
+ if(!can_target(target, user, silent))
+ return FALSE
+ return TRUE
diff --git a/code/modules/spells/spell_types/projectile.dm b/code/modules/spells/spell_types/projectile.dm
index be305520a2..3a8f48cf7c 100644
--- a/code/modules/spells/spell_types/projectile.dm
+++ b/code/modules/spells/spell_types/projectile.dm
@@ -37,8 +37,8 @@
var/projectile_type = text2path(proj_type)
projectile = new projectile_type(user)
if(istype(proj_type, /obj/effect/proc_holder/spell))
- projectile = new /obj/effect/proc_holder/spell/targeted/trigger(user)
- var/obj/effect/proc_holder/spell/targeted/trigger/T = projectile
+ projectile = new /obj/effect/proc_holder/spell/pointed/trigger(user)
+ var/obj/effect/proc_holder/spell/pointed/trigger/T = projectile
T.linked_spells += proj_type
projectile.icon = proj_icon
projectile.icon_state = proj_icon_state
diff --git a/code/modules/spells/spell_types/trigger.dm b/code/modules/spells/spell_types/trigger.dm
index 39cff63d98..df579d9243 100644
--- a/code/modules/spells/spell_types/trigger.dm
+++ b/code/modules/spells/spell_types/trigger.dm
@@ -1,30 +1,26 @@
-/obj/effect/proc_holder/spell/targeted/trigger
+/obj/effect/proc_holder/spell/pointed/trigger
name = "Trigger"
desc = "This spell triggers another spell or a few."
-
var/list/linked_spells = list() //those are just referenced by the trigger spell and are unaffected by it directly
var/list/starting_spells = list() //those are added on New() to contents from default spells and are deleted when the trigger spell is deleted to prevent memory leaks
-/obj/effect/proc_holder/spell/targeted/trigger/Initialize()
+/obj/effect/proc_holder/spell/pointed/trigger/Initialize()
. = ..()
-
for(var/spell in starting_spells)
var/spell_to_add = text2path(spell)
new spell_to_add(src) //should result in adding to contents, needs testing
-/obj/effect/proc_holder/spell/targeted/trigger/Destroy()
+/obj/effect/proc_holder/spell/pointed/trigger/Destroy()
for(var/spell in contents)
qdel(spell)
linked_spells = null
starting_spells = null
return ..()
-/obj/effect/proc_holder/spell/targeted/trigger/cast(list/targets,mob/user = usr)
+/obj/effect/proc_holder/spell/pointed/trigger/cast(list/targets,mob/user = usr)
playMagSound()
for(var/mob/living/target in targets)
for(var/obj/effect/proc_holder/spell/spell in contents)
spell.perform(list(target),0)
for(var/obj/effect/proc_holder/spell/spell in linked_spells)
spell.perform(list(target),0)
-
- return
\ No newline at end of file
diff --git a/code/modules/spells/spell_types/wizard.dm b/code/modules/spells/spell_types/wizard.dm
index 13c47ab9ac..14f359ef81 100644
--- a/code/modules/spells/spell_types/wizard.dm
+++ b/code/modules/spells/spell_types/wizard.dm
@@ -207,40 +207,12 @@
summon_type = list(/mob/living/simple_animal/hostile/netherworld)
cast_sound = 'sound/magic/summonitems_generic.ogg'
-/obj/effect/proc_holder/spell/targeted/trigger/blind
- name = "Blind"
- desc = "This spell temporarily blinds a single person and does not require wizard garb."
-
- school = "transmutation"
- charge_max = 300
- clothes_req = NONE
- invocation = "STI KALY"
- invocation_type = "whisper"
- message = "Your eyes cry out in pain!"
- cooldown_min = 50 //12 deciseconds reduction per rank
-
- starting_spells = list("/obj/effect/proc_holder/spell/targeted/inflict_handler/blind","/obj/effect/proc_holder/spell/targeted/genetic/blind")
-
- action_icon_state = "blind"
-
/obj/effect/proc_holder/spell/aoe_turf/conjure/creature/cult
name = "Summon Creatures (DANGEROUS)"
clothes_req = SPELL_CULT_GARB
charge_max = 5000
summon_amt = 2
-
-
-/obj/effect/proc_holder/spell/targeted/inflict_handler/blind
- amt_eye_blind = 10
- amt_eye_blurry = 20
- sound = 'sound/magic/blind.ogg'
-
-/obj/effect/proc_holder/spell/targeted/genetic/blind
- mutations = list(BLINDMUT)
- duration = 300
- sound = 'sound/magic/blind.ogg'
-
/obj/effect/proc_holder/spell/aoe_turf/repulse
name = "Repulse"
desc = "This spell throws everything around the user away."
diff --git a/config/game_options.txt b/config/game_options.txt
index 1585d45e6d..a5b0d0b8c4 100644
--- a/config/game_options.txt
+++ b/config/game_options.txt
@@ -116,6 +116,7 @@ CONTINUOUS CHANGELING
CONTINUOUS WIZARD
#CONTINUOUS MONKEY
CONTINUOUS BLOODSUCKER
+CONTINUOUS HERESY
##Note: do not toggle continuous off for these modes, as they have no antagonists and would thus end immediately!
diff --git a/icons/effects/160x160.dmi b/icons/effects/160x160.dmi
index 0a97573a9b..2adedb6c03 100644
Binary files a/icons/effects/160x160.dmi and b/icons/effects/160x160.dmi differ
diff --git a/icons/effects/96x96.dmi b/icons/effects/96x96.dmi
index b60ff97b2b..34f4adf6ce 100644
Binary files a/icons/effects/96x96.dmi and b/icons/effects/96x96.dmi differ
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index a2fce4678f..52164e1d33 100644
Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ
diff --git a/icons/effects/eldritch.dmi b/icons/effects/eldritch.dmi
new file mode 100644
index 0000000000..82549dccf0
Binary files /dev/null and b/icons/effects/eldritch.dmi differ
diff --git a/icons/effects/mouse_pointers/barn_target.dmi b/icons/effects/mouse_pointers/barn_target.dmi
new file mode 100644
index 0000000000..475a2b88b1
Binary files /dev/null and b/icons/effects/mouse_pointers/barn_target.dmi differ
diff --git a/icons/effects/mouse_pointers/blind_target.dmi b/icons/effects/mouse_pointers/blind_target.dmi
new file mode 100644
index 0000000000..1d33fc7a4c
Binary files /dev/null and b/icons/effects/mouse_pointers/blind_target.dmi differ
diff --git a/icons/effects/mouse_pointers/cult_target.dmi b/icons/effects/mouse_pointers/cult_target.dmi
new file mode 100644
index 0000000000..650feb3361
Binary files /dev/null and b/icons/effects/mouse_pointers/cult_target.dmi differ
diff --git a/icons/effects/mouse_pointers/mecha_mouse-disable.dmi b/icons/effects/mouse_pointers/mecha_mouse-disable.dmi
new file mode 100644
index 0000000000..48924c58c2
Binary files /dev/null and b/icons/effects/mouse_pointers/mecha_mouse-disable.dmi differ
diff --git a/icons/effects/mouse_pointers/mecha_mouse.dmi b/icons/effects/mouse_pointers/mecha_mouse.dmi
new file mode 100644
index 0000000000..4b46a44684
Binary files /dev/null and b/icons/effects/mouse_pointers/mecha_mouse.dmi differ
diff --git a/icons/effects/mouse_pointers/mindswap_target.dmi b/icons/effects/mouse_pointers/mindswap_target.dmi
new file mode 100644
index 0000000000..32ccda154d
Binary files /dev/null and b/icons/effects/mouse_pointers/mindswap_target.dmi differ
diff --git a/icons/effects/mouse_pointers/overload_machine_target.dmi b/icons/effects/mouse_pointers/overload_machine_target.dmi
new file mode 100644
index 0000000000..8bc67cdab6
Binary files /dev/null and b/icons/effects/mouse_pointers/overload_machine_target.dmi differ
diff --git a/icons/effects/mouse_pointers/override_machine_target.dmi b/icons/effects/mouse_pointers/override_machine_target.dmi
new file mode 100644
index 0000000000..77dbb4ba32
Binary files /dev/null and b/icons/effects/mouse_pointers/override_machine_target.dmi differ
diff --git a/icons/effects/mouse_pointers/throw_target.dmi b/icons/effects/mouse_pointers/throw_target.dmi
new file mode 100644
index 0000000000..660eafbf2b
Binary files /dev/null and b/icons/effects/mouse_pointers/throw_target.dmi differ
diff --git a/icons/effects/mouse_pointers/wrap_target.dmi b/icons/effects/mouse_pointers/wrap_target.dmi
new file mode 100644
index 0000000000..2e9a338c9e
Binary files /dev/null and b/icons/effects/mouse_pointers/wrap_target.dmi differ
diff --git a/icons/mob/actions/actions_ecult.dmi b/icons/mob/actions/actions_ecult.dmi
new file mode 100644
index 0000000000..0a130f006e
Binary files /dev/null and b/icons/mob/actions/actions_ecult.dmi differ
diff --git a/icons/mob/actions/backgrounds.dmi b/icons/mob/actions/backgrounds.dmi
index 07839588ce..6b983df95a 100644
Binary files a/icons/mob/actions/backgrounds.dmi and b/icons/mob/actions/backgrounds.dmi differ
diff --git a/icons/mob/clothing/head.dmi b/icons/mob/clothing/head.dmi
index 0ad9452994..16571a4aa1 100644
Binary files a/icons/mob/clothing/head.dmi and b/icons/mob/clothing/head.dmi differ
diff --git a/icons/mob/clothing/neck.dmi b/icons/mob/clothing/neck.dmi
index 86dec62a41..7bfecb4158 100644
Binary files a/icons/mob/clothing/neck.dmi and b/icons/mob/clothing/neck.dmi differ
diff --git a/icons/mob/clothing/suit.dmi b/icons/mob/clothing/suit.dmi
index c94e8a46d3..08d276a484 100644
Binary files a/icons/mob/clothing/suit.dmi and b/icons/mob/clothing/suit.dmi differ
diff --git a/icons/mob/eldritch_mobs.dmi b/icons/mob/eldritch_mobs.dmi
new file mode 100644
index 0000000000..8a16d53f88
Binary files /dev/null and b/icons/mob/eldritch_mobs.dmi differ
diff --git a/icons/mob/hud.dmi b/icons/mob/hud.dmi
index c21fa47b9c..ad9817a43d 100644
Binary files a/icons/mob/hud.dmi and b/icons/mob/hud.dmi differ
diff --git a/icons/mob/inhands/64x64_lefthand.dmi b/icons/mob/inhands/64x64_lefthand.dmi
index 6b47171066..6dc8d82753 100644
Binary files a/icons/mob/inhands/64x64_lefthand.dmi and b/icons/mob/inhands/64x64_lefthand.dmi differ
diff --git a/icons/mob/inhands/64x64_righthand.dmi b/icons/mob/inhands/64x64_righthand.dmi
index 3750e28906..ca87f74a6f 100644
Binary files a/icons/mob/inhands/64x64_righthand.dmi and b/icons/mob/inhands/64x64_righthand.dmi differ
diff --git a/icons/mob/mob.dmi b/icons/mob/mob.dmi
index de09fb1c63..23f0e2cc13 100644
Binary files a/icons/mob/mob.dmi and b/icons/mob/mob.dmi differ
diff --git a/icons/obj/clothing/hats.dmi b/icons/obj/clothing/hats.dmi
index 60304999fa..8fbb2abe1e 100644
Binary files a/icons/obj/clothing/hats.dmi and b/icons/obj/clothing/hats.dmi differ
diff --git a/icons/obj/clothing/suits.dmi b/icons/obj/clothing/suits.dmi
index e8a360bc87..3476b16258 100644
Binary files a/icons/obj/clothing/suits.dmi and b/icons/obj/clothing/suits.dmi differ
diff --git a/icons/obj/eldritch.dmi b/icons/obj/eldritch.dmi
new file mode 100644
index 0000000000..50c2913708
Binary files /dev/null and b/icons/obj/eldritch.dmi differ
diff --git a/icons/obj/projectiles.dmi b/icons/obj/projectiles.dmi
index 92e76f78bb..94568b8633 100644
Binary files a/icons/obj/projectiles.dmi and b/icons/obj/projectiles.dmi differ
diff --git a/icons/turf/floors.dmi b/icons/turf/floors.dmi
index 83dd4f5df8..f2b84bbe03 100644
Binary files a/icons/turf/floors.dmi and b/icons/turf/floors.dmi differ
diff --git a/sound/ambience/antag/ecult_op.ogg b/sound/ambience/antag/ecult_op.ogg
new file mode 100644
index 0000000000..9944e833a6
Binary files /dev/null and b/sound/ambience/antag/ecult_op.ogg differ
diff --git a/sound/effects/footstep/rustystep1.ogg b/sound/effects/footstep/rustystep1.ogg
new file mode 100644
index 0000000000..bf90d52779
Binary files /dev/null and b/sound/effects/footstep/rustystep1.ogg differ
diff --git a/tgstation.dme b/tgstation.dme
index 9306ae0ef1..91f98dddc4 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -727,6 +727,7 @@
#include "code\game\gamemodes\dynamic\dynamic_rulesets_midround.dm"
#include "code\game\gamemodes\dynamic\dynamic_rulesets_roundstart.dm"
#include "code\game\gamemodes\dynamic\dynamic_storytellers.dm"
+#include "code\game\gamemodes\eldritch_cult\eldritch_cult.dm"
#include "code\game\gamemodes\extended\extended.dm"
#include "code\game\gamemodes\gangs\dominator.dm"
#include "code\game\gamemodes\gangs\dominator_countdown.dm"
@@ -1598,6 +1599,16 @@
#include "code\modules\antagonists\disease\disease_disease.dm"
#include "code\modules\antagonists\disease\disease_event.dm"
#include "code\modules\antagonists\disease\disease_mob.dm"
+#include "code\modules\antagonists\eldritch_cult\eldritch_antag.dm"
+#include "code\modules\antagonists\eldritch_cult\eldritch_book.dm"
+#include "code\modules\antagonists\eldritch_cult\eldritch_effects.dm"
+#include "code\modules\antagonists\eldritch_cult\eldritch_items.dm"
+#include "code\modules\antagonists\eldritch_cult\eldritch_knowledge.dm"
+#include "code\modules\antagonists\eldritch_cult\eldritch_magic.dm"
+#include "code\modules\antagonists\eldritch_cult\eldritch_monster_antag.dm"
+#include "code\modules\antagonists\eldritch_cult\knowledge\ash_lore.dm"
+#include "code\modules\antagonists\eldritch_cult\knowledge\flesh_lore.dm"
+#include "code\modules\antagonists\eldritch_cult\knowledge\rust_lore.dm"
#include "code\modules\antagonists\ert\ert.dm"
#include "code\modules\antagonists\fugitive\fugitive.dm"
#include "code\modules\antagonists\fugitive\fugitive_outfits.dm"
@@ -2584,6 +2595,7 @@
#include "code\modules\mob\living\simple_animal\constructs.dm"
#include "code\modules\mob\living\simple_animal\corpse.dm"
#include "code\modules\mob\living\simple_animal\damage_procs.dm"
+#include "code\modules\mob\living\simple_animal\eldritch_demons.dm"
#include "code\modules\mob\living\simple_animal\parrot.dm"
#include "code\modules\mob\living\simple_animal\pickle.dm"
#include "code\modules\mob\living\simple_animal\shade.dm"
@@ -3257,9 +3269,9 @@
#include "code\modules\spells\spell.dm"
#include "code\modules\spells\spell_types\aimed.dm"
#include "code\modules\spells\spell_types\area_teleport.dm"
-#include "code\modules\spells\spell_types\barnyard.dm"
#include "code\modules\spells\spell_types\bloodcrawl.dm"
#include "code\modules\spells\spell_types\charge.dm"
+#include "code\modules\spells\spell_types\cone_spells.dm"
#include "code\modules\spells\spell_types\conjure.dm"
#include "code\modules\spells\spell_types\construct_spells.dm"
#include "code\modules\spells\spell_types\curse.dm"
@@ -3278,7 +3290,6 @@
#include "code\modules\spells\spell_types\lichdom.dm"
#include "code\modules\spells\spell_types\lightning.dm"
#include "code\modules\spells\spell_types\mime.dm"
-#include "code\modules\spells\spell_types\mind_transfer.dm"
#include "code\modules\spells\spell_types\projectile.dm"
#include "code\modules\spells\spell_types\rightandwrong.dm"
#include "code\modules\spells\spell_types\rod_form.dm"
@@ -3295,6 +3306,10 @@
#include "code\modules\spells\spell_types\turf_teleport.dm"
#include "code\modules\spells\spell_types\voice_of_god.dm"
#include "code\modules\spells\spell_types\wizard.dm"
+#include "code\modules\spells\spell_types\pointed\barnyard.dm"
+#include "code\modules\spells\spell_types\pointed\blind.dm"
+#include "code\modules\spells\spell_types\pointed\mind_transfer.dm"
+#include "code\modules\spells\spell_types\pointed\pointed.dm"
#include "code\modules\station_goals\bsa.dm"
#include "code\modules\station_goals\dna_vault.dm"
#include "code\modules\station_goals\shield.dm"