diff --git a/code/__DEFINES/achievements.dm b/code/__DEFINES/achievements.dm
index bef191f217e..13aa83e7e9e 100644
--- a/code/__DEFINES/achievements.dm
+++ b/code/__DEFINES/achievements.dm
@@ -33,6 +33,7 @@
#define MEDAL_FLESH_ASCENSION "Flesh"
#define MEDAL_RUST_ASCENSION "Rust"
#define MEDAL_VOID_ASCENSION "Void"
+#define MEDAL_BLADE_ASCENSION "Blade"
#define MEDAL_TOOLBOX_SOUL "Toolsoul"
#define MEDAL_CHEM_TUT "Beginner Chemist"
#define MEDAL_HOT_DAMN "Hot Damn!"
diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm
index 1d5a4926ef8..00ca6ad3d56 100644
--- a/code/__DEFINES/antagonists.dm
+++ b/code/__DEFINES/antagonists.dm
@@ -69,6 +69,7 @@
#define PATH_RUST "Rust Path"
#define PATH_FLESH "Flesh Path"
#define PATH_VOID "Void Path"
+#define PATH_BLADE "Blade Path"
/// Defines are used in /proc/has_living_heart() to report if the heretic has no heart period, no living heart, or has a living heart.
#define HERETIC_NO_HEART_ORGAN -1
@@ -78,6 +79,12 @@
/// A define used in ritual priority for heretics.
#define MAX_KNOWLEDGE_PRIORITY 100
+/// Checks if the passed mob can become a heretic ghoul.
+/// - Must be a human (type, not species)
+/// - Skeletons cannot be husked (they are snowflaked instead of having a trait)
+/// - Monkeys are monkeys, not quite human (balance reasons)
+#define IS_VALID_GHOUL_MOB(mob) (ishuman(mob) && !isskeleton(mob) && !ismonkey(mob))
+
/// Forces the blob to place the core where they currently are, ignoring any checks.
#define BLOB_FORCE_PLACEMENT -1
/// Normal blob placement, does the regular checks to make sure the blob isn't placing itself in an invalid location
diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm
index 80dee577737..df83eeefcb5 100644
--- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm
+++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm
@@ -73,8 +73,10 @@
#define COMSIG_MOVABLE_UPDATE_GLIDE_SIZE "movable_glide_size"
///Called when a movable is hit by a plunger in layer mode, from /obj/item/plunger/attack_atom()
#define COMSIG_MOVABLE_CHANGE_DUCT_LAYER "movable_change_duct_layer"
-///Called when a movable is teleported from `do_teleport()`: (destination, channel)
+///Called when a movable is being teleported from `do_teleport()`: (destination, channel)
#define COMSIG_MOVABLE_TELEPORTED "movable_teleported"
+///Called after a movable is teleported from `do_teleport()`: ()
+#define COMSIG_MOVABLE_POST_TELEPORT "movable_post_teleport"
/// from /mob/living/can_z_move, sent to whatever the mob is buckled to. Only ridable movables should be ridden up or down btw.
#define COMSIG_BUCKLED_CAN_Z_MOVE "ridden_pre_can_z_move"
#define COMPONENT_RIDDEN_STOP_Z_MOVE 1
diff --git a/code/datums/achievements/misc_achievements.dm b/code/datums/achievements/misc_achievements.dm
index 6c1c0683328..1c3a9598065 100644
--- a/code/datums/achievements/misc_achievements.dm
+++ b/code/datums/achievements/misc_achievements.dm
@@ -152,6 +152,12 @@
database_id = MEDAL_VOID_ASCENSION
icon = "voidascend"
+/datum/award/achievement/misc/blade_ascension
+ name = "Silver and Steel"
+ desc = "You've become the master of all duellists - the paragon of blades."
+ database_id = MEDAL_BLADE_ASCENSION
+ icon = "bladeascend"
+
/datum/award/achievement/misc/toolbox_soul
name = "SOUL'd Out"
desc = "My eternal soul was destroyed to make a toolbox look funny and all I got was this achievement..."
diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm
index cb5aa00386a..4d61cd19702 100644
--- a/code/datums/helper_datums/teleport.dm
+++ b/code/datums/helper_datums/teleport.dm
@@ -25,8 +25,6 @@
if (isnull(precision))
precision = 0
- SEND_SIGNAL(teleatom, COMSIG_MOVABLE_TELEPORTED, destination, channel)
-
switch(channel)
if(TELEPORT_CHANNEL_BLUESPACE)
if(istype(teleatom, /obj/item/storage/backpack/holding))
@@ -65,13 +63,20 @@
if(!destturf || !curturf || destturf.is_transition_turf())
return FALSE
- var/area/A = get_area(curturf)
- var/area/B = get_area(destturf)
- if(!forced && (HAS_TRAIT(teleatom, TRAIT_NO_TELEPORT) || (A.area_flags & NOTELEPORT) || (B.area_flags & NOTELEPORT)))
- return FALSE
+ var/area/from_area = get_area(curturf)
+ var/area/to_area = get_area(destturf)
+ if(!forced)
+ if(HAS_TRAIT(teleatom, TRAIT_NO_TELEPORT))
+ return FALSE
- if(SEND_SIGNAL(destturf, COMSIG_ATOM_INTERCEPT_TELEPORT, channel, curturf, destturf))
- return FALSE
+ if((from_area.area_flags & NOTELEPORT) || (to_area.area_flags & NOTELEPORT))
+ return FALSE
+
+ if(SEND_SIGNAL(teleatom, COMSIG_MOVABLE_TELEPORTED, destination, channel) & COMPONENT_BLOCK_TELEPORT)
+ return FALSE
+
+ if(SEND_SIGNAL(destturf, COMSIG_ATOM_INTERCEPT_TELEPORT, channel, curturf, destturf) & COMPONENT_BLOCK_TELEPORT)
+ return FALSE
if(isobserver(teleatom))
teleatom.abstract_move(destturf)
@@ -87,6 +92,8 @@
var/mob/M = teleatom
M.cancel_camera()
+ SEND_SIGNAL(teleatom, COMSIG_MOVABLE_POST_TELEPORT)
+
return TRUE
/proc/tele_play_specials(atom/movable/teleatom, atom/location, datum/effect_system/effect, sound)
diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm
index 6dcf460e0af..c1868d3ad93 100644
--- a/code/datums/status_effects/buffs.dm
+++ b/code/datums/status_effects/buffs.dm
@@ -464,6 +464,135 @@
is the ultimate redemption, and wounds let you bask in eternal glory."
icon_state = "wounded_soldier"
+/// Summons multiple foating knives around the owner.
+/// Each knife will block an attack straight up.
+/datum/status_effect/protective_blades
+ id = "Silver Knives"
+ alert_type = null
+ status_type = STATUS_EFFECT_MULTIPLE
+ tick_interval = -1
+ /// The number of blades we summon up to.
+ var/max_num_blades = 4
+ /// The radius of the blade's orbit.
+ var/blade_orbit_radius = 20
+ /// The time between spawning blades.
+ var/time_between_initial_blades = 0.25 SECONDS
+ /// If TRUE, we self-delete our status effect after all the blades are deleted.
+ var/delete_on_blades_gone = TRUE
+ /// A list of blade effects orbiting / protecting our owner
+ var/list/obj/effect/floating_blade/blades = list()
+
+/datum/status_effect/protective_blades/on_creation(
+ mob/living/new_owner,
+ new_duration = -1,
+ max_num_blades = 4,
+ blade_orbit_radius = 20,
+ time_between_initial_blades = 0.25 SECONDS,
+)
+
+ src.duration = new_duration
+ src.max_num_blades = max_num_blades
+ src.blade_orbit_radius = blade_orbit_radius
+ src.time_between_initial_blades = time_between_initial_blades
+ return ..()
+
+/datum/status_effect/protective_blades/on_apply()
+ RegisterSignal(owner, COMSIG_HUMAN_CHECK_SHIELDS, .proc/on_shield_reaction)
+ for(var/blade_num in 1 to max_num_blades)
+ var/time_until_created = (blade_num - 1) * time_between_initial_blades
+ if(time_until_created <= 0)
+ create_blade()
+ else
+ addtimer(CALLBACK(src, .proc/create_blade), time_until_created)
+
+ return TRUE
+
+/datum/status_effect/protective_blades/on_remove()
+ UnregisterSignal(owner, COMSIG_HUMAN_CHECK_SHIELDS)
+ QDEL_LIST(blades)
+
+ return ..()
+
+/// Creates a floating blade, adds it to our blade list, and makes it orbit our owner.
+/datum/status_effect/protective_blades/proc/create_blade()
+ if(QDELETED(src) || QDELETED(owner))
+ return
+
+ var/obj/effect/floating_blade/blade = new(get_turf(owner))
+ blades += blade
+ blade.orbit(owner, blade_orbit_radius)
+ RegisterSignal(blade, COMSIG_PARENT_QDELETING, .proc/remove_blade)
+ playsound(get_turf(owner), 'sound/items/unsheath.ogg', 33, TRUE)
+
+/// Signal proc for [COMSIG_HUMAN_CHECK_SHIELDS].
+/// If we have a blade in our list, consume it and block the incoming attack (shield it)
+/datum/status_effect/protective_blades/proc/on_shield_reaction(
+ mob/living/carbon/human/source,
+ atom/movable/hitby,
+ damage = 0,
+ attack_text = "the attack",
+ attack_type = MELEE_ATTACK,
+ armour_penetration = 0,
+)
+ SIGNAL_HANDLER
+
+ if(!length(blades))
+ return
+
+ var/obj/effect/floating_blade/to_remove = blades[1]
+
+ playsound(get_turf(source), 'sound/weapons/parry.ogg', 100, TRUE)
+ source.visible_message(
+ span_warning("[to_remove] orbiting [source] snaps in front of [attack_text], blocking it before vanishing!"),
+ span_warning("[to_remove] orbiting you snaps in front of [attack_text], blocking it before vanishing!"),
+ span_hear("You hear a clink."),
+ )
+
+ qdel(to_remove)
+
+ return SHIELD_BLOCK
+
+/// Remove deleted blades from our blades list properly.
+/datum/status_effect/protective_blades/proc/remove_blade(obj/effect/floating_blade/to_remove)
+ SIGNAL_HANDLER
+
+ if(!(to_remove in blades))
+ CRASH("[type] called remove_blade() with a blade that was not in its blades list.")
+
+ to_remove.stop_orbit(owner.orbiters)
+ blades -= to_remove
+
+ if(!length(blades) && !QDELETED(src) && delete_on_blades_gone)
+ qdel(src)
+
+ return TRUE
+
+/// A subtype that doesn't self-delete / disappear when all blades are gone
+/// It instead regenerates over time back to the max after blades are consumed
+/datum/status_effect/protective_blades/recharging
+ delete_on_blades_gone = FALSE
+ /// The amount of time it takes for a blade to recharge
+ var/blade_recharge_time = 1 MINUTES
+
+/datum/status_effect/protective_blades/recharging/on_creation(
+ mob/living/new_owner,
+ new_duration = -1,
+ max_num_blades = 4,
+ blade_orbit_radius = 20,
+ time_between_initial_blades = 0.25 SECONDS,
+ blade_recharge_time = 1 MINUTES,
+)
+
+ src.blade_recharge_time = blade_recharge_time
+ return ..()
+
+/datum/status_effect/protective_blades/recharging/remove_blade(obj/effect/floating_blade/to_remove)
+ . = ..()
+ if(!.)
+ return
+
+ addtimer(CALLBACK(src, .proc/create_blade), blade_recharge_time)
+
/datum/status_effect/lightningorb
id = "Lightning Orb"
duration = 30 SECONDS
diff --git a/code/datums/status_effects/debuffs/debuffs.dm b/code/datums/status_effects/debuffs/debuffs.dm
index 9c7043450e2..436d0394a22 100644
--- a/code/datums/status_effects/debuffs/debuffs.dm
+++ b/code/datums/status_effects/debuffs/debuffs.dm
@@ -393,11 +393,13 @@
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 = ""
+ /// icon file for the underlay
+ var/effect_icon = 'icons/effects/eldritch.dmi'
+ /// icon state for the underlay
+ var/effect_icon_state = ""
/datum/status_effect/eldritch/on_creation(mob/living/new_owner, ...)
- marked_underlay = mutable_appearance('icons/effects/effects.dmi', effect_sprite,BELOW_MOB_LAYER)
+ marked_underlay = mutable_appearance(effect_icon, effect_icon_state, BELOW_MOB_LAYER)
return ..()
/datum/status_effect/eldritch/Destroy()
@@ -437,7 +439,7 @@
//Each mark has diffrent effects when it is destroyed that combine with the mansus grasp effect.
/datum/status_effect/eldritch/flesh
- effect_sprite = "emark1"
+ effect_icon_state = "emark1"
/datum/status_effect/eldritch/flesh/on_effect()
if(ishuman(owner))
@@ -449,7 +451,7 @@
return ..()
/datum/status_effect/eldritch/ash
- effect_sprite = "emark2"
+ effect_icon_state = "emark2"
/// Dictates how much stamina and burn damage the mark will cause on trigger.
var/repetitions = 1
@@ -471,7 +473,7 @@
return ..()
/datum/status_effect/eldritch/rust
- effect_sprite = "emark3"
+ effect_icon_state = "emark3"
/datum/status_effect/eldritch/rust/on_effect()
if(iscarbon(owner))
@@ -499,7 +501,7 @@
return ..()
/datum/status_effect/eldritch/void
- effect_sprite = "emark4"
+ effect_icon_state = "emark4"
/datum/status_effect/eldritch/void/on_effect()
var/turf/open/our_turf = get_turf(owner)
@@ -512,6 +514,54 @@
return ..()
+/datum/status_effect/eldritch/blade
+ effect_icon_state = "emark5"
+ /// If set, the owner of the status effect will not be able to leave this area.
+ var/area/locked_to
+
+/datum/status_effect/eldritch/blade/Destroy()
+ locked_to = null
+ return ..()
+
+/datum/status_effect/eldritch/blade/on_apply()
+ . = ..()
+ RegisterSignal(owner, COMSIG_MOVABLE_TELEPORTED, .proc/on_teleport)
+ RegisterSignal(owner, COMSIG_MOVABLE_MOVED, .proc/on_move)
+
+/datum/status_effect/eldritch/blade/on_remove()
+ UnregisterSignal(owner, list(COMSIG_MOVABLE_TELEPORTED, COMSIG_MOVABLE_MOVED))
+ return ..()
+
+/// Signal proc for [COMSIG_MOVABLE_TELEPORTED] that blocks any teleports from our locked area
+/datum/status_effect/eldritch/blade/proc/on_teleport(mob/living/source, atom/destination, channel)
+ SIGNAL_HANDLER
+
+ if(!locked_to)
+ return
+
+ if(get_area(destination) == locked_to)
+ return
+
+ to_chat(source, span_hypnophrase("An otherworldly force prevents your escape from [get_area_name(locked_to)]!"))
+
+ source.Stun(1 SECONDS)
+ return COMPONENT_BLOCK_TELEPORT
+
+/// Signal proc for [COMSIG_MOVABLE_MOVED] that blocks any movement out of our locked area
+/datum/status_effect/eldritch/blade/proc/on_move(mob/living/source, turf/old_loc, movement_dir, forced)
+ SIGNAL_HANDLER
+
+ if(!locked_to)
+ return
+
+ if(get_area(source) == locked_to)
+ return
+
+ to_chat(source, span_hypnophrase("An otherworldly force prevents your escape from [get_area_name(locked_to)]!"))
+
+ source.Stun(1 SECONDS)
+ source.throw_at(old_loc, 5, 1)
+
/// A status effect used for specifying confusion on a living mob.
/// Created automatically with /mob/living/set_confusion.
/datum/status_effect/confusion
@@ -1121,10 +1171,94 @@
status_type = STATUS_EFFECT_UNIQUE
duration = -1
alert_type = /atom/movable/screen/alert/status_effect/ghoul
+ /// The new max health value set for the ghoul, if supplied
+ var/new_max_health
+ /// Reference to the master of the ghoul's mind
+ var/datum/mind/master_mind
+ /// An optional callback invoked when a ghoul is made (on_apply)
+ var/datum/callback/on_made_callback
+ /// An optional callback invoked when a goul is unghouled (on_removed)
+ var/datum/callback/on_lost_callback
+
+/datum/status_effect/ghoul/Destroy()
+ master_mind = null
+ QDEL_NULL(on_made_callback)
+ QDEL_NULL(on_lost_callback)
+ return ..()
+
+/datum/status_effect/ghoul/on_creation(
+ mob/living/new_owner,
+ new_max_health,
+ datum/mind/master_mind,
+ datum/callback/on_made_callback,
+ datum/callback/on_lost_callback,
+)
+
+ src.new_max_health = new_max_health
+ src.master_mind = master_mind
+ src.on_made_callback = on_made_callback
+ src.on_lost_callback = on_lost_callback
+
+ . = ..()
+
+ if(master_mind)
+ linked_alert.desc += " You are an eldritch monster reanimated to serve its master, [master_mind]."
+ if(isnum(new_max_health))
+ if(new_max_health > initial(new_owner.maxHealth))
+ linked_alert.desc += " You are stronger in this form."
+ else
+ linked_alert.desc += " You are more fragile in this form."
+
+/datum/status_effect/ghoul/on_apply()
+ if(!ishuman(owner))
+ return FALSE
+
+ var/mob/living/carbon/human/human_target = owner
+
+ RegisterSignal(human_target, COMSIG_LIVING_DEATH, .proc/remove_ghoul_status)
+ human_target.revive(full_heal = TRUE, admin_revive = TRUE)
+
+ if(new_max_health)
+ human_target.setMaxHealth(new_max_health)
+ human_target.health = new_max_health
+
+ on_made_callback?.Invoke(human_target)
+ human_target.become_husk(MAGIC_TRAIT)
+ human_target.faction |= FACTION_HERETIC
+
+ if(human_target.mind)
+ var/datum/antagonist/heretic_monster/heretic_monster = human_target.mind.add_antag_datum(/datum/antagonist/heretic_monster)
+ heretic_monster.set_owner(master_mind)
+
+ return TRUE
+
+/datum/status_effect/ghoul/on_remove()
+ remove_ghoul_status()
+ return ..()
+
+/// Removes the ghoul effects from our owner and returns them to normal.
+/datum/status_effect/ghoul/proc/remove_ghoul_status(datum/source)
+ SIGNAL_HANDLER
+
+ if(!ishuman(owner))
+ return
+ var/mob/living/carbon/human/human_target = owner
+
+ if(new_max_health)
+ human_target.setMaxHealth(initial(human_target.maxHealth))
+
+ on_lost_callback?.Invoke(human_target)
+ human_target.cure_husk(MAGIC_TRAIT)
+ human_target.faction -= FACTION_HERETIC
+ human_target.mind?.remove_antag_datum(/datum/antagonist/heretic_monster)
+
+ UnregisterSignal(human_target, COMSIG_LIVING_DEATH)
+ if(!QDELETED(src))
+ qdel(src)
/atom/movable/screen/alert/status_effect/ghoul
name = "Flesh Servant"
- desc = "You are a Ghoul! A eldritch monster reanimated to serve its master."
+ desc = "You are a Ghoul!"
icon_state = ALERT_MIND_CONTROL
diff --git a/code/modules/antagonists/heretic/heretic_antag.dm b/code/modules/antagonists/heretic/heretic_antag.dm
index 11c116ae02c..4d7c14eb328 100644
--- a/code/modules/antagonists/heretic/heretic_antag.dm
+++ b/code/modules/antagonists/heretic/heretic_antag.dm
@@ -64,6 +64,7 @@
PATH_FLESH = "red",
PATH_ASH = "white",
PATH_VOID = "blue",
+ PATH_BLADE = "label", // my favorite color is label
)
data["charges"] = knowledge_points
diff --git a/code/modules/antagonists/heretic/heretic_knowledge.dm b/code/modules/antagonists/heretic/heretic_knowledge.dm
index 402aafe35fb..24893f748b2 100644
--- a/code/modules/antagonists/heretic/heretic_knowledge.dm
+++ b/code/modules/antagonists/heretic/heretic_knowledge.dm
@@ -15,6 +15,10 @@
var/desc = "Basic knowledge of forbidden arts."
/// What's shown to the heretic when the knowledge is aquired
var/gain_text
+ /// The abstract parent type of the knowledge, used in determine mutual exclusivity in some cases
+ var/datum/heretic_knowledge/abstract_parent_type = /datum/heretic_knowledge
+ /// If TRUE, populates the banned_knowledge list of every other subtype of this knowledge's abstract_parent_type
+ var/mutually_exclusive = FALSE
/// The knowledge this unlocks next after learning.
var/list/next_knowledge = list()
/// What knowledge is incompatible with this. Knowledge in this list cannot be researched with this current knowledge.
@@ -32,6 +36,15 @@
/// What path is this on. If set to "null", assumed to be unreachable (or abstract).
var/route
+/datum/heretic_knowledge/New()
+ if(!mutually_exclusive)
+ return
+
+ for(var/knowledge_type in subtypesof(abstract_parent_type))
+ if(knowledge_type == type)
+ continue
+ banned_knowledge += knowledge_type
+
/**
* Called when the knowledge is first researched.
* This is only ever called once per heretic.
@@ -141,7 +154,7 @@
var/how_much_to_use = 0
for(var/requirement in required_atoms)
if(istype(sacrificed, requirement))
- how_much_to_use = required_atoms[requirement]
+ how_much_to_use = min(required_atoms[requirement], sac_stack.amount)
break
sac_stack.use(how_much_to_use)
@@ -154,6 +167,7 @@
* A knowledge subtype that grants the heretic a certain spell.
*/
/datum/heretic_knowledge/spell
+ abstract_parent_type = /datum/heretic_knowledge/spell
/// The proc holder spell we add to the heretic. Type-path, becomes an instance via on_research().
var/obj/effect/proc_holder/spell/spell_to_add
@@ -178,6 +192,7 @@
* created at once.
*/
/datum/heretic_knowledge/limited_amount
+ abstract_parent_type = /datum/heretic_knowledge/limited_amount
/// The limit to how many items we can create at once.
var/limit = 1
/// A list of weakrefs to all items we've created.
@@ -205,10 +220,149 @@
LAZYADD(created_items, WEAKREF(created_thing))
return TRUE
+/*
+ * A knowledge subtype for limited_amount knowledge
+ * used for base knowledge (the ones that make blades)
+ *
+ * A heretic can only learn one /starting type knowledge,
+ * and their ascension depends on whichever they chose.
+ */
+/datum/heretic_knowledge/limited_amount/starting
+ abstract_parent_type = /datum/heretic_knowledge/limited_amount/starting
+ mutually_exclusive = TRUE
+ limit = 2
+ cost = 1
+ priority = MAX_KNOWLEDGE_PRIORITY - 5
+
+/datum/heretic_knowledge/limited_amount/starting/New()
+ . = ..()
+ // Starting path also determines the final knowledge we're limited too
+ for(var/datum/heretic_knowledge/final_knowledge_type as anything in subtypesof(/datum/heretic_knowledge/final))
+ if(initial(final_knowledge_type.route) == route)
+ continue
+ banned_knowledge += final_knowledge_type
+
+/*
+ * A knowledge subtype for heretic knowledge
+ * that applies a mark on use.
+ *
+ * A heretic can only learn one /mark type knowledge.
+ */
+/datum/heretic_knowledge/mark
+ abstract_parent_type = /datum/heretic_knowledge/mark
+ mutually_exclusive = TRUE
+ cost = 2
+ /// The status effect typepath we apply on people on mansus grasp.
+ var/datum/status_effect/eldritch/mark_type
+
+/datum/heretic_knowledge/mark/on_gain(mob/user)
+ RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, .proc/on_mansus_grasp)
+ RegisterSignal(user, COMSIG_HERETIC_BLADE_ATTACK, .proc/on_eldritch_blade)
+
+/datum/heretic_knowledge/mark/on_lose(mob/user)
+ UnregisterSignal(user, list(COMSIG_HERETIC_MANSUS_GRASP_ATTACK, COMSIG_HERETIC_BLADE_ATTACK))
+
+/**
+ * Signal proc for [COMSIG_HERETIC_MANSUS_GRASP_ATTACK].
+ *
+ * Whenever we cast mansus grasp on someone, apply our mark.
+ */
+/datum/heretic_knowledge/mark/proc/on_mansus_grasp(mob/living/source, mob/living/target)
+ SIGNAL_HANDLER
+
+ create_mark(source, target)
+
+/**
+ * Signal proc for [COMSIG_HERETIC_BLADE_ATTACK].
+ *
+ * Whenever we attack someone with our blade, attempt to trigger any marks on them.
+ */
+/datum/heretic_knowledge/mark/proc/on_eldritch_blade(mob/living/source, mob/living/target, obj/item/melee/sickly_blade/blade)
+ SIGNAL_HANDLER
+
+ trigger_mark(source, target)
+
+/**
+ * Creates the mark status effect on our target.
+ * This proc handles the instatiate and the application of the station effect,
+ * and returns the /datum/status_effect instance that was made.
+ *
+ * Can be overriden to set or pass in additional vars of the status effect.
+ */
+/datum/heretic_knowledge/mark/proc/create_mark(mob/living/source, mob/living/target)
+ return target.apply_status_effect(mark_type)
+
+/**
+ * Handles triggering the mark on the target.
+ *
+ * If there is no mark, returns FALSE. Returns TRUE if a mark was triggered.
+ */
+/datum/heretic_knowledge/mark/proc/trigger_mark(mob/living/source, mob/living/target)
+ var/datum/status_effect/eldritch/mark = target.has_status_effect(/datum/status_effect/eldritch)
+ if(!istype(mark))
+ return FALSE
+
+ mark.on_effect()
+ return TRUE
+
+/*
+ * A knowledge subtype for heretic knowledge that
+ * upgrades their sickly blade, either on melee or range.
+ *
+ * A heretic can only learn one /blade_upgrade type knowledge.
+ */
+/datum/heretic_knowledge/blade_upgrade
+ abstract_parent_type = /datum/heretic_knowledge/blade_upgrade
+ mutually_exclusive = TRUE
+ cost = 2
+
+/datum/heretic_knowledge/blade_upgrade/on_gain(mob/user)
+ RegisterSignal(user, COMSIG_HERETIC_BLADE_ATTACK, .proc/on_eldritch_blade)
+ RegisterSignal(user, COMSIG_HERETIC_RANGED_BLADE_ATTACK, .proc/on_ranged_eldritch_blade)
+
+/datum/heretic_knowledge/blade_upgrade/on_lose(mob/user)
+ UnregisterSignal(user, list(COMSIG_HERETIC_BLADE_ATTACK, COMSIG_HERETIC_RANGED_BLADE_ATTACK))
+
+
+/**
+ * Signal proc for [COMSIG_HERETIC_BLADE_ATTACK].
+ *
+ * Apply any melee effects from hitting someone with our blade.
+ */
+/datum/heretic_knowledge/blade_upgrade/proc/on_eldritch_blade(mob/living/source, mob/living/target, obj/item/melee/sickly_blade/blade)
+ SIGNAL_HANDLER
+
+ do_melee_effects(source, target, blade)
+
+/**
+ * Signal proc for [COMSIG_HERETIC_RANGED_BLADE_ATTACK].
+ *
+ * Apply any ranged effects from hitting someone with our blade.
+ */
+/datum/heretic_knowledge/blade_upgrade/proc/on_ranged_eldritch_blade(mob/living/source, mob/living/target, obj/item/melee/sickly_blade/blade)
+ SIGNAL_HANDLER
+
+ do_ranged_effects(source, target, blade)
+
+/**
+ * Overridable proc that invokes special effects
+ * whenever the heretic attacks someone in melee with their heretic blade.
+ */
+/datum/heretic_knowledge/blade_upgrade/proc/do_melee_effects(mob/living/source, mob/living/target, obj/item/melee/sickly_blade/blade)
+ return
+
+/**
+ * Overridable proc that invokes special effects
+ * whenever the heretic clicks on someone at range with their heretic blade.
+ */
+/datum/heretic_knowledge/blade_upgrade/proc/do_ranged_effects(mob/living/source, mob/living/target, obj/item/melee/sickly_blade/blade)
+ return
+
/*
* A knowledge subtype lets the heretic curse someone with a ritual.
*/
/datum/heretic_knowledge/curse
+ abstract_parent_type = /datum/heretic_knowledge/curse
/// The duration of the curse
var/duration = 5 MINUTES
/// Cache list of fingerprints (actual fingerprint strings) we have from our current ritual
@@ -272,6 +426,7 @@
* A knowledge subtype lets the heretic summon a monster with the ritual.
*/
/datum/heretic_knowledge/summon
+ abstract_parent_type = /datum/heretic_knowledge/summon
/// Typepath of a mob to summon when we finish the recipe.
var/mob/living/mob_to_summon
@@ -322,6 +477,8 @@
name = "Ritual of Knowledge"
desc = "A randomly generated transmutation ritual that rewards knowledge points and can only be completed once."
gain_text = "Everything can be a key to unlocking the secrets behind the Gates. I must be wary and wise."
+ abstract_parent_type = /datum/heretic_knowledge/knowledge_ritual
+ mutually_exclusive = TRUE
cost = 1
priority = MAX_KNOWLEDGE_PRIORITY - 10 // A pretty important midgame ritual.
/// Whether we've done the ritual. Only doable once.
@@ -411,6 +568,8 @@
* The special final tier of knowledges that unlocks ASCENSION.
*/
/datum/heretic_knowledge/final
+ abstract_parent_type = /datum/heretic_knowledge/final
+ mutually_exclusive = TRUE // I guess, but it doesn't really matter by this point
cost = 2
priority = MAX_KNOWLEDGE_PRIORITY + 1 // Yes, the final ritual should be ABOVE the max priority.
required_atoms = list(/mob/living/carbon/human = 3)
diff --git a/code/modules/antagonists/heretic/heretic_monsters.dm b/code/modules/antagonists/heretic/heretic_monsters.dm
index 7328179f052..6f262d2aa32 100644
--- a/code/modules/antagonists/heretic/heretic_monsters.dm
+++ b/code/modules/antagonists/heretic/heretic_monsters.dm
@@ -39,5 +39,5 @@
objectives += master_obj
owner.announce_objectives()
- to_chat(owner, span_boldnotice("You are a horrible creation brought to this plane through the Gates of the Mansus."))
+ to_chat(owner, span_boldnotice("You are a [ishuman(owner.current) ? "shambling corpse returned":"horrible creation brought"] to this plane through the Gates of the Mansus."))
to_chat(owner, span_notice("Your master is [master]. Assist them to all ends."))
diff --git a/code/modules/antagonists/heretic/items/heretic_blades.dm b/code/modules/antagonists/heretic/items/heretic_blades.dm
index fe15376efaa..915b2effd02 100644
--- a/code/modules/antagonists/heretic/items/heretic_blades.dm
+++ b/code/modules/antagonists/heretic/items/heretic_blades.dm
@@ -46,9 +46,9 @@
return
if(proximity_flag)
- SEND_SIGNAL(user, COMSIG_HERETIC_BLADE_ATTACK, target)
+ SEND_SIGNAL(user, COMSIG_HERETIC_BLADE_ATTACK, target, src)
else
- SEND_SIGNAL(user, COMSIG_HERETIC_RANGED_BLADE_ATTACK, target)
+ SEND_SIGNAL(user, COMSIG_HERETIC_RANGED_BLADE_ATTACK, target, src)
/obj/item/melee/sickly_blade/examine(mob/user)
. = ..()
@@ -57,6 +57,7 @@
. += span_notice("You can shatter the blade to teleport to a random, (mostly) safe location by activating it in-hand.")
+// Path of Rust's blade
/obj/item/melee/sickly_blade/rust
name = "\improper rusted blade"
desc = "This crescent blade is decrepit, wasting to rust. \
@@ -65,6 +66,7 @@
inhand_icon_state = "rust_blade"
after_use_message = "The Rusted Hills hear your call..."
+// Path of Ash's blade
/obj/item/melee/sickly_blade/ash
name = "\improper ashen blade"
desc = "Molten and unwrought, a hunk of metal warped to cinders and slag. \
@@ -73,6 +75,7 @@
inhand_icon_state = "ash_blade"
after_use_message = "The Nightwater hears your call..."
+// Path of Flesh's blade
/obj/item/melee/sickly_blade/flesh
name = "\improper bloody blade"
desc = "A crescent blade born from a fleshwarped creature. \
@@ -81,6 +84,7 @@
inhand_icon_state = "flesh_blade"
after_use_message = "The Marshal hears your call..."
+// Path of Void's blade
/obj/item/melee/sickly_blade/void
name = "\improper void blade"
desc = "Devoid of any substance, this blade reflects nothingness. \
@@ -88,3 +92,12 @@
icon_state = "void_blade"
inhand_icon_state = "void_blade"
after_use_message = "The Aristocrat hears your call..."
+
+// Path of the Blade's... blade
+// Opting for /dark (darkened blade) instead of /blade to avoid "sickly_blade/blade".
+/obj/item/melee/sickly_blade/dark
+ name = "\improper darkened blade"
+ desc = "A blade made of brilliant silver that shines gloriously. Unknown rage is bottled within."
+ icon_state = "dark_blade"
+ inhand_icon_state = "dark_blade"
+ after_use_message = "The Colonel hears your call..."
diff --git a/code/modules/antagonists/heretic/items/hunter_rifle.dm b/code/modules/antagonists/heretic/items/hunter_rifle.dm
new file mode 100644
index 00000000000..c43dbce8f93
--- /dev/null
+++ b/code/modules/antagonists/heretic/items/hunter_rifle.dm
@@ -0,0 +1,143 @@
+// The Lionhunter, a gun for heretics
+// The ammo it uses takes time to "charge" before firing,
+// releasing a homing, very damaging projectile
+/obj/item/gun/ballistic/rifle/lionhunter
+ name = "\improper Lionhunter's Rifle"
+ desc = "An antique looking rifle that looks immaculate despite being clearly very old."
+ slot_flags = ITEM_SLOT_BACK
+ icon_state = "moistprime"
+ inhand_icon_state = "moistprime"
+ worn_icon_state = "moistprime"
+ mag_type = /obj/item/ammo_box/magazine/internal/boltaction/lionhunter
+ fire_sound = 'sound/weapons/gun/sniper/shot.ogg'
+ zoomable = TRUE
+ zoom_amt = 5
+ zoom_out_amt = 3
+
+/obj/item/ammo_box/magazine/internal/boltaction/lionhunter
+ name = "lionhunter rifle internal magazine"
+ ammo_type = /obj/item/ammo_casing/a762/lionhunter
+ caliber = CALIBER_A762
+ max_ammo = 3
+ multiload = TRUE
+
+/obj/item/ammo_casing/a762/lionhunter
+ projectile_type = /obj/projectile/bullet/a762/lionhunter
+ /// Whether we're currently aiming this casing at something
+ var/currently_aiming = FALSE
+ /// How many seconds it takes to aim per tile of distance between the target
+ var/seconds_per_distance = 0.5 SECONDS
+ /// The minimum distance required to gain a damage bonus from aiming
+ var/min_distance = 4
+
+/obj/item/ammo_casing/a762/lionhunter/fire_casing(atom/target, mob/living/user, params, distro, quiet, zone_override, spread, atom/fired_from)
+ if(!loaded_projectile)
+ return
+ if(!check_fire(target, user))
+ return
+
+ return ..()
+
+/// Checks if we can successfully fire our projectile.
+/obj/item/ammo_casing/a762/lionhunter/proc/check_fire(atom/target, mob/living/user)
+ // In case someone puts this in turrets or something wacky, just fire like normal
+ if(!iscarbon(user) || !istype(loc, /obj/item/gun/ballistic/rifle/lionhunter))
+ return TRUE
+
+ if(currently_aiming)
+ user.balloon_alert(user, "already aiming!")
+ return FALSE
+
+ var/distance = get_dist(user, target)
+ var/fire_time = min(distance * seconds_per_distance, 10 SECONDS)
+
+ if(distance <= min_distance || !isliving(target))
+ return TRUE
+
+ user.balloon_alert(user, "taking aim...")
+ user.playsound_local(get_turf(user), 'sound/weapons/gun/general/chunkyrack.ogg', 100, TRUE)
+
+ var/image/reticle = image(
+ icon = 'icons/mob/actions/actions_items.dmi',
+ icon_state = "sniper_zoom",
+ layer = ABOVE_MOB_LAYER,
+ loc = target,
+ )
+ reticle.alpha = 0
+
+ var/list/mob/viewers = viewers(target)
+ // The shooter might be out of view, but they should be included
+ viewers |= user
+
+ for(var/mob/viewer as anything in viewers)
+ viewer.client?.images |= reticle
+
+ // Animate the fade in
+ animate(reticle, fire_time * 0.5, alpha = 255, transform = turn(reticle.transform, 180))
+ animate(reticle, fire_time * 0.5, transform = turn(reticle.transform, 180))
+
+ currently_aiming = TRUE
+ . = do_after(user, fire_time, target, IGNORE_TARGET_LOC_CHANGE, extra_checks = CALLBACK(src, .proc/check_fire_callback, target, user))
+ currently_aiming = FALSE
+
+ animate(reticle, 0.5 SECONDS, alpha = 0)
+ for(var/mob/viewer as anything in viewers)
+ viewer.client?.images -= reticle
+
+ if(!.)
+ user.balloon_alert(user, "interrupted!")
+
+ return .
+
+/// Callback for the do_after within the check_fire proc to see if something will prevent us from firing while aiming
+/obj/item/ammo_casing/a762/lionhunter/proc/check_fire_callback(mob/living/target, mob/living/user)
+ if(!isturf(target.loc))
+ return FALSE
+
+ return TRUE
+
+/obj/item/ammo_casing/a762/lionhunter/ready_proj(atom/target, mob/living/user, quiet, zone_override, atom/fired_from)
+ if(!loaded_projectile)
+ return
+
+ var/distance = get_dist(user, target)
+ // If we're close range, or the target's not a living, OR for some reason a non-carbon is firing the gun
+ // The projectile is dry-fired, and gains no buffs
+ // BUT, if we're at a decent range and the target's a living mob,
+ // the projectile's been channel fired. It has full effects and homes in.
+ if(distance > min_distance && isliving(target) && iscarbon(user))
+ loaded_projectile.damage *= 1.33
+ loaded_projectile.stamina *= 2
+ loaded_projectile.knockdown = 0.5 SECONDS
+ loaded_projectile.stutter = 6 SECONDS
+ loaded_projectile.projectile_phasing = PASSTABLE | PASSGLASS | PASSGRILLE | PASSCLOSEDTURF | PASSMACHINE | PASSSTRUCTURE | PASSDOORS
+
+ loaded_projectile.homing = TRUE
+ loaded_projectile.homing_turn_speed = 80
+ loaded_projectile.set_homing_target(target)
+
+ return ..()
+
+/obj/projectile/bullet/a762/lionhunter
+ name = "hunter's 7.62 bullet"
+ // These stats are only applied if the weapon is fired fully aimed
+ // If fired without aiming or at someone too close, it will do much less
+ damage = 30
+ stamina = 30
+ projectile_phasing = PASSTABLE | PASSGLASS | PASSGRILLE | PASSCLOSEDTURF | PASSMACHINE | PASSSTRUCTURE | PASSDOORS
+
+// Extra ammunition can be made with a heretic ritual.
+/obj/item/ammo_box/a762/lionhunter
+ name = "stripper clip (7.62mm hunter)"
+ desc = "A stripper clip of mysterious, atypical ammo. It doesn't fit into normal ballistic rifles."
+ icon_state = "762"
+ ammo_type = /obj/item/ammo_casing/a762/lionhunter
+ max_ammo = 3
+ multiple_sprites = AMMO_BOX_PER_BULLET
+
+/obj/effect/temp_visual/bullet_target
+ icon = 'icons/mob/actions/actions_items.dmi'
+ icon_state = "sniper_zoom"
+ layer = BELOW_MOB_LAYER
+ plane = GAME_PLANE
+ light_range = 2
diff --git a/code/modules/antagonists/heretic/knife_effect.dm b/code/modules/antagonists/heretic/knife_effect.dm
new file mode 100644
index 00000000000..22e44958143
--- /dev/null
+++ b/code/modules/antagonists/heretic/knife_effect.dm
@@ -0,0 +1,14 @@
+// "Floating ghost blade" effect for blade heretics
+/obj/effect/floating_blade
+ name = "knife"
+ icon = 'icons/obj/kitchen.dmi'
+ icon_state = "knife"
+ plane = GAME_PLANE_FOV_HIDDEN
+ /// The color the knife glows around it.
+ var/glow_color = "#ececff"
+
+/obj/effect/floating_blade/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/movetype_handler)
+ ADD_TRAIT(src, TRAIT_MOVE_FLYING, INNATE_TRAIT)
+ add_filter("knife", 2, list("type" = "outline", "color" = glow_color, "size" = 1))
diff --git a/code/modules/antagonists/heretic/knowledge/ash_lore.dm b/code/modules/antagonists/heretic/knowledge/ash_lore.dm
index 695897db13f..ea56f3228c3 100644
--- a/code/modules/antagonists/heretic/knowledge/ash_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/ash_lore.dm
@@ -11,6 +11,7 @@
* Ashen Eyes
*
* Mark of Ash
+ * Ritual of Knowledge
* Mask of Madness
* > Sidepaths:
* Curse of Corrosion
@@ -20,36 +21,25 @@
* Nightwater's Rebirth
* > Sidepaths:
* Ashen Ritual
- * Blood Cleave
+ * Rusted Ritual
*
* Ashlord's Rite
*/
-/datum/heretic_knowledge/limited_amount/base_ash
+/datum/heretic_knowledge/limited_amount/starting/base_ash
name = "Nightwatcher's Secret"
desc = "Opens up the Path of Ash to you. \
Allows you to transmute a match and a knife into an Ashen Blade. \
You can only create two at a time."
gain_text = "The City Guard know their watch. If you ask them at night, they may tell you about the ashy lantern."
next_knowledge = list(/datum/heretic_knowledge/ashen_grasp)
- banned_knowledge = list(
- /datum/heretic_knowledge/limited_amount/base_rust,
- /datum/heretic_knowledge/limited_amount/base_flesh,
- /datum/heretic_knowledge/limited_amount/base_void,
- /datum/heretic_knowledge/final/rust_final,
- /datum/heretic_knowledge/final/flesh_final,
- /datum/heretic_knowledge/final/void_final,
- )
required_atoms = list(
/obj/item/knife = 1,
/obj/item/match = 1,
)
result_atoms = list(/obj/item/melee/sickly_blade/ash)
- limit = 2
- cost = 1
- priority = MAX_KNOWLEDGE_PRIORITY - 5
route = PATH_ASH
-/datum/heretic_knowledge/limited_amount/base_ash/on_research(mob/user)
+/datum/heretic_knowledge/limited_amount/starting/base_ash/on_research(mob/user)
. = ..()
var/datum/antagonist/heretic/our_heretic = IS_HERETIC(user)
our_heretic.heretic_path = route
@@ -87,7 +77,7 @@
desc = "Grants you Ashen Passage, a silent but short range jaunt."
gain_text = "He knew how to walk between the planes."
next_knowledge = list(
- /datum/heretic_knowledge/ash_mark,
+ /datum/heretic_knowledge/mark/ash_mark,
/datum/heretic_knowledge/codex_cicatrix,
/datum/heretic_knowledge/essence,
/datum/heretic_knowledge/medallion,
@@ -96,7 +86,7 @@
cost = 1
route = PATH_ASH
-/datum/heretic_knowledge/ash_mark
+/datum/heretic_knowledge/mark/ash_mark
name = "Mark of Ash"
desc = "Your Mansus Grasp now applies the Mark of Ash. The mark is triggered from an attack with your Ashen Blade. \
When triggered, the victim takes additional stamina and burn damage, and the mark is transferred to any nearby heathens. \
@@ -105,46 +95,20 @@
But in spite of his duty, he regularly tranced through the Manse with his blazing lantern held high. \
He shone brightly in the darkness, until the blaze begin to die."
next_knowledge = list(/datum/heretic_knowledge/knowledge_ritual/ash)
- banned_knowledge = list(
- /datum/heretic_knowledge/rust_mark,
- /datum/heretic_knowledge/flesh_mark,
- /datum/heretic_knowledge/void_mark,
- )
- cost = 2
route = PATH_ASH
+ mark_type = /datum/status_effect/eldritch/ash
-/datum/heretic_knowledge/ash_mark/on_gain(mob/user)
- RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, .proc/on_mansus_grasp)
- RegisterSignal(user, COMSIG_HERETIC_BLADE_ATTACK, .proc/on_eldritch_blade)
-
-/datum/heretic_knowledge/ash_mark/on_lose(mob/user)
- UnregisterSignal(user, list(COMSIG_HERETIC_MANSUS_GRASP_ATTACK, COMSIG_HERETIC_BLADE_ATTACK))
-
-/datum/heretic_knowledge/ash_mark/proc/on_mansus_grasp(mob/living/source, mob/living/target)
- SIGNAL_HANDLER
-
- target.apply_status_effect(/datum/status_effect/eldritch/ash)
-
-/datum/heretic_knowledge/ash_mark/proc/on_eldritch_blade(mob/living/user, mob/living/target)
- SIGNAL_HANDLER
-
- var/datum/status_effect/eldritch/mark = target.has_status_effect(/datum/status_effect/eldritch)
- if(!istype(mark))
+/datum/heretic_knowledge/mark/ash_mark/trigger_mark(mob/living/source, mob/living/target)
+ . = ..()
+ if(!.)
return
- mark.on_effect()
-
// Also refunds 75% of charge!
- for(var/obj/effect/proc_holder/spell/targeted/touch/mansus_grasp/grasp in user.mind.spell_list)
+ for(var/obj/effect/proc_holder/spell/targeted/touch/mansus_grasp/grasp in source.mind.spell_list)
grasp.charge_counter = min(round(grasp.charge_counter + grasp.charge_max * 0.75), grasp.charge_max)
/datum/heretic_knowledge/knowledge_ritual/ash
next_knowledge = list(/datum/heretic_knowledge/mad_mask)
- banned_knowledge = list(
- /datum/heretic_knowledge/knowledge_ritual/flesh,
- /datum/heretic_knowledge/knowledge_ritual/void,
- /datum/heretic_knowledge/knowledge_ritual/rust,
- )
route = PATH_ASH
/datum/heretic_knowledge/mad_mask
@@ -154,7 +118,7 @@
It can also be forced onto a heathen, to make them unable to take it off..."
gain_text = "The Nightwater was lost. That's what the Watch believed. Yet he walked the world, unnoticed by the masses."
next_knowledge = list(
- /datum/heretic_knowledge/ash_blade_upgrade,
+ /datum/heretic_knowledge/blade_upgrade/ash,
/datum/heretic_knowledge/reroll_targets,
/datum/heretic_knowledge/curse/corrosion,
/datum/heretic_knowledge/curse/paralysis,
@@ -169,32 +133,16 @@
cost = 1
route = PATH_ASH
-/datum/heretic_knowledge/ash_blade_upgrade
+/datum/heretic_knowledge/blade_upgrade/ash
name = "Fiery Blade"
desc = "Your blade now lights enemies ablaze on attack."
gain_text = "He returned, blade in hand, he swung and swung as the ash fell from the skies. \
His city, the people he swore to watch... and watch he did, as they all burnt to cinders."
next_knowledge = list(/datum/heretic_knowledge/spell/flame_birth)
- banned_knowledge = list(
- /datum/heretic_knowledge/rust_blade_upgrade,
- /datum/heretic_knowledge/flesh_blade_upgrade,
- /datum/heretic_knowledge/void_blade_upgrade,
- )
- cost = 2
route = PATH_ASH
-/datum/heretic_knowledge/ash_blade_upgrade/on_gain(mob/user)
- . = ..()
- RegisterSignal(user, COMSIG_HERETIC_BLADE_ATTACK, .proc/on_eldritch_blade)
-
-/datum/heretic_knowledge/ash_blade_upgrade/on_lose(mob/user)
- . = ..()
- UnregisterSignal(user, COMSIG_HERETIC_BLADE_ATTACK)
-
-/datum/heretic_knowledge/ash_blade_upgrade/proc/on_eldritch_blade(mob/living/user, mob/living/target)
- SIGNAL_HANDLER
-
- if(user == target)
+/datum/heretic_knowledge/blade_upgrade/ash/do_melee_effects(mob/living/source, mob/living/target, obj/item/melee/sickly_blade/blade)
+ if(source == target)
return
target.adjust_fire_stacks(1)
@@ -210,7 +158,7 @@
next_knowledge = list(
/datum/heretic_knowledge/final/ash_final,
/datum/heretic_knowledge/summon/ashy,
- /datum/heretic_knowledge/spell/cleave,
+ /datum/heretic_knowledge/summon/rusty,
)
spell_to_add = /obj/effect/proc_holder/spell/targeted/fiery_rebirth
cost = 1
diff --git a/code/modules/antagonists/heretic/knowledge/blade_lore.dm b/code/modules/antagonists/heretic/knowledge/blade_lore.dm
new file mode 100644
index 00000000000..eaf77c5faf7
--- /dev/null
+++ b/code/modules/antagonists/heretic/knowledge/blade_lore.dm
@@ -0,0 +1,400 @@
+/**
+ * # The path of Blades. Stab stab.
+ *
+ * Goes as follows:
+ *
+ * The Cutting Edge
+ * Grasp of the Blade
+ * Dance of the Brand
+ * > Sidepaths:
+ * Shattered Risen
+ * Armorer's Ritual
+ *
+ * Mark of the Blade
+ * Ritual of Knowledge
+ * Stance of the Scarred Duelist
+ * > Sidepaths:
+ * Carving Knife
+ * Mawed Crucible
+ *
+ * Swift Blades
+ * Furious Steel
+ * > Sidepaths:
+ * Maid in the Mirror
+ * Lionhunter Rifle
+ *
+ * Maelstrom of Silver
+ */
+/datum/heretic_knowledge/limited_amount/starting/base_blade
+ name = "The Cutting Edge"
+ desc = "Opens up the path of blades to you. \
+ Allows you to transmute a knife with a bar of silver to create a Darkened Blade. \
+ You can create up to five at a time."
+ gain_text = "Our great ancestors forged swords and practiced sparring on the even of great battles."
+ next_knowledge = list(/datum/heretic_knowledge/blade_grasp)
+ required_atoms = list(
+ /obj/item/knife = 1,
+ /obj/item/stack/sheet/mineral/silver = 2,
+ )
+ result_atoms = list(/obj/item/melee/sickly_blade/dark)
+ limit = 5 // It's the blade path, it's a given
+ route = PATH_BLADE
+
+/datum/heretic_knowledge/blade_grasp
+ name = "Grasp of the Blade"
+ desc = "Your Mansus Grasp will cause a short stun when used on someone lying down or facing away from you."
+ gain_text = "The story of the footsoldier has been told since antiquity. It is one of blood and valor, \
+ and is championed by sword, steel and silver."
+ next_knowledge = list(/datum/heretic_knowledge/blade_dance)
+ cost = 1
+ route = PATH_BLADE
+
+/datum/heretic_knowledge/blade_grasp/on_gain(mob/user)
+ RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, .proc/on_mansus_grasp)
+
+/datum/heretic_knowledge/blade_grasp/on_lose(mob/user)
+ UnregisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK)
+
+/datum/heretic_knowledge/blade_grasp/proc/on_mansus_grasp(mob/living/source, mob/living/target)
+ SIGNAL_HANDLER
+
+ // Let's see if source is behind target
+ // "Behind" is defined as 3 tiles directly to the back of the target
+ // x . .
+ // x > .
+ // x . .
+
+ var/are_we_behind = FALSE
+ // No tactical spinning allowed
+ if(target.flags_1 & IS_SPINNING_1)
+ are_we_behind = TRUE
+
+ // We'll take "same tile" as "behind" for ease
+ if(target.loc == source.loc)
+ are_we_behind = TRUE
+
+ // We'll also assume lying down is behind, as mob directions when lying are unclear
+ if(target.body_position == LYING_DOWN)
+ are_we_behind = TRUE
+
+ // Exceptions aside, let's actually check if they're, yknow, behind
+ var/dir_target_to_source = get_dir(target, source)
+ if(target.dir & REVERSE_DIR(dir_target_to_source))
+ are_we_behind = TRUE
+
+ if(!are_we_behind)
+ return
+
+ // We're officially behind them, apply effects
+ target.AdjustParalyzed(1.5 SECONDS)
+ target.apply_damage(10, BRUTE, wound_bonus = CANT_WOUND)
+ target.balloon_alert(source, "backstab!")
+ playsound(get_turf(target), 'sound/weapons/guillotine.ogg', 100, TRUE)
+
+/// The cooldown duration between trigers of blade dance
+#define BLADE_DANCE_COOLDOWN 20 SECONDS
+
+/datum/heretic_knowledge/blade_dance
+ name = "Dance of the Brand"
+ desc = "Being attacked while wielding a Darkened Blade in either hand will deliver a riposte \
+ towards your attacker. This effect can only trigger once every 20 seconds."
+ gain_text = "Having the prowess to wield such a thing requires great dedication and terror."
+ next_knowledge = list(
+ /datum/heretic_knowledge/limited_amount/risen_corpse,
+ /datum/heretic_knowledge/mark/blade_mark,
+ /datum/heretic_knowledge/codex_cicatrix,
+ /datum/heretic_knowledge/armor,
+ )
+ cost = 1
+ route = PATH_BLADE
+ /// Whether the counter-attack is ready or not.
+ /// Used instead of cooldowns, so we can give feedback when it's ready again
+ var/riposte_ready = TRUE
+
+/datum/heretic_knowledge/blade_dance/on_gain(mob/user)
+ RegisterSignal(user, COMSIG_HUMAN_CHECK_SHIELDS, .proc/on_shield_reaction)
+
+/datum/heretic_knowledge/blade_dance/on_lose(mob/user)
+ UnregisterSignal(user, COMSIG_HUMAN_CHECK_SHIELDS)
+
+/datum/heretic_knowledge/blade_dance/proc/on_shield_reaction(
+ mob/living/carbon/human/source,
+ atom/movable/hitby,
+ damage = 0,
+ attack_text = "the attack",
+ attack_type = MELEE_ATTACK,
+ armour_penetration = 0,
+)
+
+ SIGNAL_HANDLER
+
+ if(attack_type != MELEE_ATTACK)
+ return
+
+ if(!riposte_ready)
+ return
+
+ if(source.incapacitated(IGNORE_GRAB))
+ return
+
+ var/mob/living/attacker = hitby.loc
+ if(!istype(attacker))
+ return
+
+ if(!source.Adjacent(attacker))
+ return
+
+ // Let's check their held items to see if we can do a riposte
+ var/obj/item/main_hand = source.get_active_held_item()
+ var/obj/item/off_hand = source.get_inactive_held_item()
+ // This is the item that ends up doing the "blocking" (flavor)
+ var/obj/item/striking_with
+
+ // First we'll check if the offhand is valid
+ if(!QDELETED(off_hand) && istype(off_hand, /obj/item/melee/sickly_blade))
+ striking_with = off_hand
+
+ // Then we'll check the mainhand
+ // We do mainhand second, because we want to prioritize it over the offhand
+ if(!QDELETED(main_hand) && istype(main_hand, /obj/item/melee/sickly_blade))
+ striking_with = main_hand
+
+ // No valid item in either slot? No riposte
+ if(!striking_with)
+ return
+
+ // If we made it here, deliver the strike
+ INVOKE_ASYNC(src, .proc/counter_attack, source, attacker, striking_with, attack_text)
+
+ // And reset after a bit
+ riposte_ready = FALSE
+ addtimer(CALLBACK(src, .proc/reset_riposte, source), BLADE_DANCE_COOLDOWN)
+
+/datum/heretic_knowledge/blade_dance/proc/counter_attack(mob/living/carbon/human/source, mob/living/target, obj/item/melee/sickly_blade/weapon, attack_text)
+ playsound(get_turf(source), 'sound/weapons/parry.ogg', 100, TRUE)
+ source.balloon_alert(source, "riposte used")
+ source.visible_message(
+ span_warning("[source] leans into [attack_text] and delivers a sudden riposte back at [target]!"),
+ span_warning("You lean into [attack_text] and deliver a sudden riposte back at [target]!"),
+ span_hear("You hear a clink, followed by a stab."),
+ )
+ weapon.melee_attack_chain(source, target)
+
+/datum/heretic_knowledge/blade_dance/proc/reset_riposte(mob/living/carbon/human/source)
+ riposte_ready = TRUE
+ source.balloon_alert(source, "riposte ready")
+
+#undef BLADE_DANCE_COOLDOWN
+
+/datum/heretic_knowledge/mark/blade_mark
+ name = "Mark of the Blade"
+ desc = "Your Mansus Grasp now applies the Mark of the Blade. While marked, \
+ the victim will be unable to leave their current room until it expires or is triggered. \
+ Triggering the mark will summon a knife that will orbit you for a short time. \
+ The knife will block any attack directed towards you, but is consumed on use."
+ gain_text = "There was no room for cowardace here. Those who ran were scolded. \
+ That is how I met them. Their name was The Colonel."
+ next_knowledge = list(/datum/heretic_knowledge/knowledge_ritual/blade)
+ route = PATH_BLADE
+ mark_type = /datum/status_effect/eldritch/blade
+
+/datum/heretic_knowledge/mark/blade_mark/create_mark(mob/living/source, mob/living/target)
+ var/datum/status_effect/eldritch/blade/blade_mark = ..()
+ if(!istype(blade_mark))
+ return
+
+ var/area/to_lock_to = get_area(target)
+ blade_mark.locked_to = to_lock_to
+ to_chat(target, span_hypnophrase("An otherworldly force is compelling you to stay in [get_area_name(to_lock_to)]!"))
+
+/datum/heretic_knowledge/mark/blade_mark/trigger_mark(mob/living/source, mob/living/target)
+ . = ..()
+ if(!.)
+ return
+ source.apply_status_effect(/datum/status_effect/protective_blades, 60 SECONDS, 1, 20, 0 SECONDS)
+
+/datum/heretic_knowledge/knowledge_ritual/blade
+ next_knowledge = list(/datum/heretic_knowledge/duel_stance)
+ route = PATH_BLADE
+
+/// The amount of blood flow reduced per level of severity of gained bleeding wounds for Stance of the Scarred Duelist.
+#define BLOOD_FLOW_PER_SEVEIRTY 1
+
+/datum/heretic_knowledge/duel_stance
+ name = "Stance of the Scarred Duelist"
+ desc = "Grants resilience to blood loss from wounds and immunity to having your limbs dismembered. \
+ Additionally, when damaged below 50% of your maximum health, \
+ you gain increased resistance to gaining wounds and stun resistance."
+ gain_text = "The Colonel was many things though out the age. But now, he is blind; he is deaf; \
+ he cannot be wounded; and he cannot be denied. His methods ensure that."
+ next_knowledge = list(
+ /datum/heretic_knowledge/blade_upgrade/blade,
+ /datum/heretic_knowledge/reroll_targets,
+ /datum/heretic_knowledge/rune_carver,
+ /datum/heretic_knowledge/crucible,
+ )
+ cost = 1
+ route = PATH_BLADE
+ /// Whether we're currently in duelist stance, gaining certain buffs (low health)
+ var/in_duelist_stance = FALSE
+
+/datum/heretic_knowledge/duel_stance/on_gain(mob/user)
+ ADD_TRAIT(user, TRAIT_NODISMEMBER, type)
+ RegisterSignal(user, COMSIG_PARENT_EXAMINE, .proc/on_examine)
+ RegisterSignal(user, COMSIG_CARBON_GAIN_WOUND, .proc/on_wound_gain)
+ RegisterSignal(user, COMSIG_CARBON_HEALTH_UPDATE, .proc/on_health_update)
+
+ on_health_update(user) // Run this once, so if the knowledge is learned while hurt it activates properly
+
+/datum/heretic_knowledge/duel_stance/on_lose(mob/user)
+ REMOVE_TRAIT(user, TRAIT_NODISMEMBER, type)
+ if(in_duelist_stance)
+ REMOVE_TRAIT(user, TRAIT_HARDLY_WOUNDED, type)
+ REMOVE_TRAIT(user, TRAIT_STUNRESISTANCE, type)
+
+ UnregisterSignal(user, list(COMSIG_PARENT_EXAMINE, COMSIG_CARBON_GAIN_WOUND, COMSIG_CARBON_HEALTH_UPDATE))
+
+/datum/heretic_knowledge/duel_stance/proc/on_examine(mob/living/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+
+ var/obj/item/held_item = source.get_active_held_item()
+ if(in_duelist_stance)
+ examine_list += span_warning("[source] looks unnaturally poised[held_item?.force >= 15 ? " and ready to strike out":""].")
+
+/datum/heretic_knowledge/duel_stance/proc/on_wound_gain(mob/living/source, datum/wound/gained_wound, obj/item/bodypart/limb)
+ SIGNAL_HANDLER
+
+ if(gained_wound.blood_flow <= 0)
+ return
+
+ gained_wound.blood_flow -= (gained_wound.severity * BLOOD_FLOW_PER_SEVEIRTY)
+
+/datum/heretic_knowledge/duel_stance/proc/on_health_update(mob/living/source)
+ SIGNAL_HANDLER
+
+ if(in_duelist_stance && source.health > source.maxHealth * 0.5)
+ source.balloon_alert(source, "exited duelist stance")
+ in_duelist_stance = FALSE
+ REMOVE_TRAIT(source, TRAIT_HARDLY_WOUNDED, type)
+ REMOVE_TRAIT(source, TRAIT_STUNRESISTANCE, type)
+ return
+
+ if(!in_duelist_stance && source.health <= source.maxHealth * 0.5)
+ source.balloon_alert(source, "entered duelist stance")
+ in_duelist_stance = TRUE
+ ADD_TRAIT(source, TRAIT_HARDLY_WOUNDED, type)
+ ADD_TRAIT(source, TRAIT_STUNRESISTANCE, type)
+ return
+
+#undef BLOOD_FLOW_PER_SEVEIRTY
+
+/datum/heretic_knowledge/blade_upgrade/blade
+ name = "Swift Blades"
+ desc = "Attacking someone with a Darkened Blade in both hands \
+ will now deliver a blow with both at once, dealing two attacks in rapid succession. \
+ The second blow will be slightly weaker."
+ gain_text = "From here, I began to learn the Colonel's arts. The prowess was finally mine to have."
+ next_knowledge = list(/datum/heretic_knowledge/spell/furious_steel)
+ route = PATH_BLADE
+
+/datum/heretic_knowledge/blade_upgrade/blade/do_melee_effects(mob/living/source, mob/living/target, obj/item/melee/sickly_blade/blade)
+ if(target == source)
+ return
+
+ var/obj/item/off_hand = source.get_inactive_held_item()
+ if(QDELETED(off_hand) || !istype(off_hand, /obj/item/melee/sickly_blade))
+ return
+ // If our off-hand is the blade that's attacking,
+ // quit out now to avoid an infinite stab combo
+ if(off_hand == blade)
+ return
+
+ // Give it a short delay (for style, also lets people dodge it I guess)
+ addtimer(CALLBACK(src, .proc/follow_up_attack, source, target, off_hand), 0.25 SECONDS)
+
+/datum/heretic_knowledge/blade_upgrade/blade/proc/follow_up_attack(mob/living/source, mob/living/target, obj/item/melee/sickly_blade/blade)
+ if(QDELETED(source) || QDELETED(target) || QDELETED(blade))
+ return
+ // Sanity to ensure that the blade we're delivering
+ // an offhand attack with is actually our offhand
+ if(blade != source.get_inactive_held_item())
+ return
+ if(!source.Adjacent(target))
+ return
+
+ // Blade are 17 force: 17 + 17 = 34 (3 hits to crit, unarmored)
+ // So, -5 force is put on the offhand blade: 17 + 12 = 29 (4 hits to crit, unarmored)
+ blade.force -= 5
+ blade.melee_attack_chain(source, target)
+ blade.force += 5
+
+/datum/heretic_knowledge/spell/furious_steel
+ name = "Furious Steel"
+ desc = "Grants you Furious Steel, a targeted spell. Using it will summon three \
+ orbiting blades around you. These blades will protect you from all attacks, \
+ but are consumed on use. Additionally, you can click to fire the blades \
+ at a target, dealing damage and causing bleeding."
+ gain_text = "His arts were those that ensured an ending."
+ next_knowledge = list(
+ /datum/heretic_knowledge/summon/maid_in_mirror,
+ /datum/heretic_knowledge/final/blade_final,
+ /datum/heretic_knowledge/rifle,
+ )
+ spell_to_add = /obj/effect/proc_holder/spell/aimed/furious_steel
+ cost = 1
+ route = PATH_BLADE
+
+/datum/heretic_knowledge/final/blade_final
+ name = "Maelstrom of Silver"
+ desc = "The ascension ritual of the Path of Blades. \
+ Bring 3 headless corpses to a transmutation rune to complete the ritual. \
+ When completed, you will be surrounded in a constant, regenerating orbit of blades. \
+ These blades will protect you from all attacks, but are consumed on use. \
+ Your Furious Steel spell will also have a shorter cooldown. \
+ Additionally, you become a master of combat, gaining full wound and stun immunity. \
+ Your Darkened Blades deal bonus damage and healing you on attack for a portion of the damage dealt."
+ gain_text = "The Colonel, in all of his expertise, revealed to me the three roots of victory. \
+ Cunning. Strength. And agony! This was their secret doctrine! With this knowledge in my potential, \
+ I AM UNMATCHED! A STORM OF STEEL AND SILVER IS UPON US! WITNESS MY ASCENSION!"
+ route = PATH_BLADE
+
+/datum/heretic_knowledge/final/blade_final/is_valid_sacrifice(mob/living/carbon/human/sacrifice)
+ . = ..()
+ if(!.)
+ return FALSE
+
+ return !sacrifice.get_bodypart(BODY_ZONE_HEAD)
+
+/datum/heretic_knowledge/final/blade_final/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc)
+ . = ..()
+ priority_announce("[generate_heretic_text()] Master of blades, the Colonel's disciple, [user.real_name] has ascended! Their steel is that which will cut reality in a maelstom of silver! [generate_heretic_text()]","[generate_heretic_text()]", ANNOUNCER_SPANOMALIES)
+ user.client?.give_award(/datum/award/achievement/misc/blade_ascension, user)
+ ADD_TRAIT(user, TRAIT_STUNIMMUNE, name)
+ ADD_TRAIT(user, TRAIT_NEVER_WOUNDED, name)
+ RegisterSignal(user, COMSIG_HERETIC_BLADE_ATTACK, .proc/on_eldritch_blade)
+ user.apply_status_effect(/datum/status_effect/protective_blades/recharging, null, 8, 30, 0.25 SECONDS, 1 MINUTES)
+
+ var/obj/effect/proc_holder/spell/aimed/furious_steel/steel_spell = locate() in user.mind.spell_list
+ steel_spell?.charge_max /= 3
+
+/datum/heretic_knowledge/final/blade_final/proc/on_eldritch_blade(mob/living/source, mob/living/target, obj/item/melee/sickly_blade/blade)
+ SIGNAL_HANDLER
+
+ if(target == source)
+ return
+
+ // Turns your heretic blades into eswords, pretty much.
+ var/bonus_damage = clamp(30 - blade.force, 0, 12)
+
+ target.apply_damage(
+ damage = bonus_damage,
+ damagetype = BRUTE,
+ spread_damage = TRUE,
+ wound_bonus = 5,
+ sharpness = SHARP_EDGED,
+ attack_direction = get_dir(source, target),
+ )
+
+ if(target.stat != DEAD)
+ // And! Get some free healing for a portion of the bonus damage dealt.
+ source.heal_overall_damage(bonus_damage / 2, bonus_damage / 2)
diff --git a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm
index 6516065556c..7f555c12952 100644
--- a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm
@@ -16,45 +16,36 @@
* Ashen Eyes
*
* Mark of Flesh
+ * Ritual of Knowledge
* Raw Ritual
* > Sidepaths:
- * Carving Knife
+ * Blood Siphon
* Curse of Paralysis
*
* Bleeding Steel
* Lonely Ritual
* > Sidepaths:
* Ashen Ritual
- * Blood Siphon
+ * Cleave
*
* Priest's Final Hymn
*/
-/datum/heretic_knowledge/limited_amount/base_flesh
+/datum/heretic_knowledge/limited_amount/starting/base_flesh
name = "Principle of Hunger"
desc = "Opens up the Path of Flesh to you. \
Allows you to transmute a knife and a pool of blood into a Bloody Blade. \
You can only create three at a time."
gain_text = "Hundreds of us starved, but not me... I found strength in my greed."
next_knowledge = list(/datum/heretic_knowledge/limited_amount/flesh_grasp)
- banned_knowledge = list(
- /datum/heretic_knowledge/limited_amount/base_ash,
- /datum/heretic_knowledge/limited_amount/base_rust,
- /datum/heretic_knowledge/limited_amount/base_void,
- /datum/heretic_knowledge/final/ash_final,
- /datum/heretic_knowledge/final/rust_final,
- /datum/heretic_knowledge/final/void_final,
- )
required_atoms = list(
/obj/item/knife = 1,
/obj/effect/decal/cleanable/blood = 1,
)
result_atoms = list(/obj/item/melee/sickly_blade/flesh)
limit = 3 // Bumped up so they can arm up their ghouls too.
- cost = 1
- priority = MAX_KNOWLEDGE_PRIORITY - 5
route = PATH_FLESH
-/datum/heretic_knowledge/limited_amount/base_flesh/on_research(mob/user)
+/datum/heretic_knowledge/limited_amount/starting/base_flesh/on_research(mob/user)
. = ..()
var/datum/antagonist/heretic/our_heretic = IS_HERETIC(user)
our_heretic.heretic_path = route
@@ -68,8 +59,9 @@
/datum/heretic_knowledge/limited_amount/flesh_grasp
name = "Grasp of Flesh"
- desc = "Your Mansus Grasp gains the ability to create a single ghoul out of corpse with a soul. \
- Ghouls have only 25 health and look like husks to the heathens' eyes, but can use Bloody Blades effectively."
+ desc = "Your Mansus Grasp gains the ability to create a ghoul out of corpse with a soul. \
+ Ghouls have only 25 health and look like husks to the heathens' eyes, but can use Bloody Blades effectively. \
+ You can only create one at a time by this method."
gain_text = "My new found desires drove me to greater and greater heights."
next_knowledge = list(/datum/heretic_knowledge/limited_amount/flesh_ghoul)
limit = 1
@@ -88,57 +80,56 @@
if(target.stat != DEAD)
return
- // Skeletons can't become husks, and monkeys are monkeys.
- if(!ishuman(target) || isskeleton(target) || ismonkey(target))
- target.balloon_alert(source, "invalid body!")
- return COMPONENT_BLOCK_CHARGE_USE
-
- var/mob/living/carbon/human/human_target = target
- human_target.grab_ghost()
- if(!human_target.mind || !human_target.client)
- target.balloon_alert(source, "no soul!")
- return COMPONENT_BLOCK_CHARGE_USE
- if(HAS_TRAIT(human_target, TRAIT_HUSK))
- target.balloon_alert(source, "husked!")
- return COMPONENT_BLOCK_CHARGE_USE
if(LAZYLEN(created_items) >= limit)
target.balloon_alert(source, "at ghoul limit!")
return COMPONENT_BLOCK_CHARGE_USE
- LAZYADD(created_items, WEAKREF(human_target))
- log_game("[key_name(source)] created a ghoul, controlled by [key_name(human_target)].")
- message_admins("[ADMIN_LOOKUPFLW(source)] created a ghoul, [ADMIN_LOOKUPFLW(human_target)].")
+ if(!IS_VALID_GHOUL_MOB(target))
+ target.balloon_alert(source, "invalid body!")
+ return COMPONENT_BLOCK_CHARGE_USE
- RegisterSignal(human_target, COMSIG_LIVING_DEATH, .proc/remove_ghoul)
- human_target.revive(full_heal = TRUE, admin_revive = TRUE)
- human_target.setMaxHealth(GHOUL_MAX_HEALTH)
- human_target.health = GHOUL_MAX_HEALTH
- human_target.become_husk(MAGIC_TRAIT)
- human_target.apply_status_effect(/datum/status_effect/ghoul)
- human_target.faction |= FACTION_HERETIC
+ // Get their ghost in here so we can raise them
+ target.grab_ghost()
- var/datum/antagonist/heretic_monster/heretic_monster = human_target.mind.add_antag_datum(/datum/antagonist/heretic_monster)
- heretic_monster.set_owner(source.mind)
+ if(!target.mind || !target.client)
+ target.balloon_alert(source, "no soul!")
+ return COMPONENT_BLOCK_CHARGE_USE
-/datum/heretic_knowledge/limited_amount/flesh_grasp/proc/remove_ghoul(mob/living/carbon/human/source)
- SIGNAL_HANDLER
+ if(HAS_TRAIT(target, TRAIT_HUSK))
+ target.balloon_alert(source, "husked!")
+ return COMPONENT_BLOCK_CHARGE_USE
- LAZYREMOVE(created_items, WEAKREF(source))
- source.setMaxHealth(initial(source.maxHealth))
- source.cure_husk(MAGIC_TRAIT)
- source.remove_status_effect(/datum/status_effect/ghoul)
- source.mind.remove_antag_datum(/datum/antagonist/heretic_monster)
+ make_ghoul(source, target)
- UnregisterSignal(source, COMSIG_LIVING_DEATH)
+/// Makes [victim] into a ghoul.
+/datum/heretic_knowledge/limited_amount/flesh_grasp/proc/make_ghoul(mob/living/user, mob/living/carbon/human/victim)
+ log_game("[key_name(user)] created a ghoul, controlled by [key_name(victim)].")
+ message_admins("[ADMIN_LOOKUPFLW(user)] created a ghoul, [ADMIN_LOOKUPFLW(victim)].")
+
+ victim.apply_status_effect(
+ /datum/status_effect/ghoul,
+ GHOUL_MAX_HEALTH,
+ user.mind,
+ CALLBACK(src, .proc/apply_to_ghoul),
+ CALLBACK(src, .proc/remove_from_ghoul),
+ )
+
+/// Callback for the ghoul status effect - Tracking all of our ghouls
+/datum/heretic_knowledge/limited_amount/flesh_grasp/proc/apply_to_ghoul(mob/living/ghoul)
+ LAZYADD(created_items, WEAKREF(ghoul))
+
+/// Callback for the ghoul status effect - Tracking all of our ghouls
+/datum/heretic_knowledge/limited_amount/flesh_grasp/proc/remove_from_ghoul(mob/living/ghoul)
+ LAZYREMOVE(created_items, WEAKREF(ghoul))
/datum/heretic_knowledge/limited_amount/flesh_ghoul
name = "Imperfect Ritual"
desc = "Allows you to transmute a corpse and a poppy to create a Voiceless Dead. \
Voiceless Dead are mute ghouls and only have 50 health, but can use Bloody Blades effectively. \
- You can only create two at a time. "
+ You can only create two at a time."
gain_text = "I found notes of a dark ritual, unfinished... yet still, I pushed forward."
next_knowledge = list(
- /datum/heretic_knowledge/flesh_mark,
+ /datum/heretic_knowledge/mark/flesh_mark,
/datum/heretic_knowledge/codex_cicatrix,
/datum/heretic_knowledge/void_cloak,
/datum/heretic_knowledge/medallion,
@@ -152,16 +143,19 @@
route = PATH_FLESH
/datum/heretic_knowledge/limited_amount/flesh_ghoul/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc)
+ . = ..()
+ if(!.)
+ return FALSE
+
for(var/mob/living/carbon/human/body in atoms)
- // Skeletons can't become husks, and monkeys because they're monkeys.
- if(body.stat != DEAD || isskeleton(body) || ismonkey(body) || HAS_TRAIT(body, TRAIT_HUSK))
+ if(body.stat != DEAD || !IS_VALID_GHOUL_MOB(body) || HAS_TRAIT(body, TRAIT_HUSK))
atoms -= body
if(!(locate(/mob/living/carbon/human) in atoms))
loc.balloon_alert(user, "ritual failed, no valid body!")
return FALSE
- return ..()
+ return TRUE
/datum/heretic_knowledge/limited_amount/flesh_ghoul/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc)
var/mob/living/carbon/human/soon_to_be_ghoul = locate() in selected_atoms
@@ -184,77 +178,43 @@
soon_to_be_ghoul.ghostize(FALSE)
soon_to_be_ghoul.key = chosen_candidate.key
- ADD_TRAIT(soon_to_be_ghoul, TRAIT_MUTE, MAGIC_TRAIT)
- log_game("[key_name(user)] created a voiceless dead, controlled by [key_name(soon_to_be_ghoul)].")
- message_admins("[ADMIN_LOOKUPFLW(user)] created a voiceless dead, [ADMIN_LOOKUPFLW(soon_to_be_ghoul)].")
- soon_to_be_ghoul.revive(full_heal = TRUE, admin_revive = TRUE)
- soon_to_be_ghoul.setMaxHealth(MUTE_MAX_HEALTH)
- soon_to_be_ghoul.health = MUTE_MAX_HEALTH // Voiceless dead are much tougher than ghouls
- soon_to_be_ghoul.become_husk()
- soon_to_be_ghoul.faction |= FACTION_HERETIC
- soon_to_be_ghoul.apply_status_effect(/datum/status_effect/ghoul)
-
- var/datum/antagonist/heretic_monster/heretic_monster = soon_to_be_ghoul.mind.add_antag_datum(/datum/antagonist/heretic_monster)
- heretic_monster.set_owner(user.mind)
-
selected_atoms -= soon_to_be_ghoul
- LAZYADD(created_items, WEAKREF(soon_to_be_ghoul))
+ make_ghoul(user, soon_to_be_ghoul)
- RegisterSignal(soon_to_be_ghoul, COMSIG_LIVING_DEATH, .proc/remove_ghoul)
- return TRUE
+/// Makes [victim] into a ghoul.
+/datum/heretic_knowledge/limited_amount/flesh_ghoul/proc/make_ghoul(mob/living/user, mob/living/carbon/human/victim)
+ log_game("[key_name(user)] created a voiceless dead, controlled by [key_name(victim)].")
+ message_admins("[ADMIN_LOOKUPFLW(user)] created a voiceless dead, [ADMIN_LOOKUPFLW(victim)].")
-/datum/heretic_knowledge/limited_amount/flesh_ghoul/proc/remove_ghoul(mob/living/carbon/human/source)
- SIGNAL_HANDLER
+ victim.apply_status_effect(
+ /datum/status_effect/ghoul,
+ MUTE_MAX_HEALTH,
+ user.mind,
+ CALLBACK(src, .proc/apply_to_ghoul),
+ CALLBACK(src, .proc/remove_from_ghoul),
+ )
- LAZYREMOVE(created_items, WEAKREF(source))
- source.setMaxHealth(initial(source.maxHealth))
- source.remove_status_effect(/datum/status_effect/ghoul)
- source.mind.remove_antag_datum(/datum/antagonist/heretic_monster)
+/// Callback for the ghoul status effect - Tracks all of our ghouls and applies effects
+/datum/heretic_knowledge/limited_amount/flesh_ghoul/proc/apply_to_ghoul(mob/living/ghoul)
+ LAZYADD(created_items, WEAKREF(ghoul))
+ ADD_TRAIT(ghoul, TRAIT_MUTE, MAGIC_TRAIT)
- UnregisterSignal(source, COMSIG_LIVING_DEATH)
+/// Callback for the ghoul status effect - Tracks all of our ghouls and applies effects
+/datum/heretic_knowledge/limited_amount/flesh_ghoul/proc/remove_from_ghoul(mob/living/ghoul)
+ LAZYREMOVE(created_items, WEAKREF(ghoul))
+ REMOVE_TRAIT(ghoul, TRAIT_MUTE, MAGIC_TRAIT)
-/datum/heretic_knowledge/flesh_mark
+/datum/heretic_knowledge/mark/flesh_mark
name = "Mark of Flesh"
desc = "Your Mansus Grasp now applies the Mark of Flesh. The mark is triggered from an attack with your Bloody Blade. \
When triggered, the victim begins to bleed significantly."
gain_text = "That's when I saw them, the marked ones. They were out of reach. They screamed, and screamed."
next_knowledge = list(/datum/heretic_knowledge/knowledge_ritual/flesh)
- banned_knowledge = list(
- /datum/heretic_knowledge/rust_mark,
- /datum/heretic_knowledge/ash_mark,
- /datum/heretic_knowledge/void_mark,
- )
- cost = 2
route = PATH_FLESH
-
-/datum/heretic_knowledge/flesh_mark/on_gain(mob/user)
- RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, .proc/on_mansus_grasp)
- RegisterSignal(user, COMSIG_HERETIC_BLADE_ATTACK, .proc/on_eldritch_blade)
-
-/datum/heretic_knowledge/flesh_mark/on_lose(mob/user)
- UnregisterSignal(user, list(COMSIG_HERETIC_MANSUS_GRASP_ATTACK, COMSIG_HERETIC_BLADE_ATTACK))
-
-/datum/heretic_knowledge/flesh_mark/proc/on_mansus_grasp(mob/living/source, mob/living/target)
- SIGNAL_HANDLER
-
- target.apply_status_effect(/datum/status_effect/eldritch/flesh)
-
-/datum/heretic_knowledge/flesh_mark/proc/on_eldritch_blade(mob/living/user, mob/living/target)
- SIGNAL_HANDLER
-
- var/datum/status_effect/eldritch/mark = target.has_status_effect(/datum/status_effect/eldritch)
- if(!istype(mark))
- return
-
- mark.on_effect()
+ mark_type = /datum/status_effect/eldritch/flesh
/datum/heretic_knowledge/knowledge_ritual/flesh
next_knowledge = list(/datum/heretic_knowledge/summon/raw_prophet)
- banned_knowledge = list(
- /datum/heretic_knowledge/knowledge_ritual/ash,
- /datum/heretic_knowledge/knowledge_ritual/void,
- /datum/heretic_knowledge/knowledge_ritual/rust,
- )
route = PATH_FLESH
/datum/heretic_knowledge/summon/raw_prophet
@@ -265,9 +225,9 @@
gain_text = "I could not continue alone. I was able to summon The Uncanny Man to help me see more. \
The screams... once constant, now silenced by their wretched appearance. Nothing was out of reach."
next_knowledge = list(
- /datum/heretic_knowledge/flesh_blade_upgrade,
+ /datum/heretic_knowledge/blade_upgrade/flesh,
/datum/heretic_knowledge/reroll_targets,
- /datum/heretic_knowledge/rune_carver,
+ /datum/heretic_knowledge/spell/blood_siphon,
/datum/heretic_knowledge/curse/paralysis,
)
required_atoms = list(
@@ -279,36 +239,22 @@
cost = 1
route = PATH_FLESH
-/datum/heretic_knowledge/flesh_blade_upgrade
+/datum/heretic_knowledge/blade_upgrade/flesh
name = "Bleeding Steel"
desc = "Your Bloody Blade now causes enemies to bleed heavily on attack."
gain_text = "The Uncanny Man was not alone. They led me to the Marshal. \
I finally began to understand. And then, blood rained from the heavens."
next_knowledge = list(/datum/heretic_knowledge/summon/stalker)
- banned_knowledge = list(
- /datum/heretic_knowledge/ash_blade_upgrade,
- /datum/heretic_knowledge/rust_blade_upgrade,
- /datum/heretic_knowledge/void_blade_upgrade,
- )
- cost = 2
route = PATH_FLESH
-/datum/heretic_knowledge/flesh_blade_upgrade/on_gain(mob/user)
- RegisterSignal(user, COMSIG_HERETIC_BLADE_ATTACK, .proc/on_eldritch_blade)
-
-/datum/heretic_knowledge/flesh_blade_upgrade/on_lose(mob/user)
- UnregisterSignal(user, COMSIG_HERETIC_BLADE_ATTACK)
-
-/datum/heretic_knowledge/flesh_blade_upgrade/proc/on_eldritch_blade(mob/living/user, mob/living/target)
- SIGNAL_HANDLER
-
- if(!iscarbon(target) || user == target)
+/datum/heretic_knowledge/blade_upgrade/flesh/do_melee_effects(mob/living/source, mob/living/target, obj/item/melee/sickly_blade/blade)
+ if(!iscarbon(target) || source == target)
return
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, attack_direction = get_dir(user, target))
+ crit_wound.apply_wound(bodypart, attack_direction = get_dir(source, target))
/datum/heretic_knowledge/summon/stalker
name = "Lonely Ritual"
@@ -319,7 +265,7 @@
next_knowledge = list(
/datum/heretic_knowledge/final/flesh_final,
/datum/heretic_knowledge/summon/ashy,
- /datum/heretic_knowledge/spell/blood_siphon,
+ /datum/heretic_knowledge/spell/cleave,
)
required_atoms = list(
/obj/item/organ/tail = 1,
@@ -359,7 +305,7 @@
grasp_ghoul.limit *= 3
var/datum/heretic_knowledge/limited_amount/flesh_ghoul/ritual_ghoul = heretic_datum.get_knowledge(/datum/heretic_knowledge/limited_amount/flesh_ghoul)
ritual_ghoul.limit *= 3
- var/datum/heretic_knowledge/limited_amount/base_flesh/blade_ritual = heretic_datum.get_knowledge(/datum/heretic_knowledge/limited_amount/base_flesh)
+ var/datum/heretic_knowledge/limited_amount/starting/base_flesh/blade_ritual = heretic_datum.get_knowledge(/datum/heretic_knowledge/limited_amount/starting/base_flesh)
blade_ritual.limit = 999
#undef GHOUL_MAX_HEALTH
diff --git a/code/modules/antagonists/heretic/knowledge/rust_lore.dm b/code/modules/antagonists/heretic/knowledge/rust_lore.dm
index a80368bc568..1f7bec62b8b 100644
--- a/code/modules/antagonists/heretic/knowledge/rust_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/rust_lore.dm
@@ -11,6 +11,7 @@
* Armorer's Ritual
*
* Mark of Rust
+ * Ritual of Knowledge
* Aggressive Spread
* > Sidepaths:
* Curse of Corrosion
@@ -24,32 +25,21 @@
*
* Rustbringer's Oath
*/
-/datum/heretic_knowledge/limited_amount/base_rust
+/datum/heretic_knowledge/limited_amount/starting/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. \
You can only create two at a time."
gain_text = "\"Let me tell you a story\", said the Blacksmith, as he gazed deep into his rusty blade."
next_knowledge = list(/datum/heretic_knowledge/rust_fist)
- banned_knowledge = list(
- /datum/heretic_knowledge/limited_amount/base_ash,
- /datum/heretic_knowledge/limited_amount/base_flesh,
- /datum/heretic_knowledge/final/ash_final,
- /datum/heretic_knowledge/final/flesh_final,
- /datum/heretic_knowledge/final/void_final,
- /datum/heretic_knowledge/limited_amount/base_void,
- )
required_atoms = list(
/obj/item/knife = 1,
/obj/item/trash = 1,
)
result_atoms = list(/obj/item/melee/sickly_blade/rust)
- limit = 2
- cost = 1
- priority = MAX_KNOWLEDGE_PRIORITY - 5
route = PATH_RUST
-/datum/heretic_knowledge/limited_amount/base_rust/on_research(mob/user)
+/datum/heretic_knowledge/limited_amount/starting/base_rust/on_research(mob/user)
. = ..()
var/datum/antagonist/heretic/our_heretic = IS_HERETIC(user)
our_heretic.heretic_path = route
@@ -89,7 +79,7 @@
desc = "Grants you passive healing and stun resistance while standing over rust."
gain_text = "The speed was unparalleled, the strength unnatural. The Blacksmith was smiling."
next_knowledge = list(
- /datum/heretic_knowledge/rust_mark,
+ /datum/heretic_knowledge/mark/rust_mark,
/datum/heretic_knowledge/codex_cicatrix,
/datum/heretic_knowledge/armor,
/datum/heretic_knowledge/essence,
@@ -139,48 +129,17 @@
source.adjustStaminaLoss(-2)
source.AdjustAllImmobility(-5)
-/datum/heretic_knowledge/rust_mark
+/datum/heretic_knowledge/mark/rust_mark
name = "Mark of Rust"
desc = "Your Mansus Grasp now applies the Mark of Rust. The mark is triggered from an attack with your Rusty Blade. \
When triggered, the victim's organs and equipment will have a 75% chance to sustain damage and may be destroyed."
gain_text = "The Blacksmith looks away. To a place lost long ago. \"Rusted Hills help those in dire need... at a cost.\""
next_knowledge = list(/datum/heretic_knowledge/knowledge_ritual/rust)
- banned_knowledge = list(
- /datum/heretic_knowledge/ash_mark,
- /datum/heretic_knowledge/flesh_mark,
- /datum/heretic_knowledge/void_mark,
- )
- cost = 2
route = PATH_RUST
-
-/datum/heretic_knowledge/rust_mark/on_gain(mob/user)
- RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, .proc/on_mansus_grasp)
- RegisterSignal(user, COMSIG_HERETIC_BLADE_ATTACK, .proc/on_eldritch_blade)
-
-/datum/heretic_knowledge/rust_mark/on_lose(mob/user)
- UnregisterSignal(user, list(COMSIG_HERETIC_MANSUS_GRASP_ATTACK, COMSIG_HERETIC_BLADE_ATTACK))
-
-/datum/heretic_knowledge/rust_mark/proc/on_mansus_grasp(mob/living/source, mob/living/target)
- SIGNAL_HANDLER
-
- target.apply_status_effect(/datum/status_effect/eldritch/rust)
-
-/datum/heretic_knowledge/rust_mark/proc/on_eldritch_blade(mob/living/user, mob/living/target)
- SIGNAL_HANDLER
-
- var/datum/status_effect/eldritch/mark = target.has_status_effect(/datum/status_effect/eldritch)
- if(!istype(mark))
- return
-
- mark.on_effect()
+ mark_type = /datum/status_effect/eldritch/rust
/datum/heretic_knowledge/knowledge_ritual/rust
next_knowledge = list(/datum/heretic_knowledge/spell/area_conversion)
- banned_knowledge = list(
- /datum/heretic_knowledge/knowledge_ritual/ash,
- /datum/heretic_knowledge/knowledge_ritual/void,
- /datum/heretic_knowledge/knowledge_ritual/flesh,
- )
route = PATH_RUST
/datum/heretic_knowledge/spell/area_conversion
@@ -189,7 +148,7 @@
Already rusted surfaces are destroyed."
gain_text = "All wise men know well not to visit the Rusted Hills... Yet the Blacksmith's tale was inspiring."
next_knowledge = list(
- /datum/heretic_knowledge/rust_blade_upgrade,
+ /datum/heretic_knowledge/blade_upgrade/rust,
/datum/heretic_knowledge/reroll_targets,
/datum/heretic_knowledge/curse/corrosion,
/datum/heretic_knowledge/crucible,
@@ -198,29 +157,15 @@
cost = 1
route = PATH_RUST
-/datum/heretic_knowledge/rust_blade_upgrade
+/datum/heretic_knowledge/blade_upgrade/rust
name = "Toxic Blade"
desc = "Your Rusty Blade now poisons enemies on attack."
gain_text = "The Blacksmith hands you their blade. \"The Blade will guide you through the flesh, should you let it.\" \
The heavy rust weights it down. You stare deeply into it. The Rusted Hills call for you, now."
next_knowledge = list(/datum/heretic_knowledge/spell/entropic_plume)
- banned_knowledge = list(
- /datum/heretic_knowledge/ash_blade_upgrade,
- /datum/heretic_knowledge/flesh_blade_upgrade,
- /datum/heretic_knowledge/void_blade_upgrade,
- )
- cost = 2
route = PATH_RUST
-/datum/heretic_knowledge/rust_blade_upgrade/on_gain(mob/user)
- RegisterSignal(user, COMSIG_HERETIC_BLADE_ATTACK, .proc/on_eldritch_blade)
-
-/datum/heretic_knowledge/rust_blade_upgrade/on_lose(mob/user)
- UnregisterSignal(user, COMSIG_HERETIC_BLADE_ATTACK)
-
-/datum/heretic_knowledge/rust_blade_upgrade/proc/on_eldritch_blade(mob/living/user, mob/living/target)
- SIGNAL_HANDLER
-
+/datum/heretic_knowledge/blade_upgrade/rust/do_melee_effects(mob/living/source, mob/living/target, obj/item/melee/sickly_blade/blade)
// No user == target check here, cause it's technically good for the heretic?
target.reagents?.add_reagent(/datum/reagent/eldritch, 5)
@@ -232,7 +177,7 @@
gain_text = "The corrosion was unstoppable. The rust was unpleasable. \
The Blacksmith was gone, and you hold their blade. Champions of hope, the Rustbringer is nigh!"
next_knowledge = list(
- /datum/heretic_knowledge/spell/cleave,
+ /datum/heretic_knowledge/rifle,
/datum/heretic_knowledge/final/rust_final,
/datum/heretic_knowledge/summon/rusty,
)
diff --git a/code/modules/antagonists/heretic/knowledge/side_blade_rust.dm b/code/modules/antagonists/heretic/knowledge/side_blade_rust.dm
new file mode 100644
index 00000000000..f3786b84d1f
--- /dev/null
+++ b/code/modules/antagonists/heretic/knowledge/side_blade_rust.dm
@@ -0,0 +1,103 @@
+// Sidepaths for knowledge between Rust and Blade.
+/datum/heretic_knowledge/armor
+ name = "Armorer's Ritual"
+ desc = "Allows you to transmute a table and a gas mask to create Eldritch Armor. \
+ Eldritch Armor provides great protection while also acting as a focus when hooded."
+ gain_text = "The Rusted Hills welcomed the Blacksmith in their generosity. And the Blacksmith \
+ returned their generosity in kind."
+ next_knowledge = list(
+ /datum/heretic_knowledge/rust_regen,
+ /datum/heretic_knowledge/blade_dance,
+ )
+ required_atoms = list(
+ /obj/structure/table = 1,
+ /obj/item/clothing/mask/gas = 1,
+ )
+ result_atoms = list(/obj/item/clothing/suit/hooded/cultrobes/eldritch)
+ cost = 1
+ route = PATH_SIDE
+
+/datum/heretic_knowledge/crucible
+ name = "Mawed Crucible"
+ desc = "Allows you to transmute a portable water tank and a table to create a Mawed Crucible. \
+ The Mawed Crubile can brew powerful potions for combat and utility, but must be fed bodyparts and organs between uses."
+ gain_text = "This is pure agony. I wasn't able to summon the figure of the Aristocrat, \
+ but with the Priest's attention I stumbled upon a different recipe..."
+ next_knowledge = list(
+ /datum/heretic_knowledge/duel_stance,
+ /datum/heretic_knowledge/spell/area_conversion,
+ )
+ required_atoms = list(
+ /obj/structure/reagent_dispensers/watertank = 1,
+ /obj/structure/table = 1,
+ )
+ result_atoms = list(/obj/structure/destructible/eldritch_crucible)
+ cost = 1
+ route = PATH_SIDE
+
+/datum/heretic_knowledge/rifle
+ name = "Lionhunter's Rifle"
+ desc = "Allows you to transmute any ballistic weapon, such as a pipegun, with hide \
+ from any animal, a plank of wood, and a camera to create the Lionhunter's rifle. \
+ The Lionhunter's Rifle is a long ranged ballistic weapon with three shots. \
+ These shots function as normal, albiet weak high caliber mutitions when fired from \
+ close range or at inanimate objects. You can aim the rifle at distant foes, \
+ causing the shot to deal massively increased damage and hone in on them."
+ gain_text = "I met an old man in an anique shop who wielded a very unusual weapon. \
+ I could not purchase it at the time, but they showed me how they made it ages ago."
+ next_knowledge = list(
+ /datum/heretic_knowledge/spell/furious_steel,
+ /datum/heretic_knowledge/spell/entropic_plume,
+ /datum/heretic_knowledge/rifle_ammo,
+ )
+ required_atoms = list(
+ /obj/item/gun/ballistic = 1,
+ /obj/item/stack/sheet/animalhide = 1,
+ /obj/item/stack/sheet/mineral/wood = 1,
+ /obj/item/camera = 1,
+ )
+ result_atoms = list(/obj/item/gun/ballistic/rifle/lionhunter)
+ cost = 1
+ route = PATH_SIDE
+
+/datum/heretic_knowledge/rifle_ammo
+ name = "Lionhunter Rifle Ammunition"
+ desc = "Allows you to transmute 3 ballistic ammo casings (used or unused) of any caliber, \
+ including shotgun shot, with any animal hide to create an extra clip of ammunition for the Lionhunter Rifle."
+ gain_text = "The weapon came with three rough iron balls, intended to be used as ammunition. \
+ They were very effective, for simple iron, but used up quickly. I soon ran out. \
+ No replacement munitions worked in their stead. It was peculiar in what it wanted."
+ required_atoms = list(
+ /obj/item/stack/sheet/animalhide = 1,
+ /obj/item/ammo_casing = 3,
+ )
+ cost = 1
+ route = PATH_SIDE
+ /// A list of calibers we will accept for "ballistic ammo casings".
+ var/static/list/acceptable_calibers = list(
+ CALIBER_10MM,
+ CALIBER_357,
+ CALIBER_38,
+ CALIBER_45,
+ CALIBER_46X30MM,
+ CALIBER_50,
+ CALIBER_712X82MM,
+ CALIBER_75,
+ CALIBER_9MM,
+ CALIBER_A556,
+ CALIBER_A762,
+ CALIBER_N762,
+ CALIBER_SHOTGUN,
+ )
+
+/datum/heretic_knowledge/rifle_ammo/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc)
+ for(var/obj/item/ammo_casing/casing in atoms)
+ if(casing.caliber in acceptable_calibers)
+ continue
+
+ // Remove any casings not in in the acceptable_calibers list from atoms
+ atoms -= casing
+
+ // We removed any invalid casings from the atoms list,
+ // return to allow the ritual to fill out selected atoms with the new list
+ return TRUE
diff --git a/code/modules/antagonists/heretic/knowledge/side_flesh_void.dm b/code/modules/antagonists/heretic/knowledge/side_flesh_void.dm
index 8c959d7108a..fc63133ccc5 100644
--- a/code/modules/antagonists/heretic/knowledge/side_flesh_void.dm
+++ b/code/modules/antagonists/heretic/knowledge/side_flesh_void.dm
@@ -18,35 +18,29 @@
cost = 1
route = PATH_SIDE
-/datum/heretic_knowledge/rune_carver
- name = "Carving Knife"
- desc = "Allows you to transmute a knife, a shard of glass, and a piece of paper to create a Carving Knife. \
- The Carving Knife allows you to etch difficult to see traps that trigger on heathens who walk overhead. \
- Also makes for a handy throwing weapon."
- gain_text = "Etched, carved... eternal. There is power hidden in everything. I can unveil it! \
- I can carve the monolith to reveal the chains!"
- next_knowledge = list(
- /datum/heretic_knowledge/spell/void_phase,
- /datum/heretic_knowledge/summon/raw_prophet,
- )
- required_atoms = list(
- /obj/item/knife = 1,
- /obj/item/shard = 1,
- /obj/item/paper = 1,
- )
- result_atoms = list(/obj/item/melee/rune_carver)
- cost = 1
- route = PATH_SIDE
-
/datum/heretic_knowledge/spell/blood_siphon
name = "Blood Siphon"
desc = "Grants you Blood Siphon, a spell that drains a victim of blood and health, transferring it to you. \
Also has a chance to transfer wounds from you to the victim."
gain_text = "\"No matter the man, we bleed all the same.\" That's what the Marshal told me."
next_knowledge = list(
- /datum/heretic_knowledge/summon/stalker,
- /datum/heretic_knowledge/spell/voidpull,
+ /datum/heretic_knowledge/spell/void_phase,
+ /datum/heretic_knowledge/summon/raw_prophet,
)
spell_to_add = /obj/effect/proc_holder/spell/pointed/blood_siphon
cost = 1
route = PATH_SIDE
+
+/datum/heretic_knowledge/spell/cleave
+ name = "Blood Cleave"
+ desc = "Grants you Cleave, an area-of-effect targeted spell \
+ that causes heavy bleeding and blood loss to anyone afflicted."
+ gain_text = "At first I didn't understand these instruments of war, but the Priest \
+ told me to use them regardless. Soon, he said, I would know them well."
+ next_knowledge = list(
+ /datum/heretic_knowledge/summon/stalker,
+ /datum/heretic_knowledge/spell/void_pull,
+ )
+ spell_to_add = /obj/effect/proc_holder/spell/pointed/cleave
+ cost = 1
+ route = PATH_SIDE
diff --git a/code/modules/antagonists/heretic/knowledge/side_rust_ash.dm b/code/modules/antagonists/heretic/knowledge/side_rust_ash.dm
index 1d5b576f217..d48b382a704 100644
--- a/code/modules/antagonists/heretic/knowledge/side_rust_ash.dm
+++ b/code/modules/antagonists/heretic/knowledge/side_rust_ash.dm
@@ -46,16 +46,29 @@
chosen_mob.remove_status_effect(/datum/status_effect/corrosion_curse)
to_chat(chosen_mob, span_notice("You start to feel better."))
-/datum/heretic_knowledge/spell/cleave
- name = "Blood Cleave"
- desc = "Grants you Cleave, an area-of-effect targeted spell \
- that causes heavy bleeding and blood loss to anyone afflicted."
- gain_text = "At first I didn't understand these instruments of war, but the Priest \
- told me to use them regardless. Soon, he said, I would know them well."
+/datum/heretic_knowledge/summon/rusty
+ name = "Rusted Ritual"
+ desc = "Allows you to transmute a pool of vomit, a book, and a head into a Rust Walker. \
+ Rust Walkers excel at spreading rust and are moderately strong in combat."
+ gain_text = "I combined my principle of hunger with my desire for corruption. The Marshal knew my name, and the Rusted Hills echoed out."
next_knowledge = list(
/datum/heretic_knowledge/spell/entropic_plume,
/datum/heretic_knowledge/spell/flame_birth,
)
- spell_to_add = /obj/effect/proc_holder/spell/pointed/cleave
+ required_atoms = list(
+ /obj/effect/decal/cleanable/vomit = 1,
+ /obj/item/book = 1,
+ /obj/item/bodypart/head = 1,
+ )
+ mob_to_summon = /mob/living/simple_animal/hostile/heretic_summon/rust_spirit
cost = 1
route = PATH_SIDE
+
+/datum/heretic_knowledge/summon/rusty/cleanup_atoms(list/selected_atoms)
+ var/obj/item/bodypart/head/ritual_head = locate() in selected_atoms
+ if(!ritual_head)
+ CRASH("[type] required a head bodypart, yet did not have one in selected_atoms when it reached cleanup_atoms.")
+
+ // Spill out any brains or stuff before we delete it.
+ ritual_head.drop_organs()
+ return ..()
diff --git a/code/modules/antagonists/heretic/knowledge/side_void_blade.dm b/code/modules/antagonists/heretic/knowledge/side_void_blade.dm
new file mode 100644
index 00000000000..78963f1aae3
--- /dev/null
+++ b/code/modules/antagonists/heretic/knowledge/side_void_blade.dm
@@ -0,0 +1,174 @@
+// Sidepaths for knowledge between Void and Blade.
+
+/// The max health given to Shattered Risen
+#define RISEN_MAX_HEALTH 125
+
+/datum/heretic_knowledge/limited_amount/risen_corpse
+ name = "Shattered Ritual"
+ desc = "Allows you to transmute a corpse with a soul, a pair of latex or nitrile gloves, and \
+ and any exosuit clothing (such as armor) to create a Shattered Risen. \
+ Shattered Risen are strong ghouls that have 125 health, but cannot hold items, \
+ instead having two brutal weapons for hands. You can only create one at a time."
+ gain_text = "I witnessed a cold, rending force drag this corpse back to near-life. \
+ When it moves, it crunches like broken glass. Its hands are no longer recognizable as human - \
+ each clenched fist contains a brutal nest of sharp bone-shards instead."
+ next_knowledge = list(
+ /datum/heretic_knowledge/cold_snap,
+ /datum/heretic_knowledge/blade_dance,
+ )
+ required_atoms = list(
+ /obj/item/clothing/suit = 1,
+ /obj/item/clothing/gloves/color/latex = 1,
+ )
+ limit = 1
+ cost = 1
+ route = PATH_SIDE
+
+/datum/heretic_knowledge/limited_amount/risen_corpse/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc)
+ . = ..()
+ if(!.)
+ return FALSE
+
+ for(var/mob/living/carbon/human/body in atoms)
+ if(body.stat != DEAD || !IS_VALID_GHOUL_MOB(body) || HAS_TRAIT(body, TRAIT_HUSK))
+ continue
+
+ if(body.mind?.get_ghost(ghosts_with_clients = TRUE))
+ selected_atoms += body
+ return TRUE
+
+ loc.balloon_alert(user, "ritual failed, no valid body!")
+ return FALSE
+
+/datum/heretic_knowledge/limited_amount/risen_corpse/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc)
+ var/mob/living/carbon/human/soon_to_be_ghoul = locate() in selected_atoms
+ if(QDELETED(soon_to_be_ghoul)) // No body? No ritual
+ stack_trace("[type] reached on_finished_recipe without a human in selected_atoms to make a ghoul out of.")
+ loc.balloon_alert(user, "ritual failed, no valid body!")
+ return FALSE
+
+ soon_to_be_ghoul.grab_ghost()
+ if(!soon_to_be_ghoul.mind || !soon_to_be_ghoul.client)
+ stack_trace("[type] reached on_finished_recipe without a minded / cliented human in selected_atoms to make a ghoul out of.")
+ loc.balloon_alert(user, "ritual failed, no valid body!")
+ return FALSE
+
+ selected_atoms -= soon_to_be_ghoul
+ make_risen(user, soon_to_be_ghoul)
+
+/// Make [victim] into a shattered risen ghoul.
+/datum/heretic_knowledge/limited_amount/risen_corpse/proc/make_risen(mob/living/user, mob/living/carbon/human/victim)
+ log_game("[key_name(user)] created a shattered risen out of [key_name(victim)].")
+ message_admins("[ADMIN_LOOKUPFLW(user)] shattered risen out of [ADMIN_LOOKUPFLW(victim)].")
+
+ victim.apply_status_effect(
+ /datum/status_effect/ghoul,
+ RISEN_MAX_HEALTH,
+ user.mind,
+ CALLBACK(src, .proc/apply_to_risen),
+ CALLBACK(src, .proc/remove_from_risen),
+ )
+
+/// Callback for the ghoul status effect - what effects are applied to the ghoul.
+/datum/heretic_knowledge/limited_amount/risen_corpse/proc/apply_to_risen(mob/living/risen)
+ LAZYADD(created_items, WEAKREF(risen))
+
+ for(var/obj/item/held as anything in risen.held_items)
+ if(istype(held))
+ risen.dropItemToGround(held)
+
+ risen.put_in_hands(new /obj/item/risen_hand(), del_on_fail = TRUE)
+
+/// Callback for the ghoul status effect - cleaning up effects after the ghoul status is removed.
+/datum/heretic_knowledge/limited_amount/risen_corpse/proc/remove_from_risen(mob/living/risen)
+ LAZYREMOVE(created_items, WEAKREF(risen))
+
+ for(var/obj/item/risen_hand/hand in risen.held_items)
+ qdel(hand)
+
+#undef RISEN_MAX_HEALTH
+
+/// The "hand" "weapon" used by shattered risen
+/obj/item/risen_hand
+ name = "bone-shards"
+ desc = "What once appeared to be a normal human fist, now holds a maulled nest of sharp bone-shards."
+ icon = 'icons/effects/blood.dmi'
+ base_icon_state = "bloodhand"
+ color = "#001aff"
+ item_flags = ABSTRACT | DROPDEL | HAND_ITEM
+ resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
+ hitsound = SFX_SHATTER
+ force = 16
+ sharpness = SHARP_EDGED
+ wound_bonus = -30
+ bare_wound_bonus = 15
+
+/obj/item/risen_hand/Initialize(mapload)
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT)
+
+/obj/item/risen_hand/visual_equipped(mob/user, slot)
+ . = ..()
+
+ // Even hand indexes are right hands,
+ // Odd hand indexes are left hand
+ // ...But also, we swap it intentionally here,
+ // so right icon is shown on the left (Because hands)
+ if(user.get_held_index_of_item(src) % 2 == 1)
+ icon_state = "[base_icon_state]_right"
+ else
+ icon_state = "[base_icon_state]_left"
+
+/obj/item/risen_hand/pre_attack(atom/hit, mob/living/user, params)
+ . = ..()
+ if(.)
+ return
+
+ // If it's a structure or machine, we get a damage bonus (allowing us to break down doors)
+ if(isstructure(hit) || ismachinery(hit))
+ force = initial(force) * 1.5
+
+ // If it's another other item make sure we're at normal force
+ else
+ force = initial(force)
+
+/datum/heretic_knowledge/rune_carver
+ name = "Carving Knife"
+ desc = "Allows you to transmute a knife, a shard of glass, and a piece of paper to create a Carving Knife. \
+ The Carving Knife allows you to etch difficult to see traps that trigger on heathens who walk overhead. \
+ Also makes for a handy throwing weapon."
+ gain_text = "Etched, carved... eternal. There is power hidden in everything. I can unveil it! \
+ I can carve the monolith to reveal the chains!"
+ next_knowledge = list(
+ /datum/heretic_knowledge/spell/void_phase,
+ /datum/heretic_knowledge/duel_stance,
+ )
+ required_atoms = list(
+ /obj/item/knife = 1,
+ /obj/item/shard = 1,
+ /obj/item/paper = 1,
+ )
+ result_atoms = list(/obj/item/melee/rune_carver)
+ cost = 1
+ route = PATH_SIDE
+
+/datum/heretic_knowledge/summon/maid_in_mirror
+ name = "Maid in the Mirror"
+ desc = "Allows you to transmute five sheets of titanium, a flash, a suit of armor, and a pair of lungs \
+ to create a Maid in the Mirror. Maid in the Mirrors are decent combatants that can become incorporeal by \
+ phasing in and out of the mirror realm, serving as powerful scouts and ambushers."
+ gain_text = "Within each reflection, lies a gateway into an unimaginable world of colors never seen and \
+ people never met. The ascent is glass, and the walls are knives. Each step is blood, if you do not have a guide."
+ next_knowledge = list(
+ /datum/heretic_knowledge/spell/void_pull,
+ /datum/heretic_knowledge/spell/furious_steel,
+ )
+ required_atoms = list(
+ /obj/item/stack/sheet/mineral/titanium = 5,
+ /obj/item/clothing/suit/armor = 1,
+ /obj/item/assembly/flash = 1,
+ /obj/item/organ/lungs = 1,
+ )
+ cost = 1
+ route = PATH_SIDE
+ mob_to_summon = /mob/living/simple_animal/hostile/heretic_summon/maid_in_the_mirror
diff --git a/code/modules/antagonists/heretic/knowledge/side_void_rust.dm b/code/modules/antagonists/heretic/knowledge/side_void_rust.dm
deleted file mode 100644
index 94c7c9e926f..00000000000
--- a/code/modules/antagonists/heretic/knowledge/side_void_rust.dm
+++ /dev/null
@@ -1,64 +0,0 @@
-// Sidepaths for knowledge between Void and Rust.
-
-/datum/heretic_knowledge/armor
- name = "Armorer's Ritual"
- desc = "Allows you to transmute a table and a gas mask to create Eldritch Armor. \
- Eldritch Armor provides great protection while also acting as a focus when hooded."
- gain_text = "The Rusted Hills welcomed the Blacksmith in their generosity. And the Blacksmith \
- returned their generosity in kind."
- next_knowledge = list(
- /datum/heretic_knowledge/rust_regen,
- /datum/heretic_knowledge/cold_snap,
- )
- required_atoms = list(
- /obj/structure/table = 1,
- /obj/item/clothing/mask/gas = 1,
- )
- result_atoms = list(/obj/item/clothing/suit/hooded/cultrobes/eldritch)
- cost = 1
- route = PATH_SIDE
-
-/datum/heretic_knowledge/crucible
- name = "Mawed Crucible"
- desc = "Allows you to transmute a portable water tank and a table to create a Mawed Crucible. \
- The Mawed Crubile can brew powerful potions for combat and utility, but must be fed bodyparts and organs between uses."
- gain_text = "This is pure agony. I wasn't able to summon the figure of the Aristocrat, \
- but with the Priest's attention I stumbled upon a different recipe..."
- next_knowledge = list(
- /datum/heretic_knowledge/spell/void_phase,
- /datum/heretic_knowledge/spell/area_conversion,
- )
- required_atoms = list(
- /obj/structure/reagent_dispensers/watertank = 1,
- /obj/structure/table = 1,
- )
- result_atoms = list(/obj/structure/destructible/eldritch_crucible)
- cost = 1
- route = PATH_SIDE
-
-/datum/heretic_knowledge/summon/rusty
- name = "Rusted Ritual"
- desc = "Allows you to transmute a pool of vomit, a book, and a head into a Rust Walker. \
- Rust Walkers excel at spreading rust and are moderately strong in combat."
- gain_text = "I combined my principle of hunger with my desire for corruption. The Marshal knew my name, and the Rusted Hills echoed out."
- next_knowledge = list(
- /datum/heretic_knowledge/spell/voidpull,
- /datum/heretic_knowledge/spell/entropic_plume,
- )
- required_atoms = list(
- /obj/effect/decal/cleanable/vomit = 1,
- /obj/item/book = 1,
- /obj/item/bodypart/head = 1,
- )
- mob_to_summon = /mob/living/simple_animal/hostile/heretic_summon/rust_spirit
- cost = 1
- route = PATH_SIDE
-
-/datum/heretic_knowledge/summon/rusty/cleanup_atoms(list/selected_atoms)
- var/obj/item/bodypart/head/ritual_head = locate() in selected_atoms
- if(!ritual_head)
- CRASH("[type] required a head bodypart, yet did not have one in selected_atoms when it reached cleanup_atoms.")
-
- // Spill out any brains or stuff before we delete it.
- ritual_head.drop_organs()
- return ..()
diff --git a/code/modules/antagonists/heretic/knowledge/starting_lore.dm b/code/modules/antagonists/heretic/knowledge/starting_lore.dm
index 8c1a8e80494..4bdc0450401 100644
--- a/code/modules/antagonists/heretic/knowledge/starting_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/starting_lore.dm
@@ -21,16 +21,14 @@ GLOBAL_LIST_INIT(heretic_start_knowledge, initialize_starting_knowledge())
desc = "Starts your journey into the Mansus. \
Grants you the Mansus Grasp, a powerful and upgradable \
disabling spell that can be cast regardless of having a focus."
- next_knowledge = list(
- /datum/heretic_knowledge/limited_amount/base_rust,
- /datum/heretic_knowledge/limited_amount/base_ash,
- /datum/heretic_knowledge/limited_amount/base_flesh,
- /datum/heretic_knowledge/limited_amount/base_void,
- )
spell_to_add = /obj/effect/proc_holder/spell/targeted/touch/mansus_grasp
cost = 0
route = PATH_START
+/datum/heretic_knowledge/spell/basic/New()
+ . = ..()
+ next_knowledge = subtypesof(/datum/heretic_knowledge/limited_amount/starting)
+
/**
* The Living Heart heretic knowledge.
*
diff --git a/code/modules/antagonists/heretic/knowledge/void_lore.dm b/code/modules/antagonists/heretic/knowledge/void_lore.dm
index 4e5b31ae2ae..d7542b55b02 100644
--- a/code/modules/antagonists/heretic/knowledge/void_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/void_lore.dm
@@ -8,23 +8,24 @@
* Aristocrat's Way
* > Sidepaths:
* Void Cloak
- * Armorer's Ritual
+ * Shattered Ritual
*
* Mark of Void
+ * Ritual of Knowledge
* Void Phase
* > Sidepaths:
* Carving Knife
- * Mawed Crucible
+ * Blood Siphon
*
* Seeking blade
* Void Pull
* > Sidepaths:
- * Rusted Ritual
- * Blood Siphon
+ * Cleave
+ * Maid in the Mirror
*
* Waltz at the End of Time
*/
-/datum/heretic_knowledge/limited_amount/base_void
+/datum/heretic_knowledge/limited_amount/starting/base_void
name = "Glimmer of Winter"
desc = "Opens up the path of void to you. \
Allows you to transmute a knife in sub-zero temperatures into a Void Blade. \
@@ -32,27 +33,16 @@
gain_text = "I feel a shimmer in the air, the air around me gets colder. \
I start to realize the emptiness of existance. Something's watching me."
next_knowledge = list(/datum/heretic_knowledge/void_grasp)
- banned_knowledge = list(
- /datum/heretic_knowledge/limited_amount/base_ash,
- /datum/heretic_knowledge/limited_amount/base_flesh,
- /datum/heretic_knowledge/limited_amount/base_rust,
- /datum/heretic_knowledge/final/ash_final,
- /datum/heretic_knowledge/final/flesh_final,
- /datum/heretic_knowledge/final/rust_final,
- )
required_atoms = list(/obj/item/knife = 1)
result_atoms = list(/obj/item/melee/sickly_blade/void)
- limit = 2
- cost = 1
- priority = MAX_KNOWLEDGE_PRIORITY - 5
route = PATH_VOID
-/datum/heretic_knowledge/limited_amount/base_void/on_research(mob/user)
+/datum/heretic_knowledge/limited_amount/starting/base_void/on_research(mob/user)
. = ..()
var/datum/antagonist/heretic/our_heretic = IS_HERETIC(user)
our_heretic.heretic_path = route
-/datum/heretic_knowledge/limited_amount/base_void/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc)
+/datum/heretic_knowledge/limited_amount/starting/base_void/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc)
if(!isopenturf(loc))
loc.balloon_alert(user, "ritual failed, invalid location!")
return FALSE
@@ -66,7 +56,7 @@
/datum/heretic_knowledge/void_grasp
name = "Grasp of Void"
- desc = "Your Masus Grasp will temporarily mute and chill the victim."
+ desc = "Your Mansus Grasp will temporarily mute and chill the victim."
gain_text = "I saw the cold watcher who observes me. The chill mounts within me. \
They are quiet. This isn't the end of the mystery."
next_knowledge = list(/datum/heretic_knowledge/cold_snap)
@@ -98,10 +88,10 @@
gain_text = "I found a thread of cold breath. It lead me to a strange shrine, all made of crystals. \
Translucent and white, a depiction of a nobleman stood before me."
next_knowledge = list(
- /datum/heretic_knowledge/void_mark,
+ /datum/heretic_knowledge/mark/void_mark,
/datum/heretic_knowledge/codex_cicatrix,
/datum/heretic_knowledge/void_cloak,
- /datum/heretic_knowledge/armor,
+ /datum/heretic_knowledge/limited_amount/risen_corpse,
)
cost = 1
route = PATH_VOID
@@ -114,49 +104,18 @@
REMOVE_TRAIT(user, TRAIT_RESISTCOLD, type)
REMOVE_TRAIT(user, TRAIT_NOBREATH, type)
-/datum/heretic_knowledge/void_mark
+/datum/heretic_knowledge/mark/void_mark
name = "Mark of Void"
desc = "Your Mansus Grasp now applies the Mark of Void. The mark is triggered from an attack with your Void Blade. \
When triggered, silences the victim and lowers their body temperature significantly."
gain_text = "A gust of wind? A shimmer in the air? The presence is overwhelming, \
my senses began to betray me. My mind is my own enemy."
next_knowledge = list(/datum/heretic_knowledge/knowledge_ritual/void)
- banned_knowledge = list(
- /datum/heretic_knowledge/rust_mark,
- /datum/heretic_knowledge/ash_mark,
- /datum/heretic_knowledge/flesh_mark,
- )
- cost = 2
route = PATH_VOID
-
-/datum/heretic_knowledge/void_mark/on_gain(mob/user)
- RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, .proc/on_mansus_grasp)
- RegisterSignal(user, COMSIG_HERETIC_BLADE_ATTACK, .proc/on_eldritch_blade)
-
-/datum/heretic_knowledge/void_mark/on_lose(mob/user)
- UnregisterSignal(user, list(COMSIG_HERETIC_MANSUS_GRASP_ATTACK, COMSIG_HERETIC_BLADE_ATTACK))
-
-/datum/heretic_knowledge/void_mark/proc/on_mansus_grasp(mob/living/source, mob/living/target)
- SIGNAL_HANDLER
-
- target.apply_status_effect(/datum/status_effect/eldritch/void)
-
-/datum/heretic_knowledge/void_mark/proc/on_eldritch_blade(mob/living/user, mob/living/target)
- SIGNAL_HANDLER
-
- var/datum/status_effect/eldritch/mark = target.has_status_effect(/datum/status_effect/eldritch)
- if(!istype(mark))
- return
-
- mark.on_effect()
+ mark_type = /datum/status_effect/eldritch/void
/datum/heretic_knowledge/knowledge_ritual/void
next_knowledge = list(/datum/heretic_knowledge/spell/void_phase)
- banned_knowledge = list(
- /datum/heretic_knowledge/knowledge_ritual/ash,
- /datum/heretic_knowledge/knowledge_ritual/rust,
- /datum/heretic_knowledge/knowledge_ritual/flesh,
- )
route = PATH_VOID
/datum/heretic_knowledge/spell/void_phase
@@ -166,59 +125,43 @@
gain_text = "The entity calls themself the Aristocrat. They effortlessly walk through air like\
nothing leaving a harsh, cold breeze in their wake. They disappear, and I am left in the snow."
next_knowledge = list(
- /datum/heretic_knowledge/void_blade_upgrade,
+ /datum/heretic_knowledge/blade_upgrade/void,
/datum/heretic_knowledge/reroll_targets,
+ /datum/heretic_knowledge/spell/blood_siphon,
/datum/heretic_knowledge/rune_carver,
- /datum/heretic_knowledge/crucible,
)
spell_to_add = /obj/effect/proc_holder/spell/pointed/void_phase
cost = 1
route = PATH_VOID
-/datum/heretic_knowledge/void_blade_upgrade
+/datum/heretic_knowledge/blade_upgrade/void
name = "Seeking blade"
desc = "You can now attack distant marked targets with your Void Blade, teleporting directly next to them."
gain_text = "Fleeting memories, fleeting feet. I mark my way with frozen blood upon the snow. Covered and forgotten."
- next_knowledge = list(/datum/heretic_knowledge/spell/voidpull)
- banned_knowledge = list(
- /datum/heretic_knowledge/ash_blade_upgrade,
- /datum/heretic_knowledge/flesh_blade_upgrade,
- /datum/heretic_knowledge/rust_blade_upgrade,
- )
- cost = 2
+ next_knowledge = list(/datum/heretic_knowledge/spell/void_pull)
route = PATH_VOID
-
-/datum/heretic_knowledge/void_blade_upgrade/on_gain(mob/user)
- RegisterSignal(user, COMSIG_HERETIC_RANGED_BLADE_ATTACK, .proc/on_ranged_eldritch_blade)
-
-/datum/heretic_knowledge/void_blade_upgrade/on_lose(mob/user)
- UnregisterSignal(user, COMSIG_HERETIC_RANGED_BLADE_ATTACK)
-
-/datum/heretic_knowledge/void_blade_upgrade/proc/on_ranged_eldritch_blade(mob/living/user, mob/living/target)
- SIGNAL_HANDLER
-
+/datum/heretic_knowledge/blade_upgrade/void/do_ranged_effects(mob/living/user, mob/living/target, obj/item/melee/sickly_blade/blade)
if(!target.has_status_effect(/datum/status_effect/eldritch))
return
var/dir = angle2dir(dir2angle(get_dir(user, target)) + 180)
user.forceMove(get_step(target, dir))
- INVOKE_ASYNC(src, .proc/follow_up_attack, user, target)
+ INVOKE_ASYNC(src, .proc/follow_up_attack, user, target, blade)
-/datum/heretic_knowledge/void_blade_upgrade/proc/follow_up_attack(mob/living/user, mob/living/target)
- var/obj/item/melee/sickly_blade/blade = user.get_active_held_item()
- blade?.melee_attack_chain(user, target)
+/datum/heretic_knowledge/blade_upgrade/void/proc/follow_up_attack(mob/living/user, mob/living/target, obj/item/melee/sickly_blade/blade)
+ blade.melee_attack_chain(user, target)
-/datum/heretic_knowledge/spell/voidpull
+/datum/heretic_knowledge/spell/void_pull
name = "Void Pull"
desc = "Grants you Void Pull, a spell that pulls all nearby heathens towards you, stunning them briefly."
gain_text = "All is fleeting, but what else stays? I'm close to ending what was started. \
The Aristocrat reveals themself to me again. They tell me I am late. Their pull is immense, I cannot turn back."
next_knowledge = list(
/datum/heretic_knowledge/final/void_final,
- /datum/heretic_knowledge/spell/blood_siphon,
- /datum/heretic_knowledge/summon/rusty
+ /datum/heretic_knowledge/spell/cleave,
+ /datum/heretic_knowledge/summon/maid_in_mirror,
)
spell_to_add = /obj/effect/proc_holder/spell/targeted/void_pull
cost = 1
diff --git a/code/modules/antagonists/heretic/magic/furious_steel.dm b/code/modules/antagonists/heretic/magic/furious_steel.dm
new file mode 100644
index 00000000000..8f74329239c
--- /dev/null
+++ b/code/modules/antagonists/heretic/magic/furious_steel.dm
@@ -0,0 +1,98 @@
+/obj/effect/proc_holder/spell/aimed/furious_steel
+ name = "Furious Steel"
+ desc = "Summon three silver blades which orbit you. \
+ While orbiting you, these blades will protect you from from attacks, but will be consumed on use. \
+ Additionally, you can click to fire the blades at a target, dealing damage and causing bleeding."
+ action_icon = 'icons/mob/actions/actions_ecult.dmi'
+ action_icon_state = "furious_steel0"
+ action_background_icon_state = "bg_ecult"
+ base_icon_state = "furious_steel"
+ invocation = "F'LSH'NG S'LV'R!"
+ invocation_type = INVOCATION_SHOUT
+ school = SCHOOL_FORBIDDEN
+ clothes_req = FALSE
+ charge_max = 30 SECONDS
+ range = 20
+ projectile_amount = 3
+ projectiles_per_fire = 1
+ projectile_type = /obj/projectile/floating_blade
+ sound = 'sound/weapons/guillotine.ogg'
+ active_msg = "You summon forth three blades of furious silver."
+ deactive_msg = "You conceal the blades of furious silver."
+ /// A ref to the status effect surrounding our heretic on activation.
+ var/datum/status_effect/protective_blades/blade_effect
+
+/obj/effect/proc_holder/spell/aimed/furious_steel/Destroy()
+ QDEL_NULL(blade_effect)
+ return ..()
+
+/obj/effect/proc_holder/spell/aimed/furious_steel/on_activation(mob/user)
+ if(!isliving(user))
+ return
+ var/mob/living/living_user = user
+ // Aimed spells snowflake and activate without checking cast_check, very cool
+ var/datum/antagonist/heretic/our_heretic = IS_HERETIC(living_user)
+ if(our_heretic && !our_heretic.ascended && !HAS_TRAIT(living_user, TRAIT_ALLOW_HERETIC_CASTING))
+ user.balloon_alert(living_user, "you need a focus!")
+ return
+
+ . = ..()
+ blade_effect = living_user.apply_status_effect(/datum/status_effect/protective_blades, null, 3, 25, 0.66 SECONDS)
+ RegisterSignal(blade_effect, COMSIG_PARENT_QDELETING, .proc/on_status_effect_deleted)
+
+/obj/effect/proc_holder/spell/aimed/furious_steel/on_deactivation(mob/user)
+ . = ..()
+ QDEL_NULL(blade_effect)
+
+/obj/effect/proc_holder/spell/aimed/furious_steel/InterceptClickOn(mob/living/caller, params, atom/target)
+ if(get_dist(caller, target) <= 1) // Let the caster prioritize melee attacks over blade casts
+ return FALSE
+ return ..()
+
+/obj/effect/proc_holder/spell/aimed/furious_steel/cast(list/targets, mob/living/user)
+ if(isnull(blade_effect) || !length(blade_effect.blades))
+ return FALSE
+ return ..()
+
+/obj/effect/proc_holder/spell/aimed/furious_steel/ready_projectile(obj/projectile/to_launch, atom/target, mob/user, iteration)
+ . = ..()
+ to_launch.def_zone = check_zone(user.zone_selected)
+
+/obj/effect/proc_holder/spell/aimed/furious_steel/fire_projectile(mob/living/user, atom/target)
+ . = ..()
+ qdel(blade_effect.blades[1])
+
+/obj/effect/proc_holder/spell/aimed/furious_steel/proc/on_status_effect_deleted(datum/source)
+ SIGNAL_HANDLER
+
+ blade_effect = null
+ on_deactivation()
+
+/obj/projectile/floating_blade
+ name = "blade"
+ icon = 'icons/obj/kitchen.dmi'
+ icon_state = "knife"
+ speed = 2
+ damage = 25
+ armour_penetration = 100
+ sharpness = SHARP_EDGED
+ wound_bonus = 15
+ pass_flags = PASSTABLE | PASSFLAPS
+
+/obj/projectile/floating_blade/Initialize(mapload)
+ . = ..()
+ add_filter("knife", 2, list("type" = "outline", "color" = "#f8f8ff", "size" = 1))
+
+/obj/projectile/floating_blade/prehit_pierce(atom/hit)
+ if(isliving(hit) && isliving(firer))
+ var/mob/living/caster = firer
+ var/mob/living/victim = hit
+ if(caster == victim)
+ return PROJECTILE_PIERCE_PHASE
+
+ if(caster.mind)
+ var/datum/antagonist/heretic_monster/monster = victim.mind?.has_antag_datum(/datum/antagonist/heretic_monster)
+ if(monster?.master == caster.mind)
+ return PROJECTILE_PIERCE_PHASE
+
+ return ..()
diff --git a/code/modules/antagonists/heretic/magic/mansus_grasp.dm b/code/modules/antagonists/heretic/magic/mansus_grasp.dm
index 704a774f9ea..27a51b72e98 100644
--- a/code/modules/antagonists/heretic/magic/mansus_grasp.dm
+++ b/code/modules/antagonists/heretic/magic/mansus_grasp.dm
@@ -3,7 +3,7 @@
desc = "A touch spell that lets you channel the power of the Old Gods through your grip."
hand_path = /obj/item/melee/touch_attack/mansus_fist
school = SCHOOL_EVOCATION
- charge_max = 100
+ charge_max = 10 SECONDS
clothes_req = FALSE
action_icon = 'icons/mob/actions/actions_ecult.dmi'
action_icon_state = "mansus_grasp"
@@ -79,7 +79,7 @@
if(SEND_SIGNAL(heretic, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, hit) & COMPONENT_BLOCK_CHARGE_USE)
return FALSE
- hit.adjustBruteLoss(10)
+ hit.apply_damage(10, BRUTE, wound_bonus = CANT_WOUND)
if(iscarbon(hit))
var/mob/living/carbon/carbon_hit = hit
carbon_hit.adjust_timed_status_effect(4 SECONDS, /datum/status_effect/speech/slurring/heretic)
diff --git a/code/modules/antagonists/heretic/magic/mirror_walk.dm b/code/modules/antagonists/heretic/magic/mirror_walk.dm
new file mode 100644
index 00000000000..ed9fc96e5b7
--- /dev/null
+++ b/code/modules/antagonists/heretic/magic/mirror_walk.dm
@@ -0,0 +1,176 @@
+/// Macro to check if the passed mob is currently in jaunting "in the mirror".
+#define IS_MIRROR_PHASED(mob) istype(user.loc, /obj/effect/dummy/phased_mob/mirror_walk)
+
+/obj/effect/proc_holder/spell/targeted/mirror_walk
+ name = "Mirror Walk"
+ desc = "Allows you to traverse invisibly and freely across the station within the realm of the mirror. \
+ You can only enter and exit the realm of mirrors when nearby reflective surfaces and items, \
+ such as windows, mirrors, and reflective walls or equipment."
+ action_icon = 'icons/mob/actions/actions_minor_antag.dmi'
+ action_icon_state = "ninja_cloak"
+ action_background_icon_state = "bg_ecult"
+ charge_max = 6 SECONDS
+ cooldown_min = 0
+ clothes_req = FALSE
+ antimagic_flags = NONE
+ phase_allowed = TRUE
+ range = -1
+ include_user = TRUE
+ overlay = null
+
+ /// The time it takes to enter the mirror / phase out / enter jaunt.
+ var/phase_out_time = 1.5 SECONDS
+ /// The time it takes to exit a mirror / phase in / exit jaunt.
+ var/phase_in_time = 2 SECONDS
+ /// Static typecache of types that are counted as reflective.
+ var/static/list/special_reflective_surfaces = typecacheof(list(
+ /obj/structure/window,
+ /obj/structure/mirror,
+ ))
+
+/obj/effect/proc_holder/spell/targeted/mirror_walk/on_lose(mob/living/user)
+ if(IS_MIRROR_PHASED(user))
+ var/obj/effect/dummy/phased_mob/mirror_walk/phase = user.loc
+ phase.eject_user()
+ qdel(phase)
+
+/obj/effect/proc_holder/spell/targeted/mirror_walk/cast_check(skipcharge = FALSE, mob/user = usr)
+ . = ..()
+ if(!.)
+ return FALSE
+
+ var/we_are_phasing = IS_MIRROR_PHASED(user)
+ var/turf/user_turf = get_turf(user)
+ var/area/user_area = get_area(user)
+ if(!user_turf || !user_area)
+ return FALSE // nullspaced?
+
+ if(user_area.area_flags & NOTELEPORT)
+ to_chat(user, span_warning("An otherwordly force is preventing you from [we_are_phasing ? "exiting":"entering"] the mirror's realm here!"))
+ return FALSE
+
+ if(user_turf.turf_flags & NOJAUNT)
+ to_chat(user, span_warning("An otherwordly force is preventing you from [we_are_phasing ? "exiting":"entering"] the mirror's realm here!"))
+ return FALSE
+
+ return TRUE
+
+/obj/effect/proc_holder/spell/targeted/mirror_walk/cast(list/targets, mob/living/user = usr)
+ var/we_are_phasing = IS_MIRROR_PHASED(user)
+ var/turf/user_turf = get_turf(user)
+
+ if(!is_reflection_nearby(user_turf))
+ to_chat(user, span_warning("There are no reflective surfaces nearby to [we_are_phasing ? "exit":"enter"] the mirror's realm here!"))
+ return FALSE
+
+ if(user_turf.is_blocked_turf(exclude_mobs = TRUE))
+ to_chat(user, span_warning("Something is blocking you from [we_are_phasing ? "exiting":"entering"] the mirror's realm here!"))
+ return FALSE
+
+ // If our loc is a phased mob, we're currently jaunting so we should exit
+ if(we_are_phasing)
+ try_exit_phase(user)
+ return
+
+ // Otherwise try to enter like normal
+ try_enter_phase(user)
+
+/obj/effect/proc_holder/spell/targeted/mirror_walk/proc/try_exit_phase(mob/living/user)
+ var/obj/effect/dummy/phased_mob/mirror_walk/phase = user.loc
+ var/atom/nearby_reflection = is_reflection_nearby(phase)
+ if(!nearby_reflection)
+ to_chat(user, span_warning("There are no reflective surfaces nearby to exit from the mirror's realm!"))
+ return FALSE
+
+ var/turf/phase_turf = get_turf(phase)
+
+ // It would likely be a bad idea to teleport into an ai monitored area (ai sat)
+ var/area/phase_area = get_area(phase_turf)
+ if(istype(phase_area, /area/ai_monitored))
+ to_chat(user, span_warning("It's probably not a very wise idea to exit the mirror's realm here."))
+ return FALSE
+
+ nearby_reflection.Beam(phase_turf, icon_state = "light_beam", time = phase_in_time)
+ nearby_reflection.visible_message(span_warning("[nearby_reflection] begins to shimmer and shake slightly!"))
+ if(!do_after(user, phase_in_time, nearby_reflection))
+ return
+
+ playsound(get_turf(user), 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1)
+ user.visible_message(
+ span_boldwarning("[user] phases into reality before your very eyes!"),
+ span_notice("You jump out of the reflection coming off of [nearby_reflection], exiting the mirror's realm."),
+ )
+
+ // We can move around while phasing in,
+ // but we'll always end up where we started it.
+ phase.forceMove(phase_turf)
+ phase.eject_user()
+ qdel(phase)
+
+ // Chilly!
+ phase_turf.TakeTemperature(-20)
+
+/obj/effect/proc_holder/spell/targeted/mirror_walk/proc/try_enter_phase(mob/living/user)
+ var/atom/nearby_reflection = is_reflection_nearby(user)
+ if(!nearby_reflection)
+ to_chat(user, span_warning("There are no reflective surfaces nearby to enter the mirror's realm!"))
+ return
+
+ user.Beam(nearby_reflection, icon_state = "light_beam", time = phase_out_time)
+ nearby_reflection.visible_message(span_warning("[nearby_reflection] begins to shimmer and shake slightly!"))
+ if(!do_after(user, phase_out_time, nearby_reflection, IGNORE_USER_LOC_CHANGE|IGNORE_INCAPACITATED))
+ return
+
+ playsound(get_turf(user), 'sound/magic/ethereal_enter.ogg', 50, TRUE, -1)
+ user.visible_message(
+ span_boldwarning("[user] phases out of reality, vanishing before your very eyes!"),
+ span_notice("You jump into the reflection coming off of [nearby_reflection], entering the mirror's realm."),
+ )
+
+ user.SetAllImmobility(0)
+ user.setStaminaLoss(0)
+
+ var/obj/effect/dummy/phased_mob/mirror_walk/phase = new(get_turf(nearby_reflection))
+ user.forceMove(phase)
+
+/**
+ * Goes through all nearby atoms in sight of the
+ * passed caster and determines if they are "reflective"
+ * for the purpose of us being able to utilize it to enter or exit.
+ *
+ * Returns an object reference to a "reflective" object in view if one was found,
+ * or null if no object was found that was determined to be "reflective".
+ */
+/obj/effect/proc_holder/spell/targeted/mirror_walk/proc/is_reflection_nearby(atom/caster)
+ for(var/atom/thing as anything in view(2, caster))
+ if(isitem(thing))
+ var/obj/item/item_thing = thing
+ if(item_thing.IsReflect())
+ return thing
+
+ if(ishuman(thing))
+ var/mob/living/carbon/human/human_thing = thing
+ if(human_thing.check_reflect())
+ return thing
+
+ if(isturf(thing))
+ var/turf/turf_thing = thing
+ if(turf_thing.turf_flags & NOJAUNT)
+ continue
+ if(turf_thing.flags_ricochet & RICOCHET_SHINY)
+ return thing
+
+ if(is_type_in_typecache(thing, special_reflective_surfaces))
+ return thing
+
+ return null
+
+/obj/effect/dummy/phased_mob/mirror_walk
+ name = "reflection"
+
+/obj/effect/dummy/phased_mob/mirror_walk/proc/eject_user()
+ var/mob/living/jaunter = locate() in contents
+ if(QDELETED(jaunter))
+ CRASH("[type] called eject_user() without a mob/living within its contents.")
+
+ jaunter.forceMove(drop_location())
diff --git a/code/modules/antagonists/heretic/mobs/maid_in_mirror.dm b/code/modules/antagonists/heretic/mobs/maid_in_mirror.dm
new file mode 100644
index 00000000000..c2ef2536027
--- /dev/null
+++ b/code/modules/antagonists/heretic/mobs/maid_in_mirror.dm
@@ -0,0 +1,78 @@
+// A summon which floats around the station incorporeally, and can appear in any mirror
+/mob/living/simple_animal/hostile/heretic_summon/maid_in_the_mirror
+ name = "Maid in the Mirror"
+ real_name = "Maid in the Mirror"
+ desc = "A floating and flowing wisp of chilled air. Glancing at it causes it to shimmer slightly."
+ icon = 'icons/mob/mob.dmi'
+ icon_state = "stand"
+ icon_living = "stand" // Placeholder sprite
+ speak_emote = list("whispers")
+ movement_type = FLOATING
+ status_flags = CANSTUN | CANPUSH
+ attack_sound = SFX_SHATTER
+ maxHealth = 80
+ health = 80
+ melee_damage_lower = 12
+ melee_damage_upper = 16
+ sight = SEE_MOBS | SEE_OBJS | SEE_TURFS
+ deathmessage = "shatters and vanishes, releasing a gust of cold air."
+ loot = list(
+ /obj/item/shard,
+ /obj/effect/decal/cleanable/ash,
+ /obj/item/clothing/suit/armor,
+ /obj/item/organ/lungs,
+ )
+ spells_to_add = list(/obj/effect/proc_holder/spell/targeted/mirror_walk)
+
+ /// Whether we take damage when we're examined
+ var/weak_on_examine = TRUE
+ /// The cooldown after being examined that the same mob cannot trigger it again
+ var/recent_examine_damage_cooldown = 10 SECONDS
+ /// A list of REFs to people who recently examined us
+ var/list/recent_examiner_refs = list()
+
+/mob/living/simple_animal/hostile/heretic_summon/maid_in_the_mirror/death(gibbed)
+ var/turf/death_turf = get_turf(src)
+ death_turf.TakeTemperature(-40)
+ return ..()
+
+// Examining them will harm them, on a cooldown.
+/mob/living/simple_animal/hostile/heretic_summon/maid_in_the_mirror/examine(mob/user)
+ . = ..()
+ if(!weak_on_examine)
+ return
+
+ if(IS_HERETIC_OR_MONSTER(user) || user == src)
+ return
+
+ var/user_ref = REF(user)
+ if(user_ref in recent_examiner_refs)
+ return
+
+ // If we have health, we take some damage
+ if(health > (maxHealth * 0.125))
+ visible_message(
+ span_warning("[src] seems to fade in and out slightly."),
+ span_userdanger("[user]'s gaze pierces your every being!"),
+ )
+
+ recent_examiner_refs += user_ref
+ apply_damage(maxHealth * 0.1) // We take 10% of our health as damage upon being examined
+ playsound(src, 'sound/effects/ghost2.ogg', 40, TRUE)
+ addtimer(CALLBACK(src, .proc/clear_recent_examiner, user_ref), recent_examine_damage_cooldown)
+
+ // If we're examined on low enough health we die straight up
+ else
+ visible_message(
+ span_danger("[src] vanishes from existence!"),
+ span_userdanger("[user]'s gaze shatters your form, destroying you!"),
+ )
+
+ death()
+
+/mob/living/simple_animal/hostile/heretic_summon/maid_in_the_mirror/proc/clear_recent_examiner(mob_ref)
+ if(!(mob_ref in recent_examiner_refs))
+ return
+
+ recent_examiner_refs -= mob_ref
+ heal_overall_damage(5)
diff --git a/code/modules/religion/sparring/sparring_datum.dm b/code/modules/religion/sparring/sparring_datum.dm
index 391b8378e16..35d03b39720 100644
--- a/code/modules/religion/sparring/sparring_datum.dm
+++ b/code/modules/religion/sparring/sparring_datum.dm
@@ -40,7 +40,7 @@
//arena conditions
RegisterSignal(sparring, COMSIG_MOVABLE_MOVED, .proc/arena_violation)
//severe violations (insta violation win for other party) conditions
- RegisterSignal(sparring, COMSIG_MOVABLE_TELEPORTED, .proc/teleport_violation)
+ RegisterSignal(sparring, COMSIG_MOVABLE_POST_TELEPORT, .proc/teleport_violation)
//win conditions
RegisterSignal(sparring, COMSIG_MOB_STATCHANGE, .proc/check_for_victory)
//flub conditions
@@ -62,7 +62,7 @@
COMSIG_MOB_GRENADE_ARMED,
COMSIG_MOB_ITEM_ATTACK,
COMSIG_MOVABLE_MOVED,
- COMSIG_MOVABLE_TELEPORTED,
+ COMSIG_MOVABLE_POST_TELEPORT,
COMSIG_MOB_STATCHANGE,
COMSIG_PARENT_ATTACKBY,
COMSIG_ATOM_HULK_ATTACK,
diff --git a/code/modules/spells/spell_types/aimed.dm b/code/modules/spells/spell_types/aimed.dm
index 2d6d5b92356..06278b18698 100644
--- a/code/modules/spells/spell_types/aimed.dm
+++ b/code/modules/spells/spell_types/aimed.dm
@@ -15,30 +15,36 @@
var/mob/living/user = usr
if(!istype(user))
return
- var/msg
if(!can_cast(user))
- msg = span_warning("You can no longer cast [name]!")
- remove_ranged_ability(msg)
+ remove_ranged_ability(span_warning("You can no longer cast [name]!"))
return
+
if(active)
- msg = span_notice("[deactive_msg]")
- if(charge_type == "recharge")
- var/refund_percent = current_amount/projectile_amount
- charge_counter = charge_max * refund_percent
- start_recharge()
- remove_ranged_ability(msg)
on_deactivation(user)
else
- msg = span_notice("[active_msg] Left-click to shoot it at a target!")
- current_amount = projectile_amount
- add_ranged_ability(user, msg, TRUE)
on_activation(user)
+/**
+ * Activate the spell for user.
+ */
/obj/effect/proc_holder/spell/aimed/proc/on_activation(mob/user)
- return
+ SHOULD_CALL_PARENT(TRUE)
+ current_amount = projectile_amount
+ add_ranged_ability(user, span_notice("[active_msg] Left-click to shoot it at a target!"), TRUE)
+
+/**
+ * Deactivate the spell from user.
+ */
/obj/effect/proc_holder/spell/aimed/proc/on_deactivation(mob/user)
- return
+ SHOULD_CALL_PARENT(TRUE)
+
+ if(charge_type == "recharge")
+ var/refund_percent = current_amount / projectile_amount
+ charge_counter = charge_max * refund_percent
+ start_recharge()
+ remove_ranged_ability(span_notice("[deactive_msg]"))
+
/obj/effect/proc_holder/spell/aimed/update_icon()
if(!action)
@@ -156,6 +162,7 @@
ranged_clickcd_override = TRUE
/obj/effect/proc_holder/spell/aimed/spell_cards/on_activation(mob/M)
+ . = ..()
QDEL_NULL(lockon_component)
lockon_component = M.AddComponent(/datum/component/lockon_aiming, 5, GLOB.typecache_living, 1, null, CALLBACK(src, .proc/on_lockon_component))
@@ -170,6 +177,7 @@
M.face_atom(A)
/obj/effect/proc_holder/spell/aimed/spell_cards/on_deactivation(mob/M)
+ . = ..()
QDEL_NULL(lockon_component)
/obj/effect/proc_holder/spell/aimed/spell_cards/ready_projectile(obj/projectile/P, atom/target, mob/user, iteration)
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index cc7d2b5a265..bec2d6e7c89 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
index 82549dccf08..87f691cd49f 100644
Binary files a/icons/effects/eldritch.dmi and b/icons/effects/eldritch.dmi differ
diff --git a/icons/mob/actions/actions_ecult.dmi b/icons/mob/actions/actions_ecult.dmi
index 2d1b4cf9638..5c022fb79c8 100644
Binary files a/icons/mob/actions/actions_ecult.dmi and b/icons/mob/actions/actions_ecult.dmi differ
diff --git a/icons/mob/inhands/64x64_lefthand.dmi b/icons/mob/inhands/64x64_lefthand.dmi
index 5aa54e694c7..b01c4fbcf03 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 be49e1eb0eb..fe24c641f8e 100644
Binary files a/icons/mob/inhands/64x64_righthand.dmi and b/icons/mob/inhands/64x64_righthand.dmi differ
diff --git a/icons/obj/eldritch.dmi b/icons/obj/eldritch.dmi
index 38de4f4fb8b..84232b4ef82 100644
Binary files a/icons/obj/eldritch.dmi and b/icons/obj/eldritch.dmi differ
diff --git a/icons/ui_icons/achievements/achievements.dmi b/icons/ui_icons/achievements/achievements.dmi
index fb838d897ce..6affa6ba900 100644
Binary files a/icons/ui_icons/achievements/achievements.dmi and b/icons/ui_icons/achievements/achievements.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 6c1c288d5c1..89bc72706d4 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -2078,6 +2078,7 @@
#include "code\modules\antagonists\heretic\heretic_living_heart.dm"
#include "code\modules\antagonists\heretic\heretic_monsters.dm"
#include "code\modules\antagonists\heretic\influences.dm"
+#include "code\modules\antagonists\heretic\knife_effect.dm"
#include "code\modules\antagonists\heretic\rust_effect.dm"
#include "code\modules\antagonists\heretic\transmutation_rune.dm"
#include "code\modules\antagonists\heretic\items\eldritch_flask.dm"
@@ -2085,15 +2086,18 @@
#include "code\modules\antagonists\heretic\items\heretic_armor.dm"
#include "code\modules\antagonists\heretic\items\heretic_blades.dm"
#include "code\modules\antagonists\heretic\items\heretic_necks.dm"
+#include "code\modules\antagonists\heretic\items\hunter_rifle.dm"
#include "code\modules\antagonists\heretic\items\madness_mask.dm"
#include "code\modules\antagonists\heretic\knowledge\ash_lore.dm"
+#include "code\modules\antagonists\heretic\knowledge\blade_lore.dm"
#include "code\modules\antagonists\heretic\knowledge\flesh_lore.dm"
#include "code\modules\antagonists\heretic\knowledge\general_side.dm"
#include "code\modules\antagonists\heretic\knowledge\rust_lore.dm"
#include "code\modules\antagonists\heretic\knowledge\side_ash_flesh.dm"
+#include "code\modules\antagonists\heretic\knowledge\side_blade_rust.dm"
#include "code\modules\antagonists\heretic\knowledge\side_flesh_void.dm"
#include "code\modules\antagonists\heretic\knowledge\side_rust_ash.dm"
-#include "code\modules\antagonists\heretic\knowledge\side_void_rust.dm"
+#include "code\modules\antagonists\heretic\knowledge\side_void_blade.dm"
#include "code\modules\antagonists\heretic\knowledge\starting_lore.dm"
#include "code\modules\antagonists\heretic\knowledge\void_lore.dm"
#include "code\modules\antagonists\heretic\knowledge\sacrifice_knowledge\sacrifice_buff.dm"
@@ -2111,13 +2115,16 @@
#include "code\modules\antagonists\heretic\magic\eldritch_telepathy.dm"
#include "code\modules\antagonists\heretic\magic\expand_sight.dm"
#include "code\modules\antagonists\heretic\magic\flesh_ascension.dm"
+#include "code\modules\antagonists\heretic\magic\furious_steel.dm"
#include "code\modules\antagonists\heretic\magic\madness_touch.dm"
#include "code\modules\antagonists\heretic\magic\manse_link.dm"
#include "code\modules\antagonists\heretic\magic\mansus_grasp.dm"
+#include "code\modules\antagonists\heretic\magic\mirror_walk.dm"
#include "code\modules\antagonists\heretic\magic\nightwater_rebirth.dm"
#include "code\modules\antagonists\heretic\magic\rust_wave.dm"
#include "code\modules\antagonists\heretic\magic\void_phase.dm"
#include "code\modules\antagonists\heretic\magic\void_pull.dm"
+#include "code\modules\antagonists\heretic\mobs\maid_in_mirror.dm"
#include "code\modules\antagonists\heretic\structures\carving_knife.dm"
#include "code\modules\antagonists\heretic\structures\mawed_crucible.dm"
#include "code\modules\antagonists\highlander\highlander.dm"
diff --git a/tgui/packages/tgui/interfaces/AntagInfoHeretic.tsx b/tgui/packages/tgui/interfaces/AntagInfoHeretic.tsx
index 1948239fa63..29f3ea6c2ee 100644
--- a/tgui/packages/tgui/interfaces/AntagInfoHeretic.tsx
+++ b/tgui/packages/tgui/interfaces/AntagInfoHeretic.tsx
@@ -326,7 +326,7 @@ export const AntagInfoHeretic = (props, context) => {
return (
+ height={600}>