Adds a new heretic path: Path of the Blades (#65718)

This PR adds a new heretic path.

The Path of the Blade, a path focused on becoming a powerful one-on-one combatant with your blades.

Main path:

Cutting Edge: Allows them to transmute up to five knives. Knives are made with two bars of silver.
Grasp of the Blade: The Mansus Grasp will stun and deal additional damage to people lying down or facing away from you.
Dance of the Brand: Being attacked while wielding a blade will cause you to counter-attack whoever hit you.
Mark of the Blade: Marked targets will be unable to exit their current area. Triggering the mark will give you a floating knife which will block the next attack you receive.
Stance of the Scarred Duelist: Blood wounds inflicted upon you are reduced in severity greatly, and you gain immunity to dismemberment. Dropping below 50% health will grant you stun resistance and wound resistance.
Swift Blades: Wielding a heretic blade in both hands will attack with both at once. The second attack has a small damage penalty.
Furious Steel: A spell that grants you three orbiting blades. Each blade will block an attack upon you, and you can fire the blades at people for decent damage and bleeding.
Maelstrom of Silver: Ascension ritual, surrounds you in a whirlwind of eight blades which block attacks. These regenerate over time. Heretic blades deal massively increased damage and offer lifesteal, and you become immune to stuns and wounds.
Side paths:

Void / blade 1: Shattered Risen. A summon like the voiceless dead, shattered risen are tankier and deal immense damage with their fists, especially to structures, but cannot hold items.
Void / blade 3: Maid in the Mirror. A summon that can jaunt infinitely around the station. They can only enter and exit the jaunt around reflective surfaces, such as mirrors, windows, titanium walls, or reflective armor.
Rust / blade 3: Lionhunter's rifle. A three shot rifle. When fired at a non-living or close range target, it fires like a normal weapon, however clicking on distant targets will aim at them. The projectile shot at aimed targets deal massively increased damage and cause stuns / knockdowns. It will also track them and travel through walls.
Rust / blade 3+1: Additional ammo for the lionhunter's rifle.
Other side path changes:

Blood Siphon is now a t2 side path, between Flesh and Void.
Cleave has been moved to between Flesh and Void
Rusted Ritual has been moved to beween ash and rust
Also refactors heretic paths a great deal, making it much easier to add new heretic paths.
This commit is contained in:
MrMelbert
2022-04-21 19:49:18 +01:00
committed by GitHub
parent ab7a2df56c
commit aea1b5cf14
39 changed files with 1869 additions and 486 deletions
+1
View File
@@ -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!"
+7
View File
@@ -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
@@ -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
@@ -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..."
+15 -8
View File
@@ -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)
+129
View File
@@ -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
+142 -8
View File
@@ -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
@@ -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
@@ -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)
@@ -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."))
@@ -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 <b>activating it in-hand</b>.")
// 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..."
@@ -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
@@ -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))
@@ -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
@@ -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)
@@ -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
@@ -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,
)
@@ -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
@@ -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
@@ -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 ..()
@@ -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
@@ -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 ..()
@@ -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.
*
@@ -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
@@ -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 ..()
@@ -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)
@@ -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())
@@ -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)
@@ -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,
+22 -14
View File
@@ -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] <B>Left-click to shoot it at a target!</B>")
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] <B>Left-click to shoot it at a target!</B>"), 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)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 907 KiB

After

Width:  |  Height:  |  Size: 890 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 202 KiB

+8 -1
View File
@@ -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"
@@ -326,7 +326,7 @@ export const AntagInfoHeretic = (props, context) => {
return (
<Window
width={675}
height={575}>
height={600}>
<Window.Content
style={{
// 'font-family': 'Times New Roman',