Bloodsucker leveling rework (#1994)

<!-- Write **BELOW** The Headers and **ABOVE** The comments else it may
not be viewable. -->
<!-- You can view Contributing.MD for a detailed description of the pull
request process. -->

## About The Pull Request
Bloodsuckers now no longer level up from sol past level 4, requiring you
to drink sentient blood to progress in levels. Also moves ventrue to
this same system.
Blood thickening points are obtained 1-1 on blood drunk when you are
drinking from players, these points are used for leveling, and the act
of using these points for leveling does not use blood, nor does leveling
in general.
Current blood cost is 40% of total blood cap, with 30% for ventrue.
This calculates based on the total blood volume a bloodsucker can hold,
which at level 1 is 700, thus requiring 280 blood for a level up. At
level 4 this is a cost of 440 blood. I'm tempted to increase the base
requirement to 50%, but I'd like to hear on feedback on that
Also fixes some exploits with candelabrums, 
fixes https://github.com/Bubberstation/Bubberstation/issues/1700
<!-- Describe The Pull Request. Please be sure every change is
documented or this can delay review and even discourage maintainers from
merging your PR! -->

<!-- Please make sure to actually test your PRs. If you have not tested
your PR mention it. -->

## Why It's Good For The Game
Nobody likes bloodsuckers simply not having to interact with crew to
constantly gain in strength, and just hiding and hiding in coffins to
level up, this should heavily encourage bloodsuckers to drink blood
(their main gimmikc) to gain levels, or you will simply be unable to
level up.
<!-- Argue for the merits of your changes and how they benefit the game,
especially if they are controversial and/or far reaching. If you can't
actually explain WHY what you are doing will improve the game, then it
probably isn't good for the game in the first place. -->

## Proof Of Testing

<!-- Compile and run your code locally. Make sure it works. This is the
place to show off your changes! We are not responsible for testing your
features. -->

## Changelog

<!-- If your PR modifies aspects of the game that can be concretely
observed by players or admins you should add a changelog. If your change
does NOT meet this description, remove this section. Be sure to properly
mark your PRs to prevent unnecessary GBP loss. You can read up on GBP
and its effects on PRs in the tgstation guides for contributors. Please
note that maintainers freely reserve the right to remove and add tags
should they deem it appropriate. You can attempt to finagle the system
all you want, but it's best to shoot for clear communication right off
the bat. -->

🆑
balance: The candelabrum is no longer as tough
fix: candelabrum now turns off when unsecured
balance: Bloodsucker no longer level from sol when they reach level 4
balance: Ventrue now can only use blood drunk from sentients to level up
vassals if they wish to level themselves up.
refactor: Refactors how ventrue vassalizing is handled, no longer
copying most of level up code for it.
/🆑

<!-- Both 🆑's are required for the changelog to work! You can put
your name to the right of the first 🆑 if you want to overwrite your
GitHub username as author ingame. -->
<!-- You can use multiple of the same prefix (they're only used for the
icon ingame) and delete the unneeded ones. Despite some of the tags,
changelogs should generally represent how a player might be affected by
the changes rather than a summary of the PR's contents. -->

<!-- By opening a pull request. You have read and understood the
repository rules located on the main README.md on this project. -->
This commit is contained in:
Arturlang
2024-09-30 23:32:40 -06:00
committed by GitHub
parent e0dc018b1d
commit c8f3631433
15 changed files with 258 additions and 199 deletions
@@ -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)
@@ -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
@@ -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))
@@ -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
/**
@@ -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
@@ -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.
@@ -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)
@@ -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()
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
. = ..()
@@ -148,6 +148,10 @@ const BloodsuckerIntro = () => {
<br />
Ensure to read the descriptions of each ability in the Clan &
Powers tab, you may learn something new!
<br />
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.
</Stack.Item>
<Stack.Item>
<Section textAlign="center" textColor="red" fontSize="20px">