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