diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm index 20ad4d72109..141451b8ac6 100644 --- a/code/__DEFINES/antagonists.dm +++ b/code/__DEFINES/antagonists.dm @@ -113,6 +113,7 @@ #define CONSTRUCT_JUGGERNAUT "Juggernaut" #define CONSTRUCT_WRAITH "Wraith" #define CONSTRUCT_ARTIFICER "Artificer" +#define CONSTRUCT_HARVESTER "Harvester" /// The Classic Wizard wizard loadout. #define WIZARD_LOADOUT_CLASSIC "loadout_classic" diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm index d1f489f0555..72159bde054 100644 --- a/code/__DEFINES/colors.dm +++ b/code/__DEFINES/colors.dm @@ -72,6 +72,7 @@ #define COLOR_LIME "#32CD32" #define COLOR_DARK_LIME "#00aa00" #define COLOR_VERY_PALE_LIME_GREEN "#DDFFD3" +#define COLOR_HERETIC_GREEN COLOR_VERY_PALE_LIME_GREEN // i am co-opting this as heretic glow. #define COLOR_VERY_DARK_LIME_GREEN "#003300" #define COLOR_GREEN "#008000" #define COLOR_CHRISTMAS_GREEN "#00873E" @@ -130,6 +131,7 @@ #define COLOR_DARK_ORANGE "#C3630C" #define COLOR_PRISONER_ORANGE "#A54900" #define COLOR_DARK_MODERATE_ORANGE "#8B633B" +#define COLOR_RUSTED_GLASS "#917c65" #define COLOR_BROWN "#BA9F6D" #define COLOR_DARK_BROWN "#997C4F" diff --git a/code/__DEFINES/cult.dm b/code/__DEFINES/cult.dm index 06393d145f8..005b0ca27ee 100644 --- a/code/__DEFINES/cult.dm +++ b/code/__DEFINES/cult.dm @@ -9,6 +9,8 @@ #define RUNE_COLOR_SUMMON COLOR_VIBRANT_LIME //blood magic +/// The maximum number of cult spell slots each cultist is allowed to scribe at once. +#define ENHANCED_BLOODCHARGE 5 #define MAX_BLOODCHARGE 4 #define RUNELESS_MAX_BLOODCHARGE 1 /// percent before rise @@ -28,6 +30,8 @@ #define THEME_CULT "cult" #define THEME_WIZARD "wizard" #define THEME_HOLY "holy" +/// Only used for heretic Harvesters, obtained from sacrificing cultists +#define THEME_HERETIC "heretic" /// Defines for cult item_dispensers. #define PREVIEW_IMAGE "preview" @@ -51,3 +55,8 @@ GLOBAL_LIST(sacrificed) #define CULT_VICTORY 1 #define CULT_LOSS 0 #define CULT_NARSIE_KILLED -1 + +// Used to keep track of items rewarded after a heretic is sacked. +#define CURSED_BLADE_UNLOCKED "Cursed Blade" +#define CRIMSON_FOCUS_UNLOCKED "Crimson Focus" +#define PROTEON_ORB_UNLOCKED "Proteon Orb" diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm index f4dcd496641..23cc4a8efb7 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm @@ -133,3 +133,13 @@ #define COMSIG_ATOM_PRE_CLEAN "atom_pre_clean" ///cancel clean #define COMSIG_ATOM_CANCEL_CLEAN (1<<0) +<<<<<<< HEAD +======= + +/// From /obj/item/stack/make_item() +#define COMSIG_ATOM_CONSTRUCTED "atom_constructed" + +/// From /obj/effect/particle_effect/sparks/proc/sparks_touched(datum/source, atom/movable/singed) +#define COMSIG_ATOM_TOUCHED_SPARKS "atom_touched_sparks" +#define COMSIG_ATOM_TOUCHED_HAZARDOUS_SPARKS "atom_touched_hazardous_sparks" +>>>>>>> 0c4df344b8a... [MIRROR] Cult Vs. Heretic: 7 Months Later Edition [MDB IGNORE] (#3384) 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 cf6a5a12ef7..9e23032d378 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm @@ -201,6 +201,10 @@ #define STOP_SACRIFICE (1<<0) /// Don't send a message for sacrificing this thing, we have our own #define SILENCE_SACRIFICE_MESSAGE (1<<1) + /// Don't send a message for sacrificing this thing UNLESS it's the cult target + #define SILENCE_NONTARGET_SACRIFICE_MESSAGE (1<<2) + /// Dusts the target instead of gibbing them (no soulstone) + #define DUST_SACRIFICE (1<<3) /// From /mob/living/befriend() : (mob/living/new_friend) #define COMSIG_LIVING_BEFRIENDED "living_befriended" diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index bba1c7ef758..ffd6a73ed3c 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -234,6 +234,8 @@ GLOBAL_LIST_INIT(turfs_pass_meteor, typecacheof(list( #define islandmine(A) (istype(A, /obj/effect/mine)) +#define iscloset(A) (istype(A, /obj/structure/closet)) + #define issupplypod(A) (istype(A, /obj/structure/closet/supplypod)) #define isammocasing(A) (istype(A, /obj/item/ammo_casing)) diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 93a8e397982..c89b68d3371 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_IWASBATONED "iwasbatoned" #define TRAIT_SLEEPIMMUNE "sleep_immunity" #define TRAIT_PUSHIMMUNE "push_immunity" +/// can't be kicked to the side +#define TRAIT_NO_SIDE_KICK "no_side_kick" /// Are we immune to shocks? #define TRAIT_SHOCKIMMUNE "shock_immunity" /// Are we immune to specifically tesla / SM shocks? @@ -1085,6 +1087,11 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Note this doesn't mean all spells are guaranteed to work or the mob is guaranteed to cast #define TRAIT_CASTABLE_LOC "castable_loc" +/// Needs above trait to work. +/// This trait makes it so that any cast spells will attempt to transfer to the location's location. +/// For example, a heretic inside the haunted blade's spells would emanate from the mob wielding the sword. +#define TRAIT_SPELLS_TRANSFER_TO_LOC "spells_transfer_to_loc" + ///Trait given by /datum/element/relay_attacker #define TRAIT_RELAYING_ATTACKER "relaying_attacker" @@ -1147,6 +1154,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Trait applied to objects and mobs that can attack a boulder and break it down. (See /obj/item/boulder/manual_process()) #define TRAIT_BOULDER_BREAKER "boulder_breaker" +/// Trait given to anything linked to, not necessarily allied to, the mansus +#define TRAIT_MANSUS_TOUCHED "mansus_touched" + /// Trait given to mobs wearing the clown mask #define TRAIT_PERCEIVED_AS_CLOWN "perceived_as_clown" /// Does this item bypass ranged armor checks? diff --git a/code/__HELPERS/turfs.dm b/code/__HELPERS/turfs.dm index c44845a5854..88509b88ce8 100644 --- a/code/__HELPERS/turfs.dm +++ b/code/__HELPERS/turfs.dm @@ -400,8 +400,8 @@ Turf and target are separate in case you want to teleport some distance from a t /** * Checks whether or not a particular typepath or subtype of it is present on a turf * - * Returns TRUE if an instance of the desired type or a subtype of it is found - * Returns FALSE if the type is not found, or if no turf is supplied + * Returns the first instance located if an instance of the desired type or a subtype of it is found + * Returns null if the type is not found, or if no turf is supplied * * Arguments: * * location - The turf to be checked for the desired type @@ -409,10 +409,9 @@ Turf and target are separate in case you want to teleport some distance from a t */ /proc/is_type_on_turf(turf/location, type_to_find) if(!location) - return FALSE - if(locate(type_to_find) in location) - return TRUE - return FALSE + return + var/found_type = locate(type_to_find) in location + return found_type /** * get_blueprint_data diff --git a/code/_globalvars/phobias.dm b/code/_globalvars/phobias.dm index ea1d939351d..fd26aab0e87 100644 --- a/code/_globalvars/phobias.dm +++ b/code/_globalvars/phobias.dm @@ -492,7 +492,6 @@ GLOBAL_LIST_INIT(phobia_objs, list( /obj/item/clothing/suit/wizrobe, /obj/item/clothing/under/rank/civilian/chaplain, /obj/item/codex_cicatrix, - /obj/item/cult_bastard, /obj/item/gun/magic, /obj/item/melee/cultblade, /obj/item/melee/rune_carver, diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 9f3e59ad69e..405dcd5e343 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -56,6 +56,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_NO_MISSING_ITEM_ERROR" = TRAIT_NO_MISSING_ITEM_ERROR, "TRAIT_NO_THROW_HITPUSH" = TRAIT_NO_THROW_HITPUSH, "TRAIT_NOT_ENGRAVABLE" = TRAIT_NOT_ENGRAVABLE, + "TRAIT_SPELLS_TRANSFER_TO_LOC" = TRAIT_SPELLS_TRANSFER_TO_LOC, "TRAIT_ODD_CUSTOMIZABLE_FOOD_INGREDIENT" = TRAIT_ODD_CUSTOMIZABLE_FOOD_INGREDIENT, "TRAIT_RUNECHAT_HIDDEN" = TRAIT_RUNECHAT_HIDDEN, "TRAIT_SECLUDED_LOCATION" = TRAIT_SECLUDED_LOCATION, @@ -152,6 +153,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_BOMBIMMUNE" = TRAIT_BOMBIMMUNE, "TRAIT_BONSAI" = TRAIT_BONSAI, "TRAIT_BOOZE_SLIDER" = TRAIT_BOOZE_SLIDER, + "TRAIT_BOXING_READY" = TRAIT_BOXING_READY, "TRAIT_BORN_MONKEY" = TRAIT_BORN_MONKEY, "TRAIT_BRAINWASHING" = TRAIT_BRAINWASHING, "TRAIT_BRAWLING_KNOCKDOWN_BLOCKED" = TRAIT_BRAWLING_KNOCKDOWN_BLOCKED, @@ -297,6 +299,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_MAGICALLY_GIFTED" = TRAIT_MAGICALLY_GIFTED, "TRAIT_MAGICALLY_PHASED" = TRAIT_MAGICALLY_PHASED, "TRAIT_MARTIAL_ARTS_IMMUNE" = TRAIT_MARTIAL_ARTS_IMMUNE, + "TRAIT_MANSUS_TOUCHED" = TRAIT_MANSUS_TOUCHED, "TRAIT_MEDIBOTCOMINGTHROUGH" = TRAIT_MEDIBOTCOMINGTHROUGH, "TRAIT_MEDICAL_HUD" = TRAIT_MEDICAL_HUD, "TRAIT_MESON_VISION" = TRAIT_MESON_VISION, @@ -538,6 +541,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_NO_BARCODES" = TRAIT_NO_BARCODES, "TRAIT_NO_STORAGE_INSERT" = TRAIT_NO_STORAGE_INSERT, "TRAIT_NO_TELEPORT" = TRAIT_NO_TELEPORT, + "TRAIT_NO_SIDE_KICK" = TRAIT_NO_SIDE_KICK, "TRAIT_NODROP" = TRAIT_NODROP, "TRAIT_OMNI_BAIT" = TRAIT_OMNI_BAIT, "TRAIT_PLANT_WILDMUTATE" = TRAIT_PLANT_WILDMUTATE, diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm index 2cfa8147c49..f8f79f44294 100644 --- a/code/_onclick/hud/alert.dm +++ b/code/_onclick/hud/alert.dm @@ -525,7 +525,7 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." alerttooltipstyle = "cult" var/static/image/narnar var/angle = 0 - var/mob/living/basic/construct/Cviewer + var/mob/living/basic/construct/construct_owner /atom/movable/screen/alert/bloodsense/Initialize(mapload, datum/hud/hud_owner) . = ..() @@ -533,7 +533,7 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." START_PROCESSING(SSprocessing, src) /atom/movable/screen/alert/bloodsense/Destroy() - Cviewer = null + construct_owner = null STOP_PROCESSING(SSprocessing, src) return ..() @@ -543,45 +543,53 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." if(!owner.mind) return + if(isconstruct(owner)) + construct_owner = owner + else + construct_owner = null + + // construct track + if(construct_owner?.seeking && construct_owner.master) + blood_target = construct_owner.master + desc = "Your blood sense is leading you to [construct_owner.master]" + + // cult track var/datum/antagonist/cult/antag = owner.mind.has_antag_datum(/datum/antagonist/cult,TRUE) - if(!antag) - return - var/datum/objective/sacrifice/sac_objective = locate() in antag.cult_team.objectives + if(antag) + var/datum/objective/sacrifice/sac_objective = locate() in antag.cult_team.objectives + if(antag.cult_team.blood_target) + if(!get_turf(antag.cult_team.blood_target)) + antag.cult_team.unset_blood_target() + else + blood_target = antag.cult_team.blood_target + if(!blood_target) + if(sac_objective && !sac_objective.check_completion()) + if(icon_state == "runed_sense0") + return + animate(src, transform = null, time = 1, loop = 0) + angle = 0 + cut_overlays() + icon_state = "runed_sense0" + desc = "Nar'Sie demands that [sac_objective.target] be sacrificed before the summoning ritual can begin." + add_overlay(sac_objective.sac_image) + else + var/datum/objective/eldergod/summon_objective = locate() in antag.cult_team.objectives + if(!summon_objective) + return + var/list/location_list = list() + for(var/area/area_to_check in summon_objective.summon_spots) + location_list += area_to_check.get_original_area_name() + desc = "The sacrifice is complete, summon Nar'Sie! The summoning can only take place in [english_list(location_list)]!" + if(icon_state == "runed_sense1") + return + animate(src, transform = null, time = 1, loop = 0) + angle = 0 + cut_overlays() + icon_state = "runed_sense1" + add_overlay(narnar) + return - if(antag.cult_team.blood_target) - if(!get_turf(antag.cult_team.blood_target)) - antag.cult_team.unset_blood_target() - else - blood_target = antag.cult_team.blood_target - if(Cviewer?.seeking && Cviewer.master) - blood_target = Cviewer.master - desc = "Your blood sense is leading you to [Cviewer.master]" - if(!blood_target) - if(sac_objective && !sac_objective.check_completion()) - if(icon_state == "runed_sense0") - return - animate(src, transform = null, time = 1, loop = 0) - angle = 0 - cut_overlays() - icon_state = "runed_sense0" - desc = "Nar'Sie demands that [sac_objective.target] be sacrificed before the summoning ritual can begin." - add_overlay(sac_objective.sac_image) - else - var/datum/objective/eldergod/summon_objective = locate() in antag.cult_team.objectives - if(!summon_objective) - return - var/list/location_list = list() - for(var/area/area_to_check in summon_objective.summon_spots) - location_list += area_to_check.get_original_area_name() - desc = "The sacrifice is complete, summon Nar'Sie! The summoning can only take place in [english_list(location_list)]!" - if(icon_state == "runed_sense1") - return - animate(src, transform = null, time = 1, loop = 0) - angle = 0 - cut_overlays() - icon_state = "runed_sense1" - add_overlay(narnar) - return + // actual tracking var/turf/P = get_turf(blood_target) var/turf/Q = get_turf(owner) if(!P || !Q || (P.z != Q.z)) //The target is on a different Z level, we cannot sense that far. @@ -593,6 +601,7 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." desc = "You are currently tracking [real_target.real_name] in [get_area_name(blood_target)]." else desc = "You are currently tracking [blood_target] in [get_area_name(blood_target)]." + var/target_angle = get_angle(Q, P) var/target_dist = get_dist(P, Q) cut_overlays() diff --git a/code/datums/actions/items/cult_dagger.dm b/code/datums/actions/items/cult_dagger.dm index 76e92c7b231..6b188e85e90 100644 --- a/code/datums/actions/items/cult_dagger.dm +++ b/code/datums/actions/items/cult_dagger.dm @@ -17,10 +17,10 @@ return ..() /datum/action/item_action/cult_dagger/Trigger(trigger_flags) - for(var/obj/item/held_item as anything in owner.held_items) // In case we were already holding a dagger - if(istype(held_item, /obj/item/melee/cultblade/dagger)) - held_item.attack_self(owner) - return + if(target in owner.held_items) + var/obj/item/target_item = target + target_item.attack_self(owner) + return var/obj/item/target_item = target if(owner.can_equip(target_item, ITEM_SLOT_HANDS)) owner.temporarilyRemoveItemFromInventory(target_item) diff --git a/code/datums/elements/amputating_limbs.dm b/code/datums/components/amputating_limbs.dm similarity index 65% rename from code/datums/elements/amputating_limbs.dm rename to code/datums/components/amputating_limbs.dm index 8684a76f47f..bfaf52ca90c 100644 --- a/code/datums/elements/amputating_limbs.dm +++ b/code/datums/components/amputating_limbs.dm @@ -1,7 +1,5 @@ /// This component will intercept bare-handed attacks by the owner on sufficiently injured carbons and amputate random limbs instead -/datum/element/amputating_limbs - element_flags = ELEMENT_BESPOKE - argument_hash_start_idx = 2 +/datum/component/amputating_limbs /// How long does it take? var/surgery_time /// What is the means by which we describe the act of amputation? @@ -12,34 +10,38 @@ var/snip_chance /// The types of limb we can remove var/list/target_zones + /// Callback for a proc right before confirming the attack. If it returns FALSE, cancel + var/datum/callback/pre_hit_callback -/datum/element/amputating_limbs/Attach( - datum/target, +/datum/component/amputating_limbs/Initialize( surgery_time = 5 SECONDS, surgery_verb = "prying", minimum_stat = SOFT_CRIT, snip_chance = 100, list/target_zones = list(BODY_ZONE_L_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_ARM, BODY_ZONE_R_LEG), + datum/callback/pre_hit_callback, ) . = ..() - if (!isliving(target)) - return ELEMENT_INCOMPATIBLE + if (!isliving(parent)) + return COMPONENT_INCOMPATIBLE if (!length(target_zones)) - CRASH("[src] for [target] was not provided a valid list of body zones to target.") + CRASH("[src] for [parent] was not provided a valid list of body zones to target.") src.surgery_time = surgery_time src.surgery_verb = surgery_verb src.minimum_stat = minimum_stat src.snip_chance = snip_chance src.target_zones = target_zones - RegisterSignals(target, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET), PROC_REF(try_amputate)) + src.pre_hit_callback = pre_hit_callback -/datum/element/amputating_limbs/Detach(datum/source) - UnregisterSignal(source, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET)) - return ..() +/datum/component/amputating_limbs/RegisterWithParent() + RegisterSignals(parent, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET), PROC_REF(try_amputate)) + +/datum/component/amputating_limbs/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET)) /// Called when you click on literally anything with your hands, see if it is an injured carbon and then try to cut it up -/datum/element/amputating_limbs/proc/try_amputate(mob/living/surgeon, atom/victim, proximity, modifiers) +/datum/component/amputating_limbs/proc/try_amputate(mob/living/surgeon, atom/victim, proximity, modifiers) SIGNAL_HANDLER if (!proximity || !iscarbon(victim) || HAS_TRAIT(victim, TRAIT_NODISMEMBER) || !prob(snip_chance)) return @@ -52,6 +54,9 @@ surgeon.balloon_alert(surgeon, "already busy!") return COMPONENT_CANCEL_ATTACK_CHAIN + if(pre_hit_callback && !pre_hit_callback.Invoke(victim)) + return + var/list/valid_targets = list() for (var/obj/item/bodypart/possible_target as anything in limbed_victim.bodyparts) if (possible_target.bodypart_flags & BODYPART_UNREMOVABLE) @@ -67,8 +72,9 @@ return COMPONENT_CANCEL_ATTACK_CHAIN /// Chop one off -/datum/element/amputating_limbs/proc/amputate(mob/living/surgeon, mob/living/carbon/victim, obj/item/bodypart/to_remove) - surgeon.visible_message(span_warning("[surgeon] [surgery_verb] [to_remove] off of [victim]!")) +/datum/component/amputating_limbs/proc/amputate(mob/living/surgeon, mob/living/carbon/victim, obj/item/bodypart/to_remove) + if(surgery_time > 0 SECONDS) + surgeon.visible_message(span_warning("[surgeon] is [surgery_verb] the [to_remove] off of [victim]!")) if (surgery_time > 0 && !do_after(surgeon, delay = surgery_time, target = victim)) return to_remove.dismember() diff --git a/code/datums/components/cult_ritual_item.dm b/code/datums/components/cult_ritual_item.dm index 7d9710977ce..dedd30bda0e 100644 --- a/code/datums/components/cult_ritual_item.dm +++ b/code/datums/components/cult_ritual_item.dm @@ -128,6 +128,7 @@ INVOKE_ASYNC(src, PROC_REF(do_destroy_girder), target, cultist) return COMPONENT_NO_AFTERATTACK + if(istype(target, /obj/structure/destructible/cult)) INVOKE_ASYNC(src, PROC_REF(do_unanchor_structure), target, cultist) return COMPONENT_NO_AFTERATTACK diff --git a/code/datums/components/damage_aura.dm b/code/datums/components/damage_aura.dm index 98d323aa6d5..9c4e996113b 100644 --- a/code/datums/components/damage_aura.dm +++ b/code/datums/components/damage_aura.dm @@ -36,6 +36,12 @@ /// Which factions are immune to the damage aura var/list/immune_factions = null + /// If set, gives a message when damaged + var/damage_message = null + + /// Probability for above. + var/message_probability = 0 + /// Sets a special set of conditions for the owner var/datum/weakref/current_owner = null @@ -54,6 +60,8 @@ organ_damage = null, simple_damage = 0, immune_factions = null, + damage_message = null, + message_probability = 0, mob/living/current_owner = null, ) if (!isatom(parent)) @@ -72,6 +80,8 @@ src.organ_damage = organ_damage src.simple_damage = simple_damage src.immune_factions = immune_factions + src.damage_message = damage_message + src.message_probability = message_probability src.current_owner = WEAKREF(current_owner) /datum/component/damage_aura/Destroy(force) @@ -120,6 +130,9 @@ if (candidate.health < candidate.maxHealth) new /obj/effect/temp_visual/cosmic_gem(get_turf(candidate)) + if(damage_message && prob(message_probability)) + to_chat(candidate, damage_message) + if (iscarbon(candidate) || issilicon(candidate) || isbasicmob(candidate)) candidate.adjustBruteLoss(brute_damage * seconds_per_tick, updating_health = FALSE) candidate.adjustFireLoss(burn_damage * seconds_per_tick, updating_health = FALSE) diff --git a/code/datums/components/ghost_direct_control.dm b/code/datums/components/ghost_direct_control.dm index de5bca4fcad..fb3a2c06e83 100644 --- a/code/datums/components/ghost_direct_control.dm +++ b/code/datums/components/ghost_direct_control.dm @@ -142,13 +142,20 @@ return if (extra_control_checks && !extra_control_checks.Invoke(harbinger)) return + harbinger.log_message("took control of [new_body].", LOG_GAME) + // doesn't transfer mind because that transfers antag datum as well new_body.key = harbinger.key - to_chat(new_body, span_boldnotice(assumed_control_message)) - after_assumed_control?.Invoke(harbinger) + + // Already qdels due to below proc but just in case qdel(src) -/// When someone else assumes control via some other means, get rid of our component -/datum/component/ghost_direct_control/proc/on_login() +/// When someone assumes control, get rid of our component +/datum/component/ghost_direct_control/proc/on_login(mob/harbinger) SIGNAL_HANDLER + // This proc is called the very moment .key is set, so we need to force mind to initialize here if we want the invoke to affect the mind of the mob + if(isnull(harbinger.mind)) + harbinger.mind_initialize() + to_chat(harbinger, span_boldnotice(assumed_control_message)) + after_assumed_control?.Invoke(harbinger) qdel(src) diff --git a/code/datums/components/spawner.dm b/code/datums/components/spawner.dm index 29767d11ce6..26dbffaef6f 100644 --- a/code/datums/components/spawner.dm +++ b/code/datums/components/spawner.dm @@ -11,6 +11,8 @@ var/list/faction /// List of weak references to things we have already created var/list/spawned_things = list() + /// Callback to a proc that is called when a mob is spawned. Primarily used for sentient spawners. + var/datum/callback/spawn_callback /// How many mobs can we spawn maximum each time we try to spawn? (1 - max) var/max_spawn_per_attempt /// Distance from the spawner to spawn mobs @@ -19,7 +21,7 @@ var/spawn_distance_exclude COOLDOWN_DECLARE(spawn_delay) -/datum/component/spawner/Initialize(spawn_types = list(), spawn_time = 30 SECONDS, max_spawned = 5, max_spawn_per_attempt = 1 , faction = list(FACTION_MINING), spawn_text = null, spawn_distance = 1, spawn_distance_exclude = 0) +/datum/component/spawner/Initialize(spawn_types = list(), spawn_time = 30 SECONDS, max_spawned = 5, max_spawn_per_attempt = 1 , faction = list(FACTION_MINING), spawn_text = null, datum/callback/spawn_callback = null, spawn_distance = 1, spawn_distance_exclude = 0, initial_spawn_delay = 0 SECONDS) if (!islist(spawn_types)) CRASH("invalid spawn_types to spawn specified for spawner component!") src.spawn_time = spawn_time @@ -27,9 +29,13 @@ src.faction = faction src.spawn_text = spawn_text src.max_spawned = max_spawned + src.spawn_callback = spawn_callback src.max_spawn_per_attempt = max_spawn_per_attempt src.spawn_distance = spawn_distance src.spawn_distance_exclude = spawn_distance_exclude + // If set, doesn't instantly spawn a creature when the spawner component is applied. + if(initial_spawn_delay) + COOLDOWN_START(src, spawn_delay, spawn_time) RegisterSignal(parent, COMSIG_QDELETING, PROC_REF(stop_spawning)) RegisterSignal(parent, COMSIG_VENT_WAVE_CONCLUDED, PROC_REF(stop_spawning)) @@ -89,6 +95,7 @@ SEND_SIGNAL(src, COMSIG_SPAWNER_SPAWNED, created) RegisterSignal(created, COMSIG_QDELETING, PROC_REF(on_deleted)) + spawn_callback?.Invoke(created) if (spawn_text) diff --git a/code/datums/components/spirit_holding.dm b/code/datums/components/spirit_holding.dm index e2b1cfb96bc..f1125d8a2d8 100644 --- a/code/datums/components/spirit_holding.dm +++ b/code/datums/components/spirit_holding.dm @@ -6,12 +6,20 @@ /datum/component/spirit_holding ///bool on if this component is currently polling for observers to inhabit the item var/attempting_awakening = FALSE + /// Allows renaming the bound item + var/allow_renaming + /// Allows channeling + var/allow_channeling ///mob contained in the item. var/mob/living/basic/shade/bound_spirit -/datum/component/spirit_holding/Initialize() +/datum/component/spirit_holding/Initialize(datum/mind/soul_to_bind, mob/awakener, allow_renaming = TRUE, allow_channeling = TRUE) if(!ismovable(parent)) //you may apply this to mobs, i take no responsibility for how that works out return COMPONENT_INCOMPATIBLE + src.allow_renaming = allow_renaming + src.allow_channeling = allow_channeling + if(soul_to_bind) + bind_the_soule(soul_to_bind, awakener, soul_to_bind.name) /datum/component/spirit_holding/Destroy(force) . = ..() @@ -30,7 +38,7 @@ /datum/component/spirit_holding/proc/on_examine(datum/source, mob/user, list/examine_list) SIGNAL_HANDLER if(!bound_spirit) - examine_list += span_notice("[parent] sleeps. Use [parent] in your hands to attempt to awaken it.") + examine_list += span_notice("[parent] sleeps.[allow_channeling ? " Use [parent] in your hands to attempt to awaken it." : ""]") return examine_list += span_notice("[parent] is alive.") @@ -48,6 +56,9 @@ thing.balloon_alert(user, "spirits are unwilling!") to_chat(user, span_warning("Anomalous otherworldly energies block you from awakening [parent]!")) return + if(!allow_channeling && bound_spirit) + to_chat(user, span_warning("Try as you might, the spirit within slumbers.")) + return attempting_awakening = TRUE thing.balloon_alert(user, "channeling...") var/mob/chosen_one = SSpolling.poll_ghosts_for_target( @@ -74,28 +85,29 @@ // Immediately unregister to prevent making a new spirit UnregisterSignal(parent, COMSIG_ITEM_ATTACK_SELF) - if(QDELETED(parent)) //if the thing that we're conjuring a spirit in has been destroyed, don't create a spirit to_chat(ghost, span_userdanger("The new vessel for your spirit has been destroyed! You remain an unbound ghost.")) return - bound_spirit = new(parent) - bound_spirit.ckey = ghost.ckey - bound_spirit.fully_replace_character_name(null, "The spirit of [parent]") - bound_spirit.status_flags |= GODMODE - bound_spirit.copy_languages(awakener, LANGUAGE_MASTER) //Make sure the sword can understand and communicate with the awakener. - bound_spirit.get_language_holder().omnitongue = TRUE //Grants omnitongue + bind_the_soule(ghost, awakener) - //Add new signals for parent and stop attempting to awaken - RegisterSignal(parent, COMSIG_ATOM_RELAYMOVE, PROC_REF(block_buckle_message)) - RegisterSignal(parent, COMSIG_BIBLE_SMACKED, PROC_REF(on_bible_smacked)) + attempting_awakening = FALSE + if(!allow_renaming) + return // Now that all of the important things are in place for our spirit, it's time for them to choose their name. var/valid_input_name = custom_name(awakener) if(valid_input_name) bound_spirit.fully_replace_character_name(null, "The spirit of [valid_input_name]") - attempting_awakening = FALSE +/datum/component/spirit_holding/proc/bind_the_soule(datum/mind/chosen_spirit, mob/awakener, name_override) + bound_spirit = new(parent) + chosen_spirit.transfer_to(bound_spirit) + bound_spirit.fully_replace_character_name(null, "The spirit of [name_override ? name_override : parent]") + bound_spirit.get_language_holder().omnitongue = TRUE //Grants omnitongue + + RegisterSignal(parent, COMSIG_ATOM_RELAYMOVE, PROC_REF(block_buckle_message)) + RegisterSignal(parent, COMSIG_BIBLE_SMACKED, PROC_REF(on_bible_smacked)) /** * custom_name : Simply sends a tgui input text box to the blade asking what name they want to be called, and retries it if the input is invalid. diff --git a/code/datums/elements/cult_eyes.dm b/code/datums/elements/cult_eyes.dm index 3a46aa42c95..3e685419836 100644 --- a/code/datums/elements/cult_eyes.dm +++ b/code/datums/elements/cult_eyes.dm @@ -1,4 +1,4 @@ -/** +/*** * # Cult eyes element * * Applies and removes the glowing cult eyes @@ -16,8 +16,7 @@ /** * Cult eye setter proc - * - * Changes the eye color, and adds the glowing eye trait to the mob. + * * Changes the eye color, and adds the glowing eye trait to the mob. */ /datum/element/cult_eyes/proc/set_eyes(mob/living/target) SIGNAL_HANDLER diff --git a/code/datums/elements/eyestab.dm b/code/datums/elements/eyestab.dm index 5686524d793..b8c0d78c4ae 100644 --- a/code/datums/elements/eyestab.dm +++ b/code/datums/elements/eyestab.dm @@ -94,8 +94,9 @@ eyes.set_organ_damage(eyes.low_threshold) // At over 10 damage, there is a 50% chance they drop all their items - if (prob(50)) - if (target.stat != DEAD && target.drop_all_held_items()) + if (prob(50) && target.stat != DEAD) + var/list/dropped = target.drop_all_held_items() + if(length(dropped)) to_chat(target, span_danger("You drop what you're holding and clutch at your eyes!")) target.adjust_eye_blur_up_to(20 SECONDS, EYESTAB_MAX_BLUR) target.Unconscious(2 SECONDS) diff --git a/code/datums/elements/leeching_walk.dm b/code/datums/elements/leeching_walk.dm new file mode 100644 index 00000000000..c0afc52b245 --- /dev/null +++ b/code/datums/elements/leeching_walk.dm @@ -0,0 +1,57 @@ +/// Buffs and heals the target while standing on rust. +/datum/element/leeching_walk + +/datum/element/leeching_walk/Attach(datum/target) + . = ..() + if (!isliving(target)) + return ELEMENT_INCOMPATIBLE + + RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(on_move)) + RegisterSignal(target, COMSIG_LIVING_LIFE, PROC_REF(on_life)) + +/datum/element/leeching_walk/Detach(datum/source) + . = ..() + UnregisterSignal(source, list(COMSIG_MOVABLE_MOVED, COMSIG_LIVING_LIFE)) + +/* + * Signal proc for [COMSIG_MOVABLE_MOVED]. + * + * Checks if we should have baton resistance on the new turf. + */ +/datum/element/leeching_walk/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, type) + return + + REMOVE_TRAIT(source, TRAIT_BATON_RESISTANCE, type) + +/** + * Signal proc for [COMSIG_LIVING_LIFE]. + * + * Gradually heals the heretic ([source]) on rust, + * including baton knockdown and stamina damage. + */ +/datum/element/leeching_walk/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 + need_mob_update += source.adjustBruteLoss(-3, updating_health = FALSE) + need_mob_update += source.adjustFireLoss(-3, updating_health = FALSE) + need_mob_update += source.adjustToxLoss(-3, updating_health = FALSE, forced = TRUE) // Slimes are people to + need_mob_update += source.adjustOxyLoss(-1.5, updating_health = FALSE) + need_mob_update += source.adjustStaminaLoss(-10, updating_stamina = FALSE) + if(need_mob_update) + source.updatehealth() + // Reduces duration of stuns/etc + source.AdjustAllImmobility(-0.5 SECONDS) + // Heals blood loss + if(source.blood_volume < BLOOD_VOLUME_NORMAL) + source.blood_volume += 2.5 * seconds_per_tick diff --git a/code/datums/elements/wall_walker.dm b/code/datums/elements/wall_walker.dm index 92ac3318c12..abea9db3af8 100644 --- a/code/datums/elements/wall_walker.dm +++ b/code/datums/elements/wall_walker.dm @@ -4,16 +4,20 @@ argument_hash_start_idx = 2 /// What kind of walls can we pass through? var/wall_type + /// What trait on turfs allows us to pass through? Can be used as OR if wall_type is null, or AND if it's set. + var/or_trait /datum/element/wall_walker/Attach( datum/target, wall_type = /turf/closed/wall, + or_trait, ) . = ..() if (!isliving(target)) return ELEMENT_INCOMPATIBLE src.wall_type = wall_type + src.or_trait = or_trait RegisterSignal(target, COMSIG_LIVING_WALL_BUMP, PROC_REF(try_pass_wall)) RegisterSignal(target, COMSIG_LIVING_WALL_EXITED, PROC_REF(exit_wall)) @@ -23,7 +27,10 @@ /// If the wall is of the proper type, pass into it and keep hold on whatever you're pulling /datum/element/wall_walker/proc/try_pass_wall(mob/living/passing_mob, turf/closed/bumped_wall) - if(!istype(bumped_wall, wall_type)) + if(wall_type && !istype(bumped_wall, wall_type)) + return + + if(or_trait && !HAS_TRAIT(bumped_wall, or_trait)) return var/atom/movable/stored_pulling = passing_mob.pulling diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm index c69c865f6d1..4c1d4ad2d51 100644 --- a/code/game/machinery/doors/windowdoor.dm +++ b/code/game/machinery/doors/windowdoor.dm @@ -317,6 +317,13 @@ /obj/machinery/door/window/narsie_act() add_atom_colour(NARSIE_WINDOW_COLOUR, FIXED_COLOUR_PRIORITY) +/obj/machinery/door/window/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(max_integrity * 0.5) + /obj/machinery/door/window/should_atmos_process(datum/gas_mixture/air, exposed_temperature) return (exposed_temperature > T0C + (reinf ? 1600 : 800)) diff --git a/code/game/objects/effects/decals/cleanable/humans.dm b/code/game/objects/effects/decals/cleanable/humans.dm index 11fbbe969d7..0c15aecace9 100644 --- a/code/game/objects/effects/decals/cleanable/humans.dm +++ b/code/game/objects/effects/decals/cleanable/humans.dm @@ -97,6 +97,15 @@ /obj/effect/decal/cleanable/trail_holder/can_bloodcrawl_in() return TRUE +// normal version of the above trail holder object for use in less convoluted things +/obj/effect/decal/cleanable/blood/trails + desc = "Looks like a corpse was smeared all over the floor like ketchup. Kinda makes you hungry." + random_icon_states = list("trails_1", "trails_2") + icon_state = "trails_1" + beauty = -50 + dryname = "dried tracks" + drydesc = "Looks like a corpse was smeared all over the floor like ketchup, but it's all dried up and nasty now, ew. You lose some of your appetite." + /obj/effect/decal/cleanable/blood/gibs name = "gibs" desc = "They look bloody and gruesome." diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm index 4258465f3a0..c748ba4c494 100644 --- a/code/game/objects/items/stacks/stack.dm +++ b/code/game/objects/items/stacks/stack.dm @@ -426,6 +426,7 @@ if(created) created.setDir(builder.dir) + SEND_SIGNAL(created, COMSIG_ATOM_CONSTRUCTED, builder) on_item_crafted(builder, created) // Use up the material diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index e6c0ef7504f..566f556456a 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -1012,7 +1012,7 @@ GLOBAL_LIST_EMPTY(roundstart_station_closets) return FALSE return TRUE -/obj/structure/closet/container_resist_act(mob/living/user) +/obj/structure/closet/container_resist_act(mob/living/user, loc_required = TRUE) if(isstructure(loc)) relay_container_resist_act(user, loc) if(opened) @@ -1037,7 +1037,7 @@ GLOBAL_LIST_EMPTY(roundstart_station_closets) addtimer(CALLBACK(src, PROC_REF(check_if_shake)), 1 SECONDS) if(do_after(user,(breakout_time), target = src)) - if(!user || user.stat != CONSCIOUS || user.loc != src || opened || (!locked && !welded) ) + if(!user || user.stat != CONSCIOUS || (loc_required && (user.loc != src)) || opened || (!locked && !welded) ) return //we check after a while whether there is a point of resisting anymore and whether the user is capable of resisting user.visible_message(span_danger("[user] successfully broke out of [src]!"), diff --git a/code/game/objects/structures/crates_lockers/closets/bodybag.dm b/code/game/objects/structures/crates_lockers/closets/bodybag.dm index 38482fcfd9b..03de7375e2b 100644 --- a/code/game/objects/structures/crates_lockers/closets/bodybag.dm +++ b/code/game/objects/structures/crates_lockers/closets/bodybag.dm @@ -276,7 +276,7 @@ else icon_state = initial(icon_state) -/obj/structure/closet/body_bag/environmental/prisoner/container_resist_act(mob/living/user) +/obj/structure/closet/body_bag/environmental/prisoner/container_resist_act(mob/living/user, loc_required = TRUE) /// copy-pasted with changes because flavor text as well as some other misc stuff if(opened) return diff --git a/code/game/objects/structures/spawner.dm b/code/game/objects/structures/spawner.dm index 0f1b1bb57b5..743d76ef182 100644 --- a/code/game/objects/structures/spawner.dm +++ b/code/game/objects/structures/spawner.dm @@ -70,7 +70,9 @@ spawn_time = spawn_time, \ max_spawned = max_mobs, \ faction = faction, \ - spawn_text = spawn_text, \ + spawn_text = spawn_text,\ + spawn_callback = CALLBACK(src, PROC_REF(on_mob_spawn)), \ + initial_spawn_delay = !mapload, \ ) /obj/structure/spawner/attack_animal(mob/living/simple_animal/user, list/modifiers) @@ -78,6 +80,8 @@ return return ..() +/obj/structure/spawner/proc/on_mob_spawn(atom/created_atom) + return /obj/structure/spawner/syndicate name = "warp beacon" @@ -227,3 +231,73 @@ src.visible_message(span_warning("[living_mob] reemerges from the link!")) qdel(living_mob) +/obj/structure/spawner/sentient + var/role_name = "A sentient mob" + var/assumed_control_message = "You are a sentient mob from a badly coded spawner" + +/obj/structure/spawner/sentient/Initialize(mapload) + . = ..() + notify_ghosts( + "A [name] has been created in \the [get_area(src)]!", + source = src, + header = "Sentient Spawner Created", + notify_flags = NOTIFY_CATEGORY_NOFLASH, + ) + +/obj/structure/spawner/sentient/on_mob_spawn(atom/created_atom) + created_atom.AddComponent(\ + /datum/component/ghost_direct_control,\ + role_name = src.role_name,\ + assumed_control_message = src.assumed_control_message,\ + after_assumed_control = CALLBACK(src, PROC_REF(became_player_controlled)),\ + ) + +/obj/structure/spawner/sentient/proc/became_player_controlled(mob/proteon) + return + +/obj/structure/spawner/sentient/proteon_spawner + name = "eldritch gateway" + desc = "A dizzying structure that somehow links into Nar'Sie's own domain. The screams of the damned echo continously." + icon = 'icons/obj/antags/cult/structures.dmi' + icon_state = "hole" + light_power = 2 + light_color = COLOR_CULT_RED + max_integrity = 50 + density = FALSE + max_mobs = 2 + spawn_time = 15 SECONDS + mob_types = list(/mob/living/basic/construct/proteon/hostile) + spawn_text = "arises from" + faction = list(FACTION_CULT) + role_name = "A proteon cult construct" + assumed_control_message = null + +/obj/structure/spawner/sentient/proteon_spawner/examine_status(mob/user) + if(IS_CULTIST(user) || !isliving(user)) + return span_cult("It's at [round(atom_integrity * 100 / max_integrity)]% stability.") + return ..() + +/obj/structure/spawner/sentient/proteon_spawner/examine(mob/user) + . = ..() + if(!IS_CULTIST(user) && isliving(user)) + var/mob/living/living_user = user + living_user.adjustOrganLoss(ORGAN_SLOT_BRAIN, 15) + . += span_danger("The voices of the damned echo relentlessly in your mind, continously rebounding on the walls of your self the more you focus on [src]. Your head pounds, better keep away...") + else + . += span_cult("The gateway will create one weak proteon construct every [spawn_time * 0.1] seconds, up to a total of [max_mobs], that may be controlled by the spirits of the dead.") + +/obj/structure/spawner/sentient/proteon_spawner/became_player_controlled(mob/living/basic/construct/proteon/proteon) + proteon.mind.add_antag_datum(/datum/antagonist/cult) + proteon.add_filter("awoken_proteon", 3, list("type" = "outline", "color" = COLOR_CULT_RED, "size" = 2)) + visible_message(span_cult_bold("[proteon] awakens, glowing an eerie red as it stirs from its stupor!")) + playsound(proteon, 'sound/items/haunted/ghostitemattack.ogg', 100, TRUE) + proteon.balloon_alert_to_viewers("awoken!") + addtimer(CALLBACK(src, PROC_REF(remove_wake_outline), proteon), 8 SECONDS) + +/obj/structure/spawner/sentient/proteon_spawner/proc/remove_wake_outline(mob/proteon) + proteon.remove_filter("awoken_proteon") + proteon.add_filter("sentient_proteon", 3, list("type" = "outline", "color" = COLOR_CULT_RED, "size" = 2, "alpha" = 40)) + +/obj/structure/spawner/sentient/proteon_spawner/handle_deconstruct(disassembled) + playsound('sound/hallucinations/veryfar_noise.ogg', 125) + visible_message(span_cult_bold("[src] completely falls apart, the screams of the damned reaching a feverous pitch before slowly fading away into nothing.")) diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index f0c09fef5af..dab61a20ce7 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -568,6 +568,14 @@ 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(max_integrity * 0.5) + /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/modules/antagonists/cult/blood_magic.dm b/code/modules/antagonists/cult/blood_magic.dm index 8fee1701d6a..fc295b14c8c 100644 --- a/code/modules/antagonists/cult/blood_magic.dm +++ b/code/modules/antagonists/cult/blood_magic.dm @@ -12,6 +12,8 @@ default_button_position = DEFAULT_BLOODSPELLS var/list/spells = list() var/channeling = FALSE + /// If the magic has been enhanced somehow, likely due to a crimson focus. + var/magic_enhanced = FALSE /datum/action/innate/cult/blood_magic/Remove() for(var/X in spells) @@ -29,7 +31,7 @@ var/atom/movable/screen/movable/action_button/button = viewers[hud] var/position = screen_loc_to_offset(button.screen_loc) var/list/position_list = list() - for(var/possible_position in 1 to MAX_BLOODCHARGE) + for(var/possible_position in 1 to magic_enhanced ? ENHANCED_BLOODCHARGE : MAX_BLOODCHARGE) position_list += possible_position for(var/datum/action/innate/cult/blood_spell/blood_spell in spells) if(blood_spell.positioned) @@ -50,10 +52,10 @@ rune = TRUE break if(rune) - limit = MAX_BLOODCHARGE + limit = magic_enhanced ? ENHANCED_BLOODCHARGE : MAX_BLOODCHARGE if(length(spells) >= limit) if(rune) - to_chat(owner, span_cult_italic("You cannot store more than [MAX_BLOODCHARGE] spells. Pick a spell to remove.")) + to_chat(owner, span_cult_italic("You cannot store more than [limit] spells. Pick a spell to remove.")) else to_chat(owner, span_cult_bold_italic("You cannot store more than [RUNELESS_MAX_BLOODCHARGE] spells without an empowering rune! Pick a spell to remove.")) var/nullify_spell = tgui_input_list(owner, "Spell to remove", "Current Spells", spells) @@ -86,10 +88,15 @@ else to_chat(owner, span_cult_italic("You are already invoking blood magic!")) return - if(do_after(owner, 100 - rune*60, target = owner)) + var/spell_carving_timer = 10 SECONDS + if(rune) + spell_carving_timer = 4 SECONDS + if(magic_enhanced) + spell_carving_timer *= 0.5 + if(do_after(owner, spell_carving_timer, target = owner)) if(ishuman(owner)) var/mob/living/carbon/human/human_owner = owner - human_owner.bleed(40 - rune*32) + human_owner.bleed(rune ? 8 : 40) var/datum/action/innate/cult/blood_spell/new_spell = new BS(owner.mind) new_spell.Grant(owner, src) spells += new_spell @@ -417,6 +424,7 @@ if(IS_CULTIST(target)) return var/datum/antagonist/cult/cultist = IS_CULTIST(user) +<<<<<<< HEAD if(!isnull(cultist)) var/datum/team/cult/cult_team = cultist.get_team() var/effect_coef = 1 - (cult_team.cult_risen ? 0.4 : 0) - (cult_team.cult_ascendent ? 0.5 : 0) @@ -456,6 +464,63 @@ carbon_target.set_jitter_if_lower(30 SECONDS * effect_coef) uses-- ..() +======= + var/datum/team/cult/cult_team = cultist.get_team() + var/effect_coef = 1 + if(cult_team.cult_ascendent) + effect_coef = 0.1 + else if(cult_team.cult_risen) + effect_coef = 0.4 + user.visible_message( + span_warning("[user] holds up [user.p_their()] hand, which explodes in a flash of red light!"), + span_cult_italic("You attempt to stun [target] with the spell!"), + visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE, + ) + user.mob_light(range = 1.1, power = 2, color = LIGHT_COLOR_BLOOD_MAGIC, duration = 0.2 SECONDS) + // Heretics are momentarily disoriented by the stunning aura. Enough for both parties to go 'oh shit' but only a mild combat ability. + // Heretics have an identical effect on their grasp. The cultist's worse spell preparation is offset by their extra gear and teammates. + if(IS_HERETIC(target)) + target.AdjustKnockdown(0.5 SECONDS) + target.adjust_confusion_up_to(1.5 SECONDS, 3 SECONDS) + target.adjust_dizzy_up_to(1.5 SECONDS, 3 SECONDS) + ADD_TRAIT(target, TRAIT_NO_SIDE_KICK, REF(src)) // We don't want this to be a good stunning tool, just minor disorientation + addtimer(TRAIT_CALLBACK_REMOVE(target, TRAIT_NO_SIDE_KICK, REF(src)), 1 SECONDS) + + var/old_color = target.color + target.color = COLOR_HERETIC_GREEN + animate(target, color = old_color, time = 4 SECONDS, easing = EASE_IN) + target.mob_light(range = 1.5, power = 2.5, color = COLOR_HERETIC_GREEN, duration = 0.5 SECONDS) + playsound(target, 'sound/magic/magic_block_mind.ogg', 150, TRUE) // insanely quiet + + to_chat(user, span_warning("An eldritch force intervenes as you touch [target], absorbing most of the effects!")) + to_chat(target, span_warning("As [user] touches you with vile magicks, the Mansus absorbs most of the effects!")) + target.balloon_alert_to_viewers("absorbed!") + // NOVA EDIT ADDITION START + else if(IS_CLOCK(target)) + to_chat(user, span_warning("Some force greater than you intervenes! [target] is protected by the heretic Ratvar!")) + to_chat(target, span_warning("You are protected by your faith to Ratvar!")) + var/old_color = target.color + target.color = rgb(190, 135, 0) + animate(target, color = old_color, time = 1 SECONDS, easing = EASE_IN) + // NOVA EDIT ADDITION END + else if(target.can_block_magic()) + to_chat(user, span_warning("The spell had no effect!")) + else + to_chat(user, span_cult_italic("In a brilliant flash of red, [target] falls to the ground!")) + target.Paralyze(16 SECONDS * effect_coef) + target.flash_act(1, TRUE) + if(issilicon(target)) + var/mob/living/silicon/silicon_target = target + silicon_target.emp_act(EMP_HEAVY) + else if(iscarbon(target)) + var/mob/living/carbon/carbon_target = target + carbon_target.adjust_silence(12 SECONDS * effect_coef) + carbon_target.adjust_stutter(30 SECONDS * effect_coef) + carbon_target.adjust_timed_status_effect(30 SECONDS * effect_coef, /datum/status_effect/speech/slurring/cult) + carbon_target.set_jitter_if_lower(30 SECONDS * effect_coef) + uses-- + return ..() +>>>>>>> 0c4df344b8a... [MIRROR] Cult Vs. Heretic: 7 Months Later Edition [MDB IGNORE] (#3384) //Teleportation /obj/item/melee/blood_magic/teleport diff --git a/code/modules/antagonists/cult/cult_bastard_sword.dm b/code/modules/antagonists/cult/cult_bastard_sword.dm deleted file mode 100644 index 0d70bd503fb..00000000000 --- a/code/modules/antagonists/cult/cult_bastard_sword.dm +++ /dev/null @@ -1,98 +0,0 @@ - -/// Cult Bastard Sword, earned by cultists when they manage to sacrifice a heretic. -/obj/item/cult_bastard - name = "bloody bastard sword" - desc = "An enormous sword used by Nar'Sien cultists to rapidly harvest the souls of non-believers." - w_class = WEIGHT_CLASS_HUGE - block_chance = 50 - block_sound = 'sound/weapons/parry.ogg' - throwforce = 20 - force = 35 - armour_penetration = 45 - throw_speed = 1 - throw_range = 3 - sharpness = SHARP_EDGED - light_color = "#ff0000" - attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "rends") - attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "rend") - icon = 'icons/obj/weapons/sword.dmi' - icon_state = "cultbastard" - inhand_icon_state = "cultbastard" - hitsound = 'sound/weapons/bladeslice.ogg' - lefthand_file = 'icons/mob/inhands/64x64_lefthand.dmi' - righthand_file = 'icons/mob/inhands/64x64_righthand.dmi' - inhand_x_dimension = 64 - inhand_y_dimension = 64 - actions_types = list() - item_flags = SLOWS_WHILE_IN_HAND - ///if we are using our attack_self ability - var/spinning = FALSE - -/obj/item/cult_bastard/Initialize(mapload) - . = ..() - set_light(4) - AddComponent(/datum/component/butchering, 50, 80) - AddComponent(/datum/component/two_handed, require_twohands = TRUE) - AddComponent(/datum/component/soul_stealer, soulstone_type = /obj/item/soulstone) - AddComponent( \ - /datum/component/spin2win, \ - spin_cooldown_time = 25 SECONDS, \ - on_spin_callback = CALLBACK(src, PROC_REF(on_spin)), \ - on_unspin_callback = CALLBACK(src, PROC_REF(on_unspin)), \ - start_spin_message = span_danger("%USER begins swinging the sword around with inhuman strength!"), \ - end_spin_message = span_warning("%USER's inhuman strength dissipates and the sword's runes grow cold!") \ - ) - -/obj/item/cult_bastard/proc/on_spin(mob/living/user, duration) - var/oldcolor = user.color - user.color = "#ff0000" - user.add_stun_absorption( - source = name, - duration = duration, - priority = 2, - message = span_warning("%EFFECT_OWNER doesn't even flinch as the sword's power courses through [user.p_them()]!"), - self_message = span_boldwarning("You shrug off the stun!"), - examine_message = span_warning("%EFFECT_OWNER_THEYRE glowing with a blazing red aura!"), - ) - user.spin(duration, 1) - animate(user, color = oldcolor, time = duration, easing = EASE_IN) - addtimer(CALLBACK(user, TYPE_PROC_REF(/atom, update_atom_colour)), duration) - block_chance = 100 - slowdown += 1.5 - spinning = TRUE - -/obj/item/cult_bastard/proc/on_unspin(mob/living/user) - block_chance = 50 - slowdown -= 1.5 - spinning = FALSE - -/obj/item/cult_bastard/can_be_pulled(user) - return FALSE - -/obj/item/cult_bastard/pickup(mob/living/user) - . = ..() - if(!IS_CULTIST(user)) - if(!IS_HERETIC(user)) - to_chat(user, "\"I wouldn't advise that.\"") - force = 5 - return - else - to_chat(user, span_cult_large("\"You cling to the Forgotten Gods, as if you're more than their pawn.\"")) - to_chat(user, span_userdanger("A horrible force yanks at your arm!")) - user.emote("scream") - user.apply_damage(30, BRUTE, pick(GLOB.arm_zones)) - user.dropItemToGround(src, TRUE) - user.Paralyze(50) - return - force = initial(force) - -/obj/item/cult_bastard/IsReflect(def_zone) - if(!spinning) - return FALSE - return TRUE - -/obj/item/cult_bastard/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK, damage_type = BRUTE) - if(!prob(final_block_chance)) - return FALSE - owner.visible_message(span_danger("[owner] parries [attack_text] with [src]!")) - return TRUE diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm index b3263cfcb8c..7b380b58ac8 100644 --- a/code/modules/antagonists/cult/cult_items.dm +++ b/code/modules/antagonists/cult/cult_items.dm @@ -76,6 +76,8 @@ Striking a noncultist, however, will tear their flesh."} block_sound = 'sound/weapons/parry.ogg' attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "rends") attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "rend") + /// If TRUE, it can be used at will by anyone, non-cultists included + var/free_use = FALSE /obj/item/melee/cultblade/Initialize(mapload) . = ..() @@ -93,7 +95,7 @@ Striking a noncultist, however, will tear their flesh."} return FALSE /obj/item/melee/cultblade/attack(mob/living/target, mob/living/carbon/human/user) - if(!IS_CULTIST(user)) + if(!IS_CULTIST(user) && !free_use) user.Paralyze(100) user.dropItemToGround(src, TRUE) user.visible_message(span_warning("A powerful force shoves [user] away from [target]!"), \ @@ -106,6 +108,174 @@ Striking a noncultist, however, will tear their flesh."} return ..() +#define WIELDER_SPELLS "wielder_spell" +#define SWORD_SPELLS "sword_spell" +#define SWORD_PREFIX "sword_prefix" + +/obj/item/melee/cultblade/haunted + name = "haunted longsword" + desc = "An eerie sword with a blade that is less 'black' than it is 'absolute nothingness'. It glows with furious, restrained green energy." + icon_state = "hauntedblade" + inhand_icon_state = "hauntedblade" + worn_icon_state = "hauntedblade" + force = 35 + throwforce = 35 + block_chance = 55 + wound_bonus = -25 + bare_wound_bonus = 30 + free_use = TRUE + light_color = COLOR_HERETIC_GREEN + light_range = 4 + /// holder for the actual action when created. + var/datum/action/cooldown/spell/path_wielder_action + var/mob/living/trapped_entity + /// The heretic path that the variable below uses to index abilities. Assigned when the heretic is ensouled. + var/heretic_path + /// Nested static list used to index abilities and names. + var/static/list/heretic_paths_to_haunted_sword_abilities = list( + // Ash + PATH_ASH = list( + WIELDER_SPELLS = list(/datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash), + SWORD_SPELLS = list(/datum/action/cooldown/spell/pointed/ash_beams), + SWORD_PREFIX = "ashen", + ), + // Flesh + PATH_FLESH = list( + WIELDER_SPELLS = list(/datum/action/cooldown/spell/pointed/blood_siphon), + SWORD_SPELLS = list(/datum/action/cooldown/spell/pointed/cleave), + SWORD_PREFIX = "sanguine", + ), + // Void + PATH_VOID = list( + WIELDER_SPELLS = list(/datum/action/cooldown/spell/pointed/void_phase), + SWORD_SPELLS = list(/datum/action/cooldown/spell/cone/staggered/cone_of_cold/void), + SWORD_PREFIX = "tenebrous", + ), + // Blade + PATH_BLADE = list( + WIELDER_SPELLS = list(/datum/action/cooldown/spell/pointed/projectile/furious_steel/haunted), + SWORD_SPELLS = list(/datum/action/cooldown/spell/pointed/projectile/furious_steel/solo), + SWORD_PREFIX = "keen", + ), + // Rust + PATH_RUST = list( + WIELDER_SPELLS = list(/datum/action/cooldown/spell/cone/staggered/entropic_plume), + SWORD_SPELLS = list(/datum/action/cooldown/spell/aoe/rust_conversion, /datum/action/cooldown/spell/pointed/rust_construction), + SWORD_PREFIX = "rusted", + ), + // Cosmic + PATH_COSMIC = list( + WIELDER_SPELLS = list(/datum/action/cooldown/spell/conjure/cosmic_expansion), + SWORD_SPELLS = list(/datum/action/cooldown/spell/pointed/projectile/star_blast), + SWORD_PREFIX = "astral", + ), + // Lock + PATH_LOCK = list( + WIELDER_SPELLS = list(/datum/action/cooldown/spell/pointed/burglar_finesse), + SWORD_SPELLS = list(/datum/action/cooldown/spell/pointed/apetra_vulnera), + SWORD_PREFIX = "incisive", + ), + // 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_PREFIX = "shimmering", + ), + // Starter + PATH_START = list( + WIELDER_SPELLS = null, + SWORD_SPELLS = null, + SWORD_PREFIX = "stillborn", // lol loser + ) , + ) + +/obj/item/melee/cultblade/haunted/Initialize(mapload, mob/soul_to_bind, mob/awakener, do_bind = TRUE) + . = ..() + + AddElement(/datum/element/heretic_focus) + add_traits(list(TRAIT_CASTABLE_LOC, TRAIT_SPELLS_TRANSFER_TO_LOC), INNATE_TRAIT) + if(do_bind && !mapload) + bind_soul(soul_to_bind, awakener) + +/obj/item/melee/cultblade/haunted/proc/bind_soul(mob/soul_to_bind, mob/awakener) + + var/datum/mind/trapped_mind = soul_to_bind?.mind + + if(!trapped_mind) + return // Can't do anything further down the list + + if(trapped_mind) + AddComponent(/datum/component/spirit_holding,\ + soul_to_bind = trapped_mind,\ + awakener = awakener,\ + allow_renaming = FALSE,\ + allow_channeling = FALSE,\ + ) + + // Get the heretic's new body and antag datum. + trapped_entity = trapped_mind?.current + trapped_entity.key = trapped_mind?.key + var/datum/antagonist/heretic/heretic_holder = IS_HERETIC(trapped_entity) + if(!heretic_holder) + stack_trace("[soul_to_bind] in but not a heretic on the heretic soul blade.") + + // Give the spirit a spell that lets them try to fly around. + var/datum/action/cooldown/spell/pointed/sword_fling/fling_act = \ + new /datum/action/cooldown/spell/pointed/sword_fling(trapped_mind, to_fling = src) + fling_act.Grant(trapped_entity) + + // Set the sword's path for spell selection. + heretic_path = heretic_holder.heretic_path + + // 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() + trapped_entity.mind.remove_antag_datum(/datum/antagonist/heretic) + + // Add the fallen antag datum, give them a heads-up of what's happening. + var/datum/antagonist/soultrapped_heretic/bozo = new() + bozo.objectives |= copied_objectives + trapped_entity.mind.add_antag_datum(bozo) + to_chat(trapped_entity, span_userdanger("You've been sacrificed to the Enemy, and trapped inside a haunted blade! While you cannot escape, you may help the Cult, your current wielder, or even pester everyone with what few abilities you kept.")) + + // Assigning the spells to give to the wielder and spirit. + // Let them cast the given spell. + ADD_TRAIT(trapped_entity, TRAIT_ALLOW_HERETIC_CASTING, INNATE_TRAIT) + + var/list/path_spells = heretic_paths_to_haunted_sword_abilities[heretic_path] + + var/list/wielder_spells = path_spells[WIELDER_SPELLS] + var/list/sword_spells = path_spells[SWORD_SPELLS] + + name = "[path_spells[SWORD_PREFIX]] [name]" + + + // Granting the path spells. The sword spirit gains it outright, while it's just instanced for wielders to be added on pickup. + + if(sword_spells) + for(var/datum/action/cooldown/spell/sword_spell as anything in sword_spells) + sword_spell = new sword_spell(trapped_entity) + sword_spell?.Grant(trapped_entity) + sword_spell?.overlay_icon_state = "bg_cult_border" // for flavor, and also helps distinguish + + + if(wielder_spells) + for(var/datum/action/cooldown/spell/wielder_spell as anything in wielder_spells) + path_wielder_action = new wielder_spell(src) + wielder_spell?.overlay_icon_state = "bg_cult_border" + +/obj/item/melee/cultblade/haunted/equipped(mob/user, slot, initial) + . = ..() + if(slot & ITEM_SLOT_HANDS) + path_wielder_action?.Grant(user) + +/obj/item/melee/cultblade/haunted/dropped(mob/user, silent) + . = ..() + path_wielder_action?.Remove(user) + +#undef WIELDER_SPELLS +#undef SWORD_SPELLS +#undef SWORD_PREFIX + /obj/item/melee/cultblade/ghost name = "eldritch sword" force = 19 //can't break normal airlocks @@ -120,7 +290,7 @@ Striking a noncultist, however, will tear their flesh."} /obj/item/melee/cultblade/pickup(mob/living/user) ..() - if(!IS_CULTIST(user)) + if(!IS_CULTIST(user) && !free_use) to_chat(user, span_cult_large("\"I wouldn't advise that.\"")) /datum/action/innate/dash/cult @@ -563,6 +733,78 @@ Striking a noncultist, however, will tear their flesh."} #undef MAX_SHUTTLE_CURSES +#define GATEWAY_TURF_SCAN_RANGE 40 + +/obj/item/proteon_orb + name = "summoning orb" + desc = "An eerie translucent orb that feels impossibly light. Legends say summoning orbs are created from corrupted scrying orbs. If you hold it close to your ears, you can hear the screams of the damned." + icon = 'icons/obj/antags/cult/items.dmi' + icon_state = "summoning_orb" + light_range = 3 + light_color = COLOR_CULT_RED + +/obj/item/proteon_orb/examine(mob/user) + . = ..() + if(!IS_CULTIST(user) && isliving(user)) + var/mob/living/living_user = user + living_user.adjustOrganLoss(ORGAN_SLOT_BRAIN, 5) + . += span_danger("It hurts just to look at it. Better keep away.") + else + . += span_cult("It can be used to create a gateway to Nar'Sie's domain, which will summon weak, sentient constructs over time.") + +/obj/item/proteon_orb/attack_self(mob/living/user) + + var/list/turfs_to_scan = detect_room(get_turf(user), max_size = GATEWAY_TURF_SCAN_RANGE) + + if(!IS_CULTIST(user)) + to_chat(user, span_cult_large("\"You want to enter my domain? Go ahead.\"")) + turfs_to_scan = null // narsie wants to have some fun and the veil wont stop her + + for(var/turf/hole_candidate as anything in turfs_to_scan) + if(locate(/obj/structure/spawner/sentient/proteon_spawner) in hole_candidate) + to_chat(user, span_cult_bold("There's a gateway too close nearby. The veil is not yet weak enough to allow such close rips in its fabric.")) + return + to_chat(user, span_cult_bold_italic("You focus on [src] and direct it into the ground. It rumbles...")) + + var/turf/open/hole_spot = get_turf(user) + if(!istype(hole_spot) || isgroundlessturf(hole_spot)) + to_chat(user, span_notice("This is not a suitable spot.")) + return + + INVOKE_ASYNC(hole_spot, TYPE_PROC_REF(/turf/open, quake_gateway), user) + qdel(src) + +/** + * Bespoke proc that happens when a proteon orb is activated, creating a gateway. + * If activated by a non-cultist, they get an unusual game over. +*/ +/turf/open/proc/quake_gateway(mob/living/user) + Shake(2, 2, 5 SECONDS) + narsie_act(TRUE, TRUE, 100) + var/fucked = FALSE + if(!IS_CULTIST(user)) + fucked = TRUE + ADD_TRAIT(user, TRAIT_NO_TRANSFORM, REF(src)) // keep em in place + user.add_atom_colour(COLOR_CULT_RED, TEMPORARY_COLOUR_PRIORITY) + user.visible_message(span_cult_bold("Dark tendrils appear from the ground and root [user] in place!")) + sleep(5 SECONDS) // can we still use these or. i mean its async + new /obj/structure/spawner/sentient/proteon_spawner(src) + visible_message(span_cult_bold("A mysterious hole appears out of nowhere!")) + if(!fucked || QDELETED(user)) + return + if(get_turf(user) != src) // they get away. for now + REMOVE_TRAIT(user, TRAIT_NO_TRANSFORM, REF(src)) + return + user.visible_message(span_cult_bold("[user] is pulled into the portal through an infinitesmally minuscule hole, shredding [user.p_their()] body!")) + sleep(5 SECONDS) + user.visible_message(span_cult_italic("An unusually large construct appears through the portal!")) + user.gib() // total destruction + var/mob/living/basic/construct/proteon/hostile/remnant = new(get_step_rand(src)) + remnant.name = "[user]" // no, they do not become it + remnant.transform *= 1.5 + +#undef GATEWAY_TURF_SCAN_RANGE + /obj/item/cult_shift name = "veil shifter" desc = "This relic instantly teleports you, and anything you're pulling, forward by a moderate distance." diff --git a/code/modules/antagonists/cult/cult_other.dm b/code/modules/antagonists/cult/cult_other.dm index 82b342b87ef..9435baedba1 100644 --- a/code/modules/antagonists/cult/cult_other.dm +++ b/code/modules/antagonists/cult/cult_other.dm @@ -16,8 +16,15 @@ /proc/is_convertable_to_cult(mob/living/target, datum/team/cult/specific_cult) if(!istype(target)) return FALSE - if(isnull(target.mind) || !GET_CLIENT(target)) + if(isnull(target.mind)) return FALSE + +// disables client checks if testing for easier debugging +#ifndef TESTING + if(!GET_CLIENT(target)) + return FALSE +#endif + if(target.mind.unconvertable) return FALSE if(ishuman(target) && target.mind.holy_role) diff --git a/code/modules/antagonists/cult/cult_structure_altar.dm b/code/modules/antagonists/cult/cult_structure_altar.dm index 9347acb3321..e38591c0c07 100644 --- a/code/modules/antagonists/cult/cult_structure_altar.dm +++ b/code/modules/antagonists/cult/cult_structure_altar.dm @@ -2,6 +2,7 @@ #define ELDRITCH_WHETSTONE "Eldritch Whetstone" #define CONSTRUCT_SHELL "Construct Shell" #define UNHOLY_WATER "Flask of Unholy Water" +#define PROTEON_ORB "Portal Summoning Orb" // Cult altar. Gives out consumable items. /obj/structure/destructible/cult/item_dispenser/altar @@ -10,6 +11,7 @@ cult_examine_tip = "Can be used to create eldritch whetstones, construct shells, and flasks of unholy water." icon_state = "talismanaltar" break_message = "The altar shatters, leaving only the wailing of the damned!" + mansus_conversion_path = /obj/effect/heretic_rune /obj/structure/destructible/cult/item_dispenser/altar/setup_options() var/static/list/altar_items = list( @@ -27,7 +29,20 @@ ), ) + var/extra_item = extra_options() + options = altar_items + if(!isnull(extra_item)) + options += extra_item + +/obj/structure/destructible/cult/item_dispenser/altar/extra_options() + if(!cult_team?.unlocked_heretic_items[PROTEON_ORB_UNLOCKED]) + return + return list(PROTEON_ORB = list( + PREVIEW_IMAGE = image(icon = 'icons/obj/antags/cult/items.dmi', icon_state = "summoning_orb"), + OUTPUT_ITEMS = list(/obj/item/proteon_orb), + ), + ) /obj/structure/destructible/cult/item_dispenser/altar/succcess_message(mob/living/user, obj/item/spawned_item) to_chat(user, span_cult_italic("You kneel before [src] and your faith is rewarded with [spawned_item]!")) @@ -35,3 +50,4 @@ #undef ELDRITCH_WHETSTONE #undef CONSTRUCT_SHELL #undef UNHOLY_WATER +#undef PROTEON_ORB diff --git a/code/modules/antagonists/cult/cult_structure_archives.dm b/code/modules/antagonists/cult/cult_structure_archives.dm index a9617396633..d3a96dd1f77 100644 --- a/code/modules/antagonists/cult/cult_structure_archives.dm +++ b/code/modules/antagonists/cult/cult_structure_archives.dm @@ -2,6 +2,7 @@ #define CULT_BLINDFOLD "Zealot's Blindfold" #define CURSE_ORB "Shuttle Curse" #define VEIL_WALKER "Veil Walker Set" +#define CRIMSON_FOCUS "Crimson Focus" // Cult archives. Gives out utility items. /obj/structure/destructible/cult/item_dispenser/archives @@ -12,6 +13,7 @@ light_range = 1.5 light_color = LIGHT_COLOR_FIRE break_message = "The books and tomes of the archives burn into ash as the desk shatters!" + mansus_conversion_path = /obj/item/codex_cicatrix /obj/structure/destructible/cult/item_dispenser/archives/setup_options() var/static/list/archive_items = list( @@ -29,7 +31,20 @@ ), ) + var/extra_item = extra_options() + options = archive_items + if(!isnull(extra_item)) + options += extra_item + +/obj/structure/destructible/cult/item_dispenser/archives/extra_options() + if(!cult_team?.unlocked_heretic_items[CRIMSON_FOCUS_UNLOCKED]) + return + return list(CRIMSON_FOCUS = list( + PREVIEW_IMAGE = image(icon = 'icons/obj/clothing/neck.dmi', icon_state = "crimson_focus"), + OUTPUT_ITEMS = list(/obj/item/clothing/neck/heretic_focus/crimson_focus), + ), + ) /obj/structure/destructible/cult/item_dispenser/archives/succcess_message(mob/living/user, obj/item/spawned_item) to_chat(user, span_cult_italic("You summon [spawned_item] from [src]!")) @@ -41,3 +56,4 @@ #undef CULT_BLINDFOLD #undef CURSE_ORB #undef VEIL_WALKER +#undef CRIMSON_FOCUS diff --git a/code/modules/antagonists/cult/cult_structure_forge.dm b/code/modules/antagonists/cult/cult_structure_forge.dm index 912db7d37e9..12d15b9296e 100644 --- a/code/modules/antagonists/cult/cult_structure_forge.dm +++ b/code/modules/antagonists/cult/cult_structure_forge.dm @@ -2,6 +2,7 @@ #define NARSIE_ARMOR "Nar'Sien Hardened Armor" #define FLAGELLANT_ARMOR "Flagellant's Robe" #define ELDRITCH_SWORD "Eldritch Longsword" +#define CURSED_BLADE "Cursed Ritual Blade" // Cult forge. Gives out combat weapons. /obj/structure/destructible/cult/item_dispenser/forge @@ -12,6 +13,7 @@ light_range = 2 light_color = LIGHT_COLOR_LAVA break_message = "The forge breaks apart into shards with a howling scream!" + mansus_conversion_path = /obj/structure/destructible/eldritch_crucible /obj/structure/destructible/cult/item_dispenser/forge/setup_options() var/static/list/forge_items = list( @@ -29,7 +31,21 @@ ), ) + var/extra_item = extra_options() + options = forge_items + if(!isnull(extra_item)) + options += extra_item + +/obj/structure/destructible/cult/item_dispenser/forge/extra_options() + if(!cult_team?.unlocked_heretic_items[CURSED_BLADE_UNLOCKED]) + return + return list(CURSED_BLADE = list( + PREVIEW_IMAGE = image(icon = 'icons/obj/weapons/khopesh.dmi', icon_state = "cursed_blade"), + OUTPUT_ITEMS = list(/obj/item/melee/sickly_blade/cursed), + ), + ) + /obj/structure/destructible/cult/item_dispenser/forge/succcess_message(mob/living/user, obj/item/spawned_item) to_chat(user, span_cult_italic("You work [src] as dark knowledge guides your hands, creating [spawned_item]!")) @@ -42,3 +58,4 @@ #undef NARSIE_ARMOR #undef FLAGELLANT_ARMOR #undef ELDRITCH_SWORD +#undef CURSED_BLADE diff --git a/code/modules/antagonists/cult/cult_structures.dm b/code/modules/antagonists/cult/cult_structures.dm index 932c3ac03c1..5067dcf9799 100644 --- a/code/modules/antagonists/cult/cult_structures.dm +++ b/code/modules/antagonists/cult/cult_structures.dm @@ -12,16 +12,43 @@ var/cult_examine_tip /// The cooldown for when items can be dispensed. COOLDOWN_DECLARE(use_cooldown) + /// Assigned cult team, set when cultistism is checked. + var/datum/team/cult/cult_team + +/obj/structure/destructible/cult/Destroy() + cult_team = null + return ..() + +/obj/structure/destructible/cult/Initialize(mapload) + . = ..() + RegisterSignal(src, COMSIG_ATOM_CONSTRUCTED, PROC_REF(on_constructed)) + +/obj/structure/destructible/cult/proc/on_constructed(datum/source, mob/builder) + SIGNAL_HANDLER + var/datum/antagonist/cult/cultist = builder.mind?.has_antag_datum(/datum/antagonist/cult, TRUE) + cult_team = cultist?.get_team() + +/// Tries to find a cultist. If it succeeds, it also takes advantage of the moment to define the structure's cult team if it's not set yet. +/obj/structure/destructible/cult/proc/is_cultist_check(mob/fool) + + if(!IS_CULTIST(fool)) + return FALSE + + if(isnull(cult_team)) + var/datum/antagonist/cult/cultist = fool.mind?.has_antag_datum(/datum/antagonist/cult, TRUE) + cult_team = cultist?.get_team() + + return TRUE /obj/structure/destructible/cult/examine_status(mob/user) - if(IS_CULTIST(user) || isobserver(user)) + if(is_cultist_check(user) || isobserver(user)) return span_cult("It's at [round(atom_integrity * 100 / max_integrity)]% stability.") return ..() /obj/structure/destructible/cult/examine(mob/user) . = ..() . += span_notice("[src] is [anchored ? "secured to":"unsecured from"] the floor.") - if(IS_CULTIST(user) || isobserver(user)) + if(is_cultist_check(user) || isobserver(user)) if(cult_examine_tip) . += span_cult(cult_examine_tip) if(!COOLDOWN_FINISHED(src, use_cooldown_duration)) @@ -65,16 +92,25 @@ /obj/structure/destructible/cult/item_dispenser /// An associated list of options this structure can make. See setup_options() for format. var/list/options + /// The dispenser will create this item and then delete itself if it is rust converted. + var/obj/mansus_conversion_path = /obj/item/skub /obj/structure/destructible/cult/item_dispenser/Initialize(mapload) . = ..() setup_options() +/obj/structure/destructible/cult/item_dispenser/rust_heretic_act() + visible_message(span_notice("[src] crumbles to dust. In its midst, you spot \a [initial(mansus_conversion_path.name)].")) + var/turf/turfy = get_turf(src) + new mansus_conversion_path(turfy) + turfy.rust_heretic_act() + return ..() + /obj/structure/destructible/cult/item_dispenser/attack_hand(mob/living/user, list/modifiers) . = ..() if(.) return - if(!isliving(user) || !IS_CULTIST(user)) + if(!isliving(user) || !is_cultist_check(user)) to_chat(user, span_warning("You're pretty sure you know exactly what this is used for and you can't seem to touch it.")) return if(!anchored) @@ -84,6 +120,8 @@ to_chat(user, span_cult_italic("The magic in [src] is too weak, it will be ready to use again in [DisplayTimeText(COOLDOWN_TIMELEFT(src, use_cooldown))].")) return + setup_options() + var/list/spawned_items = get_items_to_spawn(user) if(!length(spawned_items)) return @@ -109,6 +147,18 @@ /obj/structure/destructible/cult/item_dispenser/proc/setup_options() return +/* + * Extra options, currently used for items unlocked after sacrificing a heretic. + * + * The list of options is a associated list of format: + * item_name = list( + * preview = image(), + * output = list(paths), + * ) + */ +/obj/structure/destructible/cult/item_dispenser/proc/extra_options() + return + /* * Get all items that this cult building will spawn when interacted with. * Opens a radial menu for the user and shows them the list of options, which they can choose from. @@ -150,7 +200,7 @@ * Returns TRUE if the user is a living mob that is a cultist and is not incapacitated. */ /obj/structure/destructible/cult/item_dispenser/proc/check_menu(mob/user) - return isliving(user) && IS_CULTIST(user) && !user.incapacitated() + return isliving(user) && is_cultist_check(user) && !user.incapacitated() // Spooky looking door used in gateways. Or something. /obj/effect/gateway diff --git a/code/modules/antagonists/cult/datums/cult_team.dm b/code/modules/antagonists/cult/datums/cult_team.dm index c47cc2145b5..09d4a25a321 100644 --- a/code/modules/antagonists/cult/datums/cult_team.dm +++ b/code/modules/antagonists/cult/datums/cult_team.dm @@ -16,9 +16,16 @@ var/reckoning_complete = FALSE ///Has the cult risen, and gotten red eyes? var/cult_risen = FALSE - ///Has the cult asceneded, and gotten halos? + ///Has the cult ascended, and gotten halos? var/cult_ascendent = FALSE + /// List that keeps track of which items have been unlocked after a heretic was sacked. + var/list/unlocked_heretic_items = list( + CURSED_BLADE_UNLOCKED = FALSE, + CRIMSON_FOCUS_UNLOCKED = FALSE, + PROTEON_ORB_UNLOCKED = FALSE, + ) + ///Has narsie been summoned yet? var/narsie_summoned = FALSE ///How large were we at max size. diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm index 25f13ae22b2..d1983cb552f 100644 --- a/code/modules/antagonists/cult/runes.dm +++ b/code/modules/antagonists/cult/runes.dm @@ -326,16 +326,12 @@ structure_check() searches for nearby cultist structures required for the invoca return TRUE /obj/effect/rune/convert/proc/do_sacrifice(mob/living/sacrificial, list/invokers, datum/team/cult/cult_team) - var/big_sac = FALSE + var/target_sac = FALSE if((((ishuman(sacrificial) || iscyborg(sacrificial)) && sacrificial.stat != DEAD) || cult_team.is_sacrifice_target(sacrificial.mind)) && length(invokers) < 3) for(var/invoker in invokers) to_chat(invoker, span_cult_italic("[sacrificial] is too greatly linked to the world! You need three acolytes!")) return FALSE - var/signal_result = SEND_SIGNAL(sacrificial, COMSIG_LIVING_CULT_SACRIFICED, invokers, cult_team) - if(signal_result & STOP_SACRIFICE) - return FALSE - if(sacrificial.mind) LAZYADD(GLOB.sacrificed, WEAKREF(sacrificial.mind)) for(var/datum/objective/sacrifice/sac_objective in cult_team.objectives) @@ -343,15 +339,23 @@ structure_check() searches for nearby cultist structures required for the invoca sac_objective.sacced = TRUE sac_objective.clear_sacrifice() sac_objective.update_explanation_text() - big_sac = TRUE + target_sac = TRUE else LAZYADD(GLOB.sacrificed, WEAKREF(sacrificial)) new /obj/effect/temp_visual/cult/sac(loc) - if(!(signal_result & SILENCE_SACRIFICE_MESSAGE)) + var/signal_result = SEND_SIGNAL(sacrificial, COMSIG_LIVING_CULT_SACRIFICED, invokers, cult_team) + + var/do_message = TRUE + if(signal_result & SILENCE_SACRIFICE_MESSAGE) + do_message = FALSE + if((signal_result & SILENCE_NONTARGET_SACRIFICE_MESSAGE) && !(target_sac)) + do_message = FALSE + + if(do_message) for(var/invoker in invokers) - if(big_sac) + if(target_sac) to_chat(invoker, span_cult_large("\"Yes! This is the one I desire! You have done well.\"")) continue if(ishuman(sacrificial) || iscyborg(sacrificial)) @@ -359,6 +363,10 @@ structure_check() searches for nearby cultist structures required for the invoca else to_chat(invoker, span_cult_large("\"I accept this meager sacrifice.\"")) + // post-message + if(signal_result & STOP_SACRIFICE) + return FALSE + if(iscyborg(sacrificial)) var/construct_class = show_radial_menu(invokers[1], sacrificial, GLOB.construct_radial_images, require_near = TRUE, tooltips = TRUE) if(QDELETED(sacrificial) || !construct_class) @@ -370,17 +378,20 @@ structure_check() searches for nearby cultist structures required for the invoca sacriborg.mmi = null qdel(sacrificial) return TRUE - - var/obj/item/soulstone/stone = new(loc) - if(sacrificial.mind && !HAS_TRAIT(sacrificial, TRAIT_SUICIDED)) - stone.capture_soul(sacrificial, invokers[1], forced = TRUE) - - if(sacrificial) + if(sacrificial && (signal_result & DUST_SACRIFICE)) // No soulstone when dusted + playsound(sacrificial, 'sound/magic/teleport_diss.ogg', 100, TRUE) + sacrificial.investigate_log("has been sacrificially dusted by the cult.", INVESTIGATE_DEATHS) + sacrificial.dust(TRUE, FALSE, TRUE) + else if (sacrificial) + var/obj/item/soulstone/stone = new(loc) + if(sacrificial.mind && !HAS_TRAIT(sacrificial, TRAIT_SUICIDED)) + stone.capture_soul(sacrificial, invokers[1], forced = TRUE) playsound(sacrificial, 'sound/magic/disintegrate.ogg', 100, TRUE) sacrificial.investigate_log("has been sacrificially gibbed by the cult.", INVESTIGATE_DEATHS) sacrificial.gib(DROP_ALL_REMAINS) try_spawn_sword() // after sharding and gibbing, which potentially dropped a null rod + return TRUE /// Tries to convert a null rod over the rune to a cult sword @@ -396,12 +407,12 @@ structure_check() searches for nearby cultist structures required for the invoca rod.visible_message(span_cult_italic(displayed_message)) switch(num_slain) - if(0, 1) + if(0) animate_spawn_sword(rod, /obj/item/melee/cultblade/dagger) - if(2) + if(1) animate_spawn_sword(rod, /obj/item/melee/cultblade) else - animate_spawn_sword(rod, /obj/item/cult_bastard) + animate_spawn_sword(rod, /obj/item/melee/cultblade/halberd) return TRUE return FALSE diff --git a/code/modules/antagonists/cult/sword_fling.dm b/code/modules/antagonists/cult/sword_fling.dm new file mode 100644 index 00000000000..766f97917de --- /dev/null +++ b/code/modules/antagonists/cult/sword_fling.dm @@ -0,0 +1,93 @@ + +/datum/action/cooldown/spell/pointed/sword_fling + name = "Sword Fling" + desc = "Try to fling yourself around." + ranged_mousepointer = 'icons/effects/mouse_pointers/cult_target.dmi' + background_icon_state = "bg_heretic" + overlay_icon_state = "bg_cult_border" + + button_icon = 'icons/mob/actions/actions_cult.dmi' + button_icon_state = "sword_fling" + + school = SCHOOL_EVOCATION + cooldown_time = 4 SECONDS + invocation_type = INVOCATION_NONE + spell_requirements = NONE + + cast_range = 6 + active_msg = "You ready yourself to attempt to leap!" + var/obj/item/flinged_sword + +/datum/action/cooldown/spell/pointed/sword_fling/New(Target, to_fling) + . = ..() + flinged_sword = to_fling + +/datum/action/cooldown/spell/pointed/sword_fling/Destroy() + flinged_sword = null + . = ..() + +/datum/action/cooldown/spell/pointed/sword_fling/is_valid_target(atom/cast_on) + return isatom(cast_on) + +/datum/action/cooldown/spell/pointed/sword_fling/cast(turf/cast_on) + . = ..() + var/atom/sword_loc = flinged_sword.loc + if(ismob(sword_loc)) + var/mob/loccer = sword_loc + var/resist_chance = 25 + var/fail_text = "You struggle, but [loccer] keeps [loccer.p_their()] grip on you!" + var/particle_to_spawn = null + if(IS_CULTIST_OR_CULTIST_MOB(loccer)) + resist_chance = 10 // your mastahs + fail_text = "You struggle, but [loccer]'s grip is unnaturally hard to resist!" + particle_to_spawn = /obj/effect/temp_visual/cult/sparks + if(IS_HERETIC_OR_MONSTER(loccer) || IS_LUNATIC(loccer)) + resist_chance = 15 + fail_text = "You struggle, but [loccer] deftly handles the grip movement." + particle_to_spawn = /obj/effect/temp_visual/eldritch_sparks + if(loccer.mind?.holy_role) // IS_PRIEST() + resist_chance = 12 + fail_text = "You struggle, but [loccer]'s holy grip holds tight against your thrashing." + particle_to_spawn = /obj/effect/temp_visual/blessed + if(IS_WIZARD(loccer)) + resist_chance = 5 // magic master + fail_text = "You struggle, but [loccer]'s handle on magic easily neutralizes your movement." + particle_to_spawn = /obj/effect/particle_effect/sparks/electricity + + new particle_to_spawn(get_turf(loccer)) + + if(prob(resist_chance)) + flinged_sword.forceMove(get_turf(loccer)) + flinged_sword.visible_message(span_alert("\the [flinged_sword] yanks itself out of [loccer]'s grip!")) + // flung by later code + else + to_chat(owner, span_warning(fail_text)) + return + + if(isitem(sword_loc)) + flinged_sword.forceMove(get_turf(sword_loc)) + flinged_sword.visible_message(span_alert("\the [flinged_sword] yanks itself out of [sword_loc]!")) + // flung by later code + + if(iscloset(sword_loc)) + var/obj/structure/closet/sword_closet = sword_loc + if(!(sword_closet.open(owner, force = prob(5), special_effects = TRUE))) + sword_closet.container_resist_act(owner, loc_required = FALSE) + flinged_sword.visible_message(span_alert("\the [flinged_sword] yanks itself out of [sword_closet]!")) + + // no general struct/machinery check. imagine if someone put the sword in a vendor + + if(isturf(sword_loc)) + new /obj/effect/temp_visual/sword_sparks(sword_loc) + flinged_sword.throw_at(cast_on, cast_range, flinged_sword.throw_speed, owner) + flinged_sword.visible_message(\ + span_warning("\the [flinged_sword] lunges at \the [cast_on]!")) + +/obj/effect/temp_visual/eldritch_sparks + icon_state = "purplesparkles" + +/obj/effect/temp_visual/sword_sparks + icon_state = "mech_toxin" // only used in one place and it looks kinda good + +/obj/effect/temp_visual/blessed + icon_state = "blessed" diff --git a/code/modules/antagonists/heretic/heretic_antag.dm b/code/modules/antagonists/heretic/heretic_antag.dm index 01d7c33f718..e038da74f07 100644 --- a/code/modules/antagonists/heretic/heretic_antag.dm +++ b/code/modules/antagonists/heretic/heretic_antag.dm @@ -82,6 +82,15 @@ PATH_MOON = COLOR_BLUE_LIGHT, ) + /// 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_focus = 0, + /mob/living/basic/construct/harvester/heretic = 0, + ) + /// Simpler version of above used to limit amount of loot that can be hoarded + var/rewards_given = 0 + /datum/antagonist/heretic/Destroy() LAZYNULL(sac_targets) return ..() @@ -229,6 +238,8 @@ if (!issilicon(our_mob)) GLOB.reality_smash_track.add_tracked_mind(owner) + ADD_TRAIT(our_mob, TRAIT_MANSUS_TOUCHED, REF(src)) + RegisterSignal(our_mob, COMSIG_LIVING_CULT_SACRIFICED, PROC_REF(on_cult_sacrificed)) RegisterSignals(our_mob, list(COMSIG_MOB_BEFORE_SPELL_CAST, COMSIG_MOB_SPELL_ACTIVATED), PROC_REF(on_spell_cast)) RegisterSignal(our_mob, COMSIG_MOB_ITEM_AFTERATTACK, PROC_REF(on_item_afterattack)) RegisterSignal(our_mob, COMSIG_MOB_LOGIN, PROC_REF(fix_influence_network)) @@ -242,12 +253,14 @@ 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_MOB_ITEM_AFTERATTACK, COMSIG_MOB_LOGIN, COMSIG_LIVING_POST_FULLY_HEAL, + COMSIG_LIVING_CULT_SACRIFICED, )) /datum/antagonist/heretic/on_body_transfer(mob/living/old_body, mob/living/new_body) @@ -395,6 +408,118 @@ var/datum/heretic_knowledge/living_heart/heart_knowledge = get_knowledge(/datum/heretic_knowledge/living_heart) heart_knowledge.on_research(source, src) +/// Signal proc for [COMSIG_LIVING_CULT_SACRIFICED] to reward cultists for sacrificing a heretic +/datum/antagonist/heretic/proc/on_cult_sacrificed(mob/living/source, list/invokers) + SIGNAL_HANDLER + + for(var/mob/dead/observer/ghost in GLOB.dead_mob_list) // uhh let's find the guy to shove him back in + if((ghost.mind?.current == source) && ghost.client) // is it the same guy and do they have the same client + ghost.reenter_corpse() // shove them in! it doesnt do it automatically + + // Drop all items and splatter them around messily. + var/list/dustee_items = source.unequip_everything() + for(var/obj/item/loot as anything in dustee_items) + loot.throw_at(get_step_rand(source), 2, 4, pick(invokers), TRUE) + + // Create the blade, give it the heretic and a randomly-chosen master for the soul sword component + var/obj/item/melee/cultblade/haunted/haunted_blade = new(get_turf(source), source, pick(invokers)) + + // Cool effect for the rune as well as the item + var/obj/effect/rune/convert/conversion_rune = locate() in get_turf(source) + if(conversion_rune) + conversion_rune.gender_reveal( + outline_color = COLOR_HERETIC_GREEN, + ray_color = null, + do_float = FALSE, + do_layer = FALSE, + ) + + haunted_blade.gender_reveal(outline_color = null, ray_color = COLOR_HERETIC_GREEN) + + for(var/mob/living/culto as anything in invokers) + to_chat(culto, span_cult_large("\"A follower of the forgotten gods! You must be rewarded for such a valuable sacrifice.\"")) + + // Locate a cultist team (Is there a better way??) + var/mob/living/random_cultist = pick(invokers) + var/datum/antagonist/cult/antag = random_cultist.mind.has_antag_datum(/datum/antagonist/cult) + ASSERT(antag) + var/datum/team/cult/cult_team = antag.get_team() + + // Unlock one of 3 special items! + var/list/possible_unlocks + for(var/i in cult_team.unlocked_heretic_items) + if(cult_team.unlocked_heretic_items[i]) + continue + LAZYADD(possible_unlocks, i) + if(length(possible_unlocks)) + var/result = pick(possible_unlocks) + cult_team.unlocked_heretic_items[result] = TRUE + + for(var/datum/mind/mind as anything in cult_team.members) + if(mind.current) + SEND_SOUND(mind.current, 'sound/magic/clockwork/narsie_attack.ogg') + to_chat(mind.current, span_cult_large(span_warning("Arcane and forbidden knowledge floods your forges and archives. The cult has learned how to create the ")) + span_cult_large(span_hypnophrase("[result]!"))) + + return SILENCE_SACRIFICE_MESSAGE|DUST_SACRIFICE + +/** + * Creates an animation of the item slowly lifting up from the floor with a colored outline, then slowly drifting back down. + * Arguments: + * * outline_color: Default is between pink and light blue, is the color of the outline filter. + * * ray_color: Null by default. If not set, just copies outline. Used for the ray filter. + * * anim_time: Total time of the animation. Split into two different calls. + * * do_float: Lets you disable the sprite floating up and down. + * * do_layer: Lets you disable the layering increase. + */ +/obj/proc/gender_reveal( + outline_color = null, + ray_color = null, + anim_time = 10 SECONDS, + do_float = TRUE, + do_layer = TRUE, +) + + var/og_layer + if(do_layer) + // Layering above to stand out! + og_layer = layer + layer = ABOVE_MOB_LAYER + + // Slowly floats up, then slowly goes down. + if(do_float) + animate(src, pixel_y = 12, time = anim_time * 0.5, easing = QUAD_EASING | EASE_OUT) + animate(pixel_y = 0, time = anim_time * 0.5, easing = QUAD_EASING | EASE_IN) + + // Adding a cool outline effect + if(outline_color) + add_filter("gender_reveal_outline", 3, list("type" = "outline", "color" = outline_color, "size" = 0.5)) + // Animating it! + var/gay_filter = get_filter("gender_reveal_outline") + animate(gay_filter, alpha = 110, time = 1.5 SECONDS, loop = -1) + animate(alpha = 40, time = 2.5 SECONDS) + + // Adding a cool ray effect + if(ray_color) + add_filter(name = "gender_reveal_ray", priority = 1, params = list( + type = "rays", + size = 45, + color = ray_color, + density = 6 + )) + // Animating it! + var/ray_filter = get_filter("gender_reveal_ray") + // I understand nothing but copypaste saves lives + animate(ray_filter, offset = 100, time = 30 SECONDS, loop = -1, flags = ANIMATION_PARALLEL) + + addtimer(CALLBACK(src, PROC_REF(remove_gender_reveal_fx), og_layer), anim_time) + +/** + * Removes the non-animate effects from above proc + */ +/obj/proc/remove_gender_reveal_fx(og_layer) + remove_filter(list("gender_reveal_outline", "gender_reveal_ray")) + layer = og_layer + /** * Create our objectives for our heretic. */ diff --git a/code/modules/antagonists/heretic/heretic_knowledge.dm b/code/modules/antagonists/heretic/heretic_knowledge.dm index 5369e5fee8d..6758b077404 100644 --- a/code/modules/antagonists/heretic/heretic_knowledge.dm +++ b/code/modules/antagonists/heretic/heretic_knowledge.dm @@ -38,6 +38,8 @@ var/priority = 0 /// What path is this on. If set to "null", assumed to be unreachable (or abstract). var/route + ///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 /datum/heretic_knowledge/New() if(!mutually_exclusive) @@ -523,11 +525,23 @@ abstract_parent_type = /datum/heretic_knowledge/summon /// Typepath of a mob to summon when we finish the recipe. var/mob/living/mob_to_summon - ///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 /datum/heretic_knowledge/summon/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc) - var/mob/living/summoned = new mob_to_summon(loc) + summon_ritual_mob(user, loc, mob_to_summon) + +/** + * Creates the ritual mob and grabs a ghost for it + * + * * user - the mob doing the summoning + * * loc - where the summon is happening + * * mob_to_summon - either a mob instance or a mob typepath + */ +/datum/heretic_knowledge/proc/summon_ritual_mob(mob/living/user, turf/loc, mob/living/mob_to_summon) + var/mob/living/summoned + if(isliving(mob_to_summon)) + summoned = mob_to_summon + else + summoned = new mob_to_summon(loc) summoned.ai_controller?.set_ai_status(AI_STATUS_OFF) // Fade in the summon while the ghost poll is ongoing. // Also don't let them mess with the summon while waiting @@ -557,6 +571,7 @@ var/datum/antagonist/heretic_monster/heretic_monster = summoned.mind.add_antag_datum(/datum/antagonist/heretic_monster) heretic_monster.set_owner(user.mind) + summoned.RegisterSignal(user, COMSIG_LIVING_DEATH, TYPE_PROC_REF(/mob/living/, on_master_death)) var/datum/objective/heretic_summon/summon_objective = locate() in user.mind.get_all_objectives() summon_objective?.num_summoned++ diff --git a/code/modules/antagonists/heretic/heretic_monsters.dm b/code/modules/antagonists/heretic/heretic_monsters.dm index 1f95ed62ea9..2ce653c5b67 100644 --- a/code/modules/antagonists/heretic/heretic_monsters.dm +++ b/code/modules/antagonists/heretic/heretic_monsters.dm @@ -41,3 +41,7 @@ owner.announce_objectives() to_chat(owner, span_boldnotice("You are a [ishuman(owner.current) ? "shambling corpse returned":"horrible creation brought"] to this plane through the Gates of the Mansus.")) to_chat(owner, span_notice("Your master is [master]. Assist them to all ends.")) + + if(istype(owner.current, /mob/living/basic/construct/harvester/heretic)) + var/mob/living/basic/construct/harvester/heretic/shitcode = owner.current + shitcode.master = master diff --git a/code/modules/antagonists/heretic/items/heretic_blades.dm b/code/modules/antagonists/heretic/items/heretic_blades.dm index edcf5e15368..777c0d49abc 100644 --- a/code/modules/antagonists/heretic/items/heretic_blades.dm +++ b/code/modules/antagonists/heretic/items/heretic_blades.dm @@ -24,18 +24,46 @@ attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "rend") var/after_use_message = "" +<<<<<<< HEAD /obj/item/melee/sickly_blade/attack(mob/living/M, mob/living/user) if(!IS_HERETIC_OR_MONSTER(user)) +======= +/obj/item/melee/sickly_blade/examine(mob/user) + . = ..() + if(!check_usability(user)) + return + + . += span_notice("You can shatter the blade to teleport to a random, (mostly) safe location by activating it in-hand.") + +/// Checks if the passed mob can use this blade without being stunned +/obj/item/melee/sickly_blade/proc/check_usability(mob/living/user) + return IS_HERETIC_OR_MONSTER(user) + +/obj/item/melee/sickly_blade/pre_attack(atom/A, mob/living/user, params) + . = ..() + if(.) + return . + if(!check_usability(user)) +>>>>>>> 0c4df344b8a... [MIRROR] Cult Vs. Heretic: 7 Months Later Edition [MDB IGNORE] (#3384) to_chat(user, span_danger("You feel a pulse of alien intellect lash out at your mind!")) var/mob/living/carbon/human/human_user = user human_user.AdjustParalyzed(5 SECONDS) return TRUE +<<<<<<< HEAD return ..() +======= + return . +>>>>>>> 0c4df344b8a... [MIRROR] Cult Vs. Heretic: 7 Months Later Edition [MDB IGNORE] (#3384) /obj/item/melee/sickly_blade/attack_self(mob/user) - var/turf/safe_turf = find_safe_turf(zlevel = z, extended_safety_checks = TRUE) - if(IS_HERETIC_OR_MONSTER(user)) + seek_safety(user) + return ..() + +/// Attempts to teleport the passed mob to somewhere safe on the station, if they can use the blade. +/obj/item/melee/sickly_blade/proc/seek_safety(mob/user) + var/turf/safe_turf = find_safe_turf(zlevels = z, extended_safety_checks = TRUE) + if(check_usability(user)) if(do_teleport(user, safe_turf, channel = TELEPORT_CHANNEL_MAGIC)) to_chat(user, span_warning("As you shatter [src], you feel a gust of energy flow through your body. [after_use_message]")) else @@ -45,6 +73,7 @@ playsound(src, SFX_SHATTER, 70, TRUE) //copied from the code for smashing a glass sheet onto the ground to turn it into a shard qdel(src) +<<<<<<< HEAD /obj/item/melee/sickly_blade/afterattack(atom/target, mob/user, proximity_flag, click_parameters) . = ..() if(!isliving(target)) @@ -54,13 +83,16 @@ SEND_SIGNAL(user, COMSIG_HERETIC_BLADE_ATTACK, target, src) else SEND_SIGNAL(user, COMSIG_HERETIC_RANGED_BLADE_ATTACK, target, src) +======= +/obj/item/melee/sickly_blade/afterattack(atom/target, mob/user, click_parameters) + if(isliving(target)) + SEND_SIGNAL(user, COMSIG_HERETIC_BLADE_ATTACK, target, src) -/obj/item/melee/sickly_blade/examine(mob/user) - . = ..() - if(!IS_HERETIC_OR_MONSTER(user)) - return - - . += span_notice("You can shatter the blade to teleport to a random, (mostly) safe location by activating it in-hand.") +/obj/item/melee/sickly_blade/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) + if(isliving(interacting_with)) + SEND_SIGNAL(user, COMSIG_HERETIC_RANGED_BLADE_ATTACK, interacting_with, src) + return ITEM_INTERACT_BLOCKING +>>>>>>> 0c4df344b8a... [MIRROR] Cult Vs. Heretic: 7 Months Later Edition [MDB IGNORE] (#3384) // Path of Rust's blade /obj/item/melee/sickly_blade/rust @@ -154,3 +186,76 @@ icon_state = "moon_blade" inhand_icon_state = "moon_blade" after_use_message = "The Moon hears your call..." + +// Path of Nar'Sie's blade +// What!? This blade is given to cultists as an altar item when they sacrifice a heretic. +// It is also given to the heretic themself if they sacrifice a cultist. +/obj/item/melee/sickly_blade/cursed + name = "\improper cursed blade" + desc = "A dark blade, cursed to bleed forever. In constant struggle between the eldritch and the dark, it is forced to accept any wielder as its master. \ + Its eye's cornea drips blood endlessly into the ground, yet its piercing gaze remains on you." + force = 25 + throwforce = 15 + block_chance = 35 + wound_bonus = 25 + bare_wound_bonus = 15 + armour_penetration = 35 + icon_state = "cursed_blade" + inhand_icon_state = "cursed_blade" + +/obj/item/melee/sickly_blade/cursed/Initialize(mapload) + . = ..() + + var/examine_text = {"Allows the scribing of blood runes of the cult of Nar'Sie. + The combination of eldritch power and Nar'Sie's might allows for vastly increased rune drawing speed, + alongside the vicious strength of the blade being more powerful than usual.\n + It can also be shattered in-hand by cultists (via right-click), teleporting them to relative safety."} + + AddComponent(/datum/component/cult_ritual_item, span_cult(examine_text), turfs_that_boost_us = /turf) // Always fast to draw! + +/obj/item/melee/sickly_blade/cursed/attack_self_secondary(mob/user) + seek_safety(user, TRUE) + +/obj/item/melee/sickly_blade/cursed/seek_safety(mob/user, secondary_attack = FALSE) + if(IS_CULTIST(user) && !secondary_attack) + return FALSE + return ..() + +/obj/item/melee/sickly_blade/cursed/check_usability(mob/living/user) + if(IS_HERETIC_OR_MONSTER(user) || IS_CULTIST(user)) + return TRUE + if(prob(15)) + to_chat(user, span_cult_large(pick("\"An untouched mind? Amusing.\"", "\" I suppose it isn't worth the effort to stop you.\"", "\"Go ahead. I don't care.\"", "\"You'll be mine soon enough.\""))) + var/obj/item/bodypart/affecting = user.get_active_hand() + if(!affecting) + return + affecting.receive_damage(burn = 5) + playsound(src, SFX_SEAR, 25, TRUE) + to_chat(user, span_danger("Your hand sizzles.")) // Nar nar might not care but their essence still doesn't like you + else if(prob(15)) + to_chat(user, span_big(span_hypnophrase("LW'NAFH'NAHOR UH'ENAH'YMG EPGOKA AH NAFL MGEMPGAH'EHYE"))) + to_chat(user, span_danger("Horrible, unintelligible utterances flood your mind!")) + user.adjustOrganLoss(ORGAN_SLOT_BRAIN, 15) // This can kill you if you ignore it + return TRUE + +/obj/item/melee/sickly_blade/cursed/equipped(mob/user, slot) + . = ..() + if(IS_HERETIC_OR_MONSTER(user)) + after_use_message = "The Mansus hears your call..." + else if(IS_CULTIST(user)) + after_use_message = "Nar'Sie hears your call..." + else + after_use_message = null + +/obj/item/melee/sickly_blade/cursed/interact_with_atom(atom/target, mob/living/user, list/modifiers) + . = ..() + + var/datum/antagonist/heretic/heretic_datum = IS_HERETIC(user) + if(!heretic_datum) + return NONE + + // Can only carve runes with it if off combat mode. + if(isopenturf(target) && !user.combat_mode) + heretic_datum.try_draw_rune(user, target, drawing_time = 14 SECONDS) // Faster than pen, slower than cicatrix + return ITEM_INTERACT_BLOCKING + return NONE diff --git a/code/modules/antagonists/heretic/items/heretic_necks.dm b/code/modules/antagonists/heretic/items/heretic_necks.dm index e24c17abdee..3f140dc99df 100644 --- a/code/modules/antagonists/heretic/items/heretic_necks.dm +++ b/code/modules/antagonists/heretic/items/heretic_necks.dm @@ -9,6 +9,101 @@ . = ..() AddElement(/datum/element/heretic_focus) +/obj/item/clothing/neck/heretic_focus/crimson_focus + name = "Crimson Focus" + desc = "A blood-red focusing glass that provides a link to the world beyond, and worse. Its eye is constantly twitching and gazing in all directions. It almost seems to be silently screaming..." + icon_state = "crimson_focus" + /// The aura healing component. Used to delete it when taken off. + var/datum/component/component + /// If active or not, used to add and remove its cult and heretic buffs. + var/active = FALSE + +/obj/item/clothing/neck/heretic_focus/crimson_focus/equipped(mob/living/user, slot) + . = ..() + if(!(slot & ITEM_SLOT_NECK)) + return + + var/team_color = COLOR_ADMIN_PINK + if(IS_CULTIST(user)) + var/datum/action/innate/cult/blood_magic/magic_holder = locate() in user.actions + team_color = COLOR_CULT_RED + magic_holder.magic_enhanced = TRUE + else if(IS_HERETIC_OR_MONSTER(user) && !active) + for(var/datum/action/cooldown/spell/spell_action in user.actions) + spell_action.cooldown_time *= 0.5 + active = TRUE + team_color = COLOR_GREEN + else + team_color = pick(COLOR_CULT_RED, COLOR_GREEN) + + user.add_traits(list(TRAIT_MANSUS_TOUCHED, TRAIT_BLOODY_MESS), REF(src)) + to_chat(user, span_alert("Your heart takes on a strange yet soothing irregular rhythm, and your blood feels significantly less viscous than it used to be. You're not sure if that's a good thing.")) + component = user.AddComponent( \ + /datum/component/aura_healing, \ + range = 3, \ + brute_heal = 1, \ + burn_heal = 1, \ + blood_heal = 2, \ + suffocation_heal = 5, \ + simple_heal = 0.6, \ + requires_visibility = FALSE, \ + limit_to_trait = TRAIT_MANSUS_TOUCHED, \ + healing_color = team_color, \ + ) + +/obj/item/clothing/neck/heretic_focus/crimson_focus/dropped(mob/living/user) + . = ..() + + if(!istype(user)) + return + + if(HAS_TRAIT_FROM(user, TRAIT_MANSUS_TOUCHED, REF(src))) + to_chat(user, span_notice("Your heart and blood return to their regular old rhythm and flow.")) + + if(IS_HERETIC_OR_MONSTER(user) && active) + for(var/datum/action/cooldown/spell/spell_action in user.actions) + spell_action.cooldown_time *= 2 + active = FALSE + QDEL_NULL(component) + user.remove_traits(list(TRAIT_MANSUS_TOUCHED, TRAIT_BLOODY_MESS), REF(src)) + + // If boosted enable is set, to prevent false dropped() calls from repeatedly nuking the max spells. + var/datum/action/innate/cult/blood_magic/magic_holder = locate() in user.actions + // Remove the last spell if over new limit, as we will reduce our max spell amount. Done beforehand as it causes a index out of bounds runtime otherwise. + if(magic_holder?.magic_enhanced) + QDEL_NULL(magic_holder.spells[ENHANCED_BLOODCHARGE]) + magic_holder?.magic_enhanced = FALSE + + +/obj/item/clothing/neck/heretic_focus/crimson_focus/attack_self(mob/living/user, modifiers) + . = ..() + to_chat(user, span_danger("You start tightly squeezing [src]...")) + if(!do_after(user, 1.25 SECONDS, src)) + return + to_chat(user, span_danger("[src] explodes into a shower of gore and blood, drenching your arm. You can feel the blood seeping into your skin. You inmediately feel better, but soon, the feeling turns hollow as your veins itch.")) + new /obj/effect/gibspawner/generic(get_turf(src)) + var/heal_amt = user.adjustBruteLoss(-50) + user.adjustFireLoss( -(50 - abs(heal_amt)) ) // no double dipping + + // I want it to poison the user but I also think it'd be neat if they got their juice as well. But that cancels most of the damage out. So I dunno. + user.reagents?.add_reagent(/datum/reagent/fuel/unholywater, rand(6, 10)) + user.reagents?.add_reagent(/datum/reagent/eldritch, rand(6, 10)) + qdel(src) + +/obj/item/clothing/neck/heretic_focus/crimson_focus/examine(mob/user) + . = ..() + + var/magic_dude + if(IS_CULTIST(user)) + . += span_cult_bold("This focus will allow you to store one extra spell and halve the empowering time, alongside providing a small regenerative effect.") + magic_dude = TRUE + if(IS_HERETIC_OR_MONSTER(user)) + . += span_notice("This focus will halve your spell cooldowns, alongside granting a small regenerative effect to any nearby heretics or monsters, including you.") + magic_dude = TRUE + + if(magic_dude) + . += span_red("You can also squeeze it to recover a large amount of health quickly, at a cost...") + /obj/item/clothing/neck/eldritch_amulet name = "Warm Eldritch Medallion" desc = "A strange medallion. Peering through the crystalline surface, the world around you melts away. You see your own beating heart, and the pulsing of a thousand others." diff --git a/code/modules/antagonists/heretic/knowledge/rust_lore.dm b/code/modules/antagonists/heretic/knowledge/rust_lore.dm index fe553c8b8c7..5a10b55d1c8 100644 --- a/code/modules/antagonists/heretic/knowledge/rust_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/rust_lore.dm @@ -73,7 +73,7 @@ SIGNAL_HANDLER // Rusting an airlock causes it to lose power, mostly to prevent the airlock from shocking you. - // This is a bit of a hack, but fixing this would require the enture wire cut/pulse system to be reworked. + // This is a bit of a hack, but fixing this would require the entire wire cut/pulse system to be reworked. if(istype(target, /obj/machinery/door/airlock)) var/obj/machinery/door/airlock/airlock = target airlock.loseMainPower() @@ -95,56 +95,10 @@ route = PATH_RUST /datum/heretic_knowledge/rust_regen/on_gain(mob/user, datum/antagonist/heretic/our_heretic) - RegisterSignal(user, COMSIG_MOVABLE_MOVED, PROC_REF(on_move)) - RegisterSignal(user, COMSIG_LIVING_LIFE, PROC_REF(on_life)) + user.AddElement(/datum/element/leeching_walk) /datum/heretic_knowledge/rust_regen/on_lose(mob/user, datum/antagonist/heretic/our_heretic) - UnregisterSignal(user, list(COMSIG_MOVABLE_MOVED, COMSIG_LIVING_LIFE)) - -/* - * Signal proc for [COMSIG_MOVABLE_MOVED]. - * - * Checks if we should have baton resistance on the new turf. - */ -/datum/heretic_knowledge/rust_regen/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, type) - return - - REMOVE_TRAIT(source, TRAIT_BATON_RESISTANCE, type) - -/** - * Signal proc for [COMSIG_LIVING_LIFE]. - * - * Gradually heals the heretic ([source]) on rust, - * including baton knockdown and stamina damage. - */ -/datum/heretic_knowledge/rust_regen/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 - need_mob_update += source.adjustBruteLoss(-3, updating_health = FALSE) - need_mob_update += source.adjustFireLoss(-3, updating_health = FALSE) - need_mob_update += source.adjustToxLoss(-3, updating_health = FALSE, forced = TRUE) // Slimes are people too - need_mob_update += source.adjustOxyLoss(-1.5, updating_health = FALSE) - need_mob_update += source.adjustStaminaLoss(-10, updating_stamina = FALSE) - if(need_mob_update) - source.updatehealth() - // Reduces duration of stuns/etc - source.AdjustAllImmobility(-0.5 SECONDS) - // Heals blood loss - if(source.blood_volume < BLOOD_VOLUME_NORMAL) - source.blood_volume += 2.5 * seconds_per_tick - // Slowly regulates your body temp - source.adjust_bodytemperature((source.get_body_temp_normal() - source.bodytemperature)/5) + user.RemoveElement(/datum/element/leeching_walk) /datum/heretic_knowledge/mark/rust_mark name = "Mark of Rust" 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 d45b1039b26..b4772a753e6 100644 --- a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm +++ b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm @@ -80,8 +80,11 @@ // If we have targets, we can check to see if we can do a sacrifice // Let's remove any humans in our atoms list that aren't a sac target for(var/mob/living/carbon/human/sacrifice in atoms) - // If the mob's not in soft crit or worse, or isn't one of the sacrifices, remove it from the list - if(sacrifice.stat < SOFT_CRIT || !(sacrifice in heretic_datum.sac_targets)) + // If the mob's not in soft crit or worse, remove from list + if(sacrifice.stat < SOFT_CRIT) + atoms -= sacrifice + // Otherwise if it's neither a target nor a cultist, remove it + else if(!(sacrifice in heretic_datum.sac_targets) && !IS_CULTIST(sacrifice)) atoms -= sacrifice // Finally, return TRUE if we have a target in the list @@ -94,7 +97,9 @@ /datum/heretic_knowledge/hunt_and_sacrifice/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc) var/datum/antagonist/heretic/heretic_datum = IS_HERETIC(user) - if(!LAZYLEN(heretic_datum.sac_targets)) + // Force it to work if the sacrifice is a cultist, even if there's no targets. + var/mob/living/carbon/human/sac = selected_atoms[1] + if(!LAZYLEN(heretic_datum.sac_targets) && !IS_CULTIST(sac)) if(obtain_targets(user, heretic_datum = heretic_datum)) return TRUE else @@ -190,34 +195,128 @@ * * selected_atoms - a list of all atoms chosen. Should be (at least) one human. * * loc - the turf the sacrifice is occuring on */ -/datum/heretic_knowledge/hunt_and_sacrifice/proc/sacrifice_process(mob/living/user, list/selected_atoms) +/datum/heretic_knowledge/hunt_and_sacrifice/proc/sacrifice_process(mob/living/user, list/selected_atoms, turf/loc) var/datum/antagonist/heretic/heretic_datum = IS_HERETIC(user) var/mob/living/carbon/human/sacrifice = locate() in selected_atoms if(!sacrifice) CRASH("[type] sacrifice_process didn't have a human in the atoms list. How'd it make it so far?") - if(!(sacrifice in heretic_datum.sac_targets)) - CRASH("[type] sacrifice_process managed to get a non-target human. This is incorrect.") + if(!(sacrifice in heretic_datum.sac_targets) && !IS_CULTIST(sacrifice)) + CRASH("[type] sacrifice_process managed to get a non-target, non-cult human. This is incorrect.") if(sacrifice.mind) LAZYADD(target_blacklist, sacrifice.mind) heretic_datum.remove_sacrifice_target(sacrifice) + var/feedback = "Your patrons accept your offer" var/sac_job_flag = sacrifice.mind?.assigned_role?.job_flags | sacrifice.last_mind?.assigned_role?.job_flags - if(sac_job_flag & JOB_HEAD_OF_STAFF) - heretic_datum.knowledge_points++ + var/datum/antagonist/cult/cultist_datum = IS_CULTIST(sacrifice) + // 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.high_value_sacrifices++ feedback += " graciously" + else if(cultist_datum) + heretic_datum.knowledge_points += 1 + grant_reward(user, sacrifice, loc) + // easier to read + var/rewards_given = heretic_datum.rewards_given + // Chance for it to send a warning to cultists, higher with each reward. Stops after 5 because they probably got the hint by then. + if(prob(min(15 * rewards_given)) && (rewards_given <= 5)) + for(var/datum/mind/mind as anything in cultist_datum.cult_team.members) + if(mind.current) + SEND_SOUND(mind.current, 'sound/magic/clockwork/narsie_attack.ogg') + var/message = span_narsie("A vile heretic has ") + \ + span_cult_large(span_hypnophrase("sacrificed")) + \ + span_narsie(" one of our own. Destroy and sacrifice the infidel before it claims more!") + to_chat(mind.current, message) + // he(retic) gets a warn too + var/message = span_cult_bold("You feel that your action has attracted") + \ + span_cult_bold_italic(" attention.") + to_chat(user, message) + return + else + heretic_datum.knowledge_points += 2 to_chat(user, span_hypnophrase("[feedback].")) - heretic_datum.total_sacrifices++ - heretic_datum.knowledge_points += 2 + if(!begin_sacrifice(sacrifice)) + disembowel_target(sacrifice) + return sacrifice.apply_status_effect(/datum/status_effect/heretic_curse, user) - if(!begin_sacrifice(sacrifice)) - disembowel_target(sacrifice) + +/datum/heretic_knowledge/hunt_and_sacrifice/proc/grant_reward(mob/living/user, mob/living/sacrifice, turf/loc) + + // Visible and audible encouragement! + to_chat(user, span_big(span_hypnophrase("A servant of the Sanguine Apostate!"))) + to_chat(user, span_hierophant("Your patrons are rapturous!")) + playsound(sacrifice, 'sound/magic/disintegrate.ogg', 75, TRUE) + + // Drop all items and splatter them around messily. + var/list/dustee_items = sacrifice.unequip_everything() + for(var/obj/item/loot as anything in dustee_items) + loot.throw_at(get_step_rand(sacrifice), 2, 4, user, TRUE) + + // The loser is DUSTED. + sacrifice.dust(TRUE, TRUE) + + // Increase reward counter + var/datum/antagonist/heretic/antag = IS_HERETIC(user) + antag.rewards_given++ + + // We limit the amount so the heretic doesn't just turn into a frickin' god (early) + to_chat(user, span_hierophant("You feel the rotten energies of the infidel warp and twist, mixing with that of your own...")) + if(prob(8 * antag.rewards_given)) + to_chat(user, span_hierophant("Faint, dark red sparks flit around the dust, then fade. It looks like your patrons weren't able to fashion something out of it.")) + return + + // Cool effect for the rune as well as the item + var/obj/effect/heretic_rune/rune = locate() in range(2, user) + if(rune) + rune.gender_reveal( + outline_color = COLOR_CULT_RED, + ray_color = null, + do_float = FALSE, + do_layer = FALSE, + ) + + addtimer(CALLBACK(src, PROC_REF(deposit_reward), user, loc, null, rune), 5 SECONDS) + + +/datum/heretic_knowledge/hunt_and_sacrifice/proc/deposit_reward(mob/user, turf/loc, loop = 0, obj/rune) + if(loop > 5) // Max limit for retrying a reward + return + // Remove the rays, we don't need them anymore. + rune?.remove_filter("reward_outline") + playsound(loc, 'sound/magic/repulse.ogg', 75, TRUE) + var/datum/antagonist/heretic/heretic_datum = IS_HERETIC(user) + ASSERT(heretic_datum) + // This list will be almost identical to unlocked_heretic_items, with the same keys, the difference being the values will be 1 to 5. + var/list/rewards = heretic_datum.unlocked_heretic_items.Copy() + // We will make it increasingly less likely to get a reward if you've already got it + for(var/possible_reward in heretic_datum.unlocked_heretic_items) + var/amount_already_awarded = heretic_datum.unlocked_heretic_items[possible_reward] + rewards[possible_reward] = min(5 - (amount_already_awarded * 2), 1) + + var/atom/reward = pick_weight(rewards) + reward = new reward(loc) + + if(isliving(reward)) + if(summon_ritual_mob(user, loc, reward) == FALSE) + qdel(reward) + deposit_reward(user, loc, loop++, rune) // If no ghosts, try again until limit is hit + return + + else if(isitem(reward)) + var/obj/item/item_reward = reward + item_reward.gender_reveal(outline_color = null, ray_color = COLOR_CULT_RED) + + ASSERT(reward) + + return reward /** * This proc is called from [proc/sacrifice_process] after the heretic successfully sacrifices [sac_target].) diff --git a/code/modules/antagonists/heretic/magic/aggressive_spread.dm b/code/modules/antagonists/heretic/magic/aggressive_spread.dm index fedc30193ed..dfb4a948474 100644 --- a/code/modules/antagonists/heretic/magic/aggressive_spread.dm +++ b/code/modules/antagonists/heretic/magic/aggressive_spread.dm @@ -17,7 +17,16 @@ aoe_radius = 2 /datum/action/cooldown/spell/aoe/rust_conversion/get_things_to_cast_on(atom/center) - return RANGE_TURFS(aoe_radius, center) + + var/list/things_to_convert = RANGE_TURFS(aoe_radius, center) + + // Also converts things right next to you. + for(var/atom/movable/nearby_movable in view(1, center)) + if(nearby_movable == owner || !isstructure(nearby_movable) ) + continue + things_to_convert += nearby_movable + + return things_to_convert /datum/action/cooldown/spell/aoe/rust_conversion/cast_on_thing_in_aoe(turf/victim, mob/living/caster) // We have less chance of rusting stuff that's further @@ -27,9 +36,11 @@ if(prob(chance_of_not_rusting)) return - caster.do_rust_heretic_act(victim) + if(ismob(caster)) + caster.do_rust_heretic_act(victim) + else + victim.rust_heretic_act() -/datum/action/cooldown/spell/aoe/rust_conversion/small - name = "Rust Conversion" - desc = "Spreads rust onto nearby surfaces." - aoe_radius = 2 +/datum/action/cooldown/spell/aoe/rust_conversion/construct + name = "Construct Spread" + cooldown_time = 15 SECONDS diff --git a/code/modules/antagonists/heretic/magic/ash_ascension.dm b/code/modules/antagonists/heretic/magic/ash_ascension.dm index 0d8ca8da4f1..8b564198a61 100644 --- a/code/modules/antagonists/heretic/magic/ash_ascension.dm +++ b/code/modules/antagonists/heretic/magic/ash_ascension.dm @@ -129,16 +129,17 @@ INVOKE_ASYNC(src, PROC_REF(fire_line), owner, line_target(offset, flame_line_length, target, owner)) /datum/action/cooldown/spell/pointed/ash_beams/proc/line_target(offset, range, atom/at, atom/user) + var/turf/user_loc = get_turf(user) if(!at) return - var/angle = ATAN2(at.x - user.x, at.y - user.y) + offset + var/angle = ATAN2(at.x - user_loc.x, at.y - user_loc.y) + offset var/turf/T = get_turf(user) for(var/i in 1 to range) - var/turf/check = locate(user.x + cos(angle) * i, user.y + sin(angle) * i, user.z) + var/turf/check = locate(user_loc.x + cos(angle) * i, user_loc.y + sin(angle) * i, user_loc.z) if(!check) break T = check - return (get_line(user, T) - get_turf(user)) + return (get_line(user_loc, T) - user_loc) /datum/action/cooldown/spell/pointed/ash_beams/proc/fire_line(atom/source, list/turfs) var/list/hit_list = list() diff --git a/code/modules/antagonists/heretic/magic/burglar_finesse.dm b/code/modules/antagonists/heretic/magic/burglar_finesse.dm index 7bb6960354e..a90acb8495f 100644 --- a/code/modules/antagonists/heretic/magic/burglar_finesse.dm +++ b/code/modules/antagonists/heretic/magic/burglar_finesse.dm @@ -26,7 +26,7 @@ return FALSE var/obj/storage_item = locate(/obj/item/storage/backpack) in cast_on.contents - + if(isnull(storage_item)) return FALSE diff --git a/code/modules/antagonists/heretic/magic/fire_blast.dm b/code/modules/antagonists/heretic/magic/fire_blast.dm index f76a1f18d17..4c17ca5ffc0 100644 --- a/code/modules/antagonists/heretic/magic/fire_blast.dm +++ b/code/modules/antagonists/heretic/magic/fire_blast.dm @@ -23,8 +23,8 @@ var/beam_duration = 2 SECONDS /datum/action/cooldown/spell/charged/beam/fire_blast/cast(atom/cast_on) - if(isliving(cast_on)) - var/mob/living/caster = cast_on + var/mob/living/caster = get_caster_from_target(cast_on) + if(istype(caster)) // Caster becomes fireblasted, but in a good way - heals damage over time caster.apply_status_effect(/datum/status_effect/fire_blasted, beam_duration, -2) return ..() diff --git a/code/modules/antagonists/heretic/magic/furious_steel.dm b/code/modules/antagonists/heretic/magic/furious_steel.dm index 15648a9b4d3..0ab882a9289 100644 --- a/code/modules/antagonists/heretic/magic/furious_steel.dm +++ b/code/modules/antagonists/heretic/magic/furious_steel.dm @@ -67,7 +67,7 @@ QDEL_NULL(blade_effect) var/mob/living/living_user = on_who - blade_effect = living_user.apply_status_effect(/datum/status_effect/protective_blades, null, projectile_amount, 25, 0.66 SECONDS) + blade_effect = living_user.apply_status_effect(/datum/status_effect/protective_blades, null, projectile_amount, 25, 0.66 SECONDS, projectile_type) RegisterSignal(blade_effect, COMSIG_QDELETING, PROC_REF(on_status_effect_deleted)) /datum/action/cooldown/spell/pointed/projectile/furious_steel/on_deactivation(mob/on_who, refund_cooldown = TRUE) @@ -106,10 +106,12 @@ sharpness = SHARP_EDGED wound_bonus = 15 pass_flags = PASSTABLE | PASSFLAPS + /// Color applied as an outline filter on init + var/outline_color = "#f8f8ff" /obj/projectile/floating_blade/Initialize(mapload) . = ..() - add_filter("dio_knife", 2, list("type" = "outline", "color" = "#f8f8ff", "size" = 1)) + add_filter("dio_knife", 2, list("type" = "outline", "color" = outline_color, "size" = 1)) /obj/projectile/floating_blade/prehit_pierce(atom/hit) if(isliving(hit) && isliving(firer)) @@ -128,3 +130,40 @@ return PROJECTILE_DELETE_WITHOUT_HITTING return ..() + +/obj/projectile/floating_blade/haunted + name = "ritual blade" + icon = 'icons/obj/weapons/khopesh.dmi' + icon_state = "render" + damage = 35 + wound_bonus = 25 + outline_color = "#D7CBCA" + +/datum/action/cooldown/spell/pointed/projectile/furious_steel/solo + name = "Lesser Furious Steel" + cooldown_time = 20 SECONDS + projectile_amount = 1 + active_msg = "You summon forth a blade of furious silver." + deactive_msg = "You conceal the blade of furious silver." + +/datum/action/cooldown/spell/pointed/projectile/furious_steel/haunted + name = "Cursed Steel" + desc = "Summon two cursed blades which orbit you. \ + While orbiting you, these blades will protect you from from attacks, but will be consumed on use. \ + Additionally, you can click to fire the blades at a target, dealing damage and causing bleeding." + background_icon_state = "bg_heretic" // kept intentionally + overlay_icon_state = "bg_cult_border" + button_icon = 'icons/mob/actions/actions_ecult.dmi' + button_icon_state = "cursed_steel" + sound = 'sound/weapons/guillotine.ogg' + + cooldown_time = 40 SECONDS + invocation = "IA!" + invocation_type = INVOCATION_SHOUT + + spell_requirements = NONE + + active_msg = "You summon forth two cursed blades." + deactive_msg = "You conceal the cursed blades." + projectile_amount = 2 + projectile_type = /obj/projectile/floating_blade/haunted diff --git a/code/modules/antagonists/heretic/magic/mansus_grasp.dm b/code/modules/antagonists/heretic/magic/mansus_grasp.dm index 4ba6aceb200..e3fbb364f6b 100644 --- a/code/modules/antagonists/heretic/magic/mansus_grasp.dm +++ b/code/modules/antagonists/heretic/magic/mansus_grasp.dm @@ -38,11 +38,34 @@ var/mob/living/living_hit = victim living_hit.apply_damage(10, BRUTE, wound_bonus = CANT_WOUND) - if(iscarbon(victim)) - var/mob/living/carbon/carbon_hit = victim - carbon_hit.adjust_timed_status_effect(4 SECONDS, /datum/status_effect/speech/slurring/heretic) - carbon_hit.AdjustKnockdown(5 SECONDS) - carbon_hit.adjustStaminaLoss(80) + if(!iscarbon(victim)) + return TRUE + + var/mob/living/carbon/carbon_hit = victim + + // Cultists are momentarily disoriented by the stunning aura. Enough for both parties to go 'oh shit' but only a mild combat ability. + // Cultists have an identical effect on their stun hand. The heretic's faster spell charge time is made up for by their lack of teammates. + if(IS_CULTIST(carbon_hit)) + carbon_hit.AdjustKnockdown(0.5 SECONDS) + carbon_hit.adjust_confusion_up_to(1.5 SECONDS, 3 SECONDS) + carbon_hit.adjust_dizzy_up_to(1.5 SECONDS, 3 SECONDS) + ADD_TRAIT(carbon_hit, TRAIT_NO_SIDE_KICK, REF(src)) // We don't want this to be a good stunning tool, just minor disorientation + addtimer(TRAIT_CALLBACK_REMOVE(carbon_hit, TRAIT_NO_SIDE_KICK, REF(src)), 1 SECONDS) + + var/old_color = carbon_hit.color + carbon_hit.color = COLOR_CULT_RED + animate(carbon_hit, color = old_color, time = 4 SECONDS, easing = EASE_IN) + carbon_hit.mob_light(range = 1.5, power = 2.5, color = COLOR_CULT_RED, duration = 0.5 SECONDS) + playsound(carbon_hit, 'sound/magic/curse.ogg', 50, TRUE) + + to_chat(caster, span_warning("An unholy force intervenes as you grasp [carbon_hit], absorbing most of the effects!")) + to_chat(carbon_hit, span_warning("As [caster] grasps you with eldritch forces, your blood magic absorbs most of the effects!")) + carbon_hit.balloon_alert_to_viewers("absorbed!") + return TRUE + + carbon_hit.adjust_timed_status_effect(4 SECONDS, /datum/status_effect/speech/slurring/heretic) + carbon_hit.AdjustKnockdown(5 SECONDS) + carbon_hit.adjustStaminaLoss(80) return TRUE diff --git a/code/modules/antagonists/heretic/magic/rust_construction.dm b/code/modules/antagonists/heretic/magic/rust_construction.dm index 130e3e06be2..f8d6a2ff2be 100644 --- a/code/modules/antagonists/heretic/magic/rust_construction.dm +++ b/code/modules/antagonists/heretic/magic/rust_construction.dm @@ -8,7 +8,7 @@ check_flags = AB_CHECK_INCAPACITATED|AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED school = SCHOOL_FORBIDDEN - cooldown_time = 5 SECONDS + cooldown_time = 8 SECONDS invocation = "Someone raises a wall of rust." invocation_self_message = "You raise a wall of rust." @@ -16,15 +16,19 @@ spell_requirements = NONE cast_range = 4 - aim_assist = FALSE /// How long does the filter last on walls we make? var/filter_duration = 2 MINUTES +/** + * Overrides 'aim assist' because we always want to hit just the turf we clicked on. + */ +/datum/action/cooldown/spell/pointed/rust_construction/aim_assist(mob/living/caller, atom/target) + return get_turf(target) + /datum/action/cooldown/spell/pointed/rust_construction/is_valid_target(atom/cast_on) - if(!isfloorturf(cast_on)) - if(isturf(cast_on) && owner) - cast_on.balloon_alert(owner, "not a floor!") + if(!isturf(cast_on)) + cast_on.balloon_alert(owner, "not a wall or floor!") return FALSE if(!HAS_TRAIT(cast_on, TRAIT_RUSTY)) @@ -43,9 +47,22 @@ invocation = span_danger("[owner] drags [owner.p_their()] hand[living_owner.usable_hands == 1 ? "":"s"] upwards as a wall of rust rises out of [cast_on]!") invocation_self_message = span_notice("You drag [living_owner.usable_hands == 1 ? "a hand":"your hands"] upwards as a wall of rust rises out of [cast_on].") -/datum/action/cooldown/spell/pointed/rust_construction/cast(turf/open/cast_on) +/datum/action/cooldown/spell/pointed/rust_construction/cast(turf/cast_on) . = ..() var/rises_message = "rises out of [cast_on]" + + // If we casted at a wall we'll try to rust it. In the case of an enchanted wall it'll deconstruct it + if(isclosedturf(cast_on)) + cast_on.visible_message(span_warning("\The [cast_on] quakes as the rust causes it to crumble!")) + var/mob/living/living_owner = owner + living_owner?.do_rust_heretic_act(cast_on) + // ref transfers to floor + cast_on.Shake(shake_interval = 0.1 SECONDS, duration = 0.5 SECONDS) + // which we need to re-rust + living_owner?.do_rust_heretic_act(cast_on) + playsound(cast_on, 'sound/effects/bang.ogg', 50, vary = TRUE) + return + var/turf/closed/wall/new_wall = cast_on.place_on_top(/turf/closed/wall) if(!istype(new_wall)) return @@ -53,7 +70,8 @@ playsound(new_wall, 'sound/effects/constructform.ogg', 50, TRUE) new_wall.rust_heretic_act() new_wall.name = "\improper enchanted [new_wall.name]" - new_wall.hardness = 10 + new_wall.AddComponent(/datum/component/torn_wall) + new_wall.hardness = 60 new_wall.sheet_amount = 0 new_wall.girder_type = null @@ -61,8 +79,8 @@ // but I guess a fading filter will have to do for now as walls have 0 depth (currently) // damn though with 3/4ths walls this'll look sick just imagine it new_wall.add_filter("rust_wall", 2, list("type" = "outline", "color" = "#85be299c", "size" = 2)) - addtimer(CALLBACK(src, PROC_REF(fade_wall_filter), new_wall), filter_duration * (1/20)) - addtimer(CALLBACK(src,PROC_REF(remove_wall_filter), new_wall), filter_duration) + addtimer(CALLBACK(src, PROC_REF(fade_wall_filter), new_wall), filter_duration * 0.5) + addtimer(CALLBACK(src, PROC_REF(remove_wall_filter), new_wall), filter_duration) var/message_shown = FALSE for(var/mob/living/living_mob in cast_on) @@ -108,7 +126,7 @@ if(!rust_filter) return - animate(rust_filter, alpha = 0, time = filter_duration * (19/20)) + animate(rust_filter, alpha = 0, time = filter_duration * (9/20)) /datum/action/cooldown/spell/pointed/rust_construction/proc/remove_wall_filter(turf/closed/wall) if(QDELETED(wall)) diff --git a/code/modules/antagonists/heretic/magic/rust_wave.dm b/code/modules/antagonists/heretic/magic/rust_wave.dm index 65c5592b34e..0282a32b2b6 100644 --- a/code/modules/antagonists/heretic/magic/rust_wave.dm +++ b/code/modules/antagonists/heretic/magic/rust_wave.dm @@ -25,7 +25,10 @@ new /obj/effect/temp_visual/dir_setting/entropic(get_step(cast_on, cast_on.dir), cast_on.dir) /datum/action/cooldown/spell/cone/staggered/entropic_plume/do_turf_cone_effect(turf/target_turf, mob/living/caster, level) - caster.do_rust_heretic_act(target_turf) + if(ismob(caster)) + caster.do_rust_heretic_act(target_turf) + else + target_turf.rust_heretic_act() /datum/action/cooldown/spell/cone/staggered/entropic_plume/do_mob_cone_effect(mob/living/victim, atom/caster, level) if(victim.can_block_magic(antimagic_flags) || IS_HERETIC_OR_MONSTER(victim) || victim == caster) diff --git a/code/modules/antagonists/heretic/soultrapped_heretic.dm b/code/modules/antagonists/heretic/soultrapped_heretic.dm new file mode 100644 index 00000000000..ffd92e09496 --- /dev/null +++ b/code/modules/antagonists/heretic/soultrapped_heretic.dm @@ -0,0 +1,24 @@ +///a heretic that got soultrapped by cultists. does nothing, other than signify they suck +/datum/antagonist/soultrapped_heretic + name = "\improper Soultrapped Heretic" + roundend_category = "Heretics" + antagpanel_category = "Heretic" + job_rank = ROLE_HERETIC + antag_moodlet = /datum/mood_event/soultrapped_heretic + antag_hud_name = "heretic" + +// Will never show up because they're shades inside a sword +/datum/mood_event/soultrapped_heretic + description = "They trapped me! I can't escape!" + mood_change = -20 + +// always failure obj +/datum/objective/heretic_trapped + name = "soultrapped failure" + explanation_text = "Help the cult. Kill the cult. Help the crew. Kill the crew. Help your wielder. Kill your wielder. Kill everyone. Rattle your chains." + +/datum/antagonist/soultrapped_heretic/on_gain() + ..() + var/datum/objective/epic_fail = new /datum/objective/heretic_trapped() + epic_fail.completed = FALSE + objectives += epic_fail diff --git a/code/modules/antagonists/heretic/status_effects/buffs.dm b/code/modules/antagonists/heretic/status_effects/buffs.dm index d2058a5b4f1..35a6ab92687 100644 --- a/code/modules/antagonists/heretic/status_effects/buffs.dm +++ b/code/modules/antagonists/heretic/status_effects/buffs.dm @@ -118,6 +118,8 @@ var/time_between_initial_blades = 0.25 SECONDS /// If TRUE, we self-delete our status effect after all the blades are deleted. var/delete_on_blades_gone = TRUE + /// What blade type to create + var/blade_type = /obj/effect/floating_blade /// A list of blade effects orbiting / protecting our owner var/list/obj/effect/floating_blade/blades = list() @@ -127,12 +129,14 @@ max_num_blades = 4, blade_orbit_radius = 20, time_between_initial_blades = 0.25 SECONDS, + blade_type = /obj/effect/floating_blade, ) src.duration = new_duration src.max_num_blades = max_num_blades src.blade_orbit_radius = blade_orbit_radius src.time_between_initial_blades = time_between_initial_blades + src.blade_type = blade_type return ..() /datum/status_effect/protective_blades/on_apply() @@ -157,7 +161,7 @@ if(QDELETED(src) || QDELETED(owner)) return - var/obj/effect/floating_blade/blade = new(get_turf(owner)) + var/obj/effect/floating_blade/blade = new blade_type(get_turf(owner)) blades += blade blade.orbit(owner, blade_orbit_radius) RegisterSignal(blade, COMSIG_QDELETING, PROC_REF(remove_blade)) diff --git a/code/modules/antagonists/heretic/structures/mawed_crucible.dm b/code/modules/antagonists/heretic/structures/mawed_crucible.dm index 8e5410f0f67..2135ffa134c 100644 --- a/code/modules/antagonists/heretic/structures/mawed_crucible.dm +++ b/code/modules/antagonists/heretic/structures/mawed_crucible.dm @@ -57,6 +57,10 @@ return span_notice("It's at [round(atom_integrity * 100 / max_integrity)]% stability.") return ..() +// no breaky herety thingy +/obj/structure/destructible/eldritch_crucible/rust_heretic_act() + return + /obj/structure/destructible/eldritch_crucible/attacked_by(obj/item/weapon, mob/living/user) if(!iscarbon(user)) return ..() diff --git a/code/modules/antagonists/heretic/transmutation_rune.dm b/code/modules/antagonists/heretic/transmutation_rune.dm index 8d5f08db749..0c510e34ff2 100644 --- a/code/modules/antagonists/heretic/transmutation_rune.dm +++ b/code/modules/antagonists/heretic/transmutation_rune.dm @@ -1,8 +1,9 @@ /// The heretic's rune, which they use to complete transmutation rituals. /obj/effect/heretic_rune name = "transmutation rune" - desc = "A flowing circle of shapes and runes is etched into the floor, filled with a thick black tar-like fluid." - icon_state = "" + desc = "A flowing circle of shapes and runes is etched into the floor, filled with a thick black tar-like fluid. This one looks pretty small." + icon = 'icons/obj/antags/cult/rune.dmi' + icon_state = "main1" anchored = TRUE interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF diff --git a/code/modules/antagonists/wizard/equipment/soulstone.dm b/code/modules/antagonists/wizard/equipment/soulstone.dm index 6b76a628c1b..bc470c78e90 100644 --- a/code/modules/antagonists/wizard/equipment/soulstone.dm +++ b/code/modules/antagonists/wizard/equipment/soulstone.dm @@ -265,16 +265,17 @@ icon = 'icons/mob/shells.dmi' icon_state = "construct_cult" desc = "A wicked machine used by those skilled in magical arts. It is inactive." - -/obj/structure/constructshell/examine(mob/user) - . = ..() - if(IS_CULTIST(user) || HAS_MIND_TRAIT(user, TRAIT_MAGICALLY_GIFTED) || user.stat == DEAD) - . += {"A construct shell, used to house bound souls from a soulstone.\n + var/extra_desc = {"A construct shell, used to house bound souls from a soulstone.\n Placing a soulstone with a soul into this shell allows you to produce your choice of the following:\n An Artificer, which can produce more shells and soulstones, as well as fortifications.\n A Wraith, which does high damage and can jaunt through walls, though it is quite fragile.\n A Juggernaut, which is very hard to kill and can produce temporary walls, but is slow."} +/obj/structure/constructshell/examine(mob/user) + . = ..() + if(IS_CULTIST(user) || HAS_MIND_TRAIT(user, TRAIT_MAGICALLY_GIFTED) || user.stat == DEAD) + . += extra_desc + /obj/structure/constructshell/attackby(obj/item/O, mob/user, params) if(istype(O, /obj/item/soulstone)) var/obj/item/soulstone/SS = O @@ -499,6 +500,11 @@ make_new_construct(/mob/living/basic/construct/artificer/angelic, target, creator, cultoverride, loc_override) if(THEME_CULT) make_new_construct(/mob/living/basic/construct/artificer/noncult, target, creator, cultoverride, loc_override) + if(CONSTRUCT_HARVESTER) + if(IS_HERETIC_OR_MONSTER(creator)) + make_new_construct(/mob/living/basic/construct/harvester/heretic, target, creator, cultoverride, loc_override) + else + make_new_construct(/mob/living/basic/construct/harvester, target, creator, cultoverride, loc_override) /proc/make_new_construct(mob/living/basic/construct/ctype, mob/target, mob/stoner = null, cultoverride = FALSE, loc_override = null) if(QDELETED(target)) @@ -528,7 +534,7 @@ newstruct.clear_alert("bloodsense") sense_alert = newstruct.throw_alert("bloodsense", /atom/movable/screen/alert/bloodsense) if(sense_alert) - sense_alert.Cviewer = newstruct + sense_alert.construct_owner = newstruct newstruct.cancel_camera() /obj/item/soulstone/anybody diff --git a/code/modules/library/bibles.dm b/code/modules/library/bibles.dm index 31630c550a1..dfd694fcd01 100644 --- a/code/modules/library/bibles.dm +++ b/code/modules/library/bibles.dm @@ -310,26 +310,19 @@ GLOBAL_LIST_INIT(bibleitemstates, list( other_bible.inhand_icon_state = inhand_icon_state other_bible.deity_name = deity_name +<<<<<<< HEAD if(istype(bible_smacked, /obj/item/cult_bastard) && !IS_CULTIST(user)) . |= AFTERATTACK_PROCESSED_ITEM var/obj/item/cult_bastard/sword = bible_smacked bible_smacked.balloon_alert(user, "exorcising...") +======= + if(istype(bible_smacked, /obj/item/melee/cultblade/haunted) && !IS_CULTIST(user)) + var/obj/item/melee/cultblade/haunted/sword = bible_smacked + sword.balloon_alert(user, "exorcising...") +>>>>>>> 0c4df344b8a... [MIRROR] Cult Vs. Heretic: 7 Months Later Edition [MDB IGNORE] (#3384) playsound(src,'sound/hallucinations/veryfar_noise.ogg',40,TRUE) if(do_after(user, 4 SECONDS, target = sword)) playsound(src,'sound/effects/pray_chaplain.ogg',60,TRUE) - for(var/obj/item/soulstone/stone in sword.contents) - stone.required_role = null - for(var/mob/living/basic/shade/shade in stone) - var/datum/antagonist/cult/cultist = shade.mind.has_antag_datum(/datum/antagonist/cult) - if(cultist) - cultist.silent = TRUE - cultist.on_removal() - SSblackbox.record_feedback("tally", "cult_shade_purified", 1) - shade.theme = THEME_HOLY - shade.name = "Purified [shade.real_name]" - shade.update_appearance(UPDATE_ICON_STATE) - stone.release_shades(user) - qdel(stone) new /obj/item/nullrod/claymore(get_turf(sword)) user.visible_message(span_notice("[user] exorcises [sword]!")) qdel(sword) diff --git a/code/modules/mapfluff/ruins/lavalandruin_code/elephantgraveyard.dm b/code/modules/mapfluff/ruins/lavalandruin_code/elephantgraveyard.dm index 3bb1fb48e59..caa082d6927 100644 --- a/code/modules/mapfluff/ruins/lavalandruin_code/elephantgraveyard.dm +++ b/code/modules/mapfluff/ruins/lavalandruin_code/elephantgraveyard.dm @@ -289,7 +289,7 @@ deconstruct(TRUE) return TRUE -/obj/structure/closet/crate/grave/container_resist_act(mob/living/user) +/obj/structure/closet/crate/grave/container_resist_act(mob/living/user, loc_required = TRUE) if(opened) return // The player is trying to dig themselves out of an early grave diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index b118de06f05..b91d847f7dd 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -290,8 +290,12 @@ /mob/proc/is_holding_items() return !!locate(/obj/item) in held_items +/** + * Returns a list of all dropped held items. + * If none were dropped, returns an empty list. + */ /mob/proc/drop_all_held_items() - . = FALSE + . = list() for(var/obj/item/I in held_items) . |= dropItemToGround(I) @@ -319,24 +323,25 @@ /** * Used to drop an item (if it exists) to the ground. - * * Will pass as TRUE is successfully dropped, or if there is no item to drop. - * * Will pass FALSE if the item can not be dropped due to TRAIT_NODROP via doUnEquip() + * * Will return null if the item wasn't dropped. + * * If it was, returns the item. * If the item can be dropped, it will be forceMove()'d to the ground and the turf's Entered() will be called. */ /mob/proc/dropItemToGround(obj/item/I, force = FALSE, silent = FALSE, invdrop = TRUE) if (isnull(I)) - return TRUE + return SEND_SIGNAL(src, COMSIG_MOB_DROPPING_ITEM) - . = doUnEquip(I, force, drop_location(), FALSE, invdrop = invdrop, silent = silent) + var/try_uneqip = doUnEquip(I, force, drop_location(), FALSE, invdrop = invdrop, silent = silent) - if(!. || !I) //ensure the item exists and that it was dropped properly. + if(!try_uneqip || !I) //ensure the item exists and that it was dropped properly. return if(!(I.item_flags & NO_PIXEL_RANDOM_DROP)) I.pixel_x = I.base_pixel_x + rand(-6, 6) I.pixel_y = I.base_pixel_y + rand(-6, 6) I.do_drop_animation(src) + return I //for when the item will be immediately placed in a loc other than the ground /mob/proc/transferItemToLoc(obj/item/I, newloc = null, force = FALSE, silent = TRUE) @@ -417,13 +422,27 @@ items += worn_under.attached_accessories return items +/** + * Returns the items that were succesfully unequipped. + */ /mob/living/proc/unequip_everything() var/list/items = list() +<<<<<<< HEAD items |= get_equipped_items(include_pockets = TRUE) +======= + items |= get_equipped_items(INCLUDE_POCKETS) + // In case something isn't actually unequipped somehow + var/list/dropped_items = list() +>>>>>>> 0c4df344b8a... [MIRROR] Cult Vs. Heretic: 7 Months Later Edition [MDB IGNORE] (#3384) for(var/I in items) - dropItemToGround(I) - drop_all_held_items() - + var/return_val = dropItemToGround(I) + if(!isitem(return_val)) + continue + dropped_items |= return_val + var/return_val = drop_all_held_items() + if(islist(return_val)) + dropped_items |= return_val + return dropped_items /mob/living/carbon/proc/check_obscured_slots(transparent_protection) var/obscured = NONE diff --git a/code/modules/mob/living/basic/cult/constructs/_construct.dm b/code/modules/mob/living/basic/cult/constructs/_construct.dm index 2583b3a88a6..79e6e748772 100644 --- a/code/modules/mob/living/basic/cult/constructs/_construct.dm +++ b/code/modules/mob/living/basic/cult/constructs/_construct.dm @@ -51,10 +51,12 @@ THEME_CULT = list(/obj/item/ectoplasm/construct), THEME_HOLY = list(/obj/item/ectoplasm/angelic), THEME_WIZARD = list(/obj/item/ectoplasm/mystic), + THEME_HERETIC = list(/obj/item/ectoplasm/construct), ) /mob/living/basic/construct/Initialize(mapload) . = ..() + throw_alert("bloodsense", /atom/movable/screen/alert/bloodsense) AddElement(/datum/element/simple_flying) var/list/remains = string_list(remains_by_theme[theme]) if(length(remains)) diff --git a/code/modules/mob/living/basic/cult/constructs/harvester.dm b/code/modules/mob/living/basic/cult/constructs/harvester.dm index da8dad827b0..6d41bb43f26 100644 --- a/code/modules/mob/living/basic/cult/constructs/harvester.dm +++ b/code/modules/mob/living/basic/cult/constructs/harvester.dm @@ -26,13 +26,16 @@ /mob/living/basic/construct/harvester/Initialize(mapload) . = ..() - AddElement(\ - /datum/element/amputating_limbs,\ + grant_abilities() + +/mob/living/basic/construct/harvester/proc/grant_abilities() + AddElement(/datum/element/wall_walker, /turf/closed/wall/mineral/cult) + AddComponent(\ + /datum/component/amputating_limbs,\ surgery_time = 0,\ surgery_verb = "slicing",\ minimum_stat = CONSCIOUS,\ ) - AddElement(/datum/element/wall_walker, /turf/closed/wall/mineral/cult) var/datum/action/innate/seek_prey/seek = new(src) seek.Grant(src) seek.Activate() @@ -59,7 +62,7 @@ overlay_icon_state = "bg_demon_border" buttontooltipstyle = "cult" - button_icon = "icons/mob/actions/actions_cult.dmi" + button_icon = 'icons/mob/actions/actions_cult.dmi' button_icon_state = "cult_mark" /// Where is nar nar? Are we even looking? var/tracking = FALSE @@ -93,7 +96,6 @@ the_construct.seeking = TRUE to_chat(the_construct, span_cult_italic("You are now tracking your master.")) - /datum/action/innate/seek_prey name = "Seek the Harvest" desc = "None can hide from Nar'Sie, activate to track a survivor attempting to flee the red harvest!" @@ -126,3 +128,115 @@ desc = "Activate to track Nar'Sie!" button_icon_state = "sintouch" the_construct.seeking = TRUE + +/mob/living/basic/construct/harvester/heretic + name = "Rusted Harvester" + real_name = "Rusted Harvester" + desc = "A long, thin, decrepit construct originally built to herald Nar'Sie's rise, corrupted and rusted by the forces of the Mansus to spread its will instead." + icon_state = "harvester" + icon_living = "harvester" + construct_spells = list( + /datum/action/cooldown/spell/aoe/rust_conversion, + /datum/action/cooldown/spell/pointed/rust_construction, + ) + can_repair = FALSE + slowed_by_drag = FALSE + faction = list(FACTION_HERETIC) + maxHealth = 35 + health = 35 + melee_damage_lower = 20 + melee_damage_upper = 25 + // Dim green + lighting_cutoff_red = 10 + lighting_cutoff_green = 20 + lighting_cutoff_blue = 5 + playstyle_string = "You are a Rusted Harvester, built to serve the Sanguine Apostate, twisted to work the will of the Mansus. You are fragile and weak, but you rend cultists (only) apart on each attack. Follow your Master's orders!" + theme = THEME_HERETIC + +/mob/living/basic/construct/harvester/heretic/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_MANSUS_TOUCHED, REF(src)) + add_filter("rusted_harvester", 3, list("type" = "outline", "color" = COLOR_GREEN, "size" = 2, "alpha" = 40)) + + +/** + * Somewhat janky proc called when a heretic monster's master dies + * Used to kill any living Rusted Harvester + */ +/mob/living/proc/on_master_death() + return + +/mob/living/basic/construct/harvester/heretic/attack_animal(mob/living/simple_animal/user, list/modifiers) + // They're pretty fragile so this is probably necessary to prevent bullshit deaths. + if(user == src) + return + return ..() + +/mob/living/basic/construct/harvester/heretic/on_master_death() + to_chat(src, span_userdanger("Your link to the mansus suddenly snaps as your master perishes! Without its support, your body crumbles...")) + visible_message(span_alert("[src] suddenly crumbles to dust!")) + death() + +/mob/living/basic/construct/harvester/heretic/grant_abilities() + AddElement(/datum/element/wall_walker, or_trait = TRAIT_RUSTY) + AddElement(/datum/element/leeching_walk) + AddComponent(\ + /datum/component/amputating_limbs,\ + surgery_time = 1.5 SECONDS,\ + surgery_verb = "slicing",\ + minimum_stat = CONSCIOUS,\ + pre_hit_callback = CALLBACK(src, PROC_REF(is_cultist_handler)),\ + ) + AddComponent(/datum/component/damage_aura,\ + range = 3,\ + brute_damage = 0.5,\ + burn_damage = 0.5,\ + toxin_damage = 0.5,\ + stamina_damage = 4,\ + simple_damage = 1.5,\ + immune_factions = list(FACTION_HERETIC),\ + damage_message = span_boldwarning("Your body wilts and withers as it comes near [src]'s aura."),\ + message_probability = 7,\ + current_owner = src,\ + ) + var/datum/action/innate/seek_master/heretic/seek = new(src) + seek.Grant(src) + seek.Activate() + +// These aren't friends they're assholes +// Don't let them be near you! +/mob/living/basic/construct/harvester/heretic/Life(seconds_per_tick, times_fired) + . = ..() + if(!SPT_PROB(7, seconds_per_tick)) + return + + var/turf/adjacent = get_step(src, pick(GLOB.alldirs)) + // 90% chance to be directional, otherwise what we're on top of + var/turf/open/land = (isopenturf(adjacent) && prob(90)) ? adjacent : get_turf(src) + do_rust_heretic_act(land) + + if(prob(7)) + to_chat(src, span_notice("Eldritch energies emanate from your body.")) + +/mob/living/basic/construct/harvester/heretic/proc/is_cultist_handler(mob/victim) + return IS_CULTIST(victim) + +/datum/action/innate/seek_master/heretic + name = "Seek your Master" + desc = "Use your direct link to the Mansus to sense where your master is located via the arrow on the top-right of your HUD." + button_icon = 'icons/mob/actions/actions_cult.dmi' + background_icon_state = "bg_heretic" + overlay_icon_state = "bg_heretic_border" + tracking = TRUE + +/datum/action/innate/seek_master/heretic/New(Target) + . = ..() + the_construct = Target + the_construct.seeking = TRUE + var/datum/antagonist/heretic_monster/antag = IS_HERETIC_MONSTER(the_construct) + if(antag) + the_construct.master = antag.master + +// no real reason for most of this weird oldcode +/datum/action/innate/seek_master/Activate() + return diff --git a/code/modules/mob/living/basic/farm_animals/gorilla/gorilla.dm b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla.dm index b2aff7371a4..e1b6811472e 100644 --- a/code/modules/mob/living/basic/farm_animals/gorilla/gorilla.dm +++ b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla.dm @@ -60,8 +60,8 @@ AddElement(/datum/element/dextrous) AddElement(/datum/element/footstep, FOOTSTEP_MOB_BAREFOOT) AddElement(/datum/element/basic_eating, heal_amt = 10, food_types = gorilla_food) - AddElement( - /datum/element/amputating_limbs, \ + AddComponent( + /datum/component/amputating_limbs, \ surgery_time = 0 SECONDS, \ surgery_verb = "punches",\ ) diff --git a/code/modules/mob/living/basic/farm_animals/sheep.dm b/code/modules/mob/living/basic/farm_animals/sheep.dm index 2fdaeda657d..5617da83a53 100644 --- a/code/modules/mob/living/basic/farm_animals/sheep.dm +++ b/code/modules/mob/living/basic/farm_animals/sheep.dm @@ -59,7 +59,7 @@ if(cult_converted) for(var/mob/living/cultist as anything in invokers) to_chat(cultist, span_cult_italic("[src] has already been sacrificed!")) - return STOP_SACRIFICE + return STOP_SACRIFICE|SILENCE_SACRIFICE_MESSAGE for(var/mob/living/cultist as anything in invokers) to_chat(cultist, span_cult_italic("This feels a bit too cliché, don't you think?")) diff --git a/code/modules/mob/living/basic/heretic/flesh_worm.dm b/code/modules/mob/living/basic/heretic/flesh_worm.dm index 3c60a9b653c..92b910c717f 100644 --- a/code/modules/mob/living/basic/heretic/flesh_worm.dm +++ b/code/modules/mob/living/basic/heretic/flesh_worm.dm @@ -35,8 +35,8 @@ /mob/living/basic/heretic_summon/armsy/Initialize(mapload, spawn_bodyparts = TRUE, worm_length = 6) . = ..() AddElement(/datum/element/wall_smasher, ENVIRONMENT_SMASH_RWALLS) - AddElement(\ - /datum/element/amputating_limbs,\ + AddComponent(\ + /datum/component/amputating_limbs,\ surgery_time = 0 SECONDS,\ surgery_verb = "tears",\ minimum_stat = CONSCIOUS,\ diff --git a/code/modules/mob/living/basic/heretic/rust_walker.dm b/code/modules/mob/living/basic/heretic/rust_walker.dm index 24b77d4d0b8..230747efa5a 100644 --- a/code/modules/mob/living/basic/heretic/rust_walker.dm +++ b/code/modules/mob/living/basic/heretic/rust_walker.dm @@ -19,7 +19,7 @@ AddElement(/datum/element/footstep, FOOTSTEP_MOB_RUST) var/static/list/grantable_spells = list( - /datum/action/cooldown/spell/aoe/rust_conversion/small = BB_GENERIC_ACTION, + /datum/action/cooldown/spell/aoe/rust_conversion = BB_GENERIC_ACTION, /datum/action/cooldown/spell/basic_projectile/rust_wave/short = BB_TARGETED_ACTION, ) grant_actions_by_list(grantable_spells) diff --git a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm index ccab4d3c031..ad10e30acec 100644 --- a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm +++ b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm @@ -43,8 +43,8 @@ AddElement(/datum/element/mob_grabber) AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW) AddElement(/datum/element/basic_eating, food_types = target_foods) - AddElement(\ - /datum/element/amputating_limbs,\ + AddComponent(\ + /datum/component/amputating_limbs,\ surgery_verb = "begins snipping",\ target_zones = GLOB.arm_zones,\ ) diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index 903fadbb3c5..1e912328d07 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -744,5 +744,7 @@ . |= SHOVE_CAN_STAGGER if(IsKnockdown() && !IsParalyzed()) . |= SHOVE_CAN_KICK_SIDE + if(HAS_TRAIT(src, TRAIT_NO_SIDE_KICK)) // added as an extra check, just in case + . &= ~SHOVE_CAN_KICK_SIDE #undef SHAKE_ANIMATION_OFFSET diff --git a/code/modules/mob/living/carbon/human/species_types/snail.dm b/code/modules/mob/living/carbon/human/species_types/snail.dm index a1ec5b365a0..5e9ffd0d4ba 100644 --- a/code/modules/mob/living/carbon/human/species_types/snail.dm +++ b/code/modules/mob/living/carbon/human/species_types/snail.dm @@ -87,9 +87,15 @@ . = ..() var/obj/item/storage/backpack/bag = new_snailperson.get_item_by_slot(ITEM_SLOT_BACK) if(!istype(bag, /obj/item/storage/backpack/snail)) +<<<<<<< HEAD if(new_snailperson.dropItemToGround(bag)) //returns TRUE even if its null new_snailperson.equip_to_slot_or_del(new /obj/item/storage/backpack/snail(new_snailperson), ITEM_SLOT_BACK) new_snailperson.AddElement(/datum/element/snailcrawl) +======= + new_snailperson.equip_to_slot_or_del(new /obj/item/storage/backpack/snail(new_snailperson), ITEM_SLOT_BACK) + new_snailperson.AddElement(/datum/element/lube_walking, require_resting = TRUE) + new_snailperson.add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/snail, multiplicative_slowdown = snail_speed_mod) +>>>>>>> 0c4df344b8a... [MIRROR] Cult Vs. Heretic: 7 Months Later Edition [MDB IGNORE] (#3384) /datum/species/snail/on_species_loss(mob/living/carbon/former_snailperson, datum/species/new_species, pref_load) . = ..() diff --git a/code/modules/pai/pai.dm b/code/modules/pai/pai.dm index fa9eb1cf0ea..2d91f64b79f 100644 --- a/code/modules/pai/pai.dm +++ b/code/modules/pai/pai.dm @@ -462,7 +462,7 @@ for(var/mob/living/cultist as anything in invokers) to_chat(cultist, span_cult_italic("You don't think this is what Nar'Sie had in mind when She asked for blood sacrifices...")) - return STOP_SACRIFICE + return STOP_SACRIFICE|SILENCE_SACRIFICE_MESSAGE /// Updates the distance we can be from our pai card /mob/living/silicon/pai/proc/increment_range(increment_amount) diff --git a/code/modules/projectiles/guns/magic/wand.dm b/code/modules/projectiles/guns/magic/wand.dm index 99b60b2107e..c367d228048 100644 --- a/code/modules/projectiles/guns/magic/wand.dm +++ b/code/modules/projectiles/guns/magic/wand.dm @@ -278,3 +278,78 @@ charges-- user.AddComponent(/datum/component/shrink, -1) // small forever return ..() + +// Wand of debugging + +#ifdef TESTING + +/obj/item/gun/magic/wand/antag + name = "wand of antag" + desc = "This wand uses the powers of bullshit to turn anyone it hits into an antag" + school = SCHOOL_FORBIDDEN + ammo_type = /obj/item/ammo_casing/magic/antag + icon_state = "revivewand" + base_icon_state = "revivewand" + color = COLOR_ADMIN_PINK + max_charges = 99999 + +/obj/item/gun/magic/wand/antag/zap_self(mob/living/user) + . = ..() + var/obj/item/ammo_casing/magic/antag/casing = new ammo_type() + var/obj/projectile/magic/magic_proj = casing.projectile_type + magic_proj = new magic_proj(src) + magic_proj.on_hit(user) + QDEL_NULL(casing) + +/obj/item/ammo_casing/magic/antag + projectile_type = /obj/projectile/magic/antag + harmful = FALSE + +/obj/projectile/magic/antag + name = "bolt of antag" + icon_state = "ion" + var/antag = /datum/antagonist/traitor + +/obj/projectile/magic/antag/on_hit(atom/target, blocked, pierce_hit) + . = ..() + + if(isliving(target)) + var/mob/living/victim = target + if(isnull(victim.mind)) + victim.mind_initialize() + if(victim.mind.has_antag_datum(antag)) + victim.mind.remove_antag_datum(antag) + to_chat(world, "removed") + else + victim.mind.add_antag_datum(antag) + to_chat(world, "added") + +/obj/item/gun/magic/wand/antag/heretic + name = "wand of antag heretic" + desc = "This wand uses the powers of bullshit to turn anyone it hits into an antag heretic" + color = COLOR_GREEN + ammo_type = /obj/item/ammo_casing/magic/antag/heretic + +/obj/item/ammo_casing/magic/antag/heretic + projectile_type = /obj/projectile/magic/antag/heretic + +/obj/projectile/magic/antag/heretic + name = "bolt of antag heretic" + icon_state = "ion" + antag = /datum/antagonist/heretic + +/obj/item/gun/magic/wand/antag/cult + name = "wand of antag cultist" + desc = "This wand uses the powers of bullshit to turn anyone it hits into an antag cultist" + color = COLOR_CULT_RED + ammo_type = /obj/item/ammo_casing/magic/antag/cult + +/obj/item/ammo_casing/magic/antag/cult + projectile_type = /obj/projectile/magic/antag/cult + +/obj/projectile/magic/antag/cult + name = "bolt of antag cult" + icon_state = "ion" + antag = /datum/antagonist/cult + +#endif diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm index dc30f36149b..f76ecb104e7 100644 --- a/code/modules/spells/spell.dm +++ b/code/modules/spells/spell.dm @@ -246,7 +246,11 @@ return target // They're just standing around, proceed as normal if(HAS_TRAIT(cast_loc, TRAIT_CASTABLE_LOC)) - return cast_loc // They're in an atom which allows casting, so redirect the caster to loc + if(HAS_TRAIT(cast_loc, TRAIT_SPELLS_TRANSFER_TO_LOC) && ismob(cast_loc.loc)) + return cast_loc.loc + else + return cast_loc + // They're in an atom which allows casting, so redirect the caster to loc return null diff --git a/code/modules/spells/spell_types/pointed/_pointed.dm b/code/modules/spells/spell_types/pointed/_pointed.dm index 04c3ed47944..edf3dab2179 100644 --- a/code/modules/spells/spell_types/pointed/_pointed.dm +++ b/code/modules/spells/spell_types/pointed/_pointed.dm @@ -16,7 +16,7 @@ var/deactive_msg /// The casting range of our spell var/cast_range = 7 - /// Variable dictating if the spell will use turf based aim assist + /// If aim asisst is used. Disable to disable var/aim_assist = TRUE /datum/action/cooldown/spell/pointed/New(Target) @@ -65,17 +65,18 @@ return TRUE /datum/action/cooldown/spell/pointed/InterceptClickOn(mob/living/caller, params, atom/target) - var/atom/aim_assist_target - if(aim_assist && isturf(target)) - // Find any human in the list. We aren't picky, it's aim assist after all - aim_assist_target = locate(/mob/living/carbon/human) in target - if(!aim_assist_target) - // If we didn't find a human, we settle for any living at all - aim_assist_target = locate(/mob/living) in target - + if(aim_assist) + aim_assist_target = aim_assist(caller, target) return ..(caller, params, aim_assist_target || target) +/datum/action/cooldown/spell/pointed/proc/aim_assist(mob/living/caller, atom/target) + if(!isturf(target)) + return + + // Find any human, or if that fails, any living target + return locate(/mob/living/carbon/human) in target || locate(/mob/living) in target + /datum/action/cooldown/spell/pointed/is_valid_target(atom/cast_on) if(cast_on == owner) to_chat(owner, span_warning("You cannot cast [src] on yourself!")) diff --git a/code/modules/surgery/bodyparts/helpers.dm b/code/modules/surgery/bodyparts/helpers.dm index fb0647d0fb5..863cdd9cb61 100644 --- a/code/modules/surgery/bodyparts/helpers.dm +++ b/code/modules/surgery/bodyparts/helpers.dm @@ -48,6 +48,15 @@ which_hand = BODY_ZONE_PRECISE_R_HAND return get_bodypart(check_zone(which_hand)) +/// Gets the inactive hand of the mob. Returns FALSE on non-carbons, otherwise returns the /obj/item/bodypart. +/mob/proc/get_inactive_hand() + return null + +/mob/living/carbon/get_inactive_hand() + var/which_hand = BODY_ZONE_PRECISE_R_HAND + if(!(active_hand_index % RIGHT_HANDS)) + which_hand = BODY_ZONE_PRECISE_L_HAND + return get_bodypart(check_zone(which_hand)) /mob/proc/has_left_hand(check_disabled = TRUE) return TRUE diff --git a/icons/mob/actions/actions_cult.dmi b/icons/mob/actions/actions_cult.dmi index f73f5bb6367..7725ae691b4 100644 Binary files a/icons/mob/actions/actions_cult.dmi and b/icons/mob/actions/actions_cult.dmi differ diff --git a/icons/mob/actions/actions_ecult.dmi b/icons/mob/actions/actions_ecult.dmi index 67b6bf1fd22..3ce72ae54ff 100644 Binary files a/icons/mob/actions/actions_ecult.dmi and b/icons/mob/actions/actions_ecult.dmi differ diff --git a/icons/mob/clothing/neck.dmi b/icons/mob/clothing/neck.dmi index a193fbd74a8..ed0bb34b464 100644 Binary files a/icons/mob/clothing/neck.dmi and b/icons/mob/clothing/neck.dmi differ diff --git a/icons/mob/inhands/64x64_lefthand.dmi b/icons/mob/inhands/64x64_lefthand.dmi index e0545c0d893..d15a47206f9 100644 Binary files a/icons/mob/inhands/64x64_lefthand.dmi and b/icons/mob/inhands/64x64_lefthand.dmi differ diff --git a/icons/mob/inhands/64x64_righthand.dmi b/icons/mob/inhands/64x64_righthand.dmi index bf101ad0ea7..88ad954734b 100644 Binary files a/icons/mob/inhands/64x64_righthand.dmi and b/icons/mob/inhands/64x64_righthand.dmi differ diff --git a/icons/mob/nonhuman-player/cult.dmi b/icons/mob/nonhuman-player/cult.dmi index 9241b138227..683ee9bd6fe 100644 Binary files a/icons/mob/nonhuman-player/cult.dmi and b/icons/mob/nonhuman-player/cult.dmi differ diff --git a/icons/obj/antags/cult/items.dmi b/icons/obj/antags/cult/items.dmi index 9a3435dcd83..fcd5f13c85b 100644 Binary files a/icons/obj/antags/cult/items.dmi and b/icons/obj/antags/cult/items.dmi differ diff --git a/icons/obj/antags/cult/structures.dmi b/icons/obj/antags/cult/structures.dmi index 373371f5a2f..982742e8764 100644 Binary files a/icons/obj/antags/cult/structures.dmi and b/icons/obj/antags/cult/structures.dmi differ diff --git a/icons/obj/clothing/neck.dmi b/icons/obj/clothing/neck.dmi index e937d125ec2..e8726cfcb73 100644 Binary files a/icons/obj/clothing/neck.dmi and b/icons/obj/clothing/neck.dmi differ diff --git a/icons/obj/weapons/khopesh.dmi b/icons/obj/weapons/khopesh.dmi index 95774e7f6c7..3c4ba40b34a 100644 Binary files a/icons/obj/weapons/khopesh.dmi and b/icons/obj/weapons/khopesh.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 1f2044fc385..4983d02646a 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1128,6 +1128,7 @@ #include "code\datums\components\ai_has_target_timer.dm" #include "code\datums\components\ai_listen_to_weather.dm" #include "code\datums\components\ai_retaliate_advanced.dm" +#include "code\datums\components\amputating_limbs.dm" #include "code\datums\components\anti_magic.dm" #include "code\datums\components\appearance_on_aggro.dm" #include "code\datums\components\aquarium_content.dm" @@ -1484,7 +1485,6 @@ #include "code\datums\elements\ai_retaliate.dm" #include "code\datums\elements\ai_swap_combat_mode.dm" #include "code\datums\elements\ai_target_damagesource.dm" -#include "code\datums\elements\amputating_limbs.dm" #include "code\datums\elements\animal_variety.dm" #include "code\datums\elements\art.dm" #include "code\datums\elements\atmos_requirements.dm" @@ -1570,6 +1570,7 @@ #include "code\datums\elements\kneejerk.dm" #include "code\datums\elements\knockback.dm" #include "code\datums\elements\lazy_fishing_spot.dm" +#include "code\datums\elements\leeching_walk.dm" #include "code\datums\elements\lifesteal.dm" #include "code\datums\elements\light_blocking.dm" #include "code\datums\elements\light_eaten.dm" @@ -3163,7 +3164,6 @@ #include "code\modules\antagonists\clown_ops\clownop.dm" #include "code\modules\antagonists\clown_ops\outfits.dm" #include "code\modules\antagonists\cult\blood_magic.dm" -#include "code\modules\antagonists\cult\cult_bastard_sword.dm" #include "code\modules\antagonists\cult\cult_comms.dm" #include "code\modules\antagonists\cult\cult_items.dm" #include "code\modules\antagonists\cult\cult_objectives.dm" @@ -3176,6 +3176,7 @@ #include "code\modules\antagonists\cult\cult_turf_overlay.dm" #include "code\modules\antagonists\cult\rune_spawn_action.dm" #include "code\modules\antagonists\cult\runes.dm" +#include "code\modules\antagonists\cult\sword_fling.dm" #include "code\modules\antagonists\cult\datums\constructs.dm" #include "code\modules\antagonists\cult\datums\cult_team.dm" #include "code\modules\antagonists\cult\datums\cultist.dm" @@ -3198,6 +3199,7 @@ #include "code\modules\antagonists\heretic\knife_effect.dm" #include "code\modules\antagonists\heretic\moon_lunatic.dm" #include "code\modules\antagonists\heretic\rust_effect.dm" +#include "code\modules\antagonists\heretic\soultrapped_heretic.dm" #include "code\modules\antagonists\heretic\transmutation_rune.dm" #include "code\modules\antagonists\heretic\items\corrupted_organs.dm" #include "code\modules\antagonists\heretic\items\eldritch_flask.dm"