From c8f3631433263ba6365dbb75d842ce33b2945ebd Mon Sep 17 00:00:00 2001 From: Arturlang <24881678+Arturlang@users.noreply.github.com> Date: Tue, 1 Oct 2024 08:32:40 +0300 Subject: [PATCH] Bloodsucker leveling rework (#1994) ## 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 ## 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. ## Proof Of Testing ## Changelog :cl: 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. /:cl: --- .../code/__DEFINES/bloodsucker_defines.dm | 7 +- .../bloodsucker/bloodsuckers/bloodsucker.dm | 5 +- .../bloodsucker/bloodsuckers/integration.dm | 3 - .../bloodsucker/bloodsuckers/life.dm | 22 +- .../bloodsucker/bloodsuckers/procs.dm | 40 ++-- .../bloodsucker/bloodsuckers/sol.dm | 7 + .../antagonists/bloodsucker/clans/clan.dm | 108 +++++----- .../bloodsucker/clans/clan_tremere.dm | 23 ++- .../bloodsucker/clans/clan_ventrue.dm | 194 ++++++++---------- .../antagonists/bloodsucker/powers/feed.dm | 36 +++- .../powers/tremere/_powers_tremere.dm | 1 - .../bloodsucker/powers/tremere/auspex.dm | 1 + .../bloodsucker/powers/tremere/thaumaturgy.dm | 1 + .../bloodsucker/structures/crypt.dm | 5 +- .../tgui/interfaces/AntagInfoBloodsucker.tsx | 4 + 15 files changed, 258 insertions(+), 199 deletions(-) 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.