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.