Repository: Near-Web/The-Final-Nights
Pull request #286: Thaumaturgy buff and rework 
Author: login: chazzyjazzy
Labels: name: Enhancement
description: Enhances something already in the game
name: Balance
description: This PR or Issue touches the balance of the game.
Created: 2025-04-13T20:07:39Z | Merged: 2025-04-23T12:21:52Z

## About The Pull Request

This PR is literally the same as https://github.com/Near-Web/The-Final-Nights/pull/109 except with some of the changes requested being implemented - particularly the span macros. The original PR has been inactive for a while now so I'm going to work on it (sorry to the original PR owner - all credits to you bro)

let me know if you have any suggestions or changes ill change them probably within a day

## Why It's Good For The Game

Thaumaturgy is really weak and Tremere players have been waiting for a buff for several months now - Thaumaturgy is generally considered to be one of the weakest disciplines. This buff adds the caster's mentality to the damage of thaum 2 and 3, as well as cuts the cooldowns of thaum 1, 2 and 3 from 5 seconds to 3, 2.5 and 1 respectively. It also reworks thaum 4 ('theft of vitae') to drain blood, draining 2 bps from the target and awarding 2 bps to the caster. 

Thaum 5 is also reworked to a three stage attack - where the target rolls their physique to resist each stage. Each stage applies 20 burn (30 burn for the last stage) and a 2.5 second stun, with visual effects and causing the target to scream and twitch as their blood boils. The attack will either carry through all three stages, or be resisted entirely, depending on how many successes they rolled. 

For example:

John Tremere thaum 5's Amy Ventrue because she said he was a blood thief. Amy Ventrue has 2 physique - so she rolls two d10s to resist thaum 5. She succeeds both rolls (5 or above) - but unfortunately for her, in order to resist stage two, she needs three successes. Since she only has phys 2, the max successes she can get is 2. She suffers a 5 second stun, and 40 burn damage. John Tremere is pleased with the visual effects and Amy Ventrue's screaming as her blood boils - but he must wait 15 seconds before he can make her blood boil once more. 

John Tremere then decides to thaum 5 Maximilian Salubri, a jacked physique 5 salubri warior. But he is displeased when Maximilian Salubri rolls four successes, completely resisting the entire attack. John Tremere must now wait 15 seconds before he can swap to thaum 3 and start firing those blood bolts at Max Salubri.

Now, you might be wondering about balance, since a Kindred who suffers all three stages of this attack will be taking 140 burn and a ~7 second hardstun. 

Since the diceroll is set to difficulty 5, that means there is greater odds to rolling successful resists than not. At phys 5, there is an 8.7% chance to suffer from all three attacks, while there is a 33.3% chance to resist the attack entirely (no damage, no stun). 

Physique 6 would resist the entire attack 54% of the time. 

and with a 15 second cooldown, theres little possibility for things like stunlocks.

Let me know if there are balance or code concerns, I'll fix it up fast.

## Testing Photographs and Procedure
https://www.youtube.com/watch?v=uN-zWYMkfJU

unlisted youtube video of me demonstrating


## Changelog
:cl:
balance: thaumaturgy 1-3 cooldowns slashed, thaumaturgy 4 drains blood, thaumaturgy 5 reworked to a three-stage attack that deals massive damage only if the target fails a rather easy resist roll based on their physique.
/:cl:



Diff:
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index 9df84b4b5dc..696a1899c22 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -126,6 +126,7 @@
 
 	var/damage = 10
 	var/damage_type = BRUTE //BRUTE, BURN, TOX, OXY, CLONE are the only things that should be in here
+	var/cruelty_multiplier = 2
 	var/nodamage = FALSE //Determines if the projectile will skip any damage inflictions
 	var/flag = BULLET //Defines what armor to use when it hits things.  Must be set to bullet, laser, energy,or bomb
 	///How much armor this projectile pierces.
@@ -576,7 +577,7 @@
 			var/mob/living/carbon/human/frer = firer
 			if(frer.blood)
 				if(prob(frer.get_total_blood()*10))
-					damage *= 2
+					damage *= cruelty_multiplier
 	if(temporary_unstoppable_movement)
 		temporary_unstoppable_movement = FALSE
 		movement_type &= ~PHASING
diff --git a/code/modules/vtmb/artifacts.dm b/code/modules/vtmb/artifacts.dm
index c79dfa95ac7..5fa7c09cc4e 100644
--- a/code/modules/vtmb/artifacts.dm
+++ b/code/modules/vtmb/artifacts.dm
@@ -173,7 +173,7 @@
 
 /obj/item/vtm_artifact/daimonori/get_powers()
 	..()
