Adds 5 new Heretic spells. Rebalances some aspects of Heretics. Refactors some spells as well, and makes it so emote spells require free hands. (#71044)

## About The Pull Request

- Adds 5 new heretic spells!
- For Flesh: Flesh Surgery. This spell is a touch spell that can either
be used to heal your minions or extract organs from mobs.
- For Void: Cone of Cold. This is a simple spell - it shoots out a cone,
of cold, that freezes and damages people caught in it.
- For Ash: Volcano Blast. This spell functions like Tesla Blast, but
instead of electricity, it shoots out of a beam of fire that hurts to
walk over.
- For Blade: Realignment. Think of this like "Fleshmend but for stuns /
stamcrit". It rapidly regenerates stamina damage and reduces stuns,
while making you a pacifist. It can also be cast in rapid succession,
but this will increase the cooldown.
- For Rust: Rust Construction. Point at a rusted tile, and a wall will
be raised where it was instantly. This even damages people and throws
them aside - Or, if on a multi-z map, can lift up.

- Number of influences has increased.
  - 5 at 1 heretic
  - 9 at 2 
  - 12 at 3
  - 14 at 4
  - 15 at 5
  - 16 at 6, and so on

- Heretics are given a 5th sacrifice target, selected randomly. On
average an additional sacrifice is needed for their objectives.

- Being sacrificed grants you a permanent phobia of the supernatural.
Phobia of the supernatural has been expanded to cover heretic items and
mobs.

- The equation for offhand damage of blade heretics was tweaked. Actual
result unchanged, it's just more resilient to future changes now.

- Touch spells were refactored a bit, and overall expanded to be easier
to use

- Charged spells were added, and charged beam spells. Tesla blast uses
this.

- Cone spells were refactored to be easier to setup.

- Jaunting will now hide your runechat when it triggers, to make it less
easy to follow.

- Heretic Ghouls now take less stamina damage based on how low their
health pool is.

- Emote based spells now require hands to be unblocked to be cast, like
mime spells.
- Yes this gets rid of handcuffed invisible walls... Not 100% on this,
but I figured it's good for consistency? Open to discussion

## Why It's Good For The Game

A lotta feedback has passed through about heretic and it's time to
address some of it

- Problem: Not enough cool flash spells. Makes Focus not worth it. 
   - Solution: Adds some more spells to encourage focus use. 

- Problem: Sacrifice targets being too willing or not harmed enough
   - Solution: A permanent trauma. 

- Problem: Not enough ways to power up.
- Solution: Adding more influences around, though I think there should
be more variety in knowledge rituals as well.

- Problem: Ash Passage sucks
   - Solution: Makes it a smidge better to stay hidden with it.

- Problem: Heretic Ghouls get one hit by batons
- Solution: Stamina modifier should put them on par with unmodified
humans.

## Changelog

🆑 Melbert
add: Added five new heretic spells, one for each path. They come after
the Ritual of Knowledge.
add: Cone of Cold, for Void heretics. Shoots out a freezing chill in a
cone which deal damage and freezes.
add: Flesh Surgery for Flesh heretics. A touch spell which can either
heal minions or be used on mobs to extract organs without surgery.
add: Volcano Blast for Ash heretics. A beam spell, like Tesla Blast,
which fires out a beam of fire that bounces between people.
add: Realignment for Blade heretics. Fleshmend, but for stuns and
stamina damage. Makes you a pacifist, but rapidly regenerates stamina.
add: Rust Construction for Rust heretics. Places a wall of rust on the
target rusted flooring. Can even be used to ascend z-levels!
balance: Nerfed the cooldown of Cleave slightly, buffed the cooldown of
Lesser Cleave slightly.
balance: Slightly more influences will spawn on the station per heretic.
balance: Heretics require an additional sacrifice on average for
ascension, but are given a fifth sacrifice target (randomly selected).
balance: Being sacrificed by a heretic now gives you a permanent phobia
of spooky things, including heretic mobs and items.
balance: Heretic ghouls now take reduced stamina damage, depending on
how small their health pool is.
balance: Using Jaunts will conceal your runechat for their duration. 
balance: Spells which require emoting (Mime spells) require your hands
not be blocked to use.
refactor: Touch Spells were improved a bit. Added some new template
spells - Charged spells, and Charged beam spells.
fix: Fixes a runtime from losing heretic.
/🆑
This commit is contained in:
MrMelbert
2022-11-21 21:30:46 -08:00
committed by GitHub
parent b238e5d363
commit 7dde8a5e66
63 changed files with 1539 additions and 443 deletions
+7
View File
@@ -7,6 +7,13 @@
///Action button checks if user is conscious
#define AB_CHECK_CONSCIOUS (1<<3)
DEFINE_BITFIELD(check_flags, list(
"CHECK IF HANDS BLOCKED" = AB_CHECK_HANDS_BLOCKED,
"CHECK IF IMMOBILIZED" = AB_CHECK_IMMOBILE,
"CHECK IF LYING DOWN" = AB_CHECK_LYING,
"CHECK IF CONSCIOUS" = AB_CHECK_CONSCIOUS,
))
///Action button triggered with right click
#define TRIGGER_SECONDARY_ACTION (1<<0)
+10
View File
@@ -95,3 +95,13 @@ DEFINE_BITFIELD(antimagic_flags, list(
"MAGIC_RESISTANCE_HOLY" = MAGIC_RESISTANCE_HOLY,
"MAGIC_RESISTANCE_MIND" = MAGIC_RESISTANCE_MIND,
))
/**
* Checks if our mob is jaunting actively (within a phased mob object)
* Used in jaunting spells specifically to determine whether they should be entering or exiting jaunt
*
* If you want to use this in non-jaunt related code, it is preferable
* to instead check for trait [TRAIT_MAGICALLY_PHASED] instead of using this
* as it encompasses more states in which a mob may be "incorporeal from magic"
*/
#define is_jaunting(atom) (istype(atom.loc, /obj/effect/dummy/phased_mob))
+1
View File
@@ -7,6 +7,7 @@
#define CHANNEL_AMBIENCE 1019
#define CHANNEL_BUZZ 1018
#define CHANNEL_TRAITOR 1017
#define CHANNEL_CHARGED_SPELL 1016
///Default range of a sound.
#define SOUND_RANGE 17
+3
View File
@@ -965,6 +965,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
/// Makes a species be better/worse at defending against tackling depending on their tail's status
#define TRAIT_TACKLING_TAILED_DEFENDER "tackling_tailed_defender"
/// Is runechat for this atom/movable currently disabled, regardless of prefs or anything?
#define TRAIT_RUNECHAT_HIDDEN "runechat_hudden"
/// some trait sorces dirived from bodyparts BODYPART_TRAIT is generic.
#define BODYPART_TRAIT "bodypart"
#define HEAD_TRAIT "head"
+2
View File
@@ -802,6 +802,8 @@ GLOBAL_LIST_EMPTY(species_list)
return BODY_ZONE_CHEST
if(BODY_ZONE_PRECISE_EYES)
return BODY_ZONE_HEAD
if(BODY_ZONE_PRECISE_MOUTH)
return BODY_ZONE_HEAD
if(BODY_ZONE_PRECISE_R_HAND)
return BODY_ZONE_R_ARM
if(BODY_ZONE_PRECISE_L_HAND)
+16 -3
View File
@@ -69,6 +69,7 @@ GLOBAL_LIST_INIT(phobia_mobs, list(
/mob/living/simple_animal/hostile/construct,
/mob/living/simple_animal/hostile/dark_wizard,
/mob/living/simple_animal/hostile/faithless,
/mob/living/simple_animal/hostile/heretic_summon,
/mob/living/simple_animal/hostile/imp,
/mob/living/simple_animal/hostile/retaliate/bat,
/mob/living/simple_animal/hostile/retaliate/ghost,
@@ -335,16 +336,25 @@ GLOBAL_LIST_INIT(phobia_objs, list(
)),
"the supernatural" = typecacheof(list(
/obj/effect/floating_blade,
/obj/effect/heretic_influence,
/obj/effect/heretic_rune,
/obj/effect/rune,
/obj/effect/visible_heretic_influence,
/obj/item/clothing/head/hooded/cult_hoodie,
/obj/item/clothing/head/wizard,
/obj/item/clothing/mask/madness_mask,
/obj/item/clothing/neck/heretic_focus,
/obj/item/clothing/neck/eldritch_amulet,
/obj/item/clothing/suit/hooded/cultrobes,
/obj/item/clothing/suit/wizrobe,
/obj/item/clothing/under/rank/civilian/chaplain,
/obj/item/codex_cicatrix,
/obj/item/cult_bastard,
/obj/item/gun/magic/staff,
/obj/item/gun/magic/wand,
/obj/item/gun/magic,
/obj/item/melee/cultblade,
/obj/item/melee/rune_carver,
/obj/item/melee/sickly_blade,
/obj/item/necromantic_stone,
/obj/item/nullrod,
/obj/item/restraints/legcuffs/bola/cult,
@@ -356,13 +366,16 @@ GLOBAL_LIST_INIT(phobia_objs, list(
/obj/item/staff,
/obj/item/storage/toolbox/haunted,
/obj/item/tome,
/obj/item/toy/cards/deck/tarot,
/obj/item/toy/eightball/haunted,
/obj/item/toy/eldritch_book,
/obj/item/toy/reality_pierce,
/obj/item/warp_whistle,
/obj/machinery/door/airlock/cult,
/obj/narsie,
/obj/structure/destructible/cult,
/obj/structure/destructible/eldritch_crucible,
/obj/structure/spirit_board,
/obj/item/toy/cards/deck/tarot,
)),
"aliens" = typecacheof(list(
+1
View File
@@ -204,6 +204,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_SNOWSTORM_IMMUNE" = TRAIT_SNOWSTORM_IMMUNE,
"TRAIT_VOIDSTORM_IMMUNE" = TRAIT_VOIDSTORM_IMMUNE,
"TRAIT_WEATHER_IMMUNE" = TRAIT_WEATHER_IMMUNE,
"TRAIT_RUNECHAT_HIDDEN" = TRAIT_RUNECHAT_HIDDEN,
),
/obj/item/card/id = list(
"TRAIT_MAGNETIC_ID_CARD" = TRAIT_MAGNETIC_ID_CARD,
+2
View File
@@ -260,6 +260,8 @@
/mob/proc/create_chat_message(atom/movable/speaker, datum/language/message_language, raw_message, list/spans, runechat_flags = NONE)
if(SSlag_switch.measures[DISABLE_RUNECHAT] && !HAS_TRAIT(speaker, TRAIT_BYPASS_MEASURES))
return
if(HAS_TRAIT(speaker, TRAIT_RUNECHAT_HIDDEN))
return
// Ensure the list we are using, if present, is a copy so we don't modify the list provided to us
spans = spans ? spans.Copy() : list()
+3
View File
@@ -218,6 +218,9 @@
for(var/datum/objective/objective as anything in get_all_objectives())
to_chat(current, "<B>[objective.objective_name] #[obj_count]</B>: [objective.explanation_text]")
obj_count++
// Objectives are often stored in the static data of antag uis, so we should update those as well
for(var/datum/antagonist/antag as anything in antag_datums)
antag.update_static_data(current)
/datum/mind/proc/find_syndicate_uplink(check_unlocked)
var/list/L = current.get_all_contents()
+2 -4
View File
@@ -76,10 +76,8 @@
living_cast_on.setDir(original_dir)
/datum/action/cooldown/spell/cone/staggered/fire_breath/calculate_cone_shape(current_level)
// This makes the cone shoot out into a 3 wide column of flames.
// You may be wondering, "that equation doesn't seem like it'd make a 3 wide column"
// well it does, and that's all that matters.
return (2 * current_level) - 1
// This makes the cone shoot out into a 3 wide column of flames no matter the distance
return 3
/datum/action/cooldown/spell/cone/staggered/fire_breath/do_turf_cone_effect(turf/target_turf, atom/caster, level)
// Further turfs experience less exposed_temperature and exposed_volume
+1
View File
@@ -32,6 +32,7 @@
cooldown_time = 12 SECONDS
invocation_type = INVOCATION_NONE
spell_requirements = NONE
antimagic_flags = NONE
//Vars for zaps made when power chromosome is applied, ripped and toned down from reactive tesla armor code.
///This var decides if the spell should chain, dictated by presence of power chromosome
+4 -1
View File
@@ -52,6 +52,10 @@
duration = 8
can_melt = FALSE
/datum/status_effect/freon/lasting
id = "lasting_frozen"
duration = -1
/datum/status_effect/hypernob_protection
id = "hypernob_protection"
duration = 10 SECONDS
@@ -80,4 +84,3 @@
var/mob/living/carbon/human/human_owner = owner
human_owner.remove_movespeed_modifier(/datum/movespeed_modifier/reagent/hypernoblium)
REMOVE_TRAIT(human_owner, TRAIT_NOFIRE, type)
@@ -49,15 +49,8 @@
var/static/list/scribing_tools = typecacheof(list(/obj/item/pen, /obj/item/toy/crayon))
/// A blacklist of turfs we cannot scribe on.
var/static/list/blacklisted_rune_turfs = typecacheof(list(/turf/open/space, /turf/open/openspace, /turf/open/lava, /turf/open/chasm))
/datum/antagonist/heretic/Destroy()
LAZYNULL(sac_targets)
return ..()
/datum/antagonist/heretic/ui_data(mob/user)
var/list/data = list()
var/static/list/path_to_color = list(
/// Static list of what each path converts to in the UI (colors are TGUI colors)
var/static/list/path_to_ui_color = list(
PATH_START = "grey",
PATH_SIDE = "green",
PATH_RUST = "brown",
@@ -67,8 +60,20 @@
PATH_BLADE = "label", // my favorite color is label
)
data["charges"] = knowledge_points
/datum/antagonist/heretic/Destroy()
LAZYNULL(sac_targets)
return ..()
/datum/antagonist/heretic/ui_data(mob/user)
var/list/data = list()
data["charges"] = knowledge_points
data["total_sacrifices"] = total_sacrifices
data["ascended"] = ascended
// This should be cached in some way, but the fact that final knowledge
// has to update its disabled state based on whether all objectives are complete,
// makes this very difficult. I'll figure it out one day maybe
for(var/datum/heretic_knowledge/knowledge as anything in get_researchable_knowledge())
var/list/knowledge_data = list()
knowledge_data["path"] = knowledge
@@ -83,10 +88,17 @@
knowledge_data["disabled"] = !can_ascend()
knowledge_data["hereticPath"] = initial(knowledge.route)
knowledge_data["color"] = path_to_color[initial(knowledge.route)] || "grey"
knowledge_data["color"] = path_to_ui_color[initial(knowledge.route)] || "grey"
data["learnableKnowledge"] += list(knowledge_data)
return data
/datum/antagonist/heretic/ui_static_data(mob/user)
var/list/data = list()
data["objectives"] = get_objectives()
for(var/path in researched_knowledge)
var/list/knowledge_data = list()
var/datum/heretic_knowledge/found_knowledge = researched_knowledge[path]
@@ -95,21 +107,12 @@
knowledge_data["gainFlavor"] = found_knowledge.gain_text
knowledge_data["cost"] = found_knowledge.cost
knowledge_data["hereticPath"] = found_knowledge.route
knowledge_data["color"] = path_to_color[found_knowledge.route] || "grey"
knowledge_data["color"] = path_to_ui_color[found_knowledge.route] || "grey"
data["learnedKnowledge"] += list(knowledge_data)
return data
/datum/antagonist/heretic/ui_static_data(mob/user)
var/list/data = list()
data["total_sacrifices"] = total_sacrifices
data["ascended"] = ascended
data["objectives"] = get_objectives()
return data
/datum/antagonist/heretic/ui_act(action, params)
. = ..()
if(.)
@@ -122,9 +125,9 @@
CRASH("Heretic attempted to learn non-heretic_knowledge path! (Got: [researched_path])")
if(initial(researched_path.cost) > knowledge_points)
return
return TRUE
if(!gain_knowledge(researched_path))
return
return TRUE
log_heretic_knowledge("[key_name(owner)] gained knowledge: [initial(researched_path.name)]")
knowledge_points -= initial(researched_path.cost)
@@ -183,7 +186,7 @@
/datum/antagonist/heretic/on_removal()
for(var/knowledge_index in researched_knowledge)
var/datum/heretic_knowledge/knowledge = researched_knowledge[knowledge_index]
knowledge.on_lose(owner.current)
knowledge.on_lose(owner.current, src)
GLOB.reality_smash_track.remove_tracked_mind(owner)
QDEL_LIST_ASSOC_VAL(researched_knowledge)
@@ -218,8 +221,8 @@
. = ..()
for(var/knowledge_index in researched_knowledge)
var/datum/heretic_knowledge/knowledge = researched_knowledge[knowledge_index]
knowledge.on_lose(old_body)
knowledge.on_gain(new_body)
knowledge.on_lose(old_body, src)
knowledge.on_gain(new_body, src)
/*
* Signal proc for [COMSIG_MOB_BEFORE_SPELL_CAST] and [COMSIG_MOB_SPELL_ACTIVATED].
@@ -476,7 +479,7 @@
.["Adjust Knowledge Points"] = CALLBACK(src, PROC_REF(admin_change_points))
/*
/**
* Admin proc for giving a heretic a Living Heart easily.
*/
/datum/antagonist/heretic/proc/give_living_heart(mob/admin)
@@ -489,9 +492,9 @@
to_chat(admin, span_warning("The heretic doesn't have a living heart knowledge for some reason. What?"))
return
heart_knowledge.on_research(owner.current)
heart_knowledge.on_research(owner.current, src)
/*
/**
* Admin proc for adding a marked mob to a heretic's sac list.
*/
/datum/antagonist/heretic/proc/add_marked_as_target(mob/admin)
@@ -510,7 +513,7 @@
add_sacrifice_target(new_target)
/*
/**
* Admin proc for removing a mob from a heretic's sac list.
*/
/datum/antagonist/heretic/proc/remove_target(mob/admin)
@@ -536,7 +539,7 @@
if(tgui_alert(admin, "Let them know their targets have been updated?", "Whispers of the Mansus", list("Yes", "No")) == "Yes")
to_chat(owner.current, span_danger("The Mansus has modified your targets."))
/*
/**
* Admin proc for easily adding / removing knowledge points.
*/
/datum/antagonist/heretic/proc/admin_change_points(mob/admin)
@@ -575,7 +578,7 @@
. += "<i>None!</i><br>"
. += "<br>"
/*
/**
* Learns the passed [typepath] of knowledge, creating a knowledge datum
* and adding it to our researched knowledge list.
*
@@ -589,10 +592,11 @@
return FALSE
var/datum/heretic_knowledge/initialized_knowledge = new knowledge_type()
researched_knowledge[knowledge_type] = initialized_knowledge
initialized_knowledge.on_research(owner.current)
initialized_knowledge.on_research(owner.current, src)
update_static_data(owner.current)
return TRUE
/*
/**
* Get a list of all knowledge TYPEPATHS that we can currently research.
*/
/datum/antagonist/heretic/proc/get_researchable_knowledge()
@@ -606,13 +610,13 @@
researchable_knowledge -= banned_knowledge
return researchable_knowledge
/*
/**
* Check if the wanted type-path is in the list of research knowledge.
*/
/datum/antagonist/heretic/proc/get_knowledge(wanted)
return researched_knowledge[wanted]
/*
/**
* Get a list of all rituals this heretic can invoke on a rune.
* Iterates over all of our knowledge and, if we can invoke it, adds it to our list.
*
@@ -629,7 +633,7 @@
return sortTim(rituals, GLOBAL_PROC_REF(cmp_heretic_knowledge), associative = TRUE)
/*
/**
* Checks to see if our heretic can ccurrently ascend.
*
* Returns FALSE if not all of our objectives are complete, or TRUE otherwise.
@@ -640,7 +644,7 @@
return FALSE
return TRUE
/*
/**
* Helper to determine if a Heretic
* - Has a Living Heart
* - Has a an organ in the correct slot that isn't a living heart
@@ -666,7 +670,7 @@
/datum/objective/minor_sacrifice/New(text)
. = ..()
target_amount = rand(2, 3)
target_amount = rand(3, 4)
update_explanation_text()
/datum/objective/minor_sacrifice/update_explanation_text()
@@ -50,14 +50,15 @@
* This is only ever called once per heretic.
*
* Arguments
* * user - the heretic who researched something
* * user - The heretic who researched something
* * our_heretic - The antag datum of who researched us. This should never be null.
*/
/datum/heretic_knowledge/proc/on_research(mob/user)
/datum/heretic_knowledge/proc/on_research(mob/user, datum/antagonist/heretic/our_heretic)
SHOULD_CALL_PARENT(TRUE)
if(gain_text)
to_chat(user, span_warning("[gain_text]"))
on_gain(user)
on_gain(user, our_heretic)
/**
* Called when the knowledge is applied to a mob.
@@ -66,8 +67,9 @@
*
* Arguments
* * user - the heretic which we're applying things to
* * our_heretic - The antag datum of who gained us. This should never be null.
*/
/datum/heretic_knowledge/proc/on_gain(mob/user)
/datum/heretic_knowledge/proc/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
return
/**
@@ -76,8 +78,9 @@
*
* Arguments
* * user - the heretic which we're removing things from
* * our_heretic - The antag datum of who is losing us. This should never be null.
*/
/datum/heretic_knowledge/proc/on_lose(mob/user)
/datum/heretic_knowledge/proc/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
return
/**
@@ -165,7 +168,7 @@
selected_atoms -= sacrificed
qdel(sacrificed)
/*
/**
* A knowledge subtype that grants the heretic a certain spell.
*/
/datum/heretic_knowledge/spell
@@ -179,7 +182,7 @@
QDEL_NULL(created_spell_ref)
return ..()
/datum/heretic_knowledge/spell/on_gain(mob/user)
/datum/heretic_knowledge/spell/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
// Added spells are tracked on the body, and not the mind,
// because we handle heretic mind transfers
// via the antag datum (on_gain and on_lose).
@@ -187,11 +190,11 @@
created_spell.Grant(user)
created_spell_ref = WEAKREF(created_spell)
/datum/heretic_knowledge/spell/on_lose(mob/user)
/datum/heretic_knowledge/spell/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
var/datum/action/cooldown/spell/created_spell = created_spell_ref?.resolve()
created_spell?.Remove(user)
/*
/**
* A knowledge subtype for knowledge that can only
* have a limited amount of it's resulting atoms
* created at once.
@@ -225,7 +228,7 @@
LAZYADD(created_items, WEAKREF(created_thing))
return TRUE
/*
/**
* A knowledge subtype for limited_amount knowledge
* used for base knowledge (the ones that make blades)
*
@@ -247,11 +250,12 @@
continue
banned_knowledge += final_knowledge_type
/datum/heretic_knowledge/limited_amount/starting/on_research(mob/user)
/datum/heretic_knowledge/limited_amount/starting/on_research(mob/user, datum/antagonist/heretic/our_heretic)
. = ..()
our_heretic.heretic_path = route
SSblackbox.record_feedback("tally", "heretic_path_taken", 1, route)
/*
/**
* A knowledge subtype for heretic knowledge
* that applies a mark on use.
*
@@ -264,11 +268,11 @@
/// The status effect typepath we apply on people on mansus grasp.
var/datum/status_effect/eldritch/mark_type
/datum/heretic_knowledge/mark/on_gain(mob/user)
/datum/heretic_knowledge/mark/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, PROC_REF(on_mansus_grasp))
RegisterSignal(user, COMSIG_HERETIC_BLADE_ATTACK, PROC_REF(on_eldritch_blade))
/datum/heretic_knowledge/mark/on_lose(mob/user)
/datum/heretic_knowledge/mark/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
UnregisterSignal(user, list(COMSIG_HERETIC_MANSUS_GRASP_ATTACK, COMSIG_HERETIC_BLADE_ATTACK))
/**
@@ -316,7 +320,7 @@
mark.on_effect()
return TRUE
/*
/**
* A knowledge subtype for heretic knowledge that
* upgrades their sickly blade, either on melee or range.
*
@@ -327,11 +331,11 @@
mutually_exclusive = TRUE
cost = 2
/datum/heretic_knowledge/blade_upgrade/on_gain(mob/user)
/datum/heretic_knowledge/blade_upgrade/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
RegisterSignal(user, COMSIG_HERETIC_BLADE_ATTACK, PROC_REF(on_eldritch_blade))
RegisterSignal(user, COMSIG_HERETIC_RANGED_BLADE_ATTACK, PROC_REF(on_ranged_eldritch_blade))
/datum/heretic_knowledge/blade_upgrade/on_lose(mob/user)
/datum/heretic_knowledge/blade_upgrade/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
UnregisterSignal(user, list(COMSIG_HERETIC_BLADE_ATTACK, COMSIG_HERETIC_RANGED_BLADE_ATTACK))
@@ -369,7 +373,7 @@
/datum/heretic_knowledge/blade_upgrade/proc/do_ranged_effects(mob/living/source, mob/living/target, obj/item/melee/sickly_blade/blade)
return
/*
/**
* A knowledge subtype lets the heretic curse someone with a ritual.
*/
/datum/heretic_knowledge/curse
@@ -492,7 +496,7 @@
chosen_mob.remove_filter(name)
/*
/**
* A knowledge subtype lets the heretic summon a monster with the ritual.
*/
/datum/heretic_knowledge/summon
@@ -540,7 +544,7 @@
/// The amount of knowledge points the knowledge ritual gives on success.
#define KNOWLEDGE_RITUAL_POINTS 4
/*
/**
* A subtype of knowledge that generates random ritual components.
*/
/datum/heretic_knowledge/knowledge_ritual
@@ -599,7 +603,7 @@
// 1 uncommon item.
required_atoms[pick(potential_uncommoner_items)] += 1
/datum/heretic_knowledge/knowledge_ritual/on_research(mob/user)
/datum/heretic_knowledge/knowledge_ritual/on_research(mob/user, datum/antagonist/heretic/our_heretic)
. = ..()
var/list/requirements_string = list()
@@ -634,7 +638,7 @@
#undef KNOWLEDGE_RITUAL_POINTS
/*
/**
* The special final tier of knowledges that unlocks ASCENSION.
*/
/datum/heretic_knowledge/ultimate
@@ -644,16 +648,15 @@
priority = MAX_KNOWLEDGE_PRIORITY + 1 // Yes, the final ritual should be ABOVE the max priority.
required_atoms = list(/mob/living/carbon/human = 3)
/datum/heretic_knowledge/ultimate/on_research(mob/user)
/datum/heretic_knowledge/ultimate/on_research(mob/user, datum/antagonist/heretic/our_heretic)
. = ..()
var/datum/antagonist/heretic/heretic_datum = IS_HERETIC(user)
var/total_points = 0
for(var/datum/heretic_knowledge/knowledge as anything in flatten_list(heretic_datum.researched_knowledge))
for(var/datum/heretic_knowledge/knowledge as anything in flatten_list(our_heretic.researched_knowledge))
total_points += knowledge.cost
log_heretic_knowledge("[key_name(user)] gained knowledge of their final ritual at [worldtime2text()]. \
They have [length(heretic_datum.researched_knowledge)] knowledge nodes researched, totalling [total_points] points \
and have sacrificed [heretic_datum.total_sacrifices] people ([heretic_datum.high_value_sacrifices] of which were high value)")
They have [length(our_heretic.researched_knowledge)] knowledge nodes researched, totalling [total_points] points \
and have sacrificed [our_heretic.total_sacrifices] people ([our_heretic.high_value_sacrifices] of which were high value)")
/datum/heretic_knowledge/ultimate/can_be_invoked(datum/antagonist/heretic/invoker)
if(invoker.ascended)
@@ -689,6 +692,9 @@
var/datum/antagonist/heretic/heretic_datum = IS_HERETIC(user)
heretic_datum.ascended = TRUE
// Show the cool red gradiant in our UI
heretic_datum.update_static_data(user)
if(ishuman(user))
var/mob/living/carbon/human/human_user = user
human_user.physiology.brute_mod *= 0.5
@@ -1,6 +1,6 @@
/// The number of influences spawned per heretic
#define NUM_INFLUENCES_PER_HERETIC 4
#define NUM_INFLUENCES_PER_HERETIC 5
/**
* #Reality smash tracker
@@ -65,10 +65,6 @@
* and the number of minds we're tracking.
*/
/datum/reality_smash_tracker/proc/generate_new_influences()
// 1 heretic = 4 influences
// 2 heretics = 7 influences
// 3 heretics = 9 influences
// 4 heretics = 10 influences, +1 for each onwards.
var/how_many_can_we_make = 0
for(var/heretic_number in 1 to length(tracked_heretics))
how_many_can_we_make += max(NUM_INFLUENCES_PER_HERETIC - heretic_number + 1, 1)
@@ -12,6 +12,7 @@
*
* Mark of Ash
* Ritual of Knowledge
* Fire Blast
* Mask of Madness
* > Sidepaths:
* Curse of Corrosion
@@ -39,11 +40,6 @@
result_atoms = list(/obj/item/melee/sickly_blade/ash)
route = PATH_ASH
/datum/heretic_knowledge/limited_amount/starting/base_ash/on_research(mob/user)
. = ..()
var/datum/antagonist/heretic/our_heretic = IS_HERETIC(user)
our_heretic.heretic_path = route
/datum/heretic_knowledge/ashen_grasp
name = "Grasp of Ash"
desc = "Your Mansus Grasp will burn the eyes of the victim, causing damage and blindness."
@@ -53,10 +49,10 @@
cost = 1
route = PATH_ASH
/datum/heretic_knowledge/ashen_grasp/on_gain(mob/user)
/datum/heretic_knowledge/ashen_grasp/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, PROC_REF(on_mansus_grasp))
/datum/heretic_knowledge/ashen_grasp/on_lose(mob/user)
/datum/heretic_knowledge/ashen_grasp/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
UnregisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK)
/datum/heretic_knowledge/ashen_grasp/proc/on_mansus_grasp(mob/living/source, mob/living/target)
@@ -110,9 +106,21 @@
grasp.UpdateButtons()
/datum/heretic_knowledge/knowledge_ritual/ash
next_knowledge = list(/datum/heretic_knowledge/mad_mask)
next_knowledge = list(/datum/heretic_knowledge/spell/fire_blast)
route = PATH_ASH
/datum/heretic_knowledge/spell/fire_blast
name = "Volcano Blast"
desc = "Grants you Volcano Blast, a spell that - after a short charge - fires off a beam of energy \
at a nearby enemy, setting them on fire and burning them. If they do not extinguish themselves, \
the beam will continue to another target."
gain_text = "No fire was hot enough to rekindle them. No fire was bright enough to save them. No fire is eternal."
next_knowledge = list(/datum/heretic_knowledge/mad_mask)
spell_to_add = /datum/action/cooldown/spell/charged/beam/fire_blast
cost = 1
route = PATH_ASH
/datum/heretic_knowledge/mad_mask
name = "Mask of Madness"
desc = "Allows you to transmute any mask, four candles, a stun baton, and a liver to create a Mask of Madness. \
@@ -209,6 +217,12 @@
var/datum/action/cooldown/spell/fire_cascade/big/screen_wide_fire_spell = new(user.mind)
screen_wide_fire_spell.Grant(user)
var/datum/action/cooldown/spell/charged/beam/fire_blast/existing_beam_spell = locate() in user.actions
if(existing_beam_spell)
existing_beam_spell.max_beam_bounces *= 2 // Double beams
existing_beam_spell.beam_duration *= 0.66 // Faster beams
existing_beam_spell.cooldown_time *= 0.66 // Lower cooldown
user.client?.give_award(/datum/award/achievement/misc/ash_ascension, user)
for(var/trait in traits_to_apply)
ADD_TRAIT(user, trait, MAGIC_TRAIT)
@@ -12,6 +12,7 @@
*
* Mark of the Blade
* Ritual of Knowledge
* Realignment
* Stance of the Scarred Duelist
* > Sidepaths:
* Carving Knife
@@ -49,10 +50,10 @@
cost = 1
route = PATH_BLADE
/datum/heretic_knowledge/blade_grasp/on_gain(mob/user)
/datum/heretic_knowledge/blade_grasp/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, PROC_REF(on_mansus_grasp))
/datum/heretic_knowledge/blade_grasp/on_lose(mob/user)
/datum/heretic_knowledge/blade_grasp/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
UnregisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK)
/datum/heretic_knowledge/blade_grasp/proc/on_mansus_grasp(mob/living/source, mob/living/target)
@@ -112,10 +113,10 @@
/// Used instead of cooldowns, so we can give feedback when it's ready again
var/riposte_ready = TRUE
/datum/heretic_knowledge/blade_dance/on_gain(mob/user)
/datum/heretic_knowledge/blade_dance/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
RegisterSignal(user, COMSIG_HUMAN_CHECK_SHIELDS, PROC_REF(on_shield_reaction))
/datum/heretic_knowledge/blade_dance/on_lose(mob/user)
/datum/heretic_knowledge/blade_dance/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
UnregisterSignal(user, COMSIG_HUMAN_CHECK_SHIELDS)
/datum/heretic_knowledge/blade_dance/proc/on_shield_reaction(
@@ -201,12 +202,11 @@
/datum/heretic_knowledge/mark/blade_mark/create_mark(mob/living/source, mob/living/target)
var/datum/status_effect/eldritch/blade/blade_mark = ..()
if(!istype(blade_mark))
return
var/area/to_lock_to = get_area(target)
blade_mark.locked_to = to_lock_to
to_chat(target, span_hypnophrase("An otherworldly force is compelling you to stay in [get_area_name(to_lock_to)]!"))
if(istype(blade_mark))
var/area/to_lock_to = get_area(target)
blade_mark.locked_to = to_lock_to
to_chat(target, span_hypnophrase("An otherworldly force is compelling you to stay in [get_area_name(to_lock_to)]!"))
return blade_mark
/datum/heretic_knowledge/mark/blade_mark/trigger_mark(mob/living/source, mob/living/target)
. = ..()
@@ -215,7 +215,18 @@
source.apply_status_effect(/datum/status_effect/protective_blades, 60 SECONDS, 1, 20, 0 SECONDS)
/datum/heretic_knowledge/knowledge_ritual/blade
next_knowledge = list(/datum/heretic_knowledge/spell/realignment)
route = PATH_BLADE
/datum/heretic_knowledge/spell/realignment
name = "Realignment"
desc = "Grants you Realignment a spell that wil realign your body rapidly for a short period. \
During this process, you will rapidly regenerate stamina and stuns, however, you will be unable to attack. \
This spell can be casted in rapid succession, but doing so will increase the cooldown."
gain_text = "In the flurry of death, he found peace within himself. Despite insurmountable odds, he forged on."
next_knowledge = list(/datum/heretic_knowledge/duel_stance)
spell_to_add = /datum/action/cooldown/spell/realignment
cost = 1
route = PATH_BLADE
/// The amount of blood flow reduced per level of severity of gained bleeding wounds for Stance of the Torn Champion.
@@ -239,7 +250,7 @@
/// Whether we're currently in duelist stance, gaining certain buffs (low health)
var/in_duelist_stance = FALSE
/datum/heretic_knowledge/duel_stance/on_gain(mob/user)
/datum/heretic_knowledge/duel_stance/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
ADD_TRAIT(user, TRAIT_NODISMEMBER, type)
RegisterSignal(user, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
RegisterSignal(user, COMSIG_CARBON_GAIN_WOUND, PROC_REF(on_wound_gain))
@@ -247,7 +258,7 @@
on_health_update(user) // Run this once, so if the knowledge is learned while hurt it activates properly
/datum/heretic_knowledge/duel_stance/on_lose(mob/user)
/datum/heretic_knowledge/duel_stance/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
REMOVE_TRAIT(user, TRAIT_NODISMEMBER, type)
if(in_duelist_stance)
REMOVE_TRAIT(user, TRAIT_HARDLY_WOUNDED, type)
@@ -298,6 +309,10 @@
a flurry of blades, neither hitting their mark, for the Champion was indomitable."
next_knowledge = list(/datum/heretic_knowledge/spell/furious_steel)
route = PATH_BLADE
/// How much force do we apply to the offhand?
var/offand_force_decrement = 0
/// How much force was the last weapon we offhanded with? If it's different, we need to re-calculate the decrement
var/last_weapon_force = -1
/datum/heretic_knowledge/blade_upgrade/blade/do_melee_effects(mob/living/source, mob/living/target, obj/item/melee/sickly_blade/blade)
if(target == source)
@@ -317,18 +332,36 @@
/datum/heretic_knowledge/blade_upgrade/blade/proc/follow_up_attack(mob/living/source, mob/living/target, obj/item/melee/sickly_blade/blade)
if(QDELETED(source) || QDELETED(target) || QDELETED(blade))
return
// Sanity to ensure that the blade we're delivering
// an offhand attack with is actually our offhand
// Sanity to ensure that the blade we're delivering an offhand attack with is ACTUALLY our offhand
if(blade != source.get_inactive_held_item())
return
// And we easily could've moved away
if(!source.Adjacent(target))
return
// Blade are 17 force: 17 + 17 = 34 (3 hits to crit, unarmored)
// So, -5 force is put on the offhand blade: 17 + 12 = 29 (4 hits to crit, unarmored)
blade.force -= 5
// Check if we need to recaclulate our offhand force
// This is just so we don't run this block every attack, that's wasteful
if(last_weapon_force != blade.force)
offand_force_decrement = 0
// We want to make sure that the offhand blade increases their hits to crit by one, just about
// So, let's do some quick math. Yes this'll be inaccurate if their mainhand blade is modified (whetstone), no I don't care
// Find how much force we need to detract from the second blade
var/hits_to_crit_on_average = ROUND_UP(100 / (blade.force * 2))
while(hits_to_crit_on_average <= 3) // 3 hits and beyond is a bit too absurd
if(offand_force_decrement + 2 > blade.force * 0.5) // But also cutting the force beyond half is absurd
break
offand_force_decrement += 2
hits_to_crit_on_average = ROUND_UP(100 / (blade.force * 2 - offand_force_decrement))
// Save the force as our last weapon force
last_weapon_force = blade.force
// Subtract the decrement
blade.force -= offand_force_decrement
// Perform the offhand attack
blade.melee_attack_chain(source, target)
blade.force += 5
// Restore the force.
blade.force = last_weapon_force
/datum/heretic_knowledge/spell/furious_steel
name = "Furious Steel"
@@ -17,6 +17,7 @@
*
* Mark of Flesh
* Ritual of Knowledge
* Flesh Surgery
* Raw Ritual
* > Sidepaths:
* Blood Siphon
@@ -45,11 +46,8 @@
limit = 3 // Bumped up so they can arm up their ghouls too.
route = PATH_FLESH
/datum/heretic_knowledge/limited_amount/starting/base_flesh/on_research(mob/user)
/datum/heretic_knowledge/limited_amount/starting/base_flesh/on_research(mob/user, datum/antagonist/heretic/our_heretic)
. = ..()
var/datum/antagonist/heretic/our_heretic = IS_HERETIC(user)
our_heretic.heretic_path = route
var/datum/objective/heretic_summon/summon_objective = new()
summon_objective.owner = our_heretic.owner
our_heretic.objectives += summon_objective
@@ -68,10 +66,10 @@
cost = 1
route = PATH_FLESH
/datum/heretic_knowledge/limited_amount/flesh_grasp/on_gain(mob/user)
/datum/heretic_knowledge/limited_amount/flesh_grasp/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, PROC_REF(on_mansus_grasp))
/datum/heretic_knowledge/limited_amount/flesh_grasp/on_lose(mob/user)
/datum/heretic_knowledge/limited_amount/flesh_grasp/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
UnregisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK)
/datum/heretic_knowledge/limited_amount/flesh_grasp/proc/on_mansus_grasp(mob/living/source, mob/living/target)
@@ -219,7 +217,19 @@
mark_type = /datum/status_effect/eldritch/flesh
/datum/heretic_knowledge/knowledge_ritual/flesh
next_knowledge = list(/datum/heretic_knowledge/spell/flesh_surgery)
route = PATH_FLESH
/datum/heretic_knowledge/spell/flesh_surgery
name = "Knitting of Flesh"
desc = "Grants you the spell Knit Flesh. This spell allows you to remove organs from victims \
without requiring a lengthy surgery. This process is much longer if the target is not dead. \
This spell also allows you to heal your minions and summons, or restore failing organs to acceptable status."
gain_text = "But they were not out of my reach for long. With every step, the screams grew, until at last \
I learned that they could be silenced."
next_knowledge = list(/datum/heretic_knowledge/summon/raw_prophet)
spell_to_add = /datum/action/cooldown/spell/touch/flesh_surgery
cost = 1
route = PATH_FLESH
/datum/heretic_knowledge/summon/raw_prophet
@@ -34,7 +34,7 @@
if(!target_finder)
CRASH("Heretic datum didn't have a hunt_and_sacrifice knowledge learned, what?")
if(!target_finder.obtain_targets(user))
if(!target_finder.obtain_targets(user, heretic_datum = heretic_datum))
loc.balloon_alert(user, "ritual failed, no targets found!")
return FALSE
@@ -12,6 +12,7 @@
*
* Mark of Rust
* Ritual of Knowledge
* Rust Construction
* Aggressive Spread
* > Sidepaths:
* Curse of Corrosion
@@ -39,11 +40,6 @@
result_atoms = list(/obj/item/melee/sickly_blade/rust)
route = PATH_RUST
/datum/heretic_knowledge/limited_amount/starting/base_rust/on_research(mob/user)
. = ..()
var/datum/antagonist/heretic/our_heretic = IS_HERETIC(user)
our_heretic.heretic_path = route
/datum/heretic_knowledge/rust_fist
name = "Grasp of Rust"
desc = "Your Mansus Grasp will deal 500 damage to non-living matter and rust any surface it touches. \
@@ -53,11 +49,11 @@
cost = 1
route = PATH_RUST
/datum/heretic_knowledge/rust_fist/on_gain(mob/user)
/datum/heretic_knowledge/rust_fist/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, PROC_REF(on_mansus_grasp))
RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK_SECONDARY, PROC_REF(on_secondary_mansus_grasp))
/datum/heretic_knowledge/rust_fist/on_lose(mob/user)
/datum/heretic_knowledge/rust_fist/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
UnregisterSignal(user, list(COMSIG_HERETIC_MANSUS_GRASP_ATTACK, COMSIG_HERETIC_MANSUS_GRASP_ATTACK_SECONDARY))
/datum/heretic_knowledge/rust_fist/proc/on_mansus_grasp(mob/living/source, mob/living/target)
@@ -87,11 +83,11 @@
cost = 1
route = PATH_RUST
/datum/heretic_knowledge/rust_regen/on_gain(mob/user)
/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))
/datum/heretic_knowledge/rust_regen/on_lose(mob/user)
/datum/heretic_knowledge/rust_regen/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
UnregisterSignal(user, list(COMSIG_MOVABLE_MOVED, COMSIG_LIVING_LIFE))
/*
@@ -144,7 +140,18 @@
mark_type = /datum/status_effect/eldritch/rust
/datum/heretic_knowledge/knowledge_ritual/rust
next_knowledge = list(/datum/heretic_knowledge/spell/rust_construction)
route = PATH_RUST
/datum/heretic_knowledge/spell/rust_construction
name = "Rust Construction"
desc = "Grants you Rust Construction, a spell that allows you to raise a wall out of a rusted floor. \
Anyone overtop the wall will be throw aside (or upwards) and sustain damage."
gain_text = "Images of foreign and ominous structures began to dance in my mind. Covered head to toe in thick rust, \
they no longer looked man made. Or perhaps they never were in the first place."
next_knowledge = list(/datum/heretic_knowledge/spell/area_conversion)
spell_to_add = /datum/action/cooldown/spell/pointed/rust_construction
cost = 1
route = PATH_RUST
/datum/heretic_knowledge/spell/area_conversion
@@ -221,7 +228,7 @@
TRAIT_NOBREATH,
)
/datum/heretic_knowledge/ultimate/rust_final/on_research(mob/user)
/datum/heretic_knowledge/ultimate/rust_final/on_research(mob/user, datum/antagonist/heretic/our_heretic)
. = ..()
// This map doesn't have a Bridge, for some reason??
// Let them complete the ritual anywhere
@@ -16,6 +16,8 @@
cost = 0
priority = MAX_KNOWLEDGE_PRIORITY // Should be at the top
route = PATH_START
/// How many targets do we generate?
var/num_targets_to_generate = 5
/// Whether we've generated a heretic sacrifice z-level yet, from any heretic.
var/static/heretic_level_generated = FALSE
/// A weakref to the mind of our heretic.
@@ -23,27 +25,29 @@
/// Lazylist of minds that we won't pick as targets.
var/list/datum/mind/target_blacklist
/// An assoc list of [ref] to [timers] - a list of all the timers of people in the shadow realm currently
var/return_timers
var/list/return_timers
/datum/heretic_knowledge/hunt_and_sacrifice/Destroy(force, ...)
heretic_mind = null
LAZYCLEARLIST(target_blacklist)
return ..()
/datum/heretic_knowledge/hunt_and_sacrifice/on_research(mob/user, regained = FALSE)
/datum/heretic_knowledge/hunt_and_sacrifice/on_research(mob/user, datum/antagonist/heretic/our_heretic)
. = ..()
obtain_targets(user, silent = TRUE)
heretic_mind = user.mind
obtain_targets(user, silent = TRUE, heretic_datum = our_heretic)
heretic_mind = our_heretic.owner
if(!heretic_level_generated)
heretic_level_generated = TRUE
message_admins("Generating z-level for heretic sacrifices...")
log_game("Generating z-level for heretic sacrifices...")
INVOKE_ASYNC(src, PROC_REF(generate_heretic_z_level))
/// Generate the sacrifice z-level.
/datum/heretic_knowledge/hunt_and_sacrifice/proc/generate_heretic_z_level()
var/datum/map_template/heretic_sacrifice_level/new_level = new()
if(!new_level.load_new_z())
message_admins("The heretic sacrifice z-level failed to load. Any heretics are gonna have a field day disemboweling people, probably. Up to you if you're fine with it.")
log_game("The heretic sacrifice z-level failed to load.")
message_admins("The heretic sacrifice z-level failed to load. Heretic sacrifices won't be teleported to the shadow realm. \
If you want, you can spawn an /obj/effect/landmark/heretic somewhere to stop that from happening.")
CRASH("Failed to initialize heretic sacrifice z-level!")
/datum/heretic_knowledge/hunt_and_sacrifice/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc)
@@ -79,7 +83,7 @@
/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))
if(obtain_targets(user))
if(obtain_targets(user, heretic_datum = heretic_datum))
return TRUE
else
loc.balloon_alert(user, "ritual failed, no targets found!")
@@ -94,7 +98,7 @@
*
* Returns FALSE if no targets are found, TRUE if the targets list was populated.
*/
/datum/heretic_knowledge/hunt_and_sacrifice/proc/obtain_targets(mob/living/user, silent = FALSE)
/datum/heretic_knowledge/hunt_and_sacrifice/proc/obtain_targets(mob/living/user, silent = FALSE, datum/antagonist/heretic/heretic_datum)
// First construct a list of minds that are valid objective targets.
var/list/datum/mind/valid_targets = list()
@@ -143,18 +147,12 @@
valid_targets -= department_mind
break
// Final target, just get someone random.
final_targets += pick_n_take(valid_targets)
// If any of our targets failed to aquire,
// Let's run a loop until we get four total, grabbing random targets.
// Now grab completely random targets until we'll full
var/target_sanity = 0
while(length(final_targets) < 4 && length(valid_targets) > 4 && target_sanity < 25)
while(length(final_targets) < num_targets_to_generate && length(valid_targets) > num_targets_to_generate && target_sanity < 25)
final_targets += pick_n_take(valid_targets)
target_sanity++
var/datum/antagonist/heretic/heretic_datum = IS_HERETIC(user)
if(!silent)
to_chat(user, span_danger("Your targets have been determined. Your Living Heart will allow you to track their position. Go and sacrifice them!"))
@@ -216,9 +214,9 @@
if(!LAZYLEN(GLOB.heretic_sacrifice_landmarks))
CRASH("[type] - begin_sacrifice was called, but no heretic sacrifice landmarks were found!")
var/obj/effect/landmark/heretic/destination_landmark = GLOB.heretic_sacrifice_landmarks[our_heretic.heretic_path]
var/obj/effect/landmark/heretic/destination_landmark = GLOB.heretic_sacrifice_landmarks[our_heretic.heretic_path] || GLOB.heretic_sacrifice_landmarks[PATH_START]
if(!destination_landmark)
CRASH("[type] - begin_sacrifice could not find a destination landmark to send the sacrifice! (heretic's path: [our_heretic.heretic_path])")
CRASH("[type] - begin_sacrifice could not find a destination landmark OR default landmark to send the sacrifice! (Heretic's path: [our_heretic.heretic_path])")
var/turf/destination = get_turf(destination_landmark)
@@ -241,6 +239,7 @@
// If our target is dead, try to revive them
// and if we fail to revive them, don't proceede the chain
sac_target.adjustOxyLoss(-100, FALSE)
if(!sac_target.heal_and_revive(50, span_danger("[sac_target]'s heart begins to beat with an unholy force as they return from death!")))
return
@@ -283,7 +282,8 @@
// If our target died during the (short) wait timer,
// and we fail to revive them (using a lower number than before),
// just disembowel them and stop the chain
if(!sac_target.heal_and_revive(75, span_danger("[sac_target]'s heart begins to beat with an unholy force as they return from death!")))
sac_target.adjustOxyLoss(-100, FALSE)
if(!sac_target.heal_and_revive(60, span_danger("[sac_target]'s heart begins to beat with an unholy force as they return from death!")))
disembowel_target(sac_target)
return
@@ -368,6 +368,7 @@
sac_target.remove_status_effect(/datum/status_effect/unholy_determination)
sac_target.reagents?.del_reagent(/datum/reagent/inverse/helgrasp/heretic)
sac_target.clear_mood_event("shadow_realm")
sac_target.gain_trauma(/datum/brain_trauma/mild/phobia/supernatural, TRAUMA_RESILIENCE_MAGIC)
// Wherever we end up, we sure as hell won't be able to explain
sac_target.adjust_timed_status_effect(40 SECONDS, /datum/status_effect/speech/slurring/heretic)
@@ -51,10 +51,9 @@ GLOBAL_LIST_INIT(heretic_start_knowledge, initialize_starting_knowledge())
/// The typepath of the organ type required for our heart.
var/required_organ_type = /obj/item/organ/internal/heart
/datum/heretic_knowledge/living_heart/on_research(mob/user)
/datum/heretic_knowledge/living_heart/on_research(mob/user, datum/antagonist/heretic/our_heretic)
. = ..()
var/datum/antagonist/heretic/our_heretic = IS_HERETIC(user)
var/obj/item/organ/where_to_put_our_heart = user.getorganslot(our_heretic.living_heart_organ_slot)
// Our heart slot is not valid to put a heart
if(!is_valid_heart(where_to_put_our_heart))
@@ -93,8 +92,7 @@ GLOBAL_LIST_INIT(heretic_start_knowledge, initialize_starting_knowledge())
else
to_chat(user, span_boldnotice("You don't have a heart, or any chest organs for that matter. You didn't get a Living Heart because of it."))
/datum/heretic_knowledge/living_heart/on_lose(mob/user)
var/datum/antagonist/heretic/our_heretic = IS_HERETIC(user)
/datum/heretic_knowledge/living_heart/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
var/obj/item/organ/our_living_heart = user.getorganslot(our_heretic.living_heart_organ_slot)
if(our_living_heart)
qdel(our_living_heart.GetComponent(/datum/component/living_heart))
@@ -12,6 +12,7 @@
*
* Mark of Void
* Ritual of Knowledge
* Cone of Cold
* Void Phase
* > Sidepaths:
* Carving Knife
@@ -37,11 +38,6 @@
result_atoms = list(/obj/item/melee/sickly_blade/void)
route = PATH_VOID
/datum/heretic_knowledge/limited_amount/starting/base_void/on_research(mob/user)
. = ..()
var/datum/antagonist/heretic/our_heretic = IS_HERETIC(user)
our_heretic.heretic_path = route
/datum/heretic_knowledge/limited_amount/starting/base_void/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc)
if(!isopenturf(loc))
loc.balloon_alert(user, "ritual failed, invalid location!")
@@ -63,10 +59,10 @@
cost = 1
route = PATH_VOID
/datum/heretic_knowledge/void_grasp/on_gain(mob/user)
/datum/heretic_knowledge/void_grasp/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, PROC_REF(on_mansus_grasp))
/datum/heretic_knowledge/void_grasp/on_lose(mob/user)
/datum/heretic_knowledge/void_grasp/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
UnregisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK)
/datum/heretic_knowledge/void_grasp/proc/on_mansus_grasp(mob/living/source, mob/living/target)
@@ -94,11 +90,11 @@
cost = 1
route = PATH_VOID
/datum/heretic_knowledge/cold_snap/on_gain(mob/user)
/datum/heretic_knowledge/cold_snap/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
ADD_TRAIT(user, TRAIT_RESISTCOLD, type)
ADD_TRAIT(user, TRAIT_NOBREATH, type)
/datum/heretic_knowledge/cold_snap/on_lose(mob/user)
/datum/heretic_knowledge/cold_snap/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
REMOVE_TRAIT(user, TRAIT_RESISTCOLD, type)
REMOVE_TRAIT(user, TRAIT_NOBREATH, type)
@@ -113,15 +109,26 @@
mark_type = /datum/status_effect/eldritch/void
/datum/heretic_knowledge/knowledge_ritual/void
next_knowledge = list(/datum/heretic_knowledge/spell/void_cone)
route = PATH_VOID
/datum/heretic_knowledge/spell/void_cone
name = "Void Blast"
desc = "Grants you Void Blast, a spell that shoots out a freezing blast in a code ahead of you, \
freezing the ground and any victims within."
gain_text = "Every door I open racks my body. I am afraid of what is behind them. Someone is expecting me, \
and my legs start to drag. Is that... snow?"
next_knowledge = list(/datum/heretic_knowledge/spell/void_phase)
spell_to_add = /datum/action/cooldown/spell/cone/staggered/cone_of_cold/void
cost = 1
route = PATH_VOID
/datum/heretic_knowledge/spell/void_phase
name = "Void Phase"
desc = "Grants you Void Phase, a long range targeted teleport spell. \
Additionally causes damage to heathens around your original and target destination."
gain_text = "The entity calls themself the Aristocrat. They effortlessly walk through air like\
nothing leaving a harsh, cold breeze in their wake. They disappear, and I am left in the snow."
gain_text = "The entity calls themself the Aristocrat. They effortlessly walk through air like \
nothing - leaving a harsh, cold breeze in their wake. They disappear, and I am left in the blizzard."
next_knowledge = list(
/datum/heretic_knowledge/blade_upgrade/void,
/datum/heretic_knowledge/reroll_targets,
@@ -204,7 +211,7 @@
RegisterSignal(user, COMSIG_LIVING_LIFE, PROC_REF(on_life))
RegisterSignal(user, COMSIG_LIVING_DEATH, PROC_REF(on_death))
/datum/heretic_knowledge/ultimate/void_final/on_lose(mob/user)
/datum/heretic_knowledge/ultimate/void_final/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
on_death() // Losing is pretty much dying. I think
RegisterSignal(user, list(COMSIG_LIVING_LIFE, COMSIG_LIVING_DEATH))
@@ -243,7 +250,7 @@
*
* Stop the storm when the heretic passes away.
*/
/datum/heretic_knowledge/ultimate/void_final/proc/on_death()
/datum/heretic_knowledge/ultimate/void_final/proc/on_death(datum/source)
SIGNAL_HANDLER
if(sound_loop)
@@ -7,15 +7,18 @@
ranged_mousepointer = 'icons/effects/mouse_pointers/throw_target.dmi'
school = SCHOOL_FORBIDDEN
cooldown_time = 35 SECONDS
cooldown_time = 40 SECONDS
invocation = "CL'VE"
invocation = "CL'VE!"
invocation_type = INVOCATION_WHISPER
spell_requirements = NONE
cast_range = 9
/// The radius of the cleave effect
var/cleave_radius = 1
/// What type of wound we apply
var/wound_type = /datum/wound/slash/critical
/datum/action/cooldown/spell/pointed/cleave/is_valid_target(atom/cast_on)
return ..() && ishuman(cast_on)
@@ -27,7 +30,7 @@
nearby += nearby_human
for(var/mob/living/carbon/human/victim as anything in nearby)
if(victim == owner)
if(victim == owner || IS_HERETIC_OR_MONSTER(victim))
continue
if(victim.can_block_magic())
victim.visible_message(
@@ -45,17 +48,18 @@
)
var/obj/item/bodypart/bodypart = pick(victim.bodyparts)
var/datum/wound/slash/critical/crit_wound = new()
var/datum/wound/slash/crit_wound = new wound_type()
crit_wound.apply_wound(bodypart)
victim.apply_damage(20, BURN, wound_bonus = CANT_WOUND)
new /obj/effect/temp_visual/cleave(victim.drop_location())
new /obj/effect/temp_visual/cleave(get_turf(victim))
return TRUE
/datum/action/cooldown/spell/pointed/cleave/long
name = "Lesser Cleave"
cooldown_time = 65 SECONDS
cooldown_time = 60 SECONDS
wound_type = /datum/wound/slash/severe
/obj/effect/temp_visual/cleave
icon = 'icons/effects/eldritch.dmi'
@@ -10,7 +10,7 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 15 SECONDS
invocation = "FL'MS O'ET'RN'ITY"
invocation = "FL'MS O'ET'RN'ITY."
invocation_type = INVOCATION_WHISPER
spell_requirements = NONE
@@ -0,0 +1,178 @@
/datum/action/cooldown/spell/charged/beam/fire_blast
name = "Volcano Blast"
desc = "Charge up a blast of fire that chains between nearby targets, setting them ablaze. \
Targets already on fire will take priority. If the target fails to catch ablaze, or \
extinguishes themselves before it bounces, the chain will stop."
background_icon_state = "bg_ecult"
icon_icon = 'icons/mob/actions/actions_ecult.dmi'
button_icon_state = "flames"
sound = 'sound/magic/fireball.ogg'
school = SCHOOL_FORBIDDEN
cooldown_time = 45 SECONDS
invocation = "V'LC'N!"
invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
channel_time = 5 SECONDS
target_radius = 5
max_beam_bounces = 4
/// How long the beam visual lasts, also used to determine time between jumps
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
// 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 ..()
/datum/action/cooldown/spell/charged/beam/fire_blast/send_beam(atom/origin, mob/living/carbon/to_beam, bounces = 4)
// Send a beam from the origin to the hit mob
origin.Beam(to_beam, icon_state = "solar_beam", time = beam_duration, beam_type = /obj/effect/ebeam/fire)
// If they block the magic, the chain wont necessarily stop,
// but likely will (due to them not catching on fire)
if(to_beam.can_block_magic(antimagic_flags))
to_beam.visible_message(
span_warning("[to_beam] absorbs the spell, remaining unharmed!"),
span_userdanger("You absorb the spell, remaining unharmed!"),
)
// Apply status effect but with no overlay
to_beam.apply_status_effect(/datum/status_effect/fire_blasted)
// Otherwise, if unblocked apply the damage and set them up
else
to_beam.apply_damage(20, BURN, wound_bonus = 5)
to_beam.adjust_fire_stacks(3)
to_beam.ignite_mob()
// Apply the fire blast status effect to show they got blasted
to_beam.apply_status_effect(/datum/status_effect/fire_blasted, beam_duration * 0.5)
// We can keep bouncing, try to continue the chain
if(bounces >= 1)
playsound(to_beam, sound, 50, vary = TRUE, extrarange = -1)
// Chain continues shortly after. If they extinguish themselves in this time, the chain will stop anyways.
addtimer(CALLBACK(src, PROC_REF(continue_beam), to_beam, bounces), beam_duration * 0.5)
else
playsound(to_beam, sound, 50, vary = TRUE, frequency = 12000)
// We hit the maximum chain length, apply a bonus for managing it
new /obj/effect/temp_visual/fire_blast_bonus(to_beam.loc)
for(var/mob/living/nearby_living in range(1, to_beam))
if(IS_HERETIC_OR_MONSTER(nearby_living) || nearby_living == owner)
continue
nearby_living.Knockdown(0.8 SECONDS)
nearby_living.apply_damage(15, BURN, wound_bonus = 5)
nearby_living.adjust_fire_stacks(2)
nearby_living.ignite_mob()
/// Timer callback to continue the chain, calling send_fire_bream recursively.
/datum/action/cooldown/spell/charged/beam/fire_blast/proc/continue_beam(mob/living/carbon/beamed, bounces)
// We will only continue the chain if we exist, are still on fire, and still have the status effect
if(QDELETED(beamed) || !beamed.on_fire || !beamed.has_status_effect(/datum/status_effect/fire_blasted))
return
// We fulfilled the conditions, get the next target
var/mob/living/carbon/to_beam_next = get_target(beamed)
if(isnull(to_beam_next)) // No target = no chain
return
// Chain again! Recursively
send_beam(beamed, to_beam_next, bounces - 1)
/// Pick a carbon mob in a radius around us that we can reach.
/// Mobs on fire will have priority and be targeted over others.
/// Returns null or a carbon mob.
/datum/action/cooldown/spell/charged/beam/fire_blast/get_target(atom/center)
var/list/possibles = list()
var/list/priority_possibles = list()
for(var/mob/living/carbon/to_check in view(target_radius, center))
if(to_check == center || to_check == owner)
continue
if(to_check.has_status_effect(/datum/status_effect/fire_blasted)) // Already blasted
continue
if(IS_HERETIC_OR_MONSTER(to_check))
continue
if(!length(get_path_to(center, to_check, max_distance = target_radius, simulated_only = FALSE)))
continue
possibles += to_check
if(to_check.on_fire && to_check.stat != DEAD)
priority_possibles += to_check
if(!length(possibles))
return null
return length(priority_possibles) ? pick(priority_possibles) : pick(possibles)
/**
* Status effect applied when someone's hit by the fire blast.
*
* Applies an overlay, then causes a damage over time (or heal over time)
*/
/datum/status_effect/fire_blasted
id = "fire_blasted"
alert_type = null
duration = 5 SECONDS
tick_interval = 0.5 SECONDS
/// How much fire / stam to do per tick (stamina damage is doubled this)
var/tick_damage = 1
/// How long does the animation of the appearance last? If 0 or negative, we make no overlay
var/animate_duration = 0.75 SECONDS
/datum/status_effect/fire_blasted/on_creation(mob/living/new_owner, animate_duration = -1, tick_damage = 1)
src.animate_duration = animate_duration
src.tick_damage = tick_damage
return ..()
/datum/status_effect/fire_blasted/on_apply()
if(owner.on_fire && animate_duration > 0 SECONDS)
var/image/warning_sign = image(icon = 'icons/effects/effects.dmi', icon_state = "blessed", layer = BELOW_MOB_LAYER, loc = owner)
owner.flick_overlay_view(warning_sign, initial(duration))
warning_sign.alpha = 50
animate(warning_sign, alpha = 255, time = animate_duration)
return TRUE
/datum/status_effect/fire_blasted/tick(delta_time, times_fired)
owner.adjustFireLoss(tick_damage)
owner.adjustStaminaLoss(2 * tick_damage)
// The beam fireblast spits out, causes people to walk through it to be on fire
/obj/effect/ebeam/fire
name = "fire beam"
/obj/effect/ebeam/fire/Initialize(mapload)
. = ..()
var/static/list/loc_connections = list(
COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
)
AddElement(/datum/element/connect_loc, loc_connections)
if(!isturf(loc) || mapload) // idk if this would ever be maploaded but you never know
return
for(var/mob/living/living_mob in loc)
on_entered(entered = living_mob)
/obj/effect/ebeam/fire/proc/on_entered(datum/source, atom/movable/entered)
SIGNAL_HANDLER
if(!isliving(entered))
return
var/mob/living/living_entered = entered
if(IS_HERETIC_OR_MONSTER(living_entered) || living_entered.has_status_effect(/datum/status_effect/fire_blasted))
return
living_entered.apply_damage(10, BURN, wound_bonus = 5)
living_entered.adjust_fire_stacks(2)
living_entered.ignite_mob()
// Apply the fireblasted effect - no overlay
living_entered.apply_status_effect(/datum/status_effect/fire_blasted)
// Visual effect played when we hit the max bounces
/obj/effect/temp_visual/fire_blast_bonus
name = "fire blast"
icon = 'icons/effects/effects.dmi'
icon_state = "explosion"
duration = 1 SECONDS
@@ -0,0 +1,232 @@
/datum/action/cooldown/spell/touch/flesh_surgery
name = "Knit Flesh"
desc = "A touch spell that allows you to either harvest or restore flesh of target. \
Left-clicking will extract the organs of a victim without needing to complete surgery or disembowel. \
Right-clicking, if done on summons or minions, will restore health. Can also be used to heal damaged organs."
background_icon_state = "bg_ecult"
icon_icon = 'icons/mob/actions/actions_ecult.dmi'
button_icon_state = "mad_touch"
sound = null
school = SCHOOL_FORBIDDEN
cooldown_time = 20 SECONDS
invocation = "CL'M M'N!" // "CLAIM MINE", but also almost "KALI MA"
invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
hand_path = /obj/item/melee/touch_attack/flesh_surgery
can_cast_on_self = TRUE
/// If used on an organ, how much percent of the organ's HP do we restore
var/organ_percent_healing = 0.5
/// If used on a heretic mob, how much brute do we heal
var/monster_brute_healing = 10
/// If used on a heretic mob, how much burn do we heal
var/monster_burn_healing = 5
/datum/action/cooldown/spell/touch/flesh_surgery/is_valid_target(atom/cast_on)
return isliving(cast_on) || isorgan(cast_on)
/datum/action/cooldown/spell/touch/flesh_surgery/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster)
if(isorgan(victim))
return heal_organ(hand, victim, caster)
if(isliving(victim))
return steal_organ_from_mob(hand, victim, caster)
return FALSE
/datum/action/cooldown/spell/touch/flesh_surgery/cast_on_secondary_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster)
if(isorgan(victim))
return SECONDARY_ATTACK_CALL_NORMAL
if(isliving(victim))
var/mob/living/mob_victim = victim
if(mob_victim.stat == DEAD || !IS_HERETIC_MONSTER(mob_victim))
return SECONDARY_ATTACK_CALL_NORMAL
if(heal_heretic_monster(hand, mob_victim, caster))
return SECONDARY_ATTACK_CONTINUE_CHAIN
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
/datum/action/cooldown/spell/touch/flesh_surgery/register_hand_signals()
. = ..()
RegisterSignal(attached_hand, COMSIG_ITEM_REQUESTING_CONTEXT_FOR_TARGET, PROC_REF(add_item_context))
attached_hand.item_flags |= ITEM_HAS_CONTEXTUAL_SCREENTIPS
/datum/action/cooldown/spell/touch/flesh_surgery/unregister_hand_signals()
. = ..()
UnregisterSignal(attached_hand, COMSIG_ITEM_REQUESTING_CONTEXT_FOR_TARGET)
/// Signal proc for [COMSIG_ITEM_REQUESTING_CONTEXT_FOR_TARGET] to add some context to the hand.
/datum/action/cooldown/spell/touch/flesh_surgery/proc/add_item_context(obj/item/melee/touch_attack/source, list/context, atom/victim, mob/living/user)
SIGNAL_HANDLER
. = NONE
if(isliving(victim))
var/mob/living/mob_victim = victim
if(iscarbon(mob_victim))
context[SCREENTIP_CONTEXT_LMB] = "Extract organ"
. = CONTEXTUAL_SCREENTIP_SET
if(IS_HERETIC_MONSTER(mob_victim))
context[SCREENTIP_CONTEXT_RMB] = "Heal [ishuman(mob_victim) ? "minion" : "summon"]"
. = CONTEXTUAL_SCREENTIP_SET
else if(isorgan(victim))
context[SCREENTIP_CONTEXT_LMB] = "Heal organ"
. = CONTEXTUAL_SCREENTIP_SET
return .
/// If cast on an organ, we'll restore it's health and even un-fail it.
/datum/action/cooldown/spell/touch/flesh_surgery/proc/heal_organ(obj/item/melee/touch_attack/hand, obj/item/organ/to_heal, mob/living/carbon/caster)
to_heal.balloon_alert(caster, "healing organ...")
if(!do_after(caster, 1 SECONDS, to_heal, extra_checks = CALLBACK(src, PROC_REF(heal_checks), hand, to_heal, caster)))
to_heal.balloon_alert(caster, "interrupted!")
return FALSE
var/organ_hp_to_heal = to_heal.maxHealth * organ_percent_healing
if(to_heal.damage < organ_hp_to_heal)
to_heal.setOrganDamage(organ_hp_to_heal)
to_heal.balloon_alert(caster, "organ healed")
playsound(to_heal, 'sound/magic/staff_healing.ogg', 30)
new /obj/effect/temp_visual/cult/sparks(get_turf(to_heal))
caster.visible_message(
span_warning("[caster]'s hand glows a brilliant red as [caster.p_they()] restore \the [to_heal] to good condition!"),
span_notice("Your hand glows a brilliant red as you restore \the [to_heal] to good condition!"),
)
else
to_heal.balloon_alert(caster, "already in good condition!")
return TRUE
/// If cast on a heretic monster who's not dead we'll heal it a bit.
/datum/action/cooldown/spell/touch/flesh_surgery/proc/heal_heretic_monster(obj/item/melee/touch_attack/hand, mob/living/to_heal, mob/living/carbon/caster)
var/what_are_we = ishuman(to_heal) ? "minion" : "summon"
to_heal.balloon_alert(caster, "healing [what_are_we]...")
if(!do_after(caster, 1 SECONDS, to_heal, extra_checks = CALLBACK(src, PROC_REF(heal_checks), hand, to_heal, caster)))
to_heal.balloon_alert(caster, "interrupted!")
return FALSE
// Keep in mind that, for simplemobs(summons), this will just flat heal the combined value of both brute and burn healing,
// while for human minions(ghouls), this will heal brute and burn like normal. So be careful adjusting to bigger numbers
to_heal.balloon_alert(caster, "[what_are_we] healed")
to_heal.heal_overall_damage(monster_brute_healing, monster_burn_healing)
playsound(to_heal, 'sound/magic/staff_healing.ogg', 30)
new /obj/effect/temp_visual/cult/sparks(get_turf(to_heal))
caster.visible_message(
span_warning("[caster]'s hand glows a brilliant red as [caster.p_they()] restore [to_heal] to good condition!"),
span_notice("Your hand glows a brilliant red as you restore [to_heal] to good condition!"),
)
return TRUE
/// If cast on a carbon, we'll try to steal one of their organs directly from their person.
/datum/action/cooldown/spell/touch/flesh_surgery/proc/steal_organ_from_mob(obj/item/melee/touch_attack/hand, mob/living/victim, mob/living/carbon/caster)
var/mob/living/carbon/carbon_victim = victim
if(!istype(carbon_victim) || !length(carbon_victim.internal_organs))
victim.balloon_alert(caster, "no organs!")
return FALSE
// Round u pto the nearest generic zone (body, chest, arm)
var/zone_to_check = check_zone(caster.zone_selected)
var/parsed_zone = parse_zone(zone_to_check)
var/list/organs_we_can_remove = list()
for(var/obj/item/organ/organ as anything in carbon_victim.internal_organs)
// Only show organs which are in our generic zone
if(deprecise_zone(organ.zone) != zone_to_check)
continue
// Also, some organs to exclude. Don't remove vital (brains), don't remove synthetics, and don't remove unremovable
if(organ.organ_flags & (ORGAN_SYNTHETIC|ORGAN_VITAL|ORGAN_UNREMOVABLE))
continue
organs_we_can_remove[organ.name] = organ
if(!length(organs_we_can_remove))
victim.balloon_alert(caster, "no organs there!")
return FALSE
var/chosen_organ = tgui_input_list(caster, "Which organ do you want to extract?", name, sort_list(organs_we_can_remove))
if(isnull(chosen_organ))
return FALSE
var/obj/item/organ/picked_organ = organs_we_can_remove[chosen_organ]
if(!istype(picked_organ) || !extraction_checks(picked_organ, hand, victim, caster))
return FALSE
// Don't let people stam crit into steal heart true combo
var/time_it_takes = carbon_victim.stat == DEAD ? 3 SECONDS : 15 SECONDS
// Sure you can remove your own organs, fun party trick
if(carbon_victim == caster)
var/are_you_sure = tgui_alert(caster, "Are you sure you want to remove your own [chosen_organ]?", "Are you sure?", list("Yes", "No"))
if(are_you_sure != "Yes" || !extraction_checks(picked_organ, hand, victim, caster))
return FALSE
time_it_takes = 6 SECONDS
caster.visible_message(
span_danger("[caster]'s hand glows a brilliant red as [caster.p_they()] reach directly into [caster.p_their()] own [parsed_zone]!"),
span_userdanger("Your hand glows a brilliant red as you reach directly into your own [parsed_zone]!"),
)
else
carbon_victim.visible_message(
span_danger("[caster]'s hand glows a brilliant red as [caster.p_they()] reach directly into [carbon_victim]'s [parsed_zone]!"),
span_userdanger("[caster]'s hand glows a brilliant red as [caster.p_they()] reach directly into your [parsed_zone]!"),
)
carbon_victim.balloon_alert(caster, "extracting [chosen_organ]...")
playsound(victim, 'sound/weapons/slice.ogg', 50, TRUE)
carbon_victim.add_atom_colour(COLOR_DARK_RED, TEMPORARY_COLOUR_PRIORITY)
if(!do_after(caster, time_it_takes, carbon_victim, extra_checks = CALLBACK(src, PROC_REF(extraction_checks), picked_organ, hand, victim, caster)))
carbon_victim.balloon_alert(caster, "interrupted!")
return FALSE
// Visible message done before Remove()
// Mainly so it gets across if you're taking the eyes of someone who's conscious
if(carbon_victim == caster)
caster.visible_message(
span_bolddanger("[caster] pulls [caster.p_their()] own [chosen_organ] out of [caster.p_their()] [parsed_zone]!!"),
span_userdanger("You pull your own [chosen_organ] out of your [parsed_zone]!!"),
)
else
carbon_victim.visible_message(
span_bolddanger("[caster]'s pulls [carbon_victim]'s [chosen_organ] out of [carbon_victim.p_their()] [parsed_zone]!!"),
span_userdanger("[caster]'s pulls your [chosen_organ] out of [carbon_victim.p_their()] [parsed_zone]!!"),
)
picked_organ.Remove(carbon_victim)
carbon_victim.balloon_alert(caster, "[chosen_organ] removed")
carbon_victim.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, COLOR_DARK_RED)
playsound(victim, 'sound/effects/dismember.ogg', 50, TRUE)
if(carbon_victim.stat == CONSCIOUS)
carbon_victim.adjust_timed_status_effect(15 SECONDS, /datum/status_effect/speech/slurring/heretic)
carbon_victim.emote("scream")
// We need to wait for the spell to actually finish casting to put the organ in their hands, hence, 1 ms timer.
addtimer(CALLBACK(caster, TYPE_PROC_REF(/mob, put_in_hands), picked_organ), 1)
return TRUE
/// Extra checks ran while we're extracting an organ to make sure we can continue to do.
/datum/action/cooldown/spell/touch/flesh_surgery/proc/extraction_checks(obj/item/organ/picked_organ, obj/item/melee/touch_attack/hand, mob/living/carbon/victim, mob/living/carbon/caster)
if(QDELETED(src) || QDELETED(hand) || QDELETED(picked_organ) || QDELETED(victim) || !IsAvailable())
return FALSE
return TRUE
/// Extra checks ran while we're healing something (organ, mob).
/datum/action/cooldown/spell/touch/flesh_surgery/proc/heal_checks(obj/item/melee/touch_attack/hand, atom/healing, mob/living/carbon/caster)
if(QDELETED(src) || QDELETED(hand) ||QDELETED(healing) || !IsAvailable())
return FALSE
return TRUE
/obj/item/melee/touch_attack/flesh_surgery
name = "\improper knit flesh"
desc = "Let's go practice medicine."
icon_state = "disintegrate"
inhand_icon_state = "disintegrate"
@@ -12,23 +12,21 @@
spell_requirements = NONE
antimagic_flags = MAGIC_RESISTANCE|MAGIC_RESISTANCE_MIND
/datum/action/cooldown/spell/touch/mad_touch/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster)
if(!ishuman(victim))
/datum/action/cooldown/spell/touch/mad_touch/is_valid_target(atom/cast_on)
if(!ishuman(cast_on))
return FALSE
var/mob/living/carbon/human/human_victim = victim
if(!human_victim.mind || IS_HERETIC(human_victim))
var/mob/living/carbon/human/human_cast_on = cast_on
if(!human_cast_on.mind || !human_cast_on.mob_mood || IS_HERETIC_OR_MONSTER(human_cast_on))
return FALSE
if(human_victim.can_block_magic(antimagic_flags))
victim.visible_message(
span_danger("The spell bounces off of [victim]!"),
span_danger("The spell bounces off of you!"),
)
return FALSE
to_chat(caster, span_warning("[human_victim.name] has been cursed!"))
if (isliving(target))
var/mob/living/target_mob = target
target_mob.add_mood_event("gates_of_mansus", /datum/mood_event/gates_of_mansus)
return TRUE
/datum/action/cooldown/spell/touch/mad_touch/on_antimagic_triggered(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster)
victim.visible_message(
span_danger("The spell bounces off of [victim]!"),
span_danger("The spell bounces off of you!"),
)
/datum/action/cooldown/spell/touch/mad_touch/cast_on_hand_hit(obj/item/melee/touch_attack/hand, mob/living/carbon/human/victim, mob/living/carbon/caster)
to_chat(caster, span_warning("[victim.name] has been cursed!"))
victim.add_mood_event("gates_of_mansus", /datum/mood_event/gates_of_mansus)
return TRUE
@@ -13,6 +13,7 @@
invocation = "PI'RC' TH' M'ND."
invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
antimagic_flags = MAGIC_RESISTANCE|MAGIC_RESISTANCE_MIND
cast_range = 7
@@ -16,24 +16,26 @@
hand_path = /obj/item/melee/touch_attack/mansus_fist
/datum/action/cooldown/spell/touch/mansus_grasp/is_valid_target(atom/cast_on)
return TRUE // This baby can hit anything
/datum/action/cooldown/spell/touch/mansus_grasp/can_cast_spell(feedback = TRUE)
return ..() && !!IS_HERETIC(owner)
/datum/action/cooldown/spell/touch/mansus_grasp/on_antimagic_triggered(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster)
victim.visible_message(
span_danger("The spell bounces off of [victim]!"),
span_danger("The spell bounces off of you!"),
)
/datum/action/cooldown/spell/touch/mansus_grasp/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster)
if(!isliving(victim))
return FALSE
var/mob/living/living_hit = victim
if(living_hit.can_block_magic(antimagic_flags))
victim.visible_message(
span_danger("The spell bounces off of [victim]!"),
span_danger("The spell bounces off of you!"),
)
return FALSE
if(SEND_SIGNAL(caster, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, victim) & COMPONENT_BLOCK_HAND_USE)
return FALSE
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
@@ -84,9 +86,12 @@
/obj/item/melee/touch_attack/mansus_fist/suicide_act(mob/living/user)
user.visible_message(span_suicide("[user] covers [user.p_their()] face with [user.p_their()] sickly-looking hand! It looks like [user.p_theyre()] trying to commit suicide!"))
var/mob/living/carbon/carbon_user = user //iscarbon already used in spell's parent
var/datum/action/cooldown/spell/touch/mansus_grasp/source = locate() in user.actions
if(!IS_HERETIC(user))
return
var/datum/action/cooldown/spell/touch/mansus_grasp/source = spell_which_made_us?.resolve()
if(QDELETED(source) || !IS_HERETIC(user))
return SHAME
if(user.can_block_magic(source.antimagic_flags))
return SHAME
var/escape_our_torment = 0
while(carbon_user.stat == CONSCIOUS)
@@ -102,7 +107,7 @@
carbon_user.emote("scream")
carbon_user.adjust_stutter(26 SECONDS)
source?.cast_on_hand_hit(src, user, user)
source.cast_on_hand_hit(src, user, user)
escape_our_torment++
stoplag(0.4 SECONDS)
@@ -35,11 +35,12 @@
return things
/datum/action/cooldown/spell/aoe/fiery_rebirth/cast_on_thing_in_aoe(mob/living/carbon/victim, mob/living/carbon/human/caster)
new /obj/effect/temp_visual/eldritch_smoke(victim.drop_location())
new /obj/effect/temp_visual/eldritch_smoke(get_turf(victim))
victim.Beam(caster, icon_state = "r_beam", time = 2 SECONDS)
//This is essentially a death mark, use this to finish your opponent quicker.
if(HAS_TRAIT(victim, TRAIT_CRITICAL_CONDITION) && !HAS_TRAIT(victim, TRAIT_NODEATH))
victim.investigate_log("has been killed by fiery rebirth.", INVESTIGATE_DEATHS)
if(CAN_SUCCUMB(victim))
victim.investigate_log("has been executed by fiery rebirth.", INVESTIGATE_DEATHS)
victim.death()
victim.apply_damage(20, BURN)
@@ -0,0 +1,80 @@
// Realignment. It's like Fleshmend but solely for stamina damage and stuns. Sec meta
/datum/action/cooldown/spell/realignment
name = "Realignment"
desc = "Realign yourself, rapidly regenerating stamina and reducing any stuns or knockdowns. \
You cannot attack while realigning. Can be casted multiple times in short succession, but each cast lengthens the cooldown."
background_icon_state = "bg_ecult"
icon_icon = 'icons/obj/implants.dmi'
button_icon_state = "adrenal"
// sound = 'sound/magic/whistlereset.ogg'
school = SCHOOL_FORBIDDEN
cooldown_time = 6 SECONDS
cooldown_reduction_per_rank = -6 SECONDS // we're not a wizard spell but we use the levelling mechanic
spell_max_level = 10 // we can get up to / over a minute duration cd time
invocation = "R'S'T."
invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
/datum/action/cooldown/spell/realignment/is_valid_target(atom/cast_on)
return isliving(cast_on)
/datum/action/cooldown/spell/realignment/cast(mob/living/cast_on)
. = ..()
cast_on.apply_status_effect(/datum/status_effect/realignment)
to_chat(cast_on, span_notice("We begin to realign ourselves."))
/datum/action/cooldown/spell/realignment/after_cast(atom/cast_on)
. = ..()
// With every cast, our spell level increases for a short time, which goes back down after a period
// and with every spell level, the cooldown duration of the spell goes up
if(level_spell())
var/reduction_timer = max(cooldown_time * spell_max_level * 0.5, 1.5 MINUTES)
addtimer(CALLBACK(src, PROC_REF(delevel_spell)), reduction_timer)
/datum/action/cooldown/spell/realignment/update_spell_name()
var/spell_title = ""
switch(spell_level)
if(1, 2)
spell_title = "Hasty " // Hasty Realignment
if(3, 4)
spell_title = "" // Realignment
if(5, 6, 7)
spell_title = "Slowed " // Slowed Realignment
if(8, 9, 10)
spell_title = "Laborious " // Laborious Realignment (don't reach here)
name = "[spell_title][initial(name)]"
UpdateButtons()
/datum/status_effect/realignment
id = "realigment"
status_type = STATUS_EFFECT_REFRESH
duration = 8 SECONDS
alert_type = /atom/movable/screen/alert/status_effect/realignment
tick_interval = 0.2 SECONDS
/datum/status_effect/realignment/get_examine_text()
return span_notice("[owner.p_theyre(TRUE)] glowing a soft white.")
/datum/status_effect/realignment/on_apply()
ADD_TRAIT(owner, TRAIT_PACIFISM, id)
owner.add_filter(id, 2, list("type" = "outline", "color" = "#d6e3e7", "size" = 2))
var/filter = owner.get_filter(id)
animate(filter, alpha = 127, time = 1 SECONDS, loop = -1)
animate(alpha = 63, time = 2 SECONDS)
return TRUE
/datum/status_effect/realignment/on_remove()
REMOVE_TRAIT(owner, TRAIT_PACIFISM, id)
owner.remove_filter(id)
/datum/status_effect/realignment/tick(delta_time, times_fired)
owner.adjustStaminaLoss(-5)
owner.AdjustAllImmobility(-0.5 SECONDS)
/atom/movable/screen/alert/status_effect/realignment
name = "Realignment"
desc = "You're realignment yourself. You cannot attack, but are rapidly regenerating stamina."
icon_state = "realignment"
@@ -0,0 +1,117 @@
/datum/action/cooldown/spell/pointed/rust_construction
name = "Rust Formation"
desc = "Transforms a rusted floor into a full wall of rust. Creating a wall underneath a mob will harm it."
background_icon_state = "bg_ecult"
icon_icon = 'icons/mob/actions/actions_ecult.dmi'
button_icon_state = "cleave"
ranged_mousepointer = 'icons/effects/mouse_pointers/throw_target.dmi'
check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED
school = SCHOOL_FORBIDDEN
cooldown_time = 5 SECONDS
invocation = "Someone raises a wall of rust."
invocation_self_message = "You raise a wall of rust."
invocation_type = INVOCATION_EMOTE
spell_requirements = NONE
cast_range = 4
aim_assist = FALSE
/// How long does the filter last on walls we make?
var/filter_duration = 2 MINUTES
/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!")
return FALSE
if(!HAS_TRAIT(cast_on, TRAIT_RUSTY))
if(owner)
cast_on.balloon_alert(owner, "not rusted!")
return FALSE
return TRUE
/datum/action/cooldown/spell/pointed/rust_construction/before_cast(turf/open/cast_on)
. = ..()
if(!isliving(owner))
return
var/mob/living/living_owner = owner
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)
. = ..()
var/rises_message = "rises out of [cast_on]"
var/turf/closed/wall/new_wall = cast_on.PlaceOnTop(/turf/closed/wall)
if(!istype(new_wall))
return
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.sheet_amount = 0
new_wall.girder_type = null
// I wanted to do a cool animation of a wall raising from the ground
// 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)
var/message_shown = FALSE
for(var/mob/living/living_mob in cast_on)
message_shown = TRUE
if(IS_HERETIC_OR_MONSTER(living_mob) || living_mob == owner)
living_mob.visible_message(
span_warning("\A [new_wall] [rises_message] and pushes along [living_mob]!"),
span_notice("\A [new_wall] [rises_message] beneath your feet and pushes you along!"),
)
else
living_mob.visible_message(
span_warning("\A [new_wall] [rises_message] and slams into [living_mob]!"),
span_userdanger("\A [new_wall] [rises_message] beneath your feet and slams into you!"),
)
living_mob.apply_damage(10, BRUTE, wound_bonus = 10)
living_mob.Knockdown(5 SECONDS)
living_mob.SpinAnimation(5, 1)
// If we're a multiz map send them to the next floor
var/turf/above_us = get_step_multiz(cast_on, UP)
if(above_us)
living_mob.forceMove(above_us)
continue
// If we're not throw them to a nearby (open) turf
var/list/turfs_by_us = get_adjacent_open_turfs(cast_on)
// If there is no side by us, hardstun them
if(!length(turfs_by_us))
living_mob.Paralyze(5 SECONDS)
continue
// If there's an open turf throw them to the side
living_mob.throw_at(pick(turfs_by_us), 1, 3, thrower = owner, spin = FALSE)
if(!message_shown)
new_wall.visible_message(span_warning("\A [new_wall] [rises_message]!"))
/datum/action/cooldown/spell/pointed/rust_construction/proc/fade_wall_filter(turf/closed/wall)
if(QDELETED(wall))
return
var/rust_filter = wall.get_filter("rust_wall")
if(!rust_filter)
return
animate(rust_filter, alpha = 0, time = filter_duration * (19/20))
/datum/action/cooldown/spell/pointed/rust_construction/proc/remove_wall_filter(turf/closed/wall)
if(QDELETED(wall))
return
wall.remove_filter("rust_wall")
@@ -1,7 +1,9 @@
// Shoots out in a wave-like, what rust heretics themselves get
/datum/action/cooldown/spell/cone/staggered/entropic_plume
name = "Entropic Plume"
desc = "Spews forth a disorienting plume that causes enemies to strike each other, briefly blinds them(increasing with range) and poisons them(decreasing with range). Also spreads rust in the path of the plume."
desc = "Spews forth a disorienting plume that causes enemies to strike each other, \
briefly blinds them (increasing with range) and poisons them (decreasing with range). \
Also spreads rust in the path of the plume."
background_icon_state = "bg_ecult"
icon_icon = 'icons/mob/actions/actions_ecult.dmi'
button_icon_state = "entropic_plume"
@@ -25,19 +27,21 @@
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))
if(victim.can_block_magic(antimagic_flags) || IS_HERETIC_OR_MONSTER(victim) || victim == caster)
return
victim.apply_status_effect(/datum/status_effect/amok)
victim.apply_status_effect(/datum/status_effect/cloudstruck, level * 1 SECONDS)
victim.reagents?.add_reagent(/datum/reagent/eldritch, max(1, 6 - level))
/datum/action/cooldown/spell/cone/staggered/entropic_plume/calculate_cone_shape(current_level)
if(current_level == cone_levels)
return 5
else if(current_level == cone_levels - 1)
// At the first level (that isn't level 1) we will be small
if(current_level == 2)
return 3
else
return 2
// At the max level, we turn small again
if(current_level == cone_levels)
return 3
// Otherwise, all levels in between will be wider
return 5
/obj/effect/temp_visual/dir_setting/entropic
icon = 'icons/effects/160x160.dmi'
@@ -0,0 +1,36 @@
/datum/action/cooldown/spell/cone/staggered/cone_of_cold/void
name = "Void Blast"
desc = "Fires a cone of chilling void in front of you, freezing everything in its path. \
Enemies in the cone of the blast will be damaged slightly, slowed, and chilled overtime. \
Additionally, objects hit will be frozen and can shatter, and ground hit will be iced over and slippery - \
though they may thaw shortly if used in room temperature."
background_icon_state = "bg_ecult"
icon_icon = 'icons/mob/actions/actions_ecult.dmi'
button_icon_state = "entropic_plume"
school = SCHOOL_FORBIDDEN
cooldown_time = 1 MINUTES
invocation = "FR'ZE!"
invocation_type = INVOCATION_SHOUT
spell_requirements = NONE
// In room temperature, the ice won't last very long
// ...but in space / freezing rooms, it will stick around
turf_freeze_type = TURF_WET_ICE
unfreeze_turf_duration = 1 MINUTES
// Applies an "infinite" version of basic void chill
// (This stacks with mansus grasp's void chill)
frozen_status_effect_path = /datum/status_effect/void_chill/lasting
unfreeze_mob_duration = 30 SECONDS
// Does a smidge of damage
on_freeze_brute_damage = 12
on_freeze_burn_damage = 10
// Also freezes stuff (Which will likely be unfrozen similarly to turfs)
unfreeze_object_duration = 30 SECONDS
/datum/action/cooldown/spell/cone/staggered/cone_of_cold/void/do_mob_cone_effect(mob/living/target_mob, atom/caster, level)
if(IS_HERETIC_OR_MONSTER(target_mob))
return
return ..()
@@ -11,7 +11,7 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 30 SECONDS
invocation = "RE'L'TY PH'S'E"
invocation = "RE'L'TY PH'S'E."
invocation_type = INVOCATION_WHISPER
spell_requirements = NONE
@@ -21,13 +21,14 @@
/// The radius of damage around the void bubble
var/damage_radius = 1
/datum/action/cooldown/spell/pointed/void_phase/is_valid_target(atom/cast_on)
// We do the close range check first
if(get_dist(get_turf(owner), get_turf(cast_on)) < min_cast_range)
owner.balloon_alert(owner, "too close!")
return FALSE
/datum/action/cooldown/spell/pointed/void_phase/before_cast(atom/cast_on)
. = ..()
if(. & SPELL_CANCEL_CAST)
return
return ..()
if(owner && get_dist(get_turf(owner), get_turf(cast_on)) < min_cast_range)
cast_on.balloon_alert(owner, "too close!")
return . | SPELL_CANCEL_CAST
/datum/action/cooldown/spell/pointed/void_phase/cast(atom/cast_on)
. = ..()
@@ -10,7 +10,7 @@
school = SCHOOL_FORBIDDEN
cooldown_time = 40 SECONDS
invocation = "BR'NG F'RTH TH'M T' M'"
invocation = "BR'NG F'RTH TH'M T' M'."
invocation_type = INVOCATION_WHISPER
spell_requirements = NONE
@@ -29,8 +29,7 @@
new /obj/effect/temp_visual/voidin(get_turf(cast_on))
// Before we cast the actual effects, deal AOE damage to anyone adjacent to us
var/list/mob/living/people_near_us = get_things_to_cast_on(cast_on, damage_radius)
for(var/mob/living/nearby_living as anything in people_near_us)
for(var/mob/living/nearby_living as anything in get_things_to_cast_on(cast_on, damage_radius))
nearby_living.apply_damage(30, BRUTE, wound_bonus = CANT_WOUND)
/datum/action/cooldown/spell/aoe/void_pull/get_things_to_cast_on(atom/center, radius_override = 0)
@@ -14,10 +14,12 @@
icon_state = "void_chill"
/datum/status_effect/void_chill/on_apply()
owner.add_atom_colour(COLOR_BLUE_LIGHT, TEMPORARY_COLOUR_PRIORITY)
owner.add_movespeed_modifier(/datum/movespeed_modifier/void_chill, update = TRUE)
return TRUE
/datum/status_effect/void_chill/on_remove()
owner.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, COLOR_BLUE_LIGHT)
owner.remove_movespeed_modifier(/datum/movespeed_modifier/void_chill, update = TRUE)
/datum/status_effect/void_chill/tick()
@@ -27,6 +29,10 @@
duration = 10 SECONDS
cooling_per_tick = -20
/datum/status_effect/void_chill/lasting
id = "lasting_void_chill"
duration = -1
/datum/movespeed_modifier/void_chill
multiplicative_slowdown = 0.3
@@ -5,6 +5,8 @@
alert_type = /atom/movable/screen/alert/status_effect/ghoul
/// The new max health value set for the ghoul, if supplied
var/new_max_health
/// What, if any, stamina modifier we applied to the ghoul mob
var/stamina_mod_applied
/// Reference to the master of the ghoul's mind
var/datum/mind/master_mind
/// An optional callback invoked when a ghoul is made (on_apply)
@@ -51,6 +53,9 @@
human_target.revive(ADMIN_HEAL_ALL) // Have to do an admin heal here, otherwise they'll likely just die due to missing organs or limbs
if(new_max_health)
if(new_max_health < human_target.maxHealth)
stamina_mod_applied = (new_max_health / human_target.maxHealth)
human_target.physiology.stamina_mod *= stamina_mod_applied
human_target.setMaxHealth(new_max_health)
human_target.health = new_max_health
@@ -78,6 +83,8 @@
var/mob/living/carbon/human/human_target = owner
if(new_max_health)
if(isnum(stamina_mod_applied))
human_target.physiology.stamina_mod /= stamina_mod_applied
human_target.setMaxHealth(initial(human_target.maxHealth))
on_lost_callback?.Invoke(human_target)
@@ -45,7 +45,7 @@
/datum/spellbook_entry/teslablast
name = "Tesla Blast"
desc = "Charge up a tesla arc and release it at a random nearby target! You can move freely while it charges. The arc jumps between targets and can knock them down."
spell_type = /datum/action/cooldown/spell/tesla
spell_type = /datum/action/cooldown/spell/charged/beam/tesla
category = "Offensive"
/datum/spellbook_entry/lightningbolt
+11 -4
View File
@@ -181,10 +181,12 @@
if(spell_requirements & SPELL_REQUIRES_WIZARD_GARB)
var/mob/living/carbon/human/human_owner = owner
if(!(human_owner.wear_suit?.clothing_flags & CASTING_CLOTHES))
to_chat(owner, span_warning("You don't feel strong enough without your robe!"))
if(feedback)
to_chat(owner, span_warning("You don't feel strong enough without your robe!"))
return FALSE
if(!(human_owner.head?.clothing_flags & CASTING_CLOTHES))
to_chat(owner, span_warning("You don't feel strong enough without your hat!"))
if(feedback)
to_chat(owner, span_warning("You don't feel strong enough without your hat!"))
return FALSE
else
@@ -208,6 +210,7 @@
/**
* Check if the target we're casting on is a valid target.
* For self-casted spells, the target being checked (cast_on) is the caster.
* For click_to_activate spells, the target being checked is the clicked atom.
*
* Return TRUE if cast_on is valid, FALSE otherwise
*/
@@ -384,7 +387,7 @@
return FALSE
spell_level++
cooldown_time = max(cooldown_time - cooldown_reduction_per_rank, 0)
cooldown_time = max(cooldown_time - cooldown_reduction_per_rank, 0.25 SECONDS) // 0 second CD starts to break things.
update_spell_name()
return TRUE
@@ -400,7 +403,11 @@
return FALSE
spell_level--
cooldown_time = min(cooldown_time + cooldown_reduction_per_rank, initial(cooldown_time))
if(cooldown_reduction_per_rank > 0 SECONDS)
cooldown_time = min(cooldown_time + cooldown_reduction_per_rank, initial(cooldown_time))
else
cooldown_time = max(cooldown_time + cooldown_reduction_per_rank, initial(cooldown_time))
update_spell_name()
return TRUE
@@ -0,0 +1,158 @@
/**
* ## Channelled spells
*
* These spells do something after a channel time.
* To use this template, all that's needed is for cast() to be implemented.
*/
/datum/action/cooldown/spell/charged
/// What message do we display when we start chanelling?
var/channel_message
/// Whether we're currently channelling / charging the spell
var/currently_channeling = FALSE
/// How long it takes to channel the spell.
var/channel_time = 10 SECONDS
// Overlay optional, applied when we start channelling
/// What icon should we use for our overlay
var/charge_overlay_icon
/// What icon state should we use for our overlay
var/charge_overlay_state
/// The actual appearance / our overlay. Don't mess with this
var/mutable_appearance/charge_overlay_instance
// Sound optional, played when we start chanelling
/// What soundpath should we play when we start chanelling
var/charge_sound
/// The actual sound we generate, don't mess with this
var/sound/charge_sound_instance
/datum/action/cooldown/spell/charged/New(Target, original)
. = ..()
if(!channel_message)
channel_message = span_notice("You start chanelling [src]...")
if(charge_sound)
charge_sound_instance = sound(charge_sound, channel = CHANNEL_CHARGED_SPELL)
if(charge_overlay_icon && charge_overlay_state)
charge_overlay_instance = mutable_appearance(charge_overlay_icon, charge_overlay_state, EFFECTS_LAYER)
/datum/action/cooldown/spell/charged/Destroy()
if(owner)
stop_channel_effect(owner)
charge_overlay_instance = null
charge_sound_instance = null
return ..()
/datum/action/cooldown/spell/charged/Remove(mob/living/remove_from)
stop_channel_effect(remove_from)
return ..()
/datum/action/cooldown/spell/charged/can_cast_spell(feedback = TRUE)
. = ..()
if(!.)
return FALSE
if(currently_channeling)
if(feedback)
to_chat(owner, span_warning("You're already channeling [src]!"))
return FALSE
return TRUE
/datum/action/cooldown/spell/charged/before_cast(atom/cast_on)
. = ..()
if(. & SPELL_CANCEL_CAST)
return
to_chat(cast_on, channel_message)
if(charge_sound_instance)
playsound(cast_on, charge_sound_instance, 50, FALSE)
if(charge_overlay_instance)
cast_on.add_overlay(charge_overlay_instance)
currently_channeling = TRUE
UpdateButtons(status_only = TRUE)
if(!do_after(cast_on, channel_time, timed_action_flags = (IGNORE_USER_LOC_CHANGE|IGNORE_HELD_ITEM)))
stop_channel_effect(cast_on)
return . | SPELL_CANCEL_CAST
/datum/action/cooldown/spell/charged/cast(atom/cast_on)
. = ..()
stop_channel_effect(cast_on)
/datum/action/cooldown/spell/charged/set_statpanel_format()
. = ..()
if(!islist(.))
return
if(currently_channeling)
.[PANEL_DISPLAY_STATUS] = "CHANNELING"
/// Interrupts the chanelling effect, removing any overlay or sound playing (for the passed mob)
/datum/action/cooldown/spell/charged/proc/stop_channel_effect(mob/for_who)
if(charge_overlay_instance)
for_who.cut_overlay(charge_overlay_instance)
if(charge_sound_instance)
for_who.stop_sound_channel(CHANNEL_CHARGED_SPELL)
// Play a null sound in to cancel the sound playing, because byond
playsound(for_who, sound(null, repeat = 0, channel = CHANNEL_CHARGED_SPELL), 50, FALSE)
currently_channeling = FALSE
UpdateButtons(status_only = TRUE)
/**
* ### Channelled "Beam" spells
*
* Channelled spells that pick a random target from nearby atoms to cast a spell on.
* Commonly used for beams, hence the name, but nothing's stopping projectiles or whatever from working.
*
* If no targets are nearby, cancels the spell and refunds the cooldown.
*/
/datum/action/cooldown/spell/charged/beam
/// The radius around the caster to find a target.
var/target_radius = 5
/// The maximum number of bounces the beam will go before stopping.
var/max_beam_bounces = 1
/// Who's our initial beam target? Set in before cast, used in cast.
var/atom/initial_target
/datum/action/cooldown/spell/charged/beam/Destroy()
initial_target = null // This like shouuld never hang references but I've seen some cursed things so let's be safe
return ..()
/datum/action/cooldown/spell/charged/beam/before_cast(atom/cast_on)
. = ..()
if(. & SPELL_CANCEL_CAST)
return
initial_target = get_target(cast_on)
if(isnull(initial_target))
cast_on.balloon_alert(cast_on, "no targets nearby!")
stop_channel_effect(cast_on)
return . | SPELL_CANCEL_CAST
/datum/action/cooldown/spell/charged/beam/cast(atom/cast_on)
. = ..()
send_beam(cast_on, initial_target, max_beam_bounces)
initial_target = null
/datum/action/cooldown/spell/charged/beam/proc/send_beam(atom/origin, atom/to_beam, bounces)
SHOULD_CALL_PARENT(FALSE)
CRASH("[type] did not implement send_beam and either has no effects or implemented the spell incorrectly.")
/datum/action/cooldown/spell/charged/beam/proc/get_target(atom/center)
var/list/things = list()
for(var/atom/nearby_thing in range(target_radius, center))
if(nearby_thing == owner || nearby_thing == center)
continue
things += nearby_thing
if(!length(things))
return null
return pick(things)
@@ -0,0 +1,62 @@
/datum/action/cooldown/spell/charged/beam/tesla
name = "Tesla Blast"
desc = "Charge up a tesla arc and release it at random nearby targets! \
You can move freely while it charges. The arc jumps between targets and can knock them down."
button_icon_state = "lightning"
sound = 'sound/magic/lightningbolt.ogg'
cooldown_time = 30 SECONDS
cooldown_reduction_per_rank = 6.75 SECONDS
invocation = "UN'LTD P'WAH!"
invocation_type = INVOCATION_SHOUT
school = SCHOOL_EVOCATION
channel_message = span_notice("You start gathering power...")
charge_overlay_icon = 'icons/effects/effects.dmi'
charge_overlay_state = "electricity"
charge_sound = 'sound/magic/lightning_chargeup.ogg'
target_radius = 7
max_beam_bounces = 5
/// How much energy / damage does the initial bounce
var/initial_energy = 30
/// How much energy / damage is lost per bounce
var/energy_lost_per_bounce = 5
/// Zaps a target, the bolt originating from origin.
/datum/action/cooldown/spell/charged/beam/tesla/send_beam(atom/origin, mob/living/carbon/to_beam, bolt_energy = 30, bounces = 5)
origin.Beam(to_beam, icon_state = "lightning[rand(1,12)]", time = 0.5 SECONDS)
playsound(get_turf(to_beam), 'sound/magic/lightningshock.ogg', 50, TRUE, -1)
if(to_beam.can_block_magic(antimagic_flags))
to_beam.visible_message(
span_warning("[to_beam] absorbs the spell, remaining unharmed!"),
span_userdanger("You absorb the spell, remaining unharmed!"),
)
else
to_beam.electrocute_act(bolt_energy, "Lightning Bolt", flags = SHOCK_NOGLOVES)
// Bounce again! Call our proc recursively to keep the chain going (even if our mob blocked it with antimagic)
if(bounces < 1)
return
var/mob/living/carbon/to_beam_next = get_target(to_beam)
if(isnull(to_beam_next))
return
send_beam(to_beam, to_beam_next, max(bolt_energy - energy_lost_per_bounce, energy_lost_per_bounce), bounces - 1)
/datum/action/cooldown/spell/charged/beam/tesla/get_target(atom/center)
var/list/possibles = list()
for(var/mob/living/carbon/to_check in view(target_radius, center))
if(to_check == center || to_check == owner)
continue
if(!length(get_path_to(center, to_check, max_distance = target_radius, simulated_only = FALSE)))
continue
possibles += to_check
if(!length(possibles))
return null
return pick(possibles)
+30 -19
View File
@@ -70,39 +70,50 @@
left_dir = SOUTH
right_dir = NORTH
for(var/i in 1 to cone_levels)
// Go though every level of the cone levels and generate the cone.
for(var/level in 1 to cone_levels)
var/list/level_turfs = list()
// Our center turf always exists, it's straight ahead of the caster.
turf_to_use = get_step(turf_to_use, dir_to_use)
level_turfs += turf_to_use
if(i != 1)
left_turf = get_step(turf_to_use, left_dir)
level_turfs += left_turf
right_turf = get_step(turf_to_use, right_dir)
level_turfs += right_turf
for(var/left_i in 1 to i -calculate_cone_shape(i))
if(left_turf.density && respect_density)
// Level 1 only ever has 1 turf, it's a cone.
if(level != 1)
var/level_width_in_each_direction = round((calculate_cone_shape(level) - 1) / 2)
left_turf = turf_to_use
right_turf = turf_to_use
// Check turfs to the left...
for(var/left_of_center in 1 to level_width_in_each_direction)
if(respect_density && left_turf.density)
break
left_turf = get_step(left_turf, left_dir)
level_turfs += left_turf
for(var/right_i in 1 to i -calculate_cone_shape(i))
if(right_turf.density && respect_density)
// And turfs to the right.
for(var/right_of_enter in 1 to level_width_in_each_direction)
if(respect_density && right_turf.density)
break
right_turf = get_step(right_turf, right_dir)
level_turfs += right_turf
// Add the list of all turfs on this level to the turfs to return
turfs_to_return += list(level_turfs)
if(i == cone_levels)
continue
if(turf_to_use.density && respect_density)
// If we're at the last level, we're done
if(level == cone_levels)
break
// But if we're not at the last level, we should check that we can keep going
if(respect_density && turf_to_use.density)
break
return turfs_to_return
///This proc adjusts the cones width depending on the level.
/**
* Adjusts the width of the cone at the passed level.
* This is never called on the first level of the cone (level 1 is always 1 width)
*
* Return a number - the TOTAL width of the cone at the passed level.
*/
/datum/action/cooldown/spell/cone/proc/calculate_cone_shape(current_level)
var/end_taper_start = round(cone_levels * 0.8)
if(current_level > end_taper_start)
return (current_level % end_taper_start) * 2 //someone more talented and probably come up with a better formula.
else
return 2
// Default formula: (1 (innate) -> 3 -> 5 -> 5 -> 7 -> 7 -> 9 -> 9 -> ...)
return current_level + (current_level % 2) + 1
/**
* ### Staggered Cone
@@ -0,0 +1,67 @@
/datum/action/cooldown/spell/cone/staggered/cone_of_cold
name = "Cone of Cold"
desc = "Shoots out a freezing cone in front of you."
school = SCHOOL_EVOCATION
cooldown_time = 30 SECONDS
cooldown_reduction_per_rank = 4 SECONDS
invocation = "ISAGE!" // What killed the dinosaurs? THE ICE AGE
invocation_type = INVOCATION_SHOUT
cone_levels = 4
respect_density = TRUE
delay_between_level = 0.05 SECONDS
/// What flags do we pass to MakeSlippery when affecting turfs?
/// null / NONE / TURF_DRY means the turf is unaffected
var/turf_freeze_type = TURF_WET_PERMAFROST
/// How long do turfs remain slippery / frozen for?
/// 0 seconds means the turf is unaffected, INFINITY means it's made perma-wet
var/unfreeze_turf_duration = 45 SECONDS
/// What status effect do we apply when affecting mobs?
/// null means no status effect is applied
var/datum/status_effect/frozen_status_effect_path = /datum/status_effect/freon/lasting
/// How long do mobs remain frozen for?
/// 0 seconds means no status effect is applied, INFINITY means infinite duration (or default duration of the status effect)
var/unfreeze_mob_duration = 20 SECONDS
/// How much brute do we apply on freeze?
var/on_freeze_brute_damage = 10
/// How much burn do we apply on freeze?
var/on_freeze_burn_damage = 20
/// How long do objects remain frozen for?
/// 0 seconds mean no objects are frozen, INFINITY means infinite duration freeze
var/unfreeze_object_duration = 20 SECONDS
/datum/action/cooldown/spell/cone/staggered/cone_of_cold/do_turf_cone_effect(turf/target_turf, atom/caster, level)
if(!turf_freeze_type || unfreeze_turf_duration <= 0 SECONDS) // 0 duration = don't apply the slip
return
if(!isopenturf(target_turf))
return
var/turf/open/frozen_floor = target_turf
frozen_floor.MakeSlippery(turf_freeze_type, unfreeze_turf_duration, permanent = (unfreeze_turf_duration == INFINITY))
/datum/action/cooldown/spell/cone/staggered/cone_of_cold/do_mob_cone_effect(mob/living/target_mob, atom/caster, level)
if(target_mob.can_block_magic(antimagic_flags) || target_mob == caster)
return
if(ispath(frozen_status_effect_path) && unfreeze_mob_duration > 0 SECONDS) // 0 duration = don't apply the status effect
var/datum/status_effect/freeze = target_mob.apply_status_effect(frozen_status_effect_path)
if(unfreeze_mob_duration != INFINITY)
freeze.duration = world.time + unfreeze_mob_duration
if(on_freeze_brute_damage || on_freeze_burn_damage)
target_mob.take_overall_damage(on_freeze_brute_damage, on_freeze_burn_damage)
to_chat(target_mob, span_userdanger("You feel a bitter cold!"))
/datum/action/cooldown/spell/cone/staggered/cone_of_cold/do_obj_cone_effect(obj/target_obj, atom/caster, level)
if(unfreeze_object_duration <= 0 SECONDS) // 0 duration = don't apply a freeze
return
if(!target_obj.freeze())
return
if(unfreeze_object_duration == INFINITY) // Infinity duration = don't set an unfreeze timer
return
addtimer(CALLBACK(target_obj, TYPE_PROC_REF(/obj/, unfreeze)), unfreeze_object_duration)
@@ -4,6 +4,7 @@
background_icon_state = "bg_mime"
icon_icon = 'icons/mob/actions/actions_mime.dmi'
button_icon_state = "invisible_chair"
check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED
panel = "Mime"
sound = null
@@ -4,6 +4,7 @@
background_icon_state = "bg_mime"
icon_icon = 'icons/mob/actions/actions_mime.dmi'
button_icon_state = "invisible_wall"
check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED
panel = "Mime"
sound = null
@@ -6,6 +6,7 @@
background_icon_state = "bg_mime"
icon_icon = 'icons/mob/actions/actions_mime.dmi'
button_icon_state = "invisible_box"
check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED
panel = "Mime"
sound = null
@@ -19,6 +19,9 @@
/// What dummy mob type do we put jaunters in on jaunt?
var/jaunt_type = /obj/effect/dummy/phased_mob
/datum/action/cooldown/spell/jaunt/before_cast(atom/cast_on)
return ..() | SPELL_NO_FEEDBACK // Don't do the feedback until after we're jaunting
/datum/action/cooldown/spell/jaunt/can_cast_spell(feedback = TRUE)
. = ..()
if(!.)
@@ -49,10 +52,16 @@
* Returns the holder mob that was created
*/
/datum/action/cooldown/spell/jaunt/proc/enter_jaunt(mob/living/jaunter, turf/loc_override)
SHOULD_CALL_PARENT(TRUE)
var/obj/effect/dummy/phased_mob/jaunt = new jaunt_type(loc_override || get_turf(jaunter), jaunter)
RegisterSignal(jaunt, COMSIG_MOB_EJECTED_FROM_JAUNT, PROC_REF(on_jaunt_exited))
spell_requirements |= SPELL_CASTABLE_WHILE_PHASED
ADD_TRAIT(jaunter, TRAIT_MAGICALLY_PHASED, REF(src))
ADD_TRAIT(jaunter, TRAIT_RUNECHAT_HIDDEN, REF(src))
// Don't do the feedback until we have runechat hidden.
// Otherwise the text will follow the jaunt holder, which reveals where our caster is travelling.
spell_feedback()
// This needs to happen at the end, after all the traits and stuff is handled
SEND_SIGNAL(jaunter, COMSIG_MOB_ENTER_JAUNT, src, jaunt)
@@ -68,6 +77,8 @@
* Returns TRUE on successful exit, FALSE otherwise
*/
/datum/action/cooldown/spell/jaunt/proc/exit_jaunt(mob/living/unjaunter, turf/loc_override)
SHOULD_CALL_PARENT(TRUE)
var/obj/effect/dummy/phased_mob/jaunt = unjaunter.loc
if(!istype(jaunt))
return FALSE
@@ -92,13 +103,10 @@
SHOULD_CALL_PARENT(TRUE)
spell_requirements &= ~SPELL_CASTABLE_WHILE_PHASED
REMOVE_TRAIT(unjaunter, TRAIT_MAGICALLY_PHASED, REF(src))
REMOVE_TRAIT(unjaunter, TRAIT_RUNECHAT_HIDDEN, REF(src))
// This needs to happen at the end, after all the traits and stuff is handled
SEND_SIGNAL(unjaunter, COMSIG_MOB_AFTER_EXIT_JAUNT, src)
/// Simple helper to check if the passed mob is currently jaunting or not
/datum/action/cooldown/spell/jaunt/proc/is_jaunting(mob/living/user)
return istype(user.loc, /obj/effect/dummy/phased_mob)
/datum/action/cooldown/spell/jaunt/Remove(mob/living/remove_from)
exit_jaunt(remove_from)
if (!is_jaunting(remove_from)) // In case you have made exit_jaunt conditional, as in mirror walk
@@ -23,7 +23,7 @@
/// List of valid exit points
var/list/exit_point_list
/datum/action/cooldown/spell/jaunt/ethereal_jaunt/enter_jaunt(mob/living/jaunter)
/datum/action/cooldown/spell/jaunt/ethereal_jaunt/enter_jaunt(mob/living/jaunter, turf/loc_override)
. = ..()
if(!.)
return
@@ -87,12 +87,17 @@
to_chat(owner, span_warning("You cannot cast [src] on yourself!"))
return FALSE
if(get_dist(owner, cast_on) > cast_range)
to_chat(owner, span_warning("[cast_on.p_theyre(TRUE)] too far away!"))
return FALSE
return TRUE
/datum/action/cooldown/spell/pointed/before_cast(atom/cast_on)
. = ..()
if(. & SPELL_CANCEL_CAST)
return
if(owner && get_dist(get_turf(owner), get_turf(cast_on)) > cast_range)
cast_on.balloon_alert(owner, "too far away!")
return . | SPELL_CANCEL_CAST
/**
* ### Pointed projectile spells
*
@@ -5,6 +5,7 @@
background_icon_state = "bg_mime"
icon_icon = 'icons/mob/actions/actions_mime.dmi'
button_icon_state = "finger_guns0"
check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED
panel = "Mime"
sound = null
@@ -45,6 +45,7 @@
background_icon_state = "bg_mime"
icon_icon = 'icons/mob/actions/actions_mime.dmi'
button_icon_state = "invisible_blockade"
check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED
panel = "Mime"
sound = null
@@ -1,125 +0,0 @@
/datum/action/cooldown/spell/tesla
name = "Tesla Blast"
desc = "Charge up a tesla arc and release it at random nearby targets! \
You can move freely while it charges. The arc jumps between targets and can knock them down."
button_icon_state = "lightning"
cooldown_time = 30 SECONDS
cooldown_reduction_per_rank = 6.75 SECONDS
invocation = "UN'LTD P'WAH!"
invocation_type = INVOCATION_SHOUT
school = SCHOOL_EVOCATION
/// Whether we're currently channelling a tesla blast or not
var/currently_channeling = FALSE
/// How long it takes to channel the zap.
var/channel_time = 10 SECONDS
/// The radius around (either the caster or people shocked) to which the tesla blast can reach
var/shock_radius = 7
/// The halo that appears around the caster while charging the spell
var/static/mutable_appearance/halo
/// The sound played while charging the spell
/// Quote: "the only way i can think of to stop a sound, thank MSO for the idea."
var/sound/charge_sound
/datum/action/cooldown/spell/tesla/Remove(mob/living/remove_from)
reset_tesla(remove_from)
return ..()
/datum/action/cooldown/spell/tesla/set_statpanel_format()
. = ..()
if(!islist(.))
return
if(currently_channeling)
.[PANEL_DISPLAY_STATUS] = "CHANNELING"
/datum/action/cooldown/spell/tesla/can_cast_spell(feedback = TRUE)
. = ..()
if(!.)
return FALSE
if(currently_channeling)
if(feedback)
to_chat(owner, span_warning("You're already channeling [src]!"))
return FALSE
return TRUE
/datum/action/cooldown/spell/tesla/before_cast(atom/cast_on)
. = ..()
if(. & SPELL_CANCEL_CAST)
return
to_chat(cast_on, span_notice("You start gathering power..."))
charge_sound = new /sound('sound/magic/lightning_chargeup.ogg', channel = 7)
halo ||= mutable_appearance('icons/effects/effects.dmi', "electricity", EFFECTS_LAYER)
cast_on.add_overlay(halo)
playsound(get_turf(cast_on), charge_sound, 50, FALSE)
currently_channeling = TRUE
if(!do_after(cast_on, channel_time, timed_action_flags = (IGNORE_USER_LOC_CHANGE|IGNORE_HELD_ITEM)))
reset_tesla(cast_on)
return . | SPELL_CANCEL_CAST
/datum/action/cooldown/spell/tesla/reset_spell_cooldown()
reset_tesla(owner)
return ..()
/// Resets the tesla effect.
/datum/action/cooldown/spell/tesla/proc/reset_tesla(atom/to_reset)
to_reset.cut_overlay(halo)
currently_channeling = FALSE
/datum/action/cooldown/spell/tesla/cast(atom/cast_on)
. = ..()
// byond, why you suck?
charge_sound = sound(null, repeat = 0, wait = 1, channel = charge_sound.channel)
// Sorry MrPerson, but the other ways just didn't do it the way i needed to work, this is the only way.
playsound(get_turf(cast_on), charge_sound, 50, FALSE)
var/mob/living/carbon/to_zap_first = get_target(cast_on)
if(QDELETED(to_zap_first))
cast_on.balloon_alert(cast_on, "no targets nearby!")
reset_spell_cooldown()
return FALSE
playsound(get_turf(cast_on), 'sound/magic/lightningbolt.ogg', 50, TRUE)
zap_target(cast_on, to_zap_first)
reset_tesla(cast_on)
/// Zaps a target, the bolt originating from origin.
/datum/action/cooldown/spell/tesla/proc/zap_target(atom/origin, mob/living/carbon/to_zap, bolt_energy = 30, bounces = 5)
origin.Beam(to_zap, icon_state = "lightning[rand(1,12)]", time = 0.5 SECONDS)
playsound(get_turf(to_zap), 'sound/magic/lightningshock.ogg', 50, TRUE, -1)
if(to_zap.can_block_magic(antimagic_flags))
to_zap.visible_message(
span_warning("[to_zap] absorbs the spell, remaining unharmed!"),
span_userdanger("You absorb the spell, remaining unharmed!"),
)
else
to_zap.electrocute_act(bolt_energy, "Lightning Bolt", flags = SHOCK_NOGLOVES)
if(bounces >= 1)
var/mob/living/carbon/to_zap_next = get_target(to_zap)
if(!QDELETED(to_zap_next))
zap_target(to_zap, to_zap_next, max((bolt_energy - 5), 5), bounces - 1)
/// Get a target in view of us to zap next. Returns a carbon, or null if none were found.
/datum/action/cooldown/spell/tesla/proc/get_target(atom/center)
var/list/possibles = list()
for(var/mob/living/carbon/to_check in view(shock_radius, center))
if(to_check == center || to_check == owner)
continue
if(!length(get_path_to(center, to_check, max_distance = shock_radius, simulated_only = FALSE)))
continue
possibles += to_check
if(!length(possibles))
return null
return pick(possibles)
+108 -16
View File
@@ -1,3 +1,23 @@
/**
* ## Touch Spell
*
* Touch spells are spells which function through the power of an item attack.
*
* Instead of the spell triggering when the caster presses the button,
* pressing the button will give them a hand object.
* The spell's effects are cast when the hand object makes contact with something.
*
* To implement a touch spell, all you need is to implement:
* * is_valid_target - to check whether the slapped target is valid
* * cast_on_hand_hit - to implement effects on cast
*
* However, for added complexity, you can optionally implement:
* * on_antimagic_triggered - to cause effects when antimagic is triggered
* * cast_on_secondary_hand_hit - to implement different effects if the caster r-clicked
* It is not necessarily to touch any of the core functions, and is
* (generally) inadvisable unless you know what you're doing
*/
/datum/action/cooldown/spell/touch
check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED
sound = 'sound/items/welder.ogg'
@@ -12,6 +32,8 @@
var/draw_message = span_notice("You channel the power of the spell to your hand.")
/// The message displayed upon willingly dropping / deleting / cancelling the touch hand before using it
var/drop_message = span_notice("You draw the power out of your hand.")
/// If TRUE, the caster can willingly hit themselves with the hand
var/can_cast_on_self = FALSE
/datum/action/cooldown/spell/touch/Destroy()
// If we have an owner, the hand is cleaned up in Remove(), which Destroy() calls.
@@ -23,6 +45,10 @@
remove_hand(remove_from)
return ..()
// PreActivate is overridden to not check is_valid_target on the caster, as it makes less sense.
/datum/action/cooldown/spell/touch/PreActivate(atom/target)
return Activate(target)
/datum/action/cooldown/spell/touch/UpdateButton(atom/movable/screen/movable/action_button/button, status_only = FALSE, force = FALSE)
. = ..()
if(!button)
@@ -42,6 +68,9 @@
. = ..()
if(!.)
return FALSE
// Currently, hard checks for carbons.
// If someone wants to add support for simplemobs
// adminbussed to have hands, go for it
if(!iscarbon(owner))
return FALSE
var/mob/living/carbon/carbon_owner = owner
@@ -49,8 +78,9 @@
return FALSE
return TRUE
// Checks if the mob slapped with the hand is a valid target.
/datum/action/cooldown/spell/touch/is_valid_target(atom/cast_on)
return iscarbon(cast_on)
return isliving(cast_on)
/**
* Creates a new hand_path hand and equips it to the caster.
@@ -59,6 +89,8 @@
* Otherwise, registers signals and returns TRUE.
*/
/datum/action/cooldown/spell/touch/proc/create_hand(mob/living/carbon/cast_on)
SHOULD_CALL_PARENT(TRUE)
var/obj/item/melee/touch_attack/new_hand = new hand_path(cast_on, src)
if(!cast_on.put_in_hands(new_hand, del_on_fail = TRUE))
reset_spell_cooldown()
@@ -69,10 +101,7 @@
return FALSE
attached_hand = new_hand
RegisterSignal(attached_hand, COMSIG_ITEM_AFTERATTACK, PROC_REF(on_hand_hit))
RegisterSignal(attached_hand, COMSIG_ITEM_AFTERATTACK_SECONDARY, PROC_REF(on_secondary_hand_hit))
RegisterSignal(attached_hand, COMSIG_PARENT_QDELETING, PROC_REF(on_hand_deleted))
RegisterSignal(attached_hand, COMSIG_ITEM_DROPPED, PROC_REF(on_hand_dropped))
register_hand_signals()
to_chat(cast_on, draw_message)
return TRUE
@@ -83,8 +112,10 @@
* If reset_cooldown_after is FALSE, we will instead just start the spell's cooldown
*/
/datum/action/cooldown/spell/touch/proc/remove_hand(mob/living/hand_owner, reset_cooldown_after = FALSE)
SHOULD_CALL_PARENT(TRUE)
if(!QDELETED(attached_hand))
UnregisterSignal(attached_hand, list(COMSIG_ITEM_AFTERATTACK, COMSIG_ITEM_AFTERATTACK_SECONDARY, COMSIG_PARENT_QDELETING, COMSIG_ITEM_DROPPED))
unregister_hand_signals()
hand_owner?.temporarilyRemoveItemFromInventory(attached_hand)
QDEL_NULL(attached_hand)
@@ -95,6 +126,26 @@
else
StartCooldown()
/// Registers all signal procs for the hand.
/datum/action/cooldown/spell/touch/proc/register_hand_signals()
SHOULD_CALL_PARENT(TRUE)
RegisterSignal(attached_hand, COMSIG_ITEM_AFTERATTACK, PROC_REF(on_hand_hit))
RegisterSignal(attached_hand, COMSIG_ITEM_AFTERATTACK_SECONDARY, PROC_REF(on_secondary_hand_hit))
RegisterSignal(attached_hand, COMSIG_ITEM_DROPPED, PROC_REF(on_hand_dropped))
RegisterSignal(attached_hand, COMSIG_PARENT_QDELETING, PROC_REF(on_hand_deleted))
/// Unregisters all signal procs for the hand.
/datum/action/cooldown/spell/touch/proc/unregister_hand_signals()
SHOULD_CALL_PARENT(TRUE)
UnregisterSignal(attached_hand, list(
COMSIG_ITEM_AFTERATTACK,
COMSIG_ITEM_AFTERATTACK_SECONDARY,
COMSIG_ITEM_DROPPED,
COMSIG_PARENT_QDELETING,
))
// Touch spells don't go on cooldown OR give off an invocation until the hand is used itself.
/datum/action/cooldown/spell/touch/before_cast(atom/cast_on)
return ..() | SPELL_NO_FEEDBACK | SPELL_NO_IMMEDIATE_COOLDOWN
@@ -114,12 +165,11 @@
*/
/datum/action/cooldown/spell/touch/proc/on_hand_hit(datum/source, atom/victim, mob/caster, proximity_flag, click_parameters)
SIGNAL_HANDLER
SHOULD_NOT_OVERRIDE(TRUE) // DEFINITELY don't put effects here, put them in cast_on_hand_hit
if(!proximity_flag)
return
if(victim == caster)
return
if(!can_cast_spell(feedback = FALSE))
if(!can_hit_with_hand(victim, caster))
return
INVOKE_ASYNC(src, PROC_REF(do_hand_hit), source, victim, caster)
@@ -131,22 +181,43 @@
*/
/datum/action/cooldown/spell/touch/proc/on_secondary_hand_hit(datum/source, atom/victim, mob/caster, proximity_flag, click_parameters)
SIGNAL_HANDLER
SHOULD_NOT_OVERRIDE(TRUE) // DEFINITELY don't put effects here, put them in cast_on_secondary_hand_hit
if(!proximity_flag)
return
if(victim == caster)
return
if(!can_cast_spell(feedback = FALSE))
if(!can_hit_with_hand(victim, caster))
return
INVOKE_ASYNC(src, PROC_REF(do_secondary_hand_hit), source, victim, caster)
return COMPONENT_SECONDARY_CANCEL_ATTACK_CHAIN
/// Checks if the passed victim can be cast on by the caster.
/datum/action/cooldown/spell/touch/proc/can_hit_with_hand(atom/victim, mob/caster)
if(!can_cast_on_self && victim == caster)
return FALSE
if(!is_valid_target(victim))
return FALSE
if(!can_cast_spell(feedback = TRUE))
return FALSE
return TRUE
/**
* Calls cast_on_hand_hit() from the caster onto the victim.
* It's worth noting that victim will be guaranteed to be whatever checks are implemented in is_valid_target by this point.
*
* Implements checks for antimagic.
*/
/datum/action/cooldown/spell/touch/proc/do_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster)
SHOULD_NOT_OVERRIDE(TRUE) // Don't put effects here, put them in cast_on_hand_hit
SEND_SIGNAL(src, COMSIG_SPELL_TOUCH_HAND_HIT, victim, caster, hand)
if(!cast_on_hand_hit(hand, victim, caster))
var/mob/mob_victim = victim
if(istype(mob_victim) && mob_victim.can_block_magic(antimagic_flags))
on_antimagic_triggered(hand, victim, caster)
else if(!cast_on_hand_hit(hand, victim, caster))
return
log_combat(caster, victim, "cast the touch spell [name] on", hand)
@@ -155,8 +226,13 @@
/**
* Calls do_secondary_hand_hit() from the caster onto the victim.
* It's worth noting that victim will be guaranteed to be whatever checks are implemented in is_valid_target by this point.
* Does NOT check for antimagic on its own. Implement your own checks if you want the r-click to abide by it.
*/
/datum/action/cooldown/spell/touch/proc/do_secondary_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster)
SHOULD_NOT_OVERRIDE(TRUE) // Don't put effects here, put them in cast_on_secondary_hand_hit
var/secondary_result = cast_on_secondary_hand_hit(hand, victim, caster)
switch(secondary_result)
// Continue will remove the hand here and stop
@@ -216,6 +292,22 @@
remove_hand(dropper, reset_cooldown_after = TRUE)
/**
* Called whenever our spell is cast, but blocked by antimagic.
*/
/datum/action/cooldown/spell/touch/proc/on_antimagic_triggered(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster)
return
/**
* ## Touch attack item
*
* Used for touch spells to have something physical to slap people with.
*
* Try to avoid adding behavior onto these for your touch spells!
* The spells themselves should handle most, if not all, of the casted effects.
*
* These should generally just be dummy objects - holds name and icon stuff.
*/
/obj/item/melee/touch_attack
name = "\improper outstretched hand"
desc = "High Five?"
@@ -224,7 +316,7 @@
righthand_file = 'icons/mob/inhands/items/touchspell_righthand.dmi'
icon_state = "latexballon"
inhand_icon_state = null
item_flags = NEEDS_PERMIT | ABSTRACT
item_flags = NEEDS_PERMIT | ABSTRACT | HAND_ITEM
w_class = WEIGHT_CLASS_HUGE
force = 0
throwforce = 0
@@ -243,7 +335,7 @@
if(!iscarbon(user)) //Look ma, no hands
return TRUE
if(!(user.mobility_flags & MOBILITY_USE))
to_chat(user, span_warning("You can't reach out!"))
user.balloon_alert(user, "can't reach out!")
return TRUE
return ..()
@@ -254,7 +346,7 @@
* However, if you want to consume the hand and not give a cooldown,
* such as adding a unique behavior to the hand specifically, this function will do that.
*/
/obj/item/melee/touch_attack/mansus_fist/proc/remove_hand_with_no_refund(mob/holder)
/obj/item/melee/touch_attack/proc/remove_hand_with_no_refund(mob/holder)
var/datum/action/cooldown/spell/touch/hand_spell = spell_which_made_us?.resolve()
if(!QDELETED(hand_spell))
hand_spell.remove_hand(holder, reset_cooldown_after = FALSE)
@@ -14,11 +14,7 @@
hand_path = /obj/item/melee/touch_attack/duffelbag
/datum/action/cooldown/spell/touch/duffelbag/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster)
if(!iscarbon(victim))
return FALSE
var/mob/living/carbon/duffel_victim = victim
/// Some meme "elaborate backstories" to use.
var/static/list/elaborate_backstory = list(
"spacewar origin story",
"military background",
@@ -28,52 +24,57 @@
"upbringing on the space farm",
"fond memories with your buddy Keith",
)
if(duffel_victim.can_block_magic(antimagic_flags))
to_chat(caster, span_warning("The spell can't seem to affect [duffel_victim]!"))
to_chat(duffel_victim, span_warning("You really don't feel like talking about your [pick(elaborate_backstory)] with complete strangers today."))
return TRUE
/datum/action/cooldown/spell/touch/duffelbag/is_valid_target(atom/cast_on)
return iscarbon(cast_on)
/datum/action/cooldown/spell/touch/duffelbag/on_antimagic_triggered(obj/item/melee/touch_attack/hand, mob/living/carbon/victim, mob/living/carbon/caster)
to_chat(caster, span_warning("The spell can't seem to affect [victim]!"))
to_chat(victim, span_warning("You really don't feel like talking about your [pick(elaborate_backstory)] with complete strangers today."))
/datum/action/cooldown/spell/touch/duffelbag/cast_on_hand_hit(obj/item/melee/touch_attack/hand, mob/living/carbon/victim, mob/living/carbon/caster)
// To get it started, stun and knockdown the person being hit
duffel_victim.flash_act()
duffel_victim.Immobilize(5 SECONDS)
duffel_victim.apply_damage(80, STAMINA)
duffel_victim.Knockdown(5 SECONDS)
victim.flash_act()
victim.Immobilize(5 SECONDS)
victim.apply_damage(80, STAMINA)
victim.Knockdown(5 SECONDS)
// If someone's already cursed, don't try to give them another
if(HAS_TRAIT(duffel_victim, TRAIT_DUFFEL_CURSE_PROOF))
to_chat(caster, span_warning("The burden of [duffel_victim]'s duffel bag becomes too much, shoving them to the floor!"))
to_chat(duffel_victim, span_warning("The weight of this bag becomes overburdening!"))
if(HAS_TRAIT(victim, TRAIT_DUFFEL_CURSE_PROOF))
to_chat(caster, span_warning("The burden of [victim]'s duffel bag becomes too much, shoving them to the floor!"))
to_chat(victim, span_warning("The weight of this bag becomes overburdening!"))
return TRUE
// However if they're uncursed, they're fresh for getting a cursed bag
var/obj/item/storage/backpack/duffelbag/cursed/conjured_duffel = new get_turf(victim)
duffel_victim.visible_message(
span_danger("A growling duffel bag appears on [duffel_victim]!"),
victim.visible_message(
span_danger("A growling duffel bag appears on [victim]!"),
span_danger("You feel something attaching itself to you, and a strong desire to discuss your [pick(elaborate_backstory)] at length!"),
)
// This duffelbag is now cuuuurrrsseed! Equip it on them
ADD_TRAIT(conjured_duffel, TRAIT_DUFFEL_CURSE_PROOF, CURSED_ITEM_TRAIT(conjured_duffel.name))
conjured_duffel.pickup(duffel_victim)
conjured_duffel.forceMove(duffel_victim)
conjured_duffel.pickup(victim)
conjured_duffel.forceMove(victim)
// Put it on their back first
if(duffel_victim.dropItemToGround(duffel_victim.back))
duffel_victim.equip_to_slot_if_possible(conjured_duffel, ITEM_SLOT_BACK, TRUE, TRUE)
if(victim.dropItemToGround(victim.back))
victim.equip_to_slot_if_possible(conjured_duffel, ITEM_SLOT_BACK, TRUE, TRUE)
return TRUE
// If the back equip failed, put it in their hands first
if(duffel_victim.put_in_hands(conjured_duffel))
if(victim.put_in_hands(conjured_duffel))
return TRUE
// If they had no empty hands, try to put it in their inactive hand first
duffel_victim.dropItemToGround(duffel_victim.get_inactive_held_item())
if(duffel_victim.put_in_hands(conjured_duffel))
victim.dropItemToGround(victim.get_inactive_held_item())
if(victim.put_in_hands(conjured_duffel))
return TRUE
// If their inactive hand couldn't be emptied or found, put it in their active hand
duffel_victim.dropItemToGround(duffel_victim.get_active_held_item())
if(duffel_victim.put_in_hands(conjured_duffel))
victim.dropItemToGround(victim.get_active_held_item())
if(victim.put_in_hands(conjured_duffel))
return TRUE
// Well, we failed to give them the duffel bag,
@@ -12,14 +12,13 @@
hand_path = /obj/item/melee/touch_attack/flesh_to_stone
/datum/action/cooldown/spell/touch/flesh_to_stone/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster)
if(!isliving(victim))
return FALSE
/datum/action/cooldown/spell/touch/flesh_to_stone/on_antimagic_triggered(obj/item/melee/touch_attack/hand, mob/living/victim, mob/living/carbon/caster)
to_chat(caster, span_warning("The spell can't seem to affect [victim]!"))
to_chat(victim, span_warning("You feel your flesh turn to stone for a moment, then revert back!"))
/datum/action/cooldown/spell/touch/flesh_to_stone/cast_on_hand_hit(obj/item/melee/touch_attack/hand, mob/living/victim, mob/living/carbon/caster)
var/mob/living/living_victim = victim
if(living_victim.can_block_magic(antimagic_flags))
to_chat(caster, span_warning("The spell can't seem to affect [victim]!"))
to_chat(victim, span_warning("You feel your flesh turn to stone for a moment, then revert back!"))
return TRUE
living_victim.Stun(4 SECONDS)
+20 -18
View File
@@ -14,26 +14,28 @@
hand_path = /obj/item/melee/touch_attack/smite
/datum/action/cooldown/spell/touch/smite/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster)
if(!isliving(victim))
return FALSE
/// Smite is pretty extravagant, so whenever we get casted, we blind everyone nearby.
/datum/action/cooldown/spell/touch/smite/proc/blind_everyone_nearby(mob/living/victim, atom/center)
do_sparks(sparks_amt, FALSE, get_turf(victim))
for(var/mob/living/nearby_spectator in view(caster, 7))
if(nearby_spectator == caster)
for(var/mob/living/nearby_spectator in view(center, 7))
if(nearby_spectator == center)
continue
nearby_spectator.flash_act(affect_silicon = FALSE)
var/mob/living/living_victim = victim
if(living_victim.can_block_magic(antimagic_flags))
caster.visible_message(
span_warning("The feedback blows [caster]'s arm off!"),
span_userdanger("The spell bounces from [living_victim]'s skin back into your arm!"),
)
caster.flash_act()
var/obj/item/bodypart/to_dismember = caster.get_holding_bodypart_of_item(hand)
to_dismember?.dismember()
return TRUE
/datum/action/cooldown/spell/touch/smite/on_antimagic_triggered(obj/item/melee/touch_attack/hand, mob/living/victim, mob/living/carbon/caster)
caster.visible_message(
span_warning("The feedback blows [caster]'s arm off!"),
span_userdanger("The spell bounces from [victim]'s skin back into your arm!"),
)
// Off goes the arm we were casting with!
var/obj/item/bodypart/to_dismember = caster.get_holding_bodypart_of_item(hand)
to_dismember?.dismember()
// And do the blind (us included)
caster.flash_act()
blind_everyone_nearby(caster, caster)
/datum/action/cooldown/spell/touch/smite/cast_on_hand_hit(obj/item/melee/touch_attack/hand, mob/living/victim, mob/living/carbon/caster)
blind_everyone_nearby(victim, caster)
if(ishuman(victim))
var/mob/living/carbon/human/human_victim = victim
@@ -45,8 +47,8 @@
new /obj/effect/gibspawner(get_turf(victim))
return TRUE
living_victim.investigate_log("has been gibbed by the smite spell.", INVESTIGATE_DEATHS)
living_victim.gib()
victim.investigate_log("has been gibbed by the smite spell.", INVESTIGATE_DEATHS)
victim.gib()
return TRUE
/obj/item/melee/touch_attack/smite
Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 125 KiB

