Cult Vs. Heretic: 7 Months Later Edition (#82877)

## About The Pull Request

This PR was originally meant as a replacement for the Bloody Bastard
blade, but then I stopped existing for 7 months. Now that I'm here
again, I'm finishing the job once and for all.

### **HERETICS VERSUS CULTISTS**

### Heretics

Heretics can now sacrifice cultists, which will give them one of three
gifts: The Cursed Blade, the Crimson Focus, and the Rusted Harvester.
The gifts given are weighted to be spread out equally with each type.
They will also gain one knowledge point.

- The Cursed Blade is a free heretic blade that is more powerful than
the normal heretic blade, including a small block chance. It can also be
used to draw heretic runes off combat mode.
- The Crimson Focus is a necklace that grants focusing and a minor
regeneration effect which also affects nearby heretics, at the cost of
gaining the BLOODY_MESS trait while wearing it. Additionally, it can be
squeezed to heal 50 points of brute/burn damage, injecting yourself with
three to six (separately) units of Eldritch ~~Water~~ Essence and Unholy
Water. Yes, this isn't good.
- The Rusted Harvester is a heretic 'monster' summon. It's a normal
Harvester, but instead of Area Conversion and Forcewall, it has
Aggressive Spread and Rust Construction (Raise Wall). It can delimb, but
only cultists, with a delay. It has an aura of decay, corroding the
environment and withering enemies near it, but it's VERY fragile.

Rusting cultist item dispensers will now cause them to turn into a
Heretic object. Altars turn into small heretic runes, Archives turn into
Codex Cicatrixi, Forges turn into Mawed Crucibles.

Ideally, Heretics would be able to gain an amount of these new powers
and use them to turn the tide against the cultists, amassing their power
and almost forming a sect of their own in turn which sweeps over and
converts the cult.

### Cultists

When a Cultist sacrifices a heretic, two things will happen:

- A new item will be available for creation at one of the dispensers.
- The Heretic will be trapped inside a powerful Haunted Blade.

`/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."`

This blade will be stronger across-the-board than a normal cult sword,
and will even allow those who wield it to cast one heretic spell from
their previous path. The only downside? The heretic can also cast one
spell. It's up to the trapped spirit if it wants to help you, or be a
nuisance.

The unlocked items are:
- The Cursed Blade, again. For cultists, it can be used to draw runes
twice as fast as usual, and they can even right-click it to teleport to
safety, just like a heretic!
- The Crimson Focus, again. Cultists are twice as fast at carving spells
into their body, and they gain a 5th spellslot as long as they wear the
amulet. It still causes hemophilia and grants weak regeneration.
- The Proteon Orb. This orb will create a gateway to Nar'sie's own
realm, spawning one Proteon every 15 seconds, which ghosts can possess.
The gateways cannot be placed close to one another.

Originally, they were going to be able to create a Harvester Shell, but
there were some concerns of it being too OP.

The true Bastard sword has been fully deleted. The null rod conversion
has been changed to a Bloody Halberd instead.

I'm considering re-enabling Stun Hand on Heretics, with Mansus Grasp
stats.

### Other

All the items above can be used by both Heretics and Cultists, no matter
how they were first created. Hell, even normal crew can use them! This
is probably not the best idea a lot of the time, though.

There are a lot of other changes in this PR. A loooooot. I will likely
miss some in the changelog, but I'll try to be as thorough as possible.
There's probably also some leftover garbo that I didn't find and clear
out yet.

## Why It's Good For The Game

Cult and Heretics, despite being mortally opposed, have very few
interactions with eachother, especially now that the Blade's gone. The
only thing of note is just the Heretic's unfair complete resistance to
stun hand, which is only marginally better than the alternative. This PR
will reintroduce their animosity, and give both sides a very, very good
reason to fight eachother.

The Cult will gain a sick sword that keeps the heretic in the game, and
unlike with the original implementation, will recieve a cult-wide bonus
in the form of a powerful, well deserved, and fun new item to summon.

The Heretic will gain powerful trinkets and knowledge from the
sacrifices, incentivizing them to become a terrifying cult-hunter. And
if they do succeed in wiping out the cult, they will have quite the
rewards to help with their ascension.

The crew, while mostly unaffected, will have a damn good reason to not
just Side with the heretic, out of fear of what they may become after
the cult is stomped down. They can also use a few of the items here in
an attempt to get one up on either side, as long as they manage to stay
clear of the side-effects.

Let the heretics eradicate the apostates.

Let the cultists root out the heathens.

![image](https://github.com/tgstation/tgstation/assets/53100513/b43d5d94-d06d-43b8-9b10-57f5a4a32bdb)
The haunted longsword creates an aura of darkness (disabled for the
cultist for the image)
Sprites... are not great. Hopefully someone comes by and improves them.

code: Added get_inactive_hand() as an easy shortcut for carbons
code: Wall walker element can now accept a trait for wall-checking
fix: Fixed soulsword component being unable to invoke the post summon
callback
refactor: Turned Heretic rust turf healing into an element, given to
Rust Walkers and Rusted Harvesters
refactor: Converted Limb Amputation from an element to a component

Blade and Sword sprites by meyhaza!!! I did the inhands though. Cuz im
cool
This commit is contained in:
carlarctg
2024-06-29 12:40:33 -05:00
committed by GitHub
parent 10c3e7c91b
commit 4eaa299c0b
91 changed files with 1755 additions and 399 deletions
+1
View File
@@ -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"
+2
View File
@@ -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"
+9
View File
@@ -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"
@@ -134,6 +134,9 @@
///cancel clean
#define COMSIG_ATOM_CANCEL_CLEAN (1<<0)
/// 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"
@@ -204,6 +204,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"
+2
View File
@@ -231,6 +231,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))
+10
View File
@@ -98,6 +98,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?
@@ -1089,6 +1091,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"
@@ -1151,6 +1158,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?
+5 -6
View File
@@ -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
-1
View File
@@ -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,
+4
View File
@@ -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,
@@ -149,6 +150,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_BOXING_READY" = TRAIT_BOXING_READY,
"TRAIT_BRAINWASHING" = TRAIT_BRAINWASHING,
@@ -296,6 +298,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,
@@ -535,6 +538,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,
+49 -40
View File
@@ -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
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(isconstruct(owner))
construct_owner = owner
else
construct_owner = null
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
// 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)
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
// 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()
+4 -4
View File
@@ -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)
@@ -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()
@@ -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
+13
View File
@@ -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)
+11 -4
View File
@@ -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)
+8 -1
View File
@@ -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)
+25 -13
View File
@@ -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.
+2 -3
View File
@@ -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
+3 -2
View File
@@ -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)
+57
View File
@@ -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
+8 -1
View File
@@ -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
+7
View File
@@ -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))
@@ -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."
+1
View File
@@ -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
@@ -1013,7 +1013,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)
@@ -1038,7 +1038,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]!"),
@@ -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
+76 -1
View File
@@ -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"
@@ -226,3 +230,74 @@
newmob.desc = "It's [living_mob], but [living_mob.p_their()] flesh has an ashy texture, and [living_mob.p_their()] face is featureless save an eerie smile."
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 <b>[round(atom_integrity * 100 / max_integrity)]%</b> 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."))
+8
View File
@@ -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."
+33 -10
View File
@@ -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. <b>Pick a spell to remove.</b>"))
to_chat(owner, span_cult_italic("You cannot store more than [limit] spells. <b>Pick a spell to remove.</b>"))
else
to_chat(owner, span_cult_bold_italic("<u>You cannot store more than [RUNELESS_MAX_BLOODCHARGE] spells without an empowering rune! Pick a spell to remove.</u>"))
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
@@ -428,19 +435,35 @@
return
var/datum/antagonist/cult/cultist = IS_CULTIST(user)
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)
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))
to_chat(user, span_warning("Some force greater than you intervenes! [target] is protected by the Forgotten Gods!"))
to_chat(target, span_warning("You are protected by your faith to the Forgotten Gods."))
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 = rgb(0, 128, 0)
animate(target, color = old_color, time = 1 SECONDS, easing = EASE_IN)
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!")
else if(target.can_block_magic())
to_chat(user, span_warning("The spell had no effect!"))
else
@@ -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, "<span class='cultlarge'>\"I wouldn't advise that.\"</span>")
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
+244 -2
View File
@@ -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."
+8 -1
View File
@@ -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)
@@ -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 = "<span class='warning'>The altar shatters, leaving only the wailing of the damned!</span>"
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
@@ -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 = "<span class='warning'>The books and tomes of the archives burn into ash as the desk shatters!</span>"
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
@@ -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 = "<span class='warning'>The forge breaks apart into shards with a howling scream!</span>"
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
@@ -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 <b>[round(atom_integrity * 100 / max_integrity)]%</b> 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 <b>[DisplayTimeText(COOLDOWN_TIMELEFT(src, use_cooldown))]</b>."))
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
@@ -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.
+28 -17
View File
@@ -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
@@ -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"
@@ -85,6 +85,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 ..()
@@ -230,6 +239,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_USER_ITEM_INTERACTION, PROC_REF(on_item_use))
RegisterSignal(our_mob, COMSIG_MOB_LOGIN, PROC_REF(fix_influence_network))
@@ -243,12 +254,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_USER_ITEM_INTERACTION,
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.
*/
@@ -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++
@@ -38,3 +38,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
@@ -24,23 +24,37 @@
attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "rend")
var/after_use_message = ""
/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 <b>activating it in-hand</b>.")
/// 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(!IS_HERETIC_OR_MONSTER(user))
if(!check_usability(user))
to_chat(user, span_danger("You feel a pulse of alien intellect lash out at your mind!"))
user.AdjustParalyzed(5 SECONDS)
var/mob/living/carbon/human/human_user = user
human_user.AdjustParalyzed(5 SECONDS)
return TRUE
return .
/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/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
@@ -50,18 +64,15 @@
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)
/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/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
/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 <b>activating it in-hand</b>.")
// Path of Rust's blade
/obj/item/melee/sickly_blade/rust
name = "\improper rusted blade"
@@ -154,3 +165,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
<b>It can also be shattered in-hand by cultists (via right-click), teleporting them to relative safety.<b>"}
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
@@ -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."
@@ -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"
@@ -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
@@ -183,34 +188,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 += " <i>graciously</i>"
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].)
@@ -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
@@ -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()
@@ -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
@@ -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 ..()
@@ -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
@@ -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
@@ -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("<b>[owner]</b> 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))
@@ -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)
@@ -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
@@ -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))
@@ -57,6 +57,10 @@
return span_notice("It's at <b>[round(atom_integrity * 100 / max_integrity)]%</b> 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 ..()
@@ -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
@@ -260,16 +260,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)
. += {"<span class='cult'>A construct shell, used to house bound souls from a soulstone.\n
var/extra_desc = {"<span class='cult'>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 <b>Artificer</b>, which can produce <b>more shells and soulstones</b>, as well as fortifications.\n
A <b>Wraith</b>, which does high damage and can jaunt through walls, though it is quite fragile.\n
A <b>Juggernaut</b>, which is very hard to kill and can produce temporary walls, but is slow.</span>"}
/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
@@ -494,6 +495,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))
@@ -523,7 +529,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
+3 -16
View File
@@ -310,25 +310,12 @@ GLOBAL_LIST_INIT(bibleitemstates, list(
if(.)
return .
if(istype(bible_smacked, /obj/item/cult_bastard) && !IS_CULTIST(user))
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...")
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)
@@ -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
+24 -9
View File
@@ -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)
@@ -416,13 +421,23 @@
items += worn_under.attached_accessories
return items
/**
* Returns the items that were succesfully unequipped.
*/
/mob/living/proc/unequip_everything()
var/list/items = list()
items |= get_equipped_items(INCLUDE_POCKETS)
// In case something isn't actually unequipped somehow
var/list/dropped_items = list()
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
@@ -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))
@@ -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 = "<B>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!<B>"
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
@@ -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",\
)
@@ -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?"))
@@ -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,\
@@ -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)
@@ -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,\
)
@@ -698,5 +698,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
@@ -89,8 +89,7 @@
. = ..()
var/obj/item/storage/backpack/bag = new_snailperson.get_item_by_slot(ITEM_SLOT_BACK)
if(!istype(bag, /obj/item/storage/backpack/snail))
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.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)
+1 -1
View File
@@ -470,7 +470,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)
@@ -277,3 +277,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
+5 -1
View File
@@ -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
@@ -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!"))
@@ -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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 908 KiB

After

Width:  |  Height:  |  Size: 904 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 22 KiB

+4 -2
View File
@@ -1016,6 +1016,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"
@@ -1372,7 +1373,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"
@@ -1458,6 +1458,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"
@@ -3050,7 +3051,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"
@@ -3063,6 +3063,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"
@@ -3085,6 +3086,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"