-	owner.thaum_damage_plus = 20
+	owner.thaum_damage_plus = 10
 
 /obj/item/vtm_artifact/daimonori/remove_powers()
 	..()
diff --git a/code/modules/wod13/datums/powers/discipline/thaumaturgy.dm b/code/modules/wod13/datums/powers/discipline/thaumaturgy.dm
index 6b6055344d2..d88ce2f1ac7 100644
--- a/code/modules/wod13/datums/powers/discipline/thaumaturgy.dm
+++ b/code/modules/wod13/datums/powers/discipline/thaumaturgy.dm
@@ -52,7 +52,7 @@
 	pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE
 	damage = 5
 	damage_type = BURN
-	hitsound = 'code/modules/wod13/sounds/drinkblood1.ogg'
+	//hitsound = 'code/modules/wod13/sounds/bloodbeam.ogg'
 	hitsound_wall = 'sound/weapons/effects/searwall.ogg'
 	flag = LASER
 	light_system = MOVABLE_LIGHT
@@ -67,100 +67,31 @@
 	var/level = 1
 
 /obj/projectile/thaumaturgy/on_hit(atom/target, blocked = FALSE, pierce_hit)
-	if(!ishuman(firer))
-		return
-	var/mob/living/carbon/human/caster = firer
-	if(!isliving(target))
-		return
-	var/mob/living/target_l = target
-
-	if(target_l.stat == DEAD)
-		return
-
-	if(!ishuman(target_l)) //Is this mob a human?
-		if(iswerewolf(target_l))
-			src.on_hit_werewolf(target_l,caster)
-		else
-			src.on_hit_other(target_l,caster)
-	else
-		var/mob/living/carbon/human/target_h = target
-		if(iscathayan(target_h))
-			src.on_hit_cathayan(target_h,caster)
-		else if(iskindred(target_h))
-			src.on_hit_kindred(target_h,caster)
-		else if(isgarou(target_h))
-			src.on_hit_garou(target_h,caster)
-		else
-			src.on_hit_human(target_h,caster)
-
-
-
-
-/obj/projectile/thaumaturgy/proc/on_hit_other(mob/living/target,mob/living/carbon/human/caster)
-	var/sucked = min(target.bloodpool, level)
-	if(target.bloodpool >= 1)
-		target.bloodpool = max(target.bloodpool - sucked, 0)
-		caster.bloodpool = min(caster.bloodpool + sucked, caster.maxbloodpool)
-		target.visible_message(span_danger("[target]'s wounds spill out, returning to [caster]!"), span_userdanger("Your blood sprays out towards [caster]!"))
-	else
-		target.visible_message(span_danger("[target]'s wounds run dry!"), span_userdanger("Your empty veins cry out!"))
-		target.apply_damage((damage/2), BRUTE)
-
-/obj/projectile/thaumaturgy/proc/on_hit_werewolf(mob/living/carbon/target,mob/living/carbon/human/caster)
-	var/sucked = min(target.bloodpool, level)
-	if(target.bloodpool >= 1)
-		target.bloodpool = max(target.bloodpool - sucked, 0)
-		target.apply_damage(45, BURN)
-		target.visible_message(span_danger("[target]'s wounds spray boiling hot blood!"), span_userdanger("Your blood boils!"))
-		target.add_splatter_floor(get_turf(target))
-		target.add_splatter_floor(get_turf(get_step(target, target.dir)))
-	else
-		target.visible_message(span_danger("[target]'s wounds run dry!"), span_userdanger("Your empty veins cry out!"))
-		target.apply_damage((damage/2), BRUTE)
-
-/obj/projectile/thaumaturgy/proc/on_hit_garou(mob/living/carbon/human/target,mob/living/carbon/human/caster)
-	if(target.bloodpool >= 1)
-		target.blood_volume = max(target.blood_volume-35, 100)
-		target.bloodpool = max(target.bloodpool - 1, 0)
-		target.visible_message(span_danger("[target]'s wounds spray boiling hot blood!"), span_userdanger("Your blood boils!"))
-		target.apply_damage(45, BURN)
-		target.add_splatter_floor(get_turf(target))
-		target.add_splatter_floor(get_turf(get_step(target, target.dir)))
-	else
-		target.blood_volume = 100
-		target.visible_message(span_danger("[target]'s wounds run dry!"), span_userdanger("Your empty veins cry out!"))
-		target.apply_damage((damage/2), BRUTE)
-
-/obj/projectile/thaumaturgy/proc/on_hit_human(mob/living/carbon/human/target,mob/living/carbon/human/caster)
-	if(target.bloodpool >= 1)
-		target.blood_volume = max(target.blood_volume-35, 100)
-		target.bloodpool = max(target.bloodpool - 1, 0)
-		target.visible_message(span_danger("[target]'s wounds spill out, blood flowing to [caster]!"), span_userdanger("Your blood sprays out towards [caster]!"))
-		caster.bloodpool = min(caster.bloodpool + max(1, target.bloodquality-1), caster.maxbloodpool)
-	else
-		target.blood_volume = 100
-		target.visible_message(span_danger("[target]'s wounds run dry!"), span_userdanger("Your empty veins cry out!"))
-		target.apply_damage((damage/2), BRUTE)
-
-/obj/projectile/thaumaturgy/proc/on_hit_kindred(mob/living/carbon/human/target,mob/living/carbon/human/caster)
-	var/sucked = min(target.bloodpool, level)
-	if(target.bloodpool >= 0)
-		target.bloodpool = max(target.bloodpool - sucked, 0)
-		caster.bloodpool = min(caster.bloodpool + sucked, caster.maxbloodpool)
-		target.visible_message(span_danger("[target]'s wounds spill out, returning to [caster]!"), span_userdanger("Your blood sprays out towards [caster]!"))
-	else
-		target.visible_message(span_danger("[target]'s wounds run dry!"), span_userdanger("Your empty veins cry out!"))
-		target.apply_damage((damage/2), BRUTE)
-
-/obj/projectile/thaumaturgy/proc/on_hit_cathayan(mob/living/carbon/human/target,mob/living/carbon/human/caster)
-	var/sucked = min(target.bloodpool, level)
-	if(target.bloodpool >= 0)
-		target.bloodpool = max(target.bloodpool - sucked, 0)
-		caster.bloodpool = min(caster.bloodpool + sucked, caster.maxbloodpool)
-		target.visible_message(span_danger("[target]'s wounds spill out, returning to [caster]!"), span_userdanger("Your blood sprays out towards [caster]!"))
-	else
-		target.visible_message(span_danger("[target]'s wounds run dry!"), span_userdanger("Your empty veins cry out!"))
-		target.apply_damage((damage/2), BRUTE)
+	if(ishuman(firer))
+		var/mob/living/carbon/human/VH = firer
+		if(isliving(target))
+			var/mob/living/VL = target
+			if(isgarou(VL))
+				if(VL.bloodpool >= 1 && VL.stat != DEAD)
+					var/sucked = min(VL.bloodpool, 2)
+					VL.bloodpool = max(VL.bloodpool - sucked, 0)
+					VL.apply_damage(45, BURN)
+					VL.visible_message(span_danger("[target]'s wounds spray boiling hot blood!"), span_userdanger("Your blood boils!"))
+					VL.add_splatter_floor(get_turf(target))
+					VL.add_splatter_floor(get_turf(get_step(target, target.dir)))
+				if(!iskindred(target))
+					if(VL.bloodpool >= 1 && VL.stat != DEAD)
+						var/sucked = min(VL.bloodpool, 2)
+						VL.bloodpool = max(VL.bloodpool - sucked, 0)
+					if(ishuman(VL))
+						if(VL.bloodpool >= 1 && VL.stat != DEAD)
+							var/mob/living/carbon/human/VHL = VL
+							VHL.bloodpool = max(VHL.bloodpool - 1, 0)
+			else
+				if(VL.bloodpool >= 1)
+					var/sucked = min(VL.bloodpool, level)
+					VL.bloodpool = max(VL.bloodpool - sucked, 0)
+					VH.bloodpool = min(VH.bloodpool + sucked, VH.maxbloodpool)
 
 /datum/discipline_power/thaumaturgy/a_taste_for_blood
 	name = "A Taste for Blood"
