diff --git a/code/__DEFINES/dcs/signals/signals_spell.dm b/code/__DEFINES/dcs/signals/signals_spell.dm index 5328849d809..8fa567028d7 100644 --- a/code/__DEFINES/dcs/signals/signals_spell.dm +++ b/code/__DEFINES/dcs/signals/signals_spell.dm @@ -33,6 +33,8 @@ // Spell type signals // Pointed projectiles +// Sent from /datum/action/cooldown/spell/pointed/projectile/fire_projectile() to the caster: (datum/action/cooldown/spell/spell, atom/cast_on, obj/projectile/to_fire) +#define COMSIG_MOB_SPELL_PROJECTILE "mob_spell_projectile" /// Sent from /datum/action/cooldown/spell/pointed/projectile/on_cast_hit: (atom/firer, atom/target, atom/hit, angle, hit_limb) #define COMSIG_SPELL_PROJECTILE_HIT "spell_projectile_hit" diff --git a/code/__DEFINES/magic.dm b/code/__DEFINES/magic.dm index d4b10d7aa19..e92e17ea23c 100644 --- a/code/__DEFINES/magic.dm +++ b/code/__DEFINES/magic.dm @@ -26,6 +26,8 @@ #define SCHOOL_NECROMANCY "necromancy" /// Other forbidden magics, such as heretic spells #define SCHOOL_FORBIDDEN "forbidden" +/// Blood magic, involves vampirism, draining blood, etc. +#define SCHOOL_SANGUINE "sanguine" // Invocation types - what does the wizard need to do to invoke (cast) the spell? /// Allows being able to cast the spell without saying or doing anything. diff --git a/code/datums/components/splattercasting.dm b/code/datums/components/splattercasting.dm new file mode 100644 index 00000000000..ade34e99fc1 --- /dev/null +++ b/code/datums/components/splattercasting.dm @@ -0,0 +1,97 @@ +///how much we multiply cooldown (deciseconds) by to get the amount of blood to remove. +///BLOOD_VOLUME_NORMAL is 560, expensive spells max out at around 60 seconds which is 600 deciseconds +///removing 9/10ths of the cooldown from that puts us at 540 deciseconds, mult by 0.5 gives 270 blood taken +///one second is worth 5 blood, roughly half of your normal amount of blood taken for a huge spell, seems fair +#define COOLDOWN_TO_BLOOD_RATIO 0.5 + +/** + * # splattercasting component! + * + * Component that makes casted spells cost blood from the user and dramatically lowers their cooldown. + */ +/datum/component/splattercasting + +/datum/component/splattercasting/Initialize() + if(!iscarbon(parent)) + return COMPONENT_INCOMPATIBLE + +/datum/component/splattercasting/RegisterWithParent() + . = ..() + RegisterSignal(parent, COMSIG_SPECIES_LOSS, .proc/on_species_change) + RegisterSignal(parent, COMSIG_MOB_SPELL_PROJECTILE, .proc/on_spell_projectile) + RegisterSignal(parent, COMSIG_MOB_BEFORE_SPELL_CAST, .proc/on_before_spell_cast) + RegisterSignal(parent, COMSIG_MOB_AFTER_SPELL_CAST, .proc/on_after_spell_cast) + +/datum/component/splattercasting/UnregisterFromParent() + . = ..() + UnregisterSignal(parent, list(COMSIG_SPECIES_LOSS, COMSIG_MOB_SPELL_PROJECTILE, COMSIG_MOB_BEFORE_SPELL_CAST, COMSIG_MOB_AFTER_SPELL_CAST)) + +///signal sent when a spell casts a projectile +/datum/component/splattercasting/proc/on_species_change(mob/living/carbon/source, datum/species/lost_species) + SIGNAL_HANDLER + qdel(src) + +///signal sent when the parent casts a spell that has a projectile +/datum/component/splattercasting/proc/on_spell_projectile(mob/living/carbon/source, datum/action/cooldown/spell/spell, atom/cast_on, obj/projectile/to_fire) + SIGNAL_HANDLER + + if(spell.school == SCHOOL_SANGUINE) + //already has blood themed projectiles + return + + playsound(source, 'sound/effects/wounds/splatter.ogg', 60, TRUE, -1) + to_fire.color = "#ff7070" + to_fire.name = "blood-[to_fire.name]" + to_fire.set_light(2, 2, LIGHT_COLOR_BLOOD_MAGIC, TRUE) + +///signal sent before parent casts a spell +/datum/component/splattercasting/proc/on_before_spell_cast(mob/living/carbon/source, datum/action/cooldown/spell/spell, atom/cast_on) + SIGNAL_HANDLER + + var/changed_spell = FALSE + if(!(spell.spell_requirements & SPELL_REQUIRES_NO_ANTIMAGIC)) + spell.spell_requirements |= SPELL_REQUIRES_NO_ANTIMAGIC + changed_spell = TRUE + if(!(spell.antimagic_flags & MAGIC_RESISTANCE_HOLY)) + spell.antimagic_flags |= MAGIC_RESISTANCE_HOLY + changed_spell = TRUE + + if(changed_spell) + //we changed some kind of antimagic so we should check if the new version of the spell is still valid. + //since can_cast_spell has already been checked before "before spell cast" only antimagic check should fail + if(!spell.can_cast_spell(feedback = TRUE)) + return SPELL_CANCEL_CAST + +///signal sent after parent casts a spell +/datum/component/splattercasting/proc/on_after_spell_cast(mob/living/carbon/source, datum/action/cooldown/spell/spell, atom/cast_on) + SIGNAL_HANDLER + + if(spell.school == SCHOOL_SANGUINE) + //allows for sanguine spells that work specially with blood to not interact with splattercasting. + //might sound weird, but maybe in the future we'll have a spell that adds blood to the user when it hits a target + //we wouldn't want that to cost blood. + return + + //normal cooldown spell has + var/cooldown_remaining = spell.next_use_time - world.time + //how much we discount, we make the spell cost 1/10th of its actual cooldown + var/new_cooldown = cooldown_remaining / 10 + //convert how much cooldown that spell saved into blood cost + var/blood_cost = (cooldown_remaining - new_cooldown ) * COOLDOWN_TO_BLOOD_RATIO + + spell.StartCooldown(new_cooldown) + source.blood_volume -= blood_cost + + var/cost_desc + + switch(blood_cost) + if(1 to 50) + cost_desc = "trickle" + if(51 to 100) + cost_desc = "stream" + if(101 to 200) + cost_desc = "river" + if(201 to INFINITY) + cost_desc = "torrent" + + to_chat(source, span_danger("You feel a [cost_desc] of your blood drained into the spell you just cast.")) diff --git a/code/datums/mutations/holy_mutation/honorbound.dm b/code/datums/mutations/holy_mutation/honorbound.dm index f5dadddbef9..5fe9c89dd46 100644 --- a/code/datums/mutations/holy_mutation/honorbound.dm +++ b/code/datums/mutations/holy_mutation/honorbound.dm @@ -189,7 +189,7 @@ switch(school) if(SCHOOL_HOLY, SCHOOL_MIME, SCHOOL_RESTORATION) return - if(SCHOOL_NECROMANCY, SCHOOL_FORBIDDEN) + if(SCHOOL_NECROMANCY, SCHOOL_FORBIDDEN, SCHOOL_SANGUINE) to_chat(user, span_userdanger("[GLOB.deity] is enraged by your use of forbidden magic!")) lightningbolt(user) owner.add_mood_event("honorbound", /datum/mood_event/banished) diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/defensive.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/defensive.dm index 4227927ae22..bb14b8fdd20 100644 --- a/code/modules/antagonists/wizard/equipment/spellbook_entries/defensive.dm +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/defensive.dm @@ -54,6 +54,7 @@ it will become easier for others to find your item of power." spell_type = /datum/action/cooldown/spell/lichdom category = "Defensive" + no_coexistance_typecache = list(/datum/action/cooldown/spell/splattercasting) /datum/spellbook_entry/spacetime_dist name = "Spacetime Distortion" diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm index 75fbb720a79..8992bffd731 100644 --- a/code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm @@ -77,6 +77,14 @@ spell_type = /datum/action/cooldown/spell/pointed/barnyardcurse category = "Offensive" +/datum/spellbook_entry/splattercasting + name = "Splattercasting" + desc = "Dramatically lowers the cooldown on all spells, but each one will cost blood, as well as it naturally \ + draining from you over time. You can replenish it from your victims, specifically their necks." + spell_type = /datum/action/cooldown/spell/splattercasting + category = "Offensive" + no_coexistance_typecache = list(/datum/action/cooldown/spell/lichdom) + /datum/spellbook_entry/item/staffchaos name = "Staff of Chaos" desc = "A caprious tool that can fire all sorts of magic without any rhyme or reason. Using it on people you care about is not recommended." diff --git a/code/modules/spells/spell_types/aoe_spell/magic_missile.dm b/code/modules/spells/spell_types/aoe_spell/magic_missile.dm index 51d8f8b5141..be8878403e0 100644 --- a/code/modules/spells/spell_types/aoe_spell/magic_missile.dm +++ b/code/modules/spells/spell_types/aoe_spell/magic_missile.dm @@ -32,6 +32,7 @@ /datum/action/cooldown/spell/aoe/magic_missile/proc/fire_projectile(atom/victim, mob/caster) var/obj/projectile/to_fire = new projectile_type() to_fire.preparePixelProjectile(victim, caster) + SEND_SIGNAL(caster, COMSIG_MOB_SPELL_PROJECTILE, src, victim, to_fire) to_fire.fire() /datum/action/cooldown/spell/aoe/magic_missile/lesser diff --git a/code/modules/spells/spell_types/pointed/_pointed.dm b/code/modules/spells/spell_types/pointed/_pointed.dm index 3a3856390b6..21641752dd0 100644 --- a/code/modules/spells/spell_types/pointed/_pointed.dm +++ b/code/modules/spells/spell_types/pointed/_pointed.dm @@ -160,6 +160,7 @@ for(var/i in 1 to projectiles_per_fire) var/obj/projectile/to_fire = new projectile_type() ready_projectile(to_fire, target, owner, i) + SEND_SIGNAL(owner, COMSIG_MOB_SPELL_PROJECTILE, src, target, to_fire) to_fire.fire() return TRUE diff --git a/code/modules/spells/spell_types/projectile/_basic_projectile.dm b/code/modules/spells/spell_types/projectile/_basic_projectile.dm index f9bd303f56f..343de438cd6 100644 --- a/code/modules/spells/spell_types/projectile/_basic_projectile.dm +++ b/code/modules/spells/spell_types/projectile/_basic_projectile.dm @@ -26,4 +26,5 @@ /datum/action/cooldown/spell/basic_projectile/proc/fire_projectile(atom/target, atom/caster) var/obj/projectile/to_fire = new projectile_type() to_fire.preparePixelProjectile(target, caster) + SEND_SIGNAL(caster, COMSIG_MOB_SPELL_PROJECTILE, src, target, to_fire) to_fire.fire() diff --git a/code/modules/spells/spell_types/self/splattercasting_spell.dm b/code/modules/spells/spell_types/self/splattercasting_spell.dm new file mode 100644 index 00000000000..a34e4d5eac9 --- /dev/null +++ b/code/modules/spells/spell_types/self/splattercasting_spell.dm @@ -0,0 +1,44 @@ +/datum/action/cooldown/spell/splattercasting + name = "Marrabbio's Splattercasting" + desc = "A spell invented by a banished wizard, who obsessed over aligning \ + the primal essences of life and magic into one. Dramatically lowers the \ + cooldown on all spells, but each one will cost blood, as well as it naturally \ + draining from you. You can replenish it from your victims." + button_icon_state = "splattercasting" + + school = SCHOOL_SANGUINE + cooldown_time = 1 SECONDS + + invocation = "THE STARS ALIGN! THE COSMOS BLEEDS!" + invocation_type = INVOCATION_SHOUT + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC|SPELL_REQUIRES_OFF_CENTCOM|SPELL_REQUIRES_MIND + antimagic_flags = MAGIC_RESISTANCE|MAGIC_RESISTANCE_HOLY + spell_max_level = 1 + +/datum/action/cooldown/spell/splattercasting/cast(mob/living/cast_on) + . = ..() + + to_chat(cast_on, span_green("You focus your arcane knowledge into your lifeforce, your blood simmering with new potential...")) + if(!do_after(cast_on, 5 SECONDS)) + to_chat(cast_on, span_warning("Your focus is broken, and the simmering slowly fades.")) + return + + playsound(cast_on, 'sound/effects/pope_entry.ogg', 100) + to_chat(cast_on, span_danger("You feel your very essense binding to your magic! A stabbing pain within \ + brings unimaginable momentary torment as your heart stops, and your skin grows cold. You are now \ + merely a vessel for the arcane flow. Soon, all that is left is not pain, but hunger.")) + + cast_on.set_species(/datum/species/vampire) + + cast_on.AddComponent(/datum/component/splattercasting) + + if(ishuman(cast_on)) + var/mob/living/carbon/human/human_cast_on = cast_on + human_cast_on.dropItemToGround(human_cast_on.w_uniform) + human_cast_on.dropItemToGround(human_cast_on.wear_suit) + human_cast_on.dropItemToGround(human_cast_on.head) + human_cast_on.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe/red(human_cast_on), ITEM_SLOT_OCLOTHING) + human_cast_on.equip_to_slot_or_del(new /obj/item/clothing/head/wizard/red(human_cast_on), ITEM_SLOT_HEAD) + human_cast_on.equip_to_slot_or_del(new /obj/item/clothing/under/color/red(human_cast_on), ITEM_SLOT_ICLOTHING) + + qdel(src) diff --git a/icons/mob/actions/actions_spells.dmi b/icons/mob/actions/actions_spells.dmi index cfc17adb29a..31f8b905fda 100644 Binary files a/icons/mob/actions/actions_spells.dmi and b/icons/mob/actions/actions_spells.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 001348edc02..12f610810eb 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -884,6 +884,7 @@ #include "code\datums\components\spin2win.dm" #include "code\datums\components\spinny.dm" #include "code\datums\components\spirit_holding.dm" +#include "code\datums\components\splattercasting.dm" #include "code\datums\components\squashable.dm" #include "code\datums\components\squeak.dm" #include "code\datums\components\stationloving.dm" @@ -4424,6 +4425,7 @@ #include "code\modules\spells\spell_types\self\smoke.dm" #include "code\modules\spells\spell_types\self\soultap.dm" #include "code\modules\spells\spell_types\self\spacetime_distortion.dm" +#include "code\modules\spells\spell_types\self\splattercasting_spell.dm" #include "code\modules\spells\spell_types\self\stop_time.dm" #include "code\modules\spells\spell_types\self\summonitem.dm" #include "code\modules\spells\spell_types\self\voice_of_god.dm"