mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-01-09 00:13:55 +00:00
## About The Pull Request This PR was originally meant as a replacement for the Bloody Bastard blade, but then I stopped existing for 7 months. Now that I'm here again, I'm finishing the job once and for all. ### **HERETICS VERSUS CULTISTS** ### Heretics Heretics can now sacrifice cultists, which will give them one of three gifts: The Cursed Blade, the Crimson Focus, and the Rusted Harvester. The gifts given are weighted to be spread out equally with each type. They will also gain one knowledge point. - The Cursed Blade is a free heretic blade that is more powerful than the normal heretic blade, including a small block chance. It can also be used to draw heretic runes off combat mode. - The Crimson Focus is a necklace that grants focusing and a minor regeneration effect which also affects nearby heretics, at the cost of gaining the BLOODY_MESS trait while wearing it. Additionally, it can be squeezed to heal 50 points of brute/burn damage, injecting yourself with three to six (separately) units of Eldritch ~~Water~~ Essence and Unholy Water. Yes, this isn't good. - The Rusted Harvester is a heretic 'monster' summon. It's a normal Harvester, but instead of Area Conversion and Forcewall, it has Aggressive Spread and Rust Construction (Raise Wall). It can delimb, but only cultists, with a delay. It has an aura of decay, corroding the environment and withering enemies near it, but it's VERY fragile. Rusting cultist item dispensers will now cause them to turn into a Heretic object. Altars turn into small heretic runes, Archives turn into Codex Cicatrixi, Forges turn into Mawed Crucibles. Ideally, Heretics would be able to gain an amount of these new powers and use them to turn the tide against the cultists, amassing their power and almost forming a sect of their own in turn which sweeps over and converts the cult. ### Cultists When a Cultist sacrifices a heretic, two things will happen: - A new item will be available for creation at one of the dispensers. - The Heretic will be trapped inside a powerful Haunted Blade. `/obj/item/melee/cultblade/haunted` ` name = "haunted longsword"` ` desc = "An eerie sword with a blade that is less 'black' than it is 'absolute nothingness'. It glows with furious, restrained green energy."` This blade will be stronger across-the-board than a normal cult sword, and will even allow those who wield it to cast one heretic spell from their previous path. The only downside? The heretic can also cast one spell. It's up to the trapped spirit if it wants to help you, or be a nuisance. The unlocked items are: - The Cursed Blade, again. For cultists, it can be used to draw runes twice as fast as usual, and they can even right-click it to teleport to safety, just like a heretic! - The Crimson Focus, again. Cultists are twice as fast at carving spells into their body, and they gain a 5th spellslot as long as they wear the amulet. It still causes hemophilia and grants weak regeneration. - The Proteon Orb. This orb will create a gateway to Nar'sie's own realm, spawning one Proteon every 15 seconds, which ghosts can possess. The gateways cannot be placed close to one another. Originally, they were going to be able to create a Harvester Shell, but there were some concerns of it being too OP. The true Bastard sword has been fully deleted. The null rod conversion has been changed to a Bloody Halberd instead. I'm considering re-enabling Stun Hand on Heretics, with Mansus Grasp stats. ### Other All the items above can be used by both Heretics and Cultists, no matter how they were first created. Hell, even normal crew can use them! This is probably not the best idea a lot of the time, though. There are a lot of other changes in this PR. A loooooot. I will likely miss some in the changelog, but I'll try to be as thorough as possible. There's probably also some leftover garbo that I didn't find and clear out yet. ## Why It's Good For The Game Cult and Heretics, despite being mortally opposed, have very few interactions with eachother, especially now that the Blade's gone. The only thing of note is just the Heretic's unfair complete resistance to stun hand, which is only marginally better than the alternative. This PR will reintroduce their animosity, and give both sides a very, very good reason to fight eachother. The Cult will gain a sick sword that keeps the heretic in the game, and unlike with the original implementation, will recieve a cult-wide bonus in the form of a powerful, well deserved, and fun new item to summon. The Heretic will gain powerful trinkets and knowledge from the sacrifices, incentivizing them to become a terrifying cult-hunter. And if they do succeed in wiping out the cult, they will have quite the rewards to help with their ascension. The crew, while mostly unaffected, will have a damn good reason to not just Side with the heretic, out of fear of what they may become after the cult is stomped down. They can also use a few of the items here in an attempt to get one up on either side, as long as they manage to stay clear of the side-effects. Let the heretics eradicate the apostates. Let the cultists root out the heathens.  The haunted longsword creates an aura of darkness (disabled for the cultist for the image) Sprites... are not great. Hopefully someone comes by and improves them. code: Added get_inactive_hand() as an easy shortcut for carbons code: Wall walker element can now accept a trait for wall-checking fix: Fixed soulsword component being unable to invoke the post summon callback refactor: Turned Heretic rust turf healing into an element, given to Rust Walkers and Rusted Harvesters refactor: Converted Limb Amputation from an element to a component Blade and Sword sprites by meyhaza!!! I did the inhands though. Cuz im cool
503 lines
20 KiB
Plaintext
503 lines
20 KiB
Plaintext
/**
|
|
* # The spell action
|
|
*
|
|
* This is the base action for how many of the game's
|
|
* spells (and spell adjacent) abilities function.
|
|
* These spells function off of a cooldown-based system.
|
|
*
|
|
* ## Pre-spell checks:
|
|
* - [can_cast_spell][/datum/action/cooldown/spell/can_cast_spell] checks if the OWNER
|
|
* of the spell is able to cast the spell.
|
|
* - [is_valid_target][/datum/action/cooldown/spell/is_valid_target] checks if the TARGET
|
|
* THE SPELL IS BEING CAST ON is a valid target for the spell. NOTE: The CAST TARGET is often THE SAME as THE OWNER OF THE SPELL,
|
|
* but is not always - depending on how [Pre Activate][/datum/action/cooldown/spell/PreActivate] is resolved.
|
|
* - [try_invoke][/datum/action/cooldown/spell/try_invoke] is run in can_cast_spell to check if
|
|
* the OWNER of the spell is able to say the current invocation.
|
|
*
|
|
* ## The spell chain:
|
|
* - [before_cast][/datum/action/cooldown/spell/before_cast] is the last chance for being able
|
|
* to interrupt a spell cast. This returns a bitflag. if SPELL_CANCEL_CAST is set, the spell will not continue.
|
|
* - [spell_feedback][/datum/action/cooldown/spell/spell_feedback] is called right before cast, and handles
|
|
* invocation and sound effects. Overridable, if you want a special method of invocation or sound effects,
|
|
* or you want your spell to handle invocation / sound via special means.
|
|
* - [cast][/datum/action/cooldown/spell/cast] is where the brunt of the spell effects should be done
|
|
* and implemented.
|
|
* - [after_cast][/datum/action/cooldown/spell/after_cast] is the aftermath - final effects that follow
|
|
* the main cast of the spell. By now, the spell cooldown has already started
|
|
*
|
|
* ## Other procs called / may be called within the chain:
|
|
* - [invocation][/datum/action/cooldown/spell/invocation] handles saying any vocal (or emotive) invocations the spell
|
|
* may have, and can be overriden or extended. Called by spell_feedback.
|
|
* - [reset_spell_cooldown][/datum/action/cooldown/spell/reset_spell_cooldown] is a way to handle reverting a spell's
|
|
* cooldown and making it ready again if it fails to go off at any point. Not called anywhere by default. If you
|
|
* want to cancel a spell in before_cast and would like the cooldown restart, call this.
|
|
*
|
|
* ## Other procs of note:
|
|
* - [level_spell][/datum/action/cooldown/spell/level_spell] is where the process of adding a spell level is handled.
|
|
* this can be extended if you wish to add unique effects on level up for wizards.
|
|
* - [delevel_spell][/datum/action/cooldown/spell/delevel_spell] is where the process of removing a spell level is handled.
|
|
* this can be extended if you wish to undo unique effects on level up for wizards.
|
|
* - [get_spell_title][/datum/action/cooldown/spell/get_spell_title] returns the prefix of the spell name based on its level,
|
|
* for use in updating the button name / spell name.
|
|
*/
|
|
/datum/action/cooldown/spell
|
|
name = "Spell"
|
|
desc = "A wizard spell."
|
|
background_icon_state = "bg_spell"
|
|
button_icon = 'icons/mob/actions/actions_spells.dmi'
|
|
button_icon_state = "spell_default"
|
|
overlay_icon_state = "bg_spell_border"
|
|
active_overlay_icon_state = "bg_spell_border_active_red"
|
|
check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_PHASED
|
|
panel = "Spells"
|
|
melee_cooldown_time = 0 SECONDS
|
|
|
|
/// The sound played on cast.
|
|
var/sound = null
|
|
/// The school of magic the spell belongs to.
|
|
/// Checked by some holy sects to punish the
|
|
/// caster for casting things that do not align
|
|
/// with their sect's alignment - see magic.dm in defines to learn more
|
|
var/school = SCHOOL_UNSET
|
|
/// If the spell uses the wizard spell rank system, the cooldown reduction per rank of the spell
|
|
var/cooldown_reduction_per_rank = 0 SECONDS
|
|
/// What is uttered when the user casts the spell
|
|
var/invocation
|
|
/// What is shown in chat when the user casts the spell, only matters for INVOCATION_EMOTE
|
|
var/invocation_self_message
|
|
/// if true, doesn't garble the invocation sometimes with backticks
|
|
var/garbled_invocation_prob = 50
|
|
/// What type of invocation the spell is.
|
|
/// Can be "none", "whisper", "shout", "emote"
|
|
var/invocation_type = INVOCATION_NONE
|
|
/// Flag for certain states that the spell requires the user be in to cast.
|
|
var/spell_requirements = SPELL_REQUIRES_WIZARD_GARB|SPELL_REQUIRES_NO_ANTIMAGIC
|
|
/// This determines what type of antimagic is needed to block the spell.
|
|
/// (MAGIC_RESISTANCE, MAGIC_RESISTANCE_MIND, MAGIC_RESISTANCE_HOLY)
|
|
/// If SPELL_REQUIRES_NO_ANTIMAGIC is set in Spell requirements,
|
|
/// The spell cannot be cast if the caster has any of the antimagic flags set.
|
|
var/antimagic_flags = MAGIC_RESISTANCE
|
|
/// The current spell level, if taken multiple times by a wizard
|
|
var/spell_level = 1
|
|
/// The max possible spell level
|
|
var/spell_max_level = 5
|
|
/// If set to a positive number, the spell will produce sparks when casted.
|
|
var/sparks_amt = 0
|
|
/// The typepath of the smoke to create on cast.
|
|
var/smoke_type
|
|
/// The amount of smoke to create on cast. This is a range, so a value of 5 will create enough smoke to cover everything within 5 steps.
|
|
var/smoke_amt = 0
|
|
|
|
/datum/action/cooldown/spell/Grant(mob/grant_to)
|
|
// If our spell is mind-bound, we only wanna grant it to our mind
|
|
if(istype(target, /datum/mind))
|
|
var/datum/mind/mind_target = target
|
|
if(mind_target.current != grant_to)
|
|
return
|
|
|
|
. = ..()
|
|
if(!owner)
|
|
return
|
|
|
|
// Register some signals so our button's icon stays up to date
|
|
if(spell_requirements & SPELL_REQUIRES_STATION)
|
|
RegisterSignal(owner, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(update_status_on_signal))
|
|
if(spell_requirements & (SPELL_REQUIRES_NO_ANTIMAGIC|SPELL_REQUIRES_WIZARD_GARB))
|
|
RegisterSignals(owner, list(COMSIG_MOB_EQUIPPED_ITEM, COMSIG_MOB_UNEQUIPPED_ITEM), PROC_REF(update_status_on_signal))
|
|
if(invocation_type == INVOCATION_EMOTE)
|
|
RegisterSignals(owner, list(SIGNAL_ADDTRAIT(TRAIT_EMOTEMUTE), SIGNAL_REMOVETRAIT(TRAIT_EMOTEMUTE)), PROC_REF(update_status_on_signal))
|
|
if(invocation_type == INVOCATION_SHOUT || invocation_type == INVOCATION_WHISPER)
|
|
RegisterSignals(owner, list(SIGNAL_ADDTRAIT(TRAIT_MUTE), SIGNAL_REMOVETRAIT(TRAIT_MUTE)), PROC_REF(update_status_on_signal))
|
|
|
|
RegisterSignals(owner, list(COMSIG_MOB_ENTER_JAUNT, COMSIG_MOB_AFTER_EXIT_JAUNT), PROC_REF(update_status_on_signal))
|
|
owner.client?.stat_panel.send_message("check_spells")
|
|
|
|
/datum/action/cooldown/spell/Remove(mob/living/remove_from)
|
|
|
|
remove_from.client?.stat_panel.send_message("check_spells")
|
|
UnregisterSignal(remove_from, list(
|
|
COMSIG_MOB_AFTER_EXIT_JAUNT,
|
|
COMSIG_MOB_ENTER_JAUNT,
|
|
COMSIG_MOB_EQUIPPED_ITEM,
|
|
COMSIG_MOB_UNEQUIPPED_ITEM,
|
|
COMSIG_MOVABLE_Z_CHANGED,
|
|
SIGNAL_ADDTRAIT(TRAIT_EMOTEMUTE),
|
|
SIGNAL_REMOVETRAIT(TRAIT_EMOTEMUTE),
|
|
SIGNAL_ADDTRAIT(TRAIT_MUTE),
|
|
SIGNAL_REMOVETRAIT(TRAIT_MUTE),
|
|
))
|
|
|
|
return ..()
|
|
|
|
/datum/action/cooldown/spell/IsAvailable(feedback = FALSE)
|
|
return ..() && can_cast_spell(feedback)
|
|
|
|
/datum/action/cooldown/spell/set_click_ability(mob/on_who)
|
|
if(SEND_SIGNAL(on_who, COMSIG_MOB_SPELL_ACTIVATED, src) & SPELL_CANCEL_CAST)
|
|
return FALSE
|
|
|
|
return ..()
|
|
|
|
// Where the cast chain starts
|
|
/datum/action/cooldown/spell/PreActivate(atom/target)
|
|
if(SEND_SIGNAL(owner, COMSIG_MOB_ABILITY_STARTED, src, target) & COMPONENT_BLOCK_ABILITY_START)
|
|
return FALSE
|
|
if(target == owner)
|
|
target = get_caster_from_target(target)
|
|
if(isnull(target) || !is_valid_target(target))
|
|
return FALSE
|
|
|
|
return Activate(target)
|
|
|
|
/// Checks if the owner of the spell can currently cast it.
|
|
/// Does not check anything involving potential targets.
|
|
/datum/action/cooldown/spell/proc/can_cast_spell(feedback = TRUE)
|
|
if(!owner)
|
|
CRASH("[type] - can_cast_spell called on a spell without an owner!")
|
|
|
|
// Certain spells are not allowed on the centcom zlevel
|
|
var/turf/caster_turf = get_turf(owner)
|
|
// Spells which require being on the station
|
|
if((spell_requirements & SPELL_REQUIRES_STATION) && !is_station_level(caster_turf.z))
|
|
if(feedback)
|
|
to_chat(owner, span_warning("You can't cast [src] here!"))
|
|
return FALSE
|
|
|
|
if((spell_requirements & SPELL_REQUIRES_MIND) && !owner.mind)
|
|
// No point in feedback here, as mindless mobs aren't players
|
|
return FALSE
|
|
|
|
if((spell_requirements & SPELL_REQUIRES_MIME_VOW) && !HAS_MIND_TRAIT(owner, TRAIT_MIMING))
|
|
// In the future this can be moved out of spell checks exactly
|
|
if(feedback)
|
|
to_chat(owner, span_warning("You must dedicate yourself to silence first!"))
|
|
return FALSE
|
|
|
|
// If the spell requires the user has no antimagic equipped, and they're holding antimagic
|
|
// that corresponds with the spell's antimagic, then they can't actually cast the spell
|
|
if((spell_requirements & SPELL_REQUIRES_NO_ANTIMAGIC) && !owner.can_cast_magic(antimagic_flags))
|
|
if(feedback)
|
|
to_chat(owner, span_warning("Some form of antimagic is preventing you from casting [src]!"))
|
|
return FALSE
|
|
|
|
if(!try_invoke(owner, feedback = feedback))
|
|
return FALSE
|
|
|
|
if(ishuman(owner))
|
|
if(spell_requirements & SPELL_REQUIRES_WIZARD_GARB)
|
|
var/mob/living/carbon/human/human_owner = owner
|
|
if(!(human_owner.wear_suit?.clothing_flags & CASTING_CLOTHES) && !ismonkey(human_owner)) // Monkeys don't need robes to cast as they are inherently imbued with power from the banana dimension
|
|
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) && !(human_owner.glasses?.clothing_flags & CASTING_CLOTHES))
|
|
if(feedback)
|
|
to_chat(owner, span_warning("You don't feel strong enough without your hat!"))
|
|
return FALSE
|
|
|
|
else
|
|
// If you strictly need to be a human, well, goodbye.
|
|
if(spell_requirements & SPELL_REQUIRES_HUMAN)
|
|
if(feedback)
|
|
to_chat(owner, span_warning("[src] can only be cast by humans!"))
|
|
return FALSE
|
|
|
|
// Otherwise, we can check for contents if they have wizardly apparel. This isn't *quite* perfect, but it'll do, especially since many of the edge cases (gorilla holding a wizard hat) still more or less make sense.
|
|
if(spell_requirements & SPELL_REQUIRES_WIZARD_GARB)
|
|
var/any_casting = FALSE
|
|
for(var/obj/item/clothing/item in owner)
|
|
if(item.clothing_flags & CASTING_CLOTHES)
|
|
any_casting = TRUE
|
|
break
|
|
|
|
if(!any_casting)
|
|
if(feedback)
|
|
to_chat(owner, span_warning("You don't feel strong enough without your hat!"))
|
|
return FALSE
|
|
|
|
if(!(spell_requirements & SPELL_CASTABLE_AS_BRAIN) && isbrain(owner))
|
|
if(feedback)
|
|
to_chat(owner, span_warning("[src] can't be cast in this state!"))
|
|
return FALSE
|
|
|
|
return TRUE
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
/datum/action/cooldown/spell/proc/is_valid_target(atom/cast_on)
|
|
return TRUE
|
|
|
|
/**
|
|
* Used to get the cast_on atom if a self cast spell is being cast.
|
|
*
|
|
* Allows for some atoms to be used as casting sources if a spell caster is located within.
|
|
*/
|
|
/datum/action/cooldown/spell/proc/get_caster_from_target(atom/target)
|
|
var/atom/cast_loc = target.loc
|
|
if(isnull(cast_loc))
|
|
return null // No magic in nullspace
|
|
|
|
if(isturf(cast_loc))
|
|
return target // They're just standing around, proceed as normal
|
|
|
|
if(HAS_TRAIT(cast_loc, TRAIT_CASTABLE_LOC))
|
|
if(HAS_TRAIT(cast_loc, TRAIT_SPELLS_TRANSFER_TO_LOC) && ismob(cast_loc.loc))
|
|
return cast_loc.loc
|
|
else
|
|
return cast_loc
|
|
// They're in an atom which allows casting, so redirect the caster to loc
|
|
|
|
return null
|
|
|
|
// The actual cast chain occurs here, in Activate().
|
|
// You should generally not be overriding or extending Activate() for spells.
|
|
// Defer to any of the cast chain procs instead.
|
|
/datum/action/cooldown/spell/Activate(atom/cast_on)
|
|
SHOULD_NOT_OVERRIDE(TRUE)
|
|
|
|
// Pre-casting of the spell
|
|
// Pre-cast is the very last chance for a spell to cancel
|
|
// Stuff like target input can go here.
|
|
var/precast_result = before_cast(cast_on)
|
|
if(precast_result & SPELL_CANCEL_CAST)
|
|
return FALSE
|
|
|
|
// Spell is officially being cast
|
|
if(!(precast_result & SPELL_NO_FEEDBACK))
|
|
// We do invocation and sound effects here, before actual cast
|
|
// That way stuff like teleports or shape-shifts can be invoked before ocurring
|
|
spell_feedback(owner)
|
|
|
|
// Actually cast the spell. Main effects go here
|
|
cast(cast_on)
|
|
|
|
if(!(precast_result & SPELL_NO_IMMEDIATE_COOLDOWN))
|
|
// The entire spell is done, start the actual cooldown at its set duration
|
|
StartCooldown()
|
|
|
|
// And then proceed with the aftermath of the cast
|
|
// Final effects that happen after all the casting is done can go here
|
|
after_cast(cast_on)
|
|
build_all_button_icons()
|
|
|
|
return TRUE
|
|
|
|
/**
|
|
* Actions done before the actual cast is called.
|
|
* This is the last chance to cancel the spell from being cast.
|
|
*
|
|
* Can be used for target selection or to validate checks on the caster (cast_on).
|
|
*
|
|
* Returns a bitflag.
|
|
* - SPELL_CANCEL_CAST will stop the spell from being cast.
|
|
* - SPELL_NO_FEEDBACK will prevent the spell from calling [proc/spell_feedback] on cast. (invocation), sounds)
|
|
* - SPELL_NO_IMMEDIATE_COOLDOWN will prevent the spell from starting its cooldown between cast and before after_cast.
|
|
*/
|
|
/datum/action/cooldown/spell/proc/before_cast(atom/cast_on)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
|
|
// Bonus invocation check done here:
|
|
// If the caster has no tongue and it's a verbal spell,
|
|
// Or has no hands and is a gesture spell - cancel it,
|
|
// and show a funny message that they tried
|
|
if(ishuman(owner) && !(spell_requirements & SPELL_CASTABLE_WITHOUT_INVOCATION))
|
|
var/mob/living/carbon/human/caster = owner
|
|
switch(invocation_type)
|
|
if(INVOCATION_WHISPER, INVOCATION_SHOUT)
|
|
if(!caster.get_organ_slot(ORGAN_SLOT_TONGUE))
|
|
invocation(caster)
|
|
to_chat(caster, span_warning("Your lack of tongue is making it difficult to say the correct words to cast [src]..."))
|
|
StartCooldown(2 SECONDS)
|
|
return SPELL_CANCEL_CAST
|
|
|
|
if(INVOCATION_EMOTE)
|
|
if(caster.usable_hands <= 0)
|
|
var/arm_describer = (caster.num_hands >= 2 ? "arms limply" : (caster.num_hands == 1 ? "arm wildly" : "arm stumps"))
|
|
caster.visible_message(
|
|
span_warning("[caster] wiggles around [caster.p_their()] [arm_describer]."),
|
|
ignored_mobs = caster,
|
|
)
|
|
to_chat(caster, span_warning("You can't position your hands correctly to invoke [src][caster.num_hands > 0 ? "" : ", as you have none"]..."))
|
|
StartCooldown(2 SECONDS)
|
|
return SPELL_CANCEL_CAST
|
|
|
|
var/sig_return = SEND_SIGNAL(src, COMSIG_SPELL_BEFORE_CAST, cast_on)
|
|
if(owner)
|
|
sig_return |= SEND_SIGNAL(owner, COMSIG_MOB_BEFORE_SPELL_CAST, src, cast_on)
|
|
|
|
return sig_return
|
|
|
|
/**
|
|
* Actions done as the main effect of the spell.
|
|
*
|
|
* For spells without a click intercept, [cast_on] will be the owner.
|
|
* For click spells, [cast_on] is whatever the owner clicked on in casting the spell.
|
|
*/
|
|
/datum/action/cooldown/spell/proc/cast(atom/cast_on)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
|
|
SEND_SIGNAL(src, COMSIG_SPELL_CAST, cast_on)
|
|
if(owner)
|
|
SEND_SIGNAL(owner, COMSIG_MOB_CAST_SPELL, src, cast_on)
|
|
if(owner.ckey)
|
|
owner.log_message("cast the spell [name][cast_on != owner ? " on / at [cast_on]":""].", LOG_ATTACK)
|
|
|
|
/**
|
|
* Actions done after the main cast is finished.
|
|
* This is called after the cooldown's already begun.
|
|
*
|
|
* It can be used to apply late spell effects where order matters
|
|
* (for example, causing smoke *after* a teleport occurs in cast())
|
|
* or to clean up variables or references post-cast.
|
|
*/
|
|
/datum/action/cooldown/spell/proc/after_cast(atom/cast_on)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
if(!owner) // Could have been destroyed by the effect of the spell
|
|
SEND_SIGNAL(src, COMSIG_SPELL_AFTER_CAST, cast_on)
|
|
return
|
|
|
|
if(sparks_amt)
|
|
do_sparks(sparks_amt, FALSE, get_turf(owner))
|
|
if(ispath(smoke_type, /datum/effect_system/fluid_spread/smoke))
|
|
var/datum/effect_system/fluid_spread/smoke/smoke = new smoke_type()
|
|
smoke.set_up(smoke_amt, holder = owner, location = get_turf(owner))
|
|
smoke.start()
|
|
|
|
// Send signals last in case they delete the spell
|
|
SEND_SIGNAL(owner, COMSIG_MOB_AFTER_SPELL_CAST, src, cast_on)
|
|
SEND_SIGNAL(src, COMSIG_SPELL_AFTER_CAST, cast_on)
|
|
|
|
/// Provides feedback after a spell cast occurs, in the form of a cast sound and/or invocation
|
|
/datum/action/cooldown/spell/proc/spell_feedback(mob/living/invoker)
|
|
if(!invoker)
|
|
return
|
|
|
|
///even INVOCATION_NONE should go through this because the signal might change that
|
|
invocation(invoker)
|
|
playsound(invoker, sound, 50, vary = TRUE)
|
|
|
|
/// The invocation that accompanies the spell, called from spell_feedback() before cast().
|
|
/datum/action/cooldown/spell/proc/invocation(mob/living/invoker)
|
|
//lists can be sent by reference, a string would be sent by value
|
|
var/list/invocation_list = list(invocation, invocation_type, garbled_invocation_prob)
|
|
SEND_SIGNAL(invoker, COMSIG_MOB_PRE_INVOCATION, src, invocation_list)
|
|
var/used_invocation_message = invocation_list[INVOCATION_MESSAGE]
|
|
var/used_invocation_type = invocation_list[INVOCATION_TYPE]
|
|
var/used_invocation_garble_prob = invocation_list[INVOCATION_GARBLE_PROB]
|
|
|
|
switch(used_invocation_type)
|
|
if(INVOCATION_SHOUT)
|
|
if(prob(used_invocation_garble_prob))
|
|
invoker.say(replacetext(used_invocation_message," ","`"), forced = "spell ([src])")
|
|
else
|
|
invoker.say(used_invocation_message, forced = "spell ([src])")
|
|
|
|
if(INVOCATION_WHISPER)
|
|
if(prob(used_invocation_garble_prob))
|
|
invoker.whisper(replacetext(used_invocation_message," ","`"), forced = "spell ([src])")
|
|
else
|
|
invoker.whisper(used_invocation_message, forced = "spell ([src])")
|
|
|
|
if(INVOCATION_EMOTE)
|
|
invoker.visible_message(used_invocation_message, invocation_self_message)
|
|
|
|
/// Checks if the current OWNER of the spell is in a valid state to say the spell's invocation
|
|
/datum/action/cooldown/spell/proc/try_invoke(mob/living/invoker, feedback = TRUE)
|
|
if(spell_requirements & SPELL_CASTABLE_WITHOUT_INVOCATION)
|
|
return TRUE
|
|
|
|
if(invocation_type == INVOCATION_NONE)
|
|
return TRUE
|
|
|
|
// If you want a spell usable by ghosts for some reason, it must be INVOCATION_NONE
|
|
if(!istype(invoker))
|
|
if(feedback)
|
|
to_chat(invoker, span_warning("You need to be living to invoke [src]!"))
|
|
return FALSE
|
|
|
|
var/invoke_sig_return = SEND_SIGNAL(invoker, COMSIG_MOB_TRY_INVOKE_SPELL, src, feedback)
|
|
if(invoke_sig_return & SPELL_INVOCATION_ALWAYS_SUCCEED)
|
|
return TRUE // skips all of the following checks
|
|
if(invoke_sig_return & SPELL_INVOCATION_FAIL)
|
|
return FALSE
|
|
|
|
if(invocation_type == INVOCATION_EMOTE && HAS_TRAIT(invoker, TRAIT_EMOTEMUTE))
|
|
if(feedback)
|
|
to_chat(invoker, span_warning("You can't position your hands correctly to invoke [src]!"))
|
|
return FALSE
|
|
|
|
if((invocation_type == INVOCATION_WHISPER || invocation_type == INVOCATION_SHOUT) && !invoker.can_speak())
|
|
if(feedback)
|
|
to_chat(invoker, span_warning("You can't get the words out to invoke [src]!"))
|
|
return FALSE
|
|
|
|
return TRUE
|
|
|
|
/// Resets the cooldown of the spell, sending COMSIG_SPELL_CAST_RESET
|
|
/// and allowing it to be used immediately (+ updating button icon accordingly)
|
|
/datum/action/cooldown/spell/proc/reset_spell_cooldown()
|
|
SEND_SIGNAL(src, COMSIG_SPELL_CAST_RESET)
|
|
next_use_time -= cooldown_time // Basically, ensures that the ability can be used now
|
|
build_all_button_icons()
|
|
|
|
/**
|
|
* Levels the spell up a single level, reducing the cooldown.
|
|
* If bypass_cap is TRUE, will level the spell up past it's set cap.
|
|
*/
|
|
/datum/action/cooldown/spell/proc/level_spell(bypass_cap = FALSE)
|
|
// Spell cannot be levelled
|
|
if(spell_max_level <= 1)
|
|
return FALSE
|
|
|
|
// Spell is at cap, and we will not bypass it
|
|
if(!bypass_cap && (spell_level >= spell_max_level))
|
|
return FALSE
|
|
|
|
spell_level++
|
|
cooldown_time = max(cooldown_time - cooldown_reduction_per_rank, 0.25 SECONDS) // 0 second CD starts to break things.
|
|
name = "[get_spell_title()][initial(name)]"
|
|
build_all_button_icons(UPDATE_BUTTON_NAME)
|
|
return TRUE
|
|
|
|
/**
|
|
* Levels the spell down a single level, down to 1.
|
|
*/
|
|
/datum/action/cooldown/spell/proc/delevel_spell()
|
|
// Spell cannot be levelled
|
|
if(spell_max_level <= 1)
|
|
return FALSE
|
|
|
|
if(spell_level <= 1)
|
|
return FALSE
|
|
|
|
spell_level--
|
|
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))
|
|
|
|
name = "[get_spell_title()][initial(name)]"
|
|
build_all_button_icons(UPDATE_BUTTON_NAME)
|
|
return TRUE
|
|
|
|
/// Gets the title of the spell based on its level.
|
|
/datum/action/cooldown/spell/proc/get_spell_title()
|
|
switch(spell_level)
|
|
if(2)
|
|
return "Efficient "
|
|
if(3)
|
|
return "Quickened "
|
|
if(4)
|
|
return "Free "
|
|
if(5)
|
|
return "Instant "
|
|
if(6)
|
|
return "Ludicrous "
|
|
|
|
return ""
|