@@ -168,6 +99,8 @@
 
 	level = 1
 
+	cooldown_length = 3 SECONDS
+
 	grouped_powers = list(
 		/datum/discipline_power/thaumaturgy/blood_rage,
 		/datum/discipline_power/thaumaturgy/blood_of_potency,
@@ -190,6 +123,8 @@
 
 	level = 2
 
+	cooldown_length = 2.5 SECONDS
+
 	grouped_powers = list(
 		/datum/discipline_power/thaumaturgy/a_taste_for_blood,
 		/datum/discipline_power/thaumaturgy/blood_of_potency,
@@ -202,10 +137,13 @@
 	var/turf/start = get_turf(owner)
 	var/obj/projectile/thaumaturgy/H = new(start)
 	H.firer = owner
-	H.damage = 10 + owner.thaum_damage_plus
+	H.damage = 10 + owner.thaum_damage_plus + owner.get_total_mentality()
 	H.preparePixelProjectile(target, start)
 	H.level = 2
 	H.fire(direct_target = target)
+	H.cruelty_multiplier = 1.1
+	to_chat(target, span_danger("A bolt of boiling blood flies toward you!"))
+
 
 //BLOOD OF POTENCY
 /datum/discipline_power/thaumaturgy/blood_of_potency
@@ -214,6 +152,8 @@
 
 	level = 3
 
+	cooldown_length = 1 SECONDS
+
 	grouped_powers = list(
 		/datum/discipline_power/thaumaturgy/a_taste_for_blood,
 		/datum/discipline_power/thaumaturgy/blood_rage,
@@ -226,10 +166,13 @@
 	var/turf/start = get_turf(owner)
 	var/obj/projectile/thaumaturgy/H = new(start)
 	H.firer = owner
-	H.damage = 15 + owner.thaum_damage_plus
+	H.damage = 15 + owner.thaum_damage_plus + owner.get_total_mentality()
 	H.preparePixelProjectile(target, start)
 	H.level = 2
 	H.fire(direct_target = target)
+	H.cruelty_multiplier = 1.1
+	to_chat(target, span_danger("A bolt of boiling blood flies toward you!"))
+
 
 //THEFT OF VITAE
 /mob/living/proc/tremere_gib()
@@ -268,10 +211,12 @@
 /datum/discipline_power/thaumaturgy/theft_of_vitae/activate(mob/living/target)
 	. = ..()
 	if(iscarbon(target))
-		target.Stun(2.5 SECONDS)
-		target.visible_message(span_danger("[target] throws up!"), "<span class='userdanger'>You throw up!</span>")
+		target.visible_message(span_danger("[target] throws up!"), span_userdanger("You throw up!"))
 		target.add_splatter_floor(get_turf(target))
 		target.add_splatter_floor(get_turf(get_step(target, target.dir)))
+		if(target.bloodpool >= 2)
+			target.bloodpool -= 2
+			owner.bloodpool = min(owner.bloodpool + 3, owner.maxbloodpool) // costs 1 bp to cast so this nets 2 bps
 	else
 		owner.bloodpool = min(owner.bloodpool + target.bloodpool, owner.maxbloodpool)
 		if(!istype(target, /mob/living/simple_animal/hostile/megafauna))
@@ -282,9 +227,25 @@
 	name = "Cauldron of Blood"
 	desc = "Boil your target's blood in their body, killing almost anyone."
 
+	cooldown_length = 15.0 SECONDS
 	level = 5
 
-	effect_sound = 'code/modules/wod13/sounds/vomit.ogg'
+	//effect_sound = 'code/modules/wod13/sounds/bloodcauldron.ogg'
+
+	grouped_powers = list(
+		/datum/discipline_power/thaumaturgy/a_taste_for_blood,
+		/datum/discipline_power/thaumaturgy/blood_rage,
+		/datum/discipline_power/thaumaturgy/blood_of_potency,
+		/datum/discipline_power/thaumaturgy/theft_of_vitae
+	)
+
+/datum/discipline_power/thaumaturgy/cauldron_of_blood
+	name = "Cauldron of Blood"
+	desc = "Boil your target's blood in their body, killing almost anyone."
+
+	level = 5
+
+	//effect_sound = 'code/modules/wod13/sounds/bloodcauldron.ogg'
 
 	grouped_powers = list(
 		/datum/discipline_power/thaumaturgy/a_taste_for_blood,
@@ -296,15 +257,66 @@
 /datum/discipline_power/thaumaturgy/cauldron_of_blood/activate(mob/living/target)
 	. = ..()
 	if(iscarbon(target))
-		target.Stun(2.5 SECONDS)
-		target.visible_message(span_danger("[target] throws up!"), "<span class='userdanger'>You throw up!</span>")
-		target.add_splatter_floor(get_turf(target))
-		target.add_splatter_floor(get_turf(get_step(target, target.dir)))
+		new /obj/effect/temp_visual/tremere(target.loc, "gib")
+
+
+
+		target.visible_message(span_danger("[target] reddens and quakes!"), span_userdanger("Your veins feel like they're on fire!"))
+
+		var/dice = clamp(target.get_total_physique(), 1, 8)
+		var/successes = SSroll.storyteller_roll(dice, difficulty = 5, mobs_to_show_output = target, numerical = TRUE)
+
+		if(successes <= 1)
+			// All stages (fail completely)
+			animate(target, pixel_y = 16, color = "#ff0000", time = 5 SECONDS, loop = 1)
+			addtimer(CALLBACK(src, /datum/discipline_power/thaumaturgy/cauldron_of_blood/proc/reset_target_appearance, target), 5 SECONDS)
+			addtimer(CALLBACK(src, .proc/blood_burn_stage1, target), 0)
+			addtimer(CALLBACK(src, .proc/blood_burn_stage2, target), 2.5 SECONDS)
+			addtimer(CALLBACK(src, .proc/blood_burn_stage3, target), 5 SECONDS)
+
+		else if(successes == 2)
+			// Stages 1 & 2
+			animate(target, pixel_y = 16, color = "#ff0000", time = 2.5 SECONDS, loop = 1)
+			addtimer(CALLBACK(src, /datum/discipline_power/thaumaturgy/cauldron_of_blood/proc/reset_target_appearance, target), 2.5 SECONDS)
+			addtimer(CALLBACK(src, .proc/blood_burn_stage1, target), 0)
+			addtimer(CALLBACK(src, .proc/blood_burn_stage2, target), 2.5 SECONDS)
+
+		else if(successes == 3)
+			animate(target, pixel_y = 16, color = "#ff0000", time = 1 SECONDS, loop = 1)
+			addtimer(CALLBACK(src, /datum/discipline_power/thaumaturgy/cauldron_of_blood/proc/reset_target_appearance, target), 1 SECONDS)
+			// Stage 1 only
+			addtimer(CALLBACK(src, .proc/blood_burn_stage1, target), 0)
+
+		else
+			// Resisted completely
+			to_chat(target, span_notice("You steel yourself as the blood begins to churn—but your will holds firm."))
+
 	else
 		owner.bloodpool = min(owner.bloodpool + target.bloodpool, owner.maxbloodpool)
 		if(!istype(target, /mob/living/simple_animal/hostile/megafauna))
 			target.tremere_gib()
 
+/datum/discipline_power/thaumaturgy/cauldron_of_blood/proc/blood_burn_stage1(mob/living/target)
+	if(!target) return
+	target.Stun(2.5 SECONDS)
+	target.apply_damage(20, BURN, owner.zone_selected)
+	target.emote("twitch")
+	target.visible_message(span_warning("[target] begins to violently shake!"), span_userdanger("You feel yourself trembling uncontrollably!"))
+
+/datum/discipline_power/thaumaturgy/cauldron_of_blood/proc/blood_burn_stage2(mob/living/target)
+	if(!target) return
+	target.apply_damage(20, BURN, owner.zone_selected)
+	target.emote("scream")
+	target.emote("twitch")
+	target.visible_message(span_warning("[target] howls in agony, their whole body convulsing!"), span_userdanger("You scream in anguish as your blood boils!"))
+
+/datum/discipline_power/thaumaturgy/cauldron_of_blood/proc/blood_burn_stage3(mob/living/target)
+	if(!target) return
+	target.Stun(2.5 SECONDS)
+	target.apply_damage(30, BURN, owner.zone_selected)
+	target.visible_message(span_warning("[target] collapses to the floor, thrashing in torment!"), span_userdanger("IT BURNS! IT BURNS!! IT BURNS!!!"))
+	target.emote("collapse")
+
 //MISCELLANEOUS BULLSHIT
 /datum/action/thaumaturgy
 	name = "Thaumaturgy"
@@ -334,7 +346,7 @@
 		var/ritual = input(owner, "Choose rune to draw:", "Thaumaturgy") as null|anything in shit
 		if(ritual)
 			drawing = TRUE
-			if(do_after(H, 3 SECONDS * max(1, 5 - H.mentality), H))
+			if(do_after(H, 3 SECONDS * max(1, 5 - H.get_total_mentality()), H))
 				drawing = FALSE
 				new ritual(H.loc)
 				H.bloodpool = max(H.bloodpool - 2, 0)
@@ -352,7 +364,7 @@
 		var/ritual = input(owner, "Choose rune to draw (You need an Arcane Tome to reduce random):", "Thaumaturgy") as null|anything in list("???")
 		if(ritual)
 			drawing = TRUE
-			if(do_after(H, 3 SECONDS * max(1, 5 - H.mentality), H))
+			if(do_after(H, 3 SECONDS * max(1, 5 - H.get_total_mentality()), H))
 				drawing = FALSE
 				var/rune = pick(shit)
 				new rune(H.loc)
@@ -415,3 +427,7 @@
 			owner.physiology.armor.bullet = owner.physiology.armor.bullet-(15*mod)
 			owner.color = initial(owner.color)
 */
+
+/datum/discipline_power/thaumaturgy/cauldron_of_blood/proc/reset_target_appearance(mob/living/target)
+	if(!target) return
+	animate(target, pixel_y = 0, color = null)


Reviews:
user: login: XeonMations
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-04-16T06:12:51Z

Review comments:
diff_hunk: @@ -67,107 +67,40 @@
 	var/level = 1
 
 /obj/projectile/thaumaturgy/on_hit(atom/target, blocked = FALSE, pierce_hit)
-	if(!ishuman(firer))
-		return
-	var/mob/living/carbon/human/caster = firer
-	if(!isliving(target))
-		return
-	var/mob/living/target_l = target
-
-	if(target_l.stat == DEAD)
-		return
-
-	if(!ishuman(target_l)) //Is this mob a human?
-		if(iswerewolf(target_l))
-			src.on_hit_werewolf(target_l,caster)
-		else
-			src.on_hit_other(target_l,caster)
-	else
-		var/mob/living/carbon/human/target_h = target
-		if(iscathayan(target_h))
-			src.on_hit_cathayan(target_h,caster)
-		else if(iskindred(target_h))
-			src.on_hit_kindred(target_h,caster)
-		else if(isgarou(target_h))
-			src.on_hit_garou(target_h,caster)
-		else
-			src.on_hit_human(target_h,caster)
-
-
-
-
-/obj/projectile/thaumaturgy/proc/on_hit_other(mob/living/target,mob/living/carbon/human/caster)
-	var/sucked = min(target.bloodpool, level)
-	if(target.bloodpool >= 1)
-		target.bloodpool = max(target.bloodpool - sucked, 0)
-		caster.bloodpool = min(caster.bloodpool + sucked, caster.maxbloodpool)
-		target.visible_message(span_danger("[target]'s wounds spill out, returning to [caster]!"), span_userdanger("Your blood sprays out towards [caster]!"))
-	else
-		target.visible_message(span_danger("[target]'s wounds run dry!"), span_userdanger("Your empty veins cry out!"))
-		target.apply_damage((damage/2), BRUTE)
-
-/obj/projectile/thaumaturgy/proc/on_hit_werewolf(mob/living/carbon/target,mob/living/carbon/human/caster)
-	var/sucked = min(target.bloodpool, level)
-	if(target.bloodpool >= 1)
-		target.bloodpool = max(target.bloodpool - sucked, 0)
-		target.apply_damage(45, BURN)
-		target.visible_message(span_danger("[target]'s wounds spray boiling hot blood!"), span_userdanger("Your blood boils!"))
-		target.add_splatter_floor(get_turf(target))
-		target.add_splatter_floor(get_turf(get_step(target, target.dir)))
-	else
-		target.visible_message(span_danger("[target]'s wounds run dry!"), span_userdanger("Your empty veins cry out!"))
-		target.apply_damage((damage/2), BRUTE)
-
-/obj/projectile/thaumaturgy/proc/on_hit_garou(mob/living/carbon/human/target,mob/living/carbon/human/caster)
-	if(target.bloodpool >= 1)
-		target.blood_volume = max(target.blood_volume-35, 100)
-		target.bloodpool = max(target.bloodpool - 1, 0)
-		target.visible_message(span_danger("[target]'s wounds spray boiling hot blood!"), span_userdanger("Your blood boils!"))
-		target.apply_damage(45, BURN)
-		target.add_splatter_floor(get_turf(target))
-		target.add_splatter_floor(get_turf(get_step(target, target.dir)))
-	else
-		target.blood_volume = 100
-		target.visible_message(span_danger("[target]'s wounds run dry!"), span_userdanger("Your empty veins cry out!"))
-		target.apply_damage((damage/2), BRUTE)
-
-/obj/projectile/thaumaturgy/proc/on_hit_human(mob/living/carbon/human/target,mob/living/carbon/human/caster)
-	if(target.bloodpool >= 1)
-		target.blood_volume = max(target.blood_volume-35, 100)
-		target.bloodpool = max(target.bloodpool - 1, 0)
-		target.visible_message(span_danger("[target]'s wounds spill out, blood flowing to [caster]!"), span_userdanger("Your blood sprays out towards [caster]!"))
-		caster.bloodpool = min(caster.bloodpool + max(1, target.bloodquality-1), caster.maxbloodpool)
-	else
-		target.blood_volume = 100
-		target.visible_message(span_danger("[target]'s wounds run dry!"), span_userdanger("Your empty veins cry out!"))
-		target.apply_damage((damage/2), BRUTE)
-
-/obj/projectile/thaumaturgy/proc/on_hit_kindred(mob/living/carbon/human/target,mob/living/carbon/human/caster)
-	var/sucked = min(target.bloodpool, level)
-	if(target.bloodpool >= 0)
-		target.bloodpool = max(target.bloodpool - sucked, 0)
-		caster.bloodpool = min(caster.bloodpool + sucked, caster.maxbloodpool)
-		target.visible_message(span_danger("[target]'s wounds spill out, returning to [caster]!"), span_userdanger("Your blood sprays out towards [caster]!"))
-	else
-		target.visible_message(span_danger("[target]'s wounds run dry!"), span_userdanger("Your empty veins cry out!"))
-		target.apply_damage((damage/2), BRUTE)
-
-/obj/projectile/thaumaturgy/proc/on_hit_cathayan(mob/living/carbon/human/target,mob/living/carbon/human/caster)
-	var/sucked = min(target.bloodpool, level)
-	if(target.bloodpool >= 0)
-		target.bloodpool = max(target.bloodpool - sucked, 0)
-		caster.bloodpool = min(caster.bloodpool + sucked, caster.maxbloodpool)
-		target.visible_message(span_danger("[target]'s wounds spill out, returning to [caster]!"), span_userdanger("Your blood sprays out towards [caster]!"))
-	else
-		target.visible_message(span_danger("[target]'s wounds run dry!"), span_userdanger("Your empty veins cry out!"))
-		target.apply_damage((damage/2), BRUTE)
+	if(ishuman(firer))
path: code/modules/wod13/datums/powers/discipline/thaumaturgy.dm
user: login: XeonMations
body: unindent where possible
created_at: 2025-04-13T20:51:51Z
author_association: COLLABORATOR
diff_hunk: @@ -296,10 +251,60 @@
 /datum/discipline_power/thaumaturgy/cauldron_of_blood/activate(mob/living/target)
 	. = ..()
 	if(iscarbon(target))
-		target.Stun(2.5 SECONDS)
-		target.visible_message(span_danger("[target] throws up!"), "<span class='userdanger'>You throw up!</span>")
-		target.add_splatter_floor(get_turf(target))
-		target.add_splatter_floor(get_turf(get_step(target, target.dir)))
+		new /obj/effect/temp_visual/tremere(target.loc, "gib")
+		animate(target, pixel_y = 16, color = "#ff0000", time = 5 SECONDS, loop = 1)
+		spawn(5 SECONDS)
path: code/modules/wod13/datums/powers/discipline/thaumaturgy.dm
user: login: XeonMations
body: use proper timer callbacks instead of spawn()
created_at: 2025-04-16T06:11:44Z
author_association: COLLABORATOR
diff_hunk: @@ -296,10 +251,60 @@
 /datum/discipline_power/thaumaturgy/cauldron_of_blood/activate(mob/living/target)
 	. = ..()
 	if(iscarbon(target))
-		target.Stun(2.5 SECONDS)
-		target.visible_message(span_danger("[target] throws up!"), "<span class='userdanger'>You throw up!</span>")
-		target.add_splatter_floor(get_turf(target))
-		target.add_splatter_floor(get_turf(get_step(target, target.dir)))
+		new /obj/effect/temp_visual/tremere(target.loc, "gib")
+		animate(target, pixel_y = 16, color = "#ff0000", time = 5 SECONDS, loop = 1)
+		spawn(5 SECONDS)
+			animate(target, color = null, pixel_y = 0, time = 5 SECONDS, loop = 1)
+
+		target.visible_message(span_danger("[target] reddens and quakes!"), span_userdanger("Your veins feel like they're on fire!"))
+
+		var/dice = clamp(target.get_total_physique(), 1, 8)
+		var/successes = SSroll.storyteller_roll(dice, difficulty = 5, mobs_to_show_output = target, numerical = TRUE)
+
+		if(successes <= 1)
+			// All stages (fails completely)
+			target.Stun(2.5 SECONDS)
+			target.apply_damage(20, BURN, owner.zone_selected)
+			target.emote("twitch")
+			target.visible_message(span_warning("[target] begins to violently shake!"), span_userdanger("You feel yourself trembling uncontrollably!"))
+			sleep(2.5 SECONDS)
path: code/modules/wod13/datums/powers/discipline/thaumaturgy.dm
user: login: XeonMations
body: generally not a good idea to sleep in the code. I suggest seperating the damage into procs, then using timer callbacks to properly call them.
created_at: 2025-04-16T06:12:28Z
author_association: COLLABORATOR
diff_hunk: @@ -414,4 +419,4 @@
 			owner.physiology.armor.melee = owner.physiology.armor.melee-(15*mod)
 			owner.physiology.armor.bullet = owner.physiology.armor.bullet-(15*mod)
 			owner.color = initial(owner.color)
-*/
+*/
path: code/modules/wod13/datums/powers/discipline/thaumaturgy.dm
user: login: XeonMations
body: make sure to have a new line at the bottom of the file
created_at: 2025-04-16T06:12:48Z
author_association: COLLABORATOR

PR comments:
user: login: chazzyjazzy
created_at: 2025-04-17T20:24:25Z
body: Changes requested have been implemented - created a new proc to end the tremere gib animation to replace the Spawn(5 Seconds), as well as created three separate procs for each stage of the blood cauldron attack to replace the sleeps.
author_association: COLLABORATOR
user: login: chazzyjazzy
created_at: 2025-04-18T23:57:41Z
body: Putting this here so I am reminded - Thaum 4 allows you to overfill your blood since it increments your blood by 2, needs to be fixed, and thaum 5 plays the animation even when you resist the spell, will fix soon
author_association: COLLABORATOR
user: login: anything86
created_at: 2025-04-21T06:00:04Z
body: 42 burn damage (84 - 168 against vampires depending on Cruelty crit) on Thaum 3 with the Daimonori artifact seems a bit much for a 1 second cooldown. I'm also concerned about Blood Cauldron getting cast through walls and locked doors in combination with Auspex 1.
author_association: CONTRIBUTOR
user: login: chazzyjazzy
created_at: 2025-04-21T06:13:16Z
body: > 42 burn damage (84 - 168 against vampires depending on Cruelty crit) on Thaum 3 with the Daimonori artifact seems a bit much for a 1 second cooldown. 

Players do not always have daimonori, nor do they always have successful cruelty shots. nonetheless I've reduced Daimonori's damage by half and this spell should have never had cruelty mod  in the first place because it makes no sense.

>I'm also concerned about Blood Cauldron getting cast through walls and locked doors in combination with Auspex 1.

Tremeres are blood sorcerers. and the spell has a 33% chance to not fire at all and a 15 sec cooldown.

would also like to point out that this 'concern' is coming on after you were fragged by thaum


author_association: COLLABORATOR
user: login: anything86
created_at: 2025-04-21T11:58:00Z
body: > Tremeres are blood sorcerers. and the spell has a 33% chance to not fire at all and a 15 sec cooldown.
> 
> would also like to point out that this 'concern' is coming on after you were fragged by thaum

I genuinely do not know what you are talking about and just think it could lead to some really unfun situations where a Tremere is hiding behind a door that must be lockpicked and just casting Blood Cauldron/Siphon on whoever is trying to break in. Couldn't a check be added to make sure it doesn't get cast through walls?
author_association: CONTRIBUTOR
user: login: chazzyjazzy
created_at: 2025-04-22T06:41:57Z
body: new changes - chat message for the person being hit by thaum 2 and 3 (because before there would be no way to tell you were getting hit by it besides losing health and the screen being red

balance - removal of toggle_resting() from thaum 5 stage 3 because if someone was hit by it theyd have to click the button to stand up which added a couple more seconds to the stun and was mechanically unfun
author_association: COLLABORATOR
user: login: XeonMations
created_at: 2025-04-23T12:21:49Z
body: no issues reportred. merging
author_association: COLLABORATOR