mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-23 05:00:55 +01:00
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:
@@ -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)
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+20
-19
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
<span style={hereticGreen}>transmutation rune</span> to
|
||||
<span style={hereticGreen}>transmutation rune</span> in critical
|
||||
or worse condition to
|
||||
<span style={hereticRed}>sacrifice</span> them for
|
||||
<span style={hereticBlue}>knowledge points</span>. The Mansus{' '}
|
||||
<b>ONLY</b> accepts targets pointed to by the
|
||||
<span style={hereticRed}>Living Heart</span>.
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
- Make yourself a <span style={hereticYellow}>focus</span> to be
|
||||
able to cast various advanced spells to assist you in acquire
|
||||
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',
|
||||
|
||||
Reference in New Issue
Block a user