+3 -1
View File
@@ -191,12 +191,14 @@
"ghost",
"god",
"goddess",
"heretic",
"lich",
"magic",
"manusus",
"nar nar",
"nar'sie",
"nar-nar",
"nar-sie",
"nar'sie",
"nightmare",
"ratvar",
"religion",
+9 -2
View File
@@ -18,6 +18,7 @@
#include "code\_compile_options.dm"
#include "code\_debugger.dm"
#include "code\world.dm"
#include "code\__DEFINES\_bitfields.dm"
#include "code\__DEFINES\_click.dm"
#include "code\__DEFINES\_globals.dm"
#include "code\__DEFINES\_helpers.dm"
@@ -40,7 +41,6 @@
#include "code\__DEFINES\art.dm"
#include "code\__DEFINES\atom_hud.dm"
#include "code\__DEFINES\basic_mobs.dm"
#include "code\__DEFINES\bitfields.dm"
#include "code\__DEFINES\blackmarket.dm"
#include "code\__DEFINES\blend_modes.dm"
#include "code\__DEFINES\blob_defines.dm"
@@ -2353,15 +2353,20 @@
#include "code\modules\antagonists\heretic\magic\eldritch_shapeshift.dm"
#include "code\modules\antagonists\heretic\magic\eldritch_telepathy.dm"
#include "code\modules\antagonists\heretic\magic\expand_sight.dm"
#include "code\modules\antagonists\heretic\magic\fire_blast.dm"
#include "code\modules\antagonists\heretic\magic\flesh_ascension.dm"
#include "code\modules\antagonists\heretic\magic\flesh_surgery.dm"
#include "code\modules\antagonists\heretic\magic\furious_steel.dm"
#include "code\modules\antagonists\heretic\magic\madness_touch.dm"
#include "code\modules\antagonists\heretic\magic\manse_link.dm"
#include "code\modules\antagonists\heretic\magic\mansus_grasp.dm"
#include "code\modules\antagonists\heretic\magic\mirror_walk.dm"
#include "code\modules\antagonists\heretic\magic\nightwatcher_rebirth.dm"
#include "code\modules\antagonists\heretic\magic\realignment.dm"
#include "code\modules\antagonists\heretic\magic\rust_construction.dm"
#include "code\modules\antagonists\heretic\magic\rust_wave.dm"
#include "code\modules\antagonists\heretic\magic\shadow_cloak.dm"
#include "code\modules\antagonists\heretic\magic\void_cold_cone.dm"
#include "code\modules\antagonists\heretic\magic\void_phase.dm"
#include "code\modules\antagonists\heretic\magic\void_pull.dm"
#include "code\modules\antagonists\heretic\mobs\maid_in_mirror.dm"
@@ -4405,7 +4410,10 @@
#include "code\modules\spells\spell_types\aoe_spell\magic_missile.dm"
#include "code\modules\spells\spell_types\aoe_spell\repulse.dm"
#include "code\modules\spells\spell_types\aoe_spell\sacred_flame.dm"
#include "code\modules\spells\spell_types\charged\_charged.dm"
#include "code\modules\spells\spell_types\charged\tesla_blast.dm"
#include "code\modules\spells\spell_types\cone\_cone.dm"
#include "code\modules\spells\spell_types\cone\cone_of_cold.dm"
#include "code\modules\spells\spell_types\conjure\_conjure.dm"
#include "code\modules\spells\spell_types\conjure\bees.dm"
#include "code\modules\spells\spell_types\conjure\carp.dm"
@@ -4448,7 +4456,6 @@
#include "code\modules\spells\spell_types\self\disable_tech.dm"
#include "code\modules\spells\spell_types\self\forcewall.dm"
#include "code\modules\spells\spell_types\self\lichdom.dm"
#include "code\modules\spells\spell_types\self\lightning.dm"
#include "code\modules\spells\spell_types\self\mime_vow.dm"
#include "code\modules\spells\spell_types\self\mutate.dm"
#include "code\modules\spells\spell_types\self\night_vision.dm"
@@ -136,12 +136,18 @@ const GuideSection = () => {
<Stack.Item>
- Follow your <span style={hereticRed}>Living Heart</span> to find
your targets. Bring them back to a&nbsp;
<span style={hereticGreen}>transmutation rune</span> to&nbsp;
<span style={hereticGreen}>transmutation rune</span> in critical&nbsp;
or worse condition to&nbsp;
<span style={hereticRed}>sacrifice</span> them for&nbsp;
<span style={hereticBlue}>knowledge points</span>. The Mansus{' '}
<b>ONLY</b> accepts targets pointed to by the&nbsp;
<span style={hereticRed}>Living Heart</span>.
</Stack.Item>
<Stack.Item>
- Make yourself a <span style={hereticYellow}>focus</span> to be&nbsp;
able to cast various advanced spells to assist you in acquire&nbsp;
harder and harder sacrifices.
</Stack.Item>
<Stack.Item>
- Accomplish all of your objectives to be able to learn the{' '}
<span style={hereticYellow}>final ritual</span>. Complete the ritual
@@ -304,7 +310,7 @@ export const AntagInfoHeretic = (props, context) => {
const [currentTab, setTab] = useLocalState(context, 'currentTab', 0);
return (
<Window width={675} height={600}>
<Window width={675} height={625}>
<Window.Content
style={{
// 'font-family': 'Times New Roman',