diff --git a/modular_zubbers/code/__DEFINES/bloodsucker_defines.dm b/modular_zubbers/code/__DEFINES/bloodsucker_defines.dm
index 815e7ee2a82..d83d54b331e 100644
--- a/modular_zubbers/code/__DEFINES/bloodsucker_defines.dm
+++ b/modular_zubbers/code/__DEFINES/bloodsucker_defines.dm
@@ -30,9 +30,8 @@
#define HUMANITY_LOST_MAXIMUM 50
/// Level up blood cost define, max_blood * this = blood cost
-#define BLOODSUCKER_LEVELUP_PERCENTAGE 0
-/// Upper bound for Blood cost increase; used for blood thickening
-#define BLOOD_LEVEL_GAIN_MAX 0.9
+#define BLOODSUCKER_LEVELUP_PERCENTAGE 0.45
+#define BLOODSUCKER_LEVELUP_PERCENTAGE_VENTRUE BLOODSUCKER_LEVELUP_PERCENTAGE - 0.1
///The level when at a bloodsucker becomes snobby about who they drink from and gain their non-fledling reputation
#define BLOODSUCKER_HIGH_LEVEL 4
@@ -119,6 +118,8 @@
#define TREMERE_CAN_BUY (1<<2)
/// This Power can be purchased by Vassals
#define VASSAL_CAN_BUY (1<<3)
+/// If this Power can be bought if you already own it
+#define CAN_BUY_OWNED (1<<4)
/// This Power is a Continuous Effect, processing every tick
#define BP_CONTINUOUS_EFFECT (1<<0)
diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/bloodsucker.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/bloodsucker.dm
index 533cd26432c..3b66d0338a4 100644
--- a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/bloodsucker.dm
+++ b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/bloodsucker.dm
@@ -14,7 +14,7 @@
/// How much blood we have, starting off at default blood levels. Do not adjust this directly, use adjustBloodVolume(), and use getBloodVolume() to get the current value.
VAR_PRIVATE/bloodsucker_blood_volume = BLOOD_VOLUME_NORMAL
- /// How much blood we can have at once, increases per level.
+ /// How much blood we can have without it deckaying quickly, increases per level.
var/max_blood_volume = 600
var/datum/bloodsucker_clan/my_clan
@@ -68,7 +68,8 @@
/// Used for Bloodsuckers gaining levels from drinking blood
var/blood_level_gain = 0
- var/blood_level_gain_amount = 0
+ /// How many levels you can get from Sol
+ var/sol_levels = 3
///Blood display HUD
var/atom/movable/screen/bloodsucker/blood_counter/blood_display
diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/integration.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/integration.dm
index c57615471f8..c8d8041661b 100644
--- a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/integration.dm
+++ b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/integration.dm
@@ -13,9 +13,6 @@
if(bloodsuckerdatum.my_clan && istype(bloodsuckerdatum.my_clan, /datum/bloodsucker_clan/ventrue) && bloodsuckerdatum.GetBloodVolume() >= BLOOD_VOLUME_SAFE)
return ..()
- if(bloodsuckerdatum.GetRank() >= BLOODSUCKER_HIGH_LEVEL)
- exposed_mob.adjust_disgust(5 SECONDS, DISGUST_LEVEL_GROSS)
- reac_volume = reac_volume * 0.3
if(bloodsuckerdatum.GetBloodVolume() >= BLOOD_VOLUME_NORMAL)
return ..()
bloodsuckerdatum.AdjustBloodVolume(round(reac_volume, 0.1))
diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/life.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/life.dm
index cce27713d05..462fc456204 100644
--- a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/life.dm
+++ b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/life.dm
@@ -80,11 +80,11 @@
#undef MASQUERADE
/// mult: SILENT feed is 1/3 the amount
-/datum/antagonist/bloodsucker/proc/handle_feeding(mob/living/carbon/target, mult=1, power_level)
+/datum/antagonist/bloodsucker/proc/handle_feeding(mob/living/carbon/target, mult=1, power_level, already_drunk = 0)
// Starts at 15 (now 8 since we doubled the Feed time)
var/feed_amount = 15 + (power_level * 2)
- var/blood_taken = min(feed_amount, target.blood_volume) * mult
- target.blood_volume -= blood_taken
+ var/blood_taken = feed_amount * mult
+ target.blood_volume = max(target.blood_volume - blood_taken, 0)
///////////
// Shift Body Temp (toward Target's temp, by volume taken)
@@ -96,25 +96,27 @@
blood_taken /= 3
if(!ishuman(target)) // Penalty for Non-Human Blood
blood_taken /= 2
- // High level vampires get much less blood from mindless targets
- else if(bloodsucker_level >= BLOODSUCKER_HIGH_LEVEL && !target.mind)
- blood_taken /= 4
else if(!target?.mind) // Penalty for Mindless Blood
blood_taken /= 2
- //if (!iscarbon(target)) // Penalty for Animals (they're junk food)
// Apply to Volume
AdjustBloodVolume(blood_taken)
+ total_blood_drank += blood_taken
OverfeedHealing(blood_taken)
// Reagents (NOT Blood!)
if(target.reagents && target.reagents.total_volume)
target.reagents.trans_to(owner.current, INGEST, 1) // Run transfer of 1 unit of reagent from them to me.
owner.current.playsound_local(null, 'sound/effects/singlebeat.ogg', 40, 1) // Play THIS sound for user only. The "null" is where turf would go if a location was needed. Null puts it right in their head.
- total_blood_drank += blood_taken
if(target.mind) // Checks if the target has a mind
+ // closer it is to max, the less level up blood you get
+ var/blood_for_leveling = blood_taken
+ if(already_drunk > BLOOD_VOLUME_NORMAL)
+ var/max_threshold = BLOOD_VOLUME_NORMAL * 2
+ var/modify_blood_gain = 1 - (already_drunk / max_threshold)
+ blood_for_leveling = max(blood_taken * modify_blood_gain, 0)
if(IS_VASSAL(target)) // Checks if the target is a vassal
- blood_level_gain += blood_taken / 4
+ blood_level_gain += blood_for_leveling / 4
else
- blood_level_gain += blood_taken
+ blood_level_gain += blood_for_leveling
return blood_taken
/**
diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/procs.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/procs.dm
index 173430bd33a..96271be186b 100644
--- a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/procs.dm
+++ b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/procs.dm
@@ -166,27 +166,39 @@
//returnString += "\n" Don't need spacers. Using . += "" in examine.dm does this on its own.
return returnIcon + returnString
+/datum/antagonist/bloodsucker/proc/can_gain_blood_rank(silent = TRUE, requires_blood = FALSE)
+ var/level_cost = get_level_cost()
+ var/mob/living/carbon/user = owner.current
+ if(blood_level_gain < level_cost)
+ if(!silent)
+ user.balloon_alert(user, "not enough blood thickening points!")
+ return FALSE
+ if(requires_blood && bloodsucker_blood_volume < level_cost)
+ if(!silent)
+ user.balloon_alert(user, "not enough blood!")
+ return FALSE
+ return TRUE
+
// Blood level gain is used to give Bloodsuckers more levels if they are being agressive and drinking from real, sentient people.
// The maximum blood that counts towards this
-/datum/antagonist/bloodsucker/proc/blood_level_gain()
+/datum/antagonist/bloodsucker/proc/blood_level_gain(silent = TRUE, requires_blood = FALSE)
var/level_cost = get_level_cost()
- if(blood_level_gain >= level_cost && bloodsucker_blood_volume >= level_cost) // Checks if we have drunk enough blood from the living to allow us to gain a level up as well as checking if we have enough blood to actually use on the level up
- switch(tgui_alert(owner.current, "You have drunk enough blood from the living to thicken your blood, this will cost you [level_cost] blood and give you another level", "Thicken your blood?.", list("Yes", "No"))) //asks user if they want to spend their blood on a level
- if("Yes")
- AdjustUnspentRank(1) // gives level
- blood_level_gain -= level_cost // Subtracts the cost from the pool of drunk blood
+ if(can_gain_blood_rank(silent, requires_blood)) // Checks if we have drunk enough blood from the living to allow us to gain a level up as well as checking if we have enough blood to actually use on the level up
+ var/input = tgui_alert(owner.current, "You have drunk enough blood from the living to thicken your blood, this will cost you [level_cost] blood and give you another level", "Thicken your blood?.", list("Yes", "No")) //asks user if they want to spend their blood on a level
+ if(input == "Yes")
+ AdjustUnspentRank(1) // gives level
+ blood_level_gain -= level_cost // Subtracts the cost from the pool of drunk blood
+ if(requires_blood)
AdjustBloodVolume(-level_cost) // Subtracts the cost from the bloodsucker's actual blood
- blood_level_gain_amount += 1 // Increments the variable that makes future levels more expensive
+ return TRUE
+ return FALSE
/datum/antagonist/bloodsucker/proc/get_level_cost()
- var/level_cost = (0.3 + (0.05 * blood_level_gain_amount))
- level_cost = min(level_cost, BLOOD_LEVEL_GAIN_MAX)
- level_cost = max_blood_volume * level_cost
- return level_cost
-
+ var/percentage_needed = my_clan ? my_clan.level_cost : BLOODSUCKER_LEVELUP_PERCENTAGE
+ return max_blood_volume * percentage_needed
/datum/antagonist/bloodsucker/proc/max_vassals()
- return bloodsucker_level
+ return round(bloodsucker_level * 0.5)
/datum/antagonist/bloodsucker/proc/free_vassal_slots()
return max(max_vassals() - length(vassals), 0)
@@ -350,7 +362,7 @@
if(GetUnspentRank() < 1)
blood_level_gain()
// Level ups cost 30% of your max blood volume, which scales with your rank.
- SpendRank(blood_cost = max_blood_volume * BLOODSUCKER_LEVELUP_PERCENTAGE)
+ SpendRank()
/datum/antagonist/bloodsucker/proc/on_owner_deletion(mob/living/deleted_mob)
SIGNAL_HANDLER
diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/sol.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/sol.dm
index 4efd4b900d2..6021226a3cd 100644
--- a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/sol.dm
+++ b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/sol.dm
@@ -8,6 +8,13 @@
/datum/antagonist/bloodsucker/proc/sol_rank_up(atom/source)
SIGNAL_HANDLER
+ // Bloodsuckers above BLOODSUCKER_HIGH_LEVEL must drink blood to level up.
+ if(sol_levels == 1) // just in case a bloodsucker is stuck at this level
+ // very important info here, so we use span_danger
+ to_chat(owner.current, span_danger("Sol's foul gaze no longer grants you power. You must drink blood to advance further."))
+ if(sol_levels <= 0)
+ return
+ sol_levels--
INVOKE_ASYNC(src, PROC_REF(RankUp))
///Called when Sol is near starting.
diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan.dm
index f3d9cda83a8..0227d8c7efa 100644
--- a/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan.dm
+++ b/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan.dm
@@ -30,7 +30,8 @@
/// How much stamina armor we get in frenzy
var/frenzy_stamina_mod = 0.4
var/buy_power_flags = BLOODSUCKER_CAN_BUY
- var/level_up_percentage_cost = 0.1
+ // what percentage of blood you need to spend to level up, divided by 100
+ var/level_cost = BLOODSUCKER_LEVELUP_PERCENTAGE
/datum/bloodsucker_clan/New(datum/antagonist/bloodsucker/owner_datum)
. = ..()
@@ -139,49 +140,64 @@
* Called when a Bloodsucker successfully starts spending their Rank
* args:
* bloodsuckerdatum - the antagonist datum of the Bloodsucker running this.
- * target - The Vassal (if any) we are upgrading.
* cost_rank - TRUE/FALSE on whether this will cost us a rank when we go through with it.
* blood_cost - A number saying how much it costs to rank up.
*/
-/datum/bloodsucker_clan/proc/on_spend_rank(datum/antagonist/bloodsucker/source, mob/living/carbon/target, cost_rank = TRUE, blood_cost, force)
+/datum/bloodsucker_clan/proc/on_spend_rank(datum/antagonist/bloodsucker/source, mob/living/carbon/human/target, cost_rank = TRUE, blood_cost, force)
SIGNAL_HANDLER
- INVOKE_ASYNC(src, PROC_REF(spend_rank), bloodsuckerdatum, target, cost_rank, blood_cost)
-
-/datum/bloodsucker_clan/proc/spend_rank(datum/antagonist/bloodsucker/source, mob/living/carbon/target, cost_rank = TRUE, blood_cost)
- // Purchase Power Prompt
- var/list/options = level_up_options()
- var/mob/living/carbon/human/human_user = bloodsuckerdatum.owner.current
- if(options.len < 1)
- to_chat(bloodsuckerdatum.owner.current, span_notice("You grow more ancient by the night!"))
- else
- // Give them the UI to purchase a power.
- var/choice = tgui_input_list(human_user, "You have the opportunity to grow more ancient.[blood_cost > 0 ? " Spend [round(blood_cost, 1)] blood to advance your rank" : ""]", "Your Blood Thickens...", options)
- // Prevent Bloodsuckers from closing/reopning their coffin to spam Levels.
- if(cost_rank && bloodsuckerdatum.GetUnspentRank() <= 0)
- return
- if(blood_cost && bloodsuckerdatum.GetBloodVolume() < blood_cost)
- human_user.balloon_alert(human_user, "not enough blood!")
- to_chat(human_user, span_notice("You need at the very least [blood_cost] blood to thicken your blood."))
- return
- // Did you choose a power?
- if(!choice || !options[choice])
- to_chat(human_user, span_notice("You prevent your blood from thickening just yet, but you may try again later."))
- return
- // Prevent Bloodsuckers from purchasing a power while outside of their Coffin.
- if(!bloodsuckerdatum.is_valid_coffin())
- to_chat(human_user, span_warning("You must be in your Coffin to purchase Powers."))
- return
+ INVOKE_ASYNC(src, PROC_REF(spend_rank), bloodsuckerdatum, cost_rank, blood_cost)
+/datum/bloodsucker_clan/proc/spend_rank(datum/antagonist/bloodsucker/source, cost_rank = TRUE, blood_cost, requires_coffin = TRUE)
+ var/list/options = list_available_powers()
+ if(length(options))
+ var/datum/action/cooldown/bloodsucker/choice = choose_powers(
+ "You have the opportunity to grow more ancient. [blood_cost > 0 ? " Spend [round(blood_cost, 1)] blood to advance your rank" : ""]",
+ "Your Blood Thickens...",
+ options
+ )
+ if(!is_valid_choice(choice, cost_rank, blood_cost, requires_coffin))
+ return FALSE
// Good to go - Buy Power!
- if(!purchase_choice(bloodsuckerdatum, options[choice]))
- to_chat(human_user, span_warning("Cannot buy duplicate powers!"))
- return
+ purchase_choice(source, choice)
+ level_message(initial(choice.name))
- human_user.balloon_alert(human_user, "learned [choice]!")
- to_chat(human_user, span_notice("You have learned how to use [choice]!"))
+ return finalize_spend_rank(bloodsuckerdatum, cost_rank, blood_cost)
- finalize_spend_rank(bloodsuckerdatum, cost_rank, blood_cost)
+/datum/bloodsucker_clan/proc/level_message(power_name)
+ var/mob/living/carbon/human/human_user = bloodsuckerdatum.owner.current
+ human_user.balloon_alert(human_user, "learned [power_name]!")
+ to_chat(human_user, span_notice("You have learned how to use [power_name]!"))
+
+/datum/bloodsucker_clan/proc/choose_powers(message, title, options = list())
+ var/mob/living/carbon/human/human_user = bloodsuckerdatum.owner.current
+ if(!length(options))
+ return FALSE
+
+ var/choice = tgui_input_list(human_user, message, title, options)
+ return options[choice]
+
+/datum/bloodsucker_clan/proc/is_valid_choice(datum/action/cooldown/bloodsucker/power, cost_rank, blood_cost, requires_coffin)
+ var/mob/living/carbon/human/human_user = bloodsuckerdatum.owner.current
+ if(!power)
+ return FALSE
+ if(cost_rank && bloodsuckerdatum.GetUnspentRank() <= 0)
+ return FALSE
+ if(blood_cost && bloodsuckerdatum.GetBloodVolume() < blood_cost)
+ human_user.balloon_alert(human_user, "not enough blood!")
+ to_chat(human_user, span_notice("You need at the very least [blood_cost] blood to thicken your blood."))
+ return FALSE
+ // Prevent Bloodsuckers from purchasing a power while outside of their Coffin.
+ if(requires_coffin && !istype(human_user.loc, /obj/structure/closet/crate/coffin))
+ to_chat(human_user, span_warning("You must be in your Coffin to purchase Powers."))
+ return FALSE
+ if(!(initial(power.purchase_flags) & buy_power_flags))
+ to_chat(human_user, span_notice("[initial(power.name)] is not available for purchase."))
+ return FALSE
+ if(!(buy_power_flags & CAN_BUY_OWNED) && locate(power) in bloodsuckerdatum.powers)
+ to_chat(human_user, span_notice("You already know [initial(power.name)]!"))
+ return FALSE
+ return TRUE
/datum/bloodsucker_clan/proc/finalize_spend_rank(datum/antagonist/bloodsucker/source, cost_rank = TRUE, blood_cost)
level_up_powers(source)
@@ -208,7 +224,6 @@
// Ranked up enough to get your true Reputation?
if(bloodsuckerdatum.GetRank() == BLOODSUCKER_HIGH_LEVEL)
- to_chat(bloodsuckerdatum.owner.current, span_warning("Drinking from mindless humans and blood bags is now much more less effective."))
bloodsuckerdatum.SelectReputation(am_fledgling = FALSE, forced = TRUE)
@@ -225,23 +240,20 @@
bloodsuckerdatum.owner.teach_crafting_recipe(/datum/crafting_recipe/bloodthrone)
bloodsuckerdatum.owner.teach_crafting_recipe(/datum/crafting_recipe/meatcoffin)
bloodsuckerdatum.owner.current.balloon_alert(bloodsuckerdatum.owner.current, "new recipes learned! Vassalization unlocked!")
+ return TRUE
-/datum/bloodsucker_clan/proc/level_up_options()
+/datum/bloodsucker_clan/proc/list_available_powers(already_known = bloodsuckerdatum.powers, powers_list = bloodsuckerdatum.all_bloodsucker_powers)
var/list/options = list()
- for(var/datum/action/cooldown/bloodsucker/power as anything in bloodsuckerdatum.all_bloodsucker_powers)
- if(initial(power.purchase_flags) & buy_power_flags && !(locate(power) in bloodsuckerdatum.powers))
+ for(var/datum/action/cooldown/bloodsucker/power as anything in powers_list)
+ if(initial(power.purchase_flags) & buy_power_flags && !(locate(power) in already_known))
options[initial(power.name)] = power
return options
-/datum/bloodsucker_clan/proc/level_up_powers(datum/antagonist/bloodsucker/source)
- bloodsuckerdatum.LevelUpPowers()
-
/datum/bloodsucker_clan/proc/purchase_choice(datum/antagonist/bloodsucker/source, datum/action/cooldown/bloodsucker/purchased_power)
- if(purchased_power in bloodsuckerdatum.powers)
- to_chat(source.owner.current, span_notice("You already have this power!"))
- return
return bloodsuckerdatum.BuyPower(purchased_power)
+/datum/bloodsucker_clan/proc/level_up_powers(datum/antagonist/bloodsucker/source)
+ bloodsuckerdatum.LevelUpPowers()
/**
* Called when we are trying to turn someone into a Favorite Vassal
* args:
@@ -281,19 +293,21 @@
if(!options.len)
master.balloon_alert(master, "Out of Special Vassal slots!")
- return
+ return FALSE
to_chat(master, span_notice("You can change who this Vassal is, who are they to you? This will cost [SPECIAL_VASSAL_COST] blood."))
var/vassal_response = show_radial_menu(master, servant, radial_display)
if(!vassal_response)
- return
+ return FALSE
var/datum/antagonist/vassal/vassal_type = options[vassal_response]
// let's ask if the vassal themselves actually wants to be a favorite
+#ifndef BLOODSUCKER_TESTING
servant.balloon_alert(master, "asking...")
var/vassal_permission = tgui_alert(servant, initial(vassal_type.vassal_description), "Become a Special Vassal?", list("Yes", "No"), 1 MINUTES) == "Yes"
if(!vassal_permission)
servant.balloon_alert(master, "refused!")
return FALSE
+#endif
if(QDELETED(src) || QDELETED(master) || QDELETED(servant) || !vassal_type)
return FALSE
if(bloodsuckerdatum.GetBloodVolume() < SPECIAL_VASSAL_COST)
diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_tremere.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_tremere.dm
index e8534194421..0e37bcbae67 100644
--- a/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_tremere.dm
+++ b/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_tremere.dm
@@ -7,8 +7,8 @@
clan_objective = /datum/objective/bloodsucker/tremere_power
join_icon_state = "tremere"
join_description = "You will burn if you enter the Chapel, lose all default powers, \
- but gain Blood Magic instead, powers you level up overtime."
- buy_power_flags = TREMERE_CAN_BUY
+ but gain Blood Magic instead, stronger powers you level up overtime."
+ buy_power_flags = TREMERE_CAN_BUY|CAN_BUY_OWNED
/datum/bloodsucker_clan/tremere/New(mob/living/carbon/user)
. = ..()
@@ -32,18 +32,19 @@
bloodsuckerdatum.owner.current.adjust_fire_stacks(2)
bloodsuckerdatum.owner.current.ignite_mob()
-
/datum/bloodsucker_clan/tremere/level_up_powers(datum/antagonist/bloodsucker/source)
return
-/datum/bloodsucker_clan/tremere/level_up_options(datum/antagonist/bloodsucker/source)
- var/list/options = list()
- // get current powers to upgrade
- for(var/datum/action/cooldown/bloodsucker/targeted/power as anything in bloodsuckerdatum.powers)
- if(!(power.purchase_flags & buy_power_flags))
- continue
- options[initial(power.name)] = power
- return options
+/datum/bloodsucker_clan/tremere/level_message(power_name)
+ var/mob/living/carbon/human/human_user = bloodsuckerdatum.owner.current
+ human_user.balloon_alert(human_user, "upgraded [power_name]!")
+ to_chat(human_user, span_notice("You have upgraded [power_name]!"))
+
+// redefine the default args
+/datum/bloodsucker_clan/tremere/list_available_powers(already_known, powers_list)
+ already_known = list()
+ powers_list = bloodsuckerdatum.powers
+ return ..()
/datum/bloodsucker_clan/tremere/purchase_choice(datum/antagonist/bloodsucker/source, datum/action/cooldown/bloodsucker/purchased_power)
return purchased_power.upgrade_power()
diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_ventrue.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_ventrue.dm
index 725285e6edb..9206fadc6be 100644
--- a/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_ventrue.dm
+++ b/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_ventrue.dm
@@ -1,7 +1,5 @@
///The maximum level a Ventrue Bloodsucker can be, before they have to level up their vassal instead.
#define VENTRUE_MAX_POWERS 3
-///How much it costs for a Ventrue to rank up without a spare rank to spend.
-#define BLOODSUCKER_BLOOD_RANKUP_COST (550)
/datum/bloodsucker_clan/ventrue
name = CLAN_VENTRUE
@@ -14,6 +12,7 @@
join_description = "Lose the ability to drink from mindless mobs, can't level up or gain new powers, \
instead you raise a vassal into a Bloodsucker."
blood_drink_type = BLOODSUCKER_DRINK_SNOBBY
+ level_cost = BLOODSUCKER_LEVELUP_PERCENTAGE_VENTRUE
/datum/bloodsucker_clan/ventrue/New(datum/antagonist/bloodsucker/owner_datum)
. = ..()
@@ -28,92 +27,11 @@
return TRUE
return FALSE
-/datum/bloodsucker_clan/ventrue/spend_rank(datum/antagonist/bloodsucker/source, mob/living/carbon/target, cost_rank = TRUE, blood_cost)
- if(!target && !has_enough_abilities())
- return ..()
- else if(!target)
- to_chat(bloodsuckerdatum.owner.current, span_danger("You can only have up to [VENTRUE_MAX_POWERS] powers, anything further will be ranks to spend on your Favorite Vassal through a Persuasion Rack."))
+/datum/bloodsucker_clan/ventrue/spend_rank(datum/antagonist/bloodsucker/source, cost_rank = TRUE, blood_cost)
+ if(has_enough_abilities())
+ to_chat(bloodsuckerdatum.owner.current, span_danger("You can only have up to [VENTRUE_MAX_POWERS] powers, anything further must be ranks to spend on your Favorite Vassal through a Persuasion Rack."))
return FALSE
- var/datum/antagonist/vassal/favorite/vassaldatum = target.mind.has_antag_datum(/datum/antagonist/vassal/favorite)
- var/datum/antagonist/vassal/non_favorite = target.mind.has_antag_datum(/datum/antagonist/vassal)
- if(!vassaldatum && vassaldatum.master != bloodsuckerdatum.owner.current || !IS_BLOODSUCKER(target) && !non_favorite && vassaldatum.master != bloodsuckerdatum.owner.current)
- target.balloon_alert(target, "is not a sired bloodsucker nor your favorite vassal!")
- return FALSE
- if(!vassaldatum.owner.current.mind)
- target.balloon_alert(target, "vassal must be awake!")
- return FALSE
- // Purchase Power Prompt
- var/list/options = list()
- for(var/datum/action/cooldown/bloodsucker/power as anything in bloodsuckerdatum.all_bloodsucker_powers)
- if(initial(power.purchase_flags) & VASSAL_CAN_BUY && !(locate(power) in vassaldatum.bloodsucker_powers))
- options[initial(power.name)] = power
-
- if(options.len < 1)
- to_chat(bloodsuckerdatum.owner.current, span_notice("You grow more ancient by the night!"))
- else
- // Give them the UI to purchase a power.
- var/choice = tgui_input_list(bloodsuckerdatum.owner.current, "You have the opportunity to level up your Favorite Vassal. Select a power you wish them to recieve.", "Your Blood Thickens...", options)
- // Prevent Bloodsuckers from closing/reopning their coffin to spam Levels.
- if(cost_rank && bloodsuckerdatum.GetUnspentRank() <= 0)
- return
- // Did you choose a power?
- if(!choice || !options[choice])
- to_chat(bloodsuckerdatum.owner.current, span_notice("You prevent your blood from thickening just yet, but you may try again later."))
- return
- // Prevent Bloodsuckers from closing/reopning their coffin to spam Levels.
- if((locate(options[choice]) in vassaldatum.bloodsucker_powers))
- to_chat(bloodsuckerdatum.owner.current, span_notice("You prevent your blood from thickening just yet, but you may try again later."))
- return
-
- // Good to go - Buy Power!
- var/datum/action/cooldown/bloodsucker/purchased_power = options[choice]
- if(!vassaldatum.BuyPower(purchased_power, vassaldatum.bloodsucker_powers))
- bloodsuckerdatum.owner.current.balloon_alert(bloodsuckerdatum.owner.current, "[target] already knows [choice]!")
- return
- bloodsuckerdatum.owner.current.balloon_alert(bloodsuckerdatum.owner.current, "taught [choice]!")
- to_chat(bloodsuckerdatum.owner.current, span_notice("You taught [target] how to use [choice]!"))
- target.balloon_alert(target, "learned [choice]!")
- to_chat(target, span_notice("Your master taught you how to use [choice]!"))
- if(IS_VASSAL(target) && IS_BLOODSUCKER(target))
- finish_spend_rank(vassaldatum, cost_rank, blood_cost)
- return
- vassaldatum.vassal_level++
- finish_spend_rank(vassaldatum, cost_rank, blood_cost)
- var/traits_to_add = list()
- switch(vassaldatum.vassal_level)
- if(1)
- traits_to_add = list(TRAIT_COLDBLOODED, TRAIT_NOBREATH, TRAIT_AGEUSIA)
- to_chat(target, span_notice("Your blood begins to feel cold, and as a mote of ash lands upon your tongue, you stop breathing..."))
- if(2)
- traits_to_add = list(TRAIT_NOCRITDAMAGE, TRAIT_NOSOFTCRIT, TRAIT_SLEEPIMMUNE, TRAIT_VIRUSIMMUNE)
- to_chat(target, span_notice("You feel your Master's blood reinforce you, strengthening you up."))
- if(ishuman(target))
- var/mob/living/carbon/human/human_target = target
- human_target.skin_tone = "albino"
- if(3)
- traits_to_add = list(TRAIT_NOHARDCRIT, TRAIT_HARDLY_WOUNDED)
- to_chat(target, span_notice("You feel yourself able to take cuts and stabbings like it's nothing."))
- if(4 to INFINITY)
- var/datum/antagonist/bloodsucker/bloodsucker_target = IS_BLOODSUCKER(target)
- if(!bloodsucker_target)
- to_chat(target, span_notice("You feel your heart stop pumping for the last time as you begin to thirst for blood, you feel... dead."))
- // Unfavorites you, so the ventrue isn't stuck with you forever
- vassaldatum.silent = TRUE
- var/powers_to_transfer = list()
- // Get rid of the favorite datum and replace with a normal vassal datum
- if(target.mind.has_antag_datum(/datum/antagonist/vassal/favorite))
- for(var/datum/power as anything in vassaldatum.bloodsucker_powers)
- powers_to_transfer += power.type
- target.mind.remove_antag_datum(/datum/antagonist/vassal/favorite)
- var/datum/antagonist/bloodsucker/vamp = new()
- vamp.ventrue_sired = bloodsuckerdatum
- bloodsucker_target = target.mind.add_antag_datum(vamp)
- bloodsucker_target.BuyPowers(powers_to_transfer)
- // Check for the recuperate power and remove it if they have it
- bloodsuckerdatum.owner.current.add_mood_event("madevamp", /datum/mood_event/madevamp)
- if(vassaldatum && QDELETED(vassaldatum) && length(traits_to_add))
- target.add_traits(traits_to_add, VASSAL_TRAIT)
- vassaldatum.traits += traits_to_add
+ . = ..()
/datum/bloodsucker_clan/ventrue/proc/finish_spend_rank(datum/antagonist/vassal/vassaldatum, cost_rank, blood_cost)
finalize_spend_rank(bloodsuckerdatum, cost_rank, blood_cost)
@@ -125,26 +43,96 @@
return TRUE
if(!istype(vassaldatum))
return FALSE
- if(!bloodsuckerdatum.GetUnspentRank() <= 0)
- bloodsuckerdatum.SpendRank(vassaldatum.owner.current)
- return TRUE
- if(bloodsuckerdatum.GetBloodVolume() >= BLOODSUCKER_BLOOD_RANKUP_COST)
- // We don't have any ranks to spare? Let them upgrade... with enough Blood.
- to_chat(bloodsuckerdatum.owner.current, span_warning("Do you wish to spend [BLOODSUCKER_BLOOD_RANKUP_COST] Blood to Rank [vassaldatum.owner.current] up?"))
- var/static/list/rank_options = list(
- "Yes" = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_yes"),
- "No" = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_no"),
- )
- var/rank_response = show_radial_menu(bloodsuckerdatum.owner.current, vassaldatum.owner.current, rank_options, radius = 36, require_near = TRUE)
- if(rank_response == "Yes")
- bloodsuckerdatum.SpendRank(vassaldatum.owner.current, cost_rank = FALSE, blood_cost = BLOODSUCKER_BLOOD_RANKUP_COST)
- return TRUE
- to_chat(bloodsuckerdatum.owner.current, span_danger("You don't have any levels or enough Blood to Rank [vassaldatum.owner.current] up with."))
+ to_chat(bloodsuckerdatum.owner.current, span_warning("Do you wish to Rank [vassaldatum.owner.current] up?"))
+ to_chat(bloodsuckerdatum.owner.current, span_warning("This will use [bloodsuckerdatum.GetUnspentRank() >= 1 ? "a unspent Rank" : "[bloodsuckerdatum.get_level_cost()] Blood Thickening Points"]!"))
+
+ var/static/list/rank_options = list(
+ "Yes" = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_yes"),
+ "No" = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_no"),
+ )
+ var/rank_response = show_radial_menu(bloodsuckerdatum.owner.current, vassaldatum.owner.current, rank_options, radius = 36, require_near = TRUE)
+ if(rank_response == "Yes")
+ if(!bloodsuckerdatum.GetUnspentRank() >= 1 && !source.blood_level_gain(FALSE))
+ to_chat(bloodsuckerdatum.owner.current, span_danger("You don't have any levels or enough blood thickening points to Rank [vassaldatum.owner.current] up with."))
+ return FALSE
+ return vassal_level(vassaldatum)
+ return FALSE
+
+/datum/bloodsucker_clan/ventrue/proc/vassal_level(datum/antagonist/vassal/favorite/vassaldatum)
+ var/list/options = list_available_powers(vassaldatum.bloodsucker_powers)
+ var/mob/living/carbon/human/target = vassaldatum.owner.current
+ var/datum/action/cooldown/bloodsucker/choice = choose_powers(
+ "You have the opportunity to level up your Favorite Vassal. Select a power you wish them to recieve.",
+ "A wise master's hand is neccesary",
+ options
+ )
+ if(!choice)
+ return FALSE
+ var/power_name = initial(choice.name)
+ if(!vassaldatum.BuyPower(choice, vassaldatum.bloodsucker_powers))
+ bloodsuckerdatum.owner.current.balloon_alert(bloodsuckerdatum.owner.current, "[target] already knows [power_name]!")
+ return FALSE
+ bloodsuckerdatum.owner.current.balloon_alert(bloodsuckerdatum.owner.current, "taught [power_name]!")
+ to_chat(bloodsuckerdatum.owner.current, span_notice("You taught [target] how to use [power_name]!"))
+
+ target.balloon_alert(target, "learned [power_name]!")
+ to_chat(target, span_notice("Your master taught you how to use [power_name]!"))
+
+ vassaldatum.vassal_level++
+ finish_spend_rank(vassaldatum, TRUE, FALSE)
+ bloodsuckerify(vassaldatum)
return TRUE
+/datum/bloodsucker_clan/ventrue/proc/bloodsuckerify(datum/antagonist/vassal/favorite/vassaldatum)
+ var/mob/living/carbon/human/target = vassaldatum.owner.current
+ var/stage = vassaldatum.vassal_level
+ var/list/traits_possible = list(
+ list(TRAIT_COLDBLOODED, TRAIT_NOBREATH, TRAIT_AGEUSIA),
+ list(TRAIT_NOCRITDAMAGE, TRAIT_NOSOFTCRIT, TRAIT_SLEEPIMMUNE, TRAIT_VIRUSIMMUNE),
+ list(TRAIT_NOHARDCRIT, TRAIT_HARDLY_WOUNDED)
+ )
+ var/traits_to_add = length(traits_possible) >= stage ? traits_possible[stage] : list()
+ switch(stage)
+ if(1)
+ to_chat(target, span_notice("Your blood begins to feel cold, and as a mote of ash lands upon your tongue, you stop breathing..."))
+
+ if(2)
+ to_chat(target, span_notice("You feel your Master's blood reinforce you, strengthening you up."))
+ if(ishuman(target))
+ var/mob/living/carbon/human/human_target = target
+ human_target.skin_tone = "albino"
+
+ if(3)
+ to_chat(target, span_notice("You feel yourself able to take cuts and stabbings like it's nothing."))
+
+ if(4 to INFINITY)
+ var/datum/antagonist/bloodsucker/bloodsucker_target = IS_BLOODSUCKER(target)
+ if(!bloodsucker_target)
+ to_chat(target, span_notice("You feel your heart stop pumping for the last time as you begin to thirst for blood, you feel... dead."))
+ // Unfavorites you, so the ventrue isn't stuck with you forever
+ var/powers_to_transfer = list()
+ // Get rid of the favorite datum and replace with a normal vassal datum
+ if(vassaldatum)
+ vassaldatum.silent = TRUE
+ for(var/datum/power as anything in vassaldatum.bloodsucker_powers)
+ powers_to_transfer += power.type
+ target.mind.remove_antag_datum(/datum/antagonist/vassal/favorite)
+ else
+ target.remove_traits(flatten_list(traits_to_add), VASSAL_TRAIT)
+
+ var/datum/antagonist/bloodsucker/vamp = new()
+ vamp.ventrue_sired = bloodsuckerdatum
+ bloodsucker_target = target.mind.add_antag_datum(vamp)
+ bloodsucker_target.BuyPowers(powers_to_transfer)
+ // Check for the recuperate power and remove it if they have it
+ bloodsuckerdatum.owner.current.add_mood_event("madevamp", /datum/mood_event/madevamp)
+
+ if(vassaldatum && QDELETED(vassaldatum) && length(traits_to_add))
+ target.add_traits(traits_to_add, VASSAL_TRAIT)
+ vassaldatum.traits += traits_to_add
+
/datum/bloodsucker_clan/ventrue/favorite_vassal_gain(datum/antagonist/bloodsucker/source, datum/antagonist/vassal/vassaldatum)
to_chat(source.owner.current, span_announce("* Bloodsucker Tip: You can now upgrade your Favorite Vassal by buckling them onto a persuasion rack!"))
vassaldatum.BuyPower(/datum/action/cooldown/bloodsucker/distress)
-#undef BLOODSUCKER_BLOOD_RANKUP_COST
#undef VENTRUE_MAX_POWERS
diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/feed.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/feed.dm
index 426f25eefd4..a82e25f1994 100644
--- a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/feed.dm
+++ b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/feed.dm
@@ -33,6 +33,8 @@
var/silent_feed = TRUE
///Have we notified you already that you are at maximum blood?
var/notified_overfeeding = FALSE
+ ///assoc list of weakrefs to targets and how much blood we've taken from them.
+ var/list/targets_and_blood = list()
/datum/action/cooldown/bloodsucker/feed/get_power_explanation_extended()
. = list()
@@ -206,7 +208,11 @@
feed_strength_mult = 1
else
feed_strength_mult = 0.3
- blood_taken += bloodsuckerdatum_power.handle_feeding(feed_target, feed_strength_mult, level_current)
+ var/already_drunk = targets_and_blood[target_ref] || 0
+ var/blood_eaten = bloodsuckerdatum_power.handle_feeding(feed_target, feed_strength_mult, level_current, already_drunk)
+ blood_taken += blood_eaten
+ targets_and_blood[target_ref] += blood_eaten
+ decrement_blood_drunk(blood_eaten * 0.5)
if(feed_strength_mult > 5 && feed_target.stat < DEAD)
user.add_mood_event("drankblood", /datum/mood_event/drankblood)
@@ -244,7 +250,7 @@
if(owner.pulling && isliving(owner.pulling))
if(!can_feed_from(owner.pulling, give_warnings = TRUE))
return FALSE
- target_ref = WEAKREF(owner.pulling)
+ set_target(owner.pulling)
return TRUE
var/list/close_living_mobs = list()
@@ -259,16 +265,30 @@
//Check living first
for(var/mob/living/suckers in close_living_mobs)
if(can_feed_from(suckers))
- target_ref = WEAKREF(suckers)
+ set_target(suckers)
return TRUE
//If not, check dead
for(var/mob/living/suckers in close_dead_mobs)
if(can_feed_from(suckers))
- target_ref = WEAKREF(suckers)
+ set_target(suckers)
return TRUE
//No one to suck blood from.
return FALSE
+// this lets us compare and access things by weakrefs, if we use the actual same weakref instance in the assoc list
+/datum/action/cooldown/bloodsucker/feed/proc/set_target(mob/living/target)
+ if(!length(targets_and_blood))
+ target_ref = WEAKREF(target)
+ return
+
+ for(var/datum/weakref/weakref as anything in targets_and_blood)
+ var/mob/living/old_target = weakref.resolve()
+ if(old_target == target)
+ target_ref = weakref
+ break
+ if(!target_ref)
+ target_ref = WEAKREF(target)
+
/datum/action/cooldown/bloodsucker/feed/proc/can_feed_from(mob/living/target, give_warnings = FALSE)
if(istype(target, /mob/living/basic/mouse))
if(bloodsuckerdatum_power.my_clan && bloodsuckerdatum_power.my_clan.blood_drink_type == BLOODSUCKER_DRINK_SNOBBY)
@@ -315,5 +335,13 @@
COOLDOWN_START(src, feed_movement_notify_cooldown, 3 SECONDS)
owner.balloon_alert(owner, "you cannot move while feeding! Click the power to stop.")
+/datum/action/cooldown/bloodsucker/feed/proc/decrement_blood_drunk(amount = 0)
+ for(var/datum/weakref/weakref as anything in targets_and_blood)
+ if(weakref == target_ref)
+ continue
+ targets_and_blood[weakref] = max(0, targets_and_blood[weakref] - amount)
+ if(targets_and_blood[weakref] <= 0)
+ targets_and_blood -= weakref
+
#undef FEED_NOTICE_RANGE
#undef FEED_DEFAULT_TIMER
diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/tremere/_powers_tremere.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/tremere/_powers_tremere.dm
index f6f8a31c74b..8054842a7f1 100644
--- a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/tremere/_powers_tremere.dm
+++ b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/tremere/_powers_tremere.dm
@@ -17,7 +17,6 @@
level_current = 0
// Re-defining these as we want total control over them
power_flags = BP_AM_STATIC_COOLDOWN
- purchase_flags = TREMERE_CAN_BUY
// Targeted stuff
unset_after_click = FALSE
diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/tremere/auspex.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/tremere/auspex.dm
index 47b1718a42d..694fb8dba1c 100644
--- a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/tremere/auspex.dm
+++ b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/tremere/auspex.dm
@@ -17,6 +17,7 @@
level_current = 1
button_icon_state = "power_auspex"
bloodsucker_check_flags = BP_CANT_USE_IN_TORPOR
+ purchase_flags = TREMERE_CAN_BUY
bloodcost = 10
constant_bloodcost = 1
cooldown_time = 12 SECONDS
diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/tremere/thaumaturgy.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/tremere/thaumaturgy.dm
index 06856ff3290..e416f24d84f 100644
--- a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/tremere/thaumaturgy.dm
+++ b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/tremere/thaumaturgy.dm
@@ -22,6 +22,7 @@
level_current = 1
button_icon_state = "power_thaumaturgy"
check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED
+ purchase_flags = TREMERE_CAN_BUY
// custom cooldown handling based on charges
power_flags = BP_AM_STATIC_COOLDOWN
bloodcost = 5
diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/structures/crypt.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/structures/crypt.dm
index 55108038a72..f48da7d99ce 100644
--- a/modular_zubbers/code/modules/antagonists/bloodsucker/structures/crypt.dm
+++ b/modular_zubbers/code/modules/antagonists/bloodsucker/structures/crypt.dm
@@ -423,7 +423,7 @@
all_implants.removed(target, silent = TRUE)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
+// todo, make this steal blood into a internal reservoir from nearby non-vassals/bloodsuckers
/obj/structure/bloodsucker/candelabrum
name = "candelabrum"
desc = "It burns slowly, but doesn't radiate any heat."
@@ -432,6 +432,7 @@
light_color = "#66FFFF"//LIGHT_COLOR_BLUEGREEN // lighting.dm
light_power = 3
light_range = 0 // to 2
+ max_integrity = 100
density = FALSE
can_buckle = TRUE
anchored = FALSE
@@ -462,6 +463,8 @@
. = ..()
set_anchored(FALSE)
density = FALSE
+ if(lit)
+ toggle()
/obj/structure/bloodsucker/candelabrum/attack_hand(mob/living/user, list/modifiers)
. = ..()
diff --git a/tgui/packages/tgui/interfaces/AntagInfoBloodsucker.tsx b/tgui/packages/tgui/interfaces/AntagInfoBloodsucker.tsx
index 707215a8ca6..c6b2c112b14 100644
--- a/tgui/packages/tgui/interfaces/AntagInfoBloodsucker.tsx
+++ b/tgui/packages/tgui/interfaces/AntagInfoBloodsucker.tsx
@@ -148,6 +148,10 @@ const BloodsuckerIntro = () => {
Ensure to read the descriptions of each ability in the Clan &
Powers tab, you may learn something new!
+
+ After a certain level, Sol will no longer grant you levels,
+ instead, you will need to feed on the blood of others to gain
+ levels.