diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index b9729451625..37798b5aa90 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -225,6 +225,8 @@
/tools/build/ @scriptis @stylemistake
/tools/tgs_scripts/ @Cyberboss @scriptis
+/code/modules/antagonists/heretic @Xander3359 @EnterTheJake
+
# Host Hell
/code/controllers/configuration/entries @scriptis
diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm
index 9a2390557d8..60d1212540c 100644
--- a/code/__DEFINES/antagonists.dm
+++ b/code/__DEFINES/antagonists.dm
@@ -101,15 +101,53 @@
#define HKT_NEXT "next"
#define HKT_BAN "ban"
#define HKT_DEPTH "depth"
+#define HKT_PURCHASED_DEPTH "purchased_depth"
#define HKT_ROUTE "route"
#define HKT_UI_BGR "ui_bgr"
+#define HKT_COST "cost"
+#define HKT_CATEGORY "category"
+/// Only present for already researched knowledge.
+#define HKT_INSTANCE "instance"
+/// unique identifier most commonly used for identifying what knowledge is researchable
+#define HKT_ID "id"
+#define BGR_SIDE "node_side"
+
+#define MAGIC_RESISTANCE_MOON (MAGIC_RESISTANCE|MAGIC_RESISTANCE_MIND)
/// 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
#define HERETIC_NO_LIVING_HEART 0
#define HERETIC_HAS_LIVING_HEART 1
+#define HERETIC_DRAFT_TIER_MAX 5
+
+/// The default drain speed for heretic rift's, anything below this will be considered a fast drain, and be very noticeable and cause a overlay
+#define HERETIC_RIFT_DEFAULT_DRAIN_SPEED 10 SECONDS
+
+/// Sources of knowledge purchased for heretics, used for positioning in the UI
+#define HERETIC_KNOWLEDGE_TREE "tree"
+#define HERETIC_KNOWLEDGE_SHOP "shop"
+#define HERETIC_KNOWLEDGE_DRAFT "draft"
+#define HERETIC_KNOWLEDGE_START "start"
+
+/// defines for the depths of the heretic knowledge tree nodes
+#define HKT_DEPTH_START 2
+#define HKT_DEPTH_TIER_1 3
+#define HKT_DEPTH_DRAFT_1 4
+#define HKT_DEPTH_TIER_2 5
+#define HKT_DEPTH_DRAFT_2 6
+#define HKT_DEPTH_ROBES 7
+#define HKT_DEPTH_TIER_3 8
+#define HKT_DEPTH_DRAFT_3 9
+#define HKT_DEPTH_ARMOR 10
+#define HKT_DEPTH_TIER_4 11
+#define HKT_DEPTH_DRAFT_4 12
+#define HKT_DEPTH_ASCENSION 13
+
+#define HERETIC_CAN_ASCEND "can_ascend"
+
+
/// A define used in ritual priority for heretics.
#define MAX_KNOWLEDGE_PRIORITY 100
diff --git a/code/__DEFINES/dcs/declarations.dm b/code/__DEFINES/dcs/declarations.dm
index f5be9fbc6d3..d6517601a35 100644
--- a/code/__DEFINES/dcs/declarations.dm
+++ b/code/__DEFINES/dcs/declarations.dm
@@ -68,6 +68,7 @@
#define CALTROP_SILENT (1 << 2)
#define CALTROP_NOSTUN (1 << 3)
#define CALTROP_NOCRAWL (1 << 4)
+#define CALTROP_ANTS (1 << 5)
//Ingredient type in datum/component/ingredients_holder
#define CUSTOM_INGREDIENT_TYPE_EDIBLE 1
diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_attack.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_attack.dm
index bf1f2eff3ab..0219f795c32 100644
--- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_attack.dm
+++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_attack.dm
@@ -72,3 +72,10 @@
/// Called on the atom being hit, from /datum/component/anti_magic/on_attack() : (obj/item/weapon, mob/user, antimagic_flags)
#define COMSIG_ATOM_HOLYATTACK "atom_holyattacked"
+
+/// Called from [/mob/living/proc/send_item_attack_message()]: (obj/item/weapon, /mob/living/victim, mob/living/attacker)
+#define COMSIG_SEND_ITEM_ATTACK_MESSAGE_OBJECT "send_item_attack_message_object"
+/// Called from [/mob/living/proc/send_item_attack_message()]: (mob/living/victim, obj/item/weapon, mob/living/user)
+#define COMSIG_SEND_ITEM_ATTACK_MESSAGE_CARBON "send_item_attack_message_carbon"
+ /// Return value if the hitby messages are changed.
+ #define SIGNAL_MESSAGE_MODIFIED (1<<0)
diff --git a/code/__DEFINES/dcs/signals/signals_food.dm b/code/__DEFINES/dcs/signals/signals_food.dm
index 1ab582428b7..0041076802c 100644
--- a/code/__DEFINES/dcs/signals/signals_food.dm
+++ b/code/__DEFINES/dcs/signals/signals_food.dm
@@ -2,6 +2,8 @@
//Food
// Eating stuff
+/// From datum/component/edible/proc/TakeBite: (atom/owner)
+#define COMSIG_LIVING_EAT_FOOD "food_bit"
/// From datum/component/edible/proc/TakeBite: (mob/living/eater, mob/feeder, bitecount, bitesize)
#define COMSIG_FOOD_EATEN "food_eaten"
#define DESTROY_FOOD (1<<0)
diff --git a/code/__DEFINES/dcs/signals/signals_heretic.dm b/code/__DEFINES/dcs/signals/signals_heretic.dm
index fb76b312316..14fe7a51e53 100644
--- a/code/__DEFINES/dcs/signals/signals_heretic.dm
+++ b/code/__DEFINES/dcs/signals/signals_heretic.dm
@@ -16,3 +16,11 @@
/// For [/datum/status_effect/protective_blades] to signal when it is triggered
#define COMSIG_BLADE_BARRIER_TRIGGERED "blade_barrier_triggered"
+
+/// at the end of determine_drafted_knowledge
+#define COMSIG_HERETIC_SHOP_SETUP "heretic_shop_finished"
+
+/// called on the antagonist datum, upgrades the passive to level 2
+#define COMSIG_HERETIC_PASSIVE_UPGRADE_FIRST "heretic_passive_upgrade_first"
+/// called on the antagonist datum, upgrades the passive to level 3
+#define COMSIG_HERETIC_PASSIVE_UPGRADE_FINAL "heretic_passive_upgrade_final"
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm
index d94e8336699..e57699a34a9 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm
@@ -16,6 +16,8 @@
#define COMSIG_ORGAN_SURGICALLY_INSERTED "organ_surgically_inserted"
/// Called when an organ finishes inserting into a bodypart (obj/item/bodypart/limb, movement_flags)
#define COMSIG_ORGAN_BODYPART_INSERTED "organ_bodypart_inserted"
+/// Called when a organ's damage is adjusted apply_organ_damage (damage_amount, maximum, required_organ_flag)
+#define COMSIG_ORGAN_ADJUST_DAMAGE "organ_adjust_damage"
///Called when movement intent is toggled.
#define COMSIG_MOVE_INTENT_TOGGLED "move_intent_toggled"
diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm
index da898958e42..8c221d28d1d 100644
--- a/code/__DEFINES/dcs/signals/signals_object.dm
+++ b/code/__DEFINES/dcs/signals/signals_object.dm
@@ -148,6 +148,8 @@
#define COMSIG_ITEM_DRIED "item_dried"
///from base of obj/item/dropped(): (mob/user)
#define COMSIG_ITEM_DROPPED "item_drop"
+///a mob has just dropped an item
+#define COMSIG_MOB_DROPPED_ITEM "mob_dropped_item"
///from base of obj/item/pickup(): (/mob/taker)
#define COMSIG_ITEM_PICKUP "item_pickup"
///from base of obj/item/on_outfit_equip(): (mob/equipper, visuals_only, slot)
diff --git a/code/__DEFINES/dcs/signals/signals_status_effect.dm b/code/__DEFINES/dcs/signals/signals_status_effect.dm
new file mode 100644
index 00000000000..917f5dffc9a
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_status_effect.dm
@@ -0,0 +1,2 @@
+/// From /datum/status_effect/fire_handler/fire_stacks/tick()
+#define COMSIG_FIRE_STACKS_UPDATED "fire_stacks_updated"
diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index 43dacfa4e2b..50c1a054b77 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -175,6 +175,8 @@ GLOBAL_LIST_INIT(turfs_pass_meteor, typecacheof(list(
#define is_simian(A) (isgorilla(A) || ismonkey(A))
+#define isstargazer(A) (istype(A, /mob/living/basic/heretic_summon/star_gazer))
+
/// returns whether or not the atom is either a basic mob OR simple animal
#define isanimal_or_basicmob(A) (istype(A, /mob/living/simple_animal) || istype(A, /mob/living/basic))
diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm
index 124c68f7cf4..004f990bcdf 100644
--- a/code/__DEFINES/role_preferences.dm
+++ b/code/__DEFINES/role_preferences.dm
@@ -41,8 +41,6 @@
#define ROLE_VOIDWALKER "Voidwalker"
#define ROLE_SUNWALKER "Sunwalker"
-// Latejoin roles
-#define ROLE_HERETIC_SMUGGLER "Heretic Smuggler"
#define ROLE_PROVOCATEUR "Provocateur"
#define ROLE_STOWAWAY_CHANGELING "Stowaway Changeling"
#define ROLE_SYNDICATE_INFILTRATOR "Syndicate Infiltrator"
diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm
index 0f6a4ea6c2e..5e10a84417c 100644
--- a/code/__DEFINES/traits/declarations.dm
+++ b/code/__DEFINES/traits/declarations.dm
@@ -96,6 +96,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_EMOTEMUTE "emotemute"
#define TRAIT_DEAF "deaf"
#define TRAIT_FAT "fat"
+/// If you are fat, you no longer get the slowdown from it
+#define TRAIT_FAT_IGNORE_SLOWDOWN "fat_ignore_slowdown"
/// Always hungry. They can eat as much as they want without eating slowdown.
#define TRAIT_GLUTTON "glutton"
#define TRAIT_HUSK "husk"
@@ -180,6 +182,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_NUKEIMMUNE "nuke_immunity"
/// Can't be given viruses
#define TRAIT_VIRUSIMMUNE "virus_immunity"
+/// Stepping on ants wont cause damage
+#define TRAIT_SPACE_ANT_IMMUNITY "space_ant_immunity"
/// Won't become a husk under any circumstances
#define TRAIT_UNHUSKABLE "trait_unhuskable"
/// Reduces the chance viruses will spread to this mob, and if the mob has a virus, slows its advancement
@@ -464,6 +468,14 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_HALLUCINATION_IMMUNE "hallucination_immune"
/// Increases chance of getting special traumas, makes them harder to cure
#define TRAIT_SPECIAL_TRAUMA_BOOST "special_trauma_boost"
+
+//---- Brain trauma resists
+/// Unable to gain any brain trauma whatsoever
+#define TRAIT_BRAIN_TRAUMA_IMMUNITY "brain_trauma_immunity"
+
+/// Prevents death from having too much brain damage
+#define TRAIT_BRAIN_DAMAGE_NODEATH "brain_damage_nodeath"
+
#define TRAIT_SPACEWALK "spacewalk"
/// Mobs with this trait still breathe gas in and out but aren't harmed by lacking any particular gas mix. (You can still be hurt by TOO MUCH of a specific gas).
#define TRAIT_NO_BREATHLESS_DAMAGE "spacebreathing"
@@ -646,6 +658,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_NO_GUN_AKIMBO "no_gun_akimbo"
/// Mobs with this trait cannot be hit by projectiles, meaning the projectiles will just go through.
#define TRAIT_UNHITTABLE_BY_PROJECTILES "unhittable_by_projectiles"
+/// Mobs with this trait can never be hit by laser projectiles, meaning the projectiles will just go through.
+#define TRAIT_UNHITTABLE_BY_LASERS "unhittable_by_lasers"
/// Mobs with this trait do care about a few grisly things, such as digging up graves. They also really do not like bringing people back to life or tending wounds, but love autopsies and amputations.
#define TRAIT_MORBID "morbid"
@@ -887,8 +901,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_ROD_ATTRACT_SHINY_LOVERS "rod_attract_shiny_lovers"
/// This rod can be used to fish on lava
#define TRAIT_ROD_LAVA_USABLE "rod_lava_usable"
-/// This rod was infused by a heretic, making it awesome and improving influence gain
-#define TRAIT_ROD_MANSUS_INFUSED "rod_infused"
/// Stuff that can go inside fish cases and aquariums
#define TRAIT_AQUARIUM_CONTENT "aquarium_content"
/// If the item can be used as a bit.
@@ -937,10 +949,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_NO_STRIP "no_strip"
/// Disallows this item from being pricetagged with a barcode
#define TRAIT_NO_BARCODES "no_barcode"
-/// Allows heretics to cast their spells.
-#define TRAIT_ALLOW_HERETIC_CASTING "allow_heretic_casting"
-/// Designates a heart as a living heart for a heretic.
-#define TRAIT_LIVING_HEART "living_heart"
/// Prevents the same person from being chosen multiple times for kidnapping objective
#define TRAIT_HAS_BEEN_KIDNAPPED "has_been_kidnapped"
/// An item still plays its hitsound even if it has 0 force, instead of the tap
@@ -962,6 +970,22 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
/// An item is ALWAYS considered baseline reachable and will pipe into CanBeReached().
#define TRAIT_SKIP_BASIC_REACH_CHECK "skip_basic_reach_check"
+//---- Heretic Traits
+/// Hides the heretic overlay that outs them as the heretic
+#define TRAIT_HERETIC_AURA_HIDDEN "heretic_aura_hidden"
+/// This rod was infused by a heretic, making it awesome and improving influence gain
+#define TRAIT_ROD_MANSUS_INFUSED "rod_infused"
+/// Allows heretics to cast their spells.
+#define TRAIT_ALLOW_HERETIC_CASTING "allow_heretic_casting"
+/// Designates a heart as a living heart for a heretic.
+#define TRAIT_LIVING_HEART "living_heart"
+/// Trait given to all participants in a heretic arena
+#define TRAIT_ELDRITCH_ARENA_PARTICIPANT "eldritch_arena_participant"
+/// Trait given to heretic summons, making them immune to heretic spells
+#define TRAIT_HERETIC_SUMMON "heretic_summon"
+/// Lock heretic grasp no longer goes on cooldown when opening things
+#define TRAIT_LOCK_GRASP_UPGRADED "lock_grasp_upgraded"
+
//quirk traits
#define TRAIT_ALCOHOL_TOLERANCE "alcohol_tolerance"
#define TRAIT_ANOSMIA "anosmia"
@@ -1393,9 +1417,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
/// Trait given to anything linked to, not necessarily allied to, the mansus
#define TRAIT_MANSUS_TOUCHED "mansus_touched"
-/// Trait given to all participants in a heretic arena
-#define TRAIT_ELDRITCH_ARENA_PARTICIPANT "eldritch_arena_participant"
-
// These traits are used in IS_X() as an OR, and is utilized for pseudoantags (such as deathmatch or domains) so they don't need to actually get antag status.
// To specifically and only get the antag datum, GET_X() exists now.
#define TRAIT_ACT_AS_CULTIST "act_as_cultist"
@@ -1522,9 +1543,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
///A trait given to users as a mutex to prevent repeated unresolved attempts to christen a shuttle
#define TRAIT_ATTEMPTING_CHRISTENING "attempting_christening"
-///Trait given to heretic summons, making them immune to heretic spells
-#define TRAIT_HERETIC_SUMMON "heretic_summon"
-
///trait given to mobs that are difficult to tame through mounting
#define TRAIT_MOB_DIFFICULT_TO_MOUNT "difficult_to_mount"
diff --git a/code/__DEFINES/traits/sources.dm b/code/__DEFINES/traits/sources.dm
index 86d1c82a7f7..14bf919dc62 100644
--- a/code/__DEFINES/traits/sources.dm
+++ b/code/__DEFINES/traits/sources.dm
@@ -59,8 +59,6 @@
#define SHOES_TRAIT "shoes"
/// Trait inherited by implants
#define IMPLANT_TRAIT "implant"
-/// Traits given by the heretic arena spell
-#define HERETIC_ARENA_TRAIT "heretic_arena"
#define GLASSES_TRAIT "glasses"
/// inherited from riding vehicles
#define VEHICLE_TRAIT "vehicle"
@@ -104,6 +102,10 @@
/// Trait given by being recruited as a nuclear operative
#define NUKE_OP_MINION_TRAIT "nuke-op-minion"
+//---- Heretic Traits Sources
+/// Traits given by the heretic arena spell
+#define HERETIC_ARENA_TRAIT "heretic_arena"
+
/// Trait given to you by shapeshifting
#define SHAPESHIFT_TRAIT "shapeshift_trait"
diff --git a/code/__DEFINES/turfs.dm b/code/__DEFINES/turfs.dm
index 41319ab7b9e..3f7010c80f2 100644
--- a/code/__DEFINES/turfs.dm
+++ b/code/__DEFINES/turfs.dm
@@ -89,6 +89,7 @@
#define RUST_RESISTANCE_REINFORCED 2
#define RUST_RESISTANCE_TITANIUM 3
#define RUST_RESISTANCE_ORGANIC 4
+/// Should not be rustable. EVER. Includes thing like space, lava, chasms, admin walls
#define RUST_RESISTANCE_ABSOLUTE 5
/// Turf will be passable if density is 0
diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm
index 2d64985f28f..22969385579 100644
--- a/code/_globalvars/traits/_traits.dm
+++ b/code/_globalvars/traits/_traits.dm
@@ -34,6 +34,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_SPINNING" = TRAIT_SPINNING,
"TRAIT_STICKERED" = TRAIT_STICKERED,
"TRAIT_UNHITTABLE_BY_PROJECTILES" = TRAIT_UNHITTABLE_BY_PROJECTILES,
+ "TRAIT_UNHITTABLE_BY_LASERS" = TRAIT_UNHITTABLE_BY_LASERS,
"TRAIT_UNLINKABLE_FISHING_SPOT" = TRAIT_UNLINKABLE_FISHING_SPOT,
"TRAIT_TETHER_ATTACHED" = TRAIT_TETHER_ATTACHED,
),
@@ -143,6 +144,9 @@ GLOBAL_LIST_INIT(traits_by_type, list(
/datum/wound = list(
"TRAIT_WOUND_SCANNED" = TRAIT_WOUND_SCANNED,
),
+ /datum/antagonist/heretic = list(
+ "TRAIT_LOCK_GRASP_UPGRADED" = TRAIT_LOCK_GRASP_UPGRADED,
+ ),
/obj = list(
"TRAIT_CONTRABAND" = TRAIT_CONTRABAND,
"TRAIT_DUCT_TAPE_UNREPAIRABLE" = TRAIT_DUCT_TAPE_UNREPAIRABLE,
@@ -640,6 +644,9 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_PREVENT_BLINK_LOOPS" = TRAIT_PREVENT_BLINK_LOOPS,
"TRAIT_NO_EYELIDS" = TRAIT_NO_EYELIDS,
"TRAIT_CARPOTOXIN_IMMUNE" = TRAIT_CARPOTOXIN_IMMUNE,
+ "TRAIT_FAT_IGNORE_SLOWDOWN" = TRAIT_FAT_IGNORE_SLOWDOWN,
+ "TRAIT_SPACE_ANT_IMMUNITY" = TRAIT_SPACE_ANT_IMMUNITY,
+ "TRAIT_BRAIN_TRAUMA_IMMUNITY" = TRAIT_BRAIN_TRAUMA_IMMUNITY,
),
/mob/dead/observer = list(
"TRAIT_NO_OBSERVE" = TRAIT_NO_OBSERVE,
@@ -687,6 +694,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_UNCOMPOSTABLE" = TRAIT_UNCOMPOSTABLE,
"TRAIT_UNIQUE_AQUARIUM_CONTENT" = TRAIT_UNIQUE_AQUARIUM_CONTENT,
"TRAIT_WIELDED" = TRAIT_WIELDED,
+ "TRAIT_HERETIC_AURA_HIDDEN" = TRAIT_HERETIC_AURA_HIDDEN,
),
/obj/item/ammo_casing = list(
"TRAIT_DART_HAS_INSERT" = TRAIT_DART_HAS_INSERT,
@@ -761,6 +769,9 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_LIVING_HEART" = TRAIT_LIVING_HEART,
"TRAIT_USED_ORGAN" = TRAIT_USED_ORGAN,
),
+ /obj/item/organ/brain = list(
+ "TRAIT_BRAIN_DAMAGE_NODEATH" = TRAIT_BRAIN_DAMAGE_NODEATH,
+ ),
/obj/item/organ/liver = list(
"TRAIT_BALLMER_SCIENTIST" = TRAIT_BALLMER_SCIENTIST,
"TRAIT_COMEDY_METABOLISM" = TRAIT_COMEDY_METABOLISM,
diff --git a/code/_globalvars/traits/admin_tooling.dm b/code/_globalvars/traits/admin_tooling.dm
index 5599bbef7ce..40241195347 100644
--- a/code/_globalvars/traits/admin_tooling.dm
+++ b/code/_globalvars/traits/admin_tooling.dm
@@ -7,6 +7,7 @@ GLOBAL_LIST_INIT(admin_visible_traits, list(
"TRAIT_CATCH_AND_RELEASE" = TRAIT_CATCH_AND_RELEASE,
"TRAIT_KEEP_TOGETHER" = TRAIT_KEEP_TOGETHER,
"TRAIT_UNHITTABLE_BY_PROJECTILES" = TRAIT_UNHITTABLE_BY_PROJECTILES,
+ "TRAIT_UNHITTABLE_BY_LASERS" = TRAIT_UNHITTABLE_BY_LASERS,
),
/atom/movable = list(
"TRAIT_ASHSTORM_IMMUNE" = TRAIT_ASHSTORM_IMMUNE,
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index ba156d8180b..d185b8c834f 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -479,6 +479,11 @@
return clamp(w_class * 6, 10, 100) // Multiply the item's weight class by 6, then clamp the value between 10 and 100
/mob/living/proc/send_item_attack_message(obj/item/weapon, mob/living/user, hit_area, def_zone)
+ if(SEND_SIGNAL(weapon, COMSIG_SEND_ITEM_ATTACK_MESSAGE_OBJECT, src, user) & SIGNAL_MESSAGE_MODIFIED)
+ return TRUE
+ if(SEND_SIGNAL(src, COMSIG_SEND_ITEM_ATTACK_MESSAGE_CARBON, weapon, user) & SIGNAL_MESSAGE_MODIFIED)
+ return TRUE
+
if(!weapon.force && !length(weapon.attack_verb_simple) && !length(weapon.attack_verb_continuous))
return
diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm
index b01611fbef7..9e685779212 100644
--- a/code/controllers/configuration/entries/general.dm
+++ b/code/controllers/configuration/entries/general.dm
@@ -793,3 +793,7 @@
/datum/config_entry/flag/generate_assets_in_init
default = FALSE
+
+/datum/config_entry/number/minimum_ascension_time
+ default = 0 // 1 minute
+ min_val = 0
diff --git a/code/controllers/subsystem/dynamic/dynamic_ruleset_latejoin.dm b/code/controllers/subsystem/dynamic/dynamic_ruleset_latejoin.dm
index d780e2fa344..88e20ce349b 100644
--- a/code/controllers/subsystem/dynamic/dynamic_ruleset_latejoin.dm
+++ b/code/controllers/subsystem/dynamic/dynamic_ruleset_latejoin.dm
@@ -35,19 +35,6 @@
/datum/dynamic_ruleset/latejoin/traitor/assign_role(datum/mind/candidate)
candidate.add_antag_datum(/datum/antagonist/traitor)
-/datum/dynamic_ruleset/latejoin/heretic
- name = "Heretic"
- config_tag = "Latejoin Heretic"
- preview_antag_datum = /datum/antagonist/heretic
- pref_flag = ROLE_HERETIC_SMUGGLER
- jobban_flag = ROLE_HERETIC
- weight = 3
- min_pop = 30 // Ensures good spread of sacrifice targets
- ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_HERETIC_SACRIFICE)
- blacklisted_roles = list(
- JOB_HEAD_OF_PERSONNEL,
- )
-
/datum/dynamic_ruleset/latejoin/heretic/assign_role(datum/mind/candidate)
candidate.add_antag_datum(/datum/antagonist/heretic)
diff --git a/code/controllers/subsystem/throwing.dm b/code/controllers/subsystem/throwing.dm
index 83f6e4e4ff8..95c6923269f 100644
--- a/code/controllers/subsystem/throwing.dm
+++ b/code/controllers/subsystem/throwing.dm
@@ -127,7 +127,7 @@ SUBSYSTEM_DEF(throwing)
qdel(src)
-/// Returns the mob thrower, or null
+/// Returns the thrower, or null
/datum/thrownthing/proc/get_thrower()
. = thrower?.resolve()
if(isnull(.))
@@ -144,11 +144,11 @@ SUBSYSTEM_DEF(throwing)
return
var/atom/movable/actual_target = initial_target?.resolve()
- var/mob/mob_thrower = get_thrower()
+ var/atom/thrower = get_thrower()
if(dist_travelled) //to catch sneaky things moving on our tile while we slept
for(var/atom/movable/obstacle as anything in get_turf(thrownthing))
- if (obstacle == thrownthing || (obstacle == mob_thrower && !ismob(thrownthing)))
+ if(obstacle == thrownthing || (obstacle == thrower && !ismob(thrownthing)))
continue
if(ismob(obstacle) && thrownthing.pass_flags & PASSMOB && (obstacle != actual_target))
continue
diff --git a/code/datums/brain_damage/brain_trauma.dm b/code/datums/brain_damage/brain_trauma.dm
index 3c7f5ce7aec..9d1caff55f6 100644
--- a/code/datums/brain_damage/brain_trauma.dm
+++ b/code/datums/brain_damage/brain_trauma.dm
@@ -8,14 +8,22 @@
abstract_type = /datum/brain_trauma
var/name = "Brain Trauma"
var/desc = "A trauma caused by brain damage, which causes issues to the patient."
- var/scan_desc = "generic brain trauma" //description when detected by a health scanner
- var/mob/living/carbon/owner //the poor bastard
- var/obj/item/organ/brain/brain //the poor bastard's brain
+ /// Description when detected by a health scanner
+ var/scan_desc = "generic brain trauma"
+ /// The poor bastard
+ var/mob/living/carbon/owner
+ /// The poor bastard's brain
+ var/obj/item/organ/brain/brain
+ /// Message sent in chat when trauma is gained
var/gain_text = span_notice("You feel traumatized.")
+ /// Message sent in chat when trauma is lost
var/lose_text = span_notice("You no longer feel traumatized.")
+ /// If the trauma can be gained, checked in can_gain_trauma
var/can_gain = TRUE
- var/random_gain = TRUE //can this be gained through random traumas?
- var/resilience = TRAUMA_RESILIENCE_BASIC //how hard is this to cure?
+ /// If this trauma can be gained randomly
+ var/random_gain = TRUE
+ /// How hard is this to cure?
+ var/resilience = TRAUMA_RESILIENCE_BASIC
/datum/brain_trauma/Destroy()
// Handles our references with our brain
diff --git a/code/datums/brain_damage/magic.dm b/code/datums/brain_damage/magic.dm
index d0026703c13..d15296baa22 100644
--- a/code/datums/brain_damage/magic.dm
+++ b/code/datums/brain_damage/magic.dm
@@ -74,8 +74,12 @@
scan_desc = "extra-sensory paranoia"
gain_text = span_warning("You feel like something wants to kill you...")
lose_text = span_notice("You no longer feel eyes on your back.")
+ /// Type of stalker that is chasing us
+ var/stalker_type = /obj/effect/client_image_holder/stalker_phantom
+ /// Reference to the stalker that is chasing us
var/obj/effect/client_image_holder/stalker_phantom/stalker
- var/close_stalker = FALSE //For heartbeat
+ /// Plays a sound when the stalker is near their victim
+ var/close_stalker = FALSE
/datum/brain_trauma/magic/stalker/Destroy()
QDEL_NULL(stalker)
@@ -87,7 +91,7 @@
/datum/brain_trauma/magic/stalker/proc/create_stalker()
var/turf/stalker_source = locate(owner.x + pick(-12, 12), owner.y + pick(-12, 12), owner.z) //random corner
- stalker = new(stalker_source, owner)
+ stalker = new stalker_type(stalker_source, owner)
/datum/brain_trauma/magic/stalker/on_lose()
QDEL_NULL(stalker)
@@ -125,3 +129,12 @@
desc = "It's coming closer..."
image_icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi'
image_state = "curseblob"
+
+// Heretic subtype that replaces the ghost guy with a stargazer
+/datum/brain_trauma/magic/stalker/cosmic
+ stalker_type = /obj/effect/client_image_holder/stalker_phantom/cosmic
+ random_gain = FALSE
+
+/obj/effect/client_image_holder/stalker_phantom/cosmic
+ image_icon = 'icons/mob/nonhuman-player/96x96eldritch_mobs.dmi'
+ image_state = "star_gazer"
diff --git a/code/datums/components/aura_healing.dm b/code/datums/components/aura_healing.dm
index 3f23b161adb..416ab713b44 100644
--- a/code/datums/components/aura_healing.dm
+++ b/code/datums/components/aura_healing.dm
@@ -44,6 +44,9 @@
/// The color to give the healing visual
var/healing_color = COLOR_GREEN
+ /// If the aura also heals the owner of the component
+ var/self_heal = TRUE
+
/// A list of being healed to active alerts
var/list/mob/living/current_alerts = list()
@@ -64,6 +67,7 @@
simple_heal = 0,
limit_to_trait = null,
healing_color = COLOR_GREEN,
+ self_heal = TRUE,
)
if (!isatom(parent))
return COMPONENT_INCOMPATIBLE
@@ -83,6 +87,7 @@
src.simple_heal = simple_heal
src.limit_to_trait = limit_to_trait
src.healing_color = healing_color
+ src.self_heal = self_heal
/datum/component/aura_healing/Destroy(force)
STOP_PROCESSING(SSaura, src)
diff --git a/code/datums/components/boomerang.dm b/code/datums/components/boomerang.dm
index e41917a2b10..82a189985bc 100644
--- a/code/datums/components/boomerang.dm
+++ b/code/datums/components/boomerang.dm
@@ -85,7 +85,7 @@
/datum/component/boomerang/proc/aerodynamic_swing(datum/thrownthing/throwingdatum)
var/mob/thrown_by = throwingdatum?.get_thrower()
var/obj/item/true_parent = parent
- if(thrown_by)
+ if(istype(thrown_by))
addtimer(CALLBACK(true_parent, TYPE_PROC_REF(/atom/movable, throw_at), thrown_by, boomerang_throw_range, throwingdatum.speed, thrown_by, TRUE), 0.1 SECONDS)
COOLDOWN_START(src, last_boomerang_throw, BOOMERANG_REBOUND_INTERVAL)
true_parent.visible_message(
diff --git a/code/datums/components/caltrop.dm b/code/datums/components/caltrop.dm
index cd06bdb2a00..43b52abb017 100644
--- a/code/datums/components/caltrop.dm
+++ b/code/datums/components/caltrop.dm
@@ -104,6 +104,8 @@
if(HAS_TRAIT(digitigrade_fan, TRAIT_LIGHT_STEP))
damage *= 0.75
+ if(flags & CALTROP_ANTS && HAS_TRAIT(digitigrade_fan, TRAIT_SPACE_ANT_IMMUNITY))
+ damage = 0
if(!(flags & CALTROP_SILENT) && !digitigrade_fan.has_status_effect(/datum/status_effect/caltropped))
digitigrade_fan.apply_status_effect(/datum/status_effect/caltropped)
diff --git a/code/datums/components/food/edible.dm b/code/datums/components/food/edible.dm
index 9da3001015f..b238be9f5a7 100644
--- a/code/datums/components/food/edible.dm
+++ b/code/datums/components/food/edible.dm
@@ -495,6 +495,7 @@ Behavior that's still missing from this component that original food items had t
playsound(eater.loc,'sound/items/eatfood.ogg', rand(10,50), TRUE)
if(!owner.reagents.total_volume)
return
+ SEND_SIGNAL(eater, COMSIG_LIVING_EAT_FOOD, owner)
var/sig_return = SEND_SIGNAL(parent, COMSIG_FOOD_EATEN, eater, feeder, bitecount, bite_consumption)
if(sig_return & DESTROY_FOOD)
qdel(owner)
diff --git a/code/datums/components/pet_commands/fetch.dm b/code/datums/components/pet_commands/fetch.dm
index 143ac9ca101..f3675534fa9 100644
--- a/code/datums/components/pet_commands/fetch.dm
+++ b/code/datums/components/pet_commands/fetch.dm
@@ -73,10 +73,11 @@
return
var/mob/thrower = throwingdatum?.get_thrower()
- if(thrower)
- try_activate_command(thrower)
- set_command_target(parent, thrown_thing)
- parent.ai_controller.set_blackboard_key(BB_FETCH_DELIVER_TO, thrower)
+ if(!istype(thrower))
+ return
+ try_activate_command(thrower)
+ set_command_target(parent, thrown_thing)
+ parent.ai_controller.set_blackboard_key(BB_FETCH_DELIVER_TO, thrower)
// Don't try and fetch turfs or anchored objects if someone points at them
/datum/pet_command/fetch/look_for_target(mob/living/pointing_friend, obj/item/pointed_atom)
diff --git a/code/datums/components/tackle.dm b/code/datums/components/tackle.dm
index ac4a44c9979..7f2d7e73ada 100644
--- a/code/datums/components/tackle.dm
+++ b/code/datums/components/tackle.dm
@@ -411,6 +411,13 @@
if(istype(potential_spine))
defense_mod += potential_spine.strength_bonus
+ if(istype(tackle_target.wear_suit, /obj/item/clothing/suit/hooded/cultrobes/eldritch/blade))
+ defense_mod += 8
+ if(istype(tackle_target.wear_suit, /obj/item/clothing/suit/hooded/cultrobes/eldritch/rust))
+ var/obj/item/clothing/suit/hooded/cultrobes/eldritch/rust/rust_robes = tackle_target.wear_suit
+ if(rust_robes.rusted)
+ defense_mod += 10
+
// OF-FENSE
var/mob/living/carbon/sacker = parent
var/sacker_drunkenness = sacker.get_drunk_amount()
diff --git a/code/datums/elements/effect_trail.dm b/code/datums/elements/effect_trail.dm
index d1d2fbec44a..58fddf51216 100644
--- a/code/datums/elements/effect_trail.dm
+++ b/code/datums/elements/effect_trail.dm
@@ -16,13 +16,55 @@
src.chosen_effect = chosen_effect
/datum/element/effect_trail/Detach(datum/target)
- . = ..()
UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
+ return ..()
/// Generates an effect
/datum/element/effect_trail/proc/generate_effect(atom/movable/target_object)
SIGNAL_HANDLER
var/turf/open/open_turf = get_turf(target_object)
- if(istype(open_turf))
- new chosen_effect(open_turf)
+ if(!istype(open_turf))
+ return
+ new chosen_effect(open_turf)
+
+/// If we are a cosmic heretic, this will return the appropriate effect trail based on our passive level. returns the default trail otherwise
+/proc/cosmic_trail_based_on_passive(mob/living/source)
+ if(isstargazer(source))
+ return /datum/element/effect_trail/cosmic_field/antiprojectile
+
+ var/datum/status_effect/heretic_passive/cosmic/cosmic_passive = source.has_status_effect(/datum/status_effect/heretic_passive/cosmic)
+ if(!cosmic_passive)
+ return /datum/element/effect_trail/cosmic_field
+ if(cosmic_passive.passive_level == 3)
+ return /datum/element/effect_trail/cosmic_field/antiprojectile
+ if(cosmic_passive.passive_level == 2)
+ return /datum/element/effect_trail/cosmic_field/antiexplosion
+ return /datum/element/effect_trail
+
+/datum/element/effect_trail/cosmic_field // Cosmic field subtype which applies any upgrades
+ var/prevents_explosions = FALSE
+ var/slows_projectiles = FALSE
+
+/datum/element/effect_trail/cosmic_field/Attach(datum/target, chosen_effect)
+ . = ..()
+ if(!ispath(chosen_effect, /obj/effect/forcefield/cosmic_field))
+ stack_trace("Tried to attach a cosmic_field effect trail with a non-cosmic field as the chosen effect")
+
+/datum/element/effect_trail/cosmic_field/generate_effect(atom/movable/target_object)
+ var/turf/open/open_turf = get_turf(target_object)
+ if(!istype(open_turf))
+ return
+ var/obj/effect/forcefield/cosmic_field/new_field = new chosen_effect(open_turf)
+
+ if(prevents_explosions)
+ new_field.prevents_explosions()
+ if(slows_projectiles)
+ new_field.slows_projectiles()
+
+/datum/element/effect_trail/cosmic_field/antiexplosion
+ prevents_explosions = TRUE
+
+/datum/element/effect_trail/cosmic_field/antiprojectile
+ prevents_explosions = TRUE
+ slows_projectiles = TRUE
diff --git a/code/datums/elements/leeching_walk.dm b/code/datums/elements/leeching_walk.dm
index f5148b43a5a..a0d9e7a2706 100644
--- a/code/datums/elements/leeching_walk.dm
+++ b/code/datums/elements/leeching_walk.dm
@@ -1,5 +1,6 @@
/// Buffs and heals the target while standing on rust.
/datum/element/leeching_walk
+ var/healing_multiplier = 1.0 // How much healing to do
/datum/element/leeching_walk/Attach(datum/target)
. = ..()
@@ -43,13 +44,14 @@
// Heals all damage + Stamina
var/need_mob_update = FALSE
var/delta_time = DELTA_WORLD_TIME(SSmobs) * 0.5 // SSmobs.wait is 2 secs, so this should be halved.
- need_mob_update += source.adjustBruteLoss(-3 * delta_time, updating_health = FALSE)
- need_mob_update += source.adjustFireLoss(-3 * delta_time, updating_health = FALSE)
- need_mob_update += source.adjustToxLoss(-3 * delta_time, updating_health = FALSE, forced = TRUE) // Slimes are people too
- need_mob_update += source.adjustOxyLoss(-1.5 * delta_time, updating_health = FALSE)
- need_mob_update += source.adjustStaminaLoss(-10 * delta_time, updating_stamina = FALSE)
+ need_mob_update += source.adjustBruteLoss(-3 * delta_time * healing_multiplier, updating_health = FALSE)
+ need_mob_update += source.adjustFireLoss(-3 * delta_time * healing_multiplier, updating_health = FALSE)
+ need_mob_update += source.adjustToxLoss(-3 * delta_time * healing_multiplier, updating_health = FALSE, forced = TRUE) // Slimes are people too
+ need_mob_update += source.adjustOxyLoss(-1.5 * delta_time * healing_multiplier, updating_health = FALSE)
+ need_mob_update += source.adjustStaminaLoss(-10 * delta_time * healing_multiplier, updating_stamina = FALSE)
if(need_mob_update)
source.updatehealth()
+ new /obj/effect/temp_visual/heal(get_turf(source), COLOR_BROWN)
// Reduces duration of stuns/etc
source.AdjustAllImmobility((-0.5 SECONDS) * delta_time)
// Heals blood loss
@@ -57,3 +59,10 @@
source.blood_volume += 2.5 * delta_time
// Slowly regulates your body temp
source.adjust_bodytemperature((source.get_body_temp_normal() - source.bodytemperature) / 5)
+
+/datum/element/leeching_walk/minor
+ healing_multiplier = 0.5
+
+// Minor variant which heals slightly less and no baton resistance
+/datum/element/leeching_walk/minor/on_move(mob/source, atom/old_loc, dir, forced, list/old_locs)
+ return
diff --git a/code/datums/elements/relay_attackers.dm b/code/datums/elements/relay_attackers.dm
index 5d16fbb4e1b..6dd9e8dfd47 100644
--- a/code/datums/elements/relay_attackers.dm
+++ b/code/datums/elements/relay_attackers.dm
@@ -74,8 +74,8 @@
var/obj/item/hit_item = hit_atom
if(!hit_item.throwforce)
return
- var/mob/thrown_by = throwingdatum?.get_thrower()
- if(!ismob(thrown_by))
+ var/atom/thrown_by = throwingdatum?.get_thrower()
+ if(!istype(thrown_by))
return
relay_attacker(target, thrown_by, hit_item.damtype == STAMINA ? ATTACKER_STAMINA_ATTACK : ATTACKER_DAMAGING_ATTACK)
diff --git a/code/datums/elements/rust.dm b/code/datums/elements/rust.dm
index 2bc63c12737..91bcd038d57 100644
--- a/code/datums/elements/rust.dm
+++ b/code/datums/elements/rust.dm
@@ -36,7 +36,7 @@
/datum/element/rust/proc/handle_examine(datum/source, mob/user, list/examine_text)
SIGNAL_HANDLER
- examine_text += span_notice("[source] is very rusty, you could probably burn or scrape it off.")
+ examine_text += span_notice("[source] is very rusty, you could probably burn or scrape it off, hell maybe even pour some space cola on it to remove the rust.")
/datum/element/rust/proc/apply_rust_overlay(atom/parent_atom, list/overlays)
SIGNAL_HANDLER
@@ -115,6 +115,10 @@
/datum/element/rust/heretic/proc/on_entered(turf/source, atom/movable/entered, ...)
SIGNAL_HANDLER
+ if(ismecha(entered))
+ var/obj/vehicle/sealed/mecha/victim = entered
+ victim.take_damage(20, armour_penetration = 100)
+ return
if(!isliving(entered))
return
var/mob/living/victim = entered
diff --git a/code/datums/looping_sounds/_looping_sound.dm b/code/datums/looping_sounds/_looping_sound.dm
index 2b2b00e19a1..d2f62f28174 100644
--- a/code/datums/looping_sounds/_looping_sound.dm
+++ b/code/datums/looping_sounds/_looping_sound.dm
@@ -68,7 +68,13 @@
//If we reserve a random sound channel, store the channel number here so we can clean it up later.
var/reserved_channel
-/datum/looping_sound/New(_parent, start_immediately = FALSE, _direct = FALSE, _skip_starting_sounds = FALSE)
+/datum/looping_sound/New(
+ _parent,
+ start_immediately = FALSE,
+ _direct = FALSE,
+ _skip_starting_sounds = FALSE,
+ sound_channel,
+)
if(!mid_sounds)
WARNING("A looping sound datum was created without sounds to play.")
return
@@ -76,6 +82,7 @@
set_parent(_parent)
direct = _direct
skip_starting_sounds = _skip_starting_sounds
+ src.sound_channel = sound_channel
if(start_immediately)
start()
@@ -164,9 +171,9 @@
*/
/datum/looping_sound/proc/play(soundfile, volume_override)
var/sound/sound_to_play = sound(soundfile)
+ sound_to_play.channel = sound_channel || SSsounds.random_available_channel()
+ sound_to_play.volume = volume_override || volume //Use volume as fallback if theres no override
if(direct)
- sound_to_play.channel = sound_channel || SSsounds.random_available_channel()
- sound_to_play.volume = volume_override || volume //Use volume as fallback if theres no override
SEND_SOUND(parent, sound_to_play)
else
playsound(
@@ -176,6 +183,7 @@
vary,
extra_range,
falloff_exponent = falloff_exponent,
+ channel = sound_to_play.channel,
pressure_affected = pressure_affected,
ignore_walls = ignore_walls,
falloff_distance = falloff_distance,
diff --git a/code/datums/materials/basemats.dm b/code/datums/materials/basemats.dm
index 7b3daf48461..5c56a8a451d 100644
--- a/code/datums/materials/basemats.dm
+++ b/code/datums/materials/basemats.dm
@@ -759,6 +759,7 @@ Unless you know what you're doing, only use the first three numbers. They're in
beauty_modifier = 0.25
turf_sound_override = FOOTSTEP_SAND
texture_layer_icon_state = "sand"
+ mat_rust_resistance = RUST_RESISTANCE_BASIC
fish_weight_modifier = 1.2
fishing_difficulty_modifier = 30 //Sand fishing rods? What the hell are you doing?
fishing_cast_range = -2
@@ -790,6 +791,7 @@ Unless you know what you're doing, only use the first three numbers. They're in
beauty_modifier = 0.3
turf_sound_override = FOOTSTEP_WOOD
texture_layer_icon_state = "brick"
+ mat_rust_resistance = RUST_RESISTANCE_BASIC
fish_weight_modifier = 1.2
fishing_difficulty_modifier = 25 //Sand fishing rods? What the hell are you doing?
fishing_cast_range = -2
@@ -817,6 +819,7 @@ Unless you know what you're doing, only use the first three numbers. They're in
beauty_modifier = 0.3
turf_sound_override = FOOTSTEP_SAND
texture_layer_icon_state = "sand"
+ mat_rust_resistance = RUST_RESISTANCE_ORGANIC
fish_weight_modifier = 0.8
fishing_difficulty_modifier = 25
fishing_cast_range = -2
diff --git a/code/datums/materials/pizza.dm b/code/datums/materials/pizza.dm
index dfd38434886..fbc54cf65c6 100644
--- a/code/datums/materials/pizza.dm
+++ b/code/datums/materials/pizza.dm
@@ -16,6 +16,7 @@
item_sound_override = 'sound/effects/meatslap.ogg'
turf_sound_override = FOOTSTEP_MEAT
texture_layer_icon_state = "pizza"
+ mat_rust_resistance = RUST_RESISTANCE_REINFORCED
fish_weight_modifier = 0.9
fishing_difficulty_modifier = 13
fishing_cast_range = -2
diff --git a/code/datums/mood.dm b/code/datums/mood.dm
index 8fc80213fc2..45590774c6b 100644
--- a/code/datums/mood.dm
+++ b/code/datums/mood.dm
@@ -260,7 +260,7 @@
/// Updates the mob's mood icon
/datum/mood/proc/update_mood_icon()
- if (!(mob_parent.client || mob_parent.hud_used))
+ if (!(mob_parent.client || mob_parent.hud_used) || isnull(mood_screen_object))
return
mood_screen_object.cut_overlays()
@@ -324,6 +324,7 @@
if(hud?.infodisplay)
hud.infodisplay -= mood_screen_object
QDEL_NULL(mood_screen_object)
+ UnregisterSignal(hud, COMSIG_QDELETING)
/// Handles clicking on the mood HUD object
/datum/mood/proc/hud_click(datum/source, location, control, params, mob/user)
diff --git a/code/datums/mutations/antenna.dm b/code/datums/mutations/antenna.dm
index f5347fab41c..f38a03f933a 100644
--- a/code/datums/mutations/antenna.dm
+++ b/code/datums/mutations/antenna.dm
@@ -102,6 +102,13 @@
to_chat(owner, span_warning("You plunge into your mind... Yep, it's your mind."))
return
+ if(cast_on.has_status_effect(/datum/status_effect/heretic_passive/moon))
+ to_chat(owner, span_hypnophrase(span_bolddanger("YOU SEEK THE TRUTH? I WILL SHOW YOU EVERYTHING.")))
+ if(isliving(owner))
+ var/mob/living/reader = owner
+ reader.apply_status_effect(/datum/status_effect/moon_converted)
+ return
+
if(HAS_TRAIT(cast_on, TRAIT_EVIL))
to_chat(owner, span_warning("As you reach into [cast_on]'s mind, \
you feel the overwhelming emptiness within. A truly evil being. \
diff --git a/code/datums/proximity_monitor/fields/heretic_arena.dm b/code/datums/proximity_monitor/fields/heretic_arena.dm
index fc89fe824d1..4c41be6cfe6 100644
--- a/code/datums/proximity_monitor/fields/heretic_arena.dm
+++ b/code/datums/proximity_monitor/fields/heretic_arena.dm
@@ -63,7 +63,8 @@ GLOBAL_LIST_EMPTY(heretic_arenas)
var/obj/item/melee/sickly_blade/training/new_blade = new(get_turf(human_in_range))
welfare_blades += new_blade
INVOKE_ASYNC(human_in_range, TYPE_PROC_REF(/mob, put_in_hands), new_blade)
- human_in_range.mind?.add_antag_datum(/datum/antagonist/heretic_arena_participant)
+ to_chat(human_in_range, span_boldbig("Escape is impossible. The only way out is to defeat another participant in this battle to the death."))
+ human_in_range.balloon_alert(human_in_range, "start killing!")
human_in_range.apply_status_effect(/datum/status_effect/arena_tracker)
RegisterSignal(human_in_range, COMSIG_CAN_Z_MOVE, PROC_REF(on_try_z_move))
RegisterSignal(human_in_range, COMSIG_LADDER_TRAVEL, PROC_REF(on_try_ladder))
@@ -75,8 +76,8 @@ GLOBAL_LIST_EMPTY(heretic_arenas)
mob.remove_traits(given_immunities, HERETIC_ARENA_TRAIT)
mob.remove_status_effect(/datum/status_effect/arena_tracker)
UnregisterSignal(mob, list(COMSIG_CAN_Z_MOVE, COMSIG_LADDER_TRAVEL, COMSIG_MOVABLE_PRE_MOVE, COMSIG_MOVABLE_POST_TELEPORT))
- if(mob.mind?.has_antag_datum(/datum/antagonist/heretic_arena_participant))
- mob.mind.remove_antag_datum(/datum/antagonist/heretic_arena_participant)
+ to_chat(mob, span_boldbig("Your bloodlust is sated."))
+ mob.balloon_alert(mob, "escape the arena!")
for(var/turf/to_restore in border_walls)
to_restore.ChangeTurf(border_walls[to_restore])
for(var/obj/to_refund as anything in welfare_blades)
diff --git a/code/datums/status_effects/buffs/xray.dm b/code/datums/status_effects/buffs/xray.dm
new file mode 100644
index 00000000000..cdeb909d623
--- /dev/null
+++ b/code/datums/status_effects/buffs/xray.dm
@@ -0,0 +1,30 @@
+/**
+ * Effectively grants a temporary form of x-ray with a cooldown period.
+ */
+/datum/status_effect/temporary_xray
+ id = "temp xray"
+ status_type = STATUS_EFFECT_UNIQUE
+ alert_type = null
+ duration = 10 SECONDS
+ show_duration = TRUE
+
+/datum/status_effect/temporary_xray/on_apply()
+ ADD_TRAIT(owner, TRAIT_XRAY_VISION, TRAIT_STATUS_EFFECT(id))
+ owner.update_sight()
+ return TRUE
+
+/datum/status_effect/temporary_xray/on_remove()
+ REMOVE_TRAIT(owner, TRAIT_XRAY_VISION, TRAIT_STATUS_EFFECT(id))
+ owner.update_sight()
+
+/datum/status_effect/temporary_xray/eldritch // Heretic subtype that plays a sound and screen alert
+ alert_type = /atom/movable/screen/alert/status_effect/temporary_xray
+
+/datum/status_effect/temporary_xray/eldritch/on_apply()
+ . = ..()
+ SEND_SOUND(owner, 'sound/effects/hallucinations/i_see_you1.ogg')
+
+/atom/movable/screen/alert/status_effect/temporary_xray
+ name = "Eldritch Sight"
+ desc = "You get a glimpse of something new..."
+ icon_state = "influence"
diff --git a/code/datums/status_effects/debuffs/fire_stacks.dm b/code/datums/status_effects/debuffs/fire_stacks.dm
index 3367eb8176c..df408af6512 100644
--- a/code/datums/status_effects/debuffs/fire_stacks.dm
+++ b/code/datums/status_effects/debuffs/fire_stacks.dm
@@ -170,6 +170,7 @@
var/decay_multiplier = HAS_TRAIT(owner, TRAIT_HUSK) ? 2 : 1 // husks decay twice as fast
adjust_stacks(owner.fire_stack_decay_rate * decay_multiplier * seconds_between_ticks)
+ SEND_SIGNAL(owner, COMSIG_FIRE_STACKS_UPDATED, stacks)
if(stacks <= 0)
qdel(src)
diff --git a/code/datums/status_effects/debuffs/rust_corruption.dm b/code/datums/status_effects/debuffs/rust_corruption.dm
index 6ba9d6a4ee9..168986beb92 100644
--- a/code/datums/status_effects/debuffs/rust_corruption.dm
+++ b/code/datums/status_effects/debuffs/rust_corruption.dm
@@ -5,8 +5,14 @@
remove_on_fullheal = TRUE
/datum/status_effect/rust_corruption/tick(seconds_between_ticks)
- if(issilicon(owner))
+ if(issilicon(owner) || isbot(owner))
owner.adjustBruteLoss(10 * seconds_between_ticks)
return
owner.adjust_disgust(5 * seconds_between_ticks)
owner.reagents?.remove_all(0.75 * seconds_between_ticks)
+ if(!iscarbon(owner))
+ return
+ var/mob/living/carbon/carbon_owner = owner
+ for(var/obj/item/bodypart/robotic_limb as anything in carbon_owner.bodyparts)
+ if(IS_ROBOTIC_LIMB(robotic_limb))
+ robotic_limb.receive_damage(10)
diff --git a/code/game/atom/alternate_appearance.dm b/code/game/atom/alternate_appearance.dm
index 1bc5ae0d641..270f26e1809 100644
--- a/code/game/atom/alternate_appearance.dm
+++ b/code/game/atom/alternate_appearance.dm
@@ -228,7 +228,7 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances)
/datum/atom_hud/alternate_appearance/basic/heretic
-/datum/atom_hud/alternate_appearance/basic/heretic/mobShouldSee(mob/M)
- if(IS_HERETIC(M))
+/datum/atom_hud/alternate_appearance/basic/heretic/mobShouldSee(mob/viewer)
+ if(IS_HERETIC_OR_MONSTER(viewer))
return TRUE
return FALSE
diff --git a/code/game/atom/atom_act.dm b/code/game/atom/atom_act.dm
index 0d9d1db463a..703a485405d 100644
--- a/code/game/atom/atom_act.dm
+++ b/code/game/atom/atom_act.dm
@@ -230,8 +230,9 @@
*
* Override this if you want custom behaviour in whatever gets hit by the rust
* /turf/rust_turf should be used instead for overriding rust on turfs
+ * rust_strength (optional) - if you want to vary the effect based on the users' strength
*/
-/atom/proc/rust_heretic_act()
+/atom/proc/rust_heretic_act(rust_strength)
return
///wrapper proc that passes our mob's rust_strength to the target we are rusting
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 3a984ee64f9..420b8118c2a 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -1328,13 +1328,13 @@
return ..()
// Calls throw_at after checking that the move strength is greater than the thrown atom's move resist. Identical args.
-/atom/movable/proc/safe_throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE)
+/atom/movable/proc/safe_throw_at(atom/target, range, speed, atom/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE)
if((force < (move_resist * MOVE_FORCE_THROW_RATIO)) || (move_resist == INFINITY))
return
return throw_at(target, range, speed, thrower, spin, diagonals_first, callback, force, gentle)
///If this returns FALSE then callback will not be called.
-/atom/movable/proc/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE, quickstart = TRUE, throw_datum_typepath = /datum/thrownthing)
+/atom/movable/proc/throw_at(atom/target, range, speed, atom/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE, quickstart = TRUE, throw_datum_typepath = /datum/thrownthing)
. = FALSE
if(QDELETED(src))
@@ -1350,36 +1350,38 @@
pulledby.stop_pulling()
//They are moving! Wouldn't it be cool if we calculated their momentum and added it to the throw?
- if (thrower && thrower.last_move && thrower.client && thrower.client.move_delay >= world.time + world.tick_lag*2)
- var/user_momentum = thrower.cached_multiplicative_slowdown
- if (!user_momentum) //no movement_delay, this means they move once per byond tick, lets calculate from that instead.
- user_momentum = world.tick_lag
+ if(ismob(thrower))
+ var/mob/thrower_mob = thrower
+ if(thrower_mob.last_move && thrower_mob.client && thrower_mob.client.move_delay >= world.time + world.tick_lag*2)
+ var/user_momentum = thrower_mob.cached_multiplicative_slowdown
+ if (!user_momentum) //no movement_delay, this means they move once per byond tick, lets calculate from that instead.
+ user_momentum = world.tick_lag
- user_momentum = 1 / user_momentum // convert from ds to the tiles per ds that throw_at uses.
+ user_momentum = 1 / user_momentum // convert from ds to the tiles per ds that throw_at uses.
- if (get_dir(thrower, target) & last_move)
- user_momentum = user_momentum //basically a noop, but needed
- else if (get_dir(target, thrower) & last_move)
- user_momentum = -user_momentum //we are moving away from the target, lets slowdown the throw accordingly
- else
- user_momentum = 0
+ if (get_dir(thrower_mob, target) & last_move)
+ user_momentum = user_momentum //basically a noop, but needed
+ else if (get_dir(target, thrower_mob) & last_move)
+ user_momentum = -user_momentum //we are moving away from the target, lets slowdown the throw accordingly
+ else
+ user_momentum = 0
-
- if (user_momentum)
- //first lets add that momentum to range.
- range *= (user_momentum / speed) + 1
- //then lets add it to speed
- speed += user_momentum
- if (speed <= 0)
- return//no throw speed, the user was moving too fast.
+ if (user_momentum)
+ //first lets add that momentum to range.
+ range *= (user_momentum / speed) + 1
+ //then lets add it to speed
+ speed += user_momentum
+ if (speed <= 0)
+ return//no throw speed, the user was moving too fast.
. = TRUE // No failure conditions past this point.
var/target_zone
if(QDELETED(thrower))
thrower = null //Let's not pass a qdeleting reference if any.
- else
- target_zone = thrower.zone_selected
+ else if(ismob(thrower))
+ var/mob/thrower_mob = thrower
+ target_zone = thrower_mob.zone_selected
var/datum/thrownthing/thrown_thing = new throw_datum_typepath(src, target, get_dir(src, target), range, speed, thrower, diagonals_first, force, gentle, callback, target_zone)
diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm
index 9c5ea9e820d..2023722ea35 100644
--- a/code/game/machinery/_machinery.dm
+++ b/code/game/machinery/_machinery.dm
@@ -1237,8 +1237,9 @@
dropped_atom.pixel_x = -8 + ((.%3)*8)
dropped_atom.pixel_y = -8 + (round( . / 3)*8)
-/obj/machinery/rust_heretic_act()
- take_damage(500, BRUTE, MELEE, 1)
+/obj/machinery/rust_heretic_act(rust_strength)
+ var/damage = 500 + rust_strength * 200
+ take_damage(damage, BRUTE, BOMB, 1)
/obj/machinery/vv_edit_var(vname, vval)
if(vname == NAMEOF(src, occupant))
diff --git a/code/game/machinery/syndicatebomb.dm b/code/game/machinery/syndicatebomb.dm
index e866fbd84df..726338848ec 100644
--- a/code/game/machinery/syndicatebomb.dm
+++ b/code/game/machinery/syndicatebomb.dm
@@ -69,6 +69,12 @@
if(!active)
return PROCESS_KILL
+ for(var/obj/effect/forcefield/cosmic_field/potential_field as anything in GLOB.active_cosmic_fields)
+ if(get_dist(potential_field, src) < 3)
+ new /obj/effect/temp_visual/revenant(get_turf(src))
+ defuse()
+ return
+
if(!isnull(next_beep) && (next_beep <= world.time))
var/volume
switch(seconds_remaining())
diff --git a/code/game/objects/effects/forcefields.dm b/code/game/objects/effects/forcefields.dm
index 377bf58fda6..08f996081a1 100644
--- a/code/game/objects/effects/forcefields.dm
+++ b/code/game/objects/effects/forcefields.dm
@@ -95,12 +95,15 @@
receive_ricochet_chance_mod = INFINITY //we do ricochet a lot!
initial_duration = 10 SECONDS
+GLOBAL_LIST_EMPTY_TYPED(active_cosmic_fields, /obj/effect/forcefield/cosmic_field)
+
/// The cosmic heretics forcefield
/obj/effect/forcefield/cosmic_field
name = "Cosmic Field"
desc = "A field that cannot be passed by people marked with a cosmic star."
icon = 'icons/effects/eldritch.dmi'
icon_state = "cosmic_carpet"
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
anchored = TRUE
layer = GIB_LAYER
density = FALSE
@@ -108,10 +111,27 @@
initial_duration = 30 SECONDS
/// Flags for what antimagic can just ignore our forcefields
var/antimagic_flags = MAGIC_RESISTANCE
+ /// If we are able to slow down projectiles
+ var/slows_projectiles = FALSE
/obj/effect/forcefield/cosmic_field/Initialize(mapload, flags = MAGIC_RESISTANCE)
. = ..()
antimagic_flags = flags
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
+ COMSIG_ATOM_EXITED = PROC_REF(on_loc_exited),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+ // Make sure that if we create a field, we apply whatever effects
+ for(var/atom/movable/thing in get_turf(src))
+ on_entered(src, thing)
+
+/obj/effect/forcefield/cosmic_field/Destroy(force)
+ // Make sure when the field goes away that the effects don't persist
+ for(var/atom/movable/thing in get_turf(src))
+ on_loc_exited(src, thing)
+ GLOB.active_cosmic_fields -= src
+ return ..()
/obj/effect/forcefield/cosmic_field/CanAllowThrough(atom/movable/mover, border_dir)
if(!isliving(mover))
@@ -119,10 +139,68 @@
var/mob/living/living_mover = mover
if(living_mover.can_block_magic(antimagic_flags, charge_cost = 0))
return ..()
+ // Being buckled/pulled by a cosmic heretic will allow you through cosmic fields EVEN IF you have a star mark
+ if(ismob(living_mover.buckled))
+ var/mob/living/fireman = living_mover.buckled
+ if(fireman.has_status_effect(/datum/status_effect/heretic_passive/cosmic))
+ return ..()
+ if(living_mover.pulledby?.has_status_effect(/datum/status_effect/heretic_passive/cosmic))
+ return ..()
if(living_mover.has_status_effect(/datum/status_effect/star_mark))
return FALSE
return ..()
+/obj/effect/forcefield/cosmic_field/proc/on_entered(datum/source, atom/movable/thing)
+ SIGNAL_HANDLER
+ if(isprojectile(thing) && slows_projectiles)
+ var/obj/projectile/bullet = thing
+ if(istype(bullet, /obj/projectile/magic/star_ball)) // Don't slow down star balls
+ return
+ bullet.speed *= 0.2 // 80% Slowdown
+ return
+
+ if(!isliving(thing))
+ return
+ var/mob/living/living_mover = thing
+ var/datum/status_effect/heretic_passive/cosmic/cosmic_passive = living_mover.has_status_effect(/datum/status_effect/heretic_passive/cosmic)
+ if(!cosmic_passive)
+ return
+ living_mover.add_movespeed_modifier(/datum/movespeed_modifier/cosmic_field)
+
+/obj/effect/forcefield/cosmic_field/proc/on_loc_exited(datum/source, atom/movable/thing)
+ SIGNAL_HANDLER
+ if(isprojectile(thing) && slows_projectiles)
+ var/obj/projectile/bullet = thing
+ if(istype(bullet, /obj/projectile/magic/star_ball)) // Don't speed up star balls
+ return
+ bullet.speed /= 0.2 // 80% Slowdown
+ return
+
+ if(!isliving(thing))
+ return
+ var/mob/living/living_mover = thing
+ var/datum/status_effect/heretic_passive/cosmic/cosmic_passive = living_mover.has_status_effect(/datum/status_effect/heretic_passive/cosmic)
+ if(!cosmic_passive)
+ return
+ living_mover.remove_movespeed_modifier(/datum/movespeed_modifier/cosmic_field)
+
+/// Adds the ability to slow down any projectiles that enters any turf we occupy
+/obj/effect/forcefield/cosmic_field/proc/slows_projectiles()
+ slows_projectiles = TRUE
+
+/// Adds our cosmic field to the global list which bombs check to see if they have to stop exploding
+/obj/effect/forcefield/cosmic_field/proc/prevents_explosions()
+ GLOB.active_cosmic_fields += src
+
+/datum/movespeed_modifier/cosmic_field
+ multiplicative_slowdown = -0.25
+
+/obj/effect/forcefield/cosmic_field/star_blast
+ initial_duration = 5 SECONDS
+
+/obj/effect/forcefield/cosmic_field/star_touch
+ initial_duration = 30 SECONDS
+
/obj/effect/forcefield/cosmic_field/fast
initial_duration = 5 SECONDS
diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
index a57c1e19082..4065a78104e 100644
--- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm
+++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
@@ -804,3 +804,6 @@
color = COLOR_FULL_TONER_BLACK
duration = 12 SECONDS
amount_to_scale = 12
+
+/obj/effect/temp_visual/circle_wave/star_blast
+ color = COLOR_VOID_PURPLE
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 431da812e0f..14c7684c6f9 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -743,7 +743,8 @@
item_flags &= ~IN_INVENTORY
UnregisterSignal(src, list(SIGNAL_ADDTRAIT(TRAIT_NO_WORN_ICON), SIGNAL_REMOVETRAIT(TRAIT_NO_WORN_ICON)))
SEND_SIGNAL(src, COMSIG_ITEM_DROPPED, user)
- if(!silent)
+ SEND_SIGNAL(user, COMSIG_MOB_DROPPED_ITEM, src)
+ if(!silent && drop_sound)
play_drop_sound(DROP_SOUND_VOLUME)
/// called just as an item is picked up (loc is not yet changed)
diff --git a/code/game/objects/items/devices/transfer_valve.dm b/code/game/objects/items/devices/transfer_valve.dm
index dc87b76644f..0b5574de31c 100644
--- a/code/game/objects/items/devices/transfer_valve.dm
+++ b/code/game/objects/items/devices/transfer_valve.dm
@@ -235,6 +235,11 @@
if(!istype(target) || (target != tank_one && target != tank_two))
return FALSE
+ for(var/obj/effect/forcefield/cosmic_field/potential_field as anything in GLOB.active_cosmic_fields)
+ if(get_dist(potential_field, src) < 3)
+ new /obj/effect/temp_visual/revenant(get_turf(src))
+ return FALSE
+
// Throw both tanks into processing queue
var/datum/gas_mixture/target_mix = target.return_air()
var/datum/gas_mixture/other_mix
diff --git a/code/game/objects/items/dice.dm b/code/game/objects/items/dice.dm
index b563e70cb2c..e88d1389390 100644
--- a/code/game/objects/items/dice.dm
+++ b/code/game/objects/items/dice.dm
@@ -70,7 +70,7 @@
/obj/item/dice/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
var/mob/thrown_by = throwingdatum?.get_thrower()
- if(thrown_by)
+ if(istype(thrown_by))
diceroll(thrown_by)
return ..()
diff --git a/code/game/objects/items/grenades/_grenade.dm b/code/game/objects/items/grenades/_grenade.dm
index 262a493cc75..11e7f3683e2 100644
--- a/code/game/objects/items/grenades/_grenade.dm
+++ b/code/game/objects/items/grenades/_grenade.dm
@@ -180,6 +180,13 @@
update_appearance()
return FALSE
+ for(var/obj/effect/forcefield/cosmic_field/potential_field as anything in GLOB.active_cosmic_fields)
+ if(get_dist(potential_field, src) < 3)
+ new /obj/effect/temp_visual/revenant(get_turf(src))
+ active = FALSE
+ update_appearance()
+ return FALSE
+
dud_flags |= GRENADE_USED // Don't detonate if we have already detonated.
if(shrapnel_type && shrapnel_radius && !shrapnel_initialized) // add a second check for adding the component in case whatever triggered the grenade went straight to prime (badminnery for example)
shrapnel_initialized = TRUE
diff --git a/code/game/objects/items/melee/baton.dm b/code/game/objects/items/melee/baton.dm
index 6d7195052e4..600d8c72cae 100644
--- a/code/game/objects/items/melee/baton.dm
+++ b/code/game/objects/items/melee/baton.dm
@@ -776,7 +776,7 @@
/obj/item/melee/baton/security/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
. = ..()
- if(!. && active && prob(throw_stun_chance) && isliving(hit_atom))
+ if(!. && active && prob(throw_stun_chance) && hit_atom)
finalize_baton_attack(hit_atom, throwingdatum?.get_thrower())
/obj/item/melee/baton/security/emp_act(severity)
diff --git a/code/game/objects/items/theft_tools.dm b/code/game/objects/items/theft_tools.dm
index ab9acd207a9..c13d621036d 100644
--- a/code/game/objects/items/theft_tools.dm
+++ b/code/game/objects/items/theft_tools.dm
@@ -204,7 +204,7 @@
if(victim.incorporeal_move || HAS_TRAIT(victim, TRAIT_GODMODE)) //try to keep this in sync with supermatter's consume fail conditions
return ..()
var/mob/thrower = throwingdatum?.get_thrower()
- if(thrower)
+ if(istype(thrower))
log_combat(thrower, hit_atom, "consumed", src)
message_admins("[src] has consumed [key_name_admin(victim)] [ADMIN_JMP(src)], thrown by [key_name_admin(thrower)].")
investigate_log("has consumed [key_name(victim)], thrown by [key_name(thrower)]", INVESTIGATE_ENGINE)
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index 383f9d953b5..a0bd2032f05 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -618,14 +618,6 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/window/reinforced/spawner, 0)
MAPPING_DIRECTIONAL_HELPERS(/obj/structure/window/reinforced/unanchored/spawner, 0)
-// You can't rust glass! So only reinforced glass can be impacted.
-/obj/structure/window/reinforced/rust_heretic_act()
- add_atom_colour(COLOR_RUSTED_GLASS, FIXED_COLOUR_PRIORITY)
- AddElement(/datum/element/rust)
- set_armor(/datum/armor/none)
- take_damage(get_integrity() * 0.5)
- modify_max_integrity(initial(max_integrity) * 0.2)
-
/obj/structure/window/plasma
name = "plasma window"
desc = "A window made out of a plasma-silicate alloy. It looks insanely tough to break and burn through."
diff --git a/code/game/turfs/open/asteroid.dm b/code/game/turfs/open/asteroid.dm
index 312b3366491..4e131d0bf98 100644
--- a/code/game/turfs/open/asteroid.dm
+++ b/code/game/turfs/open/asteroid.dm
@@ -9,12 +9,13 @@
damaged_dmi = 'icons/turf/floors.dmi'
icon_state = "asteroid"
base_icon_state = "asteroid"
+ turf_flags = IS_SOLID
footstep = FOOTSTEP_SAND
barefootstep = FOOTSTEP_SAND
clawfootstep = FOOTSTEP_SAND
heavyfootstep = FOOTSTEP_GENERIC_HEAVY
- rust_resistance = RUST_RESISTANCE_ORGANIC
+ rust_resistance = RUST_RESISTANCE_BASIC
/// Base turf type to be created by the tunnel
var/turf_type = /turf/open/misc/asteroid
/// Whether this turf has different icon states
diff --git a/code/game/turfs/open/floor/misc_floor.dm b/code/game/turfs/open/floor/misc_floor.dm
index ef96b90b021..b35ca869182 100644
--- a/code/game/turfs/open/floor/misc_floor.dm
+++ b/code/game/turfs/open/floor/misc_floor.dm
@@ -7,6 +7,7 @@
base_icon_state = "bcircuit"
light_color = LIGHT_COLOR_BABY_BLUE
floor_tile = /obj/item/stack/tile/circuit
+ rust_resistance = RUST_RESISTANCE_REINFORCED
/// If we want to ignore our area's power status and just be always off
/// Mostly for mappers doing asthetic things, or cases where the floor should be broken
var/always_off = FALSE
@@ -237,6 +238,7 @@
desc = "This one takes you back."
icon_state = "eighties"
floor_tile = /obj/item/stack/tile/eighties
+ rust_resistance = RUST_RESISTANCE_BASIC
/turf/open/floor/eighties/broken_states()
return list("eighties_damaged")
diff --git a/code/game/turfs/open/sand.dm b/code/game/turfs/open/sand.dm
index af583de7779..faccf7db104 100644
--- a/code/game/turfs/open/sand.dm
+++ b/code/game/turfs/open/sand.dm
@@ -8,7 +8,7 @@
barefootstep = FOOTSTEP_SAND
clawfootstep = FOOTSTEP_SAND
heavyfootstep = FOOTSTEP_GENERIC_HEAVY
- rust_resistance = RUST_RESISTANCE_ORGANIC
+ rust_resistance = RUST_RESISTANCE_REINFORCED
/turf/open/misc/beach/Initialize(mapload)
. = ..()
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index 223fc48a72f..5df3449c4aa 100644
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -91,7 +91,7 @@ GLOBAL_LIST_EMPTY(station_turfs)
var/force_no_gravity = FALSE
///This turf's resistance to getting rusted
- var/rust_resistance = RUST_RESISTANCE_ORGANIC
+ var/rust_resistance = RUST_RESISTANCE_BASIC
/// How pathing algorithm will check if this turf is passable by itself (not including content checks). By default it's just density check.
/// WARNING: Currently to use a density shortcircuiting this does not support dense turfs with special allow through function
diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm
index ca4bb86cc97..0554e2cae7c 100644
--- a/code/modules/antagonists/cult/cult_items.dm
+++ b/code/modules/antagonists/cult/cult_items.dm
@@ -222,7 +222,7 @@ Striking a noncultist, however, will tear their flesh."}
// Moon
PATH_MOON = list(
WIELDER_SPELLS = list(/datum/action/cooldown/spell/pointed/projectile/moon_parade),
- SWORD_SPELLS = list(/datum/action/cooldown/spell/pointed/moon_smile),
+ SWORD_SPELLS = list(/datum/action/cooldown/spell/pointed/mind_gate),
SWORD_PREFIX = "shimmering",
),
// Starter
@@ -412,7 +412,7 @@ Striking a noncultist, however, will tear their flesh."}
fling_act.Grant(trapped_entity)
// Set the sword's path for spell selection.
- heretic_path = heretic_holder.heretic_path
+ heretic_path = heretic_holder.heretic_path.route || PATH_START
// Copy the objectives to keep for roundend, remove the datum as neither us nor the heretic need it anymore
var/list/copied_objectives = heretic_holder.objectives.Copy()
diff --git a/code/modules/antagonists/heretic/heretic_antag.dm b/code/modules/antagonists/heretic/heretic_antag.dm
index caeeb61071b..875e4a9d8df 100644
--- a/code/modules/antagonists/heretic/heretic_antag.dm
+++ b/code/modules/antagonists/heretic/heretic_antag.dm
@@ -31,47 +31,63 @@
stinger_sound = 'sound/music/antag/heretic/heretic_gain.ogg'
antag_flags = parent_type::antag_flags | ANTAG_OBSERVER_VISIBLE_PANEL
- /// Whether we give this antagonist objectives on gain.
- var/give_objectives = TRUE
- /// Whether we've ascended! (Completed one of the final rituals)
- var/ascended = FALSE
- /// The path our heretic has chosen. Mostly used for flavor.
- var/heretic_path = PATH_START
- /// A sum of how many knowledge points this heretic CURRENTLY has. Used to research.
- var/knowledge_points = 1
- /// The time between gaining influence passively. The heretic gain +1 knowledge points every this duration of time.
- var/passive_gain_timer = 20 MINUTES
- /// Assoc list of [typepath] = [knowledge instance]. A list of all knowledge this heretic's reserached.
- var/list/researched_knowledge = list()
- /// The organ slot we place our Living Heart in.
- var/living_heart_organ_slot = ORGAN_SLOT_HEART
- /// A list of TOTAL how many sacrifices completed. (Includes high value sacrifices)
- var/total_sacrifices = 0
- /// A list of TOTAL how many high value sacrifices completed. (Heads of staff)
- var/high_value_sacrifices = 0
- /// Lazy assoc list of [refs to humans] to [image previews of the human]. Humans that we have as sacrifice targets.
- var/list/mob/living/carbon/human/sac_targets
- /// List of all sacrifice target's names, used for end of round report
- var/list/all_sac_targets = list()
- /// Whether we're drawing a rune or not
- var/drawing_rune = FALSE
+ /// Contains multiple separate heretic shops so you can choose between multiple when buying.
+ var/list/heretic_shops = list(
+ HERETIC_KNOWLEDGE_START = list(),
+ HERETIC_KNOWLEDGE_TREE = list(),
+ HERETIC_KNOWLEDGE_SHOP = list(),
+ HERETIC_KNOWLEDGE_DRAFT = list()
+ )
/// A static typecache of all tools we can scribe with.
var/static/list/scribing_tools = typecacheof(list(/obj/item/pen, /obj/item/toy/crayon))
/// A blacklist of turfs we cannot scribe on.
var/static/list/blacklisted_rune_turfs = typecacheof(list(/turf/open/space, /turf/open/openspace, /turf/open/lava, /turf/open/chasm))
- /// Controls what types of turf we can spread rust to, increases as we unlock more powerful rust abilites
- var/rust_strength = 0
- /// Wether we are allowed to ascend
- var/feast_of_owls = FALSE
-
+ /// A static list of all paths we can take and related info for the UI
+ var/static/list/path_info = list()
+ /// Assoc list of [typepath] = [knowledge instance]. A list of all knowledge this heretic's reserached.
+ var/list/researched_knowledge = list()
+ /// Lazy assoc list of [refs to humans] to [image previews of the human]. Humans that we have as sacrifice targets.
+ var/list/mob/living/carbon/human/sac_targets
+ /// List of all sacrifice target's names, used for end of round report
+ var/list/all_sac_targets = list()
/// List that keeps track of which items have been gifted to the heretic after a cultist was sacrificed. Used to alter drop chances to reduce dupes.
var/list/unlocked_heretic_items = list(
/obj/item/melee/sickly_blade/cursed = 0,
/obj/item/clothing/neck/heretic_focus/crimson_medallion = 0,
/mob/living/basic/construct/harvester/heretic = 0,
)
+ /// Whether or not the heretic can make unlimited blades, but unable to blade break to teleport
+ var/unlimited_blades = FALSE
+ /// Whether we are allowed to ascend
+ var/feast_of_owls = FALSE
+ /// Whether we give this antagonist objectives on gain.
+ var/give_objectives = TRUE
+ /// Whether we've ascended! (Completed one of the final rituals)
+ var/ascended = FALSE
+ /// Whether we're drawing a rune or not
+ var/drawing_rune = FALSE
+ /// The path our heretic has chosen.
+ var/datum/heretic_knowledge_tree_column/heretic_path
+ /// Reference to the overlay heretics get when they get strong enough
+ var/static/mutable_appearance/eldritch_overlay = mutable_appearance('icons/mob/effects/heretic_aura.dmi', "heretic_aura")
+ /// A sum of how many knowledge points this heretic CURRENTLY has. Used to research.
+ var/knowledge_points = 1
+ /// The time between gaining influence passively. The heretic gain +1 knowledge points every this duration of time.
+ var/passive_gain_timer = 20 MINUTES
+ /// Tracks how many knowledge points the heretic has aqcuired. Once you get enough points you lose the ability to blade break
+ var/knowledge_gained = 0
+ /// The organ slot we place our Living Heart in.
+ var/living_heart_organ_slot = ORGAN_SLOT_HEART
+ /// A list of TOTAL how many sacrifices completed. (Includes high value sacrifices)
+ var/total_sacrifices = 0
+ /// A list of TOTAL how many high value sacrifices completed. (Heads of staff)
+ var/high_value_sacrifices = 0
+ /// Controls what types of turf we can spread rust to
+ var/rust_strength = 1
/// Simpler version of above used to limit amount of loot that can be hoarded
var/rewards_given = 0
+ /// Our heretic passive level. Tracked here in case of body moving shenanigans
+ var/passive_level = 1
/datum/antagonist/heretic/Destroy()
LAZYNULL(sac_targets)
@@ -107,13 +123,6 @@
icon_path = result_mob.icon
icon_state = result_mob.icon_state
- //if the knowledge is an eldritch mark, use the mark sprite
- else if(ispath(knowledge,/datum/heretic_knowledge/mark))
- var/datum/heretic_knowledge/mark/mark_knowledge = knowledge
- var/datum/status_effect/eldritch/mark_effect = mark_knowledge.mark_type
- icon_path = mark_effect.effect_icon
- icon_state = mark_effect.effect_icon_state
-
//if the knowledge is an ascension, use the achievement sprite
else if(ispath(knowledge,/datum/heretic_knowledge/ultimate))
var/datum/heretic_knowledge/ultimate/ascension_knowledge = knowledge
@@ -130,70 +139,126 @@
result_parameters["moving"] = icon_moving
return result_parameters
-/datum/antagonist/heretic/proc/get_knowledge_data(datum/heretic_knowledge/knowledge, done)
-
+/datum/antagonist/heretic/proc/get_knowledge_data(datum/heretic_knowledge/knowledge, list/source_list, done = FALSE, category = HERETIC_KNOWLEDGE_TREE)
+ if(!length(source_list))
+ CRASH("get_knowledge_data called without source_list! (Got: [source_list || "empty list"])")
var/list/knowledge_data = list()
knowledge_data["path"] = knowledge
knowledge_data["icon_params"] = get_icon_of_knowledge(knowledge)
knowledge_data["name"] = initial(knowledge.name)
knowledge_data["gainFlavor"] = initial(knowledge.gain_text)
- knowledge_data["cost"] = initial(knowledge.cost)
- knowledge_data["disabled"] = (!done) && (initial(knowledge.cost) > knowledge_points)
- knowledge_data["bgr"] = GLOB.heretic_research_tree[knowledge][HKT_UI_BGR]
- knowledge_data["finished"] = done
- knowledge_data["ascension"] = ispath(knowledge,/datum/heretic_knowledge/ultimate)
+ knowledge_data["cost"] = source_list[knowledge][HKT_COST]
+ knowledge_data["depth"] = source_list[knowledge][HKT_DEPTH]
+ knowledge_data["bgr"] = source_list[knowledge][HKT_UI_BGR]
+ knowledge_data[HKT_CATEGORY] = category
+ knowledge_data["ascension"] = ispath(knowledge, /datum/heretic_knowledge/ultimate)
+ knowledge_data["done"] = done
//description of a knowledge might change, make sure we are not shown the initial() value in that case
- if(done)
- var/datum/heretic_knowledge/knowledge_instance = researched_knowledge[knowledge]
+ var/list/knowledge_info = researched_knowledge[knowledge]
+ if(islist(knowledge_info))
+ var/datum/heretic_knowledge/knowledge_instance = knowledge_info[HKT_INSTANCE]
+
knowledge_data["desc"] = knowledge_instance.desc
else
knowledge_data["desc"] = initial(knowledge.desc)
-
return knowledge_data
-/datum/antagonist/heretic/ui_data(mob/user)
- var/list/data = list()
- data["charges"] = knowledge_points
+/datum/antagonist/heretic/ui_interact(mob/user, datum/tgui/ui)
+ . = ..()
+ ui?.set_autoupdate(FALSE)
+
+/datum/antagonist/heretic/ui_data(mob/user)
+ var/list/data = list("charges" = knowledge_points)
+
+ data["objectives"] = get_objectives()
+ data["can_change_objective"] = can_assign_self_objectives
+
+ data["paths"] = path_info
+ data["passive_level"] = passive_level
+
data["total_sacrifices"] = total_sacrifices
data["ascended"] = ascended
- var/list/tiers = list()
+ var/list/tree_data = list()
+ var/list/shop_knowledge = list()
// This should be cached in some way, but the fact that final knowledge
// has to update its disabled state based on whether all objectives are complete,
// makes this very difficult. I'll figure it out one day maybe
- for(var/datum/heretic_knowledge/knowledge as anything in researched_knowledge)
- var/list/knowledge_data = get_knowledge_data(knowledge,TRUE)
+ for(var/knowledge_path in researched_knowledge)
+ var/list/knowledge_info = researched_knowledge[knowledge_path]
+ /// draft knowledges are only shown post-research
+ var/list/knowledge_data = get_knowledge_data(knowledge_path, researched_knowledge, TRUE, knowledge_info[HKT_CATEGORY])
+ var/category = knowledge_info[HKT_CATEGORY]
- while(GLOB.heretic_research_tree[knowledge][HKT_DEPTH] > tiers.len)
- tiers += list(list("nodes"=list()))
+ var/depth = knowledge_info[HKT_DEPTH]
+ while(depth > length(tree_data))
+ tree_data += list(list("nodes" = list()))
- tiers[GLOB.heretic_research_tree[knowledge][HKT_DEPTH]]["nodes"] += list(knowledge_data)
+ if(category == HERETIC_KNOWLEDGE_SHOP || category == HERETIC_KNOWLEDGE_DRAFT)
+ shop_knowledge += list(knowledge_data)
+ continue
- for(var/datum/heretic_knowledge/knowledge as anything in get_researchable_knowledge())
- var/list/knowledge_data = get_knowledge_data(knowledge,FALSE)
+ tree_data[depth]["nodes"] += list(knowledge_data)
+
+ // TODO: sanity for purchasing categories as bypasses are likely rn
+ var/list/heretic_tree = heretic_shops[HERETIC_KNOWLEDGE_TREE]
+ var/list/researchable_knowledges = get_researchable_knowledge()
+ for(var/datum/heretic_knowledge/knowledge_path as anything in heretic_tree)
+ if(ispath(knowledge_path, /datum/heretic_knowledge/limited_amount/starting))
+ continue
+ var/list/knowledge_info = heretic_tree[knowledge_path]
+ if(!(knowledge_info[HKT_ID] in researchable_knowledges))
+ continue
+ var/list/knowledge_data = get_knowledge_data(knowledge_path, heretic_tree, FALSE)
// Final knowledge can't be learned until all objectives are complete.
- if(ispath(knowledge, /datum/heretic_knowledge/ultimate))
- knowledge_data["disabled"] ||= !can_ascend()
+ if(ispath(knowledge_path, /datum/heretic_knowledge/ultimate))
+ var/ascension_check = can_ascend()
+ if(ascension_check != HERETIC_CAN_ASCEND)
+ knowledge_data["disabled"] = TRUE
+ knowledge_data["tooltip"] = ascension_check
- while(GLOB.heretic_research_tree[knowledge][HKT_DEPTH] > tiers.len)
- tiers += list(list("nodes"=list()))
- tiers[GLOB.heretic_research_tree[knowledge][HKT_DEPTH]]["nodes"] += list(knowledge_data)
+ var/depth = knowledge_data[HKT_DEPTH]
- data["knowledge_tiers"] = tiers
+ while(depth > length(tree_data))
+ tree_data += list(list("nodes" = list()))
- return data
+ tree_data[depth]["nodes"] += list(knowledge_data)
-/datum/antagonist/heretic/ui_static_data(mob/user)
- var/list/data = list()
- data["objectives"] = get_objectives()
- data["can_change_objective"] = can_assign_self_objectives
+ if(!heretic_path)
+ data["knowledge_tiers"] = tree_data
+ return data
+
+ var/list/heretic_drafts = heretic_shops[HERETIC_KNOWLEDGE_DRAFT]
+ for(var/datum/heretic_knowledge/knowledge_path as anything in heretic_drafts)
+ var/list/knowledge_info = heretic_drafts[knowledge_path]
+ if(!(knowledge_info[HKT_ID] in researchable_knowledges))
+ continue
+ var/list/knowledge_data = get_knowledge_data(knowledge_path, heretic_drafts, FALSE, HERETIC_KNOWLEDGE_DRAFT)
+
+ var/depth = knowledge_data[HKT_DEPTH]
+ while(depth > length(tree_data))
+ tree_data += list(list("nodes" = list()))
+
+ tree_data[depth]["nodes"] += list(knowledge_data)
+
+ data["knowledge_tiers"] = tree_data
+ var/list/shop = heretic_shops[HERETIC_KNOWLEDGE_SHOP]
+ for(var/knowledge_path in shop)
+ var/list/knowledge_info = shop[knowledge_path]
+ if(!(knowledge_info[HKT_ID] in researchable_knowledges))
+ continue
+
+ var/list/knowledge_data = get_knowledge_data(knowledge_path, shop, FALSE, HERETIC_KNOWLEDGE_SHOP)
+ shop_knowledge += list(knowledge_data)
+
+ data["knowledge_shop"] = shop_knowledge
return data
@@ -207,21 +272,27 @@
var/datum/heretic_knowledge/researched_path = text2path(params["path"])
if(!ispath(researched_path, /datum/heretic_knowledge))
CRASH("Heretic attempted to learn non-heretic_knowledge path! (Got: [researched_path || "invalid path"])")
- if(!(researched_path in get_researchable_knowledge()))
+ var/shop_category = params["category"]
+ if(!researchable_knowledge(researched_path, shop_category))
message_admins("Heretic [key_name(owner)] potentially attempted to href exploit to learn knowledge they can't learn!")
CRASH("Heretic attempted to learn knowledge they can't learn! (Got: [researched_path])")
- if(ispath(researched_path, /datum/heretic_knowledge/ultimate) && !can_ascend())
+ if(ispath(researched_path, /datum/heretic_knowledge/ultimate) & can_ascend() != HERETIC_CAN_ASCEND)
message_admins("Heretic [key_name(owner)] potentially attempted to href exploit to learn ascension knowledge without completing objectives!")
CRASH("Heretic attempted to learn a final knowledge despite not being able to ascend!")
- if(initial(researched_path.cost) > knowledge_points)
- return TRUE
- if(!gain_knowledge(researched_path))
- return TRUE
+
+ if(!purchase_knowledge(researched_path, shop_category))
+ return FALSE
+ update_data_for_all_viewers()
log_heretic_knowledge("[key_name(owner)] gained knowledge: [initial(researched_path.name)]")
- knowledge_points -= initial(researched_path.cost)
return TRUE
+/datum/antagonist/heretic/proc/researchable_knowledge(datum/heretic_knowledge/knowledge_path, shop_category = HERETIC_KNOWLEDGE_TREE)
+ var/list/knowledge_info = heretic_shops[shop_category][knowledge_path]
+ if(knowledge_info[HKT_ID] in get_researchable_knowledge())
+ return TRUE
+ return FALSE
+
/datum/antagonist/heretic/submit_player_objective(retain_existing = FALSE, retain_escape = TRUE, force = FALSE)
if (isnull(owner) || isnull(owner.current))
return
@@ -243,7 +314,7 @@
/datum/antagonist/heretic/get_preview_icon()
var/icon/icon = render_preview_outfit(preview_outfit)
- // MOTHBLOCKS TOOD: Copied and pasted from cult, make this its own proc
+ // MOTHBLOCKS TODO: Copied and pasted from cult, make this its own proc
// The sickly blade is 64x64, but getFlatIcon crunches to 32x32.
// So I'm just going to add it in post, screw it.
@@ -267,15 +338,20 @@
return ..()
/datum/antagonist/heretic/on_gain()
- if(!GLOB.heretic_research_tree)
- GLOB.heretic_research_tree = generate_heretic_research_tree()
+ generate_heretic_starting_knowledge(heretic_shops[HERETIC_KNOWLEDGE_START])
+ if(!length(path_info))
+ for(var/datum/heretic_knowledge_tree_column/path as anything in subtypesof(/datum/heretic_knowledge_tree_column))
+ path = new path()
+ path_info += list(path.get_ui_data(src, HERETIC_KNOWLEDGE_START))
+ qdel(path)
if(give_objectives)
- forge_primary_objectives()
+ forge_primary_objectives(heretic_shops[HERETIC_KNOWLEDGE_TREE])
for(var/starting_knowledge in GLOB.heretic_start_knowledge)
- gain_knowledge(starting_knowledge)
+ gain_knowledge(starting_knowledge, HERETIC_KNOWLEDGE_START, update = FALSE)
+ owner.current.AddElement(/datum/element/leeching_walk/minor)
ADD_TRAIT(owner, TRAIT_SEE_BLESSED_TILES, REF(src))
addtimer(CALLBACK(src, PROC_REF(passive_influence_gain)), passive_gain_timer) // Gain +1 knowledge every 20 minutes.
@@ -283,12 +359,15 @@
/datum/antagonist/heretic/on_removal()
if(owner.current)
- for(var/knowledge_index in researched_knowledge)
- var/datum/heretic_knowledge/knowledge = researched_knowledge[knowledge_index]
+ for(var/knowledge_path in researched_knowledge)
+ var/datum/heretic_knowledge/knowledge = researched_knowledge[knowledge_path][HKT_INSTANCE]
knowledge.on_lose(owner.current, src)
+ QDEL_NULL(researched_knowledge[knowledge_path][HKT_INSTANCE])
REMOVE_TRAIT(owner, TRAIT_SEE_BLESSED_TILES, REF(src))
- QDEL_LIST_ASSOC_VAL(researched_knowledge)
+ owner.current.RemoveElement(/datum/element/leeching_walk/minor)
+ QDEL_NULL(heretic_path)
+ owner.current.cut_overlay(eldritch_overlay)
return ..()
/datum/antagonist/heretic/apply_innate_effects(mob/living/mob_override)
@@ -296,7 +375,7 @@
handle_clown_mutation(our_mob, "Ancient knowledge described to you has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.")
our_mob.faction |= FACTION_HERETIC
- if (!issilicon(our_mob))
+ if(!issilicon(our_mob))
GLOB.reality_smash_track.add_tracked_mind(owner)
ADD_TRAIT(our_mob, TRAIT_MANSUS_TOUCHED, REF(src))
@@ -304,31 +383,89 @@
RegisterSignals(our_mob, list(COMSIG_MOB_BEFORE_SPELL_CAST, COMSIG_MOB_SPELL_ACTIVATED), PROC_REF(on_spell_cast))
RegisterSignal(our_mob, COMSIG_USER_ITEM_INTERACTION, PROC_REF(on_item_use))
RegisterSignal(our_mob, COMSIG_LIVING_POST_FULLY_HEAL, PROC_REF(after_fully_healed))
+ RegisterSignal(our_mob, COMSIG_ATOM_EXAMINE, PROC_REF(on_heretic_examine))
+
+ RegisterSignals(
+ our_mob,
+ list(SIGNAL_ADDTRAIT(TRAIT_HERETIC_AURA_HIDDEN), SIGNAL_REMOVETRAIT(TRAIT_HERETIC_AURA_HIDDEN)),
+ PROC_REF(update_heretic_aura)
+ )
/datum/antagonist/heretic/remove_innate_effects(mob/living/mob_override)
var/mob/living/our_mob = mob_override || owner.current
handle_clown_mutation(our_mob, removing = FALSE)
our_mob.faction -= FACTION_HERETIC
- if (owner in GLOB.reality_smash_track.tracked_heretics)
+ if(owner in GLOB.reality_smash_track.tracked_heretics)
GLOB.reality_smash_track.remove_tracked_mind(owner)
REMOVE_TRAIT(our_mob, TRAIT_MANSUS_TOUCHED, REF(src))
- UnregisterSignal(our_mob, list(
- COMSIG_MOB_BEFORE_SPELL_CAST,
- COMSIG_MOB_SPELL_ACTIVATED,
- COMSIG_USER_ITEM_INTERACTION,
- COMSIG_LIVING_POST_FULLY_HEAL,
- COMSIG_LIVING_CULT_SACRIFICED,
- ))
+ UnregisterSignal(
+ our_mob,
+ list(
+ COMSIG_MOB_BEFORE_SPELL_CAST,
+ COMSIG_MOB_SPELL_ACTIVATED,
+ COMSIG_USER_ITEM_INTERACTION,
+ COMSIG_LIVING_POST_FULLY_HEAL,
+ COMSIG_LIVING_CULT_SACRIFICED,
+ COMSIG_ATOM_EXAMINE,
+ SIGNAL_ADDTRAIT(TRAIT_HERETIC_AURA_HIDDEN),
+ SIGNAL_REMOVETRAIT(TRAIT_HERETIC_AURA_HIDDEN)
+ )
+ )
+
+/// Removes the ability to blade break, removes cloak of shadows and removes the cap on how many blades you can craft
+/datum/antagonist/heretic/proc/disable_blade_breaking()
+ if(unlimited_blades)
+ return
+ var/mob/heretic_mob = owner.current
+ unlimited_blades = TRUE
+ to_chat(heretic_mob, span_boldwarning("You have gained a lot of power, the mansus will no longer allow you to break your blades, but you can now make as many as you wish."))
+ heretic_mob.balloon_alert(heretic_mob, "blade breaking disabled!")
+ update_heretic_aura()
+ var/datum/action/cooldown/spell/shadow_cloak/cloak_spell = locate() in heretic_mob.actions
+ cloak_spell.Remove(heretic_mob)
+
+/// Adds an overlay to the heretic
+/datum/antagonist/heretic/proc/update_heretic_aura()
+ SIGNAL_HANDLER
+ var/mob/heretic_mob = owner.current
+ heretic_mob.cut_overlay(eldritch_overlay)
+
+ if(!should_show_aura())
+ return FALSE
+
+ heretic_mob.add_overlay(eldritch_overlay)
+ return TRUE
+
+/datum/antagonist/heretic/proc/should_show_aura()
+ if(!can_assign_self_objectives)
+ return FALSE // We spurned the offer of the Mansus :(
+ if(!unlimited_blades || HAS_TRAIT(owner.current, TRAIT_HERETIC_AURA_HIDDEN))
+ return FALSE // No aura if we have the trait or is too early still
+ if(feast_of_owls)
+ return FALSE // No use in giving the aura to a heretic that can't ascend
+ if(heretic_path?.route == PATH_LOCK)
+ return FALSE // Lock heretics never get this aura
+ return TRUE
+
+/datum/antagonist/heretic/proc/on_heretic_examine(datum/source, mob/user, text)
+ SIGNAL_HANDLER
+ if(!should_show_aura())
+ return
+ var/mob/heretic_mob = owner.current
+ var/potential_string = "[heretic_mob.p_They()] [heretic_mob.p_are()] crackling with a swirling green vortex of energy."
+ if(can_ascend() == HERETIC_CAN_ASCEND)
+ potential_string += " [heretic_mob.p_They()] [heretic_mob.p_are()] shedding [heretic_mob.p_their()] mortal shell!"
+ text += span_green(potential_string)
/datum/antagonist/heretic/on_body_transfer(mob/living/old_body, mob/living/new_body)
. = ..()
if(old_body == new_body) // if they were using a temporary body
return
- for(var/knowledge_index in researched_knowledge)
- var/datum/heretic_knowledge/knowledge = researched_knowledge[knowledge_index]
+ for(var/knowledge_path in researched_knowledge)
+ var/datum/heretic_knowledge/knowledge = researched_knowledge[knowledge_path][HKT_INSTANCE]
knowledge.on_lose(old_body, src)
knowledge.on_gain(new_body, src)
@@ -415,7 +552,7 @@
/datum/antagonist/heretic/proc/draw_rune(mob/living/user, turf/target_turf, drawing_time = 20 SECONDS, additional_checks)
drawing_rune = TRUE
- var/rune_colour = GLOB.heretic_path_to_color[heretic_path]
+ var/rune_colour = GLOB.heretic_path_to_color[heretic_path?.route || PATH_START]
target_turf.balloon_alert(user, "drawing rune...")
var/obj/effect/temp_visual/drawing_heretic_rune/drawing_effect
if (drawing_time < (10 SECONDS))
@@ -569,8 +706,8 @@
/**
* Create our objectives for our heretic.
*/
-/datum/antagonist/heretic/proc/forge_primary_objectives()
- var/datum/objective/heretic_research/research_objective = new()
+/datum/antagonist/heretic/proc/forge_primary_objectives(heretic_research_tree)
+ var/datum/objective/heretic_research/research_objective = new(heretic_research_tree = heretic_research_tree)
research_objective.owner = owner
objectives += research_objective
@@ -582,7 +719,7 @@
var/datum/objective/minor_sacrifice/sac_objective = new()
sac_objective.owner = owner
if(num_heads < 2) // They won't get major sacrifice, so bump up minor sacrifice a bit
- sac_objective.target_amount += 2
+ sac_objective.target_amount = 5
sac_objective.update_explanation_text()
objectives += sac_objective
@@ -630,11 +767,19 @@
* Used in callbacks for passive gain over time.
*/
/datum/antagonist/heretic/proc/passive_influence_gain()
- knowledge_points++
- if(owner.current.stat <= SOFT_CRIT)
+ adjust_knowledge_points(1)
+ if(owner?.current?.stat <= SOFT_CRIT)
to_chat(owner.current, "[span_hear("You hear a whisper...")] [span_hypnophrase(pick_list(HERETIC_INFLUENCE_FILE, "drain_message"))]")
addtimer(CALLBACK(src, PROC_REF(passive_influence_gain)), passive_gain_timer)
+/datum/antagonist/heretic/proc/adjust_knowledge_points(amount, update = TRUE)
+ knowledge_points = max(0, knowledge_points + amount) // Don't allow negative knowledge points
+ knowledge_gained += max(0, amount)
+ if(knowledge_gained > 8 && !unlimited_blades)
+ disable_blade_breaking()
+ if(update)
+ update_data_for_all_viewers()
+
/datum/antagonist/heretic/roundend_report()
var/list/parts = list()
@@ -665,8 +810,9 @@
var/list/string_of_knowledge = list()
- for(var/knowledge_index in researched_knowledge)
- var/datum/heretic_knowledge/knowledge = researched_knowledge[knowledge_index]
+ for(var/knowledge_path in researched_knowledge)
+ var/list/knowledge_info = researched_knowledge[knowledge_path]
+ var/datum/heretic_knowledge/knowledge = knowledge_info[HKT_INSTANCE]
string_of_knowledge += knowledge.name
parts += english_list(string_of_knowledge)
@@ -758,7 +904,7 @@
if(!change_num || QDELETED(src))
return
- knowledge_points += change_num
+ adjust_knowledge_points(change_num)
/**
* Admin proc for giving a heretic a focus.
@@ -775,8 +921,8 @@
/datum/antagonist/heretic/antag_panel_data()
var/list/string_of_knowledge = list()
- for(var/knowledge_index in researched_knowledge)
- var/datum/heretic_knowledge/knowledge = researched_knowledge[knowledge_index]
+ for(var/knowledge_path in researched_knowledge)
+ var/datum/heretic_knowledge/knowledge = researched_knowledge[knowledge_path][HKT_INSTANCE]
if(istype(knowledge, /datum/heretic_knowledge/ultimate))
string_of_knowledge += span_bold(knowledge.name)
else
@@ -797,35 +943,72 @@
. += "None! "
. += " "
+/datum/antagonist/heretic/proc/purchase_knowledge(datum/heretic_knowledge/knowledge_type, category = HERETIC_KNOWLEDGE_TREE, update = TRUE)
+ var/list/shop_list = heretic_shops[category]
+ if(!shop_list)
+ stack_trace("Heretic attempted to learn knowledge from a non-existent category! (Got: [category])")
+ return FALSE
+
+ var/list/knowledge_data = shop_list[knowledge_type]
+ if(!knowledge_data)
+ stack_trace("[type] purchase_knowledge was given a path that doesn't exist in the heretic [category] knowledge list! (Got: [knowledge_type])")
+ return FALSE
+
+ var/cost = knowledge_data[HKT_COST]
+ if(cost > knowledge_points)
+ return FALSE
+ if(!gain_knowledge(knowledge_type, category, update))
+ return FALSE
+ adjust_knowledge_points(-cost, FALSE)
+ return TRUE
/**
* Learns the passed [typepath] of knowledge, creating a knowledge datum
* and adding it to our researched knowledge list.
*
* Returns TRUE if the knowledge was added successfully. FALSE otherwise.
*/
-/datum/antagonist/heretic/proc/gain_knowledge(datum/heretic_knowledge/knowledge_type)
+/datum/antagonist/heretic/proc/gain_knowledge(datum/heretic_knowledge/knowledge_type, category = HERETIC_KNOWLEDGE_TREE, update = TRUE)
+ var/list/knowledge_list = heretic_shops[category]
if(!ispath(knowledge_type))
stack_trace("[type] gain_knowledge was given an invalid path! (Got: [knowledge_type])")
return FALSE
+ var/list/knowledge_data = knowledge_list[knowledge_type]
+ if(!islist(knowledge_data))
+ knowledge_data = make_knowledge_entry(knowledge_type, category)
+ heretic_shops[category][knowledge_type] = knowledge_data
if(get_knowledge(knowledge_type))
return FALSE
var/datum/heretic_knowledge/initialized_knowledge = new knowledge_type()
- researched_knowledge[knowledge_type] = initialized_knowledge
+ if(!initialized_knowledge.pre_research(owner.current, src))
+ return FALSE
+ researched_knowledge[knowledge_type] = knowledge_data.Copy()
+ researched_knowledge[knowledge_type][HKT_INSTANCE] = initialized_knowledge
+ researched_knowledge[knowledge_type][HKT_CATEGORY] = category
+
+ // case for letting you modify depth post-purchase
+ var/purchased_depth = knowledge_data[HKT_PURCHASED_DEPTH]
+ if(purchased_depth != 0 && isnum(purchased_depth))
+ researched_knowledge[knowledge_type][HKT_DEPTH] = purchased_depth
+
+ knowledge_list -= knowledge_type
+
initialized_knowledge.on_research(owner.current, src)
- update_static_data(owner.current)
+ if(update)
+ update_data_for_all_viewers()
+
return TRUE
/**
- * Get a list of all knowledge TYPEPATHS that we can currently research.
+ * Get a list of all knowledge IDs that we can currently research.
*/
/datum/antagonist/heretic/proc/get_researchable_knowledge()
var/list/researchable_knowledge = list()
var/list/banned_knowledge = list()
- for(var/knowledge_index in researched_knowledge)
- var/datum/heretic_knowledge/knowledge = researched_knowledge[knowledge_index]
- researchable_knowledge |= GLOB.heretic_research_tree[knowledge_index][HKT_NEXT]
- banned_knowledge |= GLOB.heretic_research_tree[knowledge_index][HKT_BAN]
- banned_knowledge |= knowledge.type
+ for(var/knowledge_type in researched_knowledge)
+ var/list/knowledge_info = researched_knowledge[knowledge_type]
+ researchable_knowledge |= knowledge_info[HKT_NEXT]
+ banned_knowledge |= knowledge_info[HKT_BAN]
+ banned_knowledge |= knowledge_type
researchable_knowledge -= banned_knowledge
return researchable_knowledge
@@ -833,7 +1016,10 @@
* Check if the wanted type-path is in the list of research knowledge.
*/
/datum/antagonist/heretic/proc/get_knowledge(wanted)
- return researched_knowledge[wanted]
+ var/list/knowledge_data = researched_knowledge[wanted]
+ if(knowledge_data)
+ return knowledge_data[HKT_INSTANCE]
+ return null
/// Makes our heretic more able to rust things.
/// if side_path_only is set to TRUE, this function does nothing for rust heretics.
@@ -852,8 +1038,8 @@
/datum/antagonist/heretic/proc/get_rituals()
var/list/rituals = list()
- for(var/knowledge_index in researched_knowledge)
- var/datum/heretic_knowledge/knowledge = researched_knowledge[knowledge_index]
+ for(var/knowledge_path in researched_knowledge)
+ var/datum/heretic_knowledge/knowledge = researched_knowledge[knowledge_path][HKT_INSTANCE]
if(!knowledge.can_be_invoked(src))
continue
rituals[knowledge.name] = knowledge
@@ -866,14 +1052,19 @@
* Returns FALSE if not all of our objectives are complete, or TRUE otherwise.
*/
/datum/antagonist/heretic/proc/can_ascend()
- if(!can_assign_self_objectives)
- return FALSE // We spurned the offer of the Mansus :(
if(feast_of_owls)
- return FALSE // We sold our ambition for immediate power :/
+ return "The owls have taken your right of ascension (denied ascension)." // We sold our ambition for immediate power :/
+ if(!can_assign_self_objectives)
+ return "The mansus has spurned you (denied ascension)."
for(var/datum/objective/must_be_done as anything in objectives)
if(!must_be_done.check_completion())
- return FALSE
- return TRUE
+ return "Must complete all objectives before ascending."
+ var/config_time = CONFIG_GET(number/minimum_ascension_time) MINUTES
+
+ var/time_passed = STATION_TIME_PASSED()
+ if(config_time >= time_passed)
+ return "Too early, must wait [DisplayTimeText(config_time - time_passed)] before ascending."
+ return HERETIC_CAN_ASCEND
/**
* Helper to determine if a Heretic
@@ -901,7 +1092,7 @@
/datum/objective/minor_sacrifice/New(text)
. = ..()
- target_amount = rand(3, 4)
+ target_amount = 4
update_explanation_text()
/datum/objective/minor_sacrifice/update_explanation_text()
@@ -932,7 +1123,7 @@
/// The length of a main path. Calculated once in New().
var/static/main_path_length = 0
-/datum/objective/heretic_research/New(text)
+/datum/objective/heretic_research/New(text, heretic_research_tree)
. = ..()
if(!main_path_length)
@@ -940,7 +1131,8 @@
// (All the main paths are (should be) the same length, so it doesn't matter.)
var/rust_paths_found = 0
for(var/datum/heretic_knowledge/knowledge as anything in subtypesof(/datum/heretic_knowledge))
- if(GLOB.heretic_research_tree[knowledge][HKT_ROUTE] == PATH_RUST)
+ var/list/knowledge_data = heretic_research_tree[knowledge]
+ if(knowledge_data && knowledge_data[HKT_ROUTE] == PATH_RUST)
rust_paths_found++
main_path_length = rust_paths_found
diff --git a/code/modules/antagonists/heretic/heretic_knowledge.dm b/code/modules/antagonists/heretic/heretic_knowledge.dm
index 97e701c717d..5300f6d1943 100644
--- a/code/modules/antagonists/heretic/heretic_knowledge.dm
+++ b/code/modules/antagonists/heretic/heretic_knowledge.dm
@@ -32,6 +32,8 @@
var/priority = 0
///If this is considered starting knowledge, TRUE if yes
var/is_starting_knowledge = FALSE
+ /// If the spell is final knowledge, disables blade breaking and removes the cap on how many blades we can make
+ var/is_final_knowledge = FALSE
/// In case we want to override the default UI icon getter and plug in our own icon instead.
/// if research_tree_icon_path is not null, research_tree_icon_state must also be specified or things may break
var/research_tree_icon_path
@@ -40,6 +42,23 @@
var/research_tree_icon_dir = SOUTH
///Determines what kind of monster ghosts will ignore from here on out. Defaults to POLL_IGNORE_HERETIC_MONSTER, but we define other types of monsters for more granularity.
var/poll_ignore_define = POLL_IGNORE_HERETIC_MONSTER
+ /// This is used for the drafting system. By default is 0 (Meaning it won't show up in the draft), also makes it show up in the shop according to this tier
+ var/drafting_tier = 0
+ /// decides if it's added to the shop, only, and not drafts
+ var/is_shop_only = FALSE
+
+/**
+ * Called before the knowledge is researched,
+ * use this for any checks that should happen before the knowledge is researched.
+ * Returns TRUE if the knowledge can be researched, FALSE otherwise.
+ */
+/datum/heretic_knowledge/proc/pre_research(mob/user, datum/antagonist/heretic/our_heretic)
+ // consider moving this check to a type instead
+ if(is_final_knowledge && !our_heretic.unlimited_blades)
+ var/choice = tgui_alert(user, "THIS WILL DISABLE BLADE BREAKING, Are you ready to research this? The blade cap will also be removed.", "Get Final Spell?", list("Yes", "No"))
+ if(choice != "Yes")
+ return FALSE
+ return TRUE
/** Called when the knowledge is first researched.
* This is only ever called once per heretic.
@@ -54,6 +73,8 @@
if(gain_text)
to_chat(user, span_warning("[gain_text]"))
on_gain(user, our_heretic)
+ if(is_final_knowledge && !our_heretic.unlimited_blades)
+ our_heretic.disable_blade_breaking()
/**
* Called when the knowledge is applied to a mob.
@@ -210,6 +231,11 @@
return ..()
/datum/heretic_knowledge/limited_amount/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc)
+ var/datum/antagonist/heretic/our_heretic = IS_HERETIC(user)
+ if(our_heretic && our_heretic.unlimited_blades)
+ if(length(result_atoms & typesof(/obj/item/melee/sickly_blade)))
+ return TRUE
+
for(var/datum/weakref/ref as anything in created_items)
var/atom/real_thing = ref.resolve()
if(QDELETED(real_thing))
@@ -230,6 +256,7 @@
/**
* A knowledge subtype for limited_amount knowledge
* used for base knowledge (the ones that make blades)
+ * Grants your path-relevant grasp upgrade, passive and grasp mark
*
* A heretic can only learn one /starting type knowledge,
* and their ascension depends on whichever they chose.
@@ -239,38 +266,55 @@
limit = 2
cost = 1
priority = MAX_KNOWLEDGE_PRIORITY - 5
+ /// The status effect typepath we apply on people on mansus grasp.
+ var/datum/status_effect/eldritch/mark_type
+ /// The status effect of our passive
+ var/datum/status_effect/heretic_passive/eldritch_passive = /datum/status_effect/heretic_passive
/datum/heretic_knowledge/limited_amount/starting/on_research(mob/user, datum/antagonist/heretic/our_heretic)
. = ..()
- our_heretic.heretic_path = GLOB.heretic_research_tree[type][HKT_ROUTE]
- SSblackbox.record_feedback("tally", "heretic_path_taken", 1, our_heretic.heretic_path)
+ for(var/datum/heretic_knowledge_tree_column/column_path as anything in subtypesof(/datum/heretic_knowledge_tree_column))
+ if(column_path::route != our_heretic.researched_knowledge[type][HKT_ROUTE])
+ continue
+ our_heretic.heretic_path = new column_path()
+ if(!our_heretic.heretic_path)
+ // If we don't have a path, we can't continue.
+ to_chat(user, span_warning("Oh shit, something broke, no path found!"))
+ stack_trace("failed to find valid path [our_heretic.heretic_shops[HERETIC_KNOWLEDGE_TREE][type][HKT_ROUTE]] from researching [src]")
+ return
+ SSblackbox.record_feedback("tally", "heretic_path_taken", 1, our_heretic.heretic_path.route)
+ our_heretic.update_heretic_aura()
+ our_heretic.generate_heretic_research_tree()
+ determine_drafted_knowledge(
+ our_heretic.heretic_path.route,
+ our_heretic.heretic_shops[HERETIC_KNOWLEDGE_TREE],
+ our_heretic.heretic_shops[HERETIC_KNOWLEDGE_SHOP],
+ our_heretic.heretic_shops[HERETIC_KNOWLEDGE_DRAFT],
+ )
+ SEND_SIGNAL(src, COMSIG_HERETIC_SHOP_SETUP)
-/**
- * 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
- 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, datum/antagonist/heretic/our_heretic)
+/datum/heretic_knowledge/limited_amount/starting/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
RegisterSignals(user, list(COMSIG_HERETIC_MANSUS_GRASP_ATTACK, COMSIG_LIONHUNTER_ON_HIT), PROC_REF(on_mansus_grasp))
RegisterSignal(user, COMSIG_HERETIC_BLADE_ATTACK, PROC_REF(on_eldritch_blade))
+ if(isliving(user))
+ var/mob/living/living_user = user
+ living_user.apply_status_effect(eldritch_passive)
-/datum/heretic_knowledge/mark/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
+/datum/heretic_knowledge/limited_amount/starting/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
UnregisterSignal(user, list(COMSIG_HERETIC_MANSUS_GRASP_ATTACK, COMSIG_HERETIC_BLADE_ATTACK))
+ if(isliving(user))
+ var/mob/living/living_user = user
+ living_user.remove_status_effect(eldritch_passive)
/**
* 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)
+/datum/heretic_knowledge/limited_amount/starting/proc/on_mansus_grasp(mob/living/source, mob/living/target)
SIGNAL_HANDLER
+ SHOULD_CALL_PARENT(TRUE)
create_mark(source, target)
@@ -279,7 +323,7 @@
*
* 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)
+/datum/heretic_knowledge/limited_amount/starting/proc/on_eldritch_blade(mob/living/source, mob/living/target, obj/item/melee/sickly_blade/blade)
SIGNAL_HANDLER
if(!isliving(target))
@@ -293,7 +337,7 @@
*
* 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)
+/datum/heretic_knowledge/limited_amount/starting/proc/create_mark(mob/living/source, mob/living/target)
if(target.stat == DEAD)
return
return target.apply_status_effect(mark_type)
@@ -303,7 +347,7 @@
*
* 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)
+/datum/heretic_knowledge/limited_amount/starting/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
@@ -319,7 +363,7 @@
*/
/datum/heretic_knowledge/blade_upgrade
abstract_parent_type = /datum/heretic_knowledge/blade_upgrade
- cost = 2
+ cost = 1
/datum/heretic_knowledge/blade_upgrade/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
RegisterSignal(user, COMSIG_HERETIC_BLADE_ATTACK, PROC_REF(on_eldritch_blade))
@@ -416,7 +460,6 @@
var/datum/antagonist/heretic_monster/heretic_monster = summoned.mind.add_antag_datum(/datum/antagonist/heretic_monster)
heretic_monster.set_owner(user.mind)
- ADD_TRAIT(heretic_monster, TRAIT_HERETIC_SUMMON, INNATE_TRAIT)
var/datum/objective/heretic_summon/summon_objective = locate() in user.mind.get_all_objectives()
summon_objective?.num_summoned++
@@ -467,22 +510,18 @@
)
var/static/list/potential_uncommoner_items = list(
- /obj/item/restraints/legcuffs/beartrap,
/obj/item/restraints/handcuffs/cable/zipties,
+ /obj/item/melee/baton,
/obj/item/circular_saw,
/obj/item/scalpel,
/obj/item/clothing/gloves/color/yellow,
- /obj/item/melee/baton/security,
/obj/item/clothing/glasses/sunglasses,
)
required_atoms = list()
- // 2 organs. Can be the same.
+ // 1 Organ, 1 Easy, 1 Hard
required_atoms[pick(potential_organs)] += 1
- required_atoms[pick(potential_organs)] += 1
- // 2-3 random easy items.
- required_atoms[pick(potential_easy_items)] += rand(2, 3)
- // 1 uncommon item.
+ required_atoms[pick(potential_easy_items)] += 1
required_atoms[pick(potential_uncommoner_items)] += 1
/datum/heretic_knowledge/knowledge_ritual/on_research(mob/user, datum/antagonist/heretic/our_heretic)
@@ -508,7 +547,7 @@
/datum/heretic_knowledge/knowledge_ritual/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc)
var/datum/antagonist/heretic/our_heretic = GET_HERETIC(user)
- our_heretic.knowledge_points += KNOWLEDGE_RITUAL_POINTS
+ our_heretic.adjust_knowledge_points(KNOWLEDGE_RITUAL_POINTS)
was_completed = TRUE
to_chat(user, span_boldnotice("[name] completed!"))
@@ -516,6 +555,7 @@
desc += " (Completed!)"
log_heretic_knowledge("[key_name(user)] completed a [name] at [gameTimestamp()].")
user.add_mob_memory(/datum/memory/heretic_knowledge_ritual)
+ SEND_SIGNAL(our_heretic, COMSIG_HERETIC_PASSIVE_UPGRADE_FINAL)
return TRUE
#undef KNOWLEDGE_RITUAL_POINTS
@@ -540,8 +580,9 @@
/datum/heretic_knowledge/ultimate/on_research(mob/user, datum/antagonist/heretic/our_heretic)
. = ..()
var/total_points = 0
- for(var/datum/heretic_knowledge/knowledge as anything in assoc_to_values(our_heretic.researched_knowledge))
- total_points += knowledge.cost
+ for(var/datum/heretic_knowledge/knowledge as anything in our_heretic.researched_knowledge)
+ var/list/cost = our_heretic.researched_knowledge[knowledge][HKT_COST]
+ total_points += cost
log_heretic_knowledge("[key_name(user)] gained knowledge of their final ritual at [gameTimestamp()]. \
They have [length(our_heretic.researched_knowledge)] knowledge nodes researched, totalling [total_points] points \
@@ -551,7 +592,7 @@
if(invoker.ascended)
return FALSE
- if(!invoker.can_ascend())
+ if(invoker.can_ascend() != HERETIC_CAN_ASCEND)
return FALSE
return TRUE
@@ -578,8 +619,11 @@
return (sacrifice.stat == DEAD) && !ismonkey(sacrifice)
/datum/heretic_knowledge/ultimate/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc)
+
var/datum/antagonist/heretic/heretic_datum = GET_HERETIC(user)
heretic_datum.ascended = TRUE
+ // In case we skipped ritual of knowledge
+ SEND_SIGNAL(heretic_datum, COMSIG_HERETIC_PASSIVE_UPGRADE_FINAL)
// Show the cool red gradiant in our UI
heretic_datum.update_static_data(user)
@@ -589,7 +633,7 @@
human_user.physiology.brute_mod *= 0.5
human_user.physiology.burn_mod *= 0.5
- SSblackbox.record_feedback("tally", "heretic_ascended", 1, GLOB.heretic_research_tree[type][HKT_ROUTE])
+ SSblackbox.record_feedback("tally", "heretic_ascended", 1, heretic_datum.heretic_path.route)
log_heretic_knowledge("[key_name(user)] completed their final ritual at [gameTimestamp()].")
notify_ghosts(
"[user.real_name] has completed an ascension ritual!",
@@ -603,9 +647,13 @@
color_override = "pink",
)
+ if(EMERGENCY_IDLE_OR_RECALLED)
+ SSshuttle.call_evac_shuttle("Critical reality rupture detected on supranatural casuality long-range scanners. Mass crew casualty and possible station destruction determined to be beyond acceptable probability. Priority evacuation shuttle dispatched.")
+ SSshuttle.emergency_no_recall = TRUE
+
if(!isnull(ascension_achievement))
user.client?.give_award(ascension_achievement, user)
- heretic_datum.increase_rust_strength()
+ heretic_datum.rust_strength = 4 // Ascended heretics can rust whatever they want (below RUST_RESISTANCE_ABSOLUTE)
ADD_TRAIT(user, TRAIT_DESENSITIZED, type)
return TRUE
diff --git a/code/modules/antagonists/heretic/heretic_monsters.dm b/code/modules/antagonists/heretic/heretic_monsters.dm
index 53ca3b88aac..08215531b12 100644
--- a/code/modules/antagonists/heretic/heretic_monsters.dm
+++ b/code/modules/antagonists/heretic/heretic_monsters.dm
@@ -23,6 +23,16 @@
master = null
return ..()
+/datum/antagonist/heretic_monster/apply_innate_effects(mob/living/mob_override)
+ . = ..()
+ var/mob/living/target = mob_override || owner.current
+ ADD_TRAIT(target, TRAIT_HERETIC_SUMMON, REF(src))
+
+/datum/antagonist/heretic_monster/remove_innate_effects(mob/living/mob_override)
+ var/mob/living/target = mob_override || owner.current
+ REMOVE_TRAIT(target, TRAIT_HERETIC_SUMMON, REF(src))
+ return ..()
+
/*
* Set our [master] var to a new mind.
*/
diff --git a/code/modules/antagonists/heretic/influences.dm b/code/modules/antagonists/heretic/influences.dm
index 426ef7a2018..b92e8487822 100644
--- a/code/modules/antagonists/heretic/influences.dm
+++ b/code/modules/antagonists/heretic/influences.dm
@@ -170,6 +170,8 @@
var/being_drained = FALSE
/// The icon state applied to the image created for this influence.
var/real_icon_state = "reality_smash"
+ /// Proximity monitor that gives any nearby heretics x-ray vision
+ var/datum/proximity_monitor/influence_monitor/monitor
/obj/effect/heretic_influence/Initialize(mapload)
. = ..()
@@ -182,12 +184,14 @@
AddElement(/datum/element/block_turf_fingerprints)
AddComponent(/datum/component/redirect_attack_hand_from_turf, interact_check = CALLBACK(src, PROC_REF(verify_user_can_see)))
AddComponent(/datum/component/fishing_spot, GLOB.preset_fish_sources[/datum/fish_source/dimensional_rift])
+ monitor = new(src, 7)
/obj/effect/heretic_influence/proc/verify_user_can_see(mob/user)
return (user.mind in GLOB.reality_smash_track.tracked_heretics)
/obj/effect/heretic_influence/Destroy()
GLOB.reality_smash_track.smashes -= src
+ QDEL_NULL(monitor)
return ..()
/obj/effect/heretic_influence/attack_hand_secondary(mob/user, list/modifiers)
@@ -224,21 +228,29 @@
*
* If successful, the influence is drained and deleted.
*/
-/obj/effect/heretic_influence/proc/drain_influence(mob/living/user, knowledge_to_gain, drain_speed = 10 SECONDS)
+/obj/effect/heretic_influence/proc/drain_influence(mob/living/user, knowledge_to_gain, drain_speed = HERETIC_RIFT_DEFAULT_DRAIN_SPEED)
being_drained = TRUE
loc.balloon_alert(user, "draining influence...")
+ // Only gives you the dripping eye effect if you have faster drain speed than default
+ var/mutable_appearance/draining_overlay = mutable_appearance('icons/mob/effects/heretic_aura.dmi', "heretic_eye_dripping")
+ if(drain_speed < HERETIC_RIFT_DEFAULT_DRAIN_SPEED)
+ draining_overlay.pixel_y = 16
+ user.add_overlay(draining_overlay)
+
if(!do_after(user, drain_speed, src, hidden = TRUE))
being_drained = FALSE
loc.balloon_alert(user, "interrupted!")
+ user.cut_overlay(draining_overlay)
return
// We don't need to set being_drained back since we delete after anyways
loc.balloon_alert(user, "influence drained")
+ user.cut_overlay(draining_overlay)
var/datum/antagonist/heretic/heretic_datum = GET_HERETIC(user)
- heretic_datum.knowledge_points += knowledge_to_gain
+ heretic_datum.adjust_knowledge_points(knowledge_to_gain)
// Aaand now we delete it
after_drain(user)
@@ -269,3 +281,19 @@
/datum/atom_hud/alternate_appearance/basic/has_antagonist/heretic
antag_datum_type = /datum/antagonist/heretic
add_ghost_version = TRUE
+
+/datum/proximity_monitor/influence_monitor
+ /// Cooldown before we can give another heretic xray
+ COOLDOWN_DECLARE(xray_cooldown)
+
+/datum/proximity_monitor/influence_monitor/on_entered(atom/source, atom/movable/arrived, turf/old_loc)
+ . = ..()
+ if(!isliving(arrived))
+ return
+ if(!COOLDOWN_FINISHED(src, xray_cooldown))
+ return
+ var/mob/living/arrived_living = arrived
+ if(!IS_HERETIC(arrived_living))
+ return
+ arrived_living.apply_status_effect(/datum/status_effect/temporary_xray/eldritch)
+ COOLDOWN_START(src, xray_cooldown, 3 MINUTES)
diff --git a/code/modules/antagonists/heretic/items/eldritch_painting.dm b/code/modules/antagonists/heretic/items/eldritch_painting.dm
index f42670b4cc8..c8b1b6f2737 100644
--- a/code/modules/antagonists/heretic/items/eldritch_painting.dm
+++ b/code/modules/antagonists/heretic/items/eldritch_painting.dm
@@ -44,7 +44,7 @@
return
if(IS_HERETIC(viewer))
return
- if(viewer.can_block_magic(MAGIC_RESISTANCE|MAGIC_RESISTANCE_MIND))
+ if(viewer.can_block_magic(MAGIC_RESISTANCE_MOON))
return
if(viewer.reagents.has_reagent(/datum/reagent/water/holywater))
return
@@ -54,7 +54,7 @@
to_chat(viewer, span_hypnophrase("Your mind is overcome! The painting leaves a mark on your psyche."))
/obj/structure/sign/painting/eldritch/wirecutter_act(mob/living/user, obj/item/I)
- if(!user.can_block_magic(MAGIC_RESISTANCE|MAGIC_RESISTANCE_MIND))
+ if(!user.can_block_magic(MAGIC_RESISTANCE_MOON))
user.add_mood_event("ripped_eldritch_painting", /datum/mood_event/eldritch_painting)
to_chat(user, span_hypnophrase("There's an itch in your brain. It's laughing at you..."))
qdel(src)
diff --git a/code/modules/antagonists/heretic/items/forbidden_book.dm b/code/modules/antagonists/heretic/items/forbidden_book.dm
index d3c22d7b687..184bc088bb3 100644
--- a/code/modules/antagonists/heretic/items/forbidden_book.dm
+++ b/code/modules/antagonists/heretic/items/forbidden_book.dm
@@ -11,7 +11,7 @@
/// Helps determine the icon state of this item when it's used on self.
var/book_open = FALSE
/// How fast we can drain influences
- var/drain_speed = 10 SECONDS
+ var/drain_speed = 5 SECONDS
/// How fast we can draw runes
var/draw_speed = 8 SECONDS
@@ -79,7 +79,7 @@
desc = "A hideous, ragged book covered in separately-blinking eyes, all of them staring at you. You have no idea how to hold this thing, and to be honest you're not sure if you want to."
base_icon_state = "book_morbus"
icon_state = "book_morbus"
- drain_speed = 7 SECONDS
+ drain_speed = 2.5 SECONDS
draw_speed = 5 SECONDS
/// List of mobs we've cursed with transmutation. When the codex is destroyed all those curses become undone
var/list/transmuted_victims = list()
diff --git a/code/modules/antagonists/heretic/items/heretic_armor.dm b/code/modules/antagonists/heretic/items/heretic_armor.dm
index 027f3fc2cf7..85e1dbf8ae2 100644
--- a/code/modules/antagonists/heretic/items/heretic_armor.dm
+++ b/code/modules/antagonists/heretic/items/heretic_armor.dm
@@ -1,42 +1,55 @@
+/*!
+ * Contains the eldritch robes for heretics, a suit of armor that they can make via a ritual
+ */
+
// Eldritch armor. Looks cool, hood lets you cast heretic spells.
-/obj/item/clothing/head/hooded/cult_hoodie/eldritch
- name = "ominous hood"
- icon = 'icons/obj/clothing/head/helmet.dmi'
- worn_icon = 'icons/mob/clothing/head/helmet.dmi'
- icon_state = "eldritch"
- desc = "A torn, dust-caked hood. Strange eyes line the inside."
- flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT
- flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH | PEPPERPROOF
- flash_protect = FLASH_PROTECTION_WELDER
-
-/obj/item/clothing/head/hooded/cult_hoodie/eldritch/Initialize(mapload)
- . = ..()
- AddElement(/datum/element/heretic_focus)
-
/obj/item/clothing/suit/hooded/cultrobes/eldritch
name = "ominous armor"
desc = "A ragged, dusty set of robes. Strange eyes line the inside."
icon_state = "eldritch_armor"
inhand_icon_state = null
- flags_inv = HIDESHOES|HIDEJUMPSUIT
- body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS
+ flags_inv = HIDESHOES | HIDEJUMPSUIT | HIDEBELT
+ body_parts_covered = CHEST | GROIN | LEGS | FEET | ARMS
+ resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF
+ clothing_flags = THICKMATERIAL | PLASMAMAN_PREVENT_IGNITION
+ transparent_protection = HIDEGLOVES | HIDESUITSTORAGE | HIDEJUMPSUIT | HIDESHOES | HIDENECK
+ cold_protection = FULL_BODY
+ min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT
allowed = list(/obj/item/melee/sickly_blade, /obj/item/gun/ballistic/rifle/lionhunter)
hoodtype = /obj/item/clothing/head/hooded/cult_hoodie/eldritch
- // Slightly better than normal cult robes
- armor_type = /datum/armor/cultrobes_eldritch
+ armor_type = /datum/armor/eldritch_armor
+ clothing_traits = list(TRAIT_HERETIC_AURA_HIDDEN)
/// Whether the hood is flipped up
var/hood_up = FALSE
-/datum/armor/cultrobes_eldritch
- melee = 50
- bullet = 50
- laser = 50
- energy = 50
- bomb = 35
- bio = 20
- fire = 20
- acid = 20
- wound = 20
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/equipped(mob/user, slot, initial)
+ . = ..()
+ if(!(slot_flags & slot))
+ return
+ if(!IS_HERETIC(user))
+ robes_side_effect(user)
+ return
+ // Heretic equipped the robes? Grant them the effects
+ on_robes_gained(user)
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/dropped(mob/living/user)
+ . = ..()
+ on_robes_lost(user)
+
+/// Adds effects to the user when they equip their robes
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/proc/on_robes_gained(mob/living/user)
+ return
+
+/// Removes any effects that our robes have, returns `TRUE` if the item dropped was not robes
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/proc/on_robes_lost(mob/living/user)
+ return
+
+/// Applies a punishment to the user when the robes are equipped
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/proc/robes_side_effect(mob/living/user)
+ SHOULD_NOT_SLEEP(TRUE) // sleep here would fuck over the timing
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/proc/is_equipped(mob/wearer)
+ return wearer.get_slot_by_item(src) & slot_flags
/obj/item/clothing/suit/hooded/cultrobes/eldritch/on_hood_up(obj/item/clothing/head/hooded/hood)
hood_up = TRUE
@@ -54,6 +67,1000 @@
// Our hood gains the heretic_focus element.
. += span_notice("Allows you to cast heretic spells while the hood is up.")
+/obj/item/clothing/head/hooded/cult_hoodie/eldritch
+ name = "ominous hood"
+ icon = 'icons/obj/clothing/head/helmet.dmi'
+ worn_icon = 'icons/mob/clothing/head/helmet.dmi'
+ icon_state = "eldritch"
+ desc = "A torn, dust-caked hood. Strange eyes line the inside."
+ flags_inv = HIDEMASK | HIDEEARS | HIDEEYES | HIDEFACE | HIDEHAIR | HIDEFACIALHAIR | HIDESNOUT
+ flags_cover = HEADCOVERSEYES | PEPPERPROOF
+ flash_protect = FLASH_PROTECTION_WELDER_HYPER_SENSITIVE
+ resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF
+ clothing_flags = THICKMATERIAL | PLASMAMAN_PREVENT_IGNITION | SNUG_FIT
+ armor_type = /datum/armor/eldritch_armor
+
+/obj/item/clothing/head/hooded/cult_hoodie/eldritch/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/heretic_focus)
+
+/datum/armor/eldritch_armor
+ melee = 50
+ bullet = 50
+ laser = 50
+ energy = 50
+ bomb = 35
+ bio = 20
+ fire = 20
+ acid = 20
+ wound = 20
+
+//---- Path-Specific Eldritch Robes, First is robes, then is hood
+
+// Ash
+// Prevents fire from decaying while worn, also passively generates fire via the toggle
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/ash
+ name = "\improper Scorched Mantle"
+ desc = "Left to burn to tatters, what remains is naught but a blackened echo of the mantle of the Watch. \
+ Yet the soot-choked folds turn blade and flame from the form within. A brief reprieve before its gaze turns inwards."
+ icon_state = "ash_armor"
+ hoodtype = /obj/item/clothing/head/hooded/cult_hoodie/eldritch/ash
+ armor_type = /datum/armor/eldritch_armor/ash
+ flags_inv = HIDEBELT
+ body_parts_covered = FULL_BODY
+ heat_protection = FULL_BODY
+ max_heat_protection_temperature = 50000
+ cold_protection = FULL_BODY
+ min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT
+ resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF | LAVA_PROOF | FREEZE_PROOF
+ actions_types = list(/datum/action/item_action/toggle/flames)
+ /// If our robes are actively generating flames
+ var/flame_generation = FALSE
+ /// Cooldown before our robes will create new flames
+ COOLDOWN_DECLARE(flame_creation)
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/ash/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/radiation_protected_clothing)
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/ash/on_robes_gained(mob/living/user)
+ if(!isliving(user))
+ return
+ var/mob/living/wearer = user
+ wearer.fire_stack_decay_rate = 0
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/ash/on_robes_lost(mob/living/user)
+ if(!isliving(user))
+ return
+ var/mob/living/wearer = user
+ wearer.fire_stack_decay_rate = initial(wearer.fire_stack_decay_rate)
+ if(flame_generation)
+ toggle_flames(wearer)
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/ash/robes_side_effect(mob/living/user)
+ if(!iscarbon(user))
+ return
+ var/mob/living/carbon/victim = user
+ var/iteration = 0
+ for(var/obj/item/bodypart/limb as anything in victim.bodyparts)
+ if(istype(limb, /obj/item/bodypart/head) || istype(limb, /obj/item/bodypart/chest))
+ continue
+ iteration++
+ addtimer(CALLBACK(src, PROC_REF(burn_limbs), limb), 1 SECONDS * iteration)
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/ash/proc/burn_limbs(obj/item/bodypart/limb)
+ if(QDELETED(limb) || !limb.owner || !is_equipped(limb.owner))
+ return
+ limb.dismember(BURN)
+
+/datum/action/item_action/toggle/flames
+ button_icon = 'icons/effects/magic.dmi'
+ button_icon_state = "fireball"
+
+/datum/action/item_action/toggle/flames/do_effect(trigger_flags)
+ var/obj/item/clothing/suit/hooded/cultrobes/eldritch/ash/item_target = target
+ if(!item_target || !istype(item_target))
+ return FALSE
+ item_target.toggle_flames(owner)
+
+/// Starts/Stops the passive generation of fire stacks on our wearer
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/ash/proc/toggle_flames(mob/living/user)
+ flame_generation = !flame_generation
+
+ if(flame_generation)
+ START_PROCESSING(SSobj, src)
+ else
+ user.extinguish()
+ STOP_PROCESSING(SSobj, src)
+
+ user.balloon_alert(user, flame_generation ? "enabled" : "disabled")
+ user.fire_stack_decay_rate = flame_generation ? 0 : initial(user.fire_stack_decay_rate)
+ // Extinguishes the wearer after they disable the flames
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/ash/process(seconds_per_tick)
+ if(!COOLDOWN_FINISHED(src, flame_creation))
+ return
+ var/mob/living/wearer = loc
+ if(!isliving(wearer))
+ STOP_PROCESSING(SSobj, src)
+ flame_generation = FALSE
+ return
+ COOLDOWN_START(src, flame_creation, 5 SECONDS)
+ wearer.adjust_fire_stacks(1)
+ wearer.ignite_mob(TRUE)
+
+/obj/item/clothing/head/hooded/cult_hoodie/eldritch/ash
+ name = "\improper Scorched Mantle"
+ desc = "Left to burn to tatters, what remains is naught but a blackened echo of the mantle of the Watch. \
+ Yet the soot-choked folds turn blade and flame from the form within. A brief reprieve before its gaze turns inwards."
+ icon_state = "ash_armor"
+ armor_type = /datum/armor/eldritch_armor/ash
+
+/datum/armor/eldritch_armor/ash
+ melee = 40
+ bullet = 60
+ laser = 50
+ energy = 50
+ bomb = 100
+ bio = 20
+ fire = 100
+ acid = 20
+ wound = 20
+
+// Blade
+// Is shock-proof and gives you baton resistance
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/blade
+ name = "\improper Shattered Panoply"
+ desc = "The sharpened edges of this ancient suit of armor assert a revelation known to aspirants of battle; \
+ a true warrior can not be distinguished from the blade they wield."
+ icon_state = "blade_armor"
+ hoodtype = /obj/item/clothing/head/hooded/cult_hoodie/eldritch/blade
+ armor_type = /datum/armor/eldritch_armor/blade
+ siemens_coefficient = 0
+ var/murdering_with_blades = FALSE
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/blade/on_robes_gained(mob/living/user)
+ . = ..()
+ user.add_traits(list(TRAIT_SHOCKIMMUNE, TRAIT_BATON_RESISTANCE), REF(src))
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/blade/on_robes_lost(mob/user, obj/item/clothing/suit/hooded/cultrobes/eldritch/robes)
+ . = ..()
+ if(.)
+ return
+ user.remove_traits(list(TRAIT_SHOCKIMMUNE, TRAIT_BATON_RESISTANCE), REF(src))
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/blade/robes_side_effect(mob/living/user)
+ INVOKE_ASYNC(src, PROC_REF(start_throwing_blades), user)
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/blade/proc/start_throwing_blades(mob/living/target)
+ if(murdering_with_blades)
+ return
+ murdering_with_blades = TRUE
+
+ var/delay = 2 SECONDS
+ var/knives = 100
+ for(var/knife in 1 to knives)
+ if(!should_keep_cutting(target))
+ break
+ addtimer(CALLBACK(src, PROC_REF(cut_em_good), target), delay * knife)
+ delay = max(0.5 SECONDS, delay - 0.1 SECONDS)
+
+ murdering_with_blades = FALSE
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/blade/proc/should_keep_cutting(mob/living/target)
+ if(target.stat == DEAD || !is_equipped(target))
+ return FALSE
+ return TRUE
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/blade/proc/cut_em_good(mob/living/target)
+ if(!should_keep_cutting(target))
+ return
+ var/list/turf/valid_turfs = get_blade_turfs(get_turf(target))
+ if(!length(valid_turfs))
+ var/mob/living/carbon/carbon_target = target
+ if(iscarbon(target))
+ var/obj/item/bodypart/limb = pick(carbon_target.bodyparts)
+ limb.force_wound_upwards(/datum/wound/slash/flesh/severe)
+ return
+ throw_blade(pick(valid_turfs), target)
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/blade/proc/get_blade_turfs(mob/user)
+ var/list/turfs_around_us = get_perimeter(user, 4)
+ var/list/valid_turfs = list()
+ for(var/turf/open/valid_turf in turfs_around_us)
+ if(!valid_turf.is_blocked_turf() && get_angle(valid_turf, user) != 180)
+ valid_turfs |= valid_turf
+ return valid_turfs
+
+/obj/item/knife/kitchen/magic
+ icon = 'icons/effects/eldritch.dmi'
+ icon_state = "dio_knife"
+ name = "magic knife"
+ throwforce = 15
+ // most importantly, this ignores shields
+ armour_penetration = 200
+ pass_flags = ALL
+
+/obj/item/knife/kitchen/magic/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/movetype_handler)
+ add_traits(list(TRAIT_MOVE_PHASING, TRAIT_MOVE_FLOATING, TRAIT_UNCATCHABLE), INNATE_TRAIT)
+ add_filter("dio_knife", 2, list("type" = "outline", "color" = "#ececff", "size" = 1))
+ set_embed(/datum/embedding/magic_knife)
+
+/obj/item/knife/kitchen/magic/get_demolition_modifier(obj/target)
+ if(!ismob(target))
+ return 100
+ return ..()
+
+/datum/embedding/magic_knife
+
+ embed_chance = 150
+ fall_chance = 0
+ impact_pain_mult = 0
+ ignore_throwspeed_threshold = TRUE
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/blade/proc/throw_blade(turf/target_turf, mob/user)
+ var/obj/item/knife/kitchen/magic/knife = new(target_turf)
+ knife.alpha = 0
+ knife.throw_at()
+
+ var/matrix/transform = matrix(knife.transform)
+ var/angle = get_angle(target_turf, user)
+ transform.Turn(angle)
+ var/appear_delay = 0.5 SECONDS
+ var/throw_delay = 1 SECONDS
+ var/delete_delay = 10 SECONDS
+ addtimer(CALLBACK(knife, TYPE_PROC_REF(/atom/movable, throw_at), user, 50, 5, null, FALSE), throw_delay)
+ animate(knife, transform = transform, time = throw_delay, ANIMATION_PARALLEL)
+ animate(knife, alpha = 255, time = appear_delay, ANIMATION_PARALLEL)
+ animate(alpha = 0, time = delete_delay)
+ QDEL_IN(knife, delete_delay + appear_delay + throw_delay)
+
+/obj/item/clothing/head/hooded/cult_hoodie/eldritch/blade
+ name = "\improper Shattered Panoply"
+ desc = "The sharpened edges of this ancient suit of armor assert a revelation known to aspirants of battle; \
+ a true warrior can not be distinguished from the blade they wield."
+ icon_state = "blade_armor"
+ armor_type = /datum/armor/eldritch_armor/blade
+ siemens_coefficient = 0
+
+/datum/armor/eldritch_armor/blade
+ melee = 50
+ bullet = 50
+ laser = 50
+ energy = 50
+ bomb = 50
+ bio = 50
+ fire = 50
+ acid = 50
+ wound = 50
+
+// Cosmic
+// Allows you to toggle gravity for yourself at will
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/cosmic
+ name = "\improper Starwoven Cloak"
+ desc = "Gleaming gems conjure forth wisps of power, turning about to illuminate the wearer in a dim radiance. \
+ Gazing upon the robe, you cannot help but feel noticed."
+ icon_state = "cosmic_armor"
+ hoodtype = /obj/item/clothing/head/hooded/cult_hoodie/eldritch/cosmic
+ armor_type = /datum/armor/eldritch_armor/cosmic
+ clothing_flags = THICKMATERIAL | PLASMAMAN_PREVENT_IGNITION | STOPSPRESSUREDAMAGE
+ cold_protection = CHEST | GROIN | LEGS | FEET | ARMS | HANDS
+ min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT
+ actions_types = list(/datum/action/item_action/toggle/gravity)
+ /// If our robes are making us weightless
+ var/weightless_enabled = FALSE
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/cosmic/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/radiation_protected_clothing)
+
+// Removes your antigravity if you lose the robes
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/cosmic/on_robes_lost(mob/user, obj/item/clothing/suit/hooded/cultrobes/eldritch/robes)
+ if(.)
+ return
+ if(weightless_enabled)
+ toggle_gravity(user)
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/cosmic/robes_side_effect(mob/living/user)
+ var/obj/item/organ/brain/victim_brain = user.get_organ_slot(ORGAN_SLOT_BRAIN)
+ if(!victim_brain)
+ return
+
+ victim_brain.gain_trauma(/datum/brain_trauma/magic/stalker/cosmic, TRAUMA_RESILIENCE_MAGIC)
+
+/datum/action/item_action/toggle/gravity
+ button_icon = 'icons/effects/magic.dmi'
+ button_icon_state = "magicm"
+
+/datum/action/item_action/toggle/gravity/do_effect(trigger_flags)
+ var/obj/item/clothing/suit/hooded/cultrobes/eldritch/cosmic/item_target = target
+ if(!item_target || !istype(item_target))
+ return FALSE
+ item_target.toggle_gravity(owner)
+
+/// Gives us free movement in 0 gravity when enabled
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/cosmic/proc/toggle_gravity(mob/living/user)
+ if(!weightless_enabled)
+ user.add_traits(list(TRAIT_NEGATES_GRAVITY, TRAIT_MOVE_FLYING, TRAIT_FREE_HYPERSPACE_MOVEMENT), REF(src))
+ user.balloon_alert(user, "enabled")
+ else
+ user.remove_traits(list(TRAIT_NEGATES_GRAVITY, TRAIT_MOVE_FLYING, TRAIT_FREE_HYPERSPACE_MOVEMENT), REF(src))
+ user.balloon_alert(user, "disabled")
+ weightless_enabled = !weightless_enabled
+
+/obj/item/clothing/head/hooded/cult_hoodie/eldritch/cosmic
+ name = "\improper Starwoven Hood"
+ desc = "Gleaming gems conjure forth wisps of power, turning about to illuminate the wearer in a dim radiance. \
+ Gazing upon the robe, you cannot help but feel noticed."
+ icon_state = "cosmic_armor"
+ armor_type = /datum/armor/eldritch_armor/cosmic
+ clothing_flags = THICKMATERIAL | PLASMAMAN_PREVENT_IGNITION | STOPSPRESSUREDAMAGE
+ cold_protection = HEAD
+ min_cold_protection_temperature = SPACE_HELM_MIN_TEMP_PROTECT
+
+/obj/item/clothing/head/hooded/cult_hoodie/eldritch/cosmic/Initialize(mapload)
+ . = ..()
+ AddElement(/datum/element/radiation_protected_clothing)
+
+/datum/armor/eldritch_armor/cosmic
+ melee = 20
+ bullet = 30
+ laser = 60
+ energy = 60
+ bomb = 35
+ bio = 20
+ fire = 20
+ acid = 20
+ wound = 20
+
+// Flesh
+// Emits a healing aura that affects any heretic summons (excluding the heretic himself)
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/flesh
+ name = "Writhing Embrace"
+ desc = "A rotten carcass, or perhaps several, twisted into fleshy polyps, knotted intestines and cracked bone. \
+ How one 'wears' this baffles reasonable understanding. It moves when it believes itself unobserved."
+ icon_state = "flesh_armor"
+ hoodtype = /obj/item/clothing/head/hooded/cult_hoodie/eldritch/flesh
+ armor_type = /datum/armor/eldritch_armor/flesh
+ /// The aura healing component. Used to delete it when taken off.
+ var/datum/component/healing_aura
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/flesh/on_robes_gained(mob/living/user)
+ healing_aura = user.AddComponent( \
+ /datum/component/aura_healing, \
+ range = 15, \
+ brute_heal = 3, \
+ burn_heal = 3, \
+ blood_heal = 3, \
+ suffocation_heal = 3, \
+ stamina_heal = 15, \
+ simple_heal = 3, \
+ requires_visibility = FALSE, \
+ limit_to_trait = TRAIT_HERETIC_SUMMON, \
+ healing_color = COLOR_RED, \
+ self_heal = FALSE, \
+ )
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/flesh/on_robes_lost(mob/user, obj/item/clothing/suit/hooded/cultrobes/eldritch/robes)
+ QDEL_NULL(healing_aura)
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/flesh/robes_side_effect(mob/living/user)
+ if(!iscarbon(user))
+ return
+ var/mob/living/carbon/victim = user
+ var/iteration = 0
+ for(var/obj/item/bodypart/limb as anything in victim.bodyparts)
+ iteration++
+ addtimer(CALLBACK(limb, TYPE_PROC_REF(/obj/item/bodypart, force_wound_upwards), /datum/wound/slash/flesh/critical), 1 SECONDS * iteration)
+
+/obj/item/clothing/head/hooded/cult_hoodie/eldritch/flesh
+ icon_state = "flesh_armor"
+ armor_type = /datum/armor/eldritch_armor/flesh
+ clothing_traits = list(TRAIT_MEDICAL_HUD)
+
+/datum/armor/eldritch_armor/flesh
+ melee = 70
+ bullet = 40
+ laser = 30
+ energy = 30
+ bomb = 35
+ bio = 100
+ fire = 0
+ acid = 100
+ wound = 20
+
+// Lock
+// Gives you digital camo, silences your footsteps and makes you un-examineable
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/lock
+ name = "Shifting Guise"
+ icon_state = "lock_armor"
+ hoodtype = /obj/item/clothing/head/hooded/cult_hoodie/eldritch/lock
+ armor_type = /datum/armor/eldritch_armor/lock
+ flags_inv = parent_type::flags_inv | HIDEMUTWINGS
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/lock/on_robes_gained(mob/living/user)
+ user.AddElement(/datum/element/digitalcamo)
+ user.add_traits(list(TRAIT_SILENT_FOOTSTEPS, TRAIT_UNKNOWN_APPEARANCE, TRAIT_UNKNOWN_VOICE), REF(src))
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/lock/on_robes_lost(mob/user, obj/item/clothing/suit/hooded/cultrobes/eldritch/robes)
+ user.RemoveElement(/datum/element/digitalcamo)
+ user.remove_traits(list(TRAIT_SILENT_FOOTSTEPS, TRAIT_UNKNOWN_APPEARANCE, TRAIT_UNKNOWN_VOICE), REF(src))
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/lock/robes_side_effect(mob/living/user)
+ if(!iscarbon(user))
+ return
+ var/mob/living/carbon/victim = user
+ var/list/things = victim.get_equipped_items(ALL)
+ var/turf/our_turf = get_turf(victim)
+ var/list/turf/nearby_turfs = RANGE_TURFS(5, our_turf) - our_turf
+ for(var/obj/item/to_throw in things)
+ if(user.dropItemToGround(to_throw))
+ to_throw.safe_throw_at(pick(nearby_turfs), 2, 1, spin = TRUE)
+
+/obj/item/clothing/head/hooded/cult_hoodie/eldritch/lock
+ icon_state = "lock_armor"
+ armor_type = /datum/armor/eldritch_armor/lock
+
+/datum/armor/eldritch_armor/lock
+ melee = 40
+ bullet = 40
+ laser = 40
+ energy = 40
+ bomb = 40
+ bio = 40
+ fire = 40
+ acid = 40
+ wound = 40
+
+// Moon
+// Converts all damage into brain damage, nullifying the attack in the process
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/moon
+ name = "\improper Resplendant Regalia"
+ desc = "The confounding nature of this opulent garb turns and twists the sight. \
+ The viewer must come to a chilling revelation; \
+ what they see is as true as any other face."
+ icon_state = "moon_armor"
+ hoodtype = /obj/item/clothing/head/hooded/cult_hoodie/eldritch/moon
+ armor_type = /datum/armor/eldritch_armor/moon
+ flags_inv = HIDESHOES | HIDEJUMPSUIT | HIDEMUTWINGS
+ clothing_traits = list(
+ TRAIT_HERETIC_AURA_HIDDEN,
+ TRAIT_BATON_RESISTANCE,
+ TRAIT_STUNIMMUNE,
+ TRAIT_NEVER_WOUNDED,
+ TRAIT_PACIFISM,
+ TRAIT_NOHUNGER
+ )
+ /// Hud that gets shown to the wearer, gives a rough estimate of their current brain damage
+ var/atom/movable/screen/moon_health/health_hud
+ /// Boolean if you are brain dead so the sound doesn't spam during the delay
+ var/braindead = FALSE
+ //---- Messages that get sent when someone wearing the moon robes is attacked
+ /// Visible message that nearby people see
+ var/static/list/visible_message_list = list(
+ "%USER seems to hardly register that they have been harmed by %ATTACKER, not even flinching naturally.",
+ "Though wounded, %USER seems oblivious to %ATTACKER.",
+ "You hear %USER laughing. But they have not made a single sound, even when struck by %ATTACKER.",
+ )
+ /// Message sent to the wearer who got attacked
+ var/static/list/self_message_list = list(
+ "Your body ripples as still water freshly disturbed. The sensation is exquisite, and you have %ATTACKER to thank.",
+ "A bell tolls. %ATTACKER has struck the hour and you tick to that tune.",
+ //"You are needed in [area name]. You need to be there. %ATTACKER might want you to stay, but you are needed in [area name].",
+ //"You see %ATTACKER strike a [name of animal]. The face of the beast is a mirror of your own. How strange.",
+ "%ATTACKER bumps you and you spill your tea. It's fine. You've plenty of cups.",
+ "You hear a roaring crash. The waves hit the boat. The is sea vast and dark. You see %ATTACKER striking the water, cursing its master.",
+ "Sequins scatter into the air around %ATTACKER. The sequins...",
+ "You notice that a button has popped off your collar. How did that happen? Maybe %ATTACKER is to blame.",
+ "%ATTACKER isn't very funny, and you're struggling to see the punchline.",
+ )
+ /// Message sent to blind people nearby
+ var/static/list/blind_message_list = list(
+ "You hear echoing laughter.",
+ "You hear a distance chorus.",
+ "You hear the sound of bells and whistles.",
+ "You hear the clack of a tambourine.",
+ )
+ /// List of all signals registered, used for cleanup
+ var/signal_registered = list()
+ /// damage modifier to all incoming damage, which is also converted to brain damage
+ var/damage_modifier = 1.15
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/moon/equipped(mob/user, slot, initial)
+ . = ..()
+ if(!ishuman(user) || !(slot_flags & slot))
+ return
+ var/mob/living/carbon/human/human_user = user
+ // Gives the hud to the wearer, if there's no hud, register the signal to be given on creation
+ if(human_user.hud_used)
+ on_hud_created(human_user)
+ else
+ RegisterSignal(human_user, COMSIG_MOB_HUD_CREATED, PROC_REF(on_hud_created))
+ signal_registered += COMSIG_MOB_HUD_CREATED
+
+ human_user.add_movespeed_mod_immunities(REF(src), /datum/movespeed_modifier/equipment_speedmod)
+ RegisterSignal(human_user, COMSIG_MOB_APPLY_DAMAGE_MODIFIERS, PROC_REF(on_apply_modifiers))
+ signal_registered += COMSIG_MOB_APPLY_DAMAGE_MODIFIERS
+
+ RegisterSignal(human_user, COMSIG_LIVING_DEATH, PROC_REF(on_death))
+ signal_registered += COMSIG_LIVING_DEATH
+
+ RegisterSignal(human_user, COMSIG_SEND_ITEM_ATTACK_MESSAGE_CARBON, PROC_REF(item_attack_response))
+ signal_registered += COMSIG_SEND_ITEM_ATTACK_MESSAGE_CARBON
+
+ var/obj/item/organ/brain/our_brain = human_user.get_organ_slot(ORGAN_SLOT_BRAIN)
+ if(!our_brain)
+ return
+ ADD_TRAIT(our_brain, TRAIT_BRAIN_DAMAGE_NODEATH, REF(src))
+ START_PROCESSING(SSobj, src)
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/moon/dropped(mob/living/user)
+ . = ..()
+ if(!ishuman(user))
+ return
+ var/mob/living/carbon/human/wearer = user
+ UnregisterSignal(wearer, signal_registered)
+ signal_registered = list()
+
+ wearer.remove_movespeed_mod_immunities(REF(src), /datum/movespeed_modifier/equipment_speedmod)
+ var/obj/item/organ/brain/our_brain = wearer.get_organ_slot(ORGAN_SLOT_BRAIN)
+ REMOVE_TRAIT(our_brain, TRAIT_BRAIN_DAMAGE_NODEATH, REF(src))
+ braindead = FALSE
+ if(health_hud in user.hud_used.infodisplay)
+ on_hud_remove(user)
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/moon/proc/on_apply_modifiers(mob/living/user, damage_mods, damage, damagetype, def_zone, sharpness, attack_direction, attacking_item)
+ SIGNAL_HANDLER
+ if(braindead)
+ return
+ damage_mods += 0
+ user.adjustOrganLoss(ORGAN_SLOT_BRAIN, damage * damage_modifier)
+ check_braindeath(user)
+
+/// Gives the health HUD to the wearer
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/moon/proc/on_hud_created(mob/living/carbon/human/wearer)
+ SIGNAL_HANDLER
+ var/datum/hud/original_hud = wearer.hud_used
+ // Remove the old health elements
+ var/list/to_remove = list(/atom/movable/screen/stamina, /atom/movable/screen/healths, /atom/movable/screen/healthdoll/human)
+ for(var/removing in original_hud.infodisplay)
+ if(is_type_in_list(removing, to_remove))
+ original_hud.infodisplay -= removing
+ QDEL_NULL(removing)
+
+ wearer.mob_mood.unmodify_hud()
+ // Add the moon health hud element
+ health_hud = new(null, original_hud)
+ original_hud.infodisplay += health_hud
+ original_hud.show_hud(original_hud.hud_version)
+ UnregisterSignal(wearer, COMSIG_MOB_HUD_CREATED)
+ signal_registered -= COMSIG_MOB_HUD_CREATED
+
+/// Removes the HUD element from the wearer
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/moon/proc/on_hud_remove(mob/living/carbon/human/wearer)
+ var/datum/hud/original_hud = wearer.hud_used
+ original_hud.infodisplay -= health_hud
+ QDEL_NULL(health_hud)
+ // Restore the old health elements
+ var/atom/movable/screen/stamina/stamina_hud = new(null, original_hud)
+ var/atom/movable/screen/healths/old_health_hud = new(null, original_hud)
+ var/atom/movable/screen/healthdoll/human/health_doll_hud = new(null, original_hud)
+ original_hud.infodisplay += stamina_hud
+ original_hud.infodisplay += old_health_hud
+ original_hud.infodisplay += health_doll_hud
+ wearer.mob_mood.modify_hud()
+ original_hud.show_hud(original_hud.hud_version)
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/moon/can_mob_unequip(mob/user)
+ if(!ishuman(user))
+ return ..()
+ var/mob/living/carbon/human/wearer = user
+ if(wearer.get_organ_loss(ORGAN_SLOT_BRAIN) > 0)
+ wearer.balloon_alert(user, "can't strip, brain damaged!")
+ return FALSE
+ return ..()
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/moon/proc/item_attack_response(mob/living/victim, obj/item/weapon, mob/living/attacker)
+ SIGNAL_HANDLER
+ var/visible_message = pick(visible_message_list)
+ visible_message = replacetext(visible_message, "%USER", victim.get_visible_name())
+ visible_message = replacetext(visible_message, "%ATTACKER", attacker.get_visible_name())
+
+ var/self_message = pick(self_message_list)
+ self_message = replacetext(self_message_list, "%ATTACKER", attacker.get_visible_name())
+
+ var/blind_message = pick(blind_message_list)
+ victim.visible_message(span_danger(visible_message), span_userdanger(self_message), span_danger(blind_message))
+ return SIGNAL_MESSAGE_MODIFIED
+
+/// Once you reach this point you're completely brain dead, so lets play our effects before you eat shit
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/moon/proc/kill_wearer(mob/living/carbon/human/wearer)
+ if(IS_HERETIC(wearer))
+ var/datum/action/cooldown/spell/aoe/moon_ringleader/temp_spell = new(wearer)
+ temp_spell.cast(wearer)
+ qdel(temp_spell)
+ var/obj/item/organ/brain/our_brain = wearer.get_organ_slot(ORGAN_SLOT_BRAIN)
+ REMOVE_TRAIT(our_brain, TRAIT_BRAIN_DAMAGE_NODEATH, REF(src))
+ wearer.death()
+
+/// Blows up your head when you die
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/moon/proc/on_death(mob/wearer)
+ SIGNAL_HANDLER
+ if(!ishuman(wearer))
+ return
+ var/mob/living/carbon/human/human_wearer = wearer
+ var/obj/item/bodypart/head/to_explode = human_wearer.get_bodypart(BODY_ZONE_HEAD)
+ if(!to_explode)
+ return
+ human_wearer.visible_message(span_warning("[human_wearer]'s head splatters with a sickening crunch!"), ignored_mobs = list(human_wearer))
+ new /obj/effect/gibspawner/generic(get_turf(human_wearer), human_wearer)
+ to_explode.dismember(dam_type = BRUTE, silent = TRUE)
+ to_explode.drop_organs()
+ qdel(to_explode)
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/moon/process(seconds_per_tick)
+ var/mob/living/carbon/human/wearer = loc
+ if(!istype(wearer) || wearer.wear_suit != src || wearer.stat == DEAD)
+ return ..()
+ if(!IS_HERETIC_OR_MONSTER(wearer))
+ wearer.adjustOrganLoss(ORGAN_SLOT_BRAIN, 20)
+ var/brain_damage = wearer.get_organ_loss(ORGAN_SLOT_BRAIN)
+ var/emote_rng = 0
+ var/list/emote_list = list()
+ switch(brain_damage)
+ if(0)
+ emote_rng = 0
+ emote_list = list()
+ if(1 to 30)
+ emote_rng = 20
+ emote_list = list("laugh")
+ if(31 to 60)
+ emote_rng = 40
+ emote_list = list("laugh", "smile")
+ if(61 to 100)
+ emote_rng = 60
+ emote_list = list("laugh", "smile", "cough")
+ if(101 to 150)
+ emote_rng = 80
+ emote_list = list("laugh", "smile", "cough", "gasp")
+ if(151 to 200)
+ emote_rng = 100
+ emote_list = list("laugh", "smile", "cough", "gasp", "scream")
+ if(!prob(emote_rng))
+ return
+ for(var/perform in emote_list)
+ wearer.emote("[perform]")
+ check_braindeath(wearer)
+
+/// Checks if you are brain dead, starts the dying process once you've reached it
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/moon/proc/check_braindeath(mob/living/carbon/human/wearer)
+ var/obj/item/organ/brain/our_brain = wearer.get_organ_slot(ORGAN_SLOT_BRAIN)
+ if(braindead || our_brain.damage < our_brain.maxHealth)
+ return
+
+ braindead = TRUE
+ wearer.setOrganLoss(ORGAN_SLOT_BRAIN, INFINITY)
+ playsound(wearer, 'sound/effects/pope_entry.ogg', 50)
+ to_chat(wearer, span_bold(span_hypnophrase("A terrible fate has befallen you.")))
+ addtimer(CALLBACK(src, PROC_REF(kill_wearer), wearer), 5 SECONDS)
+
+/obj/item/clothing/head/hooded/cult_hoodie/eldritch/moon
+ name = "\improper Resplendant Hood"
+ icon_state = "moon_armor"
+ armor_type = /datum/armor/eldritch_armor/moon
+
+/datum/armor/eldritch_armor/moon
+ melee = 0
+ bullet = 0
+ laser = 0
+ energy = 0
+ bomb = 0
+ bio = 0
+ fire = 0
+ acid = 0
+ wound = 0
+
+/atom/movable/screen/moon_health
+ name = "Health Level"
+ icon = 'icons/hud/moon_health_64x64.dmi'
+ icon_state = "moon_hud_1"
+ base_icon_state = "moon_hud"
+ screen_loc = "EAST-1:0, SOUTH+6:16"
+
+/atom/movable/screen/moon_health/Initialize(mapload, datum/hud/hud_owner)
+ . = ..()
+ if(isnull(hud_owner) || !ishuman(hud_owner.mymob))
+ return INITIALIZE_HINT_QDEL
+ var/mob/living/carbon/human/wearer = hud_owner.mymob
+ var/obj/item/organ/brain/our_brain = wearer.get_organ_slot(ORGAN_SLOT_BRAIN)
+ if(!our_brain)
+ return INITIALIZE_HINT_QDEL
+ update_health(our_brain)
+ RegisterSignal(our_brain, COMSIG_ORGAN_ADJUST_DAMAGE, PROC_REF(update_health))
+
+/// Changes the icon based on the brain health of the wearer
+/atom/movable/screen/moon_health/proc/update_health(obj/item/organ/brain, damage_amount, maximum, required_organ_flag)
+ SIGNAL_HANDLER
+ if(!brain.owner || !ishuman(brain.owner))
+ qdel(src)
+ return
+ var/mob/living/carbon/human/wearer = brain.owner
+ if(istype(wearer.wear_suit, /obj/item/clothing/suit/hooded/cultrobes/eldritch/moon))
+ var/obj/item/clothing/suit/hooded/cultrobes/eldritch/moon/robes = wearer.wear_suit
+ if(robes.braindead)
+ icon_state = base_icon_state + "_6"
+ return // Don't update the icon once our "dying" process has begun
+ switch(brain.damage)
+ if(0 to 20)
+ icon_state = base_icon_state + "_1"
+ if(21 to 50)
+ icon_state = base_icon_state + "_2"
+ if(51 to 100)
+ icon_state = base_icon_state + "_3"
+ if(101 to 150)
+ icon_state = base_icon_state + "_4"
+ if(151 to 189)
+ icon_state = base_icon_state + "_5"
+ if(190 to INFINITY)
+ icon_state = base_icon_state + "_6"
+
+// Rust
+// Gains more armor while standing on top of rust. Has an animated overlay
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/rust
+ name = "\improper Salvaged Remains"
+ desc = "Touching the folds of this plain robe seem to fill you with unease. \
+ Even looking fills you with a sense of vertigo. \
+ Some pulse threatening to pull you within."
+ icon_state = "rust_armor"
+ hoodtype = /obj/item/clothing/head/hooded/cult_hoodie/eldritch/rust
+ armor_type = /datum/armor/eldritch_armor/rust
+ /// Grace period timer before the
+ COOLDOWN_DECLARE(rust_grace_period)
+ /// If our armor is rusted, used to update the sprite
+ var/rusted = FALSE
+ /// Atom used to animate our overlay
+ var/atom/movable/rust_overlay
+ /// The mutable that is actually overlayed on the mob
+ var/mutable_appearance/rust_appearance
+ /// identifier for the overlay
+ var/static/overlay_id = 0
+ /// Overlay for the armor object
+ var/image/object_overlay
+ /// Overlay for the hood object
+ var/image/hood_object_overlay
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/rust/Initialize(mapload)
+ . = ..()
+ overlay_id++
+ if(!object_overlay)
+ object_overlay = image(icon, icon_state = "rust_armor_overlay")
+ if(!hood_object_overlay)
+ hood_object_overlay = image('icons/obj/clothing/head/helmet.dmi', icon_state = "rust_armor_overlay")
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/rust/on_robes_gained(mob/living/user)
+ . = ..()
+ RegisterSignal(user, COMSIG_MOVABLE_MOVED, PROC_REF(on_move))
+ rust_overlay = new()
+ rust_overlay.icon = 'icons/mob/clothing/suits/armor.dmi'
+ rust_overlay.render_target = "*rust_overlay_[overlay_id]"
+ rust_overlay.vis_flags |= VIS_INHERIT_DIR | VIS_INHERIT_LAYER | VIS_INHERIT_ID
+ user.vis_contents += rust_overlay // Should be invisible, we just update the sprite as needed
+
+ rust_appearance = new /mutable_appearance()
+ rust_appearance.render_source = "*rust_overlay_[overlay_id]"
+ update_appearance(UPDATE_ICON)
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/rust/on_robes_lost(mob/user, obj/item/clothing/suit/hooded/cultrobes/eldritch/robes)
+ . = ..()
+ if(.)
+ return
+ UnregisterSignal(user, list(COMSIG_MOVABLE_MOVED))
+ user.vis_contents -= rust_overlay
+ rusted = FALSE
+ set_armor(/datum/armor/eldritch_armor/rust)
+
+ REMOVE_TRAIT(user, TRAIT_PIERCEIMMUNE, REF(src))
+ cut_overlay(object_overlay)
+ QDEL_NULL(rust_overlay)
+ QDEL_NULL(rust_appearance)
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/rust/robes_side_effect(mob/living/user)
+ . = ..()
+ if(!iscarbon(user))
+ return
+ var/mob/living/carbon/victim = user
+ var/list/organ_list = victim.organs
+ if(!length(organ_list))
+ return
+
+ var/iteration = 0
+ var/organs_to_puke = rand(1, 3)
+ for(var/obj/item/organ/to_puke as anything in organ_list)
+ if(iteration > organs_to_puke)
+ break
+ iteration++
+ addtimer(CALLBACK(src, PROC_REF(vomit_your_guts_out), victim), 1 SECONDS * iteration)
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/rust/proc/vomit_your_guts_out(mob/living/carbon/victim)
+ if(QDELETED(victim) || !is_equipped(victim))
+ return
+ victim.vomit(MOB_VOMIT_BLOOD | MOB_VOMIT_MESSAGE | MOB_VOMIT_HARM | MOB_VOMIT_FORCE)
+ victim.spew_organ(rand(4, 6))
+
+/*
+ * Signal proc for [COMSIG_MOVABLE_MOVED].
+ *
+ * Checks if our armor values should be increased on the new turf
+ */
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/rust/proc/on_move(mob/source, atom/old_loc, dir, forced, list/old_locs)
+ SIGNAL_HANDLER
+
+ var/turf/mover_turf = get_turf(source)
+ if(HAS_TRAIT(mover_turf, TRAIT_RUSTY))
+ set_armor(/datum/armor/eldritch_armor/rust/on_rust)
+
+ ADD_TRAIT(source, TRAIT_PIERCEIMMUNE, REF(src))
+ COOLDOWN_RESET(src, rust_grace_period)
+ if(rusted) // Already rusted, don't update overlay
+ return
+ rusted = TRUE
+ update_rust()
+ else
+ if(!rusted) // Already unrusted, don't update overlay
+ return
+ // Start the timer for the first time we step off rust
+ if(!COOLDOWN_STARTED(src, rust_grace_period))
+ COOLDOWN_START(src, rust_grace_period, 1 SECONDS)
+ return
+ if(!COOLDOWN_FINISHED(src, rust_grace_period))
+ return
+
+ // *Actually* remove the effects after our grace period expires.
+ // Keep in mind since we call updates `on_move` this means you can technically stand still to keep the benefits.
+ COOLDOWN_RESET(src, rust_grace_period)
+ set_armor(/datum/armor/eldritch_armor/rust)
+ REMOVE_TRAIT(source, TRAIT_PIERCEIMMUNE, REF(src))
+ rusted = FALSE
+ update_rust()
+
+/// Updates the icon of our overlay and applies the animation
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/rust/proc/update_rust()
+ // Animation + Update the overlay sprite on our armor
+ if(!rusted)
+ rust_overlay?.icon_state = null
+ flick("[worn_icon_state]"+"_off", rust_overlay)
+ cut_overlay(object_overlay)
+ hood?.cut_overlay(hood_object_overlay)
+ return
+ rust_overlay?.icon_state = "[worn_icon_state]" + "_overlay"
+ flick("[worn_icon_state]"+"_on", rust_overlay)
+ add_overlay(object_overlay)
+ hood?.add_overlay(hood_object_overlay)
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/rust/worn_overlays(mutable_appearance/standing, isinhands)
+ . = ..()
+ // Should basically catch toggling the hood on/off while standing on rust
+ if(rusted)
+ rust_overlay?.icon_state = "[worn_icon_state]" + "_overlay"
+ else
+ rust_overlay?.icon_state = null
+ . += rust_appearance
+
+/obj/item/clothing/head/hooded/cult_hoodie/eldritch/rust
+ name = "\improper Salvaged Remains"
+ desc = "Touching the folds of this plain robe seem to fill you with unease. \
+ Even looking fills you with a sense of vertigo. \
+ Some pulse threatening to pull you within."
+ icon_state = "rust_armor"
+ armor_type = /datum/armor/eldritch_armor/rust
+
+/obj/item/clothing/head/hooded/cult_hoodie/eldritch/rust/equipped(mob/living/user, slot)
+ . = ..()
+ if(!(slot_flags & slot))
+ UnregisterSignal(user, list(COMSIG_MOVABLE_MOVED))
+ return
+ RegisterSignal(user, COMSIG_MOVABLE_MOVED, PROC_REF(on_move))
+
+/*
+ * Signal proc for [COMSIG_MOVABLE_MOVED].
+ *
+ * Checks if our armor values should be increased on the new turf
+ */
+/obj/item/clothing/head/hooded/cult_hoodie/eldritch/rust/proc/on_move(mob/source, atom/old_loc, dir, forced, list/old_locs)
+ SIGNAL_HANDLER
+
+ var/turf/mover_turf = get_turf(source)
+ if(HAS_TRAIT(mover_turf, TRAIT_RUSTY))
+ set_armor(/datum/armor/eldritch_armor/rust/on_rust)
+ else
+ set_armor(/datum/armor/eldritch_armor/rust)
+
+/datum/armor/eldritch_armor/rust
+ melee = 30
+ bullet = 30
+ laser = 30
+ energy = 30
+ bomb = 50
+ bio = 30
+ fire = 30
+ acid = 30
+ wound = 30
+
+/datum/armor/eldritch_armor/rust/on_rust
+ melee = 60
+ bullet = 60
+ laser = 60
+ energy = 60
+ bomb = 100
+ bio = 60
+ fire = 60
+ acid = 60
+ wound = 60
+
+// Void
+// Gives you a short stealth when you are hit
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/void
+ name = "\improper Hollow Weave"
+ desc = "At first, the empty canvas of this robe seems to shimmer with a faint, cold light. \
+ Yet upon tracking the shape of the folds more carefully, it is better to describe it as the absence of such a thing."
+ icon_state = "void_armor"
+ resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF
+ hoodtype = /obj/item/clothing/head/hooded/cult_hoodie/eldritch/void
+ armor_type = /datum/armor/eldritch_armor/void
+ /// Cooldown before we can go back into stealth
+ COOLDOWN_DECLARE(stealth_cooldown)
+ /// Timer before our stealth runs out
+ var/stealth_timer
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/void/on_robes_lost(mob/user, obj/item/clothing/suit/hooded/cultrobes/eldritch/robes)
+ . = ..()
+ if(. || !timeleft(stealth_timer))
+ return
+ // Remove from stealth when you lose the robes
+ deltimer(stealth_timer)
+ end_stealth(user)
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/void/robes_side_effect(mob/living/user)
+ . = ..()
+ user.adjust_bodytemperature(-INFINITY)
+ ADD_TRAIT(user, TRAIT_HYPOTHERMIC, REF(src))
+ if(!isliving(user))
+ return
+ var/mob/living/victim = user
+ victim.apply_status_effect(/datum/status_effect/frozenstasis/irresistable)
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/void/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text, final_block_chance, damage, attack_type, damage_type)
+ . = ..()
+ if(!COOLDOWN_FINISHED(src, stealth_cooldown))
+ return
+ COOLDOWN_START(src, stealth_cooldown, 20 SECONDS)
+ stealth_timer = addtimer(CALLBACK(src, PROC_REF(end_stealth), owner), 5 SECONDS, TIMER_STOPPABLE)
+ owner.alpha = 0
+ return TRUE
+
+/obj/item/clothing/suit/hooded/cultrobes/eldritch/void/proc/end_stealth(mob/living/carbon/human/owner)
+ animate(owner, time = 1 SECONDS, alpha = initial(owner.alpha))
+
+/obj/item/clothing/head/hooded/cult_hoodie/eldritch/void
+ name = "\improper Hollow Weave"
+ desc = "At first, the empty canvas of this robe seems to shimmer with a faint, cold light. \
+ Yet upon tracking the shape of the folds more carefully, it is better to describe it as the absence of such a thing."
+ icon_state = "void_armor"
+ armor_type = /datum/armor/eldritch_armor/void
+
+/datum/armor/eldritch_armor/void
+ melee = 40
+ bullet = 40
+ laser = 50
+ energy = 50
+ bomb = 40
+ bio = 40
+ fire = 40
+ acid = 40
+ wound = 40
+
// Void cloak. Turns invisible with the hood up, lets you hide stuff.
/obj/item/clothing/head/hooded/cult_hoodie/void
name = "void hood"
@@ -108,6 +1115,12 @@
make_visible()
ADD_TRAIT(src, TRAIT_CONTRABAND_BLOCKER, INNATE_TRAIT)
+/obj/item/clothing/suit/hooded/cultrobes/void/on_hood_up(obj/item/clothing/head/hooded/hood)
+ hood_up = TRUE
+
+/obj/item/clothing/suit/hooded/cultrobes/void/on_hood_down(obj/item/clothing/head/hooded/hood)
+ hood_up = FALSE
+
/obj/item/clothing/suit/hooded/cultrobes/void/equipped(mob/user, slot)
. = ..()
if(slot & ITEM_SLOT_OCLOTHING)
@@ -118,12 +1131,6 @@
. = ..()
UnregisterSignal(user, list(COMSIG_MOB_UNEQUIPPED_ITEM, COMSIG_MOB_EQUIPPED_ITEM))
-/obj/item/clothing/suit/hooded/cultrobes/void/on_hood_up(obj/item/clothing/head/hooded/hood)
- hood_up = TRUE
-
-/obj/item/clothing/suit/hooded/cultrobes/void/on_hood_down(obj/item/clothing/head/hooded/hood)
- hood_up = FALSE
-
/obj/item/clothing/suit/hooded/cultrobes/void/proc/hide_item(datum/source, obj/item/item, slot)
SIGNAL_HANDLER
if(slot & ITEM_SLOT_SUITSTORE)
@@ -140,6 +1147,7 @@
// Let examiners know this works as a focus only if the hood is down
. += span_notice("Allows you to cast heretic spells while the hood is down.")
+ . += span_notice("Is space worthy as long as the hood is down.")
/obj/item/clothing/suit/hooded/cultrobes/void/on_hood_down(obj/item/clothing/head/hooded/hood)
make_visible()
@@ -165,6 +1173,7 @@
RemoveElement(/datum/element/heretic_focus)
if(isliving(loc))
+ loc.remove_traits(list(TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTCOLD), REF(src))
REMOVE_TRAIT(loc, TRAIT_RESISTLOWPRESSURE, REF(src))
loc.balloon_alert(loc, "cloak hidden")
loc.visible_message(span_notice("Light shifts around [loc], making the cloak around them invisible!"))
@@ -175,6 +1184,6 @@
AddElement(/datum/element/heretic_focus)
if(isliving(loc))
- ADD_TRAIT(loc, TRAIT_RESISTLOWPRESSURE, REF(src))
+ loc.add_traits(list(TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTCOLD), REF(src))
loc.balloon_alert(loc, "cloak revealed")
loc.visible_message(span_notice("A kaleidoscope of colours collapses around [loc], a cloak appearing suddenly around their person!"))
diff --git a/code/modules/antagonists/heretic/items/heretic_blades.dm b/code/modules/antagonists/heretic/items/heretic_blades.dm
index 51de634ff43..eae583f2f82 100644
--- a/code/modules/antagonists/heretic/items/heretic_blades.dm
+++ b/code/modules/antagonists/heretic/items/heretic_blades.dm
@@ -54,6 +54,9 @@
return .
/obj/item/melee/sickly_blade/attack_self(mob/user)
+ var/datum/antagonist/heretic/heretic_datum = IS_HERETIC(user)
+ if(heretic_datum?.unlimited_blades)
+ return
if(HAS_TRAIT(user, TRAIT_ELDRITCH_ARENA_PARTICIPANT))
user.balloon_alert(user, "can't escape!")
if(escape_attempts > 2)
@@ -170,16 +173,13 @@
if(!heretic_datum)
return
- //Apply our heretic mark
- var/datum/heretic_knowledge/mark/blade_mark/mark_to_apply = heretic_datum.get_knowledge(/datum/heretic_knowledge/mark/blade_mark)
+ // Apply our heretic mark
+ var/datum/heretic_knowledge/limited_amount/starting/base_blade/mark_to_apply = heretic_datum.get_knowledge(/datum/heretic_knowledge/limited_amount/starting/base_blade)
if(!mark_to_apply)
return
mark_to_apply.create_mark(user, living_target)
-
- //Remove the infusion from any blades we own (and update their sprite)
- for(var/obj/item/melee/sickly_blade/dark/to_infuse in user.get_all_contents_type(/obj/item/melee/sickly_blade/dark))
- to_infuse.infused = FALSE
- to_infuse.update_appearance(UPDATE_ICON)
+ infused = FALSE
+ update_appearance(UPDATE_ICON)
user.update_held_items()
if(!check_behind(user, living_target))
diff --git a/code/modules/antagonists/heretic/items/heretic_necks.dm b/code/modules/antagonists/heretic/items/heretic_necks.dm
index 5faf97ba005..23df3695449 100644
--- a/code/modules/antagonists/heretic/items/heretic_necks.dm
+++ b/code/modules/antagonists/heretic/items/heretic_necks.dm
@@ -146,7 +146,6 @@
icon_state = "eye_medalion"
w_class = WEIGHT_CLASS_SMALL
-
// The amulet conversion tool used by moon heretics
/obj/item/clothing/neck/heretic_focus/moon_amulet
name = "moonlight amulet"
@@ -154,31 +153,158 @@
icon = 'icons/obj/antags/eldritch.dmi'
icon_state = "moon_amulette"
w_class = WEIGHT_CLASS_SMALL
- // How much damage does this item do to the targets sanity?
+ /// How much damage does this item do to the targets sanity?
var/sanity_damage = 20
+ var/list/possible_sounds = list(
+ 'sound/items/sitcom_laugh/SitcomLaugh1.ogg',
+ 'sound/items/sitcom_laugh/SitcomLaugh2.ogg',
+ 'sound/items/sitcom_laugh/SitcomLaugh3.ogg',
+ )
+ var/valid_weapon_type = /obj/item/melee/sickly_blade
+ var/sanity_threshold = SANITY_LEVEL_INSANE
+
+/obj/item/clothing/neck/heretic_focus/moon_amulet/examine(mob/user)
+ . = ..()
+ if(IS_HERETIC(user))
+ . += span_notice("Wearing this amulet increases your healing speed by 50%")
+
+/obj/item/clothing/neck/heretic_focus/moon_amulet/equipped(mob/living/user, slot)
+ . = ..()
+ if(!IS_HERETIC(user) && (slot_flags & slot))
+ channel_amulet(user)
+ return // Equipping the amulet as a non-heretic will give you a fat mood debuff and nothing else
+ if(!(slot_flags & slot))
+ on_amulet_deactivate(user)
+ return
+ on_amulet_activate(user)
+
+/// Modifies any blades you hold/pickup/drop when the amulet is enabled
+/obj/item/clothing/neck/heretic_focus/moon_amulet/proc/on_amulet_activate(mob/living/user)
+ RegisterSignal(user, COMSIG_HERETIC_BLADE_ATTACK, PROC_REF(blade_channel))
+ RegisterSignal(user, COMSIG_MOB_EQUIPPED_ITEM, PROC_REF(on_equip_item))
+ RegisterSignal(user, COMSIG_MOB_DROPPED_ITEM, PROC_REF(on_dropped_item))
+ // Just make sure we pacify blades potentially in our hands when we put on the amulet
+ on_equip_item(user, user.get_active_held_item(), ITEM_SLOT_HANDS)
+ on_equip_item(user, user.get_inactive_held_item(), ITEM_SLOT_HANDS)
+ ADD_TRAIT(user, TRAIT_THERMAL_VISION, REF(src))
+ user.update_sight()
+ // If the equipper is a moon heretic, we buff their passive
+ var/datum/status_effect/heretic_passive/moon/moon_passive = user.has_status_effect(/datum/status_effect/heretic_passive/moon)
+ moon_passive?.amulet_equipped = TRUE
+
+/// Modifies any blades you hold/pickup/drop when the amulet is disabled
+/obj/item/clothing/neck/heretic_focus/moon_amulet/proc/on_amulet_deactivate(mob/living/user)
+ // Make sure to restore the values of any blades we might be holding when our amulet is deactivated
+ on_dropped_item(user, user.get_active_held_item())
+ on_dropped_item(user, user.get_inactive_held_item())
+ UnregisterSignal(user, list(COMSIG_HERETIC_BLADE_ATTACK, COMSIG_MOB_EQUIPPED_ITEM, COMSIG_MOB_DROPPED_ITEM))
+ REMOVE_TRAIT(user, TRAIT_THERMAL_VISION, REF(src))
+ user.update_sight()
+ var/datum/status_effect/heretic_passive/moon/moon_passive = user.has_status_effect(/datum/status_effect/heretic_passive/moon)
+ moon_passive?.amulet_equipped = FALSE
+
+/obj/item/clothing/neck/heretic_focus/moon_amulet/dropped(mob/living/user)
+ on_amulet_deactivate(user)
+ return ..()
/obj/item/clothing/neck/heretic_focus/moon_amulet/attack(mob/living/target, mob/living/user, list/modifiers, list/attack_modifiers)
- var/mob/living/carbon/human/hit = target
- if(!IS_HERETIC_OR_MONSTER(user))
- user.balloon_alert(user, "you feel a presence watching you")
- user.add_mood_event("Moon Amulet Insanity", /datum/mood_event/amulet_insanity)
- user.mob_mood.adjust_sanity(-50)
+ if(channel_amulet(user, target))
return
+ return ..()
- if(hit.can_block_magic(MAGIC_RESISTANCE|MAGIC_RESISTANCE_MIND))
+/obj/item/clothing/neck/heretic_focus/moon_amulet/proc/blade_channel(mob/living/attacker, mob/living/victim)
+ SIGNAL_HANDLER
+ channel_amulet(attacker, victim)
+
+/// Makes whoever the target is a bit more insane. If they are insane enough, they will be zombified into a moon zombie
+/obj/item/clothing/neck/heretic_focus/moon_amulet/proc/channel_amulet(mob/user, atom/target)
+
+ if(!isliving(user))
+ return FALSE
+ var/mob/living/living_user = user
+ if(!IS_HERETIC_OR_MONSTER(living_user))
+ living_user.balloon_alert(living_user, "you feel a presence watching you")
+ living_user.add_mood_event("Moon Amulet Insanity", /datum/mood_event/amulet_insanity)
+ living_user.mob_mood.adjust_sanity(-50)
+ return FALSE
+ if(!isliving(target))
+ return FALSE
+ var/mob/living/living_target = target
+
+ if(!ishuman(target))
+ living_target.adjustFireLoss(30)
+ return TRUE
+ var/mob/living/carbon/human/human_target = target
+ if(IS_HERETIC_OR_MONSTER(human_target))
+ living_user.balloon_alert(living_user, "resists effects!")
+ return FALSE
+ if(human_target.has_status_effect(/datum/status_effect/moon_slept) || human_target.has_status_effect(/datum/status_effect/moon_converted))
+ human_target.balloon_alert(living_user, "causing damage!")
+ human_target.adjustOrganLoss(ORGAN_SLOT_BRAIN, 25)
+ return FALSE
+ if(human_target.can_block_magic(MAGIC_RESISTANCE_MOON))
+ return FALSE
+ if(!human_target.mob_mood)
+ return FALSE
+ if(human_target.mob_mood.sanity_level < sanity_threshold)
+ human_target.balloon_alert(living_user, "their mind is too strong!")
+ human_target.add_mood_event("Moon Amulet Insanity", /datum/mood_event/amulet_insanity)
+ human_target.mob_mood.adjust_sanity(-sanity_damage)
+ else
+ if(HAS_TRAIT(target, TRAIT_MINDSHIELD))
+ human_target.balloon_alert(living_user, "their mind almost bends but something protects it!")
+ human_target.apply_status_effect(/datum/status_effect/moon_slept)
+ return TRUE
+ human_target.balloon_alert(living_user, "their mind bends to see the truth!")
+ human_target.apply_status_effect(/datum/status_effect/moon_converted)
+ living_user.log_message("made [human_target] insane.", LOG_GAME)
+ human_target.log_message("was driven insane by [living_user]", LOG_GAME)
+ return TRUE
+
+/// Modifies any blades that we equip while wearing the amulet
+/obj/item/clothing/neck/heretic_focus/moon_amulet/proc/on_equip_item(mob/user, obj/item/blade, slot)
+ SIGNAL_HANDLER
+ if(!istype(blade, valid_weapon_type))
+ return // We only care about modifying blades
+ if(slot & ITEM_SLOT_HANDS)
+ blade.force = 0
+ blade.wound_bonus = 0
+ blade.exposed_wound_bonus = 0
+ blade.armour_penetration = 200
+ RegisterSignal(blade, COMSIG_SEND_ITEM_ATTACK_MESSAGE_OBJECT, PROC_REF(modify_attack_message))
return
+ blade.force = initial(blade.force)
+ blade.wound_bonus = initial(blade.wound_bonus)
+ blade.exposed_wound_bonus = initial(blade.exposed_wound_bonus)
+ blade.armour_penetration = initial(blade.armour_penetration)
+ UnregisterSignal(blade, COMSIG_SEND_ITEM_ATTACK_MESSAGE_OBJECT)
- if(!hit.mob_mood)
- return
+/obj/item/clothing/neck/heretic_focus/moon_amulet/proc/modify_attack_message(obj/item/weapon, mob/living/victim, mob/living/attacker)
+ SIGNAL_HANDLER
- if(hit.mob_mood.sanity_level > SANITY_LEVEL_UNSTABLE)
- user.balloon_alert(user, "their mind is too strong!")
- hit.add_mood_event("Moon Amulet Insanity", /datum/mood_event/amulet_insanity)
- hit.mob_mood.adjust_sanity(-sanity_damage)
- return ..()
+ var/list/attack_list = list(
+ "You sweep [weapon] towards [victim], splitting [victim.p_Their()] image in two.",
+ "You strike [victim] with [weapon], spilling forth a cascade from within. Immaculate.",
+ "As it bite deep, your [weapon] unburdens [victim] of unneeded thought.",
+ )
+ to_chat(attacker, span_danger(pick(attack_list)))
- user.balloon_alert(user, "their mind bends to see the truth!")
- hit.apply_status_effect(/datum/status_effect/moon_converted)
- user.log_message("made [target] insane.", LOG_GAME)
- hit.log_message("was driven insane by [user]")
- . = ..()
+ var/list/victim_list = list(
+ "You are struck by [attacker], but the [weapon] tears away something more than parts of your body.",
+ "You see an arch of light as [attacker]'s [weapon] twists towards you, and you see the world briefly in tetrachrome.",
+ "As [attacker] carves into you with [weapon], you lose something deep within. The agony is worse than any wound.",
+ )
+ to_chat(victim, span_userdanger(pick(victim_list)))
+ playsound(attacker, pick(possible_sounds), 40, TRUE)
+ return SIGNAL_MESSAGE_MODIFIED
+
+/// Modifies any blades that we drop while wearing the amulet
+/obj/item/clothing/neck/heretic_focus/moon_amulet/proc/on_dropped_item(mob/user, obj/item/dropped_item)
+ SIGNAL_HANDLER
+ if(!istype(dropped_item, valid_weapon_type))
+ return // We only care about modifying blades
+ dropped_item.force = initial(dropped_item.force)
+ dropped_item.wound_bonus = initial(dropped_item.wound_bonus)
+ dropped_item.exposed_wound_bonus = initial(dropped_item.exposed_wound_bonus)
+ dropped_item.armour_penetration = initial(dropped_item.armour_penetration)
+ UnregisterSignal(dropped_item, COMSIG_SEND_ITEM_ATTACK_MESSAGE_OBJECT)
diff --git a/code/modules/antagonists/heretic/items/labyrinth_handbook.dm b/code/modules/antagonists/heretic/items/labyrinth_handbook.dm
index 178b8b16da5..c1f5de8c985 100644
--- a/code/modules/antagonists/heretic/items/labyrinth_handbook.dm
+++ b/code/modules/antagonists/heretic/items/labyrinth_handbook.dm
@@ -2,14 +2,19 @@
name = "labyrinth pages"
desc = "A field of papers flying in the air, repulsing heathens with impossible force."
icon_state = "lintel"
- initial_duration = 8 SECONDS
+ initial_duration = 15 SECONDS
+
+/obj/effect/forcefield/wizard/heretic/CanAllowThrough(atom/movable/mover, border_dir)
+ if(istype(mover.throwing?.get_thrower(), /obj/effect/forcefield/wizard/heretic))
+ return TRUE
+ return ..()
/obj/effect/forcefield/wizard/heretic/Bumped(mob/living/bumpee)
. = ..()
if(!istype(bumpee) || IS_HERETIC_OR_MONSTER(bumpee))
return
var/throwtarget = get_edge_target_turf(loc, get_dir(loc, get_step_away(bumpee, loc)))
- bumpee.safe_throw_at(throwtarget, 10, 1, force = MOVE_FORCE_EXTREMELY_STRONG)
+ bumpee.safe_throw_at(throwtarget, 10, 10, src, force = MOVE_FORCE_EXTREMELY_STRONG)
visible_message(span_danger("[src] repulses [bumpee] in a storm of paper!"))
///A heretic item that spawns a barrier at the clicked turf, 3 uses
@@ -31,15 +36,21 @@
pickup_sound = 'sound/items/handling/book_pickup.ogg'
///what type of barrier do we spawn when used
var/barrier_type = /obj/effect/forcefield/wizard/heretic
- ///how many uses do we have left
- var/uses = 3
+ /// Current charges remaining
+ var/charges = 5
+ /// Max possible amount of charges
+ var/max_charges = 5
+ /// List that contains each timer for the charge
+ var/list/charge_timers = list()
+ /// How long before a charge is restored
+ var/charge_time = 15 SECONDS
/obj/item/heretic_labyrinth_handbook/examine(mob/user)
. = ..()
if(!IS_HERETIC_OR_MONSTER(user))
return
. += span_hypnophrase("Materializes a barrier upon any tile in sight, which only you can pass through. Lasts 8 seconds.")
- . += span_hypnophrase("It has [uses] uses left.")
+ . += span_notice("It has [charges] charge\s remaining.")
/obj/item/heretic_labyrinth_handbook/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(HAS_TRAIT(interacting_with, TRAIT_COMBAT_MODE_SKIP_INTERACTION))
@@ -56,6 +67,10 @@
human_user.dropItemToGround(src)
return ITEM_INTERACT_BLOCKING
+ if(charges <= 0)
+ balloon_alert(user, "no charges!")
+ return ITEM_INTERACT_BLOCKING
+
var/turf/turf_target = get_turf(interacting_with)
if(locate(barrier_type) in turf_target)
user.balloon_alert(user, "already occupied!")
@@ -64,8 +79,9 @@
new /obj/effect/temp_visual/paper_scatter(turf_target)
playsound(turf_target, 'sound/effects/magic/smoke.ogg', 30)
new barrier_type(turf_target, user)
- uses--
- if(uses <= 0)
- to_chat(user, span_warning("[src] falls apart, turning into ash and dust!"))
- qdel(src)
+ charges--
+ charge_timers.Add(addtimer(CALLBACK(src, PROC_REF(recharge)), charge_time, TIMER_STOPPABLE))
return ITEM_INTERACT_SUCCESS
+
+/obj/item/heretic_labyrinth_handbook/proc/recharge()
+ charges = min(charges+1, max_charges)
diff --git a/code/modules/antagonists/heretic/items/madness_mask.dm b/code/modules/antagonists/heretic/items/madness_mask.dm
index 1be99d41012..4e80ee862f6 100644
--- a/code/modules/antagonists/heretic/items/madness_mask.dm
+++ b/code/modules/antagonists/heretic/items/madness_mask.dm
@@ -4,9 +4,10 @@
desc = "A mask created from suffering. When you look into its eyes, it looks back."
icon_state = "mad_mask"
inhand_icon_state = null
+ clothing_flags = BLOCK_GAS_SMOKE_EFFECT | MASKINTERNALS
+ flags_cover = MASKCOVERSEYES | MASKCOVERSMOUTH | PEPPERPROOF
+ resistance_flags = LAVA_PROOF | FIRE_PROOF
w_class = WEIGHT_CLASS_SMALL
- flags_cover = MASKCOVERSEYES
- resistance_flags = FLAMMABLE
flags_inv = HIDEFACE|HIDEFACIALHAIR|HIDESNOUT
///Who is wearing this
var/mob/living/carbon/human/local_user
diff --git a/code/modules/antagonists/heretic/items/unfathomable_curio.dm b/code/modules/antagonists/heretic/items/unfathomable_curio.dm
index 44af5f0acb1..b9a35fc00f1 100644
--- a/code/modules/antagonists/heretic/items/unfathomable_curio.dm
+++ b/code/modules/antagonists/heretic/items/unfathomable_curio.dm
@@ -67,7 +67,7 @@
to_chat(wearer, span_warning("Laughter echoes in your mind...."))
wearer.adjustOrganLoss(ORGAN_SLOT_BRAIN, 40)
wearer.dropItemToGround(src, TRUE)
- wearer.gain_trauma(pick(brain_traumas) ,TRAUMA_RESILIENCE_ABSOLUTE)
+ wearer.gain_trauma(pick(brain_traumas), TRAUMA_RESILIENCE_MAGIC)
/obj/item/storage/belt/unfathomable_curio/examine(mob/living/carbon/user)
. = ..()
diff --git a/code/modules/antagonists/heretic/knowledge/_heretic_paths.dm b/code/modules/antagonists/heretic/knowledge/_heretic_paths.dm
index 5e3583e14b3..5890e459c16 100644
--- a/code/modules/antagonists/heretic/knowledge/_heretic_paths.dm
+++ b/code/modules/antagonists/heretic/knowledge/_heretic_paths.dm
@@ -1,199 +1,387 @@
-//Global typecache of all heretic knowledges -> instantiate the tree columns -> make them link themselves -> replace the old heretic stuff
-//heretic research tree is a directional graph so we can use some basic graph stuff to make internally handling it easier
-GLOBAL_LIST(heretic_research_tree)
-
-//HKT = Heretic Knowledge Tree (Heretic Research Tree :3) these objects really only exist for a short period of time at startup and then get deleted
-/datum/heretic_knowledge_tree_column
- ///Route that symbolizes what path this is
- var/route
- ///Used to determine if this is a side path or a main path
- var/abstract_parent_type = /datum/heretic_knowledge_tree_column
- ///IDs od neighbours (to left and right)
- var/neighbour_type_left
- var/neighbour_type_right
- ///Tier1 knowledge (or knowledges)
- var/tier1
- ///Tier2 knowledge (or knowledges)
- var/tier2
- ///Tier3 knowledge (or knowledges)
- var/tier3
- ///UI background
- var/ui_bgr = "node_side"
-
-/datum/heretic_knowledge_tree_column/main
- abstract_parent_type = /datum/heretic_knowledge_tree_column/main
-
- ///Starting knowledge - first thing you pick
- var/start
- ///Grasp upgrade
- var/grasp
- ///Mark upgrade
- var/mark
- ///Unique ritual of knoweldge
- var/ritual_of_knowledge
- ///Path specific unique ability
- var/unique_ability
- ///Blade upgrade
- var/blade
- ///Ascension
- var/ascension
-
-/proc/generate_heretic_research_tree()
- var/list/heretic_research_tree = list()
-
- //Initialize the data structure
- for(var/type in subtypesof(/datum/heretic_knowledge))
- heretic_research_tree[type] = list()
- heretic_research_tree[type][HKT_NEXT] = list()
- heretic_research_tree[type][HKT_BAN] = list()
- heretic_research_tree[type][HKT_DEPTH] = 1
- heretic_research_tree[type][HKT_UI_BGR] = "node_side"
-
- var/datum/heretic_knowledge/knowledge = type
- if(initial(knowledge.is_starting_knowledge))
- heretic_research_tree[type][HKT_ROUTE] = PATH_START
- continue
-
- heretic_research_tree[type][HKT_ROUTE] = null
+/// Assoc list of heretic_route.path string -> list of knowledges in that path, see generate_heretic_path() in code/modules/antagonists/heretic/knowledge/_heretic_paths.dm
+GLOBAL_LIST(heretic_path_knowledges)
+GLOBAL_LIST_INIT(heretic_path_datums, init_heretic_path_datums())
+/proc/init_heretic_path_datums()
var/list/paths = list()
- for(var/type in subtypesof(/datum/heretic_knowledge_tree_column))
- var/datum/heretic_knowledge_tree_column/column_path = type
+ for(var/datum/heretic_knowledge_tree_column/column_path as anything in subtypesof(/datum/heretic_knowledge_tree_column))
if(initial(column_path.abstract_parent_type) == column_path)
continue
+ var/datum/heretic_knowledge_tree_column/heretic_route = new column_path()
+ paths[heretic_route.route] += heretic_route
+ return paths
- var/datum/heretic_knowledge_tree_column/column = new type()
- paths[column.type] = column
+/datum/heretic_knowledge_tree_column
+ ///Route that symbolizes what path this is, MUST be unique between paths
+ var/route = PATH_START
+ var/icon_state = "dark_blade"
+ /*
+ * Complexity grades:
+ * Easy = COLOR_GREEN
+ * Medium = COLOR_YELLOW
+ * Hard = COLOR_RED
+ */
+ var/complexity = "Insane"
+ var/complexity_color = COLOR_WHITE
+ var/list/icon = list(
+ "icon" = 'icons/obj/weapons/khopesh.dmi',
+ "state" = "dark_blade",
+ "frame" = 1,
+ "dir" = SOUTH,
+ "moving" = FALSE,
+ )
+ var/list/description = list("A heretic knowledge tree column, used to define a path of knowledge.")
+ var/list/pros = list("Is bad", "Is very bad", "Is extremely bad")
+ var/list/cons = list("Smells bad", "Looks bad", "Tastes bad")
+ var/list/tips = list("Don't use it", "Don't touch it", "Don't look at it")
+ ///Used to determine if this is a side path or a main path
+ var/abstract_parent_type = /datum/heretic_knowledge_tree_column
+ ///UI background
+ var/ui_bgr = BGR_SIDE
- var/list/start_blacklist = list()
- var/list/grasp_blacklist = list()
- var/list/mark_blacklist = list()
- var/list/blade_blacklist = list()
- var/list/asc_blacklist = list()
+ //-- Knowledge in order of unlocking
+ ///Starting knowledge - first thing you pick. Gives you access to blades, grasp, mark and passive
+ var/datum/heretic_knowledge/limited_amount/starting/start
+ ///Tier1 knowledge
+ var/knowledge_tier1
+ ///Tier2 knowledge
+ var/knowledge_tier2
+ ///Path-Specific Heretic robes
+ var/robes
+ ///Tier3 knowledge
+ var/knowledge_tier3
+ ///Blade upgrade
+ var/blade
+ ///Tier4 knowledge
+ var/knowledge_tier4
+ ///Ascension
+ var/ascension
+ // Drafting system, if a path has any side-knowledge that is guaranteed to be one of the options
+ /// Knowledge guaranteed to show up in the first draft
+ var/guaranteed_side_tier1
+ /// Knowledge guaranteed to show up in the second draft
+ var/guaranteed_side_tier2
+ /// Knowledge guaranteed to show up in the third draft
+ var/guaranteed_side_tier3
- for(var/id in paths)
- if(!istype(paths[id],/datum/heretic_knowledge_tree_column/main))
- continue
- var/datum/heretic_knowledge_tree_column/main/column = paths[id]
- start_blacklist += column.start
- grasp_blacklist += column.grasp
- mark_blacklist += column.mark
- blade_blacklist += column.blade
- asc_blacklist += column.ascension
+/datum/heretic_knowledge_tree_column/proc/get_ui_data(datum/antagonist/heretic/our_heretic, category)
+ var/list/power_info = our_heretic.heretic_shops[category]
+ var/list/data = list(
+ "route" = route,
+ "icon" = icon.Copy(),
+ "complexity" = complexity,
+ "complexity_color" = complexity_color,
+ "description" = description.Copy(),
+ "pros" = pros.Copy(),
+ "cons" = cons.Copy(),
+ "tips" = tips.Copy(),
+ "starting_knowledge" = our_heretic.get_knowledge_data(start, power_info),
+ )
- heretic_research_tree[/datum/heretic_knowledge/spell/basic][HKT_NEXT] += start_blacklist
+ data["preview_abilities"] = list(
+ our_heretic.get_knowledge_data(knowledge_tier1, power_info, category = category),
+ our_heretic.get_knowledge_data(knowledge_tier2, power_info, category = category),
+ our_heretic.get_knowledge_data(knowledge_tier3, power_info, category = category),
+ our_heretic.get_knowledge_data(knowledge_tier4, power_info, category = category),
+ )
- for(var/id in paths)
- var/datum/heretic_knowledge_tree_column/this_column = paths[id]
- var/datum/heretic_knowledge_tree_column/neighbour_0 = paths[this_column.neighbour_type_left]
- var/datum/heretic_knowledge_tree_column/neighbour_1 = paths[this_column.neighbour_type_right]
- //horizontal (two way)
- var/list/tier1 = this_column.tier1
- var/list/tier2 = this_column.tier2
- var/list/tier3 = this_column.tier3
+ var/datum/status_effect/heretic_passive/passive = new start.eldritch_passive()
+ data["passive"] = list(
+ "name" = initial(passive.name),
+ "description" = passive.passive_descriptions.Copy(),
+ )
+ qdel(passive)
- //Tier1, 2 and 3 can technically be lists so we handle them here
- if(!islist(this_column.tier1))
- tier1 = list(this_column.tier1)
+ return data
- if(!islist(this_column.tier2))
- tier2 = list(this_column.tier2)
- if(!islist(this_column.tier3))
- tier3 = list(this_column.tier3)
+/**
+ * Modifies the 2 lists provided in the arguments and sets it up so the heretic can actually start researching stuff.
+ * Specifically generates the tier 1-4 knowledges + start ones for each path as well for the preview (should probably be moved to use the actual list of per-path researches)
+ * Importantly, this adds HKT_NEXT's for the starting knowledges so the heretic's next researches are considered valid
+**/
+/proc/generate_heretic_starting_knowledge(list/starting_knowledges = list())
+ for(var/knowledge in GLOB.heretic_start_knowledge)
+ starting_knowledges[knowledge] = make_knowledge_entry(knowledge, null, HERETIC_KNOWLEDGE_START)
- for(var/t1_knowledge in tier1)
- heretic_research_tree[t1_knowledge][HKT_NEXT] += neighbour_0.tier1
- heretic_research_tree[t1_knowledge][HKT_NEXT] += neighbour_1.tier1
- heretic_research_tree[t1_knowledge][HKT_ROUTE] = this_column.route
- heretic_research_tree[t1_knowledge][HKT_UI_BGR] = this_column.ui_bgr
- heretic_research_tree[t1_knowledge][HKT_DEPTH] = 4
+ var/list/start_knowledges = list()
+ var/list/start_knowledge_ids = list()
+ for(var/route in GLOB.heretic_path_datums)
+ var/datum/heretic_knowledge_tree_column/column_path = GLOB.heretic_path_datums[route]
+ var/start_knowledge = column_path::start
+ // why aren't the tiered knowledges in a list?!?!? (initial() probably)
+ var/t1_knowledge = column_path::knowledge_tier1
+ var/t2_knowledge = column_path::knowledge_tier2
+ var/t3_knowledge = column_path::knowledge_tier3
+ var/t4_knowledge = column_path::knowledge_tier4
+ starting_knowledges[start_knowledge] = make_knowledge_entry(start_knowledge, column_path, HERETIC_KNOWLEDGE_START, HKT_DEPTH_START)
+ starting_knowledges[t1_knowledge] = make_knowledge_entry(t1_knowledge, column_path, HERETIC_KNOWLEDGE_START, HKT_DEPTH_TIER_1)
+ starting_knowledges[t2_knowledge] = make_knowledge_entry(t2_knowledge, column_path, HERETIC_KNOWLEDGE_START, HKT_DEPTH_TIER_2)
+ starting_knowledges[t3_knowledge] = make_knowledge_entry(t3_knowledge, column_path, HERETIC_KNOWLEDGE_START, HKT_DEPTH_TIER_3)
+ starting_knowledges[t4_knowledge] = make_knowledge_entry(t4_knowledge, column_path, HERETIC_KNOWLEDGE_START, HKT_DEPTH_TIER_4)
+ // start the HKT_NEXT chain here
+ starting_knowledges[/datum/heretic_knowledge/spell/basic][HKT_NEXT] += starting_knowledges[start_knowledge][HKT_ID]
+ // notably, the heretic's tree isn't yet generated so we have to generate the ID here instead of fetching it, hopefully this doesn't break
+ starting_knowledges[start_knowledge][HKT_NEXT] += make_knowledge_id(t1_knowledge, HERETIC_KNOWLEDGE_TREE)
+ start_knowledges += start_knowledge
+ start_knowledge_ids += starting_knowledges[start_knowledge][HKT_ID]
- for(var/t2_knowledge in tier2)
- heretic_research_tree[t2_knowledge][HKT_NEXT] += neighbour_0.tier2
- heretic_research_tree[t2_knowledge][HKT_NEXT] += neighbour_1.tier2
- heretic_research_tree[t2_knowledge][HKT_ROUTE] = this_column.route
- heretic_research_tree[t2_knowledge][HKT_UI_BGR] = this_column.ui_bgr
- heretic_research_tree[t2_knowledge][HKT_DEPTH] = 8
+ // make sure to prevent starting on other paths
+ for(var/knowledge_path in start_knowledges)
+ var/list/target_knowledge = starting_knowledges[knowledge_path]
+ target_knowledge[HKT_BAN] += start_knowledge_ids - target_knowledge[HKT_ID]
- for(var/t3_knowledge in tier3)
- heretic_research_tree[t3_knowledge][HKT_NEXT] += neighbour_0.tier3
- heretic_research_tree[t3_knowledge][HKT_NEXT] += neighbour_1.tier3
- heretic_research_tree[t3_knowledge][HKT_ROUTE] = this_column.route
- heretic_research_tree[t3_knowledge][HKT_UI_BGR] = this_column.ui_bgr
- heretic_research_tree[t3_knowledge][HKT_DEPTH] = 10
+//TODO: use this to generate the globallist
+/datum/antagonist/heretic/proc/generate_heretic_research_tree()
+ if(!heretic_path)
+ stack_trace("somehow called generate_heretic_research_tree with a falsey heretic_path")
+ return
+ if(!length(GLOB.heretic_path_knowledges))
+ GLOB.heretic_path_knowledges = generate_global_heretic_tree()
+ var/list/selected_route = GLOB.heretic_path_knowledges[heretic_path.route]
+ if(!selected_route)
+ stack_trace("called generate_heretic_research_tree with a invalid heretic_path.route")
+ return
+ heretic_shops[HERETIC_KNOWLEDGE_TREE] = deep_copy_list_alt(selected_route)
- //Everything below this line is considered to be a "main path" and not a side path
- //Since we are handling the heretic research tree column by column this is required
- if(this_column.abstract_parent_type != /datum/heretic_knowledge_tree_column/main)
- continue
-
- var/datum/heretic_knowledge_tree_column/main/main_column = this_column
- //vertical (one way)
- heretic_research_tree[/datum/heretic_knowledge/spell/basic] += main_column.start
- heretic_research_tree[main_column.start][HKT_NEXT] += main_column.grasp
- heretic_research_tree[main_column.grasp][HKT_NEXT] += main_column.tier1
- //t1 handling
- for(var/t1_knowledge in tier1)
- heretic_research_tree[t1_knowledge][HKT_NEXT] += main_column.mark
-
- heretic_research_tree[main_column.mark][HKT_NEXT] += main_column.ritual_of_knowledge
- heretic_research_tree[main_column.ritual_of_knowledge][HKT_NEXT] += main_column.unique_ability
- heretic_research_tree[main_column.unique_ability][HKT_NEXT] += main_column.tier2
- //t2 handling
- for(var/t2_knowledge in tier2)
- heretic_research_tree[t2_knowledge][HKT_NEXT] += main_column.blade
-
- heretic_research_tree[main_column.blade][HKT_NEXT] += main_column.tier3
- //t3 handling
- for(var/t3_knowledge in tier3)
- heretic_research_tree[t3_knowledge][HKT_NEXT] += main_column.ascension
-
- //blacklist
- heretic_research_tree[main_column.start][HKT_BAN] += (start_blacklist - main_column.start) + (asc_blacklist - main_column.ascension)
- heretic_research_tree[main_column.grasp][HKT_BAN] += (grasp_blacklist - main_column.grasp)
- heretic_research_tree[main_column.mark][HKT_BAN] += (mark_blacklist - main_column.mark)
- heretic_research_tree[main_column.blade][HKT_BAN] += (blade_blacklist - main_column.blade)
-
- //route stuff
- heretic_research_tree[main_column.start][HKT_ROUTE] = main_column.route
- heretic_research_tree[main_column.grasp][HKT_ROUTE] = main_column.route
- heretic_research_tree[main_column.mark][HKT_ROUTE] = main_column.route
- heretic_research_tree[main_column.ritual_of_knowledge][HKT_ROUTE] = main_column.route
- heretic_research_tree[main_column.unique_ability][HKT_ROUTE] = main_column.route
- heretic_research_tree[main_column.blade][HKT_ROUTE] = main_column.route
- heretic_research_tree[main_column.ascension][HKT_ROUTE] = main_column.route
-
- heretic_research_tree[main_column.start][HKT_UI_BGR] = main_column.ui_bgr
- heretic_research_tree[main_column.grasp][HKT_UI_BGR] = main_column.ui_bgr
- heretic_research_tree[main_column.mark][HKT_UI_BGR] = main_column.ui_bgr
- heretic_research_tree[main_column.ritual_of_knowledge][HKT_UI_BGR] = main_column.ui_bgr
- heretic_research_tree[main_column.unique_ability][HKT_UI_BGR] = main_column.ui_bgr
- heretic_research_tree[main_column.blade][HKT_UI_BGR] = main_column.ui_bgr
- heretic_research_tree[main_column.ascension][HKT_UI_BGR] = main_column.ui_bgr
- //depth stuff
- heretic_research_tree[main_column.start][HKT_DEPTH] = 2
- heretic_research_tree[main_column.grasp][HKT_DEPTH] = 3
- heretic_research_tree[main_column.mark][HKT_DEPTH] = 5
- heretic_research_tree[main_column.ritual_of_knowledge][HKT_DEPTH] = 6
- heretic_research_tree[main_column.unique_ability][HKT_DEPTH] = 7
- heretic_research_tree[main_column.blade][HKT_DEPTH] = 9
- heretic_research_tree[main_column.ascension][HKT_DEPTH] = 11
-
- //Per path bullshit goes here \/\/\/
- for(var/t2_knowledge in tier2)
- heretic_research_tree[t2_knowledge][HKT_NEXT] += /datum/heretic_knowledge/reroll_targets
-
- // If you want to do any custom bullshit put it here \/\/\/
- heretic_research_tree[/datum/heretic_knowledge/reroll_targets][HKT_ROUTE] = PATH_SIDE
- heretic_research_tree[/datum/heretic_knowledge/reroll_targets][HKT_DEPTH] = 8
-
- heretic_research_tree[/datum/heretic_knowledge/rifle][HKT_NEXT] += /datum/heretic_knowledge/rifle_ammo
- heretic_research_tree[/datum/heretic_knowledge/rifle_ammo][HKT_ROUTE] = PATH_SIDE
- heretic_research_tree[/datum/heretic_knowledge/rifle_ammo][HKT_DEPTH] = heretic_research_tree[/datum/heretic_knowledge/rifle][HKT_DEPTH]
-
- //and we're done
- QDEL_LIST_ASSOC_VAL(paths)
+/proc/generate_global_heretic_tree()
+ var/heretic_research_tree = list()
+ for(var/path in GLOB.heretic_path_datums)
+ var/datum/heretic_knowledge_tree_column/heretic_route = GLOB.heretic_path_datums[path]
+ heretic_research_tree[path] = generate_heretic_path(heretic_route)
+ if(!length(heretic_research_tree))
+ CRASH("Somehow generated an empty heretic research tree, this should never happen.")
return heretic_research_tree
+
+/proc/make_knowledge_entry(datum/heretic_knowledge/knowledge, datum/heretic_knowledge_tree_column/path, category = HERETIC_KNOWLEDGE_TREE, depth = 1, cost = -1)
+ return list(
+ HKT_NEXT = list(),
+ HKT_BAN = list(),
+ HKT_DEPTH = depth,
+ HKT_PURCHASED_DEPTH = 0,
+ HKT_UI_BGR = path ? path::ui_bgr : BGR_SIDE,
+ HKT_COST = cost != -1 ? cost : knowledge::cost,
+ HKT_ROUTE = path ? path::route : null,
+ HKT_CATEGORY = category,
+ HKT_ID = make_knowledge_id(knowledge, category),
+ )
+
+/// ID's are not unique, the same knowledge with the same type in the same shop will always have the same ID.
+/proc/make_knowledge_id(datum/heretic_knowledge/knowledge, shop_category = HERETIC_KNOWLEDGE_TREE)
+ var/type_string = replacetext("[knowledge::type]", "/", "", 1, 2)
+ var/our_type = replacetext(type_string, "/", "_")
+ return "[shop_category]/[our_type]"
+
+/proc/generate_heretic_path(datum/heretic_knowledge_tree_column/heretic_path)
+ var/list/heretic_research_tree = list()
+ //Initialize the data structure
+ var/list/tree_paths = list()
+
+ tree_paths += list(
+ heretic_path.knowledge_tier1,
+ heretic_path.knowledge_tier2,
+ heretic_path.knowledge_tier3,
+ heretic_path.knowledge_tier4,
+ heretic_path.robes,
+ heretic_path.blade,
+ heretic_path.ascension,
+ )
+
+ for(var/datum/heretic_knowledge/type as anything in tree_paths)
+ heretic_research_tree[type] = make_knowledge_entry(type, heretic_path, depth = 1)
+
+ var/knowledge_tier1 = heretic_path.knowledge_tier1
+ var/knowledge_tier2 = heretic_path.knowledge_tier2
+ var/knowledge_tier3 = heretic_path.knowledge_tier3
+ var/knowledge_tier4 = heretic_path.knowledge_tier4
+
+ //horizontal (two way)
+ heretic_research_tree[knowledge_tier1][HKT_DEPTH] = HKT_DEPTH_TIER_1
+ heretic_research_tree[knowledge_tier1][HKT_NEXT] += heretic_research_tree[knowledge_tier2][HKT_ID]
+
+ heretic_research_tree[knowledge_tier2][HKT_DEPTH] = HKT_DEPTH_TIER_2
+ heretic_research_tree[knowledge_tier2][HKT_NEXT] += heretic_research_tree[heretic_path.robes][HKT_ID]
+
+ heretic_research_tree[knowledge_tier3][HKT_DEPTH] = HKT_DEPTH_TIER_3
+ heretic_research_tree[knowledge_tier3][HKT_NEXT] += heretic_research_tree[heretic_path.blade][HKT_ID]
+ heretic_research_tree[heretic_path.robes][HKT_NEXT] += heretic_research_tree[knowledge_tier3][HKT_ID]
+
+ heretic_research_tree[knowledge_tier4][HKT_DEPTH] = HKT_DEPTH_TIER_4
+ heretic_research_tree[heretic_path.blade][HKT_NEXT] += heretic_research_tree[knowledge_tier4][HKT_ID]
+ heretic_research_tree[knowledge_tier4][HKT_NEXT] += heretic_research_tree[heretic_path.ascension][HKT_ID]
+
+ //depth stuff
+ heretic_research_tree[heretic_path.robes][HKT_DEPTH] = HKT_DEPTH_ROBES
+ heretic_research_tree[heretic_path.blade][HKT_DEPTH] = HKT_DEPTH_ARMOR
+ heretic_research_tree[heretic_path.ascension][HKT_DEPTH] = HKT_DEPTH_ASCENSION
+ //and we're done
+ return heretic_research_tree
+
+/**
+ * Each heretic has a few drafted knowledges within their heretic knowledge tree.
+ * This is not during the knowledge tree creation because we want to know what path our heretic picks so we filter out dupe knowledges.
+ * Also generates shop knowledges as their validation is shared.
+ * Modifies shop_list and final_draft that are provided in the arguments.
+ */
+/proc/determine_drafted_knowledge(route, list/tree = list(), list/shop = list(), list/final_draft = list())
+ if(!route)
+ stack_trace("somehow called determine_drafted_knowledge with a falsey current_path")
+ return
+ var/list/heretic_research_tree = tree
+ var/datum/heretic_knowledge_tree_column/heretic_path = GLOB.heretic_path_datums[route]
+
+ /// costs by index mapped to depth
+ var/list/shop_costs = list(1, 2, 2, 2, 3)
+
+ // Relevant variables that we pull from the path
+ var/knowledge_tier1 = heretic_path.knowledge_tier1
+ var/knowledge_tier2 = heretic_path.knowledge_tier2
+ var/knowledge_tier3 = heretic_path.knowledge_tier3
+ var/knowledge_tier4 = heretic_path.knowledge_tier4
+
+ var/list/path_knowledges = list(
+ knowledge_tier1,
+ knowledge_tier2,
+ knowledge_tier3,
+ knowledge_tier4,
+ )
+
+ // Every path can have a guaranteed option that will show up in the first 3 drafts (Otherwise we just run as normal)
+ var/datum/heretic_knowledge/guaranteed_draft_t1 = heretic_path.guaranteed_side_tier1
+ var/datum/heretic_knowledge/guaranteed_draft_t2 = heretic_path.guaranteed_side_tier2
+ var/datum/heretic_knowledge/guaranteed_draft_t3 = heretic_path.guaranteed_side_tier3
+
+ var/list/guaranteed_drafts = list(
+ guaranteed_draft_t1,
+ guaranteed_draft_t2,
+ guaranteed_draft_t3,
+ )
+
+ var/list/shop_unlock_order = list(
+ knowledge_tier1,
+ knowledge_tier2,
+ heretic_path.robes,
+ knowledge_tier3,
+ knowledge_tier4,
+ )
+
+ var/list/draft_ineligible = path_knowledges.Copy()
+ draft_ineligible += guaranteed_drafts
+
+ var/list/elligible_knowledge = list()
+ var/list/shop_knowledge = list()
+ for(var/tier in 1 to HERETIC_DRAFT_TIER_MAX)
+ elligible_knowledge += list(list())
+ shop_knowledge += list(list())
+
+ for(var/datum/heretic_knowledge/potential_type as anything in subtypesof(/datum/heretic_knowledge))
+ if(potential_type::drafting_tier == 0)
+ continue
+ // Don't add the knowledge if it's obtainable later in the path
+ if(is_path_in_list(potential_type, draft_ineligible))
+ continue
+ if(!potential_type::is_shop_only)
+ elligible_knowledge[potential_type::drafting_tier] += potential_type
+ shop_knowledge[potential_type::drafting_tier] += potential_type
+
+ var/list/drafts = list(
+ list(
+ "parent_knowledge" = knowledge_tier1,
+ "guaranteed_knowledge" = guaranteed_draft_t1,
+ "probabilities" = list("1" = 50, "2" = 50, "3" = 0, "4" = 0, "5" = 0),
+ HKT_DEPTH = HKT_DEPTH_DRAFT_1,
+ ),
+ list(
+ "parent_knowledge" = knowledge_tier2,
+ "guaranteed_knowledge" = guaranteed_draft_t2,
+ "probabilities" = list("1" = 50, "2" = 25, "3" = 25, "4" = 0, "5" = 0),
+ HKT_DEPTH = HKT_DEPTH_DRAFT_2,
+ ),
+ list(
+ "parent_knowledge" = knowledge_tier3,
+ "guaranteed_knowledge" = guaranteed_draft_t3,
+ "probabilities" = list("1" = 20, "2" = 20, "3" = 20, "4" = 20, "5" = 20),
+ HKT_DEPTH = HKT_DEPTH_DRAFT_3,
+ ),
+ list(
+ "parent_knowledge" = knowledge_tier4,
+ "probabilities" = list("1" = 0, "2" = 0, "3" = 0, "4" = 0, "5" = 100),
+ HKT_DEPTH = HKT_DEPTH_DRAFT_4,
+ )
+ )
+ /// generate 3 drafts for each draft tier, while banning you from picking multiple drafts
+ for(var/draft in drafts)
+ var/parent_knowledge_path = draft["parent_knowledge"]
+ var/datum/heretic_knowledge/guaranteed_draft = draft["guaranteed_knowledge"]
+ var/list/probabilities = draft["probabilities"]
+ var/depth = draft[HKT_DEPTH]
+ var/list/draft_blacklist = list()
+
+ for(var/cycle in 1 to 3)
+ var/datum/heretic_knowledge/selected_knowledge
+ if(guaranteed_draft && cycle == 1)
+ selected_knowledge = guaranteed_draft
+ var/shop_tier = shop_knowledge[guaranteed_draft::drafting_tier]
+ if(shop_tier && !(guaranteed_draft in shop_tier))
+ shop_tier += guaranteed_draft
+ else
+ // rng kinda not correct but like, whatever
+ var/chosen_tier = min(text2num(pick_weight(probabilities)), length(elligible_knowledge))
+ var/list/picked_tier = elligible_knowledge[chosen_tier]
+ selected_knowledge = pick_n_take(picked_tier)
+
+ if(!length(picked_tier))
+ elligible_knowledge.Cut(chosen_tier, chosen_tier + 1)
+
+ if(isnull(selected_knowledge))
+ stack_trace("Failed to select a knowledge for heretic path [heretic_path] at depth [depth]. This should never happen.")
+ continue
+
+ final_draft[selected_knowledge] = make_knowledge_entry(
+ selected_knowledge,
+ null,
+ HERETIC_KNOWLEDGE_DRAFT,
+ depth,
+ 0,
+ )
+ final_draft[selected_knowledge][HKT_PURCHASED_DEPTH] = selected_knowledge::drafting_tier
+ var/draft_id = final_draft[selected_knowledge][HKT_ID]
+ draft_blacklist[selected_knowledge] = draft_id
+ heretic_research_tree[parent_knowledge_path][HKT_NEXT] |= draft_id
+
+ var/list/blacklist_ids = assoc_to_values(draft_blacklist)
+ for(var/blacklist_path in draft_blacklist)
+ var/id = draft_blacklist[blacklist_path]
+ final_draft[blacklist_path][HKT_BAN] += (blacklist_ids - id)
+
+ // all possible drafts are added to the shop, this time with costs
+ for(var/drafting_tier in 1 to length(shop_knowledge))
+ var/unlocked_by = shop_unlock_order[drafting_tier]
+ var/list/eligible_tier = shop_knowledge[drafting_tier]
+ for(var/knowledge_type in eligible_tier)
+ shop[knowledge_type] = make_knowledge_entry(
+ knowledge_type,
+ null,
+ HERETIC_KNOWLEDGE_SHOP,
+ drafting_tier,
+ shop_costs[drafting_tier],
+ )
+ var/shop_id = shop[knowledge_type][HKT_ID]
+ heretic_research_tree[unlocked_by][HKT_NEXT] |= shop_id
+ // ban the corresponding same knowledge from the final draft to prevent duplicates
+ var/found = final_draft[knowledge_type]
+ if(!found)
+ continue
+ found[HKT_BAN] |= shop_id
+
+ var/gun_path = /datum/heretic_knowledge/rifle
+ var/ammo_path = /datum/heretic_knowledge/rifle_ammo
+ shop[ammo_path] = make_knowledge_entry(ammo_path, null, HERETIC_KNOWLEDGE_SHOP, 2)
+ var/ammo_id = shop[ammo_path][HKT_ID]
+ shop[gun_path][HKT_NEXT] |= ammo_id
+
+ var/already_in = final_draft[gun_path]
+ if(already_in)
+ already_in[HKT_NEXT] |= ammo_id
diff --git a/code/modules/antagonists/heretic/knowledge/ash_lore.dm b/code/modules/antagonists/heretic/knowledge/ash_lore.dm
index 9d657c7b3e7..eb449573ca8 100644
--- a/code/modules/antagonists/heretic/knowledge/ash_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/ash_lore.dm
@@ -1,19 +1,50 @@
-
-/datum/heretic_knowledge_tree_column/main/ash
- neighbour_type_left = /datum/heretic_knowledge_tree_column/cosmic_to_ash
- neighbour_type_right = /datum/heretic_knowledge_tree_column/ash_to_moon
-
+/datum/heretic_knowledge_tree_column/ash
route = PATH_ASH
ui_bgr = "node_ash"
+ complexity = "Easy"
+ complexity_color = COLOR_GREEN
+ icon = list(
+ "icon" = 'icons/obj/weapons/khopesh.dmi',
+ "state" = "ash_blade",
+ "frame" = 1,
+ "dir" = SOUTH,
+ "moving" = FALSE,
+ )
+ description = list(
+ "The Path of Ash revolves around fire, mobility and brutal crowd control against single opponents.",
+ "Play this path if you are new to Heretic, or really enjoy hit and run playstyles.",
+ )
+ pros = list(
+ "Very potent even from the beginning of the path.",
+ "Easy access to a mobility spells and expanded vision.",
+ "Very powerful mark effect.",
+ )
+ cons = list(
+ "Has less power than most heretics beyond their starting abilities.",
+ "Lacks durability in long conflicts.",
+ "Reliant on hitting fast and hard before their opponents can mount proper countermeasures.",
+ )
+ tips = list(
+ "Your Mansus Grasp applies a short blind and a mark that puts your opponent into stamina crit when triggered by your blade. The mark can spread to nearby opponents.",
+ "Selecting this path makes you immune to high temperature damage. Remember, however, that your clothes can still burn! If you want to protect yourself from your own fire, wear a Scorched Mantle.",
+ "Your Scorched Mantle will cause you to generate firestacks on your own body (Make sure you toggle the effect!). Upon reaching 5 fire stacks, your ashen spells will be empowered (indicated by your spells being highlighted in green).",
+ "Your Ashen passage is a short cooldown jaunt capable of removing restraints. If empowered, it gains a longer jaunt time, and also will remove stuns and stamina crit.",
+ "Volcano blast can make short work of your enemies, should they be foolish enough to stick close to each other. If empowered, it will have no cast time and generate twice the amount of firestacks. Burn the heathens to ashes!",
+ "Do not neglect the Mask of Madness. It will slowly sap the stamina of your enemies and make them hallucinate.",
+ "Make sure to set as many enemies on fire as you possibly can! Nightwatcher's Rebirth will heal you and have its cooldown reduced based on how many mobs you siphon.",
+ "Your ascension grants you complete immunity to environmental hazards, including bombs! But you are still vulnerable to more conventional weaponry. Do not become overconfident.",
+ )
+
start = /datum/heretic_knowledge/limited_amount/starting/base_ash
- grasp = /datum/heretic_knowledge/ashen_grasp
- tier1 = /datum/heretic_knowledge/spell/ash_passage
- mark = /datum/heretic_knowledge/mark/ash_mark
- ritual_of_knowledge = /datum/heretic_knowledge/knowledge_ritual/ash
- unique_ability = /datum/heretic_knowledge/spell/fire_blast
- tier2 = /datum/heretic_knowledge/mad_mask
+ knowledge_tier1 = /datum/heretic_knowledge/spell/ash_passage
+ guaranteed_side_tier1 = /datum/heretic_knowledge/medallion
+ knowledge_tier2 = /datum/heretic_knowledge/spell/fire_blast
+ guaranteed_side_tier2 = /datum/heretic_knowledge/rifle
+ robes = /datum/heretic_knowledge/armor/ash
+ knowledge_tier3 = /datum/heretic_knowledge/mad_mask
+ guaranteed_side_tier3 = /datum/heretic_knowledge/summon/ashy
blade = /datum/heretic_knowledge/blade_upgrade/ash
- tier3 = /datum/heretic_knowledge/spell/flame_birth
+ knowledge_tier4 = /datum/heretic_knowledge/spell/flame_birth
ascension = /datum/heretic_knowledge/ultimate/ash_final
/datum/heretic_knowledge/limited_amount/starting/base_ash
@@ -29,24 +60,11 @@
result_atoms = list(/obj/item/melee/sickly_blade/ash)
research_tree_icon_path = 'icons/obj/weapons/khopesh.dmi'
research_tree_icon_state = "ash_blade"
+ mark_type = /datum/status_effect/eldritch/ash
+ eldritch_passive = /datum/status_effect/heretic_passive/ash
-/datum/heretic_knowledge/ashen_grasp
- name = "Grasp of Ash"
- desc = "Your Mansus Grasp will burn the eyes of the victim, damaging them and blurring their vision."
- gain_text = "The Nightwatcher was the first of them, his treason started it all. \
- Their lantern, expired to ash - their watch, absent."
- cost = 1
- research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
- research_tree_icon_state = "grasp_ash"
-
-/datum/heretic_knowledge/ashen_grasp/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
- RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, PROC_REF(on_mansus_grasp))
-
-/datum/heretic_knowledge/ashen_grasp/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
- UnregisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK)
-
-/datum/heretic_knowledge/ashen_grasp/proc/on_mansus_grasp(mob/living/source, mob/living/target)
- SIGNAL_HANDLER
+/datum/heretic_knowledge/limited_amount/starting/base_ash/on_mansus_grasp(mob/living/source, mob/living/target)
+ . = ..()
if(target.is_blind())
return
@@ -58,27 +76,7 @@
target.adjustOrganLoss(ORGAN_SLOT_EYES, 15)
target.set_eye_blur_if_lower(20 SECONDS)
-/datum/heretic_knowledge/spell/ash_passage
- name = "Ashen Passage"
- desc = "Grants you Ashen Passage, a spell that lets you phase out of reality and traverse a short distance, passing though any walls."
- gain_text = "He knew how to walk between the planes."
-
- action_to_add = /datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash
- cost = 1
-
-
-/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 a nearby heathen. \
- Damage dealt is decreased with each transfer. \
- Triggering the mark will also greatly reduce the cooldown of your Mansus Grasp."
- gain_text = "He was a very particular man, always watching in the dead of night. \
- 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."
- mark_type = /datum/status_effect/eldritch/ash
-
-/datum/heretic_knowledge/mark/ash_mark/trigger_mark(mob/living/source, mob/living/target)
+/datum/heretic_knowledge/limited_amount/starting/base_ash/trigger_mark(mob/living/source, mob/living/target)
. = ..()
if(!.)
return
@@ -89,20 +87,41 @@
grasp.next_use_time -= round(grasp.cooldown_time*0.75)
grasp.build_all_button_icons()
-/datum/heretic_knowledge/knowledge_ritual/ash
-
+/datum/heretic_knowledge/spell/ash_passage
+ name = "Ashen Passage"
+ desc = "Grants you Ashen Passage, a spell that lets you phase out of reality, allowing you to traverse a short distance, passing though any walls. \
+ When empowered, it will break you out of any stuns and restraints, and will have a longer range."
+ gain_text = "He knew how to walk between the planes."
+ action_to_add = /datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash
+ cost = 2
+ drafting_tier = 5
/datum/heretic_knowledge/spell/fire_blast
name = "Volcano Blast"
desc = "Grants you Volcano Blast, a spell that - after a short charge - fires off a beam of energy \
at a nearby enemy, setting them on fire and burning them. If they do not extinguish themselves, \
- the beam will continue to another target."
+ the beam will continue to another target. \
+ When empowered, has instant cast time and blasts enemies with more flames."
gain_text = "No fire was hot enough to rekindle them. No fire was bright enough to save them. No fire is eternal."
action_to_add = /datum/action/cooldown/spell/charged/beam/fire_blast
- cost = 1
+ cost = 2
research_tree_icon_frame = 7
+/datum/heretic_knowledge/armor/ash
+ desc = "Allows you to transmute a table (or a suit), a mask and a match to create a scorched mantle. \
+ It provides completes protection from fire, and is able to produce more flames passively. \
+ When you have enough fire, you may cast empowered versions of your ashen spells. \
+ Acts as a focus while hooded."
+ gain_text = "The Watch remain as they fell, crumbling away from sight. \
+ Yet the winds blowing through the city call them back to service, dust kicked into the air, a drifting silhouette of the fallen."
+ result_atoms = list(/obj/item/clothing/suit/hooded/cultrobes/eldritch/ash)
+ research_tree_icon_state = "ash_armor"
+ required_atoms = list(
+ list(/obj/structure/table, /obj/item/clothing/suit) = 1,
+ /obj/item/clothing/mask = 1,
+ /obj/item/match = 1,
+ )
/datum/heretic_knowledge/mad_mask
name = "Mask of Madness"
@@ -117,7 +136,7 @@
/obj/item/flashlight/flare/candle = 4,
)
result_atoms = list(/obj/item/clothing/mask/madness_mask)
- cost = 1
+ cost = 2
research_tree_icon_path = 'icons/obj/clothing/masks.dmi'
research_tree_icon_state = "mad_mask"
@@ -146,8 +165,9 @@
gain_text = "The fire was inescapable, and yet, life remained in his charred body. \
The Nightwatcher was a particular man, always watching."
action_to_add = /datum/action/cooldown/spell/aoe/fiery_rebirth
- cost = 1
+ cost = 2
research_tree_icon_frame = 5
+ is_final_knowledge = TRUE
/datum/heretic_knowledge/ultimate/ash_final
name = "Ashlord's Rite"
diff --git a/code/modules/antagonists/heretic/knowledge/blade_lore.dm b/code/modules/antagonists/heretic/knowledge/blade_lore.dm
index 759f77a2adc..495d994f137 100644
--- a/code/modules/antagonists/heretic/knowledge/blade_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/blade_lore.dm
@@ -1,20 +1,53 @@
-
-/datum/heretic_knowledge_tree_column/main/blade
- neighbour_type_left = /datum/heretic_knowledge_tree_column/void_to_blade
- neighbour_type_right = /datum/heretic_knowledge_tree_column/blade_to_rust
-
+/datum/heretic_knowledge_tree_column/blade
route = PATH_BLADE
ui_bgr = "node_blade"
+ complexity = "Hard"
+ complexity_color = COLOR_RED
+ icon = list(
+ "icon" = 'icons/obj/weapons/khopesh.dmi',
+ "state" = "dark_blade",
+ "frame" = 1,
+ "dir" = SOUTH,
+ "moving" = FALSE,
+ )
+ description = list(
+ "The Path of Blade is as the name suggests.",
+ "You are highly competent at cutting your opponents to ribbons.",
+ "Pick this path if you want to fight, and you want to be the best at fighting.",
+ )
+ pros = list(
+ "Capable of blocking incoming attacks, retaliating with a riposte.",
+ "Rapidly deals damage through dual-wielded blades and channeled strikes.",
+ "High defense against stuns and knockdowns.",
+ "Highly lethal combatant in a direct combat with a single opponent.",
+ )
+ cons = list(
+ "Requires a high degree of skill input.",
+ "Without blades, the path loses most of its fighting power.",
+ "Lacks mobility options.",
+ "Lacks environmental protections.",
+ )
+ tips = list(
+ "Your Mansus Grasp will stun your opponent if they are attacked from behind or while they are prone. This also locks them in the room they are in until the mark is detonated. Triggering the mark will grant you a orbiting knife that will protect you from one melee or ranged attack.",
+ "You have the highest blade cap out of all paths (A total of 4). But since they require silver or titanium to craft, you might be strapped for ingredients if the miners aren't doing their job. If you need materials, shuttle walls and seats are a source of titanium metal, and surgery tables a source of silver.",
+ "You are highly reliant on approaching opponents in melee. Slips, bolas and beartraps are your worst enemy. You can counteract slips by crafting a pair of Greaves Of The Prophet, or remove restraints with Ashen Passage.",
+ "Realignment will pull you out of stuns and knockdowns, but also pacifies you for the duration.",
+ "With Empowered Blades, your offensive power grows considerably. You are able to fight with dual-wielded blades, and can empower them by activating your Mansus Grasp while wielding your blades. Your blades also deal additional damage to objects, silicons and mechs.",
+ "Maintaining a good offense also creates a good defense. With orbiting blades, you are able to block additional incoming attacks.",
+ "With Furious Steel, you can not only produce several knives for defensive purposes, but throw them by clicking with an empty hand. This gives you additional ranged power in a pinch.",
+ "Use Wolves Among Sheep with caution. Not only does it have a significant cooldown, but it also arms anyone trapped in the effect with you with blades of their own. Use it either as a last ditch defense, or when you know you have the upper hand and need an extra edge. Just don't try to flee the area before taking someone out first.",
+ )
start = /datum/heretic_knowledge/limited_amount/starting/base_blade
- grasp = /datum/heretic_knowledge/blade_grasp
- tier1 = /datum/heretic_knowledge/blade_dance
- mark = /datum/heretic_knowledge/mark/blade_mark
- ritual_of_knowledge = /datum/heretic_knowledge/knowledge_ritual/blade
- unique_ability = /datum/heretic_knowledge/spell/realignment
- tier2 = /datum/heretic_knowledge/spell/furious_steel
+ knowledge_tier1 = /datum/heretic_knowledge/spell/realignment
+ guaranteed_side_tier1 = /datum/heretic_knowledge/greaves_of_the_prophet
+ knowledge_tier2 = /datum/heretic_knowledge/duel_stance
+ guaranteed_side_tier2 = /datum/heretic_knowledge/essence
+ robes = /datum/heretic_knowledge/armor/blade
+ knowledge_tier3 = /datum/heretic_knowledge/spell/furious_steel
+ guaranteed_side_tier3 = /datum/heretic_knowledge/rune_carver
blade = /datum/heretic_knowledge/blade_upgrade/blade
- tier3 = /datum/heretic_knowledge/spell/wolves_among_sheep
+ knowledge_tier4 = /datum/heretic_knowledge/spell/wolves_among_sheep
ascension = /datum/heretic_knowledge/ultimate/blade_final
/datum/heretic_knowledge/limited_amount/starting/base_blade
@@ -31,24 +64,11 @@
limit = 4 // It's the blade path, it's a given
research_tree_icon_path = 'icons/obj/weapons/khopesh.dmi'
research_tree_icon_state = "dark_blade"
+ mark_type = /datum/status_effect/eldritch/blade
+ eldritch_passive = /datum/status_effect/heretic_passive/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."
- cost = 1
- research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
- research_tree_icon_state = "grasp_blade"
-
-/datum/heretic_knowledge/blade_grasp/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
- RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, PROC_REF(on_mansus_grasp))
-
-/datum/heretic_knowledge/blade_grasp/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
- UnregisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK)
-
-/datum/heretic_knowledge/blade_grasp/proc/on_mansus_grasp(mob/living/source, mob/living/target)
- SIGNAL_HANDLER
+/datum/heretic_knowledge/limited_amount/starting/base_blade/on_mansus_grasp(mob/living/source, mob/living/target)
+ . = ..()
if(!check_behind(source, target))
return
@@ -59,109 +79,7 @@
target.balloon_alert(source, "backstab!")
playsound(target, 'sound/items/weapons/guillotine.ogg', 100, TRUE)
-/// The cooldown duration between triggers 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 Heretic Blade in either hand will deliver a riposte \
- towards your attacker. This effect can only trigger once every 20 seconds."
- gain_text = "The footsoldier was known to be a fearsome duelist. \
- Their general quickly appointed them as their personal Champion."
- cost = 1
- research_tree_icon_path = 'icons/mob/actions/actions_ecult.dmi'
- research_tree_icon_state = "shatter"
- /// 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, datum/antagonist/heretic/our_heretic)
- RegisterSignal(user, COMSIG_LIVING_CHECK_BLOCK, PROC_REF(on_shield_reaction))
-
-/datum/heretic_knowledge/blade_dance/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
- UnregisterSignal(user, COMSIG_LIVING_CHECK_BLOCK)
-
-/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,
- damage_type = BRUTE,
-)
-
- SIGNAL_HANDLER
-
- if(attack_type != MELEE_ATTACK)
- return
-
- if(!riposte_ready)
- return
-
- if(INCAPACITATED_IGNORING(source, INCAPABLE_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_REF(counter_attack), source, attacker, striking_with, attack_text)
-
- // And reset after a bit
- riposte_ready = FALSE
- addtimer(CALLBACK(src, PROC_REF(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/items/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 = "His general wished to end the war, but the Champion knew there could be no life without death. \
- He would slay the coward himself, and anyone who tried to run."
- mark_type = /datum/status_effect/eldritch/blade
-
-/datum/heretic_knowledge/mark/blade_mark/create_mark(mob/living/source, mob/living/target)
+/datum/heretic_knowledge/limited_amount/starting/base_blade/create_mark(mob/living/source, mob/living/target)
var/datum/status_effect/eldritch/blade/blade_mark = ..()
if(istype(blade_mark))
var/area/to_lock_to = get_area(target)
@@ -169,16 +87,12 @@
to_chat(target, span_hypnophrase("An otherworldly force is compelling you to stay in [get_area_name(to_lock_to)]!"))
return blade_mark
-/datum/heretic_knowledge/mark/blade_mark/trigger_mark(mob/living/source, mob/living/target)
+/datum/heretic_knowledge/limited_amount/starting/base_blade/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
-
-
-
/datum/heretic_knowledge/spell/realignment
name = "Realignment"
desc = "Grants you Realignment a spell that wil realign your body rapidly for a short period. \
@@ -186,7 +100,91 @@
This spell can be cast in rapid succession, but doing so will increase the cooldown."
gain_text = "In the flurry of death, he found peace within himself. Despite insurmountable odds, he forged on."
action_to_add = /datum/action/cooldown/spell/realignment
- cost = 1
+ cost = 2
+
+/// The amount of blood flow reduced per level of severity of gained bleeding wounds for Stance of the Torn Champion.
+#define BLOOD_FLOW_PER_SEVEIRTY -1
+
+/datum/heretic_knowledge/duel_stance
+ name = "Stance of the Torn Champion"
+ 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 resistance to slowdown."
+ gain_text = "In time, it was he who stood alone among the bodies of his former comrades, awash in blood, none of it his own. \
+ He was without rival, equal, or purpose."
+ cost = 2
+ research_tree_icon_path = 'icons/effects/blood.dmi'
+ research_tree_icon_state = "suitblood"
+ research_tree_icon_dir = SOUTH
+ drafting_tier = 5
+ /// 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, datum/antagonist/heretic/our_heretic)
+ ADD_TRAIT(user, TRAIT_NODISMEMBER, type)
+ RegisterSignal(user, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
+ RegisterSignal(user, COMSIG_CARBON_GAIN_WOUND, PROC_REF(on_wound_gain))
+ RegisterSignal(user, COMSIG_LIVING_HEALTH_UPDATE, PROC_REF(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, datum/antagonist/heretic/our_heretic)
+ REMOVE_TRAIT(user, TRAIT_NODISMEMBER, type)
+ if(in_duelist_stance)
+ user.remove_traits(list(TRAIT_HARDLY_WOUNDED), type)
+ if(isliving(user))
+ var/mob/living/living_mob = user
+ living_mob.remove_movespeed_mod_immunities(type, /datum/movespeed_modifier/damage_slowdown, TRUE)
+
+ UnregisterSignal(user, list(COMSIG_ATOM_EXAMINE, COMSIG_CARBON_GAIN_WOUND, COMSIG_LIVING_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.adjust_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
+ source.remove_traits(list(TRAIT_HARDLY_WOUNDED), type)
+ source.remove_movespeed_mod_immunities(type, /datum/movespeed_modifier/damage_slowdown, TRUE)
+ 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)
+ source.add_movespeed_mod_immunities(type, /datum/movespeed_modifier/damage_slowdown, TRUE)
+ return
+
+#undef BLOOD_FLOW_PER_SEVEIRTY
+
+/datum/heretic_knowledge/armor/blade
+ desc = "Allows you to transmute a table (or a suit), a mask and a sheet of titanium or silver to create a Shattered Panoply. \
+ Provides baton resistance and shock insulation while worn. \
+ Acts as a focus while hooded."
+ gain_text = "The echoing, directionless cacophony of violence reverberates about me. \
+ Even as the Champion's steel panoply was torn from their form, each piece craves purpose still, seeking to intercept unseen or imagined attackers."
+ result_atoms = list(/obj/item/clothing/suit/hooded/cultrobes/eldritch/blade)
+ research_tree_icon_state = "blade_armor"
+ required_atoms = list(
+ list(/obj/structure/table, /obj/item/clothing/suit) = 1,
+ /obj/item/clothing/mask = 1,
+ list(/obj/item/stack/sheet/mineral/silver, /obj/item/stack/sheet/mineral/titanium) = 1,
+ )
/datum/heretic_knowledge/spell/wolves_among_sheep
name = "Wolves Among Sheep"
@@ -199,8 +197,9 @@
I have made an enemy of all, and peace will never be known to me \
again. I have shattered bonds and severed all alliances. In this truth, \
I know now the fragility of comradery. My enemies will be all, divided."
- cost = 1
+ cost = 2
action_to_add = /datum/action/cooldown/spell/wolves_among_sheep
+ is_final_knowledge = TRUE
/datum/heretic_knowledge/blade_upgrade/blade
name = "Empowered Blades"
@@ -221,11 +220,10 @@
. = ..()
RegisterSignal(user, COMSIG_TOUCH_HANDLESS_CAST, PROC_REF(on_grasp_cast))
RegisterSignal(user, COMSIG_MOB_EQUIPPED_ITEM, PROC_REF(on_blade_equipped))
- RegisterSignal(user, COMSIG_HERETIC_BLADE_ATTACK, PROC_REF(do_melee_effects))
/datum/heretic_knowledge/blade_upgrade/blade/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
. = ..()
- UnregisterSignal(user, list(COMSIG_TOUCH_HANDLESS_CAST, COMSIG_MOB_EQUIPPED_ITEM, COMSIG_HERETIC_BLADE_ATTACK))
+ UnregisterSignal(user, list(COMSIG_TOUCH_HANDLESS_CAST, COMSIG_MOB_EQUIPPED_ITEM))
///Tries to infuse our held blade with our mansus grasp
/datum/heretic_knowledge/blade_upgrade/blade/proc/on_grasp_cast(mob/living/carbon/cast_on)
@@ -296,7 +294,7 @@
/datum/heretic_knowledge/blade_upgrade/blade/proc/on_blade_equipped(mob/user, obj/item/equipped, slot)
SIGNAL_HANDLER
if(istype(equipped, /obj/item/melee/sickly_blade/dark))
- equipped.demolition_mod = 1.5
+ equipped.demolition_mod = 2.5
/datum/heretic_knowledge/spell/furious_steel
name = "Furious Steel"
@@ -307,7 +305,7 @@
gain_text = "Without thinking, I took the knife of a fallen soldier and threw with all my might. My aim was true! \
The Torn Champion smiled at their first taste of agony, and with a nod, their blades became my own."
action_to_add = /datum/action/cooldown/spell/pointed/projectile/furious_steel
- cost = 1
+ cost = 2
/datum/heretic_knowledge/ultimate/blade_final
name = "Maelstrom of Silver"
@@ -336,7 +334,7 @@
. = ..()
ADD_TRAIT(user, TRAIT_NEVER_WOUNDED, type)
RegisterSignal(user, COMSIG_HERETIC_BLADE_ATTACK, PROC_REF(on_eldritch_blade))
- user.apply_status_effect(/datum/status_effect/protective_blades/recharging, null, 8, 30, 0.25 SECONDS, /obj/effect/floating_blade, 1 MINUTES)
+ user.apply_status_effect(/datum/status_effect/protective_blades/recharging, STATUS_EFFECT_PERMANENT, 8, 30, 0.25 SECONDS, /obj/effect/floating_blade, 60 SECONDS)
user.add_stun_absorption(
source = name,
message = span_warning("%EFFECT_OWNER throws off the stun!"),
diff --git a/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm b/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm
index ca950c8d0ea..89f21584a8d 100644
--- a/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/cosmic_lore.dm
@@ -1,20 +1,50 @@
-
-/datum/heretic_knowledge_tree_column/main/cosmic
- neighbour_type_left = /datum/heretic_knowledge_tree_column/rust_to_cosmic
- neighbour_type_right = /datum/heretic_knowledge_tree_column/cosmic_to_ash
-
+/datum/heretic_knowledge_tree_column/cosmic
route = PATH_COSMIC
ui_bgr = "node_cosmos"
+ complexity = "Hard"
+ complexity_color = COLOR_RED
+ icon = list(
+ "icon" = 'icons/obj/weapons/khopesh.dmi',
+ "state" = "cosmic_blade",
+ "frame" = 1,
+ "dir" = SOUTH,
+ "moving" = FALSE,
+ )
+ description = list(
+ "The Path of Cosmos revolves around area denial, teleporation, and mastery over space.",
+ "Pick this path if you enjoy adapting to your environment and thinking outside (or inside) the box.",
+ )
+ pros = list(
+ "Control the movement of foes with cosmic fields",
+ "Move in and around space with ease.",
+ "Teleport rapidly across the station.",
+ "Confound opponents with barriers upon barriers.",
+ )
+ cons = list(
+ "Requires you spread your star mark to affect opponents with your cosmic fields.",
+ "Relatively low damage.",
+ "Relatively low direct defense, highly reliant on proper use of abilities.",
+ )
+ tips = list(
+ "Your Mansus Grasp will mark your opponent with a star mark, as well as leave a mark that, when detonated, will teleport your opponent back to the place where the mark was applied and briefly paralyze them.",
+ "Your cosmic runes can quickly teleport you from two different locations instantly. Beware, however; non-heretics are also able to travel through them. Be creative and have your opponents teleport right into a trap. They come out star marked!",
+ "When standing on top of a cosmic rune, you can click on yourself with a empty hand to activate it.",
+ "Star marked opponents cannot cross your cosmic fields willingly. But they can be dragged through!",
+ "Star Blast is both a jaunt ability as well as a disabling tool. Use it to catch several people in your cosmic fields at once.",
+ "Star Touch will prevent your target from teleporting away. Should they fail to break the tether, they will be put to sleep and then teleport to your feet.",
+ "It's Always a good idea to leave one cosmic rune near your ritual rune, it will allow you to quickly kidnap your targets to sacrifice them.",
+ )
start = /datum/heretic_knowledge/limited_amount/starting/base_cosmic
- grasp = /datum/heretic_knowledge/cosmic_grasp
- tier1 = /datum/heretic_knowledge/spell/cosmic_runes
- mark = /datum/heretic_knowledge/mark/cosmic_mark
- ritual_of_knowledge = /datum/heretic_knowledge/knowledge_ritual/cosmic
- unique_ability = /datum/heretic_knowledge/spell/star_touch
- tier2 = /datum/heretic_knowledge/spell/star_blast
+ knowledge_tier1 = /datum/heretic_knowledge/spell/cosmic_runes
+ guaranteed_side_tier1 = /datum/heretic_knowledge/eldritch_coin
+ knowledge_tier2 = /datum/heretic_knowledge/spell/star_blast
+ guaranteed_side_tier2 = /datum/heretic_knowledge/spell/space_phase
+ robes = /datum/heretic_knowledge/armor/cosmic
+ knowledge_tier3 = /datum/heretic_knowledge/spell/star_touch
+ guaranteed_side_tier3 = /datum/heretic_knowledge/essence
blade = /datum/heretic_knowledge/blade_upgrade/cosmic
- tier3 = /datum/heretic_knowledge/spell/cosmic_expansion
+ knowledge_tier4 = /datum/heretic_knowledge/spell/cosmic_expansion
ascension = /datum/heretic_knowledge/ultimate/cosmic_final
/datum/heretic_knowledge/limited_amount/starting/base_cosmic
@@ -30,30 +60,16 @@
result_atoms = list(/obj/item/melee/sickly_blade/cosmic)
research_tree_icon_path = 'icons/obj/weapons/khopesh.dmi'
research_tree_icon_state = "cosmic_blade"
-
-/datum/heretic_knowledge/cosmic_grasp
- name = "Grasp of Cosmos"
- desc = "Your Mansus Grasp will give people a star mark (cosmic ring) and create a cosmic field where you stand. \
- People with a star mark can not pass cosmic fields."
- gain_text = "Some stars dimmed, others' magnitude increased. \
- With newfound strength I could channel the nebula's power into myself."
- cost = 1
- research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
- research_tree_icon_state = "grasp_cosmos"
-
-/datum/heretic_knowledge/cosmic_grasp/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
- RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, PROC_REF(on_mansus_grasp))
-
-/datum/heretic_knowledge/cosmic_grasp/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
- UnregisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK)
+ mark_type = /datum/status_effect/eldritch/cosmic
+ eldritch_passive = /datum/status_effect/heretic_passive/cosmic
/// Aplies the effect of the mansus grasp when it hits a target.
-/datum/heretic_knowledge/cosmic_grasp/proc/on_mansus_grasp(mob/living/source, mob/living/target)
- SIGNAL_HANDLER
+/datum/heretic_knowledge/limited_amount/starting/base_cosmic/on_mansus_grasp(mob/living/source, mob/living/target)
+ . = ..()
to_chat(target, span_danger("A cosmic ring appeared above your head!"))
target.apply_status_effect(/datum/status_effect/star_mark, source)
- new /obj/effect/forcefield/cosmic_field(get_turf(source))
+ create_cosmic_field(get_turf(source), source)
/datum/heretic_knowledge/spell/cosmic_runes
name = "Cosmic Runes"
@@ -63,20 +79,30 @@
gain_text = "The distant stars crept into my dreams, roaring and screaming without reason. \
I spoke, and heard my own words echoed back."
action_to_add = /datum/action/cooldown/spell/cosmic_rune
- cost = 1
+ cost = 2
+ drafting_tier = 5
+/datum/heretic_knowledge/spell/star_blast
+ name = "Star Blast"
+ desc = "Fires a projectile that moves very slowly, raising a short-lived wall of cosmic fields where it goes. \
+ Anyone hit by the projectile will receive burn damage, a knockdown, and give people in a three tile range a star mark."
+ gain_text = "The Beast was behind me now at all times, with each sacrifice words of affirmation coursed through me."
+ action_to_add = /datum/action/cooldown/spell/pointed/projectile/star_blast
+ cost = 2
-/datum/heretic_knowledge/mark/cosmic_mark
- name = "Mark of Cosmos"
- desc = "Your Mansus Grasp now applies the Mark of Cosmos. The mark is triggered from an attack with your Cosmic Blade. \
- When triggered, the victim is returned to the location where the mark was originally applied to them, \
- leaving a cosmic field in their place. \
- They will then be paralyzed for 2 seconds."
- gain_text = "The Beast now whispered to me occasionally, only small tidbits of their circumstances. \
- I can help them, I have to help them."
- mark_type = /datum/status_effect/eldritch/cosmic
+/datum/heretic_knowledge/armor/cosmic
-/datum/heretic_knowledge/knowledge_ritual/cosmic
+ desc = "Allows you to transmute a table (or a suit), a mask and a sheet of plasma to create a Starwoven Cloak, grants protection from the hazards of space while granting to the user the ability to levitate at will. \
+ Acts as a focus while hooded."
+ gain_text = "Like radiant cords, the stars shone in union across the silken shape of a billowing cloak, that at once does and does not drape my shoulders. \
+ The eyes of the Beast rested upon me, and through me."
+ result_atoms = list(/obj/item/clothing/suit/hooded/cultrobes/eldritch/cosmic)
+ research_tree_icon_state = "cosmic_armor"
+ required_atoms = list(
+ list(/obj/structure/table, /obj/item/clothing/suit) = 1,
+ /obj/item/clothing/mask = 1,
+ /obj/item/stack/sheet/mineral/plasma = 1,
+ )
/datum/heretic_knowledge/spell/star_touch
name = "Star Touch"
@@ -87,23 +113,14 @@
gain_text = "After waking in a cold sweat I felt a palm on my scalp, a sigil burned onto me. \
My veins now emitted a strange purple glow, the Beast knows I will surpass its expectations."
action_to_add = /datum/action/cooldown/spell/touch/star_touch
- cost = 1
-
-/datum/heretic_knowledge/spell/star_blast
- name = "Star Blast"
- desc = "Fires a projectile that moves very slowly, raising a short-lived wall of cosmic fields where it goes. \
- Anyone hit by the projectile will receive burn damage, a knockdown, and give people in a three tile range a star mark."
- gain_text = "The Beast was behind me now at all times, with each sacrifice words of affirmation coursed through me."
- action_to_add = /datum/action/cooldown/spell/pointed/projectile/star_blast
- cost = 1
+ cost = 2
/datum/heretic_knowledge/blade_upgrade/cosmic
name = "Cosmic Blade"
- desc = "Your blade now deals damage to people's organs through cosmic radiation. \
+ desc = "Your blade now star marks your victims, and allows you to attack star marked heathens from further away. \
Your attacks will chain bonus damage to up to two previous victims. \
- The combo is reset after two seconds without making an attack, \
- or if you attack someone already marked. If you combo more than four attacks you will receive, \
- a cosmic trail and increase your combo timer up to ten seconds."
+ The combo is reset after two seconds without making an attack, or if you attack someone already marked. \
+ If you combo three attacks you will receive a cosmic trail and increase your combo timer up to ten seconds."
gain_text = "The Beast took my blades in their hand, I kneeled and felt a sharp pain. \
The blades now glistened with fragmented power. I fell to the ground and wept at the beast's feet."
research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
@@ -124,19 +141,20 @@
var/increase_amount = 0.5 SECONDS
/// The hits we have on a mob with a mind.
var/combo_counter = 0
+ /// How much further we can hit people, modified by ascension
+ var/max_attack_range = 2
+
+/datum/heretic_knowledge/blade_upgrade/cosmic/on_ranged_eldritch_blade(mob/living/source, mob/living/target, obj/item/melee/sickly_blade/blade)
+ . = ..()
+ if(!isliving(target) || get_dist(source, target) > max_attack_range || !target.has_status_effect(/datum/status_effect/star_mark))
+ return
+ source.changeNext_move(blade.attack_speed)
+ return blade.attack(target, source)
/datum/heretic_knowledge/blade_upgrade/cosmic/do_melee_effects(mob/living/source, mob/living/target, obj/item/melee/sickly_blade/blade)
- var/static/list/valid_organ_slots = list(
- ORGAN_SLOT_HEART,
- ORGAN_SLOT_LUNGS,
- ORGAN_SLOT_STOMACH,
- ORGAN_SLOT_EYES,
- ORGAN_SLOT_EARS,
- ORGAN_SLOT_LIVER,
- ORGAN_SLOT_BRAIN
- )
if(source == target || !isliving(target))
return
+ target.apply_status_effect(/datum/status_effect/star_mark, source)
if(combo_timer)
deltimer(combo_timer)
combo_timer = addtimer(CALLBACK(src, PROC_REF(reset_combo), source), combo_duration, TIMER_STOPPABLE)
@@ -144,7 +162,6 @@
var/mob/living/third_target_resolved = third_target?.resolve()
var/need_mob_update = FALSE
need_mob_update += target.adjustFireLoss(5, updating_health = FALSE)
- need_mob_update += target.adjustOrganLoss(pick(valid_organ_slots), 8)
if(need_mob_update)
target.updatehealth()
if(target == second_target_resolved || target == third_target_resolved)
@@ -157,7 +174,6 @@
playsound(get_turf(second_target_resolved), 'sound/effects/magic/cosmic_energy.ogg', 25, FALSE)
need_mob_update = FALSE
need_mob_update += second_target_resolved.adjustFireLoss(14, updating_health = FALSE)
- need_mob_update += second_target_resolved.adjustOrganLoss(pick(valid_organ_slots), 12)
if(need_mob_update)
second_target_resolved.updatehealth()
if(third_target_resolved)
@@ -165,15 +181,12 @@
playsound(get_turf(third_target_resolved), 'sound/effects/magic/cosmic_energy.ogg', 50, FALSE)
need_mob_update = FALSE
need_mob_update += third_target_resolved.adjustFireLoss(28, updating_health = FALSE)
- need_mob_update += third_target_resolved.adjustOrganLoss(pick(valid_organ_slots), 14)
if(need_mob_update)
third_target_resolved.updatehealth()
- if(combo_counter > 3)
- target.apply_status_effect(/datum/status_effect/star_mark, source)
+ if(combo_counter == 3)
if(target.mind && target.stat != DEAD)
increase_combo_duration()
- if(combo_counter == 4)
- source.AddElement(/datum/element/effect_trail, /obj/effect/forcefield/cosmic_field/fast)
+ source.AddElement(cosmic_trail_based_on_passive(source), /obj/effect/forcefield/cosmic_field/fast)
third_target = second_target
second_target = WEAKREF(target)
@@ -181,8 +194,7 @@
/datum/heretic_knowledge/blade_upgrade/cosmic/proc/reset_combo(mob/living/source)
second_target = null
third_target = null
- if(combo_counter > 3)
- source.RemoveElement(/datum/element/effect_trail, /obj/effect/forcefield/cosmic_field/fast)
+ source.RemoveElement(cosmic_trail_based_on_passive(source), /obj/effect/forcefield/cosmic_field/fast)
combo_duration = combo_duration_amount
combo_counter = 0
new /obj/effect/temp_visual/cosmic_cloud(get_turf(source))
@@ -196,16 +208,17 @@
/datum/heretic_knowledge/spell/cosmic_expansion
name = "Cosmic Expansion"
- desc = "Grants you Cosmic Expansion, a spell that creates a 3x3 area of cosmic fields around you. \
+ desc = "Grants you Cosmic Expansion, a spell that creates a 5x5 area of cosmic fields around you. \
Nearby beings will also receive a star mark."
gain_text = "The ground now shook beneath me. The Beast inhabited me, and their voice was intoxicating."
action_to_add = /datum/action/cooldown/spell/conjure/cosmic_expansion
- cost = 1
+ cost = 2
+ is_final_knowledge = TRUE
/datum/heretic_knowledge/ultimate/cosmic_final
name = "Creators's Gift"
desc = "The ascension ritual of the Path of Cosmos. \
- Bring 3 corpses with bluespace dust in their body to a transmutation rune to complete the ritual. \
+ Bring 3 corpses with a star mark to a transmutation rune to complete the ritual. \
When completed, you become the owner of a Star Gazer. \
You will be able to command the Star Gazer with Alt+click. \
You can also give it commands through speech. \
@@ -228,22 +241,33 @@
/datum/pet_command/follow,
/datum/pet_command/attack/star_gazer
)
+ /// List of traits given once ascended
+ var/static/list/ascended_traits = list(TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTHIGHPRESSURE, TRAIT_RESISTCOLD, TRAIT_RESISTHEAT, TRAIT_XRAY_VISION)
+ /// List of traits given to our cute lil guy
+ var/static/list/stargazer_traits = list(TRAIT_RESISTLOWPRESSURE, TRAIT_RESISTHIGHPRESSURE, TRAIT_RESISTCOLD, TRAIT_RESISTHEAT, TRAIT_BOMBIMMUNE, TRAIT_XRAY_VISION)
/datum/heretic_knowledge/ultimate/cosmic_final/is_valid_sacrifice(mob/living/carbon/human/sacrifice)
. = ..()
if(!.)
return FALSE
- return sacrifice.has_reagent(/datum/reagent/bluespace)
+ return sacrifice.has_status_effect(/datum/status_effect/star_mark)
/datum/heretic_knowledge/ultimate/cosmic_final/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc)
. = ..()
- var/mob/living/basic/heretic_summon/star_gazer/star_gazer_mob = new /mob/living/basic/heretic_summon/star_gazer(loc)
+ user.add_traits(ascended_traits, type)
+ if(ishuman(user))
+ var/mob/living/carbon/human/ascended_human = user
+ var/obj/item/organ/eyes/heretic_eyes = ascended_human.get_organ_slot(ORGAN_SLOT_EYES)
+ ascended_human.update_sight()
+ heretic_eyes?.color_cutoffs = list(30, 30, 30)
+ ascended_human.update_sight()
+
+ var/mob/living/basic/heretic_summon/star_gazer/star_gazer_mob = new /mob/living/basic/heretic_summon/star_gazer(loc, user)
star_gazer_mob.maxHealth = INFINITY
star_gazer_mob.health = INFINITY
user.AddComponent(/datum/component/death_linked, star_gazer_mob)
star_gazer_mob.AddComponent(/datum/component/obeys_commands, star_gazer_commands, radial_menu_offset = list(30,0), radial_menu_lifetime = 15 SECONDS, radial_relative_to_user = TRUE)
- star_gazer_mob.AddComponent(/datum/component/damage_aura, range = 7, burn_damage = 0.5, simple_damage = 0.5, immune_factions = list(FACTION_HERETIC), current_owner = user)
star_gazer_mob.befriend(user)
var/datum/action/cooldown/open_mob_commands/commands_action = new /datum/action/cooldown/open_mob_commands()
commands_action.Grant(user, star_gazer_mob)
@@ -251,6 +275,8 @@
if(star_touch_spell)
star_touch_spell.set_star_gazer(star_gazer_mob)
star_touch_spell.ascended = TRUE
+ star_gazer_mob.add_traits(stargazer_traits, type)
+ star_gazer_mob.leash_to(star_gazer_mob, user)
var/datum/antagonist/heretic/heretic_datum = user.mind.has_antag_datum(/datum/antagonist/heretic)
var/datum/heretic_knowledge/blade_upgrade/cosmic/blade_upgrade = heretic_datum.get_knowledge(/datum/heretic_knowledge/blade_upgrade/cosmic)
@@ -258,6 +284,46 @@
blade_upgrade.combo_duration_amount = 10 SECONDS
blade_upgrade.max_combo_duration = 30 SECONDS
blade_upgrade.increase_amount = 2 SECONDS
+ blade_upgrade.max_attack_range = 3
var/datum/action/cooldown/spell/conjure/cosmic_expansion/cosmic_expansion_spell = locate() in user.actions
cosmic_expansion_spell?.ascended = TRUE
+
+ var/datum/action/cooldown/mob_cooldown/replace_star_gazer/replace_gazer = new(src)
+ replace_gazer.Grant(user)
+ replace_gazer.bad_dog = WEAKREF(star_gazer_mob)
+
+/// Replace an annoying griefer you were paired up to with a different but probably no less annoying player.
+/datum/action/cooldown/mob_cooldown/replace_star_gazer
+ name = "Reset Star Gazer Consciousness"
+ desc = "Replaces the mind of your summon with that of a different ghost."
+ button_icon = 'icons/mob/simple/mob.dmi'
+ button_icon_state = "ghost"
+ background_icon_state = "bg_heretic"
+ overlay_icon_state = "bg_heretic_border"
+ check_flags = NONE
+ click_to_activate = FALSE
+ cooldown_time = 5 SECONDS
+ melee_cooldown_time = 0
+ shared_cooldown = NONE
+ /// Weakref to the stargazer we care about
+ var/datum/weakref/bad_dog
+
+/datum/action/cooldown/mob_cooldown/replace_star_gazer/Activate(atom/target)
+ StartCooldown(5 MINUTES)
+
+ var/mob/living/to_reset = bad_dog.resolve()
+
+ to_chat(owner, span_hierophant("You prompt [to_reset] to shift it\'s personality..."))
+ var/mob/chosen_one = SSpolling.poll_ghost_candidates("Do you want to play as [span_danger("[owner.real_name]'s")] [span_notice(to_reset.name)]?", check_jobban = ROLE_PAI, poll_time = 10 SECONDS, alert_pic = to_reset, jump_target = owner, role_name_text = to_reset.name, amount_to_pick = 1)
+ if(isnull(chosen_one))
+ to_chat(owner, span_hierophant("Your request to shift [to_reset]'\s personality appears to have been denied... Looks like you're stuck with it for now."))
+ StartCooldown()
+ return FALSE
+ to_chat(to_reset, span_hierophant("Your summoner reset you, and your body was taken over by a ghost. Looks like they weren't happy with your performance."))
+ to_chat(owner, span_hierophant("The mind of [to_reset] has twisted itself to suit you better."))
+ message_admins("[key_name_admin(chosen_one)] has taken control of ([ADMIN_LOOKUPFLW(to_reset)])")
+ to_reset.ghostize(FALSE)
+ to_reset.PossessByPlayer(chosen_one.key)
+ StartCooldown()
+ return TRUE
diff --git a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm
index aa8bbaa5039..cb2ac3d3bde 100644
--- a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm
@@ -1,24 +1,52 @@
-/// The max amount of health a ghoul has.
-#define GHOUL_MAX_HEALTH 25
-/// The max amount of health a voiceless dead has.
-#define MUTE_MAX_HEALTH 50
-
-/datum/heretic_knowledge_tree_column/main/flesh
- neighbour_type_left = /datum/heretic_knowledge_tree_column/lock_to_flesh
- neighbour_type_right = /datum/heretic_knowledge_tree_column/flesh_to_void
-
+/datum/heretic_knowledge_tree_column/flesh
route = PATH_FLESH
ui_bgr = "node_flesh"
+ complexity = "Varies"
+ complexity_color = COLOR_ORANGE
+ icon = list(
+ "icon" = 'icons/obj/weapons/khopesh.dmi',
+ "state" = "flesh_blade",
+ "frame" = 1,
+ "dir" = SOUTH,
+ "moving" = FALSE,
+ )
+ description = list(
+ "The Path of Flesh revolves around summoning ghouls and monstrosities to do your bidding.",
+ "Pick this path if you enjoy the fantasy of being a necromancer commanding legions of allies.",
+ )
+ pros = list(
+ "Can turn dead humanoids into fragile but loyal ghouls.",
+ "Access to a versatile list of summoned minions.",
+ "Your summons are very versatie and can quicky overwhelm the crew should you coordinate your attacks",
+ "Eating organs or being fat grants various boons (depending on the level of your passive).",
+ )
+ cons = list(
+ "A high degree of your progression is obtaining additional summoned monsters.",
+ "You have very little utility beyond your summoned monsters.",
+ "You gain no inherent access to defensive, offensive or mobility spells.",
+ "You are mostly focused around supporting your minions.",
+ )
+ tips = list(
+ "Your Mansus Grasp allows you to turn dead humanoids into ghouls (even mindshielded humanoids like security officers and the captain). It also Leaves a mark that causes heavy bleeding when triggered by your bloody blade.",
+ "As a Flesh Heretic, organs and dead bodies are your best friends! You can use them for rituals, to heal or to gain buffs.",
+ "Your Flesh Surgery spell can heal your summons. Your robes grant you an aura that also heals nearby summons (but not yourself).",
+ "Your Flesh Surgery spell also lets you steal organs from humanoids. Useful if you need a spare liver.",
+ "Raw Prophets can link you and other summons in a telepathic network, allowing for long distance co-ordination.",
+ "Flesh Stalkers are decent combatants with the ability to disguise themselves as small creatures, like beepskies and corgis. They can also utilize an EMP spell, but this can potentially harm them if they transformed into a robot!",
+ "Your success with this path is reliant on how knowledgable or robust your minions are. However, there is always power in numbers; the more minions, the higher your chances of success.",
+ "Your minions are more expendable than you are. Do not be afraid to tell them to go to their deaths. You can just recover them later... maybe.",
+ )
start = /datum/heretic_knowledge/limited_amount/starting/base_flesh
- grasp = /datum/heretic_knowledge/limited_amount/flesh_grasp
- tier1 = /datum/heretic_knowledge/limited_amount/flesh_ghoul
- mark = /datum/heretic_knowledge/mark/flesh_mark
- ritual_of_knowledge = /datum/heretic_knowledge/knowledge_ritual/flesh
- unique_ability = /datum/heretic_knowledge/spell/flesh_surgery
- tier2 = /datum/heretic_knowledge/summon/raw_prophet
+ knowledge_tier1 = /datum/heretic_knowledge/limited_amount/flesh_ghoul
+ guaranteed_side_tier1 = /datum/heretic_knowledge/limited_amount/risen_corpse
+ knowledge_tier2 = /datum/heretic_knowledge/spell/flesh_surgery
+ guaranteed_side_tier2 = /datum/heretic_knowledge/crucible
+ robes = /datum/heretic_knowledge/armor/flesh
+ knowledge_tier3 = /datum/heretic_knowledge/summon/raw_prophet
+ guaranteed_side_tier3 = /datum/heretic_knowledge/spell/crimson_cleave
blade = /datum/heretic_knowledge/blade_upgrade/flesh
- tier3 = /datum/heretic_knowledge/summon/stalker
+ knowledge_tier4 = /datum/heretic_knowledge/summon/stalker
ascension = /datum/heretic_knowledge/ultimate/flesh_final
/datum/heretic_knowledge/limited_amount/starting/base_flesh
@@ -35,6 +63,8 @@
limit = 3 // Bumped up so they can arm up their ghouls too.
research_tree_icon_path = 'icons/obj/weapons/khopesh.dmi'
research_tree_icon_state = "flesh_blade"
+ mark_type = /datum/status_effect/eldritch/flesh
+ eldritch_passive = /datum/status_effect/heretic_passive/flesh
/datum/heretic_knowledge/limited_amount/starting/base_flesh/on_research(mob/user, datum/antagonist/heretic/our_heretic)
. = ..()
@@ -45,28 +75,8 @@
to_chat(user, span_hierophant("Undertaking the Path of Flesh, you are given another objective."))
our_heretic.owner.announce_objectives()
-/datum/heretic_knowledge/limited_amount/flesh_grasp
- name = "Grasp of Flesh"
- 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."
-
- limit = 1
- cost = 1
-
-
- research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
- research_tree_icon_state = "grasp_flesh"
-
-/datum/heretic_knowledge/limited_amount/flesh_grasp/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
- RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, PROC_REF(on_mansus_grasp))
-
-/datum/heretic_knowledge/limited_amount/flesh_grasp/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
- UnregisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK)
-
-/datum/heretic_knowledge/limited_amount/flesh_grasp/proc/on_mansus_grasp(mob/living/source, mob/living/target)
- SIGNAL_HANDLER
+/datum/heretic_knowledge/limited_amount/starting/base_flesh/on_mansus_grasp(mob/living/source, mob/living/target)
+ . = ..()
if(target.stat != DEAD)
return
@@ -92,8 +102,11 @@
make_ghoul(source, target)
+/// The max amount of health a ghoul has.
+#define GHOUL_MAX_HEALTH 25
+
/// Makes [victim] into a ghoul.
-/datum/heretic_knowledge/limited_amount/flesh_grasp/proc/make_ghoul(mob/living/user, mob/living/carbon/human/victim)
+/datum/heretic_knowledge/limited_amount/starting/base_flesh/proc/make_ghoul(mob/living/user, mob/living/carbon/human/victim)
user.log_message("created a ghoul, controlled by [key_name(victim)].", LOG_GAME)
message_admins("[ADMIN_LOOKUPFLW(user)] created a ghoul, [ADMIN_LOOKUPFLW(victim)].")
@@ -106,11 +119,11 @@
)
/// 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)
+/datum/heretic_knowledge/limited_amount/starting/base_flesh/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)
+/datum/heretic_knowledge/limited_amount/starting/base_flesh/proc/remove_from_ghoul(mob/living/ghoul)
LAZYREMOVE(created_items, WEAKREF(ghoul))
/datum/heretic_knowledge/limited_amount/flesh_ghoul
@@ -125,12 +138,10 @@
/obj/item/food/grown/poppy = 1,
)
limit = 2
- cost = 1
+ cost = 2
research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
research_tree_icon_state = "ghoul_voiceless"
-
-
/datum/heretic_knowledge/limited_amount/flesh_ghoul/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc)
. = ..()
if(!.)
@@ -173,6 +184,9 @@
make_ghoul(user, soon_to_be_ghoul)
return TRUE
+/// The max amount of health a voiceless dead has.
+#define MUTE_MAX_HEALTH 50
+
/// Makes [victim] into a ghoul.
/datum/heretic_knowledge/limited_amount/flesh_ghoul/proc/make_ghoul(mob/living/user, mob/living/carbon/human/victim)
user.log_message("created a voiceless dead, controlled by [key_name(victim)].", LOG_GAME)
@@ -196,17 +210,6 @@
LAZYREMOVE(created_items, WEAKREF(ghoul))
REMOVE_TRAIT(ghoul, TRAIT_MUTE, MAGIC_TRAIT)
-/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."
-
-
- mark_type = /datum/status_effect/eldritch/flesh
-
-/datum/heretic_knowledge/knowledge_ritual/flesh
-
/datum/heretic_knowledge/spell/flesh_surgery
name = "Knitting of Flesh"
desc = "Grants you the spell Knit Flesh. This spell allows you to remove organs from victims \
@@ -215,7 +218,22 @@
gain_text = "But they were not out of my reach for long. With every step, the screams grew, until at last \
I learned that they could be silenced."
action_to_add = /datum/action/cooldown/spell/touch/flesh_surgery
- cost = 1
+ cost = 2
+ drafting_tier = 5
+
+/datum/heretic_knowledge/armor/flesh
+ desc = "Allows you to transmute a table (or a suit), a mask and a pool of blood to create a writhing embrace. \
+ It grants you the ability to detect the health condition of other living (and non-living) and an aura that slowly heals your summons. \
+ Acts as a focus while hooded."
+ gain_text = "I tugged these wretched, slothing things about me, like one might a warm blanket. \
+ With eyes-not-mine, they will witness. With teeth-not-mine, they will clench. With limbs-not-mine, they will break."
+ result_atoms = list(/obj/item/clothing/suit/hooded/cultrobes/eldritch/flesh)
+ research_tree_icon_state = "flesh_armor"
+ required_atoms = list(
+ list(/obj/structure/table, /obj/item/clothing/suit) = 1,
+ /obj/item/clothing/mask = 1,
+ /obj/effect/decal/cleanable/blood = 1,
+ )
/datum/heretic_knowledge/summon/raw_prophet
name = "Raw Ritual"
@@ -230,7 +248,7 @@
/obj/item/bodypart/arm/left = 1,
)
mob_to_summon = /mob/living/basic/heretic_summon/raw_prophet
- cost = 1
+ cost = 2
poll_ignore_define = POLL_IGNORE_RAW_PROPHET
@@ -268,10 +286,10 @@
/obj/item/paper = 1,
)
mob_to_summon = /mob/living/basic/heretic_summon/stalker
- cost = 1
+ cost = 2
poll_ignore_define = POLL_IGNORE_STALKER
-
+ is_final_knowledge = TRUE
/datum/heretic_knowledge/ultimate/flesh_final
name = "Priest's Final Hymn"
@@ -297,7 +315,7 @@
worm_spell.Grant(user)
var/datum/antagonist/heretic/heretic_datum = GET_HERETIC(user)
- var/datum/heretic_knowledge/limited_amount/flesh_grasp/grasp_ghoul = heretic_datum.get_knowledge(/datum/heretic_knowledge/limited_amount/flesh_grasp)
+ var/datum/heretic_knowledge/limited_amount/starting/base_flesh/grasp_ghoul = heretic_datum.get_knowledge(/datum/heretic_knowledge/limited_amount/starting/base_flesh)
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
diff --git a/code/modules/antagonists/heretic/knowledge/heretic_armor_knowledge.dm b/code/modules/antagonists/heretic/knowledge/heretic_armor_knowledge.dm
new file mode 100644
index 00000000000..1295fe16590
--- /dev/null
+++ b/code/modules/antagonists/heretic/knowledge/heretic_armor_knowledge.dm
@@ -0,0 +1,25 @@
+/datum/heretic_knowledge/armor
+ name = "Armorer's Ritual"
+ desc = "Allows you to transmute a table (or a suit) and a 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."
+
+ required_atoms = list(
+ list(/obj/structure/table, /obj/item/clothing/suit) = 1,
+ /obj/item/clothing/mask = 1,
+ )
+ result_atoms = list(/obj/item/clothing/suit/hooded/cultrobes/eldritch)
+ cost = 1
+
+ research_tree_icon_path = 'icons/obj/clothing/suits/armor.dmi'
+ research_tree_icon_state = "eldritch_armor"
+ research_tree_icon_frame = 1
+
+/datum/heretic_knowledge/armor/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc)
+ . = ..()
+ var/datum/antagonist/heretic/heretic_datum = GET_HERETIC(user)
+ if(!heretic_datum)
+ return
+ SEND_SIGNAL(heretic_datum, COMSIG_HERETIC_PASSIVE_UPGRADE_FIRST)
+ heretic_datum.gain_knowledge(/datum/heretic_knowledge/knowledge_ritual)
diff --git a/code/modules/antagonists/heretic/knowledge/lock_lore.dm b/code/modules/antagonists/heretic/knowledge/lock_lore.dm
index b59711a9308..c4c3d0f8e2e 100644
--- a/code/modules/antagonists/heretic/knowledge/lock_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/lock_lore.dm
@@ -1,20 +1,47 @@
-
-/datum/heretic_knowledge_tree_column/main/lock
- neighbour_type_left = /datum/heretic_knowledge_tree_column/moon_to_lock
- neighbour_type_right = /datum/heretic_knowledge_tree_column/lock_to_flesh
-
+/datum/heretic_knowledge_tree_column/lock
route = PATH_LOCK
ui_bgr = "node_lock"
+ complexity = "Medium"
+ complexity_color = COLOR_YELLOW
+ icon = list(
+ "icon" = 'icons/obj/weapons/khopesh.dmi',
+ "state" = "key_blade",
+ "frame" = 1,
+ "dir" = SOUTH,
+ "moving" = FALSE,
+ )
+ description = list(
+ "The Path of Lock revolves around access, area denial, theft and gadgets.",
+ "Pick this path if you want a less confrontational playstyle and more interested in being a slippery rat.",
+ )
+ pros = list(
+ "Your mansus grasp can open any lock, unlock every terminal and bypass any access restriction.",
+ "lock heretics get a discount from the knowledge shop, making it the perfect path if you want to experiment with the various trinkets the shop has to offer.",
+ )
+ cons = list(
+ "The weakest heretic path in direct combat, period.",
+ "Very limited direct combat benefits.",
+ "You have no defensive benefits or immunities.",
+ "no mobility or direct additional teleportation",
+ "Highly reliant on sourcing power from other departments, players and the game world.",
+ )
+ tips = list(
+ "Your mansus grasp allows you to access everything, from airlocks, consoles and even exosuits, but it has no additional effects on players. It will however leave a mark that when triggered will make your victim unable to leave the room you are in.",
+ "Your blade also functions as a crowbar! You can store it in utility belts And, in a pitch, use it to force open an airlock.",
+ "Your Eldritch ID can create a portal between 2 different airlocks. Useful if you want to enstablish a secret base.",
+ "Use your labyrinth book to shake off pursuers. It creates impassible walls to anyone but you.",
+ )
start = /datum/heretic_knowledge/limited_amount/starting/base_knock
- grasp = /datum/heretic_knowledge/lock_grasp
- tier1 = /datum/heretic_knowledge/key_ring
- mark = /datum/heretic_knowledge/mark/lock_mark
- ritual_of_knowledge = /datum/heretic_knowledge/knowledge_ritual/lock
- unique_ability = /datum/heretic_knowledge/limited_amount/concierge_rite
- tier2 = /datum/heretic_knowledge/spell/burglar_finesse
+ knowledge_tier1 = /datum/heretic_knowledge/key_ring
+ guaranteed_side_tier1 = /datum/heretic_knowledge/painting
+ knowledge_tier2 = /datum/heretic_knowledge/limited_amount/concierge_rite
+ guaranteed_side_tier2 = /datum/heretic_knowledge/spell/opening_blast
+ robes = /datum/heretic_knowledge/armor/lock
+ knowledge_tier3 = /datum/heretic_knowledge/spell/burglar_finesse
+ guaranteed_side_tier3 = /datum/heretic_knowledge/summon/fire_shark
blade = /datum/heretic_knowledge/blade_upgrade/flesh/lock
- tier3 = /datum/heretic_knowledge/spell/caretaker_refuge
+ knowledge_tier4 = /datum/heretic_knowledge/spell/caretaker_refuge
ascension = /datum/heretic_knowledge/ultimate/lock_final
/datum/heretic_knowledge/limited_amount/starting/base_knock
@@ -32,33 +59,29 @@
limit = 2
research_tree_icon_path = 'icons/obj/weapons/khopesh.dmi'
research_tree_icon_state = "key_blade"
+ mark_type = /datum/status_effect/eldritch/lock
+ eldritch_passive = /datum/status_effect/heretic_passive/lock
-/datum/heretic_knowledge/lock_grasp
- name = "Grasp of Lock"
- desc = "Your mansus grasp allows you to access anything! Right click on an airlock or a locker to force it open. \
- DNA locks on mechs will be removed, and any pilot will be ejected. Works on consoles. \
- Makes a distinctive knocking sound on use."
- gain_text = "Nothing may remain closed from my touch."
- cost = 1
- research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
- research_tree_icon_state = "grasp_lock"
-
-/datum/heretic_knowledge/lock_grasp/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
+/datum/heretic_knowledge/limited_amount/starting/base_knock/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
+ . = ..()
RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK_SECONDARY, PROC_REF(on_secondary_mansus_grasp))
- RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, PROC_REF(on_mansus_grasp))
+ var/datum/action/cooldown/spell/touch/mansus_grasp/grasp_spell = locate() in user.actions
+ grasp_spell?.invocation_type = INVOCATION_NONE
+ grasp_spell?.sound = null
-/datum/heretic_knowledge/lock_grasp/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
+/datum/heretic_knowledge/limited_amount/starting/base_knock/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
+ . = ..()
UnregisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK_SECONDARY)
- UnregisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK)
-/datum/heretic_knowledge/lock_grasp/proc/on_mansus_grasp(mob/living/source, mob/living/target)
- SIGNAL_HANDLER
+/datum/heretic_knowledge/limited_amount/starting/base_knock/on_mansus_grasp(mob/living/source, mob/living/target)
+ . = ..()
+
var/obj/item/clothing/under/suit = target.get_item_by_slot(ITEM_SLOT_ICLOTHING)
if(istype(suit) && suit.adjusted == NORMAL_STYLE)
suit.toggle_jumpsuit_adjust()
suit.update_appearance()
-/datum/heretic_knowledge/lock_grasp/proc/on_secondary_mansus_grasp(mob/living/source, atom/target)
+/datum/heretic_knowledge/limited_amount/starting/base_knock/proc/on_secondary_mansus_grasp(mob/living/source, atom/target)
SIGNAL_HANDLER
if(ismecha(target))
@@ -80,7 +103,14 @@
var/turf/target_turf = get_turf(target)
SEND_SIGNAL(target_turf, COMSIG_ATOM_MAGICALLY_UNLOCKED, src, source)
- playsound(target, 'sound/effects/magic/hereticknock.ogg', 100, TRUE, -1)
+ SEND_SOUND(source, 'sound/effects/magic/hereticknock.ogg')
+
+ if(HAS_TRAIT(source, TRAIT_LOCK_GRASP_UPGRADED))
+ var/datum/action/cooldown/spell/touch/mansus_grasp/grasp = locate() in source.actions
+ if(grasp)
+ grasp.next_use_time -= round(grasp.cooldown_time*0.75)
+ grasp.build_all_button_icons()
+ return
return COMPONENT_USE_HAND
@@ -99,7 +129,7 @@
/obj/item/card/id/advanced = 1,
)
result_atoms = list(/obj/item/card/id/advanced/heretic)
- cost = 1
+ cost = 2
research_tree_icon_path = 'icons/obj/card.dmi'
research_tree_icon_state = "card_gold"
@@ -115,20 +145,10 @@
result_item.shapeshift(id)
return TRUE
-/datum/heretic_knowledge/mark/lock_mark
- name = "Mark of Lock"
- desc = "Your Mansus Grasp now applies the Mark of Lock. \
- Attack a marked person to bar them from all passages for the duration of the mark. \
- This will make it so that they have no access whatsoever, even public access doors will reject them."
- gain_text = "The Gatekeeper was a corrupt Steward. She hindered her fellows for her own twisted amusement."
- mark_type = /datum/status_effect/eldritch/lock
-
-/datum/heretic_knowledge/knowledge_ritual/lock
-
-/datum/heretic_knowledge/limited_amount/concierge_rite // item that creates 3 max at a time heretic only barriers, probably should limit to 1 only, holy people can also pass
+/datum/heretic_knowledge/limited_amount/concierge_rite
name = "Concierge's Rite"
desc = "Allows you to transmute a crayon, a wooden plank, and a multitool to create a Labyrinth Handbook. \
- It can materialize a barricade at range that only you and people resistant to magic can pass. 3 uses."
+ It can materialize a barricade at range that only you and people resistant to magic can pass. Has 5 charges which regenerate over time."
gain_text = "The Concierge scribbled my name into the Handbook. \"Welcome to your new home, fellow Steward.\""
required_atoms = list(
/obj/item/toy/crayon = 1,
@@ -136,9 +156,25 @@
/obj/item/multitool = 1,
)
result_atoms = list(/obj/item/heretic_labyrinth_handbook)
- cost = 1
+ cost = 2
research_tree_icon_path = 'icons/obj/service/library.dmi'
research_tree_icon_state = "heretichandbook"
+ drafting_tier = 5
+
+/datum/heretic_knowledge/armor/lock
+ desc = "Allows you to transmute a table (or a suit), a mask and a crowbar to create a shifting guise. \
+ It grants you camoflage from cameras, hides your identity, voice and muffles your footsteps. \
+ Acts as a focus while hooded."
+ gain_text = "While stewards are known to the Concierge, \
+ they still consort between one another and with outsiders under shaded cloaks and drawn hoods. \
+ Familiarity is treachery, even to oneself."
+ result_atoms = list(/obj/item/clothing/suit/hooded/cultrobes/eldritch/lock)
+ research_tree_icon_state = "lock_armor"
+ required_atoms = list(
+ list(/obj/structure/table, /obj/item/clothing/suit) = 1,
+ /obj/item/clothing/mask = 1,
+ /obj/item/crowbar = 1,
+ )
/datum/heretic_knowledge/spell/burglar_finesse
name = "Burglar's Finesse"
@@ -147,9 +183,9 @@
gain_text = "Consorting with Burglar spirits is frowned upon, but a Steward will always want to learn about new doors."
action_to_add = /datum/action/cooldown/spell/pointed/burglar_finesse
- cost = 1
+ cost = 2
-/datum/heretic_knowledge/blade_upgrade/flesh/lock //basically a chance-based weeping avulsion version of the former
+/datum/heretic_knowledge/blade_upgrade/flesh/lock
name = "Opening Blade"
desc = "Your blade has a chance to cause a weeping avulsion on attack."
gain_text = "The Pilgrim-Surgeon was not an Steward. Nonetheless, its blades and sutures proved a match for their keys."
@@ -169,7 +205,8 @@
You are invincible but unable to harm anything. Cancelled by being hit with an anti-magic item."
gain_text = "Jealously, the Guard and the Hound hunted me. But I unlocked my form, and was but a haze, untouchable."
action_to_add = /datum/action/cooldown/spell/caretaker
- cost = 1
+ cost = 2
+ is_final_knowledge = TRUE
/datum/heretic_knowledge/ultimate/lock_final
name = "Unlock the Labyrinth"
diff --git a/code/modules/antagonists/heretic/knowledge/moon_lore.dm b/code/modules/antagonists/heretic/knowledge/moon_lore.dm
index 69dd2dd4aad..137b4a1aef1 100644
--- a/code/modules/antagonists/heretic/knowledge/moon_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/moon_lore.dm
@@ -1,57 +1,79 @@
-
-/datum/heretic_knowledge_tree_column/main/moon
- neighbour_type_left = /datum/heretic_knowledge_tree_column/ash_to_moon
- neighbour_type_right = /datum/heretic_knowledge_tree_column/moon_to_lock
-
+/datum/heretic_knowledge_tree_column/moon
route = PATH_MOON
ui_bgr = "node_moon"
+ complexity = "Hard"
+ complexity_color = COLOR_RED
+ icon = list(
+ "icon" = 'icons/obj/weapons/khopesh.dmi',
+ "state" = "moon_blade",
+ "frame" = 1,
+ "dir" = SOUTH,
+ "moving" = FALSE,
+ )
+ description = list(
+ "The Path of Moon revolves around sanity, sowing confusion and discord, and skirting the conventional rules of combat.",
+ "Play this path if you are already experienced with Heretic and want to try something highly unconventional, or simply if you desire to play a pacifist Heretic (Yes, really!)."
+ )
+ pros = list(
+ "High amount of tools to confound foes.",
+ "Sows chaos through the station via lunatics.",
+ "Practically immune to disabling effects while wearing the Resplendent Regalia."
+ )
+ cons = list(
+ "No mobility.",
+ "Mo direct tools to damage your opponents.",
+ "Reliant on misdirection and confusion.",
+ "Lunatics can become liabilities.",
+ "Fairly fragile despite their unique protection mechanics.",
+ "Death while wearing the Resplendent Regalia results in a gorey end.",
+ )
+ tips = list(
+ "Your Mansus Grasp will make your victim briefly hallucinate and apply a mark that, when triggered by your moon blade, will apply confusion and pacify them (the latter will get removed if the victim receives too much damage at once).",
+ "Your moon blade is special compared to the other heretic blades. It can be used even if you are pacified.",
+ "Your passive makes you completely impervious to brain traumas and slowly regenerates your brain health. Makes sure to upgrade it to bolster the regeneration effect.",
+ "Your Resplendent Regalia utterly changes the rules of combat for you and your opponents; You become fully immune to disabling effect, and all damage received (lethal or non lethal) will be converted into brain damage. However. the robes themselves have no armor, and prevent you from using guns as well as pacifying you (you can still use your moon blade).",
+ "Your moon amulette allows you to channel its effects through your moon blade. When toggled on, your Moon blade will no longer do lethal damage, but do sanity damage and become unblockable.",
+ "If the sanity of your opponents goes below a certain threshold, they'll become a lunatic. Lunatics are prompted to start attacking everyone (including you). Should you want to sacrifice them (or to get them to leave you be), hit them again with your moon blade to put them to sleep.",
+ "Ringleader's Rise summons an army of clones. They do barely any damage, but should they be attacked by non-heretics, they will explode and cause sanity and brain damage to those around them.",
+ "Your ascension will grant you an aura that converts nearby people to loyal lunatics. However, if they have a mindshield implant, their heads will instead detonate after a time.",
+ )
start = /datum/heretic_knowledge/limited_amount/starting/base_moon
- grasp = /datum/heretic_knowledge/moon_grasp
- tier1 = /datum/heretic_knowledge/spell/moon_smile
- mark = /datum/heretic_knowledge/mark/moon_mark
- ritual_of_knowledge = /datum/heretic_knowledge/knowledge_ritual/moon
- unique_ability = /datum/heretic_knowledge/spell/moon_parade
- tier2 = /datum/heretic_knowledge/moon_amulet
+ knowledge_tier1 = /datum/heretic_knowledge/spell/mind_gate
+ guaranteed_side_tier1 = /datum/heretic_knowledge/phylactery
+ knowledge_tier2 = /datum/heretic_knowledge/moon_amulet
+ guaranteed_side_tier2 = /datum/heretic_knowledge/codex_morbus
+ robes = /datum/heretic_knowledge/armor/moon
+ knowledge_tier3 = /datum/heretic_knowledge/spell/moon_parade
+ guaranteed_side_tier3 = /datum/heretic_knowledge/unfathomable_curio
blade = /datum/heretic_knowledge/blade_upgrade/moon
- tier3 = /datum/heretic_knowledge/spell/moon_ringleader
+ knowledge_tier4 = /datum/heretic_knowledge/spell/moon_ringleader
ascension = /datum/heretic_knowledge/ultimate/moon_final
/datum/heretic_knowledge/limited_amount/starting/base_moon
name = "Moonlight Troupe"
desc = "Opens up the Path of Moon to you. \
- Allows you to transmute 2 sheets of iron and a knife into an Lunar Blade. \
+ Allows you to transmute 2 sheets of glass and a knife into an Lunar Blade. \
You can only create two at a time."
gain_text = "Under the light of the moon the laughter echoes."
required_atoms = list(
/obj/item/knife = 1,
- /obj/item/stack/sheet/iron = 2,
+ /obj/item/stack/sheet/glass = 2,
)
result_atoms = list(/obj/item/melee/sickly_blade/moon)
research_tree_icon_path = 'icons/obj/weapons/khopesh.dmi'
research_tree_icon_state = "moon_blade"
+ mark_type = /datum/status_effect/eldritch/moon
+ eldritch_passive = /datum/status_effect/heretic_passive/moon
/datum/heretic_knowledge/limited_amount/starting/base_moon/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
+ . = ..()
ADD_TRAIT(user, TRAIT_EMPATH, REF(src))
-/datum/heretic_knowledge/moon_grasp
- name = "Grasp of Lunacy"
- desc = "Your Mansus Grasp will cause your victims to hallucinate everyone as lunar mass, \
- and hides your identity for a short duration."
- gain_text = "The troupe on the side of the moon showed me truth, and I took it."
- cost = 1
- research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
- research_tree_icon_state = "grasp_moon"
+/datum/heretic_knowledge/limited_amount/starting/base_moon/on_mansus_grasp(mob/living/source, mob/living/target)
+ . = ..()
-/datum/heretic_knowledge/moon_grasp/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
- RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, PROC_REF(on_mansus_grasp))
-
-/datum/heretic_knowledge/moon_grasp/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
- UnregisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK)
-
-/datum/heretic_knowledge/moon_grasp/proc/on_mansus_grasp(mob/living/source, mob/living/target)
- SIGNAL_HANDLER
- if(target.can_block_magic(MAGIC_RESISTANCE_MIND))
+ if(target.can_block_magic(MAGIC_RESISTANCE_MOON))
to_chat(target, span_danger("You hear echoing laughter from above..but it is dull and distant."))
return
@@ -64,39 +86,23 @@
carbon_target.cause_hallucination(/datum/hallucination/delusion/preset/moon, "delusion/preset/moon hallucination caused by mansus grasp")
carbon_target.mob_mood.adjust_sanity(-30)
-/datum/heretic_knowledge/spell/moon_smile
- name = "Smile of the moon"
- desc = "Grants you Smile of the moon, a ranged spell muting, blinding, deafening and knocking down the target for a\
- duration based on their sanity."
- gain_text = "The moon smiles upon us all and those who see its true side can bring its joy."
+/datum/heretic_knowledge/spell/mind_gate
+ name = "Mind Gate"
+ desc = "Grants you Mind Gate, a spell which mutes,deafens, blinds, inflicts hallucinations, \
+ confusion, oxygen loss and brain damage to its target over 10 seconds.\
+ The caster takes 20 brain damage per use."
+ gain_text = "My mind swings open like a gate, and its insight will let me perceive the truth."
- action_to_add = /datum/action/cooldown/spell/pointed/moon_smile
- cost = 1
-
-/datum/heretic_knowledge/mark/moon_mark
- name = "Mark of Moon"
- desc = "Your Mansus Grasp now applies the Mark of Moon, pacifying the victim until attacked. \
- The mark can also be triggered from an attack with your Moon Blade, leaving the victim confused."
- gain_text = "The troupe on the moon would dance all day long \
- and in that dance the moon would smile upon us \
- but when the night came its smile would dull forced to gaze on the earth."
- mark_type = /datum/status_effect/eldritch/moon
-
-/datum/heretic_knowledge/knowledge_ritual/moon
-
-/datum/heretic_knowledge/spell/moon_parade
- name = "Lunar Parade"
- desc = "Grants you Lunar Parade, a spell that - after a short charge - sends a carnival forward \
- when hitting someone they are forced to join the parade and suffer hallucinations."
- gain_text = "The music like a reflection of the soul compelled them, like moths to a flame they followed"
- action_to_add = /datum/action/cooldown/spell/pointed/projectile/moon_parade
- cost = 1
+ action_to_add = /datum/action/cooldown/spell/pointed/mind_gate
+ cost = 2
/datum/heretic_knowledge/moon_amulet
name = "Moonlight Amulet"
desc = "Allows you to transmute 2 sheets of glass, a heart and a tie to create a Moonlight Amulet. \
If the item is used on someone with low sanity they go berserk attacking everyone, \
- if their sanity isn't low enough it decreases their mood."
+ if their sanity isn't low enough it decreases their mood. \
+ Wearing this will grant you the ability to see heathens through walls and make your blades harmless, they will instead directly attack their mind. \
+ Provides thermal vision and doubles the brain regen of a moon heretic while worn."
gain_text = "At the head of the parade he stood, the moon condensed into one mass, a reflection of the soul."
required_atoms = list(
@@ -105,19 +111,41 @@
/obj/item/clothing/neck/tie = 1,
)
result_atoms = list(/obj/item/clothing/neck/heretic_focus/moon_amulet)
- cost = 1
-
+ cost = 2
research_tree_icon_path = 'icons/obj/antags/eldritch.dmi'
research_tree_icon_state = "moon_amulette"
research_tree_icon_frame = 9
+/datum/heretic_knowledge/armor/moon
+ desc = "Allows you to transmute a table (or a suit), a mask and two sheets of glass to create a Resplendant Regalia, this robe will render the user fully immune to disabling effects and convert all forms of damage into brain damage, while also pacifying the user and render him unable to use ranged weapons (Moon blade will bypass pacifism). \
+ Acts as a focus while hooded."
+ gain_text = "Trails of light and mirth flowed from every arm of this magnificent attire. \
+ The troupe twirled in irridescent cascades, dazzling onlookers with the truth they sought. \
+ I observed, basking in the light, to find my self."
+ result_atoms = list(/obj/item/clothing/suit/hooded/cultrobes/eldritch/moon)
+ research_tree_icon_state = "moon_armor"
+ required_atoms = list(
+ list(/obj/structure/table, /obj/item/clothing/suit) = 1,
+ /obj/item/clothing/mask = 1,
+ /obj/item/stack/sheet/glass = 2,
+ )
+
+/datum/heretic_knowledge/spell/moon_parade
+ name = "Lunar Parade"
+ desc = "Grants you Lunar Parade, a spell that - after a short charge - sends a carnival forward \
+ when hitting someone they are forced to join the parade and suffer hallucinations."
+ gain_text = "The music like a reflection of the soul compelled them, like moths to a flame they followed"
+ action_to_add = /datum/action/cooldown/spell/pointed/projectile/moon_parade
+ cost = 2
+ drafting_tier = 5
+
/datum/heretic_knowledge/blade_upgrade/moon
name = "Moonlight Blade"
- desc = "Your blade now deals brain damage, causes random hallucinations and does sanity damage."
+ desc = "Your blade now deals brain damage, causes random hallucinations and does sanity damage. \
+ Deals more brain damage if your victim is insane or unconscious."
gain_text = "His wit was sharp as a blade, cutting through the lie to bring us joy."
-
research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
research_tree_icon_state = "blade_upgrade_moon"
@@ -125,16 +153,19 @@
if(source == target || !isliving(target))
return
- if(target.can_block_magic(MAGIC_RESISTANCE_MIND))
+ if(target.can_block_magic(MAGIC_RESISTANCE_MOON))
return
- target.adjustOrganLoss(ORGAN_SLOT_BRAIN, 10, 100)
target.cause_hallucination( \
get_random_valid_hallucination_subtype(/datum/hallucination/body), \
"upgraded path of moon blades", \
)
target.emote(pick("giggle", "laugh"))
- target.mob_mood.adjust_sanity(-10)
+ target.mob_mood?.adjust_sanity(-10)
+ if(target.stat == CONSCIOUS && target.mob_mood?.sanity >= SANITY_NEUTRAL)
+ target.adjustOrganLoss(ORGAN_SLOT_BRAIN, 10)
+ return
+ target.adjustOrganLoss(ORGAN_SLOT_BRAIN, 25)
/datum/heretic_knowledge/spell/moon_ringleader
name = "Ringleaders Rise"
@@ -145,17 +176,17 @@
The ringleader pointed up and the dim light of truth illuminated us further."
action_to_add = /datum/action/cooldown/spell/aoe/moon_ringleader
- cost = 1
-
+ cost = 2
research_tree_icon_frame = 5
+ is_final_knowledge = TRUE
/datum/heretic_knowledge/ultimate/moon_final
name = "The Last Act"
desc = "The ascension ritual of the Path of Moon. \
Bring 3 corpses with more than 50 brain damage to a transmutation rune to complete the ritual. \
When completed, you become a harbinger of madness gaining and aura of passive sanity decrease, \
- confusion increase and, if their sanity is low enough, brain damage and blindness. \
+ crewmembers with low enough sanity will be converted into acolytes. \
1/5th of the crew will turn into acolytes and follow your command, they will all receive moonlight amulets."
gain_text = "We dived down towards the crowd, his soul splitting off in search of greater venture \
for where the Ringleader had started the parade, I shall continue it unto the suns demise \
@@ -198,62 +229,91 @@
var/max_lunatics = ceil(max(length(GLOB.manifest.locked), length(lunatic_candidates)) * 0.2)
for(var/mob/living/carbon/human/crewmate as anything in lunatic_candidates)
- // Heretics, lunatics and monsters shouldn't become lunatics because they either have a master or have a mansus grasp
- if(IS_HERETIC_OR_MONSTER(crewmate))
- to_chat(crewmate, span_boldwarning("[user]'s rise is influencing those who are weak willed. Their minds shall rend." ))
- continue
- // Mindshielded and anti-magic folks are immune against this effect because this is a magical mind effect
- if(HAS_MIND_TRAIT(crewmate, TRAIT_UNCONVERTABLE) || crewmate.can_block_magic(MAGIC_RESISTANCE))
- to_chat(crewmate, span_boldwarning("You feel shielded from something." ))
- continue
if(amount_of_lunatics > max_lunatics)
to_chat(crewmate, span_boldwarning("You feel uneasy, as if for a brief moment something was gazing at you."))
continue
- var/datum/antagonist/lunatic/lunatic = crewmate.mind.add_antag_datum(/datum/antagonist/lunatic)
- lunatic.set_master(user.mind, user)
- var/obj/item/clothing/neck/heretic_focus/moon_amulet/amulet = new(crewmate.drop_location())
- var/static/list/slots = list(
- LOCATION_NECK,
- LOCATION_HANDS,
- LOCATION_RPOCKET,
- LOCATION_LPOCKET,
- LOCATION_BACKPACK,
- )
- crewmate.equip_in_one_of_slots(amulet, slots, qdel_on_fail = FALSE)
- crewmate.emote("laugh")
- amount_of_lunatics++
+ if(attempt_conversion(crewmate, user))
+ amount_of_lunatics++
+
+/datum/heretic_knowledge/ultimate/moon_final/proc/attempt_conversion(mob/living/carbon/convertee, mob/user)
+ // Heretics, lunatics and monsters shouldn't become lunatics because they either have a master or have a mansus grasp
+ if(IS_HERETIC_OR_MONSTER(convertee))
+ to_chat(convertee, span_boldwarning("[user]'s rise is influencing those who are weak willed. Their minds shall rend." ))
+ return FALSE
+ // Mindshielded and anti-magic folks are immune against this effect because this is a magical mind effect
+ if(HAS_MIND_TRAIT(convertee, TRAIT_UNCONVERTABLE) || convertee.can_block_magic(MAGIC_RESISTANCE))
+ to_chat(convertee, span_boldwarning("You feel shielded from something." ))
+ return FALSE
+
+ if(!convertee.mind)
+ return FALSE
+
+ var/datum/antagonist/lunatic/lunatic = convertee.mind.add_antag_datum(/datum/antagonist/lunatic)
+ lunatic.set_master(user.mind, user)
+ var/obj/item/clothing/neck/heretic_focus/moon_amulet/amulet = new(convertee.drop_location())
+ var/static/list/slots = list(
+ LOCATION_NECK,
+ LOCATION_HANDS,
+ LOCATION_RPOCKET,
+ LOCATION_LPOCKET,
+ LOCATION_BACKPACK,
+ )
+ convertee.equip_in_one_of_slots(amulet, slots, qdel_on_fail = FALSE)
+ INVOKE_ASYNC(convertee, TYPE_PROC_REF(/mob, emote), "laugh")
+ return TRUE
/datum/heretic_knowledge/ultimate/moon_final/proc/on_life(mob/living/source, seconds_per_tick, times_fired)
SIGNAL_HANDLER
-
visible_hallucination_pulse(
center = get_turf(source),
radius = 7,
hallucination_duration = 60 SECONDS
)
- for(var/mob/living/carbon/carbon_view in view(5, source))
+ for(var/mob/living/carbon/carbon_view in range(7, source))
+ var/carbon_sanity = carbon_view.mob_mood.sanity
if(carbon_view.stat != CONSCIOUS)
continue
if(IS_HERETIC_OR_MONSTER(carbon_view))
continue
- if(carbon_view.can_block_magic(MAGIC_RESISTANCE_MIND)) //Somehow a shitty piece of tinfoil is STILL able to hold out against the power of an ascended heretic.
+ if(carbon_view.can_block_magic(MAGIC_RESISTANCE_MOON)) //Somehow a shitty piece of tinfoil is STILL able to hold out against the power of an ascended heretic.
continue
new /obj/effect/temp_visual/moon_ringleader(get_turf(carbon_view))
+ if(carbon_view.has_status_effect(/datum/status_effect/confusion))
+ to_chat(carbon_view, span_big(span_hypnophrase("YOUR HEAD RATTLES WITH A THOUSAND VOICES JOINED IN A MADDENING CACOPHONY OF SOUND AND MUSIC. EVERY FIBER OF YOUR BEING SAYS 'RUN'.")))
carbon_view.adjust_confusion(2 SECONDS)
- carbon_view.mob_mood.adjust_sanity(-5)
- var/carbon_sanity = carbon_view.mob_mood.sanity
- if(carbon_sanity < 30)
- if(SPT_PROB(20, seconds_per_tick))
- to_chat(carbon_view, span_warning("you feel your mind beginning to rend!"))
- carbon_view.adjustOrganLoss(ORGAN_SLOT_BRAIN, 5)
+ carbon_view.mob_mood.adjust_sanity(-20)
- if(carbon_sanity < 10)
- if(SPT_PROB(20, seconds_per_tick))
- to_chat(carbon_view, span_warning("it echoes through you!"))
- visible_hallucination_pulse(
- center = get_turf(carbon_view),
- radius = 7,
- hallucination_duration = 50 SECONDS
- )
- carbon_view.adjust_temp_blindness(5 SECONDS)
+ if(carbon_sanity >= 10)
+ return
+ // So our sanity is dead, time to fuck em up
+ if(SPT_PROB(20, seconds_per_tick))
+ to_chat(carbon_view, span_warning("it echoes through you!"))
+ visible_hallucination_pulse(
+ center = get_turf(carbon_view),
+ radius = 7,
+ hallucination_duration = 50 SECONDS
+ )
+ carbon_view.adjust_temp_blindness(5 SECONDS)
+ if(should_mind_explode(carbon_view))
+ to_chat(carbon_view, span_boldbig(span_red(\
+ "YOUR SENSES REEL AS YOUR MIND IS ENVELOPED BY AN OTHERWORLDLY FORCE ATTEMPTING TO REWRITE YOUR VERY BEING. \
+ YOU CANNOT EVEN BEGIN TO SCREAM BEFORE YOUR IMPLANT ACTIVATES ITS PSIONIC FAIL-SAFE PROTOCOL, TAKING YOUR HEAD WITH IT.")))
+ var/obj/item/bodypart/head/head = locate() in carbon_view.bodyparts
+ if(head)
+ head.dismember()
+ else
+ carbon_view.gib(DROP_ALL_REMAINS)
+ var/datum/effect_system/reagents_explosion/explosion = new()
+ explosion.set_up(1, get_turf(carbon_view), TRUE, 0)
+ explosion.start(src)
+ else
+ attempt_conversion(carbon_view, source)
+
+
+/datum/heretic_knowledge/ultimate/moon_final/proc/should_mind_explode(mob/living/carbon/target)
+ if(HAS_TRAIT(target, TRAIT_MINDSHIELD))
+ return TRUE
+ if(IS_CULTIST_OR_CULTIST_MOB(target))
+ return TRUE
+ return FALSE
diff --git a/code/modules/antagonists/heretic/knowledge/rust_lore.dm b/code/modules/antagonists/heretic/knowledge/rust_lore.dm
index 4a60d372445..6f701ef761b 100644
--- a/code/modules/antagonists/heretic/knowledge/rust_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/rust_lore.dm
@@ -1,20 +1,53 @@
-
-/datum/heretic_knowledge_tree_column/main/rust
- neighbour_type_left = /datum/heretic_knowledge_tree_column/blade_to_rust
- neighbour_type_right = /datum/heretic_knowledge_tree_column/rust_to_cosmic
-
+/datum/heretic_knowledge_tree_column/rust
route = PATH_RUST
ui_bgr = "node_rust"
+ complexity = "Medium"
+ complexity_color = COLOR_YELLOW
+ icon = list(
+ "icon" = 'icons/obj/weapons/khopesh.dmi',
+ "state" = "rust_blade",
+ "frame" = 1,
+ "dir" = SOUTH,
+ "moving" = FALSE,
+ )
+ description = list(
+ "The Path of Rust revolves around durability, corruption and brute forcing your way through obstacles.",
+ "Pick this path if you enjoy a standing your ground and letting the fight come to you.",
+ )
+ pros = list(
+ "Standing on rusted tiles makes you highly durable; regenerating wounds and removing stuns.",
+ "Rusted tiles harm your foes and slow them down.",
+ "You are able to destroy walls, objects, mechs, structures and airlocks with ease.",
+ "You can instantly obliterate silicons or synthetic crew members with your Mansus Grasp.",
+ "You have a high amount of disruption abilities to make it easier to fight in your territory.",
+ )
+ cons = list(
+ "Extremely overt; throws stealth completely out as an option.",
+ "If you are not on rusted tiles, you become significantly more vulnerable.",
+ "Being locked to a territorial conflict makes it much easier to use destructive tools (like bombs) against you.",
+ "Your high amount of defensive power is at the cost of offensive power.",
+ )
+ tips = list(
+ "Your Mansus Grasp will instantly destroy mechs, silicons and androids. Hitting a marked target with your blade will cause heavy disgust and make them vomit, knocking them down briefly.",
+ "Your Mansus Grasp and your spells are capable of rusting walls and floors, making them beneficial to you and harmful to the crew and silicons. Spread rust as much as possible.",
+ "Rusted turfs will heal you, regulate your blood temperature, make you resistant to batons knockdown, regenerate your stamina and blood and heal your wound and limbs once you level up your passive.",
+ "Always fight on your turf. Your opponent entering your turf are at a significant disadvantage.",
+ "Your Reassembled Raiment is only empowered while you are on your rusted tiles. If you want the most out of its power, stay on your rusted tiles.",
+ "Your ability to destroy objects and walls improves as your passive ugprade increases; eventually you will be able to melt through airlocks, reinforced walls and even titanium walls.",
+ "Spreading rust can be fairly slow, especially early on. Consider summoning a few rust walkers to help you expand your domain.",
+ "Rusted Construction allows you to produce barriers for cover or escape, or even block off someone else's escape in a pinch. Make the most of it to manipulate the environment to your needs.",
+ )
start = /datum/heretic_knowledge/limited_amount/starting/base_rust
- grasp = /datum/heretic_knowledge/rust_fist
- tier1 = /datum/heretic_knowledge/rust_regen
- mark = /datum/heretic_knowledge/mark/rust_mark
- ritual_of_knowledge = /datum/heretic_knowledge/knowledge_ritual/rust
- unique_ability = /datum/heretic_knowledge/spell/rust_construction
- tier2 = /datum/heretic_knowledge/spell/area_conversion
+ knowledge_tier1 = /datum/heretic_knowledge/spell/area_conversion
+ guaranteed_side_tier1 = /datum/heretic_knowledge/rust_sower
+ knowledge_tier2 = /datum/heretic_knowledge/spell/rust_construction
+ guaranteed_side_tier2 = /datum/heretic_knowledge/summon/rusty
+ robes = /datum/heretic_knowledge/armor/rust
+ knowledge_tier3 = /datum/heretic_knowledge/spell/entropic_plume
+ guaranteed_side_tier3 = /datum/heretic_knowledge/crucible
blade = /datum/heretic_knowledge/blade_upgrade/rust
- tier3 = /datum/heretic_knowledge/spell/entropic_plume
+ knowledge_tier4 = /datum/heretic_knowledge/spell/rust_charge
ascension = /datum/heretic_knowledge/ultimate/rust_final
/datum/heretic_knowledge/limited_amount/starting/base_rust
@@ -30,34 +63,34 @@
result_atoms = list(/obj/item/melee/sickly_blade/rust)
research_tree_icon_path = 'icons/obj/weapons/khopesh.dmi'
research_tree_icon_state = "rust_blade"
+ mark_type = /datum/status_effect/eldritch/rust
+ eldritch_passive = /datum/status_effect/heretic_passive/rust
-/datum/heretic_knowledge/rust_fist
- name = "Grasp of Rust"
- desc = "Your Mansus Grasp will deal 500 damage to non-living matter and rust any surface it touches. \
- Already rusted surfaces are destroyed. Surfaces and structures can only be rusted by using Right-Click. \
- Allows you to rust basic iron walls and floors."
- gain_text = "On the ceiling of the Mansus, rust grows as moss does on a stone."
- cost = 1
- research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
- research_tree_icon_state = "grasp_rust"
-
-/datum/heretic_knowledge/rust_fist/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
- RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, PROC_REF(on_mansus_grasp))
+/datum/heretic_knowledge/limited_amount/starting/base_rust/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
+ . = ..()
RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK_SECONDARY, PROC_REF(on_secondary_mansus_grasp))
- our_heretic.increase_rust_strength()
+ user.RemoveElement(/datum/element/leeching_walk/minor)
-/datum/heretic_knowledge/rust_fist/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
- UnregisterSignal(user, list(COMSIG_HERETIC_MANSUS_GRASP_ATTACK, COMSIG_HERETIC_MANSUS_GRASP_ATTACK_SECONDARY))
+/datum/heretic_knowledge/limited_amount/starting/base_rust/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
+ . = ..()
+ UnregisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK_SECONDARY)
+ user.AddElement(/datum/element/leeching_walk/minor)
-/datum/heretic_knowledge/rust_fist/proc/on_mansus_grasp(mob/living/source, mob/living/target)
- SIGNAL_HANDLER
+/datum/heretic_knowledge/limited_amount/starting/base_rust/on_mansus_grasp(mob/living/source, mob/living/target)
+ . = ..()
+
+ if(iscarbon(target))
+ var/mob/living/carbon/carbon_target = target
+ for(var/obj/item/bodypart/robotic_limb as anything in carbon_target.bodyparts)
+ if(IS_ROBOTIC_LIMB(robotic_limb))
+ robotic_limb.receive_damage(500)
if(!issilicon(target) && !(target.mob_biotypes & MOB_ROBOTIC))
return
source.do_rust_heretic_act(target)
-/datum/heretic_knowledge/rust_fist/proc/on_secondary_mansus_grasp(mob/living/source, atom/target)
+/datum/heretic_knowledge/limited_amount/starting/base_rust/proc/on_secondary_mansus_grasp(mob/living/source, atom/target)
SIGNAL_HANDLER
// Rusting an airlock causes it to lose power, mostly to prevent the airlock from shocking you.
@@ -69,20 +102,14 @@
source.do_rust_heretic_act(target)
return COMPONENT_USE_HAND
-/datum/heretic_knowledge/rust_regen
- name = "Leeching Walk"
- desc = "Grants you passive healing and resistance to batons while standing over rust."
- gain_text = "The speed was unparalleled, the strength unnatural. The Blacksmith was smiling."
- cost = 1
- research_tree_icon_path = 'icons/effects/eldritch.dmi'
- research_tree_icon_state = "cloud_swirl"
+/datum/heretic_knowledge/spell/rust_charge
+ name = "Rust Charge"
+ desc = "A charge that must be started on a rusted tile and will destroy any rusted objects you come into contact with, will deal high damage to others and rust around you during the charge."
+ gain_text = "The hills sparkled now, as I neared them my mind began to wander. I quickly regained my resolve and pushed forward, this last leg would be the most treacherous."
-
-/datum/heretic_knowledge/rust_regen/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
- user.AddElement(/datum/element/leeching_walk)
-
-/datum/heretic_knowledge/rust_regen/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
- user.RemoveElement(/datum/element/leeching_walk)
+ action_to_add = /datum/action/cooldown/mob_cooldown/charge/rust
+ cost = 2
+ is_final_knowledge = TRUE
/datum/heretic_knowledge/mark/rust_mark
name = "Mark of Rust"
@@ -90,13 +117,6 @@
When triggered, your victim will suffer heavy disgust and confusion. \
Allows you to rust reinforced walls and floors as well as plasteel."
gain_text = "The Blacksmith looks away. To a place lost long ago. \"Rusted Hills help those in dire need... at a cost.\""
- mark_type = /datum/status_effect/eldritch/rust
-
-/datum/heretic_knowledge/mark/rust_mark/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
- . = ..()
- our_heretic.increase_rust_strength()
-
-/datum/heretic_knowledge/knowledge_ritual/rust
/datum/heretic_knowledge/spell/rust_construction
name = "Rust Construction"
@@ -105,7 +125,21 @@
gain_text = "Images of foreign and ominous structures began to dance in my mind. Covered head to toe in thick rust, \
they no longer looked man made. Or perhaps they never were in the first place."
action_to_add = /datum/action/cooldown/spell/pointed/rust_construction
- cost = 1
+ cost = 2
+
+/datum/heretic_knowledge/armor/rust
+ desc = "Allows you to transmute a table (or a suit), a mask and any trash item to create a Salvaged Remains. \
+ Has extra armor, tackle resistance and syringe immunity while standing on rust. \
+ Acts as a focus while hooded."
+ gain_text = "From beneath warped scrap, the Blacksmith pulls forth an ancient fabric. \
+ \"Whatever this once stood for is lost. So now, we give it new purpose.\""
+ result_atoms = list(/obj/item/clothing/suit/hooded/cultrobes/eldritch/rust)
+ research_tree_icon_state = "rust_armor"
+ required_atoms = list(
+ list(/obj/structure/table, /obj/item/clothing/suit) = 1,
+ /obj/item/clothing/mask = 1,
+ /obj/item/trash = 1,
+ )
/datum/heretic_knowledge/spell/area_conversion
name = "Aggressive Spread"
@@ -113,13 +147,9 @@
Already rusted surfaces are destroyed \ Also improves the rusting abilities of non rust-heretics."
gain_text = "All wise men know well not to visit the Rusted Hills... Yet the Blacksmith's tale was inspiring."
action_to_add = /datum/action/cooldown/spell/aoe/rust_conversion
- cost = 1
+ cost = 2
research_tree_icon_frame = 5
-/datum/heretic_knowledge/spell/area_conversion/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
- . = ..()
- our_heretic.increase_rust_strength(TRUE)
-
/datum/heretic_knowledge/blade_upgrade/rust
name = "Toxic Blade"
desc = "Your Rusty Blade now disgusts enemies on attack \ Allows you to rust Titanium and Plastitanium.."
@@ -128,10 +158,6 @@
research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
research_tree_icon_state = "blade_upgrade_rust"
-/datum/heretic_knowledge/blade_upgrade/rust/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
- . = ..()
- our_heretic.increase_rust_strength()
-
/datum/heretic_knowledge/blade_upgrade/rust/do_melee_effects(mob/living/source, mob/living/target, obj/item/melee/sickly_blade/blade)
if(source == target || !isliving(target))
return
@@ -139,6 +165,7 @@
/datum/heretic_knowledge/spell/area_conversion/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
. = ..()
+
/datum/heretic_knowledge/spell/entropic_plume
name = "Entropic Plume"
desc = "Grants you Entropic Plume, a spell that releases a vexing wave of Rust. \
@@ -148,14 +175,8 @@
The Blacksmith was gone, and you hold their blade. Champions of hope, the Rustbringer is nigh!"
action_to_add = /datum/action/cooldown/spell/cone/staggered/entropic_plume
- cost = 1
-
-
-
-/datum/heretic_knowledge/spell/entropic_plume/on_gain(mob/user)
- . = ..()
- var/datum/antagonist/heretic/our_heretic = GET_HERETIC(user)
- our_heretic.increase_rust_strength(TRUE)
+ cost = 2
+ drafting_tier = 5
/datum/heretic_knowledge/ultimate/rust_final
name = "Rustbringer's Oath"
@@ -234,7 +255,7 @@
for (var/iterator in 1 to greatest_dist)
if(!turfs_to_transform["[iterator]"])
continue
- addtimer(CALLBACK(src, PROC_REF(transform_area), turfs_to_transform["[iterator]"]), (5 SECONDS) * iterator)
+ addtimer(CALLBACK(src, PROC_REF(transform_area), turfs_to_transform["[iterator]"]), (2 SECONDS) * iterator)
/datum/heretic_knowledge/ultimate/rust_final/proc/transform_area(list/turfs)
turfs = shuffle(turfs)
@@ -287,7 +308,7 @@
return
var/need_mob_update = FALSE
- var/base_heal_amt = 2.5 * DELTA_WORLD_TIME(SSmobs)
+ var/base_heal_amt = 1 * DELTA_WORLD_TIME(SSmobs)
need_mob_update += source.adjustBruteLoss(-base_heal_amt, updating_health = FALSE)
need_mob_update += source.adjustFireLoss(-base_heal_amt, updating_health = FALSE)
need_mob_update += source.adjustToxLoss(-base_heal_amt, updating_health = FALSE, forced = TRUE)
diff --git a/code/modules/antagonists/heretic/knowledge/general_side.dm b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/reroll_targets.dm
similarity index 94%
rename from code/modules/antagonists/heretic/knowledge/general_side.dm
rename to code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/reroll_targets.dm
index e2f445cb8a6..b2c2b5b6e13 100644
--- a/code/modules/antagonists/heretic/knowledge/general_side.dm
+++ b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/reroll_targets.dm
@@ -1,4 +1,6 @@
-// Some general sidepath options.
+/*!
+ * Contains the reroll targets knowledge perk
+ */
/datum/heretic_knowledge/reroll_targets
name = "The Relentless Heartbeat"
@@ -13,9 +15,10 @@
cost = 1
research_tree_icon_path = 'icons/mob/actions/actions_animal.dmi'
research_tree_icon_state = "gaze"
+ is_shop_only = TRUE
+ drafting_tier = 2
/datum/heretic_knowledge/reroll_targets/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc)
-
var/datum/antagonist/heretic/heretic_datum = GET_HERETIC(user)
// Check first if they have a Living Heart. If it's missing, we should
// throw a fail to show the heretic that there's no point in rerolling
diff --git a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm
index b9fcc0fe2ef..25843e9f8c3 100644
--- a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm
+++ b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm
@@ -211,11 +211,11 @@
// Heads give 3 points, cultists give 1 point (and a special reward), normal sacrifices give 2 points.
heretic_datum.total_sacrifices++
if((sac_job_flag & JOB_HEAD_OF_STAFF))
- heretic_datum.knowledge_points += 3
+ heretic_datum.adjust_knowledge_points(3)
heretic_datum.high_value_sacrifices++
feedback += " graciously"
if(cultist_datum)
- heretic_datum.knowledge_points += 1
+ heretic_datum.adjust_knowledge_points(1)
grant_reward(user, sacrifice, loc)
// easier to read
var/rewards_given = heretic_datum.rewards_given
@@ -234,7 +234,7 @@
to_chat(user, non_flavor_warning)
return
else
- heretic_datum.knowledge_points += 2
+ heretic_datum.adjust_knowledge_points(2)
to_chat(user, span_hypnophrase("[feedback]."))
if(!begin_sacrifice(sacrifice))
@@ -499,7 +499,7 @@
sac_target.clear_mood_event("shadow_realm")
if(IS_HERETIC(sac_target))
var/datum/antagonist/heretic/victim_heretic = sac_target.mind?.has_antag_datum(/datum/antagonist/heretic)
- victim_heretic.knowledge_points -= 3
+ victim_heretic.adjust_knowledge_points(-3)
// Wherever we end up, we sure as hell won't be able to explain
sac_target.adjust_timed_status_effect(40 SECONDS, /datum/status_effect/speech/slurring/heretic)
diff --git a/code/modules/antagonists/heretic/knowledge/side_ash_moon.dm b/code/modules/antagonists/heretic/knowledge/side_ash_moon.dm
deleted file mode 100644
index 35b5ae382d1..00000000000
--- a/code/modules/antagonists/heretic/knowledge/side_ash_moon.dm
+++ /dev/null
@@ -1,60 +0,0 @@
-/datum/heretic_knowledge_tree_column/ash_to_moon
- neighbour_type_left = /datum/heretic_knowledge_tree_column/main/ash
- neighbour_type_right = /datum/heretic_knowledge_tree_column/main/moon
-
- route = PATH_SIDE
-
- tier1 = /datum/heretic_knowledge/medallion
- tier2 = /datum/heretic_knowledge/ether
- tier3 = /datum/heretic_knowledge/summon/ashy
-
-// Sidepaths for knowledge between Ash and Flesh.
-/datum/heretic_knowledge/medallion
- name = "Ashen Eyes"
- desc = "Allows you to transmute a pair of eyes, a candle, and a glass shard into an Eldritch Medallion. \
- The Eldritch Medallion grants you thermal vision while worn, and also functions as a focus."
- gain_text = "Piercing eyes guided them through the mundane. Neither darkness nor terror could stop them."
-
- required_atoms = list(
- /obj/item/organ/eyes = 1,
- /obj/item/shard = 1,
- /obj/item/flashlight/flare/candle = 1,
- )
- result_atoms = list(/obj/item/clothing/neck/eldritch_amulet)
- cost = 1
- research_tree_icon_path = 'icons/obj/antags/eldritch.dmi'
- research_tree_icon_state = "eye_medalion"
-
-/datum/heretic_knowledge/ether
- name = "Ether Of The Newborn"
- desc = "Transmutes a pool of vomit and a shard into a single use potion, drinking it will remove any sort of abnormality from your body including diseases, traumas and implants \
- on top of restoring it to full health, at the cost of losing consciousness for an entire minute."
- gain_text = "Vision and thought grow hazy as the fumes of this ichor swirl up to meet me. \
- Through the haze, I find myself staring back in relief, or something grossly resembling my visage. \
- It is this wretched thing that I consign to my fate, and whose own that I snatch through the haze of dreams. Fools that we are."
- required_atoms = list(
- /obj/item/shard = 1,
- /obj/effect/decal/cleanable/vomit = 1,
- )
- result_atoms = list(/obj/item/ether)
- cost = 1
- research_tree_icon_path = 'icons/obj/antags/eldritch.dmi'
- research_tree_icon_state = "poison_flask"
-
-/datum/heretic_knowledge/summon/ashy
- name = "Ashen Ritual"
- desc = "Allows you to transmute a head, a pile of ash, and a book to create an Ash Spirit. \
- Ash Spirits have a short range jaunt and the ability to cause bleeding in foes at range. \
- They also have the ability to create a ring of fire around themselves for a length of time."
- gain_text = "I combined my principle of hunger with my desire for destruction. The Marshal knew my name, and the Nightwatcher gazed on."
-
- required_atoms = list(
- /obj/effect/decal/cleanable/ash = 1,
- /obj/item/bodypart/head = 1,
- /obj/item/book = 1,
- )
- mob_to_summon = /mob/living/basic/heretic_summon/ash_spirit
- cost = 1
-
- poll_ignore_define = POLL_IGNORE_ASH_SPIRIT
-
diff --git a/code/modules/antagonists/heretic/knowledge/side_blade_rust.dm b/code/modules/antagonists/heretic/knowledge/side_blade_rust.dm
deleted file mode 100644
index 8f9a595859f..00000000000
--- a/code/modules/antagonists/heretic/knowledge/side_blade_rust.dm
+++ /dev/null
@@ -1,136 +0,0 @@
-/datum/heretic_knowledge_tree_column/blade_to_rust
- neighbour_type_left = /datum/heretic_knowledge_tree_column/main/blade
- neighbour_type_right = /datum/heretic_knowledge_tree_column/main/rust
-
- route = PATH_SIDE
-
- tier1 = /datum/heretic_knowledge/armor
- tier2 = list(/datum/heretic_knowledge/crucible, /datum/heretic_knowledge/rifle)
- tier3 = list(/datum/heretic_knowledge/spell/rust_charge, /datum/heretic_knowledge/greaves_of_the_prophet)
-
-// 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."
-
- 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
-
- research_tree_icon_path = 'icons/obj/clothing/suits/armor.dmi'
- research_tree_icon_state = "eldritch_armor"
- research_tree_icon_frame = 12
-
-
-/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 Crucible 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..."
-
- required_atoms = list(
- /obj/structure/reagent_dispensers/watertank = 1,
- /obj/structure/table = 1,
- )
- result_atoms = list(/obj/structure/destructible/eldritch_crucible)
- cost = 1
-
- research_tree_icon_path = 'icons/obj/antags/eldritch.dmi'
- research_tree_icon_state = "crucible"
-
-
-/datum/heretic_knowledge/rifle
- name = "Lionhunter's Rifle"
- desc = "Allows you to transmute a piece of wood, with hide \
- from any animal,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, albeit weak high-caliber munitions when fired from \
- close range or at inanimate objects. You can aim the rifle at distant foes, \
- causing the shot to mark your victim with your grasp and teleport you directly to them."
- gain_text = "I met an old man in an antique 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."
-
- required_atoms = list(
- /obj/item/stack/sheet/mineral/wood = 1,
- /obj/item/stack/sheet/animalhide = 1,
- /obj/item/camera = 1,
- )
- result_atoms = list(/obj/item/gun/ballistic/rifle/lionhunter)
- cost = 1
-
-
- research_tree_icon_path = 'icons/obj/weapons/guns/ballistic.dmi'
- research_tree_icon_state = "goldrevolver"
-
-/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 shells 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/ammo_casing = 3,
- )
- result_atoms = list(/obj/item/ammo_box/speedloader/strilka310/lionhunter)
- cost = 0
-
- research_tree_icon_path = 'icons/obj/weapons/guns/ammo.dmi'
- research_tree_icon_state = "310_strip"
-
- /// A list of calibers that the ritual will deny. Only ballistic calibers are allowed.
- var/static/list/caliber_blacklist = list(
- CALIBER_LASER,
- CALIBER_ENERGY,
- CALIBER_FOAM,
- CALIBER_ARROW,
- CALIBER_HARPOON,
- CALIBER_HOOK,
- )
-
-/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 caliber_blacklist))
- continue
-
- // Remove any casings in the caliber_blacklist 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
-
-/datum/heretic_knowledge/spell/rust_charge
- name = "Rust Charge"
- desc = "A charge that must be started on a rusted tile and will destroy any rusted objects you come into contact with, will deal high damage to others and rust around you during the charge."
- gain_text = "The hills sparkled now, as I neared them my mind began to wander. I quickly regained my resolve and pushed forward, this last leg would be the most treacherous."
-
- action_to_add = /datum/action/cooldown/mob_cooldown/charge/rust
- cost = 1
-
-/datum/heretic_knowledge/greaves_of_the_prophet
- name = "Greaves Of The Prophet"
- desc = "Allows you to combine a pair of Jackboots and 2 sheets of Titanium into a pair of Armored Greaves, they confer to the user fully immunity to slips."
- gain_text = " \
- Gristle churns into joint, a pop, and the fool twists a blackened foot from the \
- jaws of another. At their game for centuries, this mangled tree of limbs twists, \
- thrashing snares buried into snarling gums, seeking to shred the weight of grafted \
- neighbors. Weighed down by lacerated feet, this canopy of rancid idiots ever seeks \
- the undoing of its own bonds. I dread the thought of walking in their wake, but \
- I must press on all the same. Their rhythms keep the feud fresh with indifference \
- to barrier or border. Pulling more into their turmoil as they waltz."
- cost = 1
- required_atoms = list(
- /obj/item/clothing/shoes/jackboots = 1,
- /obj/item/stack/sheet/mineral/titanium = 2,
- )
- result_atoms = list(/obj/item/clothing/shoes/greaves_of_the_prophet)
- research_tree_icon_path = 'icons/obj/clothing/shoes.dmi'
- research_tree_icon_state = "hereticgreaves"
diff --git a/code/modules/antagonists/heretic/knowledge/side_cosmos_ash.dm b/code/modules/antagonists/heretic/knowledge/side_cosmos_ash.dm
deleted file mode 100644
index d15bb242989..00000000000
--- a/code/modules/antagonists/heretic/knowledge/side_cosmos_ash.dm
+++ /dev/null
@@ -1,62 +0,0 @@
-/datum/heretic_knowledge_tree_column/cosmic_to_ash
- neighbour_type_left = /datum/heretic_knowledge_tree_column/main/cosmic
- neighbour_type_right = /datum/heretic_knowledge_tree_column/main/ash
-
- route = PATH_SIDE
-
- tier1 = /datum/heretic_knowledge/summon/fire_shark
- tier2 = /datum/heretic_knowledge/spell/space_phase
- tier3 = /datum/heretic_knowledge/eldritch_coin
-
-
-// Sidepaths for knowledge between Cosmos and Ash.
-
-/datum/heretic_knowledge/summon/fire_shark
- name = "Scorching Shark"
- desc = "Allows you to transmute a pool of ash, a liver, and a sheet of plasma into a Fire Shark. \
- Fire Sharks are fast and strong in groups, but die quickly. They are also highly resistant against fire attacks. \
- Fire Sharks inject phlogiston into its victims and spawn plasma once they die."
- gain_text = "The cradle of the nebula was cold, but not dead. Light and heat flits even through the deepest darkness, and is hunted by its own predators."
-
- required_atoms = list(
- /obj/effect/decal/cleanable/ash = 1,
- /obj/item/organ/liver = 1,
- /obj/item/stack/sheet/mineral/plasma = 1,
- )
- mob_to_summon = /mob/living/basic/heretic_summon/fire_shark
- cost = 1
-
- poll_ignore_define = POLL_IGNORE_FIRE_SHARK
-
- research_tree_icon_dir = EAST
-
-/datum/heretic_knowledge/spell/space_phase
- name = "Space Phase"
- desc = "Grants you Space Phase, a spell that allows you to move freely through space. \
- You can only phase in and out when you are on a space or misc turf."
- gain_text = "You feel like your body can move through space as if you where dust."
-
- action_to_add = /datum/action/cooldown/spell/jaunt/space_crawl
- cost = 1
-
-
- research_tree_icon_frame = 6
-
-/datum/heretic_knowledge/eldritch_coin
- name = "Eldritch Coin"
- desc = "Allows you to transmute a sheet of plasma and a diamond to create an Eldritch Coin. \
- The coin will open or close nearby doors when landing on heads and toggle their bolts \
- when landing on tails. If you insert the coin into an airlock, it will be consumed \
- to fry its electronics, opening the airlock permanently unless bolted. "
- gain_text = "The Mansus is a place of all sorts of sins. But greed held a special role."
-
- required_atoms = list(
- /obj/item/stack/sheet/mineral/diamond = 1,
- /obj/item/stack/sheet/mineral/plasma = 1,
- )
- result_atoms = list(/obj/item/coin/eldritch)
- cost = 1
-
- research_tree_icon_path = 'icons/obj/economy.dmi'
- research_tree_icon_state = "coin_heretic"
-
diff --git a/code/modules/antagonists/heretic/knowledge/side_flesh_void.dm b/code/modules/antagonists/heretic/knowledge/side_flesh_void.dm
deleted file mode 100644
index bd7f1cf621c..00000000000
--- a/code/modules/antagonists/heretic/knowledge/side_flesh_void.dm
+++ /dev/null
@@ -1,63 +0,0 @@
-/datum/heretic_knowledge_tree_column/flesh_to_void
- neighbour_type_left = /datum/heretic_knowledge_tree_column/main/flesh
- neighbour_type_right = /datum/heretic_knowledge_tree_column/main/void
-
- route = PATH_SIDE
-
- tier1 = /datum/heretic_knowledge/void_cloak
- tier2 = /datum/heretic_knowledge/spell/blood_siphon
- tier3 = list(/datum/heretic_knowledge/spell/void_prison, /datum/heretic_knowledge/spell/cleave)
-
-// Sidepaths for knowledge between Flesh and Void.
-
-/datum/heretic_knowledge/void_cloak
- name = "Void Cloak"
- desc = "Allows you to transmute a glass shard, a bedsheet, and any outer clothing item (such as armor or a suit jacket) \
- to create a Void Cloak. While the hood is down, the cloak functions as a focus, \
- and while the hood is up, the cloak is completely invisible. It also provide decent armor and \
- has pockets which can hold one of your blades, various ritual components (such as organs), and small heretical trinkets."
- gain_text = "The Owl is the keeper of things that are not quite in practice, but in theory are. Many things are."
-
- required_atoms = list(
- /obj/item/shard = 1,
- /obj/item/clothing/suit = 1,
- /obj/item/bedsheet = 1,
- )
- result_atoms = list(/obj/item/clothing/suit/hooded/cultrobes/void)
- cost = 1
-
- research_tree_icon_path = 'icons/obj/clothing/suits/armor.dmi'
- research_tree_icon_state = "void_cloak"
-
-/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."
-
- action_to_add = /datum/action/cooldown/spell/pointed/blood_siphon
- cost = 1
-
-/datum/heretic_knowledge/spell/void_prison
- name = "Void Prison"
- desc = "Grants you Void Prison, a spell that places your victim into ball, making them unable to do anything or speak. \
- Applies void chill afterwards."
- gain_text = "At first, I see myself, waltzing along a snow-laden street. \
- I try to yell, grab hold of this fool and tell them to run. \
- But the only welts made are on my own beating fist. \
- My smiling face turns to regard me, reflecting back in glassy eyes the empty path I have been lead down."
-
- action_to_add = /datum/action/cooldown/spell/pointed/void_prison
- cost = 1
-
-/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."
-
- action_to_add = /datum/action/cooldown/spell/pointed/cleave
- cost = 1
-
-
diff --git a/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_four.dm b/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_four.dm
new file mode 100644
index 00000000000..6fb91467717
--- /dev/null
+++ b/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_four.dm
@@ -0,0 +1,121 @@
+/*!
+ * Tier 4 knowledge: Combat related knowledge
+ */
+
+/datum/heretic_knowledge/spell/space_phase
+ name = "Space Phase"
+ desc = "Grants you Space Phase, a spell that allows you to move freely through space. \
+ You can only phase in and out when you are on a space or misc turf."
+ gain_text = "You feel like your body can move through space as if you where dust."
+
+ action_to_add = /datum/action/cooldown/spell/jaunt/space_crawl
+ cost = 2
+ research_tree_icon_frame = 6
+ drafting_tier = 4
+
+/datum/heretic_knowledge/unfathomable_curio
+ name = "Unfathomable Curio"
+ desc = "Allows you to transmute 3 rods, lungs and any belt into an Unfathomable Curio, \
+ a belt that can hold blades and items for rituals. Whilst worn it will also \
+ veil you, allowing you to take 5 hits without suffering damage, this veil will recharge very slowly \
+ outside of combat."
+ gain_text = "The mansus holds many a curio, some are not meant for the mortal eye."
+
+ required_atoms = list(
+ /obj/item/organ/lungs = 1,
+ /obj/item/stack/rods = 3,
+ /obj/item/storage/belt = 1,
+ )
+ result_atoms = list(/obj/item/storage/belt/unfathomable_curio)
+ cost = 2
+ research_tree_icon_path = 'icons/obj/clothing/belts.dmi'
+ research_tree_icon_state = "unfathomable_curio"
+ drafting_tier = 4
+
+/datum/heretic_knowledge/rust_sower
+ name = "Rust Sower Grenade"
+ desc = "Allows you to combine a chemical grenade casing and some moldy food to conjure a cursed grenade filled with Eldritch Rust, upon detonating it releases a huge cloud that blinds organics, rusts affected turfs and obliterates Silicons and Mechs."
+ gain_text = "The choked vines of the Rusted Hills are burdened with such overripe fruits. It undoes the markers of progress, leaving a clean slate to work into new shapes."
+ required_atoms = list(
+ list(
+ /obj/item/food/breadslice/moldy,
+ /obj/item/food/badrecipe/moldy,
+ /obj/item/food/deadmouse/moldy,
+ /obj/item/food/pizzaslice/moldy,
+ /obj/item/food/boiledegg/rotten,
+ /obj/item/food/egg/rotten
+ ) = 1,
+ /obj/item/grenade/chem_grenade = 1
+ )
+ result_atoms = list(/obj/item/grenade/chem_grenade/rust_sower)
+ cost = 2
+ research_tree_icon_path = 'icons/obj/weapons/grenade.dmi'
+ research_tree_icon_state = "rustgrenade"
+ drafting_tier = 4
+
+/datum/heretic_knowledge/spell/crimson_cleave
+ name = "Crimson Cleave"
+ desc = "Grants you Crimson Cleave, a targeted spell which siphons health in a small AOE. Cleanses all wounds upon casting"
+ 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."
+ action_to_add = /datum/action/cooldown/spell/pointed/crimson_cleave
+ cost = 2
+ drafting_tier = 4
+
+/datum/heretic_knowledge/rifle
+ name = "Lionhunter's Rifle"
+ desc = "Allows you to transmute a piece of wood, with hide \
+ from any animal, 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, albeit weak high-caliber munitions when fired from \
+ close range or at inanimate objects. You can aim the rifle at distant foes, \
+ causing the shot to mark your victim with your grasp and teleport you directly to them."
+ gain_text = "I met an old man in an antique 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."
+ required_atoms = list(
+ /obj/item/stack/sheet/mineral/wood = 1,
+ /obj/item/stack/sheet/animalhide = 1,
+ /obj/item/camera = 1,
+ )
+ result_atoms = list(/obj/item/gun/ballistic/rifle/lionhunter)
+ cost = 2
+ research_tree_icon_path = 'icons/obj/weapons/guns/ballistic.dmi'
+ research_tree_icon_state = "goldrevolver"
+ drafting_tier = 2
+
+/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 shells 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/ammo_casing = 3,
+ )
+ result_atoms = list(/obj/item/ammo_box/speedloader/strilka310/lionhunter)
+ cost = 0
+ research_tree_icon_path = 'icons/obj/weapons/guns/ammo.dmi'
+ research_tree_icon_state = "310_strip"
+
+ /// A list of calibers that the ritual will deny. Only ballistic calibers are allowed.
+ var/static/list/caliber_blacklist = list(
+ CALIBER_LASER,
+ CALIBER_ENERGY,
+ CALIBER_FOAM,
+ CALIBER_ARROW,
+ CALIBER_HARPOON,
+ CALIBER_HOOK,
+ )
+
+/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 caliber_blacklist))
+ continue
+
+ // Remove any casings in the caliber_blacklist 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_knowledge/tier_one.dm b/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_one.dm
new file mode 100644
index 00000000000..e8a3f9b4d67
--- /dev/null
+++ b/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_one.dm
@@ -0,0 +1,102 @@
+/*!
+ * Tier 1 knowledge: Stealth and general utility
+ */
+
+/datum/heretic_knowledge/void_cloak
+ name = "Void Cloak"
+ desc = "Allows you to transmute a glass shard, a bedsheet, and any outer clothing item (such as armor or a suit jacket) \
+ to create a Void Cloak. While the hood is down, the cloak functions as a focus and protects you from space. \
+ While the hood is up, the cloak is completely invisible. It also provide decent armor and \
+ has pockets which can hold one of your blades, various ritual components (such as organs), and small heretical trinkets."
+ gain_text = "The Owl is the keeper of things that are not quite in practice, but in theory are. Many things are."
+ required_atoms = list(
+ /obj/item/shard = 1,
+ /obj/item/clothing/suit = 1,
+ /obj/item/bedsheet = 1,
+ )
+ result_atoms = list(/obj/item/clothing/suit/hooded/cultrobes/void)
+ cost = 1
+ research_tree_icon_path = 'icons/obj/clothing/suits/armor.dmi'
+ research_tree_icon_state = "void_cloak"
+ drafting_tier = 1
+
+/datum/heretic_knowledge/medallion
+ name = "Ashen Eyes"
+ desc = "Allows you to transmute a pair of eyes, a candle, and a glass shard into an Eldritch Medallion. \
+ The Eldritch Medallion grants you thermal vision while worn, and also functions as a focus."
+ gain_text = "Piercing eyes guided them through the mundane. Neither darkness nor terror could stop them."
+ required_atoms = list(
+ /obj/item/organ/eyes = 1,
+ /obj/item/shard = 1,
+ /obj/item/flashlight/flare/candle = 1,
+ )
+ result_atoms = list(/obj/item/clothing/neck/eldritch_amulet)
+ cost = 1
+ research_tree_icon_path = 'icons/obj/antags/eldritch.dmi'
+ research_tree_icon_state = "eye_medalion"
+ drafting_tier = 1
+
+/datum/heretic_knowledge/essence // AKA Eldritch Flask
+ name = "Priest's Ritual"
+ desc = "Allows you to transmute a tank of water and a glass shard into a Flask of Eldritch Essence. \
+ Eldritch Essence can be consumed for potent healing, or given to heathens for deadly poisoning."
+ gain_text = "This is an old recipe. The Owl whispered it to me. \
+ Created by the Priest - the Liquid that both was and is not."
+ required_atoms = list(
+ /obj/structure/reagent_dispensers/watertank = 1,
+ /obj/item/shard = 1,
+ )
+ result_atoms = list(/obj/item/reagent_containers/cup/beaker/eldritch)
+ cost = 1
+ research_tree_icon_path = 'icons/obj/antags/eldritch.dmi'
+ research_tree_icon_state = "eldritch_flask"
+ drafting_tier = 1
+
+/datum/heretic_knowledge/phylactery
+ name = "Phylactery of Damnation"
+ desc = "Allows you to transmute a sheet of glass and a poppy into a Phylactery that can instantly draw blood, even from long distances. \
+ Be warned, your target may still feel a prick."
+ gain_text = "A tincture twisted into the shape of a bloodsucker vermin. \
+ Whether it chose the shape for itself, or this is the humor of the sickened mind that conjured this vile implement into being is something best not pondered."
+ required_atoms = list(
+ /obj/item/stack/sheet/glass = 1,
+ /obj/item/food/grown/poppy = 1,
+ )
+ result_atoms = list(/obj/item/reagent_containers/cup/phylactery)
+ cost = 1
+ research_tree_icon_path = 'icons/obj/antags/eldritch.dmi'
+ research_tree_icon_state = "phylactery_2"
+ drafting_tier = 1
+
+/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 Crucible 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..."
+ required_atoms = list(
+ /obj/structure/reagent_dispensers/watertank = 1,
+ /obj/structure/table = 1,
+ )
+ result_atoms = list(/obj/structure/destructible/eldritch_crucible)
+ cost = 1
+ research_tree_icon_path = 'icons/obj/antags/eldritch.dmi'
+ research_tree_icon_state = "crucible"
+ drafting_tier = 1
+
+/datum/heretic_knowledge/eldritch_coin
+ name = "Eldritch Coin"
+ desc = "Allows you to transmute a sheet of plasma and a diamond to create an Eldritch Coin. \
+ The coin will open or close nearby doors when landing on heads and toggle their bolts \
+ when landing on tails. If you insert the coin into an airlock, it will be consumed \
+ to fry its electronics, opening the airlock permanently unless bolted. "
+ gain_text = "The Mansus is a place of all sorts of sins. But greed held a special role."
+ required_atoms = list(
+ /obj/item/stack/sheet/mineral/diamond = 1,
+ /obj/item/stack/sheet/mineral/plasma = 1,
+ )
+ result_atoms = list(/obj/item/coin/eldritch)
+ cost = 1
+ research_tree_icon_path = 'icons/obj/economy.dmi'
+ research_tree_icon_state = "coin_heretic"
+ drafting_tier = 1
diff --git a/code/modules/antagonists/heretic/knowledge/side_void_blade.dm b/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_three.dm
similarity index 63%
rename from code/modules/antagonists/heretic/knowledge/side_void_blade.dm
rename to code/modules/antagonists/heretic/knowledge/side_knowledge/tier_three.dm
index 86c0fd527ee..03b27b7965e 100644
--- a/code/modules/antagonists/heretic/knowledge/side_void_blade.dm
+++ b/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_three.dm
@@ -1,16 +1,58 @@
-// Sidepaths for knowledge between Void and Blade.
+/*!
+ * Tier 3 knowledge: Summons
+ */
-/datum/heretic_knowledge_tree_column/void_to_blade
- neighbour_type_left = /datum/heretic_knowledge_tree_column/main/void
- neighbour_type_right = /datum/heretic_knowledge_tree_column/main/blade
+/datum/heretic_knowledge/summon/rusty
+ name = "Rusted Ritual"
+ desc = "Allows you to transmute a pool of vomit, some cable coil, and 10 sheets of iron into a Rust Walker. \
+ Rust Walkers excel at spreading rust and are moderately strong in combat."
+ gain_text = "I combined my knowledge of creation with my desire for corruption. The Marshal knew my name, and the Rusted Hills echoed out."
+ required_atoms = list(
+ /obj/effect/decal/cleanable/vomit = 1,
+ /obj/item/stack/sheet/iron = 10,
+ /obj/item/stack/cable_coil = 15,
+ )
+ mob_to_summon = /mob/living/basic/heretic_summon/rust_walker
+ cost = 2
+ poll_ignore_define = POLL_IGNORE_RUST_SPIRIT
+ drafting_tier = 3
- route = PATH_SIDE
+/datum/heretic_knowledge/summon/maid_in_mirror
+ name = "Maid in the Mirror"
+ desc = "Allows you to transmute five sheets of glass, any suit, 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. \
+ Their attacks also apply a stack of void chill."
+ 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."
- tier1 = /datum/heretic_knowledge/limited_amount/risen_corpse
- tier2 = /datum/heretic_knowledge/rune_carver
- tier3 = /datum/heretic_knowledge/summon/maid_in_mirror
+ required_atoms = list(
+ /obj/item/stack/sheet/glass = 5,
+ /obj/item/clothing/suit = 1,
+ /obj/item/organ/lungs = 1,
+ )
+ cost = 2
+ mob_to_summon = /mob/living/basic/heretic_summon/maid_in_the_mirror
+ poll_ignore_define = POLL_IGNORE_MAID_IN_MIRROR
+ drafting_tier = 3
+/datum/heretic_knowledge/summon/ashy
+ name = "Ashen Ritual"
+ desc = "Allows you to transmute a Bonfire and a book to create an Ash Spirit. \
+ Ash Spirits have a short range jaunt and the ability to cause bleeding in foes at range. \
+ They also have the ability to create a ring of fire around themselves for a length of time. \
+ They have a low amount of health, but will passively recover given enough time to do so."
+ gain_text = "I combined my principle of hunger with my desire for destruction. The Marshal knew my name, and the Nightwatcher gazed on."
+ required_atoms = list(
+ /obj/effect/decal/cleanable/ash = 1,
+ /obj/item/book = 1,
+ /obj/structure/bonfire = 1,
+ )
+ mob_to_summon = /mob/living/basic/heretic_summon/ash_spirit
+ cost = 2
+
+ poll_ignore_define = POLL_IGNORE_ASH_SPIRIT
+ drafting_tier = 3
/// The max health given to Shattered Risen
#define RISEN_MAX_HEALTH 125
@@ -30,11 +72,10 @@
/obj/item/clothing/gloves/latex = 1,
)
limit = 1
- cost = 1
-
+ cost = 2
research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
research_tree_icon_state = "ghoul_shattered"
-
+ drafting_tier = 3
/datum/heretic_knowledge/limited_amount/risen_corpse/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc)
. = ..()
@@ -116,43 +157,22 @@
demolition_mod = 1.5
sharpness = SHARP_EDGED
-/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!"
+/datum/heretic_knowledge/summon/fire_shark
+ name = "Scorching Shark"
+ desc = "Allows you to transmute a pool of ash, a liver, and a sheet of plasma into a Fire Shark. \
+ Fire Sharks are fast and strong in groups, but die quickly. They are also highly resistant against fire attacks. \
+ Fire Sharks inject phlogiston into its victims and spawn plasma once they die."
+ gain_text = "The cradle of the nebula was cold, but not dead. Light and heat flits even through the deepest darkness, and is hunted by its own predators."
required_atoms = list(
- /obj/item/knife = 1,
- /obj/item/shard = 1,
- /obj/item/paper = 1,
+ /obj/effect/decal/cleanable/ash = 1,
+ /obj/item/organ/liver = 1,
+ /obj/item/stack/sheet/mineral/plasma = 1,
)
- result_atoms = list(/obj/item/melee/rune_carver)
- cost = 1
+ mob_to_summon = /mob/living/basic/heretic_summon/fire_shark
+ cost = 2
+ poll_ignore_define = POLL_IGNORE_FIRE_SHARK
- research_tree_icon_path = 'icons/obj/antags/eldritch.dmi'
- research_tree_icon_state = "rune_carver"
-
-/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. \
- However, they are weak to mortal gaze and take damage by being examined."
- 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."
-
- 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
-
- mob_to_summon = /mob/living/basic/heretic_summon/maid_in_the_mirror
- poll_ignore_define = POLL_IGNORE_MAID_IN_MIRROR
-
+ research_tree_icon_dir = EAST
+ drafting_tier = 3
diff --git a/code/modules/antagonists/heretic/knowledge/side_lock_moon.dm b/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_two.dm
similarity index 55%
rename from code/modules/antagonists/heretic/knowledge/side_lock_moon.dm
rename to code/modules/antagonists/heretic/knowledge/side_knowledge/tier_two.dm
index 890a1458f05..9f28a066585 100644
--- a/code/modules/antagonists/heretic/knowledge/side_lock_moon.dm
+++ b/code/modules/antagonists/heretic/knowledge/side_knowledge/tier_two.dm
@@ -1,44 +1,107 @@
-/datum/heretic_knowledge_tree_column/moon_to_lock
- neighbour_type_left = /datum/heretic_knowledge_tree_column/main/moon
- neighbour_type_right = /datum/heretic_knowledge_tree_column/main/lock
-
- route = PATH_SIDE
-
- tier1 = /datum/heretic_knowledge/spell/mind_gate
- tier2 = list(/datum/heretic_knowledge/unfathomable_curio, /datum/heretic_knowledge/painting)
- tier3 = /datum/heretic_knowledge/codex_morbus
-
-// Sidepaths for knowledge between Knock and Moon.
-
-/datum/heretic_knowledge/spell/mind_gate
- name = "Mind Gate"
- desc = "Grants you Mind Gate, a spell which inflicts hallucinations, \
- confusion, oxygen loss and brain damage to its target over 10 seconds.\
- The caster takes 20 brain damage per use."
- gain_text = "My mind swings open like a gate, and its insight will let me perceive the truth."
-
- action_to_add = /datum/action/cooldown/spell/pointed/mind_gate
- cost = 1
-
-/datum/heretic_knowledge/unfathomable_curio
- name = "Unfathomable Curio"
- desc = "Allows you to transmute 3 rods, lungs and any belt into an Unfathomable Curio, \
- a belt that can hold blades and items for rituals. Whilst worn it will also \
- veil you, allowing you to take 5 hits without suffering damage, this veil will recharge very slowly \
- outside of combat."
- gain_text = "The mansus holds many a curio, some are not meant for the mortal eye."
+/*!
+ * Tier 2 knowledge: Defensive tools and curses
+ */
+/**
+ * Codex Morbus, an upgrade to the base codex
+ * Functionally an upgraded version of the codex, but it also has the ability to cast curses by right clicking at a rune.
+ * Requires you to have the blood of your victim in your off-hand
+ */
+/datum/heretic_knowledge/codex_morbus
+ name = "Codex Morbus"
+ desc = "Allows you to to combine a codex cicatrix, and a body into a Codex Morbus. \
+ It draws runes and siphons essences a bit faster. \
+ Right Click on a rune to curse crewmembers, the target's blood is required in your off hand for a curse to take effect (Best combined with Phylactery Of Damnation)."
+ gain_text = "The spine of this leather-bound tome creaks with an eerily pained sigh. \
+ To ply page from place takes considerable effort, and I dare not linger on the suggestions the book makes for longer than necessary. \
+ It speaks of coming plagues, of waiting supplicants of dead and forgotten gods, and the undoing of mortal kind. \
+ It speaks of needles to peel the skin of the world back and leaving it to fester. And it speaks to me by name."
required_atoms = list(
- /obj/item/organ/lungs = 1,
- /obj/item/stack/rods = 3,
- /obj/item/storage/belt = 1,
+ /obj/item/codex_cicatrix = 1,
+ /mob/living/carbon/human = 1,
)
- result_atoms = list(/obj/item/storage/belt/unfathomable_curio)
- cost = 1
+ result_atoms = list(/obj/item/codex_cicatrix/morbus)
+ cost = 2
+ research_tree_icon_path = 'icons/obj/antags/eldritch.dmi'
+ research_tree_icon_state = "book_morbus"
+ drafting_tier = 2
- research_tree_icon_path = 'icons/obj/clothing/belts.dmi'
- research_tree_icon_state = "unfathomable_curio"
+/datum/heretic_knowledge/codex_morbus/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc)
+ . = ..()
+ var/mob/living/carbon/human/to_fuck_up = locate() in selected_atoms
+ for(var/_limb in to_fuck_up.bodyparts)
+ var/obj/item/bodypart/limb = _limb
+ limb.force_wound_upwards(/datum/wound/slash/flesh/critical)
+ for(var/obj/item/bodypart/limb as anything in to_fuck_up.bodyparts)
+ to_fuck_up.cause_wound_of_type_and_severity(WOUND_BLUNT, limb, WOUND_SEVERITY_CRITICAL)
+ return TRUE
+/datum/heretic_knowledge/greaves_of_the_prophet
+ name = "Greaves Of The Prophet"
+ desc = "Allows you to combine a pair of shoes and 2 sheets of titanium or silver into a pair of Armored Greaves, they confer to the user full immunity to slips."
+ gain_text = " \
+ Gristle churns into joint, a pop, and the fool twists a blackened foot from the \
+ jaws of another. At their game for centuries, this mangled tree of limbs twists, \
+ thrashing snares buried into snarling gums, seeking to shred the weight of grafted \
+ neighbors. Weighed down by lacerated feet, this canopy of rancid idiots ever seeks \
+ the undoing of its own bonds. I dread the thought of walking in their wake, but \
+ I must press on all the same. Their rhythms keep the feud fresh with indifference \
+ to barrier or border. Pulling more into their turmoil as they waltz."
+ required_atoms = list(
+ /obj/item/clothing/shoes = 1,
+ list(/obj/item/stack/sheet/mineral/titanium, /obj/item/stack/sheet/mineral/silver) = 2,
+ )
+ result_atoms = list(/obj/item/clothing/shoes/greaves_of_the_prophet)
+ cost = 2
+ research_tree_icon_path = 'icons/obj/clothing/shoes.dmi'
+ research_tree_icon_state = "hereticgreaves"
+ drafting_tier = 2
+
+/datum/heretic_knowledge/spell/opening_blast
+ name = "Wave Of Desperation"
+ desc = "Grants you Wave Of Desparation, a spell which can only be cast while restrained. \
+ It removes your restraints, repels and knocks down adjacent people, and applies the Mansus Grasp to everything nearby. \
+ However, you will fall unconscious a short time after casting this spell."
+ gain_text = "My shackles undone in dark fury, their feeble bindings crumble before my power."
+
+ action_to_add = /datum/action/cooldown/spell/aoe/wave_of_desperation
+ cost = 2
+ drafting_tier = 2
+
+/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!"
+ required_atoms = list(
+ /obj/item/knife = 1,
+ /obj/item/shard = 1,
+ /obj/item/paper = 1,
+ )
+ result_atoms = list(/obj/item/melee/rune_carver)
+ cost = 2
+ research_tree_icon_path = 'icons/obj/antags/eldritch.dmi'
+ research_tree_icon_state = "rune_carver"
+ drafting_tier = 2
+
+/datum/heretic_knowledge/ether
+ name = "Ether Of The Newborn"
+ desc = "Transmutes a pool of vomit and a shard into a single use potion, drinking it will remove any sort of abnormality from your body including diseases, traumas and implants \
+ on top of restoring it to full health, at the cost of losing consciousness for an entire minute."
+ gain_text = "Vision and thought grow hazy as the fumes of this ichor swirl up to meet me. \
+ Through the haze, I find myself staring back in relief, or something grossly resembling my visage. \
+ It is this wretched thing that I consign to my fate, and whose own that I snatch through the haze of dreams. Fools that we are."
+ required_atoms = list(
+ /obj/item/shard = 1,
+ /obj/effect/decal/cleanable/vomit = 1,
+ )
+ result_atoms = list(/obj/item/ether)
+ cost = 2
+ research_tree_icon_path = 'icons/obj/antags/eldritch.dmi'
+ research_tree_icon_state = "poison_flask"
+ drafting_tier = 2
/datum/heretic_knowledge/painting
name = "Unsealed Arts"
@@ -51,14 +114,12 @@
Master of the Rusted Mountain: Requires a piece of Trash. Curses non-heretics to rust the floor they walk on."
gain_text = "A wind of inspiration blows through me. Beyond the veil and past the gate great works exist, yet to be painted. \
They yearn for mortal eyes, so I shall give them an audience."
-
required_atoms = list(/obj/item/canvas = 1)
result_atoms = list(/obj/item/canvas)
- cost = 1
-
+ cost = 2
research_tree_icon_path = 'icons/obj/signs.dmi'
research_tree_icon_state = "eldritch_painting_weeping"
-
+ drafting_tier = 2
/datum/heretic_knowledge/painting/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc)
if(locate(/obj/item/organ/eyes) in atoms)
@@ -103,36 +164,3 @@
user.balloon_alert(user, "no additional atom present!")
return FALSE
-
-/**
- * Codex Morbus, an upgrade to the base codex
- * Functionally an upgraded version of the codex, but it also has the ability to cast curses by right clicking at a rune.
- * Requires you to have the blood of your victim in your off-hand
- */
-/datum/heretic_knowledge/codex_morbus
- name = "Codex Morbus"
- desc = "Allows you to to combine a codex cicatrix, and a body into a Codex Morbus. \
- It draws runes and siphons essences a bit faster. \
- Right Click on a rune to curse crewmembers, the target's blood is required in your off hand for a curse to take effect (Best combined with Phylactery Of Damnation)."
- gain_text = "The spine of this leather-bound tome creaks with an eerily pained sigh. \
- To ply page from place takes considerable effort, and I dare not linger on the suggestions the book makes for longer than necessary. \
- It speaks of coming plagues, of waiting supplicants of dead and forgotten gods, and the undoing of mortal kind. \
- It speaks of needles to peel the skin of the world back and leaving it to fester. And it speaks to me by name."
- required_atoms = list(
- /obj/item/codex_cicatrix = 1,
- /mob/living/carbon/human = 1,
- )
- result_atoms = list(/obj/item/codex_cicatrix/morbus)
- cost = 1
- research_tree_icon_path = 'icons/obj/antags/eldritch.dmi'
- research_tree_icon_state = "book_morbus"
-
-/datum/heretic_knowledge/codex_morbus/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc)
- . = ..()
- var/mob/living/carbon/human/to_fuck_up = locate() in selected_atoms
- for(var/_limb in to_fuck_up.bodyparts)
- var/obj/item/bodypart/limb = _limb
- limb.force_wound_upwards(/datum/wound/slash/flesh/critical)
- for(var/obj/item/bodypart/limb as anything in to_fuck_up.bodyparts)
- to_fuck_up.cause_wound_of_type_and_severity(WOUND_BLUNT, limb, WOUND_SEVERITY_CRITICAL)
- return TRUE
diff --git a/code/modules/antagonists/heretic/knowledge/side_lock_flesh.dm b/code/modules/antagonists/heretic/knowledge/side_lock_flesh.dm
deleted file mode 100644
index 11d31a64ebe..00000000000
--- a/code/modules/antagonists/heretic/knowledge/side_lock_flesh.dm
+++ /dev/null
@@ -1,50 +0,0 @@
-/datum/heretic_knowledge_tree_column/lock_to_flesh
- neighbour_type_left = /datum/heretic_knowledge_tree_column/main/lock
- neighbour_type_right = /datum/heretic_knowledge_tree_column/main/flesh
-
- route = PATH_SIDE
-
- tier1 = /datum/heretic_knowledge/phylactery
- tier2 = /datum/heretic_knowledge/spell/opening_blast
- tier3 = /datum/heretic_knowledge/spell/apetra_vulnera
-
-/**
- * Phylactery of Damnation
- */
-/datum/heretic_knowledge/phylactery
- name = "Phylactery of Damnation"
- desc = "Allows you to transmute a sheet of glass and a poppy into a Phylactery that can instantly draw blood, even from long distances. \
- Be warned, your target may still feel a prick."
- gain_text = "A tincture twisted into the shape of a bloodsucker vermin. \
- Whether it chose the shape for itself, or this is the humor of the sickened mind that conjured this vile implement into being is something best not pondered."
- required_atoms = list(
- /obj/item/stack/sheet/glass = 1,
- /obj/item/food/grown/poppy = 1,
- )
- result_atoms = list(/obj/item/reagent_containers/cup/phylactery)
- cost = 1
- research_tree_icon_path = 'icons/obj/antags/eldritch.dmi'
- research_tree_icon_state = "phylactery_2"
-
-// Sidepaths for knowledge between Knock and Flesh.
-/datum/heretic_knowledge/spell/opening_blast
- name = "Wave Of Desperation"
- desc = "Grants you Wave Of Desparation, a spell which can only be cast while restrained. \
- It removes your restraints, repels and knocks down adjacent people, and applies the Mansus Grasp to everything nearby. \
- However, you will fall unconscious a short time after casting this spell."
- gain_text = "My shackles undone in dark fury, their feeble bindings crumble before my power."
-
- action_to_add = /datum/action/cooldown/spell/aoe/wave_of_desperation
- cost = 1
-
-/datum/heretic_knowledge/spell/apetra_vulnera
- name = "Apetra Vulnera"
- desc = "Grants you Apetra Vulnera, a spell \
- which causes heavy bleeding on all bodyparts of the victim that have more than 15 brute damage. \
- Wounds a random limb if no limb is sufficiently damaged."
- gain_text = "Flesh opens, and blood spills. My master seeks sacrifice, and I shall appease."
-
- action_to_add = /datum/action/cooldown/spell/pointed/apetra_vulnera
- cost = 1
-
-
diff --git a/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm b/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm
deleted file mode 100644
index 998008e79ee..00000000000
--- a/code/modules/antagonists/heretic/knowledge/side_rust_cosmos.dm
+++ /dev/null
@@ -1,87 +0,0 @@
-/datum/heretic_knowledge_tree_column/rust_to_cosmic
- neighbour_type_left = /datum/heretic_knowledge_tree_column/main/rust
- neighbour_type_right = /datum/heretic_knowledge_tree_column/main/cosmic
-
- route = PATH_SIDE
-
- tier1 = /datum/heretic_knowledge/essence
- tier2 = list(/datum/heretic_knowledge/entropy_pulse, /datum/heretic_knowledge/rust_sower)
- tier3 = /datum/heretic_knowledge/summon/rusty
-
-
-// Sidepaths for knowledge between Rust and Cosmos.
-
-/datum/heretic_knowledge/essence
- name = "Priest's Ritual"
- desc = "Allows you to transmute a tank of water and a glass shard into a Flask of Eldritch Essence. \
- Eldritch Essence can be consumed for potent healing, or given to heathens for deadly poisoning."
- gain_text = "This is an old recipe. The Owl whispered it to me. \
- Created by the Priest - the Liquid that both was and is not."
-
- required_atoms = list(
- /obj/structure/reagent_dispensers/watertank = 1,
- /obj/item/shard = 1,
- )
- result_atoms = list(/obj/item/reagent_containers/cup/beaker/eldritch)
- cost = 1
-
-
- research_tree_icon_path = 'icons/obj/antags/eldritch.dmi'
- research_tree_icon_state = "eldritch_flask"
-
-/datum/heretic_knowledge/rust_sower
- name = "Rust Sower Grenade"
- desc = "Allws you to combine a chemical grenade casing and a liver to conjure a cursed grenade filled with Eldritch Rust, upon detonating it releases a huge cloud that blinds organics, rusts affected turfs and obliterates Silicons and Mechs."
- gain_text = "The choked vines of the Rusted Hills are burdened with such overripe fruits. It undoes the markers of progress, leaving a clean slate to work into new shapes."
- required_atoms = list(
- /obj/item/grenade/chem_grenade = 1,
- /obj/item/organ/liver = 1,
- )
- result_atoms = list(/obj/item/grenade/chem_grenade/rust_sower)
- cost = 1
- research_tree_icon_path = 'icons/obj/weapons/grenade.dmi'
- research_tree_icon_state = "rustgrenade"
-
-/datum/heretic_knowledge/entropy_pulse
- name = "Pulse of Entropy"
- desc = "Allows you to transmute 10 iron sheets and a garbage item to fill the surrounding vicinity of the rune with rust."
- gain_text = "Reality begins to whisper to me. To give it its entropic end."
- required_atoms = list(
- /obj/item/stack/sheet/iron = 10,
- /obj/item/trash = 1,
- )
- cost = 0
-
- research_tree_icon_path = 'icons/mob/actions/actions_ecult.dmi'
- research_tree_icon_state = "corrode"
- research_tree_icon_frame = 10
-
- var/rusting_range = 8
-
-/datum/heretic_knowledge/entropy_pulse/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc)
- for(var/turf/nearby_turf in view(rusting_range, loc))
- if(get_dist(nearby_turf, loc) <= 1) //tiles on rune should always be rusted
- nearby_turf.rust_heretic_act()
- //we exclude closed turf to avoid exposing cultist bases
- if(prob(10) || isclosedturf(nearby_turf))
- continue
- nearby_turf.rust_heretic_act()
- return TRUE
-
-/datum/heretic_knowledge/summon/rusty
- name = "Rusted Ritual"
- desc = "Allows you to transmute a pool of vomit, some cable coil, and 10 sheets of iron into a Rust Walker. \
- Rust Walkers excel at spreading rust and are moderately strong in combat."
- gain_text = "I combined my knowledge of creation with my desire for corruption. The Marshal knew my name, and the Rusted Hills echoed out."
-
- required_atoms = list(
- /obj/effect/decal/cleanable/vomit = 1,
- /obj/item/stack/sheet/iron = 10,
- /obj/item/stack/cable_coil = 15,
- )
- mob_to_summon = /mob/living/basic/heretic_summon/rust_walker
- cost = 1
-
- poll_ignore_define = POLL_IGNORE_RUST_SPIRIT
-
-
diff --git a/code/modules/antagonists/heretic/knowledge/starting_lore.dm b/code/modules/antagonists/heretic/knowledge/starting_lore.dm
index 907a7cb0f20..9d89cfd2ca4 100644
--- a/code/modules/antagonists/heretic/knowledge/starting_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/starting_lore.dm
@@ -211,7 +211,7 @@ GLOBAL_LIST_INIT(heretic_start_knowledge, initialize_starting_knowledge())
*/
/datum/heretic_knowledge/codex_cicatrix
name = "Codex Cicatrix"
- desc = "Allows you to transmute a book, any unique pen (anything but generic pens), and your pick from any carcass (animal or human), leather, or hide to create a Codex Cicatrix. \
+ desc = "Allows you to transmute a book, any pen, and your pick from any carcass (animal or human), leather, or hide to create a Codex Cicatrix. \
The Codex Cicatrix can be used when draining influences to gain additional knowledge, but comes at greater risk of being noticed. \
It can also be used to draw and remove transmutation runes easier, and as a spell focus in a pinch."
gain_text = "The occult leaves fragments of knowledge and power anywhere and everywhere. The Codex Cicatrix is one such example. \
@@ -221,7 +221,6 @@ GLOBAL_LIST_INIT(heretic_start_knowledge, initialize_starting_knowledge())
/obj/item/pen = 1,
list(/mob/living, /obj/item/stack/sheet/leather, /obj/item/stack/sheet/animalhide, /obj/item/food/deadmouse) = 1,
)
- banned_atom_types = list(/obj/item/pen)
result_atoms = list(/obj/item/codex_cicatrix)
cost = 1
is_starting_knowledge = TRUE
@@ -230,11 +229,6 @@ GLOBAL_LIST_INIT(heretic_start_knowledge, initialize_starting_knowledge())
research_tree_icon_path = 'icons/obj/antags/eldritch.dmi'
research_tree_icon_state = "book"
-/datum/heretic_knowledge/codex_cicatrix/parse_required_item(atom/item_path, number_of_things)
- if(item_path == /obj/item/pen)
- return "unique type of pen"
- return ..()
-
/datum/heretic_knowledge/codex_cicatrix/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc)
. = ..()
if(!.)
@@ -311,13 +305,15 @@ GLOBAL_LIST_INIT(heretic_start_knowledge, initialize_starting_knowledge())
. = TRUE
heretic_datum.feast_of_owls = TRUE
+ heretic_datum.update_heretic_aura()
user.set_temp_blindness(reward * 1 SECONDS)
user.AdjustParalyzed(reward * 1 SECONDS)
user.playsound_local(get_turf(user), 'sound/music/antag/heretic/heretic_gain_intense.ogg', 100, FALSE, pressure_affected = FALSE, use_reverb = FALSE)
for(var/i in 1 to reward)
user.emote("scream")
playsound(loc, 'sound/items/eatfood.ogg', 100, TRUE)
- heretic_datum.knowledge_points++
+ heretic_datum.adjust_knowledge_points(1)
+
to_chat(user, span_danger("You feel something invisible tearing away at your very essence!"))
user.do_jitter_animation()
sleep(1 SECONDS)
diff --git a/code/modules/antagonists/heretic/knowledge/void_lore.dm b/code/modules/antagonists/heretic/knowledge/void_lore.dm
index 0bb54d6d157..68d518f1c06 100644
--- a/code/modules/antagonists/heretic/knowledge/void_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/void_lore.dm
@@ -1,20 +1,50 @@
-
-/datum/heretic_knowledge_tree_column/main/void
- neighbour_type_left = /datum/heretic_knowledge_tree_column/flesh_to_void
- neighbour_type_right = /datum/heretic_knowledge_tree_column/void_to_blade
-
+/datum/heretic_knowledge_tree_column/void
route = PATH_VOID
ui_bgr = "node_void"
+ complexity = "Easy"
+ complexity_color = COLOR_GREEN
+ icon = list(
+ "icon" = 'icons/obj/weapons/khopesh.dmi',
+ "state" = "void_blade",
+ "frame" = 1,
+ "dir" = SOUTH,
+ "moving" = FALSE,
+ )
+ description = list(
+ "The Path of Void focuses on stealth, freezing cold, mobility and depressurization.",
+ "Pick this path if you enjoy being a highly mobile assassin who leaves their foes struggling to catch up.",
+ )
+ pros = list(
+ "Protection from the hazards of space.",
+ "Your spells apply a stacking debuff that chills and slows targets.",
+ "High amount of mobility spells.",
+ "Highly stealthy.",
+ )
+ cons = list(
+ "Though protected from space, you are not nearly as mobile in it as you are on foot.",
+ "Has a difficult time fighting opponents immune to cold effects.",
+ "Has a difficult time with silicon-based lifeforms.",
+ )
+ tips = list(
+ "Your Mansus Grasp allows you to mute your targets, making it ideal for silent assassinations (keep in mind that it won't short circuit their suit sensors, make sure you turn them off after you kill them). Yhe grasp also applies a mark that when triggered by the void blade will apply the maximum amount of stacks of void chill to your target, slowing them down to a crawl.",
+ "Void Cloak can be used to hide one of your blades and a Codex Cicatrix when the hood is down, while acting as a focus when it's up.",
+ "Void chill is a debuff applied by your spells, your grasp, your mark and your blade once you unlock the upgrade. Each stack slows your target movement speed by 10% and make them gradually colder, up to a maximum of 5 stacks.",
+ "At 5 stacks void chill will also prevent your target from heating up.",
+ "You are immune to low pressure and cold damage at the start of the shift. Upgrade your passive to level 2 to no longer need to breathe. Use this to your advantage.",
+ "Void prison can put a target in stasis for 10 seconds. Ideal if you are fighting multiple opponents and need to isolate one target at a time.",
+ "Void Conduit is your signature ability. It slowly destroys windows and airlocks around its area of effect. Use it to depressurize the station and expand your domain.",
+ )
start = /datum/heretic_knowledge/limited_amount/starting/base_void
- grasp = /datum/heretic_knowledge/void_grasp
- tier1 = /datum/heretic_knowledge/cold_snap
- mark = /datum/heretic_knowledge/mark/void_mark
- ritual_of_knowledge = /datum/heretic_knowledge/knowledge_ritual/void
- unique_ability = /datum/heretic_knowledge/spell/void_conduit
- tier2 = /datum/heretic_knowledge/spell/void_phase
+ knowledge_tier1 = /datum/heretic_knowledge/spell/void_phase
+ guaranteed_side_tier1 = /datum/heretic_knowledge/void_cloak
+ knowledge_tier2 = /datum/heretic_knowledge/spell/void_prison
+ guaranteed_side_tier2 = /datum/heretic_knowledge/ether
+ robes = /datum/heretic_knowledge/armor/void
+ knowledge_tier3 = /datum/heretic_knowledge/spell/void_pull
+ guaranteed_side_tier3 = /datum/heretic_knowledge/summon/maid_in_mirror
blade = /datum/heretic_knowledge/blade_upgrade/void
- tier3 = /datum/heretic_knowledge/spell/void_pull
+ knowledge_tier4 = /datum/heretic_knowledge/spell/void_conduit
ascension = /datum/heretic_knowledge/ultimate/void_final
/datum/heretic_knowledge/limited_amount/starting/base_void
@@ -28,6 +58,8 @@
result_atoms = list(/obj/item/melee/sickly_blade/void)
research_tree_icon_path = 'icons/obj/weapons/khopesh.dmi'
research_tree_icon_state = "void_blade"
+ mark_type = /datum/status_effect/eldritch/void
+ eldritch_passive = /datum/status_effect/heretic_passive/void
/datum/heretic_knowledge/limited_amount/starting/base_void/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc)
if(!isopenturf(loc))
@@ -41,23 +73,8 @@
return ..()
-/datum/heretic_knowledge/void_grasp
- name = "Grasp of Void"
- 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."
- cost = 1
- research_tree_icon_path = 'icons/ui_icons/antags/heretic/knowledge.dmi'
- research_tree_icon_state = "grasp_void"
-
-/datum/heretic_knowledge/void_grasp/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
- RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, PROC_REF(on_mansus_grasp))
-
-/datum/heretic_knowledge/void_grasp/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
- UnregisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK)
-
-/datum/heretic_knowledge/void_grasp/proc/on_mansus_grasp(mob/living/source, mob/living/target)
- SIGNAL_HANDLER
+/datum/heretic_knowledge/limited_amount/starting/base_void/on_mansus_grasp(mob/living/source, mob/living/target)
+ . = ..()
if(!iscarbon(target))
return
@@ -66,59 +83,6 @@
carbon_target.adjust_silence(10 SECONDS)
carbon_target.apply_status_effect(/datum/status_effect/void_chill, 2)
-/datum/heretic_knowledge/cold_snap
- name = "Aristocrat's Way"
- desc = "Grants you immunity to cold temperatures, and removes your need to breathe. \
- You can still take damage due to a lack of pressure."
- 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."
- cost = 1
- research_tree_icon_path = 'icons/effects/effects.dmi'
- research_tree_icon_state = "the_freezer"
-
- /// Traits we apply to become immune to the environment
- var/static/list/gain_traits = list(TRAIT_NO_SLIP_ICE, TRAIT_NO_SLIP_SLIDE)
-
-/datum/heretic_knowledge/cold_snap/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
- user.add_traits(list(TRAIT_NOBREATH, TRAIT_RESISTCOLD), type)
- RegisterSignal(user, COMSIG_LIVING_LIFE, PROC_REF(check_environment))
-
-/datum/heretic_knowledge/cold_snap/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
- user.remove_traits(list(TRAIT_RESISTCOLD, TRAIT_NOBREATH), type)
- UnregisterSignal(user, COMSIG_LIVING_LIFE)
-
-///Checks if our traits should be active
-/datum/heretic_knowledge/cold_snap/proc/check_environment(mob/living/user)
- SIGNAL_HANDLER
-
- var/datum/gas_mixture/environment = user.loc?.return_air()
- if(!isnull(environment))
- var/affected_temperature = environment.return_temperature()
- var/affected_pressure = environment.return_pressure()
- if(affected_temperature <= T0C || affected_pressure < ONE_ATMOSPHERE)
- user.add_traits(gain_traits, type)
- else
- user.remove_traits(gain_traits, type)
-
-/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, further silences the victim and swiftly lowers the temperature of their body and the air around them."
- 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."
- mark_type = /datum/status_effect/eldritch/void
-
-/datum/heretic_knowledge/knowledge_ritual/void
-
-/datum/heretic_knowledge/spell/void_conduit
- name = "Void Conduit"
- desc = "Grants you Void Conduit, a spell which summons a pulsing gate to the Void itself. Every pulse breaks windows and airlocks, while afflicting Heathens with an eldritch chill and shielding Heretics against low pressure."
- gain_text = "The hum in the still, cold air turns to a cacophonous rattle. \
- Over the noise, there is no distinction to the clattering of window panes and the yawning knowledge that ricochets through my skull. \
- The doors won't close. I can't keep the cold out now."
- action_to_add = /datum/action/cooldown/spell/conjure/void_conduit
- cost = 1
-
/datum/heretic_knowledge/spell/void_phase
name = "Void Phase"
desc = "Grants you Void Phase, a long range targeted teleport spell. \
@@ -126,9 +90,59 @@
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 blizzard."
action_to_add = /datum/action/cooldown/spell/pointed/void_phase
- cost = 1
+ cost = 2
research_tree_icon_frame = 7
+/datum/heretic_knowledge/spell/void_prison
+ name = "Void Prison"
+ desc = "Grants you Void Prison, a spell that places your victim into a ball, making them unable to do anything or speak. \
+ Applies void chill afterwards."
+ gain_text = "At first, I see myself, waltzing along a snow-laden street. \
+ I try to yell, grab hold of this fool and tell them to run. \
+ But the only welts made are on my own beating fist. \
+ My smiling face turns to regard me, reflecting back in glassy eyes the empty path I have been lead down."
+
+ action_to_add = /datum/action/cooldown/spell/pointed/void_prison
+ cost = 2
+ drafting_tier = 5
+
+/datum/heretic_knowledge/armor/void
+ name = "Hollow Weave"
+ desc = "Allows you to transmute a table (or a suit) and a mask in sub-zero temperatures to create a Hollow Weave, this armor will periodically nullify attacks and grant you a short stealth camoflage to reposition yourself. \
+ Acts as a focus while hooded."
+ gain_text = "Stepping through the cold air, I am shocked by a new sensation. \
+ Thousands of almost imperceivable threads cling to my form. \
+ I am left adrift with every step. \
+ Even as I hear the crunch of snow as I plant my foot to the ground, I feel nothing."
+ result_atoms = list(/obj/item/clothing/suit/hooded/cultrobes/eldritch/void)
+ research_tree_icon_state = "void_armor"
+ required_atoms = list(
+ list(/obj/structure/table, /obj/item/clothing/suit) = 1,
+ /obj/item/clothing/mask = 1,
+ )
+
+/datum/heretic_knowledge/armor/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
+
+ var/turf/open/our_turf = loc
+ if(our_turf.GetTemperature() > T0C)
+ loc.balloon_alert(user, "ritual failed, not cold enough!")
+ return FALSE
+
+ return ..()
+
+/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 themselves to me again. They tell me I am late. Their pull is immense, I cannot turn back."
+
+ action_to_add = /datum/action/cooldown/spell/aoe/void_pull
+ cost = 2
+ research_tree_icon_frame = 6
+
/datum/heretic_knowledge/blade_upgrade/void
name = "Seeking Blade"
desc = "Your blade now freezes enemies. Additionally, you can now attack distant marked targets with your Void Blade, teleporting directly next to them."
@@ -156,17 +170,15 @@
/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/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 themselves to me again. They tell me I am late. Their pull is immense, I cannot turn back."
-
- action_to_add = /datum/action/cooldown/spell/aoe/void_pull
- cost = 1
-
-
- research_tree_icon_frame = 6
+/datum/heretic_knowledge/spell/void_conduit
+ name = "Void Conduit"
+ desc = "Grants you Void Conduit, a spell which summons a pulsing gate to the Void itself. Every pulse breaks windows and airlocks, while afflicting Heathens with an eldritch chill and shielding Heretics against low pressure."
+ gain_text = "The hum in the still, cold air turns to a cacophonous rattle. \
+ Over the noise, there is no distinction to the clattering of window panes and the yawning knowledge that ricochets through my skull. \
+ The doors won't close. I can't keep the cold out now."
+ action_to_add = /datum/action/cooldown/spell/conjure/void_conduit
+ cost = 2
+ is_final_knowledge = TRUE
/datum/heretic_knowledge/ultimate/void_final
name = "Waltz at the End of Time"
diff --git a/code/modules/antagonists/heretic/magic/aggressive_spread.dm b/code/modules/antagonists/heretic/magic/aggressive_spread.dm
index 8f775f871f3..dbbdecc2cfb 100644
--- a/code/modules/antagonists/heretic/magic/aggressive_spread.dm
+++ b/code/modules/antagonists/heretic/magic/aggressive_spread.dm
@@ -16,6 +16,23 @@
aoe_radius = 2
+/datum/action/cooldown/spell/aoe/rust_conversion/before_cast(atom/cast_on)
+ . = ..()
+ if(. & SPELL_CANCEL_CAST)
+ return
+
+ return SPELL_NO_IMMEDIATE_COOLDOWN
+
+/datum/action/cooldown/spell/aoe/rust_conversion/after_cast(atom/cast_on)
+ . = ..()
+ var/datum/status_effect/heretic_passive/rust/rust_passive = owner.has_status_effect(/datum/status_effect/heretic_passive/rust)
+ if(!rust_passive)
+ StartCooldown(cooldown_time)
+ return
+
+ var/new_cooldown = 35 SECONDS - (rust_passive.passive_level * 5 SECONDS)
+ StartCooldown(new_cooldown)
+
/datum/action/cooldown/spell/aoe/rust_conversion/get_things_to_cast_on(atom/center)
var/list/things_to_convert = RANGE_TURFS(aoe_radius, center)
diff --git a/code/modules/antagonists/heretic/magic/ash_jaunt.dm b/code/modules/antagonists/heretic/magic/ash_jaunt.dm
index b64c5fdc9ae..f6c563c305d 100644
--- a/code/modules/antagonists/heretic/magic/ash_jaunt.dm
+++ b/code/modules/antagonists/heretic/magic/ash_jaunt.dm
@@ -1,6 +1,6 @@
/datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash
name = "Ashen Passage"
- desc = "A short range spell that allows you to pass unimpeded through walls."
+ desc = "A short range spell that allows you to pass unimpeded through walls, removing restraints if empowered."
background_icon_state = "bg_heretic"
overlay_icon_state = "bg_heretic_border"
button_icon = 'icons/mob/actions/actions_ecult.dmi'
@@ -20,6 +20,59 @@
jaunt_type = /obj/effect/dummy/phased_mob/spell_jaunt/red
jaunt_in_type = /obj/effect/temp_visual/dir_setting/ash_shift
jaunt_out_type = /obj/effect/temp_visual/dir_setting/ash_shift/out
+ /// If we are on fire while wearing ash robes, we can empower our next cast
+ var/empowered_cast = FALSE
+
+/datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash/Grant(mob/grant_to)
+ . = ..()
+ RegisterSignal(grant_to, COMSIG_FIRE_STACKS_UPDATED, PROC_REF(update_status_on_signal))
+
+/datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash/Remove(mob/remove_from)
+ . = ..()
+ UnregisterSignal(remove_from, COMSIG_FIRE_STACKS_UPDATED)
+
+/datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash/apply_button_overlay(atom/movable/screen/movable/action_button/current_button, force)
+ . = ..()
+ // Put an active border whenever our spell is able to be casted empowered
+ if(!ishuman(owner))
+ return
+ var/mob/living/carbon/human/human_owner = owner
+ if(!istype(human_owner.wear_suit, /obj/item/clothing/suit/hooded/cultrobes/eldritch/ash))
+ return
+ if(human_owner.fire_stacks <= 3)
+ return
+
+ current_button.cut_overlay(current_button.button_overlay)
+ current_button.button_overlay = mutable_appearance(icon = overlay_icon, icon_state = "bg_spell_border_active_green")
+ current_button.add_overlay(current_button.button_overlay)
+
+/datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash/cast(mob/living/cast_on)
+ if(!iscarbon(owner))
+ return ..()
+
+ // Wearing Ash heretic armor empowers your spells if you have over 3 fire stacks
+ if(!ishuman(owner))
+ return ..()
+ var/mob/living/carbon/human/human_owner = owner
+ if(human_owner.fire_stacks <= 3)
+ return ..()
+ if(!istype(human_owner.wear_suit, /obj/item/clothing/suit/hooded/cultrobes/eldritch/ash))
+ return ..()
+
+ empowered_cast = TRUE
+ human_owner.setStaminaLoss(0)
+ human_owner.SetAllImmobility(0)
+ var/mob/living/carbon/carbon_owner = owner
+ carbon_owner.uncuff()
+ var/obj/item/clothing/shoes/shoes = carbon_owner.shoes
+ if(istype(shoes) && shoes.tied == SHOES_KNOTTED)
+ shoes.adjust_laces(SHOES_TIED, carbon_owner)
+
+ return ..()
+
+/datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash/do_jaunt(mob/living/cast_on)
+ jaunt_duration = (empowered_cast ? 1.5 SECONDS : initial(jaunt_duration))
+ return ..()
/datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash/do_steam_effects()
return
diff --git a/code/modules/antagonists/heretic/magic/caretaker.dm b/code/modules/antagonists/heretic/magic/caretaker.dm
index 01a9970b204..f2f30747b46 100644
--- a/code/modules/antagonists/heretic/magic/caretaker.dm
+++ b/code/modules/antagonists/heretic/magic/caretaker.dm
@@ -30,7 +30,7 @@
return
for(var/mob/living/alive in orange(5, owner))
- if(alive.stat != DEAD && alive.client)
+ if(alive.stat != DEAD && alive.client && (owner in view(alive)))
owner.balloon_alert(owner, "other minds nearby!")
return . | SPELL_CANCEL_CAST
diff --git a/code/modules/antagonists/heretic/magic/cosmic_expansion.dm b/code/modules/antagonists/heretic/magic/cosmic_expansion.dm
index c191b00d32c..3d5c9e77939 100644
--- a/code/modules/antagonists/heretic/magic/cosmic_expansion.dm
+++ b/code/modules/antagonists/heretic/magic/cosmic_expansion.dm
@@ -1,6 +1,6 @@
/datum/action/cooldown/spell/conjure/cosmic_expansion
name = "Cosmic Expansion"
- desc = "This spell generates a 3x3 domain of cosmic fields. \
+ desc = "This spell generates a 5x5 domain of cosmic fields. \
Creatures up to 7 tiles away will also receive a star mark."
background_icon_state = "bg_heretic"
overlay_icon_state = "bg_heretic_border"
@@ -9,14 +9,14 @@
sound = 'sound/effects/magic/cosmic_expansion.ogg'
school = SCHOOL_FORBIDDEN
- cooldown_time = 45 SECONDS
+ cooldown_time = 15 SECONDS
invocation = "C'SM'S 'XP'ND!"
invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
- summon_amount = 9
- summon_radius = 1
+ summon_amount = 25
+ summon_radius = 2
summon_type = list(/obj/effect/forcefield/cosmic_field)
/// The range at which people will get marked with a star mark.
var/star_mark_range = 7
@@ -24,16 +24,18 @@
var/obj/effect/expansion_effect = /obj/effect/temp_visual/cosmic_domain
/// If the heretic is ascended or not
var/ascended = FALSE
+ /// Weakref to our summoner, only relevant if we are a stargazer. Prevents us from harming our master
+ var/datum/weakref/summoner
/datum/action/cooldown/spell/conjure/cosmic_expansion/cast(mob/living/cast_on)
new expansion_effect(get_turf(cast_on))
for(var/mob/living/nearby_mob in range(star_mark_range, cast_on))
- if(cast_on == nearby_mob || cast_on.buckled == nearby_mob)
+ if(cast_on == nearby_mob || cast_on.buckled == nearby_mob || IS_HERETIC_OR_MONSTER(nearby_mob) || cast_on == summoner?.resolve())
continue
nearby_mob.apply_status_effect(/datum/status_effect/star_mark, cast_on)
- if (ascended)
+ if (ascended && length(summon_type))
for(var/turf/cast_turf as anything in get_turfs(get_turf(cast_on)))
- new /obj/effect/forcefield/cosmic_field(cast_turf)
+ create_cosmic_field(cast_turf, owner, summon_type[1])
return ..()
/datum/action/cooldown/spell/conjure/cosmic_expansion/proc/get_turfs(turf/target_turf)
@@ -42,3 +44,17 @@
target_turfs += get_ranged_target_turf(target_turf, direction, 2)
target_turfs += get_ranged_target_turf(target_turf, direction, 3)
return target_turfs
+
+/datum/action/cooldown/spell/conjure/cosmic_expansion/post_summon(obj/effect/forcefield/cosmic_field/summoned_object, atom/cast_on)
+ . = ..()
+ if(isstargazer(owner))
+ summoned_object.slows_projectiles()
+ summoned_object.prevents_explosions()
+ return
+ var/datum/status_effect/heretic_passive/cosmic/cosmic_passive = owner.has_status_effect(/datum/status_effect/heretic_passive/cosmic)
+ if(!cosmic_passive)
+ return
+ if(cosmic_passive.passive_level > 1)
+ summoned_object.prevents_explosions()
+ if(cosmic_passive.passive_level > 2)
+ summoned_object.slows_projectiles()
diff --git a/code/modules/antagonists/heretic/magic/cosmic_runes.dm b/code/modules/antagonists/heretic/magic/cosmic_runes.dm
index af023e73a03..6d85604411f 100644
--- a/code/modules/antagonists/heretic/magic/cosmic_runes.dm
+++ b/code/modules/antagonists/heretic/magic/cosmic_runes.dm
@@ -70,6 +70,13 @@
silicon_image.override = TRUE
add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/silicons, "cosmic", silicon_image)
ADD_TRAIT(src, TRAIT_MOPABLE, INNATE_TRAIT)
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
+ COMSIG_ATOM_EXITED = PROC_REF(on_exited)
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+ for(var/mob/living/mobs in get_turf(src))
+ RegisterSignal(mobs, COMSIG_ATOM_ATTACK_HAND, PROC_REF(on_attack_self))
/obj/effect/cosmic_rune/attack_paw(mob/living/user, list/modifiers)
return attack_hand(user, modifiers)
@@ -92,10 +99,32 @@
return
invoke(user)
+/obj/effect/cosmic_rune/proc/on_entered(datum/source, atom/movable/arrived)
+ SIGNAL_HANDLER
+ if(!isliving(arrived))
+ return
+ RegisterSignal(arrived, COMSIG_ATOM_ATTACK_HAND, PROC_REF(on_attack_self))
+
+/// If something clicks on themselves while on top of the rune, we instead will act as if they clicked on the rune instead
+/obj/effect/cosmic_rune/proc/on_attack_self(datum/source, mob/living/user)
+ SIGNAL_HANDLER
+ if(source == user)
+ INVOKE_ASYNC(src, TYPE_PROC_REF(/atom, attack_hand), user)
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+
+/obj/effect/cosmic_rune/proc/on_exited(datum/source, exiter)
+ SIGNAL_HANDLER
+ UnregisterSignal(exiter, COMSIG_ATOM_ATTACK_HAND)
+
/// For invoking the rune
/obj/effect/cosmic_rune/proc/invoke(mob/living/user)
var/obj/effect/cosmic_rune/linked_rune_resolved = linked_rune?.resolve()
new rune_effect(get_turf(src))
+ var/atom/pulled_thing
+ if(IS_HERETIC(user))
+ if(user.pulling)
+ pulled_thing = user.pulling
+ do_teleport(user.pulling, get_turf(linked_rune_resolved), no_effects = TRUE, channel = TELEPORT_CHANNEL_MAGIC)
do_teleport(
user,
get_turf(linked_rune_resolved),
@@ -104,9 +133,13 @@
asoundin = 'sound/effects/magic/cosmic_energy.ogg',
asoundout = 'sound/effects/magic/cosmic_energy.ogg',
)
+ if(pulled_thing) // Regrab after the teleports are done
+ user.start_pulling(pulled_thing)
for(var/mob/living/person_on_rune in get_turf(src))
if(person_on_rune.has_status_effect(/datum/status_effect/star_mark))
do_teleport(person_on_rune, get_turf(linked_rune_resolved), no_effects = TRUE, channel = TELEPORT_CHANNEL_MAGIC)
+ if(!IS_HERETIC(user))
+ user.apply_status_effect(/datum/status_effect/star_mark)
new rune_effect(get_turf(linked_rune_resolved))
/// For if someone failed to invoke the rune
diff --git a/code/modules/antagonists/heretic/magic/crimson_cleave.dm b/code/modules/antagonists/heretic/magic/crimson_cleave.dm
new file mode 100644
index 00000000000..411409ee933
--- /dev/null
+++ b/code/modules/antagonists/heretic/magic/crimson_cleave.dm
@@ -0,0 +1,65 @@
+/datum/action/cooldown/spell/pointed/crimson_cleave
+ name = "Crimson Cleave"
+ desc = "A targeted spell that heals you while damaging the enemy. \
+ It cleanses you of all wounds as well."
+ background_icon_state = "bg_heretic"
+ overlay_icon_state = "bg_heretic_border"
+ button_icon = 'icons/mob/actions/actions_ecult.dmi'
+ button_icon_state = "blood_siphon"
+ ranged_mousepointer = 'icons/effects/mouse_pointers/throw_target.dmi'
+
+ school = SCHOOL_FORBIDDEN
+ cooldown_time = 30 SECONDS
+
+ invocation = "FL'MS O'ET'RN'ITY."
+ invocation_type = INVOCATION_WHISPER
+ spell_requirements = NONE
+
+ cast_range = 5
+
+ /// The radius of the cleave effect
+ var/cleave_radius = 1
+ /// What type of wound we apply
+ var/wound_type = /datum/wound/slash/flesh/critical/cleave
+
+/datum/action/cooldown/spell/pointed/crimson_cleave/can_cast_spell(feedback = TRUE)
+ return ..() && isliving(owner)
+
+/datum/action/cooldown/spell/pointed/crimson_cleave/is_valid_target(atom/cast_on)
+ return ..() && isliving(cast_on)
+
+/datum/action/cooldown/spell/pointed/crimson_cleave/cast(atom/cast_on)
+ . = ..()
+ if(iscarbon(owner))
+ var/mob/living/carbon/carbon_owner = owner
+ for(var/obj/item/bodypart/limbs as anything in carbon_owner.bodyparts)
+ for(var/datum/wound/iter_wound as anything in limbs.wounds)
+ iter_wound.remove_wound()
+
+ var/mob/living/living_owner = owner
+ for(var/mob/living/carbon/human/victim in range(cleave_radius, cast_on))
+ if(victim == owner || IS_HERETIC_OR_MONSTER(victim))
+ continue
+ if(victim.can_block_magic(antimagic_flags))
+ victim.visible_message(
+ span_danger("[victim]'s flashes in a firey glow, but repels the blaze!"),
+ span_danger("Your body begins to flash a firey glow, but you are protected!!")
+ )
+ continue
+
+ victim.visible_message(
+ span_danger("[victim]'s veins are shredded from within as an unholy blaze erupts from [victim.p_their()] blood!"),
+ span_danger("Your veins burst from within and unholy flame erupts from your blood!")
+ )
+
+ victim.apply_damage(15, BRUTE, wound_bonus = CANT_WOUND)
+ living_owner.adjustBruteLoss(-15)
+
+ if(victim.blood_volume)
+ victim.blood_volume -= 15
+ if(living_owner.blood_volume && living_owner.blood_volume < (BLOOD_VOLUME_MAXIMUM - 50))
+ living_owner.blood_volume += 15
+
+ new /obj/effect/temp_visual/cleave(get_turf(victim))
+
+ return TRUE
diff --git a/code/modules/antagonists/heretic/magic/fire_blast.dm b/code/modules/antagonists/heretic/magic/fire_blast.dm
index 104b4697cd2..873f131c28f 100644
--- a/code/modules/antagonists/heretic/magic/fire_blast.dm
+++ b/code/modules/antagonists/heretic/magic/fire_blast.dm
@@ -15,12 +15,53 @@
invocation = "V'LC'N!"
invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
- channel_time = 5 SECONDS
- target_radius = 5
+ channel_time = 2.5 SECONDS
+ target_radius = 7
max_beam_bounces = 4
/// How long the beam visual lasts, also used to determine time between jumps
var/beam_duration = 2 SECONDS
+ /// If our spell is empowered, it will have added effects
+ var/empowered_cast = FALSE
+
+/datum/action/cooldown/spell/charged/beam/fire_blast/Grant(mob/grant_to)
+ . = ..()
+ RegisterSignal(grant_to, COMSIG_FIRE_STACKS_UPDATED, PROC_REF(update_status_on_signal))
+
+/datum/action/cooldown/spell/charged/beam/fire_blast/Remove(mob/remove_from)
+ . = ..()
+ UnregisterSignal(remove_from, COMSIG_FIRE_STACKS_UPDATED)
+
+/datum/action/cooldown/spell/charged/beam/fire_blast/apply_button_overlay(atom/movable/screen/movable/action_button/current_button, force)
+ . = ..()
+ // Put an active border whenever our spell is able to be casted empowered
+ if(!ishuman(owner))
+ return
+ var/mob/living/carbon/human/human_owner = owner
+ if(!istype(human_owner.wear_suit, /obj/item/clothing/suit/hooded/cultrobes/eldritch/ash))
+ return
+ if(human_owner.fire_stacks <= 3)
+ return
+
+ current_button.cut_overlay(current_button.button_overlay)
+ current_button.button_overlay = mutable_appearance(icon = overlay_icon, icon_state = "bg_spell_border_active_green")
+ current_button.add_overlay(current_button.button_overlay)
+
+/datum/action/cooldown/spell/charged/beam/fire_blast/before_cast(atom/cast_on)
+ empowered_cast = FALSE
+ channel_time = initial(channel_time)
+ // Wearing Ash heretic armor empowers your spells if you have over 3 fire stacks
+ if(!ishuman(owner))
+ return ..()
+ var/mob/living/carbon/human/human_owner = owner
+ if(human_owner.fire_stacks <= 3)
+ return ..()
+ if(!istype(human_owner.wear_suit, /obj/item/clothing/suit/hooded/cultrobes/eldritch/ash))
+ return ..()
+ empowered_cast = TRUE
+ channel_time = 0.1 SECONDS
+ human_owner.extinguish_mob()
+ return ..()
/datum/action/cooldown/spell/charged/beam/fire_blast/cast(atom/cast_on)
var/mob/living/caster = get_caster_from_target(cast_on)
@@ -29,7 +70,7 @@
caster.apply_status_effect(/datum/status_effect/fire_blasted, beam_duration, -2)
return ..()
-/datum/action/cooldown/spell/charged/beam/fire_blast/send_beam(atom/origin, mob/living/carbon/to_beam, bounces = 4)
+/datum/action/cooldown/spell/charged/beam/fire_blast/send_beam(atom/origin, mob/living/carbon/to_beam, bounces = max_beam_bounces)
// Send a beam from the origin to the hit mob
origin.Beam(to_beam, icon_state = "solar_beam", time = beam_duration, beam_type = /obj/effect/ebeam/reacting/fire)
@@ -46,7 +87,7 @@
// Otherwise, if unblocked apply the damage and set them up
else
to_beam.apply_damage(20, BURN, wound_bonus = 5)
- to_beam.adjust_fire_stacks(3)
+ to_beam.adjust_fire_stacks(empowered_cast ? 6 : 3)
to_beam.ignite_mob()
// Apply the fire blast status effect to show they got blasted
to_beam.apply_status_effect(/datum/status_effect/fire_blasted, beam_duration * 0.5)
@@ -66,7 +107,7 @@
continue
nearby_living.Knockdown(0.8 SECONDS)
nearby_living.apply_damage(15, BURN, wound_bonus = 5)
- nearby_living.adjust_fire_stacks(2)
+ nearby_living.adjust_fire_stacks(empowered_cast ? 4 : 2)
nearby_living.ignite_mob()
/// Timer callback to continue the chain, calling send_fire_bream recursively.
diff --git a/code/modules/antagonists/heretic/magic/furious_steel.dm b/code/modules/antagonists/heretic/magic/furious_steel.dm
index af9691a01d6..237fd52ec02 100644
--- a/code/modules/antagonists/heretic/magic/furious_steel.dm
+++ b/code/modules/antagonists/heretic/magic/furious_steel.dm
@@ -10,7 +10,7 @@
sound = 'sound/items/weapons/guillotine.ogg'
school = SCHOOL_FORBIDDEN
- cooldown_time = 60 SECONDS
+ cooldown_time = 30 SECONDS
invocation = "F'LSH'NG S'LV'R!"
invocation_type = INVOCATION_SHOUT
diff --git a/code/modules/antagonists/heretic/magic/madness_touch.dm b/code/modules/antagonists/heretic/magic/madness_touch.dm
index a5d075f3a14..09743c20bc4 100644
--- a/code/modules/antagonists/heretic/magic/madness_touch.dm
+++ b/code/modules/antagonists/heretic/magic/madness_touch.dm
@@ -11,7 +11,7 @@
cooldown_time = 15 SECONDS
invocation_type = INVOCATION_NONE
spell_requirements = NONE
- antimagic_flags = MAGIC_RESISTANCE|MAGIC_RESISTANCE_MIND
+ antimagic_flags = MAGIC_RESISTANCE_MOON
/datum/action/cooldown/spell/touch/mad_touch/is_valid_target(atom/cast_on)
if(!ishuman(cast_on))
diff --git a/code/modules/antagonists/heretic/magic/mind_gate.dm b/code/modules/antagonists/heretic/magic/mind_gate.dm
index 74a04eba396..81215979468 100644
--- a/code/modules/antagonists/heretic/magic/mind_gate.dm
+++ b/code/modules/antagonists/heretic/magic/mind_gate.dm
@@ -1,7 +1,8 @@
/datum/action/cooldown/spell/pointed/mind_gate
name = "Mind Gate"
desc = "Deals you 20 brain damage and the target suffers a hallucination, \
- is left confused for 10 seconds, and suffers oxygen loss and brain damage."
+ is left confused for 10 seconds, and suffers oxygen loss and brain damage. \
+ It also blinds, mutes and deafens your target, if their sanity is low enough, they will be knocked down as well."
background_icon_state = "bg_heretic"
overlay_icon_state = "bg_heretic_border"
button_icon = 'icons/mob/actions/actions_ecult.dmi'
@@ -17,6 +18,7 @@
cast_range = 6
active_msg = "You prepare to open your mind..."
+ antimagic_flags = MAGIC_RESISTANCE_MOON
/datum/action/cooldown/spell/pointed/mind_gate/can_cast_spell(feedback = TRUE)
return ..() && isliving(owner)
@@ -31,11 +33,38 @@
to_chat(owner, span_warning("Their mind doesn't swing open, but neither does yours."))
return FALSE
+ var/mob/living/living_owner = owner
+ living_owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, 20, 140)
+
cast_on.adjust_confusion(10 SECONDS)
cast_on.adjustOxyLoss(30)
cast_on.cause_hallucination(get_random_valid_hallucination_subtype(/datum/hallucination/body), "Mind gate, cast by [owner]")
cast_on.cause_hallucination(/datum/hallucination/delusion/preset/heretic/gate, "Caused by mindgate")
cast_on.adjustOrganLoss(ORGAN_SLOT_BRAIN, 30)
- var/mob/living/living_owner = owner
- living_owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, 20, 140)
+ /// The duration of these effects are based on sanity, mainly for flavor but also to make it a weaker alpha strike
+ var/maximum_duration = 15 SECONDS
+ var/mind_gate_duration = ((SANITY_MAXIMUM - cast_on.mob_mood.sanity) / (SANITY_MAXIMUM - SANITY_INSANE)) * maximum_duration
+ to_chat(cast_on, span_warning("Your eyes cry out in pain, your ears bleed and your lips seal! THE MOON SMILES UPON YOU!"))
+ cast_on.adjust_temp_blindness(mind_gate_duration + 1 SECONDS)
+ cast_on.set_eye_blur_if_lower(mind_gate_duration + 2 SECONDS)
+
+ var/obj/item/organ/ears/ears = cast_on.get_organ_slot(ORGAN_SLOT_EARS)
+ //adjustEarDamage takes deafness duration parameter in one unit per two seconds, instead of the normal time, so we divide by two seconds
+ ears?.adjustEarDamage(0, (mind_gate_duration + 1 SECONDS) / (2 SECONDS))
+
+ cast_on.adjust_silence(mind_gate_duration + 1 SECONDS)
+ cast_on.add_mood_event("moon_smile", /datum/mood_event/moon_smile)
+
+ // Only knocksdown if the target has a low enough sanity
+ if(cast_on.mob_mood.sanity < 40)
+ cast_on.AdjustKnockdown(2 SECONDS)
+ //Lowers sanity
+ cast_on.mob_mood.adjust_sanity(-20)
+
+ //If our moon heretic has their level 3 passive, we channel the amulet effect
+ var/datum/status_effect/heretic_passive/moon/our_passive = living_owner.has_status_effect(/datum/status_effect/heretic_passive/moon)
+ if(our_passive?.amulet)
+ our_passive.amulet.channel_amulet(owner, cast_on)
+
+ return TRUE
diff --git a/code/modules/antagonists/heretic/magic/mirror_walk.dm b/code/modules/antagonists/heretic/magic/mirror_walk.dm
index 93642c7f324..436d3877950 100644
--- a/code/modules/antagonists/heretic/magic/mirror_walk.dm
+++ b/code/modules/antagonists/heretic/magic/mirror_walk.dm
@@ -2,7 +2,8 @@
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."
+ such as windows, mirrors, and reflective walls or equipment. \
+ You will slowly heal damage while in this form."
background_icon_state = "bg_heretic"
overlay_icon_state = "bg_heretic_border"
button_icon = 'icons/mob/actions/actions_minor_antag.dmi'
@@ -156,3 +157,14 @@
/obj/effect/dummy/phased_mob/mirror_walk
name = "reflection"
+
+/obj/effect/dummy/phased_mob/mirror_walk/Initialize(mapload, atom/movable/jaunter)
+ . = ..()
+ START_PROCESSING(SSobj, src)
+
+/obj/effect/dummy/phased_mob/mirror_walk/process(seconds_per_tick)
+ if(!isliving(jaunter))
+ STOP_PROCESSING(SSobj, src)
+ return ..()
+ var/mob/living/living_jaunter = jaunter
+ living_jaunter.heal_overall_damage(5 * seconds_per_tick)
diff --git a/code/modules/antagonists/heretic/magic/moon_parade.dm b/code/modules/antagonists/heretic/magic/moon_parade.dm
index 48df0ab58df..97f4eb864ba 100644
--- a/code/modules/antagonists/heretic/magic/moon_parade.dm
+++ b/code/modules/antagonists/heretic/magic/moon_parade.dm
@@ -19,7 +19,7 @@
deactive_msg = "You stop the music and halt the parade... for now."
cast_range = 12
projectile_type = /obj/projectile/moon_parade
-
+ antimagic_flags = MAGIC_RESISTANCE_MOON
/obj/projectile/moon_parade
name = "Lunar parade"
@@ -63,7 +63,7 @@
return PROJECTILE_PIERCE_PHASE
// Anti-magic destroys the projectile for consistency and counterplay
- if(victim.can_block_magic(MAGIC_RESISTANCE|MAGIC_RESISTANCE_MIND))
+ if(victim.can_block_magic(MAGIC_RESISTANCE_MOON))
visible_message(span_warning("The parade hits [victim] and a sudden wave of clarity comes over you!"))
return PROJECTILE_DELETE_WITHOUT_HITTING
@@ -76,31 +76,22 @@
var/mob/living/victim = target
- if(!(victim in mobs_hit))
- RegisterSignal(victim, COMSIG_MOB_CLIENT_PRE_LIVING_MOVE, PROC_REF(moon_block_move))
- RegisterSignal(victim, COMSIG_QDELETING, PROC_REF(clear_mob))
- victim.AddComponent(/datum/component/leash, src, distance = 1)
- victim.balloon_alert(victim, "you feel unable to move away from the parade!")
- mobs_hit += victim
+ if(!was_hit_already(victim))
+ victim.apply_status_effect(/datum/status_effect/moon_parade, src)
+ mobs_hit += WEAKREF(victim)
victim.add_mood_event("Moon Insanity", /datum/mood_event/moon_insanity)
victim.cause_hallucination(/datum/hallucination/delusion/preset/moon, name)
victim.mob_mood.adjust_sanity(-20)
+/obj/projectile/moon_parade/proc/was_hit_already(mob/living/victim)
+ for(var/datum/weakref/ref as anything in mobs_hit)
+ var/mob/living/hit_victim = ref.resolve()
+ if(hit_victim == victim)
+ return TRUE
+ return FALSE
+
/obj/projectile/moon_parade/Destroy()
- for(var/mob/living/leftover_mob as anything in mobs_hit)
- clear_mob(leftover_mob)
- mobs_hit.Cut() // You never know
+ mobs_hit.Cut()
soundloop.stop()
return ..()
-
-// Blocks movement in order to make it appear like the character is transfixed to the projectile and wandering after it
-// Coded this way because its a simple way to hold the illusion compared to other methods
-/obj/projectile/moon_parade/proc/moon_block_move(datum/source)
- SIGNAL_HANDLER
- return COMSIG_MOB_CLIENT_BLOCK_PRE_LIVING_MOVE
-
-/obj/projectile/moon_parade/proc/clear_mob(datum/source)
- SIGNAL_HANDLER
- UnregisterSignal(source, list(COMSIG_MOB_CLIENT_PRE_LIVING_MOVE, COMSIG_QDELETING))
- mobs_hit -= source
diff --git a/code/modules/antagonists/heretic/magic/moon_ringleader.dm b/code/modules/antagonists/heretic/magic/moon_ringleader.dm
index 130212f9455..a12cb14240d 100644
--- a/code/modules/antagonists/heretic/magic/moon_ringleader.dm
+++ b/code/modules/antagonists/heretic/magic/moon_ringleader.dm
@@ -1,8 +1,7 @@
/datum/action/cooldown/spell/aoe/moon_ringleader
name = "Ringleaders Rise"
- desc = "Big AoE spell that deals brain damage and causes hallucinations to everyone in the AoE. \
- The worse their sanity, the stronger this spell becomes. \
- If their sanity is low enough, they even snap and go insane, and the spell then further halves their sanity."
+ desc = "Big AoE spell that summons copies of you. \
+ If any copies are attacked, they cause brain damage, sanity damage, and will briefly stun everyone nearby."
background_icon_state = "bg_heretic"
overlay_icon_state = "bg_heretic_border"
button_icon = 'icons/mob/actions/actions_ecult.dmi'
@@ -11,7 +10,7 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 1 MINUTES
- antimagic_flags = MAGIC_RESISTANCE|MAGIC_RESISTANCE_MIND
+ antimagic_flags = MAGIC_RESISTANCE_MOON
invocation = "R'S 'E!"
invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
@@ -22,6 +21,7 @@
/datum/action/cooldown/spell/aoe/moon_ringleader/cast(mob/living/caster)
new moon_effect(get_turf(caster))
+ caster.faction |= "ringleader([REF(caster)])"
return ..()
/datum/action/cooldown/spell/aoe/moon_ringleader/get_things_to_cast_on(atom/center, radius_override)
@@ -30,10 +30,10 @@
for(var/mob/living/carbon/nearby_mob in o_range)
if(nearby_mob.stat == DEAD)
continue
- if(!nearby_mob.mob_mood)
- continue
if(IS_HERETIC_OR_MONSTER(nearby_mob))
continue
+ if(issilicon(nearby_mob))
+ continue
if(nearby_mob.can_block_magic(antimagic_flags))
continue
@@ -42,17 +42,44 @@
return stuff
/datum/action/cooldown/spell/aoe/moon_ringleader/cast_on_thing_in_aoe(mob/living/carbon/victim, mob/living/caster)
- var/victim_sanity = victim.mob_mood.sanity
+ var/mob/living/simple_animal/hostile/illusion/fake_clone = new(pick(RANGE_TURFS(2, victim)))
+ fake_clone.faction = caster.faction.Copy()
+ fake_clone.Copy_Parent(caster, 30 SECONDS, caster.health, 1, 0, "shove_mode")
+ fake_clone.GiveTarget(victim)
+ fake_clone.AddElement(/datum/element/relay_attackers)
+ RegisterSignal(fake_clone, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(on_attacked))
- victim.adjustOrganLoss(ORGAN_SLOT_BRAIN, 100 - victim_sanity, 160)
- for(var/i in 1 to round((120 - victim_sanity) / 10))
- victim.cause_hallucination(get_random_valid_hallucination_subtype(/datum/hallucination/body), name)
- if(victim_sanity < 15)
- victim.apply_status_effect(/datum/status_effect/moon_converted)
- caster.log_message("made [victim] insane.", LOG_GAME)
- victim.log_message("was driven insane by [caster]")
- victim.mob_mood.adjust_sanity(victim_sanity * -0.5)
+/// Used by Ringleaders Rise, illusions created by this spell will explode when they are interacted with
+/datum/action/cooldown/spell/aoe/moon_ringleader/proc/on_attacked(mob/victim, atom/attacker)
+ SIGNAL_HANDLER
+ if(isliving(attacker))
+ var/mob/living/living_attacker = attacker
+ if(IS_HERETIC_OR_MONSTER(living_attacker)) // Heretics cant smack these guys to trigger their effects
+ return
+ playsound(victim, 'sound/items/party_horn.ogg', 30)
+ new /obj/effect/decal/cleanable/confetti(get_turf(victim))
+ for(var/mob/living/mob in range(3, victim))
+ if(IS_HERETIC_OR_MONSTER(mob))
+ continue
+ if(mob.can_block_magic(antimagic_flags))
+ continue
+
+ //If our moon heretic has their level 3 passive, we channel the amulet effect
+ var/mob/living/simple_animal/hostile/illusion/fake_clone = victim
+ var/mob/living/living_owner = fake_clone.parent_mob_ref.resolve()
+ if(!living_owner)
+ continue
+ var/datum/status_effect/heretic_passive/moon/our_passive = living_owner.has_status_effect(/datum/status_effect/heretic_passive/moon)
+ // We channel the amulet before the "spell effects" so that people don't get converted after 1 clone goes off
+ our_passive?.amulet?.channel_amulet(living_owner, mob)
+
+ mob.AdjustStun(1 SECONDS)
+ mob.AdjustKnockdown(1 SECONDS)
+ mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 50, 150)
+ mob.mob_mood?.adjust_sanity(-50)
+
+ qdel(victim)
/obj/effect/temp_visual/moon_ringleader
icon = 'icons/effects/eldritch.dmi'
diff --git a/code/modules/antagonists/heretic/magic/moon_smile.dm b/code/modules/antagonists/heretic/magic/moon_smile.dm
deleted file mode 100644
index fd98dc0d841..00000000000
--- a/code/modules/antagonists/heretic/magic/moon_smile.dm
+++ /dev/null
@@ -1,55 +0,0 @@
-/datum/action/cooldown/spell/pointed/moon_smile
- name = "Smile of the moon"
- desc = "Lets you turn the gaze of the moon on someone \
- temporarily blinding, muting, deafening and knocking down a single target if their sanity is low enough."
- background_icon_state = "bg_heretic"
- overlay_icon_state = "bg_heretic_border"
- button_icon = 'icons/mob/actions/actions_ecult.dmi'
- button_icon_state = "moon_smile"
- ranged_mousepointer = 'icons/effects/mouse_pointers/moon_target.dmi'
-
- sound = 'sound/effects/magic/blind.ogg'
- school = SCHOOL_FORBIDDEN
- cooldown_time = 20 SECONDS
- antimagic_flags = MAGIC_RESISTANCE|MAGIC_RESISTANCE_MIND
- invocation = "M'N S'M'LE!"
- invocation_type = INVOCATION_SHOUT
- spell_requirements = NONE
- cast_range = 6
-
- active_msg = "You prepare to let them see the true face..."
-
-/datum/action/cooldown/spell/pointed/moon_smile/can_cast_spell(feedback = TRUE)
- return ..() && isliving(owner)
-
-/datum/action/cooldown/spell/pointed/moon_smile/is_valid_target(atom/cast_on)
- return ..() && ishuman(cast_on)
-
-/datum/action/cooldown/spell/pointed/moon_smile/cast(mob/living/carbon/human/cast_on)
- . = ..()
- /// The duration of these effects are based on sanity, mainly for flavor but also to make it a weaker alpha strike
- var/maximum_duration = 15 SECONDS
- var/moon_smile_duration = ((SANITY_MAXIMUM - cast_on.mob_mood.sanity) / (SANITY_MAXIMUM - SANITY_INSANE)) * maximum_duration
- if(cast_on.can_block_magic(antimagic_flags))
- to_chat(cast_on, span_notice("The moon turns, its smile no longer set on you."))
- to_chat(owner, span_warning("The moon does not smile upon them."))
- return FALSE
-
- playsound(cast_on, 'sound/effects/hallucinations/i_see_you1.ogg', 50, 1)
- to_chat(cast_on, span_warning("Your eyes cry out in pain, your ears bleed and your lips seal! THE MOON SMILES UPON YOU!"))
- cast_on.adjust_temp_blindness(moon_smile_duration + 1 SECONDS)
- cast_on.set_eye_blur_if_lower(moon_smile_duration + 2 SECONDS)
-
- var/obj/item/organ/ears/ears = cast_on.get_organ_slot(ORGAN_SLOT_EARS)
- //adjustEarDamage takes deafness duration parameter in one unit per two seconds, instead of the normal time, so we divide by two seconds
- ears?.adjustEarDamage(0, (moon_smile_duration + 1 SECONDS) / (2 SECONDS))
-
- cast_on.adjust_silence(moon_smile_duration + 1 SECONDS)
- cast_on.add_mood_event("moon_smile", /datum/mood_event/moon_smile)
-
- // Only knocksdown if the target has a low enough sanity
- if(cast_on.mob_mood.sanity < 40)
- cast_on.AdjustKnockdown(2 SECONDS)
- //Lowers sanity
- cast_on.mob_mood.adjust_sanity(-20)
- return TRUE
diff --git a/code/modules/antagonists/heretic/magic/nightwatcher_rebirth.dm b/code/modules/antagonists/heretic/magic/nightwatcher_rebirth.dm
index 6b156e01653..a71d45b25ae 100644
--- a/code/modules/antagonists/heretic/magic/nightwatcher_rebirth.dm
+++ b/code/modules/antagonists/heretic/magic/nightwatcher_rebirth.dm
@@ -10,28 +10,38 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 1 MINUTES
+ aoe_radius = 14
invocation = "GL'RY T' TH' N'GHT'W'TCH'ER."
invocation_type = INVOCATION_WHISPER
spell_requirements = SPELL_REQUIRES_HUMAN
+ /// Tracks how many victims the spell has chosen, lowers the cooldown for each target
+ var/victims_counter
+
+/datum/action/cooldown/spell/aoe/fiery_rebirth/before_cast(atom/cast_on)
+ . = ..()
+ if(. & SPELL_CANCEL_CAST)
+ return
+
+ return SPELL_NO_IMMEDIATE_COOLDOWN
/datum/action/cooldown/spell/aoe/fiery_rebirth/cast(mob/living/carbon/human/cast_on)
cast_on.extinguish_mob()
return ..()
/datum/action/cooldown/spell/aoe/fiery_rebirth/get_things_to_cast_on(atom/center)
+ victims_counter = 0
var/list/things = list()
for(var/mob/living/carbon/nearby_mob in range(aoe_radius, center))
if(nearby_mob == owner || nearby_mob == center)
continue
- if(!nearby_mob.mind || !nearby_mob.client)
- continue
if(IS_HERETIC_OR_MONSTER(nearby_mob))
continue
if(nearby_mob.stat == DEAD || !nearby_mob.on_fire)
continue
things += nearby_mob
+ victims_counter++
return things
@@ -44,17 +54,26 @@
victim.investigate_log("has been executed by fiery rebirth.", INVESTIGATE_DEATHS)
victim.death()
victim.apply_damage(20, BURN)
+ victim.extinguish_mob()
// Heal the caster for every victim damaged
var/need_mob_update = FALSE
need_mob_update += caster.adjustBruteLoss(-10, updating_health = FALSE)
need_mob_update += caster.adjustFireLoss(-10, updating_health = FALSE)
- need_mob_update += caster.adjustToxLoss(-10, updating_health = FALSE)
+ need_mob_update += caster.adjustToxLoss(-10, updating_health = FALSE, forced = TRUE)
need_mob_update += caster.adjustOxyLoss(-10, updating_health = FALSE)
need_mob_update += caster.adjustStaminaLoss(-10, updating_stamina = FALSE)
if(need_mob_update)
caster.updatehealth()
+/datum/action/cooldown/spell/aoe/fiery_rebirth/after_cast(atom/cast_on)
+ . = ..()
+ if(!victims_counter)
+ StartCooldown(cooldown_time)
+ return
+ var/new_cooldown = cooldown_time - victims_counter * 10 SECONDS
+ StartCooldown(max(9 SECONDS, new_cooldown)) // Hard capped so an ascended heretic doesn't get to freely spam
+
/obj/effect/temp_visual/eldritch_smoke
icon = 'icons/effects/eldritch.dmi'
icon_state = "smoke"
diff --git a/code/modules/antagonists/heretic/magic/rust_charge.dm b/code/modules/antagonists/heretic/magic/rust_charge.dm
index 56054bd56fd..506ef1949b4 100644
--- a/code/modules/antagonists/heretic/magic/rust_charge.dm
+++ b/code/modules/antagonists/heretic/magic/rust_charge.dm
@@ -5,36 +5,43 @@
will deal high damage to others and rust around you during the charge. \
As it is the rust that empowers you with this ability, no focus is needed."
charge_distance = 10
- charge_damage = 50
+ charge_damage = 25
cooldown_time = 45 SECONDS
+ charge_past = 0
/datum/action/cooldown/mob_cooldown/charge/rust/Activate(atom/target_atom)
var/turf/open/start_turf = get_turf(owner)
if(!istype(start_turf) || !HAS_TRAIT(start_turf, TRAIT_RUSTY))
return FALSE
StartCooldown(135 SECONDS, 135 SECONDS)
+ if(ishuman(owner))
+ var/mob/living/carbon/human/human_owner = owner
+ human_owner.physiology.damage_resistance += 100
+ RegisterSignal(owner, COMSIG_FINISHED_CHARGE, PROC_REF(affect_aoe))
charge_sequence(owner, target_atom, charge_delay, charge_past)
StartCooldown()
return TRUE
+
/datum/action/cooldown/mob_cooldown/charge/rust/on_move(atom/source, atom/new_loc, atom/target)
var/turf/victim = get_turf(owner)
if(!actively_moving)
return COMPONENT_MOVABLE_BLOCK_PRE_MOVE
new /obj/effect/temp_visual/decoy/fading(source.loc, source)
INVOKE_ASYNC(src, PROC_REF(DestroySurroundings), source)
- victim.rust_heretic_act()
- for(var/dir in GLOB.cardinals)
+ var/mob/living/living_owner = owner
+ living_owner.do_rust_heretic_act(victim)
+ for(var/dir in GLOB.alldirs)
var/turf/nearby_turf = get_step(victim, dir)
if(istype(nearby_turf))
- nearby_turf.rust_heretic_act()
+ living_owner.do_rust_heretic_act(nearby_turf)
/datum/action/cooldown/mob_cooldown/charge/rust/DestroySurroundings(atom/movable/charger)
if(!destroy_objects)
return
- for(var/dir in GLOB.cardinals)
+ for(var/dir in GLOB.alldirs)
var/turf/source = get_turf(owner)
var/turf/closed/next_turf = get_step(charger, dir)
- if(!istype(source) || !istype(next_turf) || !HAS_TRAIT(source, TRAIT_RUSTY) || !HAS_TRAIT(next_turf, TRAIT_RUSTY))
+ if(!istype(source) || !istype(next_turf))
continue
SSexplosions.medturf += next_turf
@@ -49,3 +56,15 @@
INVOKE_ASYNC(src, PROC_REF(DestroySurroundings), source)
try_hit_target(source, target, charge_damage)
+
+/datum/action/cooldown/mob_cooldown/charge/rust/proc/affect_aoe()
+ SIGNAL_HANDLER
+ UnregisterSignal(owner, COMSIG_FINISHED_CHARGE)
+ for(var/mob/living/nearby_mob in view(1, owner))
+ if(nearby_mob == owner)
+ continue
+ nearby_mob.apply_damage(charge_damage, BRUTE, wound_bonus = CANT_WOUND)
+ nearby_mob.Knockdown(5 SECONDS)
+ if(ishuman(owner))
+ var/mob/living/carbon/human/human_owner = owner
+ human_owner.physiology.damage_resistance -= 100
diff --git a/code/modules/antagonists/heretic/magic/rust_construction.dm b/code/modules/antagonists/heretic/magic/rust_construction.dm
index 59aeacf3004..acfc73231e3 100644
--- a/code/modules/antagonists/heretic/magic/rust_construction.dm
+++ b/code/modules/antagonists/heretic/magic/rust_construction.dm
@@ -8,7 +8,8 @@
check_flags = AB_CHECK_INCAPACITATED|AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED
school = SCHOOL_FORBIDDEN
- cooldown_time = 8 SECONDS
+ cooldown_time = 2 SECONDS
+ unset_after_click = FALSE
// Both of these are changed in before_cast
invocation = "Someone raises a wall of rust."
diff --git a/code/modules/antagonists/heretic/magic/rust_wave.dm b/code/modules/antagonists/heretic/magic/rust_wave.dm
index 45f1f878cff..9e87cbd7aff 100644
--- a/code/modules/antagonists/heretic/magic/rust_wave.dm
+++ b/code/modules/antagonists/heretic/magic/rust_wave.dm
@@ -17,7 +17,7 @@
invocation_type = INVOCATION_WHISPER
spell_requirements = NONE
- cone_levels = 5
+ cone_levels = 6
respect_density = TRUE
/datum/action/cooldown/spell/cone/staggered/entropic_plume/cast(atom/cast_on)
@@ -34,7 +34,7 @@
if(victim.can_block_magic(antimagic_flags) || IS_HERETIC_OR_MONSTER(victim) || victim == caster)
return
victim.apply_status_effect(/datum/status_effect/amok)
- victim.apply_status_effect(/datum/status_effect/cloudstruck, level * 1 SECONDS)
+ victim.apply_status_effect(/datum/status_effect/cloudstruck, 5 SECONDS)
victim.adjust_disgust(100)
/datum/action/cooldown/spell/cone/staggered/entropic_plume/calculate_cone_shape(current_level)
diff --git a/code/modules/antagonists/heretic/magic/star_blast.dm b/code/modules/antagonists/heretic/magic/star_blast.dm
index ad36cf9186a..b4cd2ec02b8 100644
--- a/code/modules/antagonists/heretic/magic/star_blast.dm
+++ b/code/modules/antagonists/heretic/magic/star_blast.dm
@@ -1,6 +1,7 @@
/datum/action/cooldown/spell/pointed/projectile/star_blast
name = "Star Blast"
- desc = "This spell fires a disk with cosmic energies at a target, spreading the star mark."
+ desc = "This spell fires an unstoppable disk with cosmic energies at a target, spreading the star mark. \
+ When recasted, you will be teleported to the disk, and cosmic fields will generate from the disk and from the caster, pulling nearby heathens into it."
background_icon_state = "bg_heretic"
overlay_icon_state = "bg_heretic_border"
button_icon = 'icons/mob/actions/actions_ecult.dmi'
@@ -8,7 +9,7 @@
sound = 'sound/effects/magic/cosmic_energy.ogg'
school = SCHOOL_FORBIDDEN
- cooldown_time = 20 SECONDS
+ cooldown_time = 1 SECONDS // Cooldown is tied to teleportation, not firing
invocation = "R'T'T' ST'R!"
invocation_type = INVOCATION_SHOUT
@@ -18,24 +19,78 @@
deactive_msg = "You stop swirling cosmic energies from the palm of your hand... for now."
cast_range = 12
projectile_type = /obj/projectile/magic/star_ball
+ /// Weakref to the projectile we fire, so that we can recast our ability to teleport to its location
+ var/datum/weakref/projectile_weakref
+ /// Weakref to our summoner, only relevant if we are a stargazer. Prevents us from harming our master
+ var/datum/weakref/summoner
+
+/datum/action/cooldown/spell/pointed/projectile/star_blast/ready_projectile(obj/projectile/to_fire, atom/target, mob/user, iteration)
+ . = ..()
+ projectile_weakref = WEAKREF(to_fire)
+ to_fire.AddElement(cosmic_trail_based_on_passive(user), /obj/effect/forcefield/cosmic_field/fast)
+
+/datum/action/cooldown/spell/pointed/projectile/star_blast/apply_button_overlay(atom/movable/screen/movable/action_button/current_button, force)
+ var/obj/projectile/magic/star_ball/active_ball = projectile_weakref?.resolve()
+ if(!active_ball)
+ return ..()
+
+ // Means we have a ball active so we'll put a border indicating you can re-cast it
+ current_button.cut_overlay(current_button.button_overlay)
+ current_button.button_overlay = mutable_appearance(icon = overlay_icon, icon_state = "bg_spell_border_active_green")
+ current_button.add_overlay(current_button.button_overlay)
+
+/datum/action/cooldown/spell/pointed/projectile/star_blast/set_click_ability(mob/on_who)
+ var/obj/projectile/magic/star_ball/active_ball = projectile_weakref?.resolve()
+ if(!active_ball)
+ build_all_button_icons(UPDATE_OVERLAYS)
+ return ..()
+
+ pull_victims()
+ do_teleport(owner, active_ball)
+ pull_victims() // Yes, this is intentional, we want to pull mobs from the place we were, and the place we've teleported to
+ QDEL_NULL(active_ball)
+ build_all_button_icons(UPDATE_OVERLAYS)
+ // Cooldown of the ability itself is only 1 second after shooting, it's 25 seconds after we teleport to our ball
+ StartCooldown(25 SECONDS)
+
+/datum/action/cooldown/spell/pointed/projectile/star_blast/proc/pull_victims()
+ new /obj/effect/temp_visual/circle_wave/star_blast(get_turf(owner))
+ for(var/turf/spawn_turf in range(1, get_turf(owner)))
+ if(spawn_turf.density)
+ continue
+ create_cosmic_field(spawn_turf, owner, /obj/effect/forcefield/cosmic_field/star_blast)
+ for(var/mob/living/nearby_mob in view(2, owner))
+ if(nearby_mob == owner || nearby_mob == summoner?.resolve())
+ continue
+ // Don't grab people who are tucked away or something
+ if(!isturf(nearby_mob.loc))
+ continue
+ if(IS_HERETIC_OR_MONSTER(nearby_mob))
+ continue
+ if(nearby_mob.can_block_magic(antimagic_flags))
+ continue
+ for(var/i in 1 to 3)
+ nearby_mob.forceMove(get_step_towards(nearby_mob, owner))
+ nearby_mob.apply_status_effect(/datum/status_effect/star_mark)
+
+/datum/action/cooldown/spell/pointed/projectile/star_blast/after_cast(atom/cast_on)
+ . = ..()
+ unset_click_ability(owner) // Unselect because we will re-select it to teleport
/obj/projectile/magic/star_ball
name = "star ball"
icon_state = "star_ball"
- damage = 20
- damage_type = BURN
+ damage = 0
speed = 0.2
- range = 100
+ range = 25
knockdown = 4 SECONDS
+ pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE | PASSBLOB | PASSMOB | PASSCLOSEDTURF | PASSMACHINE | PASSSTRUCTURE | PASSFLAPS | PASSDOORS | PASSVEHICLE | PASSITEM | PASSWINDOW
+ projectile_piercing = PASSMOB | PASSVEHICLE
/// Effect for when the ball hits something
var/obj/effect/explosion_effect = /obj/effect/temp_visual/cosmic_explosion
/// The range at which people will get marked with a star mark.
var/star_mark_range = 3
-/obj/projectile/magic/star_ball/Initialize(mapload)
- . = ..()
- AddElement(/datum/element/effect_trail, /obj/effect/forcefield/cosmic_field/fast)
-
/obj/projectile/magic/star_ball/on_hit(atom/target, blocked = 0, pierce_hit)
. = ..()
var/mob/living/cast_on = firer
@@ -46,9 +101,4 @@
/obj/projectile/magic/star_ball/Destroy()
playsound(get_turf(src), 'sound/effects/magic/cosmic_energy.ogg', 50, FALSE)
- for(var/turf/cast_turf as anything in get_turfs())
- new /obj/effect/forcefield/cosmic_field(cast_turf)
return ..()
-
-/obj/projectile/magic/star_ball/proc/get_turfs()
- return list(get_turf(src), pick(get_step(src, NORTH), get_step(src, SOUTH)), pick(get_step(src, EAST), get_step(src, WEST)))
diff --git a/code/modules/antagonists/heretic/magic/star_touch.dm b/code/modules/antagonists/heretic/magic/star_touch.dm
index bf7491ab037..54400f883f3 100644
--- a/code/modules/antagonists/heretic/magic/star_touch.dm
+++ b/code/modules/antagonists/heretic/magic/star_touch.dm
@@ -1,10 +1,9 @@
/datum/action/cooldown/spell/touch/star_touch
name = "Star Touch"
- desc = "Manifests cosmic fields on tiles next to you while marking the victim with a star mark \
- or consuming an already present star mark to put them to sleep for 4 seconds. \
- They will then be linked to you with a cosmic ray, burning them for up to a minute, or \
- until they can escape your sight. Star Touch can also remove Cosmic Runes, or teleport you \
- to your Star Gazer when used on yourself."
+ desc = "Can be used to apply a star mark to a target. \
+ If your victim is already star marked, tethers you to your target with a cosmic ray. \
+ If the tether remains unbroken for 8 seconds, they will be put to sleep and teleported to you. \
+ Star Touch can also remove Cosmic Runes, or teleport you to your Star Gazer when used in hand."
background_icon_state = "bg_heretic"
overlay_icon_state = "bg_heretic_border"
button_icon = 'icons/mob/actions/actions_ecult.dmi'
@@ -35,13 +34,13 @@
)
/datum/action/cooldown/spell/touch/star_touch/cast_on_hand_hit(obj/item/melee/touch_attack/hand, mob/living/victim, mob/living/carbon/caster)
- if(victim.has_status_effect(/datum/status_effect/star_mark))
- victim.apply_effect(4 SECONDS, effecttype = EFFECT_UNCONSCIOUS)
- victim.remove_status_effect(/datum/status_effect/star_mark)
- else
+ if(!victim.has_status_effect(/datum/status_effect/star_mark))
victim.apply_status_effect(/datum/status_effect/star_mark, caster)
+ return TRUE
+ victim.remove_status_effect(/datum/status_effect/star_mark)
+ victim.adjust_drowsiness(8 SECONDS)
for(var/turf/cast_turf as anything in get_turfs(victim))
- new /obj/effect/forcefield/cosmic_field(cast_turf)
+ create_cosmic_field(cast_turf, caster)
caster.apply_status_effect(/datum/status_effect/cosmic_beam, victim)
return TRUE
@@ -82,6 +81,15 @@
effects_we_clear = list(/obj/effect/cosmic_rune), \
)
+/obj/item/melee/touch_attack/star_touch/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
+ . = ..()
+ if(!isliving(interacting_with))
+ return
+ var/mob/living/living_target = interacting_with
+ if(get_dist(living_target, user) > 3)
+ return
+ return melee_attack_chain(user, living_target, modifiers)
+
/*
* Callback for effect_remover component.
*/
@@ -89,7 +97,17 @@
new /obj/effect/temp_visual/cosmic_rune_fade(get_turf(target))
var/datum/action/cooldown/spell/touch/star_touch/star_touch_spell = spell_which_made_us?.resolve()
star_touch_spell?.spell_feedback(user)
- remove_hand_with_no_refund(user)
+ if(!QDELETED(star_touch_spell))
+ qdel(star_touch_spell)
+ var/datum/action/cooldown/spell/cosmic_rune/rune_spell = locate() in user.actions
+ var/obj/effect/cosmic_rune/first_rune = rune_spell.first_rune.resolve()
+ var/obj/effect/cosmic_rune/second_rune = rune_spell.second_rune.resolve()
+ if(!QDELETED(first_rune))
+ new /obj/effect/temp_visual/cosmic_rune_fade(get_turf(first_rune))
+ QDEL_NULL(first_rune)
+ if(!QDELETED(second_rune))
+ new /obj/effect/temp_visual/cosmic_rune_fade(get_turf(second_rune))
+ QDEL_NULL(second_rune)
/obj/item/melee/touch_attack/star_touch/ignition_effect(atom/to_light, mob/user)
. = span_rose("[user] effortlessly snaps [user.p_their()] fingers near [to_light], igniting it with cosmic energies. Fucking badass!")
@@ -118,7 +136,7 @@
/datum/status_effect/cosmic_beam
id = "cosmic_beam"
tick_interval = 0.2 SECONDS
- duration = 1 MINUTES
+ duration = 8 SECONDS
status_type = STATUS_EFFECT_REPLACE
alert_type = null
/// Stores the current beam target
@@ -133,12 +151,26 @@
var/active = FALSE
/// The storage for the beam
var/datum/beam/current_beam = null
+ /// The timer for the teleport effect
+ var/teleport_timer
+ /// The effect trail that we add to our victim
+ var/cosmic_effect_trail
/datum/status_effect/cosmic_beam/on_creation(mob/living/new_owner, mob/living/current_target)
src.current_target = current_target
+ cosmic_effect_trail = cosmic_trail_based_on_passive(new_owner)
start_beam(current_target, new_owner)
+ ADD_TRAIT(current_target, TRAIT_NO_TELEPORT, REF(src))
+ teleport_timer = addtimer(CALLBACK(src, PROC_REF(yoink_victim), new_owner), 8 SECONDS, TIMER_STOPPABLE)
return ..()
+/// Puts the victim to sleep and teleports them to the casters' location
+/datum/status_effect/cosmic_beam/proc/yoink_victim(mob/living/carbon/caster)
+ current_target.apply_effect(8 SECONDS, effecttype = EFFECT_UNCONSCIOUS)
+ REMOVE_TRAIT(current_target, TRAIT_NO_TELEPORT, REF(src))
+ do_teleport(current_target, caster, channel = TELEPORT_CHANNEL_MAGIC, forced = TRUE)
+ current_target.apply_status_effect(/datum/status_effect/star_mark)
+
/datum/status_effect/cosmic_beam/be_replaced()
if(active)
QDEL_NULL(current_beam)
@@ -155,17 +187,15 @@
last_check = world.time
- if(!los_check(owner, current_target))
+ if(!get_dist(owner, current_target) > 8)
QDEL_NULL(current_beam)//this will give the target lost message
return
- if(current_target)
- on_beam_tick(current_target)
-
/**
* Proc that always is called when we want to end the beam and makes sure things are cleaned up, see beam_died()
*/
/datum/status_effect/cosmic_beam/proc/lose_target()
+ deltimer(teleport_timer)
if(active)
QDEL_NULL(current_beam)
active = FALSE
@@ -181,6 +211,7 @@
/datum/status_effect/cosmic_beam/proc/beam_died()
SIGNAL_HANDLER
to_chat(owner, span_warning("You lose control of the beam!"))
+ active = FALSE
lose_target()
duration = 0
@@ -194,24 +225,21 @@
current_target = target
active = TRUE
- current_beam = user.Beam(current_target, icon_state="cosmic_beam", time = 1 MINUTES, maxdistance = max_range, beam_type = /obj/effect/ebeam/cosmic)
+ current_beam = user.Beam(current_target, icon_state="cosmic_beam", time = 8 SECONDS, maxdistance = max_range, beam_type = /obj/effect/ebeam/cosmic)
RegisterSignal(current_beam, COMSIG_QDELETING, PROC_REF(beam_died))
SSblackbox.record_feedback("tally", "gun_fired", 1, type)
if(current_target)
- on_beam_hit(current_target)
+ on_beam_hit(current_target, user)
/// What to add when the beam connects to a target
-/datum/status_effect/cosmic_beam/proc/on_beam_hit(mob/living/target)
- if(!istype(target, /mob/living/basic/heretic_summon/star_gazer))
- target.AddElement(/datum/element/effect_trail, /obj/effect/forcefield/cosmic_field/fast)
-
-/// What to process when the beam is connected to a target
-/datum/status_effect/cosmic_beam/proc/on_beam_tick(mob/living/target)
- if(target.adjustFireLoss(3, updating_health = FALSE))
- target.updatehealth()
+/datum/status_effect/cosmic_beam/proc/on_beam_hit(mob/living/target, mob/living/user)
+ if(isstargazer(target))
+ return
+ target.AddElement(cosmic_effect_trail, /obj/effect/forcefield/cosmic_field/star_touch)
/// What to remove when the beam disconnects from a target
/datum/status_effect/cosmic_beam/proc/on_beam_release(mob/living/target)
- if(!istype(target, /mob/living/basic/heretic_summon/star_gazer))
- target.RemoveElement(/datum/element/effect_trail, /obj/effect/forcefield/cosmic_field/fast)
+ if(isstargazer(target))
+ return
+ target.RemoveElement(cosmic_effect_trail, /obj/effect/forcefield/cosmic_field/star_touch)
diff --git a/code/modules/antagonists/heretic/magic/void_conduit.dm b/code/modules/antagonists/heretic/magic/void_conduit.dm
index 0b7d325e3f8..4ba5e0d547b 100644
--- a/code/modules/antagonists/heretic/magic/void_conduit.dm
+++ b/code/modules/antagonists/heretic/magic/void_conduit.dm
@@ -28,6 +28,7 @@
icon_state = "void_conduit"
anchored = TRUE
density = TRUE
+ max_integrity = 150
///Overlay to apply to the tiles in range of the conduit
var/static/image/void_overlay = image(icon = 'icons/turf/overlays.dmi', icon_state = "voidtile")
///List of tiles that we added an overlay to, so we can clear them when the conduit is deleted
@@ -99,13 +100,9 @@
else
affected_mob.apply_status_effect(/datum/status_effect/void_chill, 1)
- if(istype(thing_to_affect, /obj/machinery/door) || istype(thing_to_affect, /obj/structure/door_assembly))
- var/obj/affected_door = thing_to_affect
- affected_door.take_damage(rand(15, 30))
-
- if(istype(thing_to_affect, /obj/structure/window) || istype(thing_to_affect, /obj/structure/grille))
- var/obj/structure/affected_structure = thing_to_affect
- affected_structure.take_damage(rand(10, 20))
+ if(istype(thing_to_affect, /obj/machinery/door) || istype(thing_to_affect, /obj/structure/door_assembly) || istype(thing_to_affect, /obj/structure/window) || istype(thing_to_affect, /obj/structure/grille))
+ var/obj/affected_structure = thing_to_affect
+ affected_structure.take_damage(rand(15, 30))
/datum/looping_sound/void_conduit
mid_sounds = 'sound/ambience/misc/ambiatm1.ogg'
diff --git a/code/modules/antagonists/heretic/magic/void_phase.dm b/code/modules/antagonists/heretic/magic/void_phase.dm
index 473fa057cf5..74f00d6fba6 100644
--- a/code/modules/antagonists/heretic/magic/void_phase.dm
+++ b/code/modules/antagonists/heretic/magic/void_phase.dm
@@ -10,7 +10,7 @@
ranged_mousepointer = 'icons/effects/mouse_pointers/throw_target.dmi'
school = SCHOOL_FORBIDDEN
- cooldown_time = 25 SECONDS
+ cooldown_time = 20 SECONDS
invocation = "RE'L'TY PH'S'E."
invocation_type = INVOCATION_WHISPER
@@ -57,7 +57,7 @@
if(living_mob.can_block_magic(antimagic_flags))
continue
living_mob.apply_damage(40, BRUTE, wound_bonus = CANT_WOUND)
- living_mob.apply_status_effect(/datum/status_effect/void_chill, 1)
+ living_mob.apply_status_effect(/datum/status_effect/void_chill, 2)
/obj/effect/temp_visual/voidin
icon = 'icons/effects/96x96.dmi'
diff --git a/code/modules/antagonists/heretic/magic/void_prison.dm b/code/modules/antagonists/heretic/magic/void_prison.dm
index a70c4e36ffa..9a57213d01d 100644
--- a/code/modules/antagonists/heretic/magic/void_prison.dm
+++ b/code/modules/antagonists/heretic/magic/void_prison.dm
@@ -53,7 +53,7 @@
/datum/status_effect/void_prison/on_remove()
if(!IS_HERETIC(owner))
- owner.apply_status_effect(/datum/status_effect/void_chill, 3)
+ owner.apply_status_effect(/datum/status_effect/void_chill, 1)
if(stasis_overlay)
//Free our prisoner
owner.remove_traits(list(TRAIT_GODMODE, TRAIT_NO_TRANSFORM, TRAIT_SOFTSPOKEN), TRAIT_STATUS_EFFECT(id))
diff --git a/code/modules/antagonists/heretic/magic/void_pull.dm b/code/modules/antagonists/heretic/magic/void_pull.dm
index 4e73ff6f49b..b705940846e 100644
--- a/code/modules/antagonists/heretic/magic/void_pull.dm
+++ b/code/modules/antagonists/heretic/magic/void_pull.dm
@@ -1,7 +1,6 @@
/datum/action/cooldown/spell/aoe/void_pull
name = "Void Pull"
- desc = "Calls the void, damaging, knocking down, and stunning people nearby. \
- Distant foes are also pulled closer to you (but not damaged)."
+ desc = "Calls the void, damaging, knocking down, pulling people closer, and stunning people nearby."
background_icon_state = "bg_heretic"
overlay_icon_state = "bg_heretic_border"
button_icon = 'icons/mob/actions/actions_ecult.dmi'
@@ -14,12 +13,7 @@
invocation = "BR'NG F'RTH TH'M T' M'."
invocation_type = INVOCATION_WHISPER
spell_requirements = NONE
-
- aoe_radius = 7
- /// The radius of the actual damage circle done before cast
- var/damage_radius = 1
- /// The radius of the stun applied to nearby people on cast
- var/stun_radius = 4
+ aoe_radius = 2
// Before the cast, we do some small AOE damage around the caster
/datum/action/cooldown/spell/aoe/void_pull/before_cast(atom/cast_on)
@@ -29,14 +23,9 @@
new /obj/effect/temp_visual/voidin(get_turf(cast_on))
- // Before we cast the actual effects, deal AOE damage to anyone adjacent to us
- for(var/mob/living/nearby_living as anything in get_things_to_cast_on(cast_on, damage_radius))
- nearby_living.apply_damage(30, BRUTE, wound_bonus = CANT_WOUND)
- nearby_living.apply_status_effect(/datum/status_effect/void_chill, 1)
-
-/datum/action/cooldown/spell/aoe/void_pull/get_things_to_cast_on(atom/center, radius_override = 1)
+/datum/action/cooldown/spell/aoe/void_pull/get_things_to_cast_on(atom/center)
var/list/things = list()
- for(var/mob/living/nearby_mob in view(radius_override || aoe_radius, center))
+ for(var/mob/living/nearby_mob in view(aoe_radius, center))
if(nearby_mob == owner || nearby_mob == center)
continue
// Don't grab people who are tucked away or something
@@ -53,11 +42,9 @@
// For the actual cast, we microstun people nearby and pull them in
/datum/action/cooldown/spell/aoe/void_pull/cast_on_thing_in_aoe(mob/living/victim, atom/caster)
- // If the victim's within the stun radius, they're stunned / knocked down
- if(get_dist(victim, caster) < stun_radius)
- victim.AdjustKnockdown(3 SECONDS)
- victim.AdjustParalyzed(0.5 SECONDS)
-
- // Otherwise, they take a few steps closer
+ victim.apply_damage(30, BRUTE, wound_bonus = CANT_WOUND)
+ victim.apply_status_effect(/datum/status_effect/void_chill, 3)
+ victim.AdjustKnockdown(3 SECONDS)
+ victim.AdjustParalyzed(0.5 SECONDS)
for(var/i in 1 to 3)
victim.forceMove(get_step_towards(victim, caster))
diff --git a/code/modules/antagonists/heretic/magic/wave_of_desperation.dm b/code/modules/antagonists/heretic/magic/wave_of_desperation.dm
index 16e3440ebbe..b05546328bf 100644
--- a/code/modules/antagonists/heretic/magic/wave_of_desperation.dm
+++ b/code/modules/antagonists/heretic/magic/wave_of_desperation.dm
@@ -1,14 +1,14 @@
/datum/action/cooldown/spell/aoe/wave_of_desperation
name = "Wave Of Desperation"
desc = "Removes your restraints, repels and knocks down adjacent people, and applies certain effects of the Mansus Grasp upon everything nearby. \
- Cannot be cast unless you are restrained, and the stress renders you unconscious 12 seconds later!"
+ Cannot be cast unless you are restrained, and the stress renders you unconscious 12 seconds later! (Can be casted without a focus)"
background_icon_state = "bg_heretic"
overlay_icon_state = "bg_heretic_border"
button_icon = 'icons/mob/actions/actions_ecult.dmi'
button_icon_state = "uncuff"
sound = 'sound/effects/magic/swap.ogg'
- school = SCHOOL_FORBIDDEN
+ school = SCHOOL_EVOCATION
cooldown_time = 5 MINUTES
invocation = "F'K 'FF."
diff --git a/code/modules/antagonists/heretic/status_effects/buffs.dm b/code/modules/antagonists/heretic/status_effects/buffs.dm
index ea067fd48f1..af689c0c025 100644
--- a/code/modules/antagonists/heretic/status_effects/buffs.dm
+++ b/code/modules/antagonists/heretic/status_effects/buffs.dm
@@ -24,6 +24,7 @@
owner.alpha = initial(owner.alpha)
owner.pass_flags &= ~(PASSCLOSEDTURF | PASSGLASS | PASSGRILLE | PASSMACHINE | PASSSTRUCTURE | PASSTABLE | PASSMOB | PASSDOORS | PASSVEHICLE)
owner.forceMove(location)
+ owner.apply_status_effect(/datum/status_effect/crucible_soul_cooldown)
location = null
/datum/status_effect/crucible_soul/get_examine_text()
@@ -43,6 +44,14 @@
target = active_effect
qdel(target)
+/datum/status_effect/crucible_soul_cooldown
+ id = "Crucible Soul Cooldown"
+ status_type = STATUS_EFFECT_UNIQUE
+ duration = 2 MINUTES
+ alert_type = /atom/movable/screen/alert/status_effect/crucible_soul/cooldown
+ show_duration = TRUE
+ remove_on_fullheal = TRUE
+
// DUSK AND DAWN
/datum/status_effect/duskndawn
id = "Blessing of Dusk and Dawn"
@@ -119,6 +128,10 @@
desc = "You phased through reality. You are halfway to your final destination..."
icon_state = "crucible"
+/atom/movable/screen/alert/status_effect/crucible_soul/cooldown
+ desc = "You have recently phased through reality. You must wait before you can do so once more."
+ icon_state = "crucible_cooldown"
+
/atom/movable/screen/alert/status_effect/duskndawn
name = "Blessing of Dusk and Dawn"
desc = "Many things hide beyond the horizon. With Owl's help I managed to slip past Sun's guard and Moon's watch."
@@ -355,3 +368,24 @@
name = "Blessing of The Moon"
desc = "The Moon clouds their vision, as the sun always has yours."
icon_state = "moon_hide"
+
+// Last Resort
+/datum/status_effect/heretic_lastresort
+ id = "heretic_lastresort"
+ alert_type = /atom/movable/screen/alert/status_effect/heretic_lastresort
+ duration = 12 SECONDS
+ status_type = STATUS_EFFECT_REPLACE
+ tick_interval = STATUS_EFFECT_NO_TICK
+
+/atom/movable/screen/alert/status_effect/heretic_lastresort
+ name = "Last Resort"
+ desc = "Your head spins, heart pumping as fast as it can, losing the fight with the ground. Run to safety!"
+ icon_state = "lastresort"
+
+/datum/status_effect/heretic_lastresort/on_apply()
+ ADD_TRAIT(owner, TRAIT_IGNORESLOWDOWN, TRAIT_STATUS_EFFECT(id))
+ to_chat(owner, span_userdanger("You won't give up that easily! Run to safety!"))
+ return TRUE
+
+/datum/status_effect/heretic_lastresort/on_remove()
+ REMOVE_TRAIT(owner, TRAIT_IGNORESLOWDOWN, TRAIT_STATUS_EFFECT(id))
diff --git a/code/modules/antagonists/heretic/status_effects/debuffs.dm b/code/modules/antagonists/heretic/status_effects/debuffs.dm
index 2ed47663fba..0e840f485cd 100644
--- a/code/modules/antagonists/heretic/status_effects/debuffs.dm
+++ b/code/modules/antagonists/heretic/status_effects/debuffs.dm
@@ -2,6 +2,7 @@
/datum/status_effect/amok
id = "amok"
status_type = STATUS_EFFECT_REPLACE
+ remove_on_fullheal = TRUE
alert_type = null
duration = 10 SECONDS
tick_interval = 1 SECONDS
@@ -33,6 +34,7 @@
/datum/status_effect/cloudstruck
id = "cloudstruck"
status_type = STATUS_EFFECT_REPLACE
+ remove_on_fullheal = TRUE
alert_type = null
duration = 3 SECONDS
on_remove_on_mob_delete = TRUE
@@ -103,6 +105,7 @@
/datum/status_effect/star_mark
id = "star_mark"
alert_type = /atom/movable/screen/alert/status_effect/star_mark
+ remove_on_fullheal = TRUE
duration = 30 SECONDS
status_type = STATUS_EFFECT_REPLACE
///overlay used to indicate that someone is marked
@@ -130,7 +133,7 @@
return ..()
/datum/status_effect/star_mark/on_apply()
- if(istype(owner, /mob/living/basic/heretic_summon/star_gazer))
+ if(isstargazer(owner))
return FALSE
var/mob/living/spell_caster_resolved = spell_caster?.resolve()
var/datum/antagonist/heretic_monster/monster = owner.mind?.has_antag_datum(/datum/antagonist/heretic_monster)
@@ -154,28 +157,7 @@
/datum/status_effect/star_mark/extended
duration = 3 MINUTES
-
-// Last Resort
-/datum/status_effect/heretic_lastresort
- id = "heretic_lastresort"
- alert_type = /atom/movable/screen/alert/status_effect/heretic_lastresort
- duration = 12 SECONDS
- status_type = STATUS_EFFECT_REPLACE
- tick_interval = STATUS_EFFECT_NO_TICK
-
-/atom/movable/screen/alert/status_effect/heretic_lastresort
- name = "Last Resort"
- desc = "Your head spins, heart pumping as fast as it can, losing the fight with the ground. Run to safety!"
- icon_state = "lastresort"
-
-/datum/status_effect/heretic_lastresort/on_apply()
- ADD_TRAIT(owner, TRAIT_IGNORESLOWDOWN, TRAIT_STATUS_EFFECT(id))
- to_chat(owner, span_userdanger("You are on the brink of losing consciousness, run!"))
- return TRUE
-
-/datum/status_effect/heretic_lastresort/on_remove()
- REMOVE_TRAIT(owner, TRAIT_IGNORESLOWDOWN, TRAIT_STATUS_EFFECT(id))
- owner.AdjustUnconscious(20 SECONDS, ignore_canstun = TRUE)
+f
/// Used by moon heretics to make people mad
/datum/status_effect/moon_converted
@@ -183,6 +165,8 @@
alert_type = /atom/movable/screen/alert/status_effect/moon_converted
duration = STATUS_EFFECT_PERMANENT
status_type = STATUS_EFFECT_REPLACE
+ remove_on_fullheal = TRUE
+ heal_flag_necessary = HEAL_ADMIN
///used to track damage
var/damage_sustained = 0
///overlay used to indicate that someone is marked
@@ -208,12 +192,12 @@
/datum/status_effect/moon_converted/on_apply()
RegisterSignal(owner, COMSIG_MOB_APPLY_DAMAGE, PROC_REF(on_damaged))
// Heals them so people who are in crit can have this affect applied on them and still be of some use for the heretic
- owner.adjustBruteLoss( -150 + owner.mob_mood.sanity)
+ owner.adjustBruteLoss(-150 + owner.mob_mood.sanity)
owner.adjustFireLoss(-150 + owner.mob_mood.sanity)
to_chat(owner, span_hypnophrase(("THE MOON SHOWS YOU THE TRUTH AND THE LIARS WISH TO COVER IT, SLAY THEM ALL!!!")))
owner.balloon_alert(owner, "they lie..THEY ALL LIE!!!")
- owner.AdjustUnconscious(7 SECONDS, ignore_canstun = FALSE)
+ owner.SetUnconscious(60 SECONDS, ignore_canstun = FALSE)
ADD_TRAIT(owner, TRAIT_MUTE, TRAIT_STATUS_EFFECT(id))
RegisterSignal(owner, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(update_owner_overlay))
owner.update_appearance(UPDATE_OVERLAYS)
@@ -249,6 +233,20 @@
owner.update_appearance(UPDATE_OVERLAYS)
return ..()
+// exists to apply sleep and deny adding duplicates
+/datum/status_effect/moon_slept
+ id = "moon slept"
+ duration = 2 MINUTES
+ status_type = STATUS_EFFECT_UNIQUE
+ remove_on_fullheal = TRUE
+ alert_type = null
+
+/datum/status_effect/moon_slept/on_apply()
+ . = owner.SetUnconscious(duration * 0.5, ignore_canstun = FALSE)
+ if(!.)
+ owner.balloon_alert(owner, "sleep resisted!")
+ to_chat(owner, span_hypnophrase(("THE MOON SHOWS YOU THE TRUTH AND THE LIARS WISH TO COVER IT, w-wait no that's not right")))
+ owner.balloon_alert(owner, "they lie..wait-what are they lying about?")
/atom/movable/screen/alert/status_effect/moon_converted
name = "Moon Converted"
@@ -262,6 +260,7 @@
alert_type = /atom/movable/screen/alert/status_effect/eldritch_painting
duration = 10 MINUTES
status_type = STATUS_EFFECT_UNIQUE
+ remove_on_fullheal = TRUE
/datum/status_effect/eldritch_painting/on_apply()
if(IS_HERETIC_OR_MONSTER(owner))
@@ -389,3 +388,64 @@
name = "Climb Over the Rusted Mountain"
desc = "Your every footfall erodes the ground beneath you! Everything crumbles away! Maybe if you looked closer at the mountain in that painting, the path might be clearer..."
icon_state = "eldritch_painting_rust"
+
+/atom/movable/screen/alert/status_effect/eldritch_parade
+ name = "Lunar Parade"
+ desc = "You MUST ENTER THE LUNAR PARADE! FOLLOW THE LIGHTS! LET THEM GUIDE YOU!"
+ icon = 'icons/obj/weapons/guns/projectiles.dmi'
+ icon_state = "lunar_parade"
+
+/datum/status_effect/moon_parade
+ id = "moon_parade"
+ remove_on_fullheal = TRUE
+ alert_type = /atom/movable/screen/alert/status_effect/eldritch_parade
+ duration = 20 SECONDS
+ tick_interval = -1
+ /// The component that leashes us to the parade
+ var/datum/component/leash/leash_component
+ /// what atom we are leashed to
+ var/atom/leashed_to
+ /// how much damage before we release the leash
+ var/damage_release_threshold = 50
+ /// how much damage we have received so far
+ var/damage_received = 0
+
+/datum/status_effect/moon_parade/on_creation(mob/living/new_owner, atom/leashed_by)
+ leashed_to = leashed_by
+ . = ..()
+
+/datum/status_effect/moon_parade/on_apply()
+ if(!istype(leashed_to))
+ return FALSE
+ owner.balloon_alert(owner, "you feel unable to move away from the [leashed_to]!")
+ leash_component = owner.AddComponent(/datum/component/leash, leashed_to, distance = 1)
+ RegisterSignal(leashed_to, COMSIG_QDELETING, PROC_REF(delete_self))
+ RegisterSignal(owner, COMSIG_MOB_CLIENT_PRE_LIVING_MOVE, PROC_REF(block_move))
+ RegisterSignal(owner, COMSIG_MOB_APPLY_DAMAGE, PROC_REF(on_damage_received))
+ return TRUE
+
+/datum/status_effect/moon_parade/on_remove()
+ . = ..()
+ QDEL_NULL(leash_component)
+ UnregisterSignal(owner, list(COMSIG_MOB_CLIENT_PRE_LIVING_MOVE, COMSIG_MOB_APPLY_DAMAGE))
+ if(leashed_to)
+ UnregisterSignal(leashed_to, COMSIG_QDELETING)
+
+/datum/status_effect/moon_parade/proc/delete_self(datum/source)
+ SIGNAL_HANDLER
+ qdel(src)
+
+/datum/status_effect/moon_parade/proc/on_damage_received(mob/attacked, damage_amount, damagetype)
+ SIGNAL_HANDLER
+ if(damagetype == STAMINA)
+ return
+ damage_received += damage_amount
+ if(damage_received >= damage_release_threshold)
+ owner.balloon_alert(owner, "you are free!")
+ qdel(src)
+
+// Blocks movement in order to make it appear like the character is transfixed to the projectile and wandering after it
+// Coded this way because its a simple way to hold the illusion compared to other methods
+/datum/status_effect/moon_parade/proc/block_move(datum/source)
+ SIGNAL_HANDLER
+ return COMSIG_MOB_CLIENT_BLOCK_PRE_LIVING_MOVE
diff --git a/code/modules/antagonists/heretic/status_effects/heretic_passive.dm b/code/modules/antagonists/heretic/status_effects/heretic_passive.dm
new file mode 100644
index 00000000000..150f1384a7c
--- /dev/null
+++ b/code/modules/antagonists/heretic/status_effects/heretic_passive.dm
@@ -0,0 +1,586 @@
+#define HERETIC_LEVEL_START 1
+#define HERETIC_LEVEL_UPGRADE 2
+#define HERETIC_LEVEL_FINAL 3
+
+/datum/status_effect/heretic_passive
+ id = "heretic_passive"
+ duration = STATUS_EFFECT_PERMANENT
+ status_type = STATUS_EFFECT_REPLACE
+ alert_type = null
+ on_remove_on_mob_delete = TRUE
+ /// Reference to the owning heretic datum
+ var/datum/antagonist/heretic/heretic_datum
+ ///What level is our passive currently on
+ var/passive_level = HERETIC_LEVEL_START
+ /// Name of the passive, used by the UI
+ var/name = "Heretic Passive"
+ var/list/passive_descriptions = list(
+ "Grants you a passive ability based on your heretic type. This ability will upgrade as you gain more power.",
+ "Your passive ability has been upgraded, doing something else.",
+ "Your passive ability has been upgraded to its final form, granting you a powerful new ability.",
+ )
+
+/datum/status_effect/heretic_passive/on_apply()
+ . = ..()
+ heretic_datum = GET_HERETIC(owner)
+ RegisterSignal(heretic_datum, COMSIG_HERETIC_PASSIVE_UPGRADE_FIRST, PROC_REF(heretic_level_upgrade))
+ RegisterSignal(heretic_datum, COMSIG_HERETIC_PASSIVE_UPGRADE_FINAL, PROC_REF(heretic_level_final))
+ if(!heretic_datum)
+ return FALSE
+
+ // Just in case of shenanigans, assume the antag datum is correct about our level
+ if(heretic_datum.passive_level == 3)
+ heretic_level_final()
+ return
+ if(heretic_datum.passive_level == 2)
+ heretic_level_upgrade()
+ return
+
+/datum/status_effect/heretic_passive/on_remove()
+ UnregisterSignal(heretic_datum, list(
+ COMSIG_HERETIC_PASSIVE_UPGRADE_FIRST,
+ COMSIG_HERETIC_PASSIVE_UPGRADE_FINAL,
+ ))
+ heretic_datum = null
+ return ..()
+
+/// Gives our first upgrade
+/datum/status_effect/heretic_passive/proc/heretic_level_upgrade()
+ SIGNAL_HANDLER
+ SHOULD_CALL_PARENT(TRUE)
+ passive_level = HERETIC_LEVEL_UPGRADE
+ heretic_datum.passive_level = HERETIC_LEVEL_UPGRADE
+ heretic_datum.update_data_for_all_viewers()
+ if(!heretic_datum.unlimited_blades)
+ heretic_datum.disable_blade_breaking()
+
+/// Gives our final upgrade
+/datum/status_effect/heretic_passive/proc/heretic_level_final()
+ SIGNAL_HANDLER
+ SHOULD_CALL_PARENT(TRUE)
+ if(passive_level == HERETIC_LEVEL_START)
+ heretic_level_upgrade()
+ passive_level = HERETIC_LEVEL_FINAL
+ heretic_datum.passive_level = HERETIC_LEVEL_FINAL
+ heretic_datum.update_data_for_all_viewers()
+
+
+//---- Ash Passive
+// Level 1 grants heat and ash storm immunity
+// Level 2 grants lava immunity
+// Level 3 grants resistance to high pressure
+/datum/status_effect/heretic_passive/ash
+ name = "Vow of Destruction"
+ passive_descriptions = list(
+ "Heat and ash storm immunity.",
+ "Lava immunity.",
+ "Resistance to high and low pressure."
+ )
+
+/datum/status_effect/heretic_passive/ash/on_apply()
+ . = ..()
+ owner.add_traits(list(TRAIT_RESISTHEAT, TRAIT_ASHSTORM_IMMUNE), REF(src))
+
+/datum/status_effect/heretic_passive/ash/heretic_level_upgrade()
+ . = ..()
+ ADD_TRAIT(owner, TRAIT_LAVA_IMMUNE, REF(src))
+
+/datum/status_effect/heretic_passive/ash/heretic_level_final()
+ . = ..()
+ owner.add_traits(list(TRAIT_RESISTHIGHPRESSURE, TRAIT_RESISTLOWPRESSURE), REF(src))
+
+/datum/status_effect/heretic_passive/ash/on_remove()
+ owner.remove_traits(list(TRAIT_RESISTHEAT, TRAIT_ASHSTORM_IMMUNE, TRAIT_LAVA_IMMUNE, TRAIT_RESISTHIGHPRESSURE, TRAIT_RESISTLOWPRESSURE), REF(src))
+ return ..()
+
+//---- Blade Passive
+// Gives you riposte while wielding a heretic blade
+// Cooldown starts at 20 and goes down 5 seconds per level
+// Level 1 has a 20 second cooldown + counts as block
+// Level 2 Makes you immune to fall damage/stun from falling
+// Level 3 only has the cooldown reduction (nothing else added)
+/datum/status_effect/heretic_passive/blade
+ name = "Dance of the Brand"
+ id = "blade_passive"
+ passive_descriptions = list(
+ "Being attacked while wielding a Heretic Blade in either hand will deliver a free, instant counterattack to the attacker. This effect can only trigger once every 20 seconds.",
+ "Immunity to fall damage.",
+ "Cooldown of the riposte reduced to 10 seconds."
+ )
+ /// The cooldown before we can riposte again
+ var/base_cooldown = 20 SECONDS
+ /// The cooldown reduction gained from upgrading
+ var/cooldown_reduction = 5 SECONDS
+ /// Whether the counter-attack is ready or not.
+ /// Used so we can give feedback when it's ready again
+ var/riposte_ready = TRUE
+
+/datum/status_effect/heretic_passive/blade/on_apply()
+ . = ..()
+ RegisterSignal(owner, COMSIG_LIVING_CHECK_BLOCK, PROC_REF(on_shield_reaction))
+
+/datum/status_effect/heretic_passive/blade/heretic_level_upgrade()
+ . = ..()
+ RegisterSignal(owner, COMSIG_LIVING_Z_IMPACT, PROC_REF(z_impact_react))
+
+/datum/status_effect/heretic_passive/blade/on_remove()
+ . = ..()
+ UnregisterSignal(owner, list(COMSIG_LIVING_CHECK_BLOCK, COMSIG_LIVING_Z_IMPACT))
+
+/// Blocks the effects from falling
+/datum/status_effect/heretic_passive/blade/proc/z_impact_react(datum/source, levels, turf/fell_on)
+ SIGNAL_HANDLER
+ new /obj/effect/temp_visual/mook_dust(fell_on)
+ owner.visible_message(span_notice("[owner] lands on [fell_on] safely, and quite stylishly on [p_their()] feet!"))
+ INVOKE_ASYNC(owner, TYPE_PROC_REF(/atom, SpinAnimation), 0.5 SECONDS, 0)
+ INVOKE_ASYNC(owner, TYPE_PROC_REF(/mob/, emote), "flip")
+ return ZIMPACT_CANCEL_DAMAGE | ZIMPACT_NO_MESSAGE | ZIMPACT_NO_SPIN
+
+/// Checks if we can counter-attack
+/datum/status_effect/heretic_passive/blade/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,
+ damage_type = BRUTE,
+)
+ SIGNAL_HANDLER
+
+ if(attack_type != MELEE_ATTACK)
+ return
+
+ if(!riposte_ready)
+ return
+
+ if(INCAPACITATED_IGNORING(source, INCAPABLE_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_REF(counter_attack), source, attacker, striking_with, attack_text)
+
+ // And reset after a bit
+ riposte_ready = FALSE
+ addtimer(CALLBACK(src, PROC_REF(reset_riposte), source), (base_cooldown - cooldown_reduction * (passive_level - 1)))
+
+ if(passive_level > HERETIC_LEVEL_START)
+ return SUCCESSFUL_BLOCK
+
+/// Does the actual counter-attack
+/datum/status_effect/heretic_passive/blade/proc/counter_attack(mob/living/carbon/human/source, mob/living/target, obj/item/melee/sickly_blade/weapon, attack_text)
+ playsound(get_turf(source), 'sound/items/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)
+
+/// Gives feedback to the user
+/datum/status_effect/heretic_passive/blade/proc/reset_riposte(mob/living/carbon/human/source)
+ riposte_ready = TRUE
+ source.balloon_alert(source, "riposte ready")
+
+//---- Cosmic Passive
+// Level 1 Cosmic fields will speed up the caster and provide stamina regen
+// Level 2 Cosmic fields will disable any nearby bombs/TTVs/Syndicate Bombs
+// Level 3 Cosmic fields will temporarily slow down bullets that pass through them
+/datum/status_effect/heretic_passive/cosmic
+ name = "Chosen of the Stars"
+ id = "cosmic_passive"
+ passive_descriptions = list(
+ "Cosmic fields speed you up and regenerate stamina.",
+ "Cosmic fields disrupt grenades or signalers from being activated and turn off already primed grenades.",
+ "Cosmic fields slow projectiles down."
+ )
+
+/datum/status_effect/heretic_passive/cosmic/tick(seconds_between_ticks)
+ . = ..()
+ if(locate(/obj/effect/forcefield/cosmic_field) in get_turf(owner))
+ var/delta_time = DELTA_WORLD_TIME(SSmobs) * 0.5 // SSmobs.wait is 2 secs, so this should be halved.
+ owner.adjustStaminaLoss(-15 * delta_time, updating_stamina = FALSE)
+
+/**
+ * Creates a cosmic field at a given loc
+ *
+ * * Args:
+ * * `loc`: Where the cosmic field is created
+ * * Optional `creator`: Checks if the passed mob has a cosmic passive. Upgrades the cosmic field based on their passive level
+ * * Optional `type`: Makes a specific type of cosmic field if we don't want the default
+ */
+/proc/create_cosmic_field(loc, mob/living/creator, type = /obj/effect/forcefield/cosmic_field)
+ var/obj/effect/forcefield/cosmic_field/new_field
+ new_field = new type(loc)
+
+ if(!creator || !ismob(creator))
+ return
+ if(isstargazer(creator))
+ new_field.slows_projectiles()
+ new_field.prevents_explosions()
+ return
+ var/datum/status_effect/heretic_passive/cosmic/cosmic_passive = creator.has_status_effect(/datum/status_effect/heretic_passive/cosmic)
+ if(!cosmic_passive)
+ return
+ if(cosmic_passive.passive_level > HERETIC_LEVEL_START)
+ new_field.prevents_explosions()
+ if(cosmic_passive.passive_level > HERETIC_LEVEL_UPGRADE)
+ new_field.slows_projectiles()
+
+//---- Flesh Passive
+// Makes you never get disgust, virus immune and immune to damage from space ants
+// Level 2, organs and raw meat heals you. You also become a voracious glutton who likes all food. No slowdown from being fat
+// Level 3, being fat gives damage resistance
+/datum/status_effect/heretic_passive/flesh
+ name = "Ravenous Hunger"
+ id = "flesh_passive"
+ passive_descriptions = list(
+ "Immunity to Diseases, Disgust and space ants.",
+ "Eating organs or meat now heals you, gain the voracious and gluttonous trait and being fat doesn't slow you down.",
+ "Gain a flat 25% damage and stamina damage reduction when fat as well as baton resistance."
+ )
+
+/datum/status_effect/heretic_passive/flesh/on_apply()
+ . = ..()
+ owner.add_traits(list(TRAIT_VIRUSIMMUNE, TRAIT_SPACE_ANT_IMMUNITY), REF(src))
+
+/datum/status_effect/heretic_passive/flesh/tick(seconds_between_ticks)
+ . = ..()
+ owner.set_disgust(0)
+
+/datum/status_effect/heretic_passive/flesh/heretic_level_upgrade()
+ . = ..()
+ RegisterSignal(owner, COMSIG_LIVING_EAT_FOOD, PROC_REF(on_eat))
+ owner.add_traits(list(TRAIT_FAT_IGNORE_SLOWDOWN, TRAIT_VORACIOUS, TRAIT_GLUTTON), REF(src))
+ if(!ishuman(owner))
+ return
+ var/mob/living/carbon/human/fat_human = owner
+ fat_human.on_fat() // Make sure to update the movespeed modifier in case we gain the trait while already fat
+ var/obj/item/organ/tongue/tongue = fat_human.get_organ_slot(ORGAN_SLOT_TONGUE)
+ tongue.liked_foodtypes = ALL
+ tongue.disliked_foodtypes = NONE
+ tongue.toxic_foodtypes = NONE
+
+/// Any time you take a bite of something, if it's meat or an organ you will heal some damage
+/datum/status_effect/heretic_passive/flesh/proc/on_eat(mob/eater, atom/food)
+ SIGNAL_HANDLER
+ var/obj/item/organ/consumed_organ = food
+ if(istype(consumed_organ) && consumed_organ.foodtype_flags & MEAT)
+ heal_glutton() // Heal the owner if they eat meat
+ return
+ var/obj/item/food/consumed_food = food
+ if(istype(consumed_food) && consumed_food.foodtypes & MEAT)
+ heal_glutton() // Heal the owner if they eat meat
+
+/datum/status_effect/heretic_passive/flesh/proc/heal_glutton()
+ var/healed_amount = owner.heal_overall_damage(2, 2, updating_health = FALSE)
+ healed_amount += owner.adjustOxyLoss(-2, FALSE)
+ healed_amount += owner.adjustToxLoss(-2, FALSE, TRUE)
+ if(!HAS_TRAIT(owner, TRAIT_NOBLOOD))
+ owner.blood_volume += 2.5
+ if(!iscarbon(owner))
+ return
+ var/mob/living/carbon/carbon_eater = owner
+ for(var/obj/item/bodypart/wounded_limb as anything in carbon_eater.bodyparts)
+ for(var/datum/wound/to_cure as anything in wounded_limb.wounds)
+ to_cure.remove_wound()
+ break
+ if(healed_amount > 0)
+ owner.updatehealth()
+ new /obj/effect/temp_visual/heal(get_turf(owner), COLOR_RED)
+
+/datum/status_effect/heretic_passive/flesh/heretic_level_final()
+ . = ..()
+ if(!ishuman(owner))
+ return
+ var/mob/living/carbon/human/fat_human = owner
+ RegisterSignals(fat_human, list(SIGNAL_ADDTRAIT(TRAIT_FAT), SIGNAL_REMOVETRAIT(TRAIT_FAT)), PROC_REF(on_fat))
+ on_fat()
+
+/// Gives/Removes damage resistance when we become/lose fatness
+/datum/status_effect/heretic_passive/flesh/proc/on_fat(datum/source)
+ if(!ishuman(owner))
+ return
+ var/mob/living/carbon/human/heretic = owner
+ if(HAS_TRAIT(heretic, TRAIT_FAT))
+ heretic.physiology.damage_resistance += 25
+ ADD_TRAIT(heretic, TRAIT_BATON_RESISTANCE, REF(src))
+ else
+ heretic.physiology.damage_resistance -= 25
+ REMOVE_TRAIT(heretic, TRAIT_BATON_RESISTANCE, REF(src))
+
+/datum/status_effect/heretic_passive/flesh/on_remove()
+ . = ..()
+ owner.remove_traits(list(TRAIT_VIRUSIMMUNE, TRAIT_SPACE_ANT_IMMUNITY, TRAIT_FAT_IGNORE_SLOWDOWN, TRAIT_VORACIOUS, TRAIT_GLUTTON, TRAIT_BATON_RESISTANCE), REF(src))
+ UnregisterSignal(owner, list(COMSIG_LIVING_EAT_FOOD, SIGNAL_ADDTRAIT(TRAIT_FAT), SIGNAL_REMOVETRAIT(TRAIT_FAT)))
+ if(!ishuman(owner))
+ return
+ var/mob/living/carbon/human/heretic = owner
+ if(!HAS_TRAIT(heretic, TRAIT_FAT))
+ return
+ heretic.physiology.damage_resistance -= 25
+ heretic.on_fat()
+
+//---- Lock Passive
+// On gain you can understand and speak every language
+// Level 1 Shock immunity + Side knowledge is cheaper
+// Level 2 Gains X-ray Vision
+// Level 3 your grasp no longer goes on cooldown when opening things
+/datum/status_effect/heretic_passive/lock
+ name = "Open Invitation"
+ id = "lock_passive"
+ passive_descriptions = list(
+ "Shock insulation, all knowledges researched from the shop are cheaper",
+ "X-ray vision, you can see through walls and objects.",
+ "Grasp no longer goes on cooldown when used to open a door or locker."
+ )
+
+/datum/status_effect/heretic_passive/lock/on_apply()
+ . = ..()
+ ADD_TRAIT(owner, TRAIT_SHOCKIMMUNE, REF(src))
+ RegisterSignal(heretic_datum, COMSIG_HERETIC_SHOP_SETUP, PROC_REF(on_shop_setup)) // Just in case we are applying this after the shop was set up
+
+/datum/status_effect/heretic_passive/lock/heretic_level_upgrade()
+ . = ..()
+ ADD_TRAIT(owner, TRAIT_XRAY_VISION, REF(src))
+ owner.update_sight()
+
+/datum/status_effect/heretic_passive/lock/heretic_level_final()
+ . = ..()
+ ADD_TRAIT(owner, TRAIT_LOCK_GRASP_UPGRADED, REF(src))
+
+/datum/status_effect/heretic_passive/lock/on_remove()
+ UnregisterSignal(owner, COMSIG_HERETIC_SHOP_SETUP)
+ owner.remove_traits(list(TRAIT_SHOCKIMMUNE, TRAIT_XRAY_VISION, TRAIT_LOCK_GRASP_UPGRADED), REF(src))
+ owner.update_sight()
+ return ..()
+
+/datum/status_effect/heretic_passive/lock/proc/on_shop_setup(datum/antagonist/heretic/heretic_datum)
+ SIGNAL_HANDLER
+ var/list/shop = heretic_datum.heretic_shops[HERETIC_KNOWLEDGE_SHOP]
+ for(var/knowledge_type in shop)
+ var/list/heretic_info = shop[knowledge_type]
+ if(heretic_info)
+ heretic_info[HKT_COST] = max(1, heretic_info[HKT_COST] - 1) // Reduce cost by 1, minimum of 1
+
+//---- Moon Passive
+// Heals 5 brain damage per level
+// Prevents brain trauma
+// Level 2 grants sleep immunity
+// Level 3, Mind gate + Ringleader's rise will channel the moon amulet effects
+/datum/status_effect/heretic_passive/moon
+ name = "Do You Hear The Voices Too?"
+ id = "moon_passive"
+ passive_descriptions = list(
+ "Can no longer develop brain traumas, passively regenerates brain health, (this bonus is halved in combat).",
+ "Sleep immunity, increases the ratio at which your brain damage regenerates.",
+ "Mind gate and Ringleader's rise will channel the moon amulet effects, further inreases brain regeneration."
+ )
+ /// Built-in moon amulet which channels through your spells
+ var/obj/item/clothing/neck/heretic_focus/moon_amulet/amulet
+ /// When were we last attacked?
+ var/last_attack = 0
+ /// How long the combat tag lasts for
+ var/combat_lockout = 5 SECONDS
+ /// Boolean if you are wearing the moon amulet
+ var/amulet_equipped = FALSE
+
+/datum/status_effect/heretic_passive/moon/on_apply()
+ . = ..()
+ var/obj/item/organ/brain/our_brain = owner.get_organ_slot(ORGAN_SLOT_BRAIN)
+ ADD_TRAIT(our_brain, TRAIT_BRAIN_TRAUMA_IMMUNITY, REF(src))
+ owner.AddElement(/datum/element/relay_attackers)
+ RegisterSignal(owner, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(on_attacked))
+
+/// Saves world.time when we are attacked by anything
+/datum/status_effect/heretic_passive/moon/proc/on_attacked(mob/victim, atom/attacker)
+ SIGNAL_HANDLER
+ last_attack = world.time
+
+/datum/status_effect/heretic_passive/moon/tick(seconds_between_ticks)
+ . = ..()
+ var/healing_amount = ((world.time > last_attack + combat_lockout) ? -1 * passive_level * seconds_between_ticks : -2 * passive_level * seconds_between_ticks)
+ if(heretic_datum.ascended)
+ healing_amount = -15 * seconds_between_ticks
+ if(!amulet_equipped)
+ healing_amount *= 0.5 // Half healing if you dont have the moon amulet
+ owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, healing_amount)
+
+ var/obj/item/organ/brain/our_brain = owner.get_organ_slot(ORGAN_SLOT_BRAIN)
+ if(!our_brain)
+ return
+ for(var/datum/brain_trauma/trauma as anything in our_brain.traumas)
+ if(istype(trauma, BRAIN_TRAUMA_MILD) || istype(trauma, BRAIN_TRAUMA_SEVERE))
+ our_brain.cure_trauma_type(trauma.type, trauma.resilience)
+
+/datum/status_effect/heretic_passive/moon/heretic_level_upgrade()
+ . = ..()
+ ADD_TRAIT(owner, TRAIT_SLEEPIMMUNE, REF(src))
+
+/datum/status_effect/heretic_passive/moon/heretic_level_final()
+ . = ..()
+ amulet = new()
+
+/datum/status_effect/heretic_passive/moon/on_remove()
+ var/obj/item/organ/brain/our_brain = owner.get_organ_slot(ORGAN_SLOT_BRAIN)
+ REMOVE_TRAIT(our_brain, TRAIT_BRAIN_TRAUMA_IMMUNITY, REF(src))
+ REMOVE_TRAIT(owner, TRAIT_SLEEPIMMUNE, REF(src))
+ UnregisterSignal(owner, COMSIG_ATOM_WAS_ATTACKED)
+ QDEL_NULL(amulet)
+ return ..()
+
+//---- Rust Passive
+// Level 2 and 3 will increase our rust strength
+// Level 1 provides healing and baton resist when standing on rust
+// Level 2 will heal wounds when standing on rust
+// Level 3 will restore lost limbs when standing on rust
+/datum/status_effect/heretic_passive/rust
+ name = "Leeching Walk"
+ id = "rust_passive"
+ passive_descriptions = list(
+ "Standing on Rusted tiles heals and purge chems off your body.",
+ "Standing on Rusted tiles closes up your wounds and heals your organs, you may now rust reinforced floors and walls, healing effect increased.",
+ "Standing on Rusted tiles regenerates your limbs, you may now rust titanium and plastitanium walls, healing effect increased."
+ )
+
+/datum/status_effect/heretic_passive/rust/on_apply()
+ . = ..()
+ RegisterSignal(owner, COMSIG_MOVABLE_MOVED, PROC_REF(on_move))
+ RegisterSignal(owner, COMSIG_LIVING_LIFE, PROC_REF(on_life))
+
+/datum/status_effect/heretic_passive/rust/on_remove()
+ . = ..()
+ UnregisterSignal(owner, list(COMSIG_MOVABLE_MOVED, COMSIG_LIVING_LIFE))
+
+/datum/status_effect/heretic_passive/rust/heretic_level_upgrade()
+ . = ..()
+ if(heretic_datum.rust_strength < 2)
+ heretic_datum.increase_rust_strength() // Bring us up to 2
+
+/datum/status_effect/heretic_passive/rust/heretic_level_final()
+ . = ..()
+ if(heretic_datum.rust_strength < 3)
+ heretic_datum.increase_rust_strength() // Bring us up to 3
+
+/*
+ * Signal proc for [COMSIG_MOVABLE_MOVED].
+ *
+ * Checks if we should have baton resistance on the new turf.
+ */
+/datum/status_effect/heretic_passive/rust/proc/on_move(mob/source, atom/old_loc, dir, forced, list/old_locs)
+ SIGNAL_HANDLER
+
+ var/turf/mover_turf = get_turf(source)
+ if(HAS_TRAIT(mover_turf, TRAIT_RUSTY))
+ ADD_TRAIT(source, TRAIT_BATON_RESISTANCE, REF(src))
+ else
+ REMOVE_TRAIT(source, TRAIT_BATON_RESISTANCE, REF(src))
+
+/**
+ * Signal proc for [COMSIG_LIVING_LIFE].
+ *
+ * Gradually heals the heretic ([source]) on rust,
+ * including baton knockdown and stamina damage.
+ */
+/datum/status_effect/heretic_passive/rust/proc/on_life(mob/living/source, seconds_per_tick, times_fired)
+ SIGNAL_HANDLER
+
+ var/turf/our_turf = get_turf(source)
+ if(!HAS_TRAIT(our_turf, TRAIT_RUSTY))
+ return
+
+ // Heals all damage + Stamina
+ var/need_mob_update = FALSE
+ var/delta_time = DELTA_WORLD_TIME(SSmobs) * 0.5 // SSmobs.wait is 2 secs, so this should be halved.
+ var/main_healing = 1 + 1 * passive_level * delta_time
+ var/stam_healing = 5 + 5 * passive_level * delta_time
+ need_mob_update += source.heal_overall_damage(-main_healing, -main_healing, updating_health = FALSE)
+ need_mob_update += source.adjustStaminaLoss(-stam_healing, updating_stamina = FALSE)
+ need_mob_update += source.adjustToxLoss(-main_healing, updating_health = FALSE, forced = TRUE) // Slimes are people too
+ need_mob_update += source.adjustOxyLoss(-main_healing, updating_health = FALSE)
+ if(need_mob_update)
+ source.updatehealth()
+ new /obj/effect/temp_visual/heal(get_turf(owner), COLOR_BROWN)
+ // Reduces duration of stuns/etc
+ var/stun_reduction = 0.5 * passive_level * delta_time
+ source.AdjustAllImmobility(-stun_reduction)
+ // Heals blood loss
+ if(source.blood_volume < BLOOD_VOLUME_NORMAL)
+ source.blood_volume += 2.5 * delta_time
+ for(var/datum/reagent/reagent as anything in source.reagents.reagent_list)
+ source.reagents.remove_reagent(reagent.type, 2 * reagent.purge_multiplier * REM * seconds_per_tick)
+
+ if(!iscarbon(source))
+ return
+ var/mob/living/carbon/carbon_owner = source
+ if(passive_level < HERETIC_LEVEL_UPGRADE)
+ return
+ for(var/obj/item/bodypart/wounded_limb as anything in carbon_owner.bodyparts)
+ for(var/datum/wound/to_cure as anything in wounded_limb.wounds)
+ to_cure.remove_wound()
+ for(var/obj/item/organ/internal as anything in carbon_owner.organs)
+ internal.apply_organ_damage(round(-2 * seconds_per_tick))
+ if(passive_level < HERETIC_LEVEL_FINAL)
+ return
+ if(length(carbon_owner.get_missing_limbs()))
+ carbon_owner.regenerate_limbs()
+
+//---- Void Passive
+// Level 1 Cold and Low pressure resist
+// Level 2 No breathe
+// Level 3 No slip on water/ice
+/datum/status_effect/heretic_passive/void
+ name = "Aristocrat's Way"
+ id = "void_passive"
+ passive_descriptions = list(
+ "Cold and low pressure immunity.",
+ "You no longer need to breathe.",
+ "Water, ice and slippery surfaces no slip you."
+ )
+
+/datum/status_effect/heretic_passive/void/on_apply()
+ . = ..()
+ owner.add_traits(list(TRAIT_RESISTCOLD, TRAIT_RESISTLOWPRESSURE), REF(src))
+
+/datum/status_effect/heretic_passive/void/heretic_level_upgrade()
+ . = ..()
+ ADD_TRAIT(owner, TRAIT_NOBREATH, REF(src))
+
+/datum/status_effect/heretic_passive/void/heretic_level_final()
+ . = ..()
+ owner.add_traits(list(TRAIT_NO_SLIP_WATER, TRAIT_NO_SLIP_ICE, TRAIT_NO_SLIP_SLIDE), REF(src))
+
+/datum/status_effect/heretic_passive/void/on_remove()
+ . = ..()
+ owner.remove_traits(list(TRAIT_RESISTCOLD, TRAIT_RESISTLOWPRESSURE, TRAIT_NOBREATH, TRAIT_NO_SLIP_WATER, TRAIT_NO_SLIP_ICE, TRAIT_NO_SLIP_SLIDE), REF(src))
+
+#undef HERETIC_LEVEL_START
+#undef HERETIC_LEVEL_UPGRADE
+#undef HERETIC_LEVEL_FINAL
diff --git a/code/modules/antagonists/heretic/status_effects/mark_effects.dm b/code/modules/antagonists/heretic/status_effects/mark_effects.dm
index 3069eb9b19d..0812a8dbacd 100644
--- a/code/modules/antagonists/heretic/status_effects/mark_effects.dm
+++ b/code/modules/antagonists/heretic/status_effects/mark_effects.dm
@@ -220,7 +220,7 @@
/datum/status_effect/eldritch/cosmic/on_effect()
new teleport_effect(get_turf(owner))
- new /obj/effect/forcefield/cosmic_field(get_turf(owner))
+ create_cosmic_field(get_turf(owner), owner)
do_teleport(
owner,
get_turf(cosmic_diamond),
@@ -258,12 +258,12 @@
/datum/status_effect/eldritch/moon/on_apply()
. = ..()
- if(owner.can_block_magic(MAGIC_RESISTANCE_MIND))
+ if(owner.can_block_magic(MAGIC_RESISTANCE_MOON))
return FALSE
ADD_TRAIT(owner, TRAIT_PACIFISM, TRAIT_STATUS_EFFECT(id))
owner.emote(pick("giggle", "laugh"))
owner.balloon_alert(owner, "you feel unable to hurt a soul!")
- RegisterSignal (owner, COMSIG_MOB_APPLY_DAMAGE, PROC_REF(on_damaged))
+ RegisterSignal(owner, COMSIG_MOB_APPLY_DAMAGE, PROC_REF(on_damaged))
return TRUE
/// Checks for damage so the heretic can't just attack them with another weapon whilst they are unable to fight back
@@ -292,7 +292,7 @@
/datum/status_effect/eldritch/moon/on_remove()
. = ..()
- UnregisterSignal (owner, COMSIG_MOB_APPLY_DAMAGE)
+ UnregisterSignal(owner, COMSIG_MOB_APPLY_DAMAGE)
// In case the trait was not removed earlier
REMOVE_TRAIT(owner, TRAIT_PACIFISM, TRAIT_STATUS_EFFECT(id))
diff --git a/code/modules/antagonists/heretic/status_effects/void_chill.dm b/code/modules/antagonists/heretic/status_effects/void_chill.dm
index ce302e21c3a..73d82cefb9e 100644
--- a/code/modules/antagonists/heretic/status_effects/void_chill.dm
+++ b/code/modules/antagonists/heretic/status_effects/void_chill.dm
@@ -26,6 +26,8 @@
/datum/status_effect/void_chill/on_apply()
if(issilicon(owner))
return FALSE
+ if(IS_HERETIC_OR_MONSTER(owner))
+ return FALSE
return TRUE
/datum/status_effect/void_chill/on_remove()
diff --git a/code/modules/antagonists/heretic/structures/mawed_crucible.dm b/code/modules/antagonists/heretic/structures/mawed_crucible.dm
index fa2199362ce..51fe408dfef 100644
--- a/code/modules/antagonists/heretic/structures/mawed_crucible.dm
+++ b/code/modules/antagonists/heretic/structures/mawed_crucible.dm
@@ -78,6 +78,25 @@
return
/obj/structure/destructible/eldritch_crucible/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
+ if(istype(tool, /obj/item/codex_cicatrix) || istype(tool, /obj/item/melee/touch_attack/mansus_fist))
+ playsound(src, 'sound/items/deconstruct.ogg', 30, TRUE, ignore_walls = FALSE)
+ set_anchored(!anchored)
+ balloon_alert(user, "[anchored ? "":"un"]anchored")
+ return ITEM_INTERACT_SUCCESS
+ if(istype(tool, /obj/item/reagent_containers/cup/beaker/eldritch))
+ if(current_mass < max_mass)
+ balloon_alert(user, "not full enough!")
+ return ITEM_INTERACT_SUCCESS
+ var/obj/item/reagent_containers/cup/beaker/eldritch/to_fill = tool
+ if(to_fill.reagents.total_volume >= to_fill.reagents.maximum_volume)
+ balloon_alert(user, "flask is full!")
+ return ITEM_INTERACT_SUCCESS
+ to_fill.reagents.add_reagent(/datum/reagent/eldritch, 50)
+ do_item_attack_animation(src, used_item = tool, animation_type = ATTACK_ANIMATION_BLUNT)
+ current_mass--
+ balloon_alert(user, "refilled flask")
+ return ITEM_INTERACT_SUCCESS
+
if(isbodypart(tool))
var/obj/item/bodypart/consumed = tool
if(!IS_ORGANIC_LIMB(consumed))
@@ -109,26 +128,6 @@
return NONE
-/obj/structure/destructible/eldritch_crucible/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
- if(istype(tool, /obj/item/codex_cicatrix) || istype(tool, /obj/item/melee/touch_attack/mansus_fist))
- playsound(src, 'sound/items/deconstruct.ogg', 30, TRUE, ignore_walls = FALSE)
- set_anchored(!anchored)
- balloon_alert(user, "[anchored ? "":"un"]anchored")
- return ITEM_INTERACT_SUCCESS
- if(istype(tool, /obj/item/reagent_containers/cup/beaker/eldritch))
- if(current_mass < max_mass)
- balloon_alert(user, "not full enough!")
- return ITEM_INTERACT_SUCCESS
- var/obj/item/reagent_containers/cup/beaker/eldritch/to_fill = tool
- if(to_fill.reagents.total_volume >= to_fill.reagents.maximum_volume)
- balloon_alert(user, "flask is full!")
- return ITEM_INTERACT_SUCCESS
- to_fill.reagents.add_reagent(/datum/reagent/eldritch, 50)
- do_item_attack_animation(src, used_item = tool, animation_type = ATTACK_ANIMATION_BLUNT)
- current_mass--
- balloon_alert(user, "refilled flask")
- return ITEM_INTERACT_SUCCESS
-
/obj/structure/destructible/eldritch_crucible/attack_hand(mob/user, list/modifiers)
. = ..()
if(.)
@@ -253,6 +252,8 @@
var/crucible_tip = "Doesn't do anything."
/// Typepath to the status effect this applies
var/status_effect
+ /// If you can drink the same potion while the effect is active
+ var/can_refresh = TRUE
/obj/item/eldritch_potion/examine(mob/user)
. = ..()
@@ -269,6 +270,9 @@
if(!iscarbon(user))
return
+ if(!can_refresh && user.has_status_effect(status_effect))
+ return
+
playsound(src, 'sound/effects/bubbles/bubbles.ogg', 50, TRUE)
if(!IS_HERETIC_OR_MONSTER(user))
@@ -297,7 +301,14 @@
desc = "A glass bottle containing a bright orange, translucent liquid."
icon_state = "crucible_soul"
status_effect = /datum/status_effect/crucible_soul
- crucible_tip = "Allows you to walk through walls. After expiring, you are teleported to your original location. Lasts 15 seconds."
+ crucible_tip = "Allows you to walk through walls. After expiring, you are teleported to your original location. Lasts 40 seconds."
+ can_refresh = FALSE
+
+/obj/item/eldritch_potion/crucible_soul/attack_self(mob/user)
+ if(user.has_status_effect(/datum/status_effect/crucible_soul_cooldown))
+ balloon_alert(user, "on cooldown!")
+ return TRUE
+ return ..()
/obj/item/eldritch_potion/duskndawn
name = "brew of dusk and dawn"
diff --git a/code/modules/antagonists/heretic/transmutation_rune.dm b/code/modules/antagonists/heretic/transmutation_rune.dm
index 7caa7fcb73a..b786991e5f4 100644
--- a/code/modules/antagonists/heretic/transmutation_rune.dm
+++ b/code/modules/antagonists/heretic/transmutation_rune.dm
@@ -189,7 +189,7 @@
// This doesn't necessarily mean the ritual will succeed, but it's valid!
// Do the animations and associated feedback.
flick("[icon_state]_active", src)
- playsound(user, 'sound/effects/magic/castsummon.ogg', 75, TRUE, extrarange = SILENCED_SOUND_EXTRARANGE, falloff_exponent = 10)
+ playsound(user, 'sound/effects/magic/castsummon.ogg', 50, TRUE, extrarange = SILENCED_SOUND_EXTRARANGE, falloff_exponent = 10, ignore_walls = FALSE)
// - We temporarily make all of our chosen atoms invisible, as some rituals may sleep,
// and we don't want people to be able to run off with ritual items.
diff --git a/code/modules/basketball/hoop.dm b/code/modules/basketball/hoop.dm
index 0e742c63347..14ab028fc48 100644
--- a/code/modules/basketball/hoop.dm
+++ b/code/modules/basketball/hoop.dm
@@ -148,13 +148,16 @@
var/click_on_hoop = TRUE
var/mob/living/thrower = throwingdatum?.get_thrower()
+ if(!istype(thrower))
+ return
+
// aim penalty for not clicking directly on the hoop when shooting
if(!istype(backboard) || backboard != src)
click_on_hoop = FALSE
score_chance *= 0.5
// aim penalty for spinning while shooting
- if(istype(thrower) && HAS_TRAIT(thrower, TRAIT_SPINNING))
+ if(HAS_TRAIT(thrower, TRAIT_SPINNING))
score_chance *= 0.5
if(prob(score_chance))
diff --git a/code/modules/cards/cards.dm b/code/modules/cards/cards.dm
index 50eb5a53621..046c3e47906 100644
--- a/code/modules/cards/cards.dm
+++ b/code/modules/cards/cards.dm
@@ -30,7 +30,7 @@
return .
var/mob/thrower = throwingdatum?.get_thrower()
- if(!thrower) // if a mob didn't throw it (need two people to play 52 pickup)
+ if(!istype(thrower)) // if a mob didn't throw it (need two people to play 52 pickup)
return
if(count_cards() == 0)
diff --git a/code/modules/cards/deck/deck.dm b/code/modules/cards/deck/deck.dm
index 27ec0fc7931..aa6bc6beec1 100644
--- a/code/modules/cards/deck/deck.dm
+++ b/code/modules/cards/deck/deck.dm
@@ -208,7 +208,7 @@
return .
var/mob/living/thrower = throwingdatum?.get_thrower()
- if(!thrower) // if a mob didn't throw it (need two people to play 52 pickup)
+ if(!istype(thrower)) // if a mob didn't throw it (need two people to play 52 pickup)
return
target.visible_message(span_warning("[target] is forced to play 52 card pickup!"), span_warning("You are forced to play 52 card pickup."))
diff --git a/code/modules/client/preferences/migrations/tgui_prefs_migration.dm b/code/modules/client/preferences/migrations/tgui_prefs_migration.dm
index ba4b4d03e90..2d852c96a02 100644
--- a/code/modules/client/preferences/migrations/tgui_prefs_migration.dm
+++ b/code/modules/client/preferences/migrations/tgui_prefs_migration.dm
@@ -33,7 +33,6 @@
// Before tgui preferences menu, "traitor" would handle both roundstart, midround, and latejoin.
// These were split apart.
/datum/preferences/proc/migrate_antagonists()
- migrate_antagonist(ROLE_HERETIC, list(ROLE_HERETIC_SMUGGLER))
migrate_antagonist(ROLE_MALF, list(ROLE_MALF_MIDROUND))
migrate_antagonist(ROLE_OPERATIVE, list(ROLE_OPERATIVE_MIDROUND, ROLE_LONE_OPERATIVE))
migrate_antagonist(ROLE_REV_HEAD, list(ROLE_PROVOCATEUR))
diff --git a/code/modules/deathmatch/deathmatch_loadouts.dm b/code/modules/deathmatch/deathmatch_loadouts.dm
index bc27f6b0282..1727dddf370 100644
--- a/code/modules/deathmatch/deathmatch_loadouts.dm
+++ b/code/modules/deathmatch/deathmatch_loadouts.dm
@@ -1041,8 +1041,7 @@
// I mean is it really that bad if they don't even know half this stuff is added to them.
// It's like, forbidden knowledge. It fits with the mansus theme - great excuse for poor design!
knowledge_to_grant = list(
- /datum/heretic_knowledge/blade_grasp,
- /datum/heretic_knowledge/blade_dance,
+ /datum/heretic_knowledge/limited_amount/starting/base_blade,
/datum/heretic_knowledge/blade_upgrade/blade,
)
@@ -1084,7 +1083,7 @@
)
knowledge_to_grant = list(
- /datum/heretic_knowledge/cosmic_grasp,
+ /datum/heretic_knowledge/limited_amount/starting/base_cosmic,
)
spells_to_add = list(
diff --git a/code/modules/fishing/sources/subtypes/rifts.dm b/code/modules/fishing/sources/subtypes/rifts.dm
index b9d4a7f39e1..b151c963bbf 100644
--- a/code/modules/fishing/sources/subtypes/rifts.dm
+++ b/code/modules/fishing/sources/subtypes/rifts.dm
@@ -157,11 +157,11 @@
fishfluence.after_drain(user)
var/datum/antagonist/heretic/heretic_datum = GET_HERETIC(user)
if(heretic_datum)
- heretic_datum.knowledge_points++
+ heretic_datum.adjust_knowledge_points(1)
to_chat(user, "[span_hear("You hear a whisper...")] [span_hypnophrase("THE HIGHER I RISE, THE MORE I FISH.")]")
// They can also gain an extra influence point if they infused their rod.
if(HAS_TRAIT(challenge.used_rod, TRAIT_ROD_MANSUS_INFUSED))
- heretic_datum.knowledge_points++
+ heretic_datum.adjust_knowledge_points(1)
to_chat(user, span_boldnotice("Your infused rod improves your knowledge gain!"))
return
diff --git a/code/modules/mapfluff/ruins/lavalandruin_code/biodome_winter.dm b/code/modules/mapfluff/ruins/lavalandruin_code/biodome_winter.dm
index 6eeffca6d09..acbd5e7332b 100644
--- a/code/modules/mapfluff/ruins/lavalandruin_code/biodome_winter.dm
+++ b/code/modules/mapfluff/ruins/lavalandruin_code/biodome_winter.dm
@@ -31,7 +31,7 @@
var/mob/thrown_by = throwingdatum?.get_thrower()
if(ismovable(hit_atom) && !caught && (!thrown_by || thrown_by && COOLDOWN_FINISHED(src, freeze_cooldown)))
freeze_hit_atom(hit_atom)
- if(thrown_by && !caught)
+ if(istype(thrown_by) && !caught)
addtimer(CALLBACK(src, TYPE_PROC_REF(/atom/movable, throw_at), thrown_by, throw_range+2, throw_speed, null, TRUE), 0.1 SECONDS)
/obj/item/freeze_cube/proc/freeze_hit_atom(atom/movable/hit_atom)
diff --git a/code/modules/mining/ores_coins.dm b/code/modules/mining/ores_coins.dm
index 6e7340dbac7..159711837e9 100644
--- a/code/modules/mining/ores_coins.dm
+++ b/code/modules/mining/ores_coins.dm
@@ -648,7 +648,7 @@ GLOBAL_LIST_INIT(sand_recipes, list(\
name = "eldritch coin"
desc = "A surprisingly heavy, ornate coin. Its sides seem to depict a different image each time you look."
icon_state = "coin_heretic"
- custom_materials = list(/datum/material/diamond =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/plasma =HALF_SHEET_MATERIAL_AMOUNT)
+ custom_materials = list(/datum/material/plasma = HALF_SHEET_MATERIAL_AMOUNT)
sideslist = list("heretic", "blade")
heads_name = "heretic"
has_action = TRUE
@@ -679,17 +679,4 @@ GLOBAL_LIST_INIT(sand_recipes, list(\
continue
target_airlock.lock()
-/obj/item/coin/eldritch/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
- if(!istype(interacting_with, /obj/machinery/door/airlock))
- return NONE
- if(!IS_HERETIC(user))
- user.adjustBruteLoss(5)
- user.adjustFireLoss(5)
- return ITEM_INTERACT_BLOCKING
- var/obj/machinery/door/airlock/target_airlock = interacting_with
- to_chat(user, span_warning("You insert [src] into the airlock."))
- target_airlock.emag_act(user, src)
- qdel(src)
- return ITEM_INTERACT_SUCCESS
-
#undef ORESTACK_OVERLAYS_MAX
diff --git a/code/modules/mob/living/basic/heretic/ash_spirit.dm b/code/modules/mob/living/basic/heretic/ash_spirit.dm
index fed64db8adc..61735861524 100644
--- a/code/modules/mob/living/basic/heretic/ash_spirit.dm
+++ b/code/modules/mob/living/basic/heretic/ash_spirit.dm
@@ -18,6 +18,9 @@
var/static/list/actions_to_add = list(
/datum/action/cooldown/spell/fire_sworn,
/datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash,
- /datum/action/cooldown/spell/pointed/cleave,
)
grant_actions_by_list(actions_to_add)
+
+/mob/living/basic/heretic_summon/ash_spirit/Life(seconds_per_tick, times_fired)
+ . = ..()
+ adjustBruteLoss(-3) // 3 health passively healing
diff --git a/code/modules/mob/living/basic/heretic/maid_in_the_mirror.dm b/code/modules/mob/living/basic/heretic/maid_in_the_mirror.dm
index 6f8c2e5c96f..ca627fd4044 100644
--- a/code/modules/mob/living/basic/heretic/maid_in_the_mirror.dm
+++ b/code/modules/mob/living/basic/heretic/maid_in_the_mirror.dm
@@ -33,6 +33,7 @@
)
AddElement(/datum/element/death_drops, loot)
GRANT_ACTION(/datum/action/cooldown/spell/jaunt/mirror_walk)
+ ADD_TRAIT(src, TRAIT_UNHITTABLE_BY_LASERS, INNATE_TRAIT)
/mob/living/basic/heretic_summon/maid_in_the_mirror/death(gibbed)
var/turf/death_turf = get_turf(src)
@@ -50,14 +51,14 @@
return
// If we have health, we take some damage
- if(health > (maxHealth * 0.125))
+ if(health > (maxHealth * 0.02))
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
+ apply_damage(maxHealth * 0.02) // We take 2% of our health as damage upon being examined
playsound(src, 'sound/effects/ghost2.ogg', 40, TRUE)
addtimer(CALLBACK(src, PROC_REF(clear_recent_examiner), user_ref), recent_examine_damage_cooldown, TIMER_DELETE_ME)
animate(src, alpha = 120, time = 0.5 SECONDS, easing = ELASTIC_EASING, loop = 2, flags = ANIMATION_PARALLEL)
@@ -78,3 +79,10 @@
recent_examiner_refs -= mob_ref
heal_overall_damage(5)
+
+/mob/living/basic/heretic_summon/maid_in_the_mirror/melee_attack(atom/target, list/modifiers, ignore_cooldown)
+ . = ..()
+ if(!. || !isliving(target))
+ return
+ var/mob/living/living_target = target
+ living_target.apply_status_effect(/datum/status_effect/void_chill, 1)
diff --git a/code/modules/mob/living/basic/heretic/star_gazer.dm b/code/modules/mob/living/basic/heretic/star_gazer.dm
index ec28da95352..6ea05af3665 100644
--- a/code/modules/mob/living/basic/heretic/star_gazer.dm
+++ b/code/modules/mob/living/basic/heretic/star_gazer.dm
@@ -35,29 +35,94 @@
mob_size = MOB_SIZE_HUGE
layer = LARGE_MOB_LAYER
flags_1 = PREVENT_CONTENTS_EXPLOSION_1
+ lighting_cutoff_red = 12
+ lighting_cutoff_green = 15
+ lighting_cutoff_blue = 34
+ sight = SEE_TURFS|SEE_MOBS|SEE_OBJS
ai_controller = /datum/ai_controller/basic_controller/star_gazer
+ /// Reference to the mob which summoned us
+ var/datum/weakref/summoner
+ /// How far we can go before being pulled back
+ var/leash_range = 20
+ /// Timer for finding a ghost so it doesn't spam dead chat with requests
+ var/begging_timer
+ ///---- Abilities given to the star gazer mob
+ var/list/abilities_to_grant = list(
+ /datum/action/cooldown/spell/conjure/cosmic_expansion,
+ /datum/action/cooldown/spell/pointed/projectile/star_blast,
+ /datum/action/cooldown/recall_stargazer,
+ /datum/action/cooldown/spell/stargazer_laser,
+ )
-/mob/living/basic/heretic_summon/star_gazer/Initialize(mapload)
+/mob/living/basic/heretic_summon/star_gazer/Initialize(mapload, mob/living/master)
. = ..()
+ for(var/datum/action/cooldown/spell/spell as anything in abilities_to_grant)
+ spell = new spell(src)
+ spell.Grant(src)
+ var/datum/action/cooldown/spell/stargazer_laser/laser = locate() in actions
+ if(master)
+ summoner = WEAKREF(master)
+ if(laser)
+ laser.our_master = WEAKREF(master)
+ AddComponent(/datum/component/seethrough_mob)
+ var/static/list/death_loot = list(/obj/effect/temp_visual/cosmic_domain)
+ AddElement(/datum/element/death_drops, death_loot)
AddElement(/datum/element/death_drops, /obj/effect/temp_visual/cosmic_domain)
AddElement(/datum/element/death_explosion, 3, 6, 12)
AddElement(/datum/element/footstep, FOOTSTEP_MOB_SHOE)
AddElement(/datum/element/wall_smasher, ENVIRONMENT_SMASH_RWALLS)
AddElement(/datum/element/simple_flying)
- AddElement(/datum/element/effect_trail, /obj/effect/forcefield/cosmic_field/fast)
+ AddElement(/datum/element/effect_trail/cosmic_field/antiprojectile, /obj/effect/forcefield/cosmic_field/fast)
AddElement(/datum/element/ai_target_damagesource)
AddComponent(/datum/component/regenerator, outline_colour = "#b97a5d")
ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT)
ADD_TRAIT(src, TRAIT_LAVA_IMMUNE, INNATE_TRAIT)
ADD_TRAIT(src, TRAIT_ASHSTORM_IMMUNE, INNATE_TRAIT)
- ADD_TRAIT(src, TRAIT_NO_TELEPORT, MEGAFAUNA_TRAIT)
ADD_TRAIT(src, TRAIT_MARTIAL_ARTS_IMMUNE, MEGAFAUNA_TRAIT)
ADD_TRAIT(src, TRAIT_NO_FLOATING_ANIM, INNATE_TRAIT)
+ ADD_TRAIT(src, TRAIT_MAGICALLY_GIFTED, INNATE_TRAIT)
set_light(4, l_color = "#dcaa5b")
+ INVOKE_ASYNC(src, PROC_REF(beg_for_ghost))
+ RegisterSignal(src, COMSIG_MOB_GHOSTIZED, PROC_REF(beg_for_ghost))
+
+/mob/living/basic/heretic_summon/star_gazer/Destroy()
+ deltimer(begging_timer)
+ return ..()
+
+/// Tries to find a ghost to take control of the mob. If no ghost accepts, ask again in a bit
+/mob/living/basic/heretic_summon/star_gazer/proc/beg_for_ghost()
+ if(timeleft(begging_timer) && !client)
+ return
+ begging_timer = addtimer(CALLBACK(src, PROC_REF(beg_for_ghost)), 2 MINUTES, TIMER_STOPPABLE | TIMER_UNIQUE) // Keep begging until someone accepts
+ var/mob/chosen_ghost = SSpolling.poll_ghost_candidates(
+ "Do you want to play as an ascended heretic's stargazer?",
+ check_jobban = ROLE_HERETIC,
+ poll_time = 20 SECONDS,
+ ignore_category = POLL_IGNORE_HERETIC_MONSTER,
+ alert_pic = mutable_appearance('icons/effects/eldritch.dmi', "cosmic_diamond"),
+ jump_target = src,
+ role_name_text = "star gazer",
+ amount_to_pick = 1
+ )
+ if(chosen_ghost)
+ PossessByPlayer(chosen_ghost.key)
+ deltimer(begging_timer)
+
+/// Connects these two mobs by a leash
+/mob/living/basic/heretic_summon/star_gazer/proc/leash_to(atom/movable/leashed, atom/movable/leashed_to)
+ leashed.AddComponent(\
+ /datum/component/leash,\
+ owner = leashed_to,\
+ distance = leash_range,\
+ force_teleport_out_effect = /obj/effect/temp_visual/guardian/phase/out,\
+ force_teleport_in_effect = /obj/effect/temp_visual/guardian/phase,\
+ )
// Star gazer attacks everything around itself applies a spooky mark
/mob/living/basic/heretic_summon/star_gazer/melee_attack(mob/living/target, list/modifiers, ignore_cooldown)
+ if(target == summoner?.resolve())
+ return FALSE
. = ..()
if (!. || !isliving(target))
return
@@ -74,6 +139,261 @@
do_attack_animation(nearby_mob, ATTACK_EFFECT_SLASH)
log_combat(src, nearby_mob, "slashed")
+/datum/action/cooldown/recall_stargazer
+ name = "Seek master"
+ desc = "Teleports you to your master"
+ background_icon_state = "bg_heretic"
+ overlay_icon_state = "bg_heretic_border"
+ button_icon = 'icons/mob/actions/actions_ecult.dmi'
+ button_icon_state = "stargazer_menu"
+ check_flags = NONE
+ cooldown_time = 5 SECONDS
+
+/datum/action/cooldown/recall_stargazer/Activate(atom/target)
+ var/mob/living/basic/heretic_summon/star_gazer/real_owner = owner
+ var/mob/living/master = real_owner.summoner?.resolve()
+ if(!master)
+ return FALSE
+ do_teleport(owner, master, no_effects = TRUE, channel = TELEPORT_CHANNEL_MAGIC, forced = TRUE)
+ StartCooldown()
+ return TRUE
+
+/datum/action/cooldown/spell/stargazer_laser
+ name = "Star Gaze"
+ desc = "Generates a massive death beam that eradicates everything in it's path. \
+ Has it's own gravitational pull, sucking in new victims."
+ background_icon_state = "bg_heretic"
+ overlay_icon_state = "bg_heretic_border"
+ button_icon = 'icons/mob/actions/actions_ecult.dmi'
+ button_icon_state = "gazer_beam_charge"
+ check_flags = NONE
+ cooldown_time = 30 SECONDS
+ invocation = "SH''P D' W''P"
+ invocation_type = INVOCATION_SHOUT
+ spell_requirements = NONE
+ /// list of turfs we are hitting while shooting our beam
+ var/list/turf/targets
+ /// The laser beam we generate
+ var/datum/beam/giga_laser
+ /// Timer that handles the damage ticking
+ var/damage_timer
+ /// Reference to our summoner so that we don't disintegrate them by accident
+ var/datum/weakref/our_master
+ /// The overlay on the caster when they fire the beam
+ var/obj/effect/abstract/gazer_orb/orb_visual
+ /// The visual effect at the beginning of the laser
+ var/obj/effect/abstract/gazer_beam/beam_visual
+ /// List of visual effects for the beam, in between and in the end
+ var/list/beam_fillings
+ /// Sound loop for the active laser
+ var/datum/looping_sound/gazer_beam/sound_loop
+ /// The visual effect at the end of the laser
+ var/obj/effect/abstract/gazer_beamend/end_visual
+ /// Tracks how many times the beam has processed, after the maximum amount of cycles it will forcibly end the beam
+ var/cycle_tracker = 0
+
+/datum/action/cooldown/spell/stargazer_laser/cast(atom/target)
+ . = ..()
+
+ cooldown_time = initial(cooldown_time)
+ if(damage_timer)
+ stop_beaming()
+
+ var/turf/check_turf = get_step(owner, owner.dir)
+ var/list/turf/targets_left = list()
+ targets_left += get_step(check_turf, turn(owner.dir, 90))
+ var/list/turf/targets_right = list()
+ targets_right += get_step(check_turf, turn(owner.dir, -90))
+ LAZYINITLIST(targets)
+ while(check_turf && length(targets) < 20)
+ targets += check_turf
+ check_turf = get_step(check_turf, owner.dir)
+ targets_left += get_step(check_turf, turn(owner.dir, 90))
+ targets_right += get_step(check_turf, turn(owner.dir, -90))
+ if(!LAZYLEN(targets))
+ return
+
+ RegisterSignals(owner, list(COMSIG_MOVABLE_MOVED, COMSIG_ATOM_DIR_CHANGE), PROC_REF(stop_beaming))
+ beam_fillings = list()
+ cycle_tracker = 0
+ orb_visual = new(get_step(owner, owner.dir))
+ var/beam_timer = addtimer(CALLBACK(src, PROC_REF(open_laser), owner, targets), 2.2 SECONDS, TIMER_STOPPABLE)
+ playsound(owner, 'sound/mobs/non-humanoids/stargazer/beam_open.ogg', 50, FALSE)
+ if(!do_after(owner, 3 SECONDS, owner))
+ cooldown_time = 1 SECONDS
+ deltimer(beam_timer)
+ QDEL_NULL(orb_visual)
+ QDEL_NULL(beam_visual)
+ QDEL_NULL(end_visual)
+ for(var/atom/to_delete as anything in beam_fillings)
+ QDEL_NULL(to_delete)
+ targets = null
+ return
+
+ sound_loop.start(owner)
+ QDEL_NULL(orb_visual)
+ beam_visual.icon_state = "gazer_beam_active"
+ beam_visual.update_appearance(UPDATE_ICON)
+ end_visual.icon_state = "gazer_beam_end"
+ end_visual.update_appearance(UPDATE_ICON)
+ targets += targets_left
+ targets += targets_right
+ process_beam()
+
+/datum/action/cooldown/spell/stargazer_laser/Destroy()
+ QDEL_NULL(sound_loop)
+ return ..()
+
+/datum/action/cooldown/spell/stargazer_laser/New(Target, original)
+ . = ..()
+ sound_loop = new
+
+/// Spawns the beginning of the laser, uses `targets` to determine the rotation
+/datum/action/cooldown/spell/stargazer_laser/proc/open_laser(mob/owner, list/turf/targets)
+ beam_visual = new(get_step(get_step(owner, owner.dir), owner.dir), targets[length(targets)])
+ end_visual = new(targets[length(targets)], owner)
+ for(var/turf/to_fill as anything in (get_line(targets[4], targets[length(targets)-2])))
+ var/obj/effect/abstract/gazer_beam_filling/new_filling = new(to_fill, owner.dir)
+ beam_fillings += new_filling
+
+// Visual effect of the big orb when you start channeling the laser
+/obj/effect/abstract/gazer_orb
+ icon = 'icons/effects/160x160.dmi'
+ icon_state = "gazer_beam_charge"
+ SET_BASE_VISUAL_PIXEL(-64, -64)
+
+// Visual effect at the start of the beam, has an opening/active/closing state
+/obj/effect/abstract/gazer_beam
+ icon = 'icons/effects/beam96x96.dmi'
+ SET_BASE_VISUAL_PIXEL(-32, -32)
+
+/obj/effect/abstract/gazer_beam/Initialize(mapload, turf/target)
+ . = ..()
+ if(!target)
+ return INITIALIZE_HINT_QDEL
+ var/Angle = get_angle_raw(x, y, pixel_x, pixel_y, target.x , target.y, target.pixel_x, target.pixel_y)
+ var/matrix/transform_matrix = matrix()
+ Angle = round(Angle, 45)
+ transform_matrix.Turn(Angle-90)
+ transform_matrix.Scale(2, 2)
+ transform = transform_matrix
+ flick("gazer_beam_start", src)
+
+// Visual effect of the middle of the beam, has an opening/active/closing state
+/obj/effect/abstract/gazer_beam_filling
+ icon = 'icons/effects/beam.dmi'
+ icon_state = "gazer_beam"
+
+/obj/effect/abstract/gazer_beam_filling/Initialize(mapload, direction)
+ . = ..()
+ if(!direction)
+ return INITIALIZE_HINT_QDEL
+ var/Angle = dir2angle(direction)
+ var/matrix/transform_matrix = matrix()
+ transform_matrix.Turn(Angle)
+ transform_matrix.Scale(2, 2)
+ transform = transform_matrix
+ flick("gazer_beam_end_opening", src)
+
+/obj/effect/abstract/gazer_beam_filling/proc/pull_victims()
+ for(var/atom/movable/movable_atom in orange(5, src))
+ if((movable_atom.anchored || movable_atom.move_resist >= MOVE_FORCE_EXTREMELY_STRONG))
+ continue
+ if(ismob(movable_atom))
+ var/mob/pulled_mob = movable_atom
+ if(pulled_mob.mob_negates_gravity())
+ continue
+ step_towards(movable_atom, src)
+
+// Visual effect at the end of the beam, has an opening/active/closing state
+/obj/effect/abstract/gazer_beamend
+ icon = 'icons/effects/beam.dmi'
+
+/obj/effect/abstract/gazer_beamend/Initialize(mapload, atom/origin)
+ . = ..()
+ if(!origin)
+ return INITIALIZE_HINT_QDEL
+ var/Angle = get_angle_raw(origin.x , origin.y, origin.pixel_x, origin.pixel_y, x, y, pixel_x, pixel_y)
+ var/matrix/transform_matrix = matrix()
+ Angle = round(Angle, 45)
+ transform_matrix.Turn(Angle)
+ transform_matrix.Scale(2, 2)
+ transform = transform_matrix
+ flick("gazer_beam_end_opening", src)
+
+/datum/looping_sound/gazer_beam
+ mid_sounds = list('sound/mobs/non-humanoids/stargazer/beam_loop_one.ogg')
+ mid_length = 109
+ volume = 80
+
+/obj/effect/ebeam/phase_in // Beam subtype that has a "windup" phase
+ alpha = 0
+
+/obj/effect/ebeam/phase_in/Initialize(mapload)
+ . = ..()
+ animate(src, 2 SECONDS, alpha = 255, transform = matrix(3, 1, MATRIX_SCALE))
+
+/obj/effect/ebeam/phased_in/Initialize(mapload, beam_owner) // phased in, fully powered laser
+ . = ..()
+ transform = matrix(2, 2, MATRIX_SCALE)
+
+/// Recursive proc which affects whatever is caught within the beam
+/datum/action/cooldown/spell/stargazer_laser/proc/process_beam()
+ if(cycle_tracker > 33)
+ stop_beaming()
+ for(var/obj/effect/abstract/gazer_beam_filling/fillings as anything in beam_fillings)
+ if(prob(98))
+ continue
+ // 2% chance to pull you towards the beam
+ fillings.pull_victims()
+ for(var/turf/target as anything in targets)
+ if(iswallturf(target))
+ var/turf/closed/wall/wall_target = target
+ wall_target.dismantle_wall(devastated = TRUE)
+ continue
+ if(isfloorturf(target))
+ var/turf/open/floor/to_burn = target
+ to_burn.burn_tile()
+ for(var/victim in target)
+ if(isobj(victim))
+ var/obj/to_obliterate = victim
+ if(to_obliterate.resistance_flags & INDESTRUCTIBLE)
+ continue
+ to_obliterate.atom_destruction(FIRE)
+ if(isliving(victim))
+ if(victim == our_master?.resolve())
+ continue
+ var/mob/living/living_victim = victim
+ if(living_victim.stat > CONSCIOUS)
+ playsound(living_victim, 'sound/effects/supermatter.ogg', 80, TRUE)
+ living_victim.visible_message(
+ span_danger("You see [living_victim] engulfed in the scorching wrath of the cosmos. \
+ For a moment, you see their silhouette flail in agony before fading to mere atoms."),
+ span_boldbig(span_hypnophrase("THE POWER OF THE COSMOS ITSELF POURS OUT OVER YOUR FORM. \
+ WAVES OF HEAT LATCH ONTO YOUR BODY, PULLING IT APART AT THE SEAMS. \
+ YOUR TOTAL ANNIHILATION TAKES ONLY A MOMENT BEFORE YOU ARE REDUCED BACK TO WHAT YOU ALWAYS WERE. \
+ MOTES OF MERE DUST..."))
+ )
+ living_victim.dust()
+ living_victim.emote("scream")
+ living_victim.apply_status_effect(/datum/status_effect/star_mark)
+ living_victim.apply_damage(damage = 30, damagetype = BURN, spread_damage = TRUE)
+ cycle_tracker++
+ damage_timer = addtimer(CALLBACK(src, PROC_REF(process_beam)), 0.3 SECONDS, TIMER_STOPPABLE)
+
+/// Stops the beam after we cancel it
+/datum/action/cooldown/spell/stargazer_laser/proc/stop_beaming()
+ SIGNAL_HANDLER
+ sound_loop.stop()
+ UnregisterSignal(owner, list(COMSIG_MOVABLE_MOVED, COMSIG_ATOM_DIR_CHANGE))
+ QDEL_NULL(giga_laser)
+ QDEL_NULL(beam_visual)
+ QDEL_NULL(end_visual)
+ QDEL_LIST(beam_fillings)
+ deltimer(damage_timer)
+ damage_timer = null
+ targets = null
+
/datum/ai_controller/basic_controller/star_gazer
blackboard = list(
BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic,
diff --git a/code/modules/mob/living/brain/brain_item.dm b/code/modules/mob/living/brain/brain_item.dm
index e4dd1b961ef..1a35fe9cedf 100644
--- a/code/modules/mob/living/brain/brain_item.dm
+++ b/code/modules/mob/living/brain/brain_item.dm
@@ -338,6 +338,8 @@
return ..()
/obj/item/organ/brain/on_life(seconds_per_tick, times_fired)
+ if(HAS_TRAIT(src, TRAIT_BRAIN_DAMAGE_NODEATH))
+ return
if(damage >= BRAIN_DAMAGE_DEATH) //rip
to_chat(owner, span_userdanger("The last spark of life in your brain fizzles out..."))
owner.investigate_log("has been killed by brain damage.", INVESTIGATE_DEATHS)
@@ -541,6 +543,9 @@
. += BT
/obj/item/organ/brain/proc/can_gain_trauma(datum/brain_trauma/trauma, resilience, natural_gain = FALSE)
+ if(HAS_TRAIT(src, TRAIT_BRAIN_TRAUMA_IMMUNITY))
+ return FALSE
+
if(!ispath(trauma))
trauma = trauma.type
if(!initial(trauma.can_gain))
diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm
index 8315e470f7b..518ba53e769 100644
--- a/code/modules/mob/living/carbon/carbon_defense.dm
+++ b/code/modules/mob/living/carbon/carbon_defense.dm
@@ -85,6 +85,12 @@
return ..()
/mob/living/carbon/send_item_attack_message(obj/item/weapon, mob/living/user, hit_area, def_zone)
+ // In the future replace these with parent call if the item attack message proc is ever unshittified
+ if(SEND_SIGNAL(weapon, COMSIG_SEND_ITEM_ATTACK_MESSAGE_OBJECT, src, user) & SIGNAL_MESSAGE_MODIFIED)
+ return TRUE
+ if(SEND_SIGNAL(src, COMSIG_SEND_ITEM_ATTACK_MESSAGE_CARBON, weapon, user) & SIGNAL_MESSAGE_MODIFIED)
+ return TRUE
+
if(!weapon.force && !length(weapon.attack_verb_simple) && !length(weapon.attack_verb_continuous))
return
var/obj/item/bodypart/hit_bodypart = get_bodypart(def_zone)
diff --git a/code/modules/mob/living/carbon/human/init_signals.dm b/code/modules/mob/living/carbon/human/init_signals.dm
index 827716b91a6..610743a474a 100644
--- a/code/modules/mob/living/carbon/human/init_signals.dm
+++ b/code/modules/mob/living/carbon/human/init_signals.dm
@@ -5,7 +5,7 @@
RegisterSignals(src, list(SIGNAL_ADDTRAIT(TRAIT_DWARF), SIGNAL_REMOVETRAIT(TRAIT_DWARF)), PROC_REF(on_dwarf_trait))
RegisterSignals(src, list(SIGNAL_ADDTRAIT(TRAIT_TOO_TALL), SIGNAL_REMOVETRAIT(TRAIT_TOO_TALL)), PROC_REF(on_tootall_trait))
- RegisterSignals(src, list(SIGNAL_ADDTRAIT(TRAIT_FAT), SIGNAL_REMOVETRAIT(TRAIT_FAT)), PROC_REF(on_fat))
+ RegisterSignals(src, list(SIGNAL_ADDTRAIT(TRAIT_FAT), SIGNAL_REMOVETRAIT(TRAIT_FAT), SIGNAL_ADDTRAIT(TRAIT_FAT_IGNORE_SLOWDOWN), SIGNAL_REMOVETRAIT(TRAIT_FAT_IGNORE_SLOWDOWN)), PROC_REF(on_fat))
RegisterSignals(src, list(SIGNAL_ADDTRAIT(TRAIT_NOHUNGER), SIGNAL_REMOVETRAIT(TRAIT_NOHUNGER)), PROC_REF(on_nohunger))
RegisterSignal(src, COMSIG_ATOM_CONTENTS_WEIGHT_CLASS_CHANGED, PROC_REF(check_pocket_weght))
@@ -37,7 +37,7 @@
hud_used?.hunger?.update_hunger_bar()
mob_mood?.update_nutrition_moodlets()
- if(HAS_TRAIT(src, TRAIT_FAT))
+ if(HAS_TRAIT(src, TRAIT_FAT) && !HAS_TRAIT(src, TRAIT_FAT_IGNORE_SLOWDOWN))
add_movespeed_modifier(/datum/movespeed_modifier/obesity)
else
remove_movespeed_modifier(/datum/movespeed_modifier/obesity)
diff --git a/code/modules/mob/living/simple_animal/hostile/illusion.dm b/code/modules/mob/living/simple_animal/hostile/illusion.dm
index 65c03c446df..81de3c901b7 100644
--- a/code/modules/mob/living/simple_animal/hostile/illusion.dm
+++ b/code/modules/mob/living/simple_animal/hostile/illusion.dm
@@ -1,3 +1,6 @@
+#define ATTACK_MODE_ATTACK "attack_mode"
+#define ATTACK_MODE_SHOVE "shove_mode"
+
/mob/living/simple_animal/hostile/illusion
name = "illusion"
desc = "It's a fake!"
@@ -22,8 +25,22 @@
var/datum/weakref/parent_mob_ref
/// Prob of getting a clone on attack
var/multiply_chance = 0
+ /// Decides how the clones attack people
+ var/attack_mode = ATTACK_MODE_ATTACK
-/mob/living/simple_animal/hostile/illusion/proc/Copy_Parent(mob/living/original, life = 5 SECONDS, hp = 100, damage = 0, replicate = 0)
+/mob/living/simple_animal/hostile/illusion/Initialize(mapload)
+ . = ..()
+ RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(check_mode))
+
+/// Called before trying to attack something
+/mob/living/simple_animal/hostile/illusion/proc/check_mode(mob/living/source, atom/attacked_target)
+ SIGNAL_HANDLER
+ if(attack_mode != ATTACK_MODE_SHOVE)
+ return
+ if(disarm(attacked_target))
+ return COMPONENT_HOSTILE_NO_ATTACK
+
+/mob/living/simple_animal/hostile/illusion/proc/Copy_Parent(mob/living/original, life = 5 SECONDS, hp = 100, damage = 0, replicate = 0, attack_mode = ATTACK_MODE_ATTACK)
appearance = original.appearance
parent_mob_ref = WEAKREF(original)
setDir(original.dir)
@@ -36,6 +53,7 @@
transform = initial(transform)
pixel_x = base_pixel_x
pixel_y = base_pixel_y
+ src.attack_mode = attack_mode
addtimer(CALLBACK(src, TYPE_PROC_REF(/mob/living, death)), life)
/mob/living/simple_animal/hostile/illusion/examine(mob/user)
@@ -81,3 +99,6 @@
/mob/living/simple_animal/hostile/illusion/mirage/death(gibbed)
do_sparks(rand(3, 6), FALSE, src)
return ..()
+
+#undef ATTACK_MODE_ATTACK
+#undef ATTACK_MODE_SHOVE
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index ca1134b2e69..2c7b6f9ba36 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -147,6 +147,8 @@
. = ..()
if(isliving(hit_atom))
var/mob/living/thrower = throwingdatum?.get_thrower()
+ if(!isliving(thrower))
+ return
toss_gun_hard(thrower, hit_atom)
/obj/item/gun/proc/toss_gun_hard(mob/living/thrower, mob/living/target) //throw a gun at them. They don't expect it.
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index b613e641a35..6341dc58244 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -635,6 +635,8 @@
var/mob/living/living_target = target
living_target.block_projectile_effects()
return FALSE
+ if(HAS_TRAIT(target, TRAIT_UNHITTABLE_BY_LASERS) && (armor_flag & LASER))
+ return FALSE
if(!ignore_source_check && firer && !direct_target)
if(target == firer || (target == firer.loc && ismecha(firer.loc)) || (target in firer.buckled_mobs))
return FALSE
diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm
index f50d488e67b..48d16e3644c 100644
--- a/code/modules/reagents/chemistry/reagents/other_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm
@@ -2917,6 +2917,11 @@
need_mob_update += drinker.adjustFireLoss(-2 * REM * seconds_per_tick, updating_health = FALSE)
if(drinker.blood_volume < BLOOD_VOLUME_NORMAL)
drinker.blood_volume += 3 * REM * seconds_per_tick
+ // Slowly regulates your body temp
+ drinker.adjust_bodytemperature((drinker.get_body_temp_normal() - drinker.bodytemperature) / 5)
+ for(var/datum/reagent/reagent as anything in drinker.reagents.reagent_list)
+ if(reagent != src)
+ drinker.reagents.remove_reagent(reagent.type, 2 * reagent.purge_multiplier * REM * seconds_per_tick)
else
need_mob_update = drinker.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3 * REM * seconds_per_tick, 150)
need_mob_update += drinker.adjustToxLoss(2 * REM * seconds_per_tick, updating_health = FALSE)
diff --git a/code/modules/reagents/reagent_containers/cups/drinks.dm b/code/modules/reagents/reagent_containers/cups/drinks.dm
index 389696f07c4..989820216c6 100644
--- a/code/modules/reagents/reagent_containers/cups/drinks.dm
+++ b/code/modules/reagents/reagent_containers/cups/drinks.dm
@@ -18,6 +18,8 @@
. = ..()
if(!.) //if the bottle wasn't caught
var/mob/thrower = throwingdatum?.get_thrower()
+ if(!istype(thrower))
+ return
smash(hit_atom, thrower, throwingdatum)
/obj/item/reagent_containers/cup/glass/proc/smash(atom/target, mob/thrower, datum/thrownthing/throwingdatum, break_top = FALSE)
@@ -302,7 +304,7 @@
if(prob(flip_chance)) // landed upright
src.visible_message(span_notice("[src] lands upright!"))
var/mob/living/thrower = throwingdatum?.get_thrower()
- if(thrower)
+ if(istype(thrower))
thrower.add_mood_event("bottle_flip", /datum/mood_event/bottle_flip)
else // landed on its side
animate(src, transform = matrix(prob(50)? 90 : -90, MATRIX_ROTATE), time = 3, loop = 0)
diff --git a/code/modules/recycling/disposal/bin.dm b/code/modules/recycling/disposal/bin.dm
index 53c68cc241b..32ae85b1720 100644
--- a/code/modules/recycling/disposal/bin.dm
+++ b/code/modules/recycling/disposal/bin.dm
@@ -561,7 +561,7 @@ GLOBAL_VAR_INIT(disposals_animals_spawned, 0)
/obj/machinery/disposal/bin/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
if(isitem(AM) && AM.CanEnterDisposals())
var/mob/thrower = throwingdatum?.get_thrower()
- if((thrower && HAS_TRAIT(thrower, TRAIT_THROWINGARM)) || prob(75))
+ if((istype(thrower) && HAS_TRAIT(thrower, TRAIT_THROWINGARM)) || prob(75))
AM.forceMove(src)
visible_message(span_notice("[AM] lands in [src]."))
update_appearance()
diff --git a/code/modules/surgery/organs/_organ.dm b/code/modules/surgery/organs/_organ.dm
index 69f0809dc89..7e969ee4843 100644
--- a/code/modules/surgery/organs/_organ.dm
+++ b/code/modules/surgery/organs/_organ.dm
@@ -249,6 +249,7 @@ INITIALIZE_IMMEDIATE(/obj/item/organ)
if(required_organ_flag && !(organ_flags & required_organ_flag))
return FALSE
damage = clamp(damage + damage_amount, 0, maximum)
+ SEND_SIGNAL(src, COMSIG_ORGAN_ADJUST_DAMAGE, damage_amount, maximum, required_organ_flag)
. = (prev_damage - damage) // return net damage
var/message = check_damage_thresholds(owner)
prev_damage = damage
diff --git a/code/modules/surgery/organs/internal/lungs/_lungs.dm b/code/modules/surgery/organs/internal/lungs/_lungs.dm
index c15dd74a263..9d725440220 100644
--- a/code/modules/surgery/organs/internal/lungs/_lungs.dm
+++ b/code/modules/surgery/organs/internal/lungs/_lungs.dm
@@ -752,13 +752,13 @@
// Low pressure.
if(breath_pp)
var/ratio = safe_breath_min / breath_pp
- suffocator.adjustOxyLoss(min(5 * ratio, HUMAN_MAX_OXYLOSS))
+ suffocator.apply_damage(min(5 * ratio, HUMAN_MAX_OXYLOSS), OXY)
return mole_count * ratio / 6
// Zero pressure.
if(suffocator.health >= suffocator.crit_threshold)
- suffocator.adjustOxyLoss(HUMAN_MAX_OXYLOSS)
+ suffocator.apply_damage(HUMAN_MAX_OXYLOSS, OXY)
else
- suffocator.adjustOxyLoss(HUMAN_CRIT_MAX_OXYLOSS)
+ suffocator.apply_damage(HUMAN_CRIT_MAX_OXYLOSS, OXY)
/obj/item/organ/lungs/proc/handle_breath_temperature(datum/gas_mixture/breath, mob/living/carbon/human/breather) // called by human/life, handles temperatures
diff --git a/code/modules/tgui/external.dm b/code/modules/tgui/external.dm
index 5d93ee76541..574edae9f73 100644
--- a/code/modules/tgui/external.dm
+++ b/code/modules/tgui/external.dm
@@ -73,6 +73,17 @@
for (var/datum/tgui/window as anything in open_uis)
window.send_full_update()
+/**
+ * public
+ *
+ * Will force an update on non-static data for all viewers.
+ * Use when you are manually controlling UI data updates,
+ * such as when you are not using the auto-update system.
+ */
+/datum/proc/update_data_for_all_viewers()
+ for(var/datum/tgui/ui as anything in open_uis)
+ ui.send_update()
+
/**
* public
*
diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm
index 1d387ffa057..8a184d7e966 100644
--- a/code/modules/unit_tests/_unit_tests.dm
+++ b/code/modules/unit_tests/_unit_tests.dm
@@ -174,7 +174,6 @@
#include "gloves_and_shoes_armor.dm"
#include "greyscale_config.dm"
#include "hallucination_icons.dm"
-#include "heretic_knowledge.dm"
#include "heretic_rituals.dm"
#include "high_five.dm"
#include "holder_loving.dm"
diff --git a/code/modules/unit_tests/heretic_knowledge.dm b/code/modules/unit_tests/heretic_knowledge.dm
deleted file mode 100644
index 4154e50285e..00000000000
--- a/code/modules/unit_tests/heretic_knowledge.dm
+++ /dev/null
@@ -1,46 +0,0 @@
-
-/*
- * This test checks all heretic knowledge nodes and validates they are setup correctly.
- * We check that all knowledge is reachable by players (through the research tree)
- * and that all knowledge have a valid next_knowledge list.
- */
-/datum/unit_test/heretic_knowledge
-
-/datum/unit_test/heretic_knowledge/Run()
- if(!GLOB.heretic_research_tree)
- GLOB.heretic_research_tree = generate_heretic_research_tree()
- // First, we get a list of all knowledge types
- // EXCLUDING all abstract types
- var/list/all_possible_knowledge = typesof(/datum/heretic_knowledge)
- for(var/datum/heretic_knowledge/knowledge_type as anything in all_possible_knowledge)
- if(initial(knowledge_type.abstract_parent_type) == knowledge_type)
- all_possible_knowledge -= knowledge_type
-
- // Now, let's build a list of all researchable knowledge
- // from the ground up. We start with all starting knowledge,
- // then add the next possible knowledges back into the list
- // repeatedly, until we run out of knowledges to add.
- var/list/list_to_check = GLOB.heretic_start_knowledge.Copy()
- var/i = 0
- while(i < length(list_to_check))
- var/datum/heretic_knowledge/knowledge = list_to_check[++i]
- if(!ispath(knowledge))
- TEST_FAIL("Heretic Knowledge: Got a non-heretic knowledge datum (Got: [knowledge]) in the list knowledges!")
- // Next knowledge is a list of typepaths.
- for(var/datum/heretic_knowledge/next_knowledge as anything in GLOB.heretic_research_tree[knowledge][HKT_NEXT])
- if(!ispath(next_knowledge))
- TEST_FAIL("Heretic Knowledge: [next_knowledge.type] has a [isnull(next_knowledge) ? "null":"invalid path"] in its next_knowledge list!")
- continue
- if(next_knowledge in list_to_check)
- continue
- list_to_check += next_knowledge
-
-
- // We now have a list that SHOULD contain all knowledges with a path set (list_to_check).
- // Let's compare it to our original list (all_possible_knowledge). If they're not identical,
- // then somewhere we missed a knowledge somewhere, and should throw a fail.
- if(length(all_possible_knowledge) != length(all_possible_knowledge & list_to_check))
- // Unreachables is a list of typepaths - all paths that cannot be obtained.
- var/list/unreachables = all_possible_knowledge - list_to_check
- for(var/datum/heretic_knowledge/lost_knowledge as anything in unreachables)
- TEST_FAIL("Heretic Knowledge: [lost_knowledge] is unreachable by players! Add it to another knowledge's 'next_knowledge' list. If it is purposeful, set its route to 'null'.")
diff --git a/config/config.txt b/config/config.txt
index 3e61e813dc0..688a0cb5f4f 100644
--- a/config/config.txt
+++ b/config/config.txt
@@ -651,3 +651,6 @@ TGUI_MAX_CHUNK_COUNT 32
## While this is uncommented, server initialization will include some asset generation usually useful for development.
## It increases initialization time significantly so you'll want to disable this in live environments.
GENERATE_ASSETS_IN_INIT
+
+## Minimum time before a heretic can ascend, in minutes
+# MINIMUM_ASCENSION_TIME 3
diff --git a/icons/effects/160x160.dmi b/icons/effects/160x160.dmi
index a63c813149b..09da69cd4a6 100644
Binary files a/icons/effects/160x160.dmi and b/icons/effects/160x160.dmi differ
diff --git a/icons/effects/beam.dmi b/icons/effects/beam.dmi
index 9915bcf238d..a4a9bbb8443 100644
Binary files a/icons/effects/beam.dmi and b/icons/effects/beam.dmi differ
diff --git a/icons/effects/beam96x96.dmi b/icons/effects/beam96x96.dmi
new file mode 100644
index 00000000000..fc3849ff99a
Binary files /dev/null and b/icons/effects/beam96x96.dmi differ
diff --git a/icons/hud/moon_health_64x64.dmi b/icons/hud/moon_health_64x64.dmi
new file mode 100644
index 00000000000..d8faec1b7df
Binary files /dev/null and b/icons/hud/moon_health_64x64.dmi differ
diff --git a/icons/hud/screen_alert.dmi b/icons/hud/screen_alert.dmi
index ffe69b9acf4..328056ef243 100644
Binary files a/icons/hud/screen_alert.dmi and b/icons/hud/screen_alert.dmi differ
diff --git a/icons/mob/actions/actions_ecult.dmi b/icons/mob/actions/actions_ecult.dmi
index 9710dfd3f50..d808f6dd0a9 100644
Binary files a/icons/mob/actions/actions_ecult.dmi and b/icons/mob/actions/actions_ecult.dmi differ
diff --git a/icons/mob/actions/backgrounds.dmi b/icons/mob/actions/backgrounds.dmi
index b2d88058f93..4ffe357b783 100644
Binary files a/icons/mob/actions/backgrounds.dmi and b/icons/mob/actions/backgrounds.dmi differ
diff --git a/icons/mob/clothing/head/helmet.dmi b/icons/mob/clothing/head/helmet.dmi
index 9c7389788ce..b2cb68fd2fa 100644
Binary files a/icons/mob/clothing/head/helmet.dmi and b/icons/mob/clothing/head/helmet.dmi differ
diff --git a/icons/mob/clothing/suits/armor.dmi b/icons/mob/clothing/suits/armor.dmi
index ec9836039ea..be6ab18d646 100644
Binary files a/icons/mob/clothing/suits/armor.dmi and b/icons/mob/clothing/suits/armor.dmi differ
diff --git a/icons/mob/effects/heretic_aura.dmi b/icons/mob/effects/heretic_aura.dmi
new file mode 100644
index 00000000000..7b5b93ca236
Binary files /dev/null and b/icons/mob/effects/heretic_aura.dmi differ
diff --git a/icons/obj/clothing/head/helmet.dmi b/icons/obj/clothing/head/helmet.dmi
index a9aaad49000..95b0f6deade 100644
Binary files a/icons/obj/clothing/head/helmet.dmi and b/icons/obj/clothing/head/helmet.dmi differ
diff --git a/icons/obj/clothing/suits/armor.dmi b/icons/obj/clothing/suits/armor.dmi
index 78ac83c2ad4..806edaafdb6 100644
Binary files a/icons/obj/clothing/suits/armor.dmi and b/icons/obj/clothing/suits/armor.dmi differ
diff --git a/sound/mobs/non-humanoids/stargazer/beam_loop_one.ogg b/sound/mobs/non-humanoids/stargazer/beam_loop_one.ogg
new file mode 100644
index 00000000000..957fb4cd175
Binary files /dev/null and b/sound/mobs/non-humanoids/stargazer/beam_loop_one.ogg differ
diff --git a/sound/mobs/non-humanoids/stargazer/beam_loop_two.ogg b/sound/mobs/non-humanoids/stargazer/beam_loop_two.ogg
new file mode 100644
index 00000000000..5d6d0004491
Binary files /dev/null and b/sound/mobs/non-humanoids/stargazer/beam_loop_two.ogg differ
diff --git a/sound/mobs/non-humanoids/stargazer/beam_open.ogg b/sound/mobs/non-humanoids/stargazer/beam_open.ogg
new file mode 100644
index 00000000000..1f99c32c61c
Binary files /dev/null and b/sound/mobs/non-humanoids/stargazer/beam_open.ogg differ
diff --git a/tgstation.dme b/tgstation.dme
index 9acaf28bb3f..aaedbfc3150 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -370,6 +370,7 @@
#include "code\__DEFINES\dcs\signals\signals_spatial_grid.dm"
#include "code\__DEFINES\dcs\signals\signals_species.dm"
#include "code\__DEFINES\dcs\signals\signals_spell.dm"
+#include "code\__DEFINES\dcs\signals\signals_status_effect.dm"
#include "code\__DEFINES\dcs\signals\signals_storage.dm"
#include "code\__DEFINES\dcs\signals\signals_subsystem.dm"
#include "code\__DEFINES\dcs\signals\signals_swab.dm"
@@ -1981,6 +1982,7 @@
#include "code\datums\status_effects\wound_effects.dm"
#include "code\datums\status_effects\buffs\stop_drop_roll.dm"
#include "code\datums\status_effects\buffs\stun_absorption.dm"
+#include "code\datums\status_effects\buffs\xray.dm"
#include "code\datums\status_effects\buffs\bioware\_bioware.dm"
#include "code\datums\status_effects\buffs\bioware\circulation.dm"
#include "code\datums\status_effects\buffs\bioware\cortex.dm"
@@ -3367,25 +3369,22 @@
#include "code\modules\antagonists\heretic\knowledge\blade_lore.dm"
#include "code\modules\antagonists\heretic\knowledge\cosmic_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\heretic_armor_knowledge.dm"
#include "code\modules\antagonists\heretic\knowledge\lock_lore.dm"
#include "code\modules\antagonists\heretic\knowledge\moon_lore.dm"
#include "code\modules\antagonists\heretic\knowledge\rust_lore.dm"
-#include "code\modules\antagonists\heretic\knowledge\side_ash_moon.dm"
-#include "code\modules\antagonists\heretic\knowledge\side_blade_rust.dm"
-#include "code\modules\antagonists\heretic\knowledge\side_cosmos_ash.dm"
-#include "code\modules\antagonists\heretic\knowledge\side_flesh_void.dm"
-#include "code\modules\antagonists\heretic\knowledge\side_lock_flesh.dm"
-#include "code\modules\antagonists\heretic\knowledge\side_lock_moon.dm"
-#include "code\modules\antagonists\heretic\knowledge\side_rust_cosmos.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\reroll_targets.dm"
#include "code\modules\antagonists\heretic\knowledge\sacrifice_knowledge\sacrifice_buff.dm"
#include "code\modules\antagonists\heretic\knowledge\sacrifice_knowledge\sacrifice_curse.dm"
#include "code\modules\antagonists\heretic\knowledge\sacrifice_knowledge\sacrifice_knowledge.dm"
#include "code\modules\antagonists\heretic\knowledge\sacrifice_knowledge\sacrifice_map.dm"
#include "code\modules\antagonists\heretic\knowledge\sacrifice_knowledge\sacrifice_moodlets.dm"
+#include "code\modules\antagonists\heretic\knowledge\side_knowledge\tier_four.dm"
+#include "code\modules\antagonists\heretic\knowledge\side_knowledge\tier_one.dm"
+#include "code\modules\antagonists\heretic\knowledge\side_knowledge\tier_three.dm"
+#include "code\modules\antagonists\heretic\knowledge\side_knowledge\tier_two.dm"
#include "code\modules\antagonists\heretic\magic\aggressive_spread.dm"
#include "code\modules\antagonists\heretic\magic\apetravulnera.dm"
#include "code\modules\antagonists\heretic\magic\ascended_shapeshift.dm"
@@ -3397,6 +3396,7 @@
#include "code\modules\antagonists\heretic\magic\caretaker.dm"
#include "code\modules\antagonists\heretic\magic\cosmic_expansion.dm"
#include "code\modules\antagonists\heretic\magic\cosmic_runes.dm"
+#include "code\modules\antagonists\heretic\magic\crimson_cleave.dm"
#include "code\modules\antagonists\heretic\magic\eldritch_blind.dm"
#include "code\modules\antagonists\heretic\magic\eldritch_emplosion.dm"
#include "code\modules\antagonists\heretic\magic\eldritch_shapeshift.dm"
@@ -3414,7 +3414,6 @@
#include "code\modules\antagonists\heretic\magic\mirror_walk.dm"
#include "code\modules\antagonists\heretic\magic\moon_parade.dm"
#include "code\modules\antagonists\heretic\magic\moon_ringleader.dm"
-#include "code\modules\antagonists\heretic\magic\moon_smile.dm"
#include "code\modules\antagonists\heretic\magic\nightwatcher_rebirth.dm"
#include "code\modules\antagonists\heretic\magic\realignment.dm"
#include "code\modules\antagonists\heretic\magic\rust_charge.dm"
@@ -3434,6 +3433,7 @@
#include "code\modules\antagonists\heretic\status_effects\buffs.dm"
#include "code\modules\antagonists\heretic\status_effects\debuffs.dm"
#include "code\modules\antagonists\heretic\status_effects\ghoul.dm"
+#include "code\modules\antagonists\heretic\status_effects\heretic_passive.dm"
#include "code\modules\antagonists\heretic\status_effects\mark_effects.dm"
#include "code\modules\antagonists\heretic\status_effects\void_chill.dm"
#include "code\modules\antagonists\heretic\structures\carving_knife.dm"
diff --git a/tgui/packages/tgui/interfaces/AntagInfoHeretic.tsx b/tgui/packages/tgui/interfaces/AntagInfoHeretic.tsx
index fb2a11e5f9a..a440f3d41cd 100644
--- a/tgui/packages/tgui/interfaces/AntagInfoHeretic.tsx
+++ b/tgui/packages/tgui/interfaces/AntagInfoHeretic.tsx
@@ -1,9 +1,10 @@
+import '../styles/interfaces/AntagInfoHeretic.scss';
+
import { useState } from 'react';
import {
Box,
Button,
DmIcon,
- Flex,
Section,
Stack,
Tabs,
@@ -12,6 +13,7 @@ import type { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
import { Window } from '../layouts';
+import { logger } from '../logging';
import {
type Objective,
ObjectivePrintout,
@@ -58,29 +60,57 @@ type Knowledge = {
gainFlavor: string;
cost: number;
bgr: string;
- disabled: BooleanLike;
- finished: BooleanLike;
+ category?: ShopCategory;
+ depth: number;
+ done: BooleanLike;
ascension: BooleanLike;
+ disabled: BooleanLike;
+ tooltip?: string;
};
-type KnowledgeInfo = {
- knowledge_tiers: KnowledgeTier[];
-};
+enum ShopCategory {
+ Tree = 'tree',
+ Shop = 'shop',
+ Draft = 'draft',
+ Start = 'start',
+}
type KnowledgeTier = {
nodes: Knowledge[];
};
+type HereticPassive = {
+ name: string;
+ description: string[];
+};
+
+type HereticPath = {
+ route: string;
+ complexity: string;
+ complexity_color: string;
+ description: string[];
+ pros: string[];
+ cons: string[];
+ tips: string[];
+ starting_knowledge: Knowledge;
+ preview_abilities: Knowledge[];
+ passive: HereticPassive;
+};
+
type Info = {
charges: number;
total_sacrifices: number;
ascended: BooleanLike;
objectives: Objective[];
can_change_objective: BooleanLike;
+ paths: HereticPath[];
+ knowledge_shop: Knowledge[];
+ knowledge_tiers: KnowledgeTier[];
+ passive_level: number;
};
const IntroductionSection = (props) => {
- const { data, act } = useBackend();
+ const { data } = useBackend();
const { objectives, ascended, can_change_objective } = data;
return (
@@ -204,7 +234,7 @@ const GuideSection = () => {
);
};
-const InformationSection = (props) => {
+const InformationSection = () => {
const { data } = useBackend();
const { charges, total_sacrifices, ascended } = data;
return (
@@ -240,91 +270,37 @@ const InformationSection = (props) => {
);
};
-const KnowledgeTree = (props) => {
- const { data, act } = useBackend();
+const KnowledgeTree = () => {
+ const { data } = useBackend();
const { knowledge_tiers } = data;
+ const nodesToShow = knowledge_tiers.filter((tier) => tier.nodes.length > 0);
+
return (
DAWN
- {knowledge_tiers.length === 0
+ {nodesToShow.length === 0
? 'None!'
- : knowledge_tiers.map((tier, i) => (
+ : nodesToShow.map((tier, i) => (
-
{tier.nodes.map((node) => (
-
-
- {!!node.ascension && (
-
- DUSK
-
- )}
-
+
))}
-
+
))}
@@ -333,64 +309,376 @@ const KnowledgeTree = (props) => {
);
};
-const ResearchInfo = (props) => {
- const { data } = useBackend();
+type KnowledgeNodeProps = {
+ node: Knowledge;
+ purchaseCategory?: ShopCategory;
+ can_buy?: BooleanLike;
+};
+
+const KnowledgeNode = (props: KnowledgeNodeProps) => {
+ const { node, can_buy = true, purchaseCategory } = props;
+ const { data, act } = useBackend();
const { charges } = data;
+ const isBuyable = can_buy && !node.done && !node.disabled;
+
+ const iconState = () => {
+ if (!can_buy) {
+ return node.bgr;
+ }
+ if (node.done) {
+ return 'node_finished';
+ }
+ if (charges < node.cost || node.disabled) {
+ return 'node_locked';
+ }
+ return node.bgr;
+ };
+
return (
-
-
+
+
+ {!!node.ascension && (
+
+ DUSK
+
+ )}
+
+ );
+};
+
+const KnowledgeShop = () => {
+ const { data } = useBackend();
+ const { knowledge_shop } = data;
+
+ if (!knowledge_shop || knowledge_shop.length === 0) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+ );
+
+ function Knowledges() {
+ // filter the list into being indexed by tier
+ const tiers: Knowledge[][] = knowledge_shop.reduce((acc, knowledge) => {
+ const tierIndex = knowledge.depth - 1; // depth starts at 1, so
+ if (!acc[tierIndex]) {
+ acc[tierIndex] = [];
+ }
+ acc[tierIndex].push(knowledge);
+ return acc;
+ }, [] as Knowledge[][]);
+
+ return tiers?.map((tier, index) => (
+
+ Tier {index + 1}
+
+ {tier.map((knowledge) => (
+
+
+
+ ))}
+
+
+
+ ));
+ }
+};
+
+const ResearchInfo = () => {
+ const { data } = useBackend();
+ const { charges, knowledge_shop } = data;
+
+ return (
+ <>
+
You have {charges || 0}
knowledge point{charges !== 1 ? 's' : ''}
{' '}
to spend.
+
+
+
+
+ {knowledge_shop?.length && (
+
+
+
+ )}
+
+ >
+ );
+};
+
+const PathInfo = ({ currentPath }: { currentPath?: HereticPath }) => {
+ const { data } = useBackend();
+ const { paths } = data;
+
+ const pathBoughtIndex = paths.findIndex(
+ (path) => currentPath && path.route === currentPath.route,
+ );
+
+ const [currentTab, setCurrentTab] = useState(
+ pathBoughtIndex !== -1 ? pathBoughtIndex : 0,
+ );
+
+ return (
+
+ {!currentPath && (
+
+
+ {paths.map((path, index) => (
+ setCurrentTab(index)}
+ >
+ {path.route}
+
+ ))}
+
+
+ )}
-
+
);
};
-export const AntagInfoHeretic = (props) => {
+const PathContent = ({
+ path,
+ isPathSelected,
+}: {
+ path: HereticPath;
+ isPathSelected: boolean;
+}) => {
const { data } = useBackend();
- const { ascended } = data;
+ const { passive_level } = data;
+ const { name, description } = path.passive;
+ return (
+ {path.route}}
+ textAlign="center"
+ fill
+ scrollable
+ >
+
+ {!isPathSelected && (
